Amethyst Studio
3774 words
19 minutes
MoonLLVM - 小型编译框架的理想

求学期间,我曾经非常迷恋 C++:模板元编程很酷,标准库看起来很「工程」、很「专业」。那会儿,我也用 C++ 写过不少东西:Lua 编译器、C 编译器,还有各种各样的小工具等等。后来毕业以后,又在国内某知名芯片公司,也做过 LLVM / MLIR 相关工作,写过优化 Pass、后端代码生成等。

但当我的经验越来越丰富,见到的东西越来越多之后,我越来越有一种感觉:

如果LLVM不是建构在C++上的就好了。

LLVM — 杰出的设计#

作为知名的编译器后端框架,LLVM 的架构设计是非常优秀的。它的 IR 设计、Pass 管线、Def-Use 链,以及对硬件的抽象,都是教科书级别的。也正因如此,LLVM 成了工业界编译器后端的标准。2013年,LLVM被授予了2012ACM软件系统奖,已足以说明工业界对它的认可。

但 LLVM 的基石语言C++,则是另一个极端。

问题频出的C++#

1. 语言太复杂#

我们在“对付语言”上花的精力,可能超过了“对付业务”。 编译器本身已经是一个超级复杂的系统,但 C++ 又额外叠加了一层复杂度:

  • 一个变量可能有二十几种初始化方式
  • 一个类可以有足足六种构造函数;
  • 模板元编程的各种黑魔法,且与C++主体编程范式大为不同的函数式范式。
  • 标准库里大量行为细节、陷阱、烂尾特性和词不达意,例如变长数组叫vector,而真正的变长数组valarray是一个烂尾的特性;vector<bool>并不是bool的容器;std::regex离谱的性能问题;std::remove实际上的作用是把元素移动到末尾等等。
  • 各种看起来炫酷,实际上有些“故作高深”的名词,例如SFINAECRTPRTTIRAIImonostate等等。

写编译器的工程师,本来应该把大部分时间花在「语言语义、优化算法、后端架构」上,但在 C++ 世界,团队不得不在很长时间里花大量精力,放在给新人培训 C++ 语法和习惯用法,或者是与各种构建配置、ABI、模板错误信息搏斗。

2. 字符串处理上问题频出#

编译器本质上你可以看做是一个复杂的字符串处理程序,但一个很不幸的事实是: C++ 标准库在字符串处理上的支持并不友好,缺少真正可靠的字符串处理库,常用的std::string或者std::regex都有不小的问题。这使得现实世界中,很多经典的库可能会选择自己动手制作字符串库,但是C++难以集成第三方库的特点由阻碍了开发者去使用它们。

而这对于想用 C++ 入门写编译器的学生来说就非常不友好了,很多人连第一关「词法分析」可能都过不去。

3. 编译慢,调试体验差#

C++的编译实在是太慢了,越大的项目这个问题越明显。这是C++本身模板展开和头文件的重复编译导致的这个问题,很多情况下,开发者稍微改一点点,就要重新等很久。

但编译器又是一个「必须频繁读源码、单步调试」的系统软件,因为编译器与Web程序或者游戏程序不同,很多 bug 无法靠打 log 简单定位;需要频繁重编译 + GDB / LLDB 调试。

这使得日常开发体验非常不友好,debug 版 LLVM + C++ 的组合,有时慢到让人怀疑人生。编译和调试的效率底下结果导致的问题就是企业的开发成本非常高。

4. 构建系统和第三方依赖太折腾#

几乎每个写 C++ 的人都和各种构建系统打过交道,Makefile / Ninja / Bazel / MSBuild / SCons / …等等。而CMake 虽然是事实标准,但在语法和使用体验上,实在是一言难尽。

构建系统混乱带来的直接后果就是引入第三方库极其麻烦,因为不同的包可能使用了不同的构建工具。即使都使用了CMake,ABI、编译选项、链接方式的兼容性问题也常常出现。一些GitHub Star很高的项目,会推崇单头文件模式,但是这样又会带来编译速度的问题。

构建系统和第三方库的问题,造成了一个C++特色:C++ 程序员,对「重复造轮子」往往已经习以为常。


如果有一个「非 C++ 的 LLVM 伴侣」……#

假如我们有这样一个东西:

  • 能和真实的 LLVM 平滑互操作,能够生成兼容 LLVM 工具链的 IR / bytecode;
  • 有着非常优秀的字符串处理,ADT,模式匹配语法,而且语法简单,上手容易,AI友好。
  • 更轻量、编译速度快、源码更容易读

