本文详细阐述了Rust程序在Solana平台上的三阶段编译过程,包括从Rust到LLVM IR,再到SBF字节码,并最终通过JIT编译在Solana验证器上高效执行,从而确保了程序的确定性和跨平台一致性。
理解 Rust 如何编译到 SBF (Solana Bytecode Format) 以及验证器如何执行它,对于构建复杂的 Solana 程序至关重要。本文解释了三阶段编译过程,帮助你理解程序大小、调试部署问题并优化性能。
当你运行 cargo build-sbf 时,你的 Rust 程序会经历三个阶段:
.so 文件)下图总结了这一过程。

Solana Rust 程序的三阶段编译过程
现在考虑这个简单的 Rust 函数,它将两个 u64 整数相加:
pub fn add(a: u64, b: u64) -> u64 {
a + b
}
该函数在 Solana 验证器上执行之前将经历所有三个编译阶段。我们将它作为一个贯穿始终的例子来 H说明每个阶段。
Rust 编译器 (rustc) 使用 LLVM 作为其后端。LLVM 是一个编译器基础设施,它提供了一个通用的中间表示 (IR)——一种用于表示代码的平台无关格式——并应用内联和死代码消除等优化。rustc 将 Rust 源代码翻译成 LLVM IR。
使用 LLVM 的语言将其代码编译为 LLVM IR。然后 LLVM 可以将该 IR 翻译为针对不同目标(如 x86、ARM、WebAssembly、BPF 等)的机器码。这种设计允许单个编译器前端支持多种硬件架构,而无需为每种架构维护单独的后端。
要查看 Rust 代码的实际 LLVM IR,请按如下方式设置环境变量 RUSTFLAGS:
RUSTFLAGS="-C debuginfo=0 --emit=llvm-ir" cargo build
这会在 target/debug/deps/ 文件夹中生成一个 LLVM IR 文件,其名称类似于 llvm-<hash>.ll。
以下是为上面所示的 Rust add 函数生成的 LLVM IR。我们将在代码块之后讨论它。
; llvm::add
; Function Attrs: uwtable
define i64 @_ZN4llvm3add17h48743c4abf0c9b05E(i64 %a, i64 %b) unnamed_addr #0 {
start:
%0 = call { i64, i1 } @llvm.uadd.with.overflow.i64(i64 %a, i64 %b)
%_3.0 = extractvalue { i64, i1 } %0, 0
%_3.1 = extractvalue { i64, i1 } %0, 1
br i1 %_3.1, label %panic, label %bb1
bb1: ; preds = %start
ret i64 %_3.0
panic: ; preds = %start
; call core::panicking::panic_const::panic_const_add_overflow
call void @_ZN4core9panicking11panic_const24panic_const_add_overflow17h0235fd41b8202631E(ptr align 8 @alloc_d358b5fc6deae9ccd21c0c027d9d651f) #3
unreachable
}
上面的代码块已精简,只显示了 add 函数的 LLVM IR。
上面的代码块已精简,只显示了 add 函数的 LLVM IR。以下是它如何映射到我们原始的 Rust 代码:
@_ZN4llvm3add17h48743c4abf0c9b05E 是我们 add 函数的编译器改编名称i64 %a 和 i64 %b 是两个 64 位整数参数@llvm.uadd.with.overflow.i64 执行加法并检查溢出%_3.1 为真),执行分支到 panic;否则返回结果(%_3.0)LLVM IR 使用类似汇编的语法:define 声明一个函数,i64 指定 64 位整数,%a/%b 是 虚拟寄存器(值的临时存储)。
LLVM 针对不同的硬件目标(x86-64、ARM64、eBPF 等)有不同的后端。Sol...
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!