rustc_codegen_gcc:用 libgccjit 生成并查看 GIMPLE 中间表示的完整实践
2026/9/6 22:11:44 网站建设 项目流程

rustc_codegen_gcc:用 libgccjit 生成并查看 GIMPLE 中间表示的完整实践

【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust

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.soLIBRARY_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_2D.3377D.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 标识>.gimple006t中的数字与字母标识对应的 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_RTLadd_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_GIMPLEset_dump_initial_gimple(true),即本文档的核心选项
CG_GCCJIT_DUMP_CODEset_dump_code_on_compile(true),dump 最终生成的代码
CG_GCCJIT_DUMP_EVERYTHINGset_dump_everything(true),一次性 dump 所有 IR 阶段
CG_GCCJIT_KEEP_INTERMEDIATESset_keep_intermediates(true),保留中间文件
CG_GCCJIT_VERBOSEadd_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_RTLCG_GCCJIT_DUMP_CODE等更高阶段的 dump 选项使用。

【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询