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

rustc_codegen_gcc:用 libgccjit 生成并查看 GIMPLE 中间表示的完整实践 rustc_codegen_gcc用 libgccjit 生成并查看 GIMPLE 中间表示的完整实践【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rustGIMPLE 是 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_blockGCC 的cfgexpand.c。这正是 GIMPLE 被“展开”expand为 RTLRegister 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 dont 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 dumpint 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_PATHpwd/gcc-build/gcc LIBRARY_PATHpwd/gcc-build/gcc ./outLD_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、_2、D.3377、D.3394这类是编译器内部生成的临时变量SSA 形式的暂存名_N是 SSA 命名D.NNNN是由优化 pass 引入的临时变量foo (x)的调用被展开为“调用结果存入_1再做乘加”这正是三地址形式把复合表达式拆成单步操作的结果函数顶部的__attribute__((const))表明属性信息已透传到 GIMPLEconst属性会影响后续 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 虚拟寄存器 RTLCG_GCCJIT_DUMP_RTL_ALL-fdump-rtl-alldump 全部 RTL passCG_GCCJIT_DUMP_TREE_ALL-fdump-tree-all-ehdump 全部树级GIMPLEpassCG_GCCJIT_DUMP_IPA_ALL-fdump-ipa-all-ehdump 全部 IPA passCG_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_GIMPLE1即可看到 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 选项使用。【免费下载链接】rustEmpowering everyone to build reliable and efficient software.项目地址: https://gitcode.com/GitHub_Trending/ru/rust创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考