给 matz/spinel 提了个 PR,Matz 合了 (#4899)。借这个机会介绍一下 Spinel —— 国内讨论得不多,但我觉得挺值得一看。
Matz 在写的 Ruby AOT 编译器:把 Ruby 源码编译成独立的原生可执行文件,不需要 Ruby 运行时。
管线是这样的:
Ruby (.rb)
│
├─ parse libprism(Prism 以 C 库链入)
├─ analyze 全程序类型推断,走到不动点
├─ codegen 生成一个 C 文件
└─ cc -O2 系统 C 编译器 + 运行时库
│
└─→ 原生二进制,无运行时依赖
几个我觉得有意思的点:
编译器本身是单个自包含的 C 二进制。 解析、推断、生成 C、调用 cc,全在一个进程里完成,中间没有序列化步骤 —— codegen 直接读 analyze 刚推断出的类型。
它自举过。 按 README 的 History:最早 C 写的,后来用 Ruby 重写,再后来用一个能编译自己的 Ruby 子集重写。现在 master 回到了 C 实现 (构建快得多,输出等价),自举的那版保留在 self-host 分支。
关键在"全程序"。 因为能看到整个程序,Integer 可以落成裸的 intptr_t 而不是装箱对象,方法调用能去虚化,死代码能整块删掉。这是 JIT 很难做到的 —— JIT 只能看到已经跑热的那部分。
README 里的数字 (2026-09-17 测,28 个 benchmark,32 核 Linux,gcc 13.3,基线 CRuby 4.0.4):
--yjit 几何平均 ~8.5x
抽几行:
| Benchmark | Spinel | Ruby 4.0.4 | + YJIT |
|---|---|---|---|
| mandelbrot | 18 ms | 939 ms | 938 ms |
| matmul | 6.1 ms | 200 ms | 201 ms |
| ao_render(光追) | 70 ms | 1646 ms | 611 ms |
| json_parse | 28 ms | 212 ms | 133 ms |
| csv_process | 95 ms | 491 ms | 396 ms |
git clone https://github.com/matz/spinel && cd spinel
make deps && make
cat > hello.rb <<'RUBY'
def fib(n) = n < 2 ? n : fib(n - 1) + fib(n - 2)
puts fib(34)
RUBY
./spinel hello.rb && ./hello
还有个 cargo/mix 风格的项目工具 spin(spin new / spin add / spin test / spin build),依赖是被编译进二进制的源码树,没有运行时加载。