内联你的运行时

这篇文章主要介绍了在编程语言实现中如何将编译器与运行时分离,并通过内联运行时来优化程序。主要内容如下:

  • 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上构建运行时。
    • 使用更好的分配器,如mimallocrpmalloc
    • 针对目标机器优化,发现使用除OptimizationLevel::None之外的优化级别会导致内存错误。
    • 静态链接编译器,使其完全自包含。
    • 实现可移植的静态链接,使编译器可分发静态libc和链接器。
阅读 28
0 条评论