首页
/ Rust Cranelift 后端使用实战:cargo-clif 替换构建、rustc-clif 直接编译与 JIT 模式

Rust Cranelift 后端使用实战:cargo-clif 替换构建、rustc-clif 直接编译与 JIT 模式

2026-09-05 13:51:34作者:昌雅子Ethen

本文围绕 rustc 仓库中 Cranelift 代码生成后端(rustc_codegen_cranelift)的使用文档 usage.md 展开,完整覆盖其三种使用方式:cargo-clif 作为 cargo build 的近似替换、rustc-clif 直接编译单文件,以及高度实验性的 JIT 即时执行模式,并结合该模块的构建系统与包装器源码,讲清每条命令背后的参数注入机制,帮助读者在现有项目中快速切换代码生成后端并理解其底层工作原理。

一、项目定位与前置准备

rustc_codegen_cranelift 的目标是为 Rust 编译器提供一个基于 Cranelift(wasmtime 项目中使用的 IR 代码生成器)的替代代码生成后端,其潜在收益是改善 debug 模式下的编译时间。官方 Readme.md 将其定位为 cargo build / cargo run 的"近似替换(near-drop-in replacement)":只要项目没有用到尚未支持的特性,就可以直接工作。

使用使用文档之前,需要先完成后端构建。文档假设 $cg_clif_dir 是你克隆该仓库的目录,并且已经执行过 y.sh preparey.sh build(或直接运行 test.sh)。构建完成后,可执行产物位于 $cg_clif_dir/dist/ 目录下。

构建系统本身的用法在 build_system/usage.txt 中有完整说明:

USAGE:
    ./y.sh prepare [--out-dir DIR] [--download-dir DIR]
    ./y.sh build [--sysroot none|clif|llvm] [--out-dir DIR] [--download-dir DIR] [--no-unstable-features] [--frozen]
    ./y.sh test [--sysroot none|clif|llvm] [--out-dir DIR] [--download-dir DIR] [--no-unstable-features] [--frozen] [--skip-test TESTNAME]
    ./y.sh abi-cafe [--sysroot none|clif|llvm] [--out-dir DIR] [--download-dir DIR] [--no-unstable-features] [--frozen]
    ./y.sh bench [--sysroot none|clif|llvm] [--out-dir DIR] [--download-dir DIR] [--no-unstable-features] [--frozen]
    ./y.sh check-todo

其中与使用方式强相关的选项有:

  • --sysroot none|clif|llvm:决定 sysroot 中标准库的来源。clif 会用 Cranelift 重新编译标准库,llvm 直接使用 rustc 预编译(LLVM 后端)的标准库,none 则不包含任何标准库。
  • --no-unstable-features:禁用尚不成熟的功能。该选项会同时关闭 JIT 模式和内联汇编支持,因此如果你的目标平台构建时使用了此选项,后文的 JIT 用法将不可用。
  • --keep-sysroot:禁止清理 sysroot 目录,使 sysroot 源码未变化时复用旧产物,适合未修改代码生成后端时的增量构建。

此外,构建默认依赖 rustup 安装 rust-toolchain.toml 指定的 nightly 工具链,并使用 Git 下载测试仓库与应用补丁。

二、Cargo 方式:cargo-clif build(推荐)

在项目的目录(即平时可以执行 cargo build 的位置)下运行:

$ $cg_clif_dir/dist/cargo-clif build

该命令会用 rustc_codegen_cranelift 而非默认的 LLVM 后端来构建你的项目。文档明确建议优先使用这种方式。

cargo-clif 包装器做了什么

