rustc_codegen_gcc:用 libgccjit 生成并查看 GIMPLE 中间表示的完整实践
GIMPLE 是 GCC 编译器树级别(tree-level)的中间表示,也是 rustc_codegen_gcc 这条“Rust 前端 → GCC 后端”编译链路中的第一个关键 IR。本文基于仓库文档 gimple.md 的完整操作流程,讲解如何通过 libgccjit 的 C API 独立生成并查看 GIMPLE 输出,并结合 base.rs 中的源码说明这套机制在 rustc_codegen_gcc 实际编译 Rust 程序时是如何被触发的,读完你可以掌握“为一段代码打印 GCC 视角的中间表示”的完整方法,并能将同样的思路应用到 rustc 自身的编译调试中。
背景:GIMPLE 在 GCC 编译管线中的位置
GCC 的官方内部手册(GCC Internals Manual 的 GIMPLE 章节)对 GIMPLE 的定义是:它是一种非常简单的三地址形式(three-address form)的中间表示,几乎只包含赋值、函数调用和条件跳转,便于在其上实现各种树级优化 pass。
在 rustc_codegen_gcc 仓库中,GIMPLE 并不是抽象概念,而是有直接的代码证据贯穿编译管线:
- 从 debugging-libgccjit.md 中记录的典型崩溃堆栈可以看到,错误发生在
during RTL pass: expand,调用链为expand_gimple_stmt_1 → expand_gimple_stmt → expand_gimple_basic_block(GCC 的cfgexpand.c)。这正是 GIMPLE 被“展开”(expand)为 RTL(Register Transfer Language)的过程——说明 libgccjit 在树级 pass 全部跑完之后,才会进入 RTL 阶段; - builder.rs 中
frem的实现里留有一条 FIXME 注释,记录的同样是 GIMPLE 展开阶段(expand_divmod/expand_gimple_stmt)的报错,说明这个后端在构造浮点取余指令时与 GIMPLE→RTL 展开路径直接交互。
因此,能够单独打印 GIMPLE,就等于能在“Rust 代码 → GCC 前端 → 优化 → RTL → 机器码”这条链路的最上游观察结果,是排查后端代码生成问题的第一手工具。
准备工作:从 libgccjit 官方测试用例出发
原文档的做法是复用 GCC 源码树(rustc_codegen_gcc 使用的 GCC 源码仓库,即仓库根下的 gcc/ 目录,注意它不是本仓库的一部分,而是按 Readme.md 要求 clone 并构建的 GCC fork)自带的 JIT 测试用例 gcc/gcc/testsuite/jit.dg/test-const-attribute.c,将其内容复制为 local.c,并删掉与测试框架(DejaGnu harness)相关的部分。需要删除的内容如下:
- /* { dg-do compile { target x86_64-*-* } } */
...
- /* We don't want set_options() in harness.h to set -O3 to see that the const
- attribute affects the optimizations. */
- #define TEST_ESCHEWS_SET_OPTIONS
- static void set_options (gcc_jit_context *ctxt, const char *argv0)
- {
- // Set "-O3".
- gcc_jit_context_set_int_option(ctxt, GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL, 3);
- }
-
- #define TEST_COMPILING_TO_FILE
- #define OUTPUT_KIND GCC_JIT_OUTPUT_KIND_ASSEMBLER
- #define OUTPUT_FILENAME "output-of-test-const-attribute.c.s"
- #include "harness.h"
...
- /* { dg-final { jit-verify-output-file-was-created "" } } */
- /* Check that the loop was optimized away */
- /* { dg-final { jit-verify-assembler-output-not "jne" } } */
删除后保留的 create_code 函数会向 libgccjit 上下文注册一个带 __attribute__((const)) 的 C 函数,其中包含一个循环和对外部函数 foo 的调用。
核心操作:用 libgccjit C API 打印初始 GIMPLE
在 local.c 中添加 main 函数,除调用 create_code 外,关键是通过 libgccjit 的上下文选项打开 GIMPLE dump:
int main() {
gcc_jit_context *ctxt = gcc_jit_context_acquire();
// To set `-O3`, update it depending on your needs.
gcc_jit_context_set_int_option(ctxt, GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL, 3);
// Very important option to generate the gimple format.
gcc_jit_context_set_bool_option(ctxt, GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE, 1);
create_code(ctxt, NULL);
gcc_jit_context_compile(ctxt);
// If you want to compile to assembly (or any other format) directly, you can
// use the following call instead:
// gcc_jit_context_compile_to_file(ctxt, GCC_JIT_OUTPUT_KIND_ASSEMBLER, "out.s");
return 0;
}
其中三个要点:
gcc_jit_context_set_int_option(ctxt, GCC_JIT_INT_OPTION_OPTIMIZATION_LEVEL, 3)设置优化级别为-O3,可按需调整;gcc_jit_context_set_bool_option(ctxt, GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE, 1)是原文档强调的“非常关键的选项”,它让 libgccjit 在编译开始时把初始 GIMPLE 直接打印出来;gcc_jit_context_compile(ctxt)触发完整编译流程;如果只是想直接得到汇编(或其他格式),可以改用注释中的gcc_jit_context_compile_to_file(ctxt, GCC_JIT_OUTPUT_KIND_ASSEMBLER, "out.s")。
前提条件与 Readme.md 一致:libgccjit 必须是包含 rustc_codegen_gcc 所需补丁的构建版本(默认配置下 CI 会下载已打好补丁的 libgccjit,也可自行构建 GCC fork)。下面命令假设 GCC 源码树在 gcc/、构建产物在 gcc-build/。
编译示例程序:
gcc local.c -I `pwd`/gcc/gcc/jit/ -L `pwd`/gcc-build/gcc -lgccjit -o out
运行它:
LD_LIBRARY_PATH=`pwd`/gcc-build/gcc LIBRARY_PATH=`pwd`/gcc-build/gcc ./out
LD_LIBRARY_PATH 用于动态链接器找到 libgccjit.so,LIBRARY_PATH 是 GCC 驱动在内部链接时使用的运行时库路径。
预期的 GIMPLE 输出及其读法
程序应打印类似如下内容:
__attribute__((const))
int xxx ()
{
int D.3394;
int sum;
int x;
<D.3377>:
x = 45;
sum = 0;
goto loop_cond;
loop_cond:
x = x >> 1;
if (x != 0) goto after_loop; else goto loop_body;
loop_body:
_1 = foo (x);
_2 = _1 * 2;
x = x + _2;
goto loop_cond;
after_loop:
D.3394 = sum;
return D.3394;
}
结合 GIMPLE 的三地址形式,这段输出可以读出几个典型特征:
- 基本块以标签(
loop_cond:、loop_body:、after_loop:)划分,块内只有顺序语句,控制流全部集中在块尾的goto; _1、_2、D.3377、D.3394这类是编译器内部生成的临时变量(SSA 形式的暂存名):_N是 SSA 命名,D.NNNN是由优化 pass 引入的临时变量;foo (x)的调用被展开为“调用结果存入_1,再做乘加”,这正是三地址形式把复合表达式拆成单步操作的结果;- 函数顶部的
__attribute__((const))表明属性信息已透传到 GIMPLE,const属性会影响后续 pass 对该函数的优化假设(原始测试用例正是用这一点来验证优化的)。
替代方案:用 -fdump-tree-gimple 把 GIMPLE 写入文件
原文档给出了第二种生成 GIMPLE 的方式,也是 GCC 命令行编译时的标准做法。只需把
gcc_jit_context_set_bool_option(ctxt, GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE, 1);
替换为:
gcc_jit_context_add_command_line_option(ctxt, "-fdump-tree-gimple");
两种方法也可以同时使用。gcc_jit_context_add_command_line_option 把选项注入到内部 GCC 驱动,因此等价于给 GCC 传递命令行参数;dump 文件会被写到磁盘而不是标准输出。
编译方式与前面相同,唯一的区别在于:运行前建议先清理 libgccjit 的临时目录,方便定位本次运行产生的文件:
rm -rf /tmp/libgccjit-*
执行完成后,你会得到一个形如 /tmp/libgccjit-9OFqkD/fake.c.006t.gimple 的文件,其中保存着 GIMPLE 表示。文件名规律是 /tmp/libgccjit-<随机后缀>/<输入名>.<pass 编号><pass 标识>.gimple,006t 中的数字与字母标识对应的 pass 顺序和类型。
同一机制在 rustc_codegen_gcc 源码中的落点
上述 libgccjit 选项并不是孤立知识,rustc_codegen_gcc 在真正的 Rust 编译流程中通过环境变量暴露了同一套 dump 能力。在 base.rs 的代码生成入口中,可以看到一组环境变量的解析逻辑,其中与 GIMPLE 直接相关的是:
if env::var("CG_GCCJIT_DUMP_GIMPLE").as_deref() == Ok("1") {
context.set_dump_initial_gimple(true);
}
这与文档中手写的 gcc_jit_context_set_bool_option(..., GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE, 1) 是同一个选项的 Rust 绑定封装。同一处代码还定义了其它 dump 开关,对应 Readme.md 的“Environment variables”一节:
| 环境变量 | 底层调用/效果 |
|---|---|
CG_GCCJIT_DUMP_RTL |
add_command_line_option("-fdump-rtl-vregs"),dump 虚拟寄存器 RTL |
CG_GCCJIT_DUMP_RTL_ALL |
-fdump-rtl-all,dump 全部 RTL pass |
CG_GCCJIT_DUMP_TREE_ALL |
-fdump-tree-all-eh,dump 全部树级(GIMPLE)pass |
CG_GCCJIT_DUMP_IPA_ALL |
-fdump-ipa-all-eh,dump 全部 IPA pass |
CG_GCCJIT_DUMP_GIMPLE |
set_dump_initial_gimple(true),即本文档的核心选项 |
CG_GCCJIT_DUMP_CODE |
set_dump_code_on_compile(true),dump 最终生成的代码 |
CG_GCCJIT_DUMP_EVERYTHING |
set_dump_everything(true),一次性 dump 所有 IR 阶段 |
CG_GCCJIT_KEEP_INTERMEDIATES |
set_keep_intermediates(true),保留中间文件 |
CG_GCCJIT_VERBOSE |
add_driver_option("-v"),开启 GCC 驱动的详细输出 |
从源码结构看,这套机制的意义在于:调试 Rust 程序经 rustc_codegen_gcc 编译时,无需改动任何代码,只要在构建命令前加 CG_GCCJIT_DUMP_GIMPLE=1 即可看到 libgccjit 接收到的初始 GIMPLE,与本文档中独立 C 示例程序打印的内容处于同一层级。
此外,tips.md 还补充了一条通用技巧:rustc 的 -Cllvm-args 参数被 rustc_codegen_gcc 复用来向 GCC 后端直接透传参数,因此要传递 -fdump-tree-gimple(本文档的替代方案)或任意 -f 开头的 GCC 选项,可以这样构建:
CG_RUSTFLAGS="-Cllvm-args=-fdump-tree-gimple" ../y.sh cargo build
这与 gcc_jit_context_add_command_line_option 是等价的另一条入口。而 tips.md 中“How to generate GIMPLE”一节也明确指引读者参考 gimple.md,说明该文档是后端调试文档体系中的正式组成部分。
小结与适用边界
本文复现了 gimple.md 的完整工作流:从 GCC 源码树的 JIT 测试用例裁剪出 local.c,通过 GCC_JIT_BOOL_OPTION_DUMP_INITIAL_GIMPLE 在标准输出打印初始 GIMPLE,或通过 gcc_jit_context_add_command_line_option(ctxt, "-fdump-tree-gimple") 将 GIMPLE 落盘到 /tmp/libgccjit-* 目录,并结合 base.rs 与 tips.md 说明了这些选项在 rustc_codegen_gcc 中的环境变量与命令行透传入口。
适用前提需要注意:
- 必须使用带 rustc_codegen_gcc 补丁的 libgccjit(默认 CI 下载版,或自行构建的 GCC fork),普通发行版的 libgccjit 不一定包含所需补丁;
- 文档中的编译/运行命令依赖
gcc/(源码)与gcc-build/(构建目录)这两个相对路径约定,实际路径以你的 GCC 构建目录为准; - 示例测试用例的目标限定为
x86_64-*-*(原测试的 DejaGnu target 标注),其他架构下该用例本身可能不适用,但 GIMPLE 生成方法不受架构影响; - 由于 GIMPLE 位于 RTL 展开之前,用它排查问题最直接的用途是确认“交给 GCC 前端的 C 级代码长什么样”,而 RTL/最终机器码层面的问题应配合
CG_GCCJIT_DUMP_RTL、CG_GCCJIT_DUMP_CODE等更高阶段的 dump 选项使用。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00