Rust 下使用 LLVM 构建 JIT 开发

使用 rust + inkwell 开发了一个 JIT 代码示例。碰到了不少问题。罗列如下,以作记录:

LLVM 版本依赖

LLVM 引擎,需要指定版本。编译 release 版本时,需要指定 RUST_FLAGS 指定 LLVM 引擎的版本。

RUSTFLAGS='-lLLVM-19' cargo build --release

千万别装多套 LLVM 引擎。我就在某次装了多个版本后,完全无法编译运行了。直到我重装了系统,才恢复正常。

if-then-else 结构

需要构建出 BasicBlock

  • then-block,匹配 if 条件下的逻辑,
  • else-block,匹配 if 条件外的逻辑,
  • merge-block,合并 then-blockelse-block 的逻辑。

其值,需要通过构建 Phi 节点,将 then-blockelse-block 的值关联起来。

特别重要的一点,需要严格关注当前代码的 position,需要确保当前的代码在正确的位置。比如,

  • then-block 的代码,必须执行 position_at_end(then-block),然后再编写
  • else-block 的代码,必须执行 position_at_end(else-block),然后再编写
  • merge-block 的代码,必须执行 position_at_end(merge-block),然后再编写

Phi 节点,需要将 then-blockelse-block 的值捏合到一起,所以,要求两侧返回的类型必须一致。

另外,理论上支持嵌套,但是我没有成功。

bool -> i64

某个场景下需要将 bool 转换成 i64。一开始使用了 builder.build_int_cast。但是 builder.build_int_cast,无法正确转换。因为 bool 的值,是 10,且无符号。必须使用 builder.build_int_cast_sign_flag 才可以,且必须设置 is_signedfalse

函数调用

普通函数可注册进 LLVM Module 中,被 JIT 代码调用。非常方便且简单,只要声明好函数的参数和返回值类型,就可以调用。

特别地,说明一下函数的返回值类型或参数类型是自定义类型时,必须使用 LLVM 提供的 StructType/StructValue 类型。

一开始我是当作普通的 StructValue 使用的,想当然使用下标进行访问。结果,数据完全不是正确的值。需要通过下面代码才能正确访问。

let val1 = builder.build_extract_value(struct_value, 0, "val1").unwrap();
let val2 = builder.build_extract_value(struct_value, 1, "val2").unwrap();

Debug

没找到什么好用的 LLVM Debug 工具。API 也没找到。不得以,用上面的函数调用转着圈实现了通过日志输出来查看数据。

封装了一个打印数据的函数,在需要打印数据的地方调用。然后封装的 rust 函数调用 log::info! 进行日志输出。间接解决了问题。

总结

总的来说,通过 inkwell 构建 LLVM JIT 引擎,非常简单。但是遇到很多问题,需要自己解决。