从源码看,cargo-clif 的实现在 scripts/cargo-clif.rs,它是一个围绕 cargo 的薄包装层,核心逻辑包括:

  1. 定位 sysroot:以可执行文件自身路径为基准向上推导 dist 目录(若可执行文件位于 bin/ 子目录则再向上一层),从而找到后端动态库和 sysroot 位置。
  2. 注入 RUSTFLAGS:通过环境变量 RUSTFLAGSRUSTDOCFLAGS 向 cargo 注入三个关键参数(scripts/cargo-clif.rs#L15-L31):
    • -Zcodegen-backend=<dist>/lib/librustc_codegen_cranelift.so(或构建为内建后端时的 -Zcodegen-backend=<name>):告诉 rustc 加载 Cranelift 后端;
    • --sysroot <dist 目录>:切换到由 Cranelift 构建的标准库环境;
    • 当构建未开启 unwind 支持(support_panic_unwind 未启用)时,自动追加 -Cpanic=abort -Zpanic-abort-tests。这与 Readme.md 中"-Cpanic=abort 默认开启、panic 时 unwinding 仍为实验性"的说明一致。
  3. 防止无限递归:若 cargo-clif 被当作 cargo 子命令以 cargo clif ... 形式调用,包装器会剥掉首个 clif 参数再继续执行(scripts/cargo-clif.rs#L42-L46)。
  4. 固定工具链:在未显式指定 CARGO 环境变量时,通过设置 RUSTUP_TOOLCHAIN 为构建时记录的 TOOLCHAIN_NAME,保证 cargo 使用与后端构建匹配的 nightly 工具链。

在 Unix 上它最终使用 cmd.exec() 直接替换当前进程(scripts/cargo-clif.rs#L83-L84),因此退出码、信号等行为与原 cargo 命令完全一致。

三、Rustc 方式:rustc-clif

如果不用 cargo,也可以直接以 rustc 方式编译单个 crate:

$ $cg_clif_dir/dist/rustc-clif my_crate.rs

文档提示"应优先使用 Cargo 方式"。rustc-clif 的实现在 scripts/rustc-clif.rs,其参数构造策略与 cargo-clif 类似,但直接面向 rustc:

  • 拼接 -Zcodegen-backend=<dist>/lib/<DLL_PREFIX>rustc_codegen_cranelift<DLL_SUFFIX> 指向后端动态库(scripts/rustc-clif.rs#L14-L30);
  • 若用户没有显式传入 --sysroot= 或空格两种形式都会识别),则自动追加 --sysroot <dist 目录>scripts/rustc-clif.rs#L31-L37);
  • 同样地,在无 unwind 支持的构建上追加 -Cpanic=abort -Zpanic-abort-tests
  • 若用户未传任何参数,包装器会清空自己注入的所有参数后直接调用 rustc,从而保证展示的是原生帮助信息而非带注入参数的报错(scripts/rustc-clif.rs#L38-L42);
  • 最终在 Unix 上以 exec 替换进程调用 rustc,在 Windows 上则 spawn 后等待并透传退出码。

四、JIT 模式:跳过可执行文件直接执行

文档对 JIT 模式给出了明确的实验性警告:

⚠⚠⚠ The JIT mode is highly experimental. It may be slower than AOT compilation due to lack of incremental compilation. It may also be hard to setup if you have cargo dependencies. ⚠⚠⚠

在 JIT 模式下,cg_clif 会直接执行你的代码而不生成可执行文件。由于不产出可执行文件,所有依赖必须能作为动态库加载(文档说明 JIT 模式可能还需要 cargo 集成来保证这一点)。

两种等价的调用方式:

# 方式一:cargo-clif 的 jit 子命令
$ $cg_clif_dir/dist/cargo-clif jit
# 方式二:手动给 rustc-clif 传 JIT 参数
$ $cg_clif_dir/dist/rustc-clif -Cllvm-args=jit-mode -Cprefer-dynamic my_crate.rs

参数解析与执行路径的源码印证

1. jit-mode 是唯一的 -Cllvm-args 选项。 后端配置结构 BackendConfig 定义在 src/config.rs

  • jit_mode: bool:默认 false(即默认走 AOT 编译路径),通过 -Cllvm-args=jit-mode 置为 true
  • jit_args: Vec<String>:JIT 模式下传给目标程序的参数,默认取自环境变量 CG_CLIF_JIT_ARGS(按空格切分);
  • 解析循环会静默忽略 -import-instr-limit(Rust 构建系统测试时会自动设置该值),其余任何未知选项都会返回 Unknown option 错误。

2. cargo-clif jit 子命令本质是命令重写。scripts/cargo-clif.rs#L48-L62 可见,jit 子命令会被重写为:

cargo rustc <剩余参数> -- -Zunstable-options -Cllvm-args=jit-mode

同时额外注入 -Cprefer-dynamic(使依赖优先走动态链接,这正是 JIT 模式依赖动态库要求的原因)。这与文档中方式二的手动参数组合完全对应。

3. 后端内部按 jit_mode 分叉。src/lib.rscodegen_crate 实现中:

  • config.jit_mode 为真且编译时启用了 jit feature,则调用 driver::jit::run_jit(tcx, target_cpu, config.jit_args),直接把 CG_CLIF_JIT_ARGS 中的参数交给运行时;
  • 若编译时未启用 jit feature(例如使用了 --no-unstable-features),会直接 fatal 报错 "jit support was disabled when compiling rustc_codegen_cranelift";
  • 否则进入正常的 AOT 路径 rustc_codegen_ssa::base::codegen_crate(driver::aot::AotDriver, tcx)

另外 src/lib.rs#L148-L149 有一条硬性限制:JIT 模式不兼容 cargo check,此时会直接报 "JIT mode doesn't work with cargo check" 错误。

4. PIC 行为差异。 JIT 与 AOT 还有一个底层差异:build_isa 中会根据是否 JIT 设置 Cranelift 的 is_pic 标志——JIT 模式设为 false,AOT 保持 truesrc/lib.rs#L268-L274)。这解释了为什么文档强调 JIT 可能"缺少增量编译"从而比 AOT 更慢:它没有可复用产物,每次都要重新生成并执行。

5. JIT 参数传递的另一个真实用例。 仓库中 scripts/filter_profile.rs 提供了一个把 rustc 包装成 JIT 执行器的一行脚本:PROFILE=$1 OUTPUT=$2 exec $RUSTC -Zunstable-options -Cllvm-args=jit-mode -Cprefer-dynamic $0,与文档中手动方式二的参数组合一致。

五、Shell 快捷函数:把 cg_clif 当"即时 Rust REPL"

文档最后给出三个 shell 函数,让你无需编译可执行文件、直接在 shell 里用 cg_clif 的 JIT 能力运行 Rust 代码(bash 语法):

function jit_naked() {
    echo "$@" | $cg_clif_dir/dist/rustc-clif - -Zunstable-options -Cllvm-args=jit-mode -Cprefer-dynamic
}

function jit() {
    jit_naked "fn main() { $@ }"
}

function jit_calc() {
    jit 'println!("0x{:x}", ' $@ ');';
}

三者层层包装:

  • jit_naked 把参数经标准输入喂给 rustc-clif- 表示从 stdin 读源码),加上 -Zunstable-options 解锁 -Cllvm-args 这类不稳定选项,并启用 JIT 与动态链接;
  • jit 把任意表达式包进 fn main() { ... },因此可写 jit 'println!(1+2);'
  • jit_calc 是典型演示:把参数按十六进制打印,例如 jit_calc 42 输出 0x2a

注意这一用法的前提与第四节的警告相同:JIT 是高度实验性模式,且依赖 --no-unstable-features 未关闭 jit feature 的构建产物。

六、关键命令与参数速查

命令 / 参数 作用 源码依据
$cg_clif_dir/dist/cargo-clif build 用 Cranelift 后端构建当前 cargo 项目(推荐方式) scripts/cargo-clif.rs
$cg_clif_dir/dist/rustc-clif my_crate.rs 用 Cranelift 后端直接编译单文件 scripts/rustc-clif.rs
$cg_clif_dir/dist/cargo-clif jit JIT 模式直接执行(重写为 cargo rustc -- -Zunstable-options -Cllvm-args=jit-mode 并加 -Cprefer-dynamic scripts/cargo-clif.rs#L48-L62
-Cllvm-args=jit-mode 唯一的合法 llvm-args,开启 JIT 模式;其余值报 Unknown option src/config.rs
-Cprefer-dynamic JIT 模式必需,使依赖以动态库方式提供 scripts/cargo-clif.rs#L50
CG_CLIF_JIT_ARGS 环境变量 按空格切分后作为参数传给 JIT 执行的程序 src/config.rs#L9-L26
--no-unstable-features(构建期) 关闭 JIT 模式与内联汇编;此构建下 JIT 调用会 fatal build_system/usage.txtsrc/lib.rs#L219-L224
`y.sh build --sysroot clif llvm none`

七、适用前提与限制小结

结合文档与源码,使用该后端的几个前提值得记住:

  1. 必须先通过 y.sh prepare + y.sh build(或 test.sh)产出 dist/ 目录,且默认要求 rustup 提供对应 nightly 工具链(见 build_system/usage.txt 的 REQUIREMENTS 一节);
  2. 未开启 panic unwinding 支持时,cargo-clif/rustc-clif 会自动注入 -Cpanic=abort -Zpanic-abort-tests,因此依赖 unwind 语义的项目需在构建期评估 --panic-unwind-support 选项;
  3. JIT 模式高度实验性:不支持增量编译、不兼容 cargo check、要求依赖以动态库形式可用,且可能被 --no-unstable-features 构建整体禁用;
  4. 尚未支持的编译器特性(SIMD、panic unwinding 等)以 Readme.md 的 "Not yet supported" 一节为准,std::simd 可用而 std::arch 部分可用。

以上命令与参数均以当前仓库中 compiler/rustc_codegen_cranelift/ 目录的实际文档与源码为准;如需进一步了解用 Rust 仓库源码构建/测试本后端的流程,可继续阅读 docs/rustc_testing.mddocs/dwarf.md

登录后查看全文
热门项目推荐
相关项目推荐