这篇文章主要介绍了在编程语言实现中如何将编译器与运行时分离,并通过内联运行时来优化程序。主要内容如下:
- Compiler vs Runtime:在编程语言实现中,常将编译器与运行时分离,对于生成固定代码有用。例如算术和条件语句需生成特定指令,内置数据结构应在运行时系统中定义,编译器生成调用运行时 API 的指令。对于高级语言,这种分离不可避免,最直接的解决方案是将编程语言实现为可执行编译器和运行时库,编译器生成二进制文件并与运行时库链接,但会错过 LLVM 中的很多优化。
- Inline Your Runtime:想要实现链接时优化(LTO),可以将运行时代码编译为 LLVM 位码,提前生成运行时位码,将生成的代码和运行时位码链接,如
llvm-link rts.bc output.bc -o main.bc
,然后进行优化和编译。 - Implementation:要在编译器中实现此功能,需构建运行时库(用 Rust 工具链编译为 LLVM 位码),构建编译器(将运行时库包含在编译器中,链接到相同的 LLVM 版本,编写调用运行时 API 的代码,将编译器编译为本地可执行文件)。
- Toolchain:需要提供 LLVM 库的开发环境和使用相同 LLVM 版本的 Rust 工具链,如
flake.nix
文件所示,选择 LLVM 18 版本,因为它受Inkwell crate
支持。 - Runtime:构建运行时库,如
rts/src/lib.rs
所示,这是一个典型的no_std
库,提供简单的内存分配、求和和释放Foo
的 API,通过类型和测试来验证运行时系统的正确性,使用cargo miri test -p rts
进行测试。 - Compiler:编写编译器,如
compiler/src/main.rs
所示,将运行时位码直接包含在可执行文件中,加载运行时位码创建main
模块,定义调用运行时 API 的main
函数,运行优化 passes,验证模块,可进行 JIT 或编译为对象文件。 - Conclusion:实现了编译器和运行时的清晰分离,运行时安全、高效、可测试且小,可直接嵌入编译器以实现最大可移植性,编译器能生成调用运行时 API 的代码,但依赖
RUSTFLAGS="--emit=llvm-bc"
,需注意 Rust 版本和生成的代码。 Homework:
- 在
std
上构建运行时。 - 使用更好的分配器,如
mimalloc
或rpmalloc
。 - 针对目标机器优化,发现使用除
OptimizationLevel::None
之外的优化级别会导致内存错误。 - 静态链接编译器,使其完全自包含。
- 实现可移植的静态链接,使编译器可分发静态
libc
和链接器。
- 在
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。