那它是不是可以作为一个很好的:

  • 学习编译器的入口与实验场;
  • 小型芯片公司 / 小团队的编译后端;
  • 以及真实 LLVM 之前的「缓冲层」和「跳板」?

这就是 MoonLLVM 想做的事情。

MoonLLVM:A Tiny, Friendly Companion to LLVM

GitHub 地址:moonbitlang/MoonLLVM


MoonLLVM 的定位:不是「重写 LLVM」,而是「LLVM 的友好伴侣」#

先把预期讲清楚:

MoonLLVM 不是 LLVM 的重构版;也不打算直接取代 LLVM

现实是:LLVM 过去二十多年集结了多家大公司的巨大投入,在优化、多后端支持、生态广度上,有压倒性的优势。MoonLLVM 短期内不可能、也没必要在这些维度上正面进攻。

MoonLLVM 想填补的是另一块空白:

  • 学生和初学者更容易接触 IR / Pass / 后端;
  • 小型芯片公司、小团队可以用更低成本做原型和特定场景的后端;
  • 熟悉 LLVM 的工程师多一个更轻量、可退出的选项。

核心目标:与真 LLVM 的三层互操作#

MoonLLVM 和很多「自建小框架」,例如QBE,Cranelift等最大的不同,是我们从一开始就认真设计了与真 LLVM 的互操作,而不是造一个完全封闭的「私有宇宙」。

可以分三层来看:

1.1 代码级互操作#

  • 我们有一个 llvm.mbt 包,它是一个真 LLVM 的 MoonBit 绑定,需要本地安装 LLVM。调用llvm.mbt得到的IR,就是原版LLVM生成出来的IR。 github: https://github.com/moonbitlang/llvm

  • MoonLLVM 的 API 与 llvm.mbt 有意识地对齐:数据结构、接口设计都保持相似。

  • 用户只需改一处:调整 moon.pkg.json 配置,即可在 MoonLLVM 和真 LLVM 之间切换:

    设计目标是:MoonLLVM → 真 LLVM 平滑切换;

    • 需要注意的是,反向切回来我们无法保证,这是因为MoonLLVM还是比真LLVM简单太多。
  • MoonLLVM / llvm.mbt 的 API 很大程度上参考了原版 C++ LLVM:

    • Context / Module / Function / BasicBlock / IRBuilder 等核心概念一一对应;
    • 操作顺序、调用方式尽量保持一致;
    • 对熟悉 C++ LLVM 的工程师来说,几乎不需要换脑子。

1.2 模块级互操作(未来)#

  • MoonLLVM 与 llvm.mbt 可以在同一工程中共存
  • 提供转换函数,将 MoonLLVM 的中间数据结构转成 llvm.mbt 的数据结构;
  • 这意味着:你可以在 MoonLLVM 中做自定义 IR 生成、轻量优化,然后把 IR 交给真 LLVM 做后续优化和后端。

1.3 产物级互操作(未来)#

  • MoonLLVM 生成的 LLVM IR / bytecode:

    • 可以被现有 LLVM 工具链(如 llcopt)识别和处理;
    • MoonLLVM 对其能力范围内的 IR 支持 Parse 和再处理。
  • 一条典型链路可以是:

    MoonLLVM → LLVM IR → llvm-opt 优化 → LLVM IR →(可选)再回 MoonLLVM

从一开始,MoonLLVM 就内建了一个清晰的「退出机制」:

  • 用户不用担心「用了 MoonLLVM 以后就被架死在这里」;
  • 未来如果有需要:可以逐步把关键路径迁回真 LLVM;或者只在某些阶段用 MoonLLVM 快速试验和开发。

MoonLLVM的其它目标#

  1. 运行速度:用「适度取舍」换来轻盈

    MoonLLVM 一开始就明确做了一个选择:不追求覆盖所有稀奇古怪的 C 语言特性(比如 VLA 等),C++特性(例如各种C++专用的异常)。也不追求支持所有冷门架构与扩展。

    这样做的好处是:数据结构和算法可以更简单,内部可以大量使用定长整数和更直接的实现,在「生成 IR / 做基础转换」这类场景下,MoonLLVM 在 MoonBit 里调用的整体运行速度,有机会显著快于直接调用真 LLVM。

  2. 编译速度快,组件细粒度模块化 MoonLLVM 有意识地把组件拆得更细,做到用到哪个编译哪个,没用到的完全不参与构建;整体编译时间可以维持在较低水平;对开发者来说:改一个 Pass,不需要重编整个「工程」;非常适合做在线 Playground / REPL 的后端;或者是新 Pass / 新后端的实验平台。

  3. 无外部依赖:只依赖 MoonBit 工具链 MoonLLVM 只需要 MoonBit 自身的工具链即可:不需要外部 C++ 编译器;也不需要 CMake / TableGen 等复杂构建工具。对于只会 MoonBit 的开发者来说,安装过程非常简单;

  4. 轻量,适合「弱环境」和多种部署形态

    得益于整体设计的轻量化和 MoonBit 语言本身的特性:MoonLLVM 可以跑在性能不算强的 PC 上,也可以通过 WebAssembly 部署到浏览器中,这使得它在部分嵌入式 / 边缘计算环境中也有实际落地的希望;

    这为「MoonBit 在线 Playground」、「嵌入式脚本 / DSL 环境」等场景,提供了很自然的技术路径。


一个典型的 MoonLLVM 程序#

下面这个示例展示了如何用 MoonLLVM 创建一个简单的加法函数:

fn main {
  // 1. 创建上下文与模块
  let ctx = @IR.Context::new()
  let mod = ctx.addModule("my_module")
  let builder = IRBuilder::new()

  // 2. 定义函数类型:i32 add(i32, i32)
  let i32_t = ctx.getInt32Ty()
  let f_type = ctx.getFunctionType(i32_t, [i32_t, i32_t])

  // 3. 向模块中添加函数,并创建入口基本块
  let f = mod.addFunction(f_type, name="add")
  let bb = f.addBasicBlock(name="entry")
  builder.setInsertPoint(bb)

  // 4. 绑定并命名参数
  let x = f.getArg(0).unwrap()
  x.setName("x")
  let y = f.getArg(1).unwrap()
  y.setName("y")

  // 5. 创建加法指令与返回
  let sum = builder.createAdd(x, y, name="sum")
  builder.createRet(sum)

  // 6. 打印生成的 LLVM IR 文本
  println(mod)
}

输出的 LLVM IR 大致如下:

;; ModuleID = 'demo'
;; Source File = "demo"
;; Generated by MoonLLVM, not llvm

define i32 @add(i32 %x, i32 %y) {
entry:
  %sum = add i32 %x, %y
  ret i32 %sum
}

注意顶部的注释部分明确标注了「Generated by MoonLLVM, not llvm」,这与原版 C++ LLVM 的输出可以清晰区分。

这份 IR 既可以交给官方 LLVM 工具链(例如 llc)继续编译,也可以作为 MoonMIR 的输入,进一步生成 riscv64aarch64 汇编。


对应的 C++ LLVM 程序对比#

下面是一段等价的 C++ LLVM API 示例代码,实现同样的 add(i32, i32) -> i32 函数:

#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/Module.h"
#include "llvm/Support/raw_ostream.h"

int main() {
  // 1. 创建上下文与模块
  llvm::LLVMContext ctx;
  auto module = std::make_unique<llvm::Module>("my_module", ctx);
  llvm::IRBuilder<> builder(ctx);

  // 2. 定义函数类型:i32 add(i32, i32)
  llvm::Type *i32_ty = llvm::Type::getInt32Ty(ctx);
  std::vector<llvm::Type *> params(2, i32_ty);
  llvm::FunctionType *fty =
      llvm::FunctionType::get(i32_ty, params, /*isVarArg=*/false);

  // 3. 在模块中创建函数与入口基本块
  llvm::Function *fn =
      llvm::Function::Create(fty, llvm::Function::ExternalLinkage, "add", module.get());
  llvm::BasicBlock *entry = llvm::BasicBlock::Create(ctx, "entry", fn);
  builder.SetInsertPoint(entry);

  // 4. 绑定并命名参数
  auto arg_iter = fn->arg_begin();
  llvm::Value *x = arg_iter++;
  x->setName("x");
  llvm::Value *y = arg_iter++;
  y->setName("y");

  // 5. 创建加法与返回指令
  llvm::Value *sum = builder.CreateAdd(x, y, "sum");
  builder.CreateRet(sum);

  // 6. 打印 IR
  module->print(llvm::outs(), nullptr);
  return 0;
}

MoonLLVM 目前已经做到什么?#

当前,MoonLLVM 已经具备以下能力:

  • 支持 LLVM IR 的构建;
  • 在此基础上,完成了初步的后端代码生成,可以独立生成 RISC-V 汇编代码,和 AArch64 汇编代码

基于 MoonLLVM,我们实现了一个 MiniMoonBit 编译器,除基础特性外,还支持模式匹配、高阶函数等特性,完成MiniMoonBit编译器后,我们还用 MiniMoonBit 跑了一个光线追踪程序,效果可以在 B 站视频中看到:

B 站视频:MiniMoonBit + MoonLLVM 光线追踪示例


性能测试:与 tcc / clang 的对比#

为了测试 MoonLLVM 的实际表现,我们设计了 5 个小例子:

  • ack.mbt:Ackermann 递归函数;
  • fib.mbt:递归 Fibonacci;
  • eigen.mbt:矩阵求特征值;
  • svd.mbt:矩阵 SVD 分解;
  • queen.mbt:八皇后问题。

测试方式如下:

  • MiniMoonBit(基于 MoonLLVM)

    • MiniMoonBit 生成 AArch64 汇编;
    • 使用 clang -O0 编译汇编文件和 runtime.c,得到可执行程序。
  • tcc

    • 使用 MoonBit 将对应 .mbt 文件通过 --target native 转为 C 程序;
    • 使用 tcc 将 C 文件与 MoonBit 标准 runtime.c 编译为可执行程序。
  • clang -O0

    • 同样先转为 C,再用 clang -O0 编译。
  • clang -O1

    • 同样先转为 C,再用 clang -O1 编译。

然后分别运行所得可执行程序,记录时间(单位:秒):

测试用例MiniMoonBittccclang -O0clang -O1
ack0.0110.0150.0130.010
fib0.0650.1690.1150.036
eigen0.4261.8581.7630.256
svd0.6253.2512.8930.455
queen3.26915.01913.0753.201

配图如下:

MoonLLVM MiniMoonBit 性能对比

性能结果分析#

整体来看:

  • 基于 MoonLLVM 的 MiniMoonBit 性能明显优于 tcc 和 clang -O0
  • clang -O1 相比,MiniMoonBit 仍有差距,但在同数量级之内。

主要原因在于:

  • MoonLLVM 当前后端已经做了寄存器分配(采用图着色寄存器分配);
  • 而 tcc 和 clang -O0 不做寄存器分配,因此在部分算例中会出现明显性能劣势;
  • clang -O1 在寄存器分配之外,还开启了更多优化 Pass,因此通常会比当前版本的 MiniMoonBit 更快。

换句话说,在仍然相对「年轻」的优化管线下,MoonLLVM 已经能在不少场景中跑到接近 clang -O1 的表现;后续优化空间依然很大。


展望:MoonLLVM 接下来要做什么?#

未来一段时间内,MoonLLVM 会重点在以下方向持续演进:

  1. 兑现互操作承诺: 持续完善与真 LLVM 的互操作能力;保证「随时可以退出到真 LLVM」这条路径长期有效。
  2. 扩展指令与类型系统: 丰富 IR 指令和类型支持;增强优化能力,引入更多调试与诊断信息。
  3. 增加更多体系结构后端: 补充 x86_64 等后端;在更多架构上验证当前抽象方案的通用性与简洁性。
  4. 打造完整工具链闭环: 开发配套汇编器(MoonAs)与链接器(MoonLD);构建由 MoonLLVM 驱动的、可独立运作的工具链闭环。

我们希望,MoonLLVM 既能成为教学与研究的友好平台,也能在特定场景下,成为真正落地可用的轻量级编译后端。


如果你对 LLVM 生态已经很熟悉,希望有一个更轻量、可实验、可与真 LLVM 平滑切换的伴侣;或者你只是想用一种更现代、更干净的方式来认识「编译器后端」这个世界,欢迎试试 MoonLLVM:

  1. MoonLLVM链接:https://github.com/moonbitlang/MoonLLVM
  2. MiniMoonBit编译器:https://github.com/moonbitlang/MiniMoonBit2025
  3. B站视频,用MoonBit写个MiniMoonBit跑了一个光线追踪: https://www.bilibili.com/video/BV1kSS4BqETn
MoonLLVM - 小型编译框架的理想
https://ziyue.cafe/posts/moonllvm-intro/
Author
Kaida Amethyst
Published at
2025-12-19