你写的每一行 C、Go、Java,最后都会变成汇编指令。
有人把这件事推到了底:连编译器本身,也用汇编写了。
7143 行 .s 文件,没有一个 C 文件,没有一个第三方库
仓库的语言统计第一项就是 Assembly——不是"带点汇编",是全部。
我把它 clone 下来,在我这台 4 核 Linux(binutils 2.42)上从头编了一遍。不是为了学一门新语言,是想看清楚一件事:当你把 libc、把 LLVM、把整个工具链全拿掉,一个编译器还剩下什么。
先看数字,我先跑了一遍
所有数都是本机实测,命令在后面
编译它自己只要两步:as src/main.s,然后ld。出来的 rubix_compiler 是65680 字节的静态 ELF,strip 完49536 字节。README 头部写的"~42 KB"是旧数,它自己的验证命令里写的 64K/49K 才对得上。
编一个 .ix 程序:hello world 的前端耗时1 毫秒,那个 203 行的压测用例2 毫秒。走完整条链路(生成汇编 → as → ld)是18 毫秒 / 21 毫秒。
同一台机器上,gcc -O2编一个 hello world 要47 毫秒,加-static要 83 毫秒。也就是说全链路它比 gcc 快一倍多,但"2 毫秒"那个数只算前端——真正的耗时在 as 和 ld 里,不在它自己身上。
产物才是最好看的部分:hello world 出来12560 字节,ldd 对着它只回一句 not a dynamic executable 。同样的 C 程序,动态链接 15.6 KB,静态链接767 KB。
更狠的是加-t windows,在 Linux 上用 ld -m i386pep 直接产出一个PE32+ 的 exe,12244 字节,不需要 MinGW,不需要 .lib 导入库。
仓库自带 test.sh,我跑了一遍:19 项全过,包括 Windows 目标和 -c 分开编译。
项目仓库自带标识,整条编译链路就是"汇编进、汇编出、系统链接器收尾"
它为什么这么快:因为它什么都不做
没有优化 pass,是它快的唯一原因
链路很短:lexer 切 token → 递归下降 parser(带 Pratt 式运算符优先级)→ 符号表管作用域栈 → codegen 直接吐 .intel_syntax noprefix 的汇编。然后交给系统的 as 和 ld。
所以"零依赖"这个词要拆开看:它是零 libc,不是零工具链。README 写"直接编译成机器码",实际是三段式。这不影响它酷,但你得知道自己在量什么。
一个编译器真正的成本不在"编译得多快",在"它生成的代码有多好"。前者是 2 毫秒的事,后者是几十年的积累。
我拿一段试除法数素数(20 万以内)对比了一下:Rubix 出来的程序90 毫秒,gcc -O260 毫秒,gcc -O0 也就 71 毫秒。差 50%,没崩。
但这恰恰说明问题:这段代码是纯循环加内存读写,本来就没有优化空间。换成能向量化、能内联的代码,差距会是几倍。没有优化器不是"另一种风格",是"少了一整层"。
能带走的三样东西,比这门语言值钱
你不需要用它写业务,你需要用它校准自己的理解
第一,它生成的汇编是一份 ABI 教材。没有优化,函数序言和尾声原样躺在那里:参数按 rdi、rsi、rdx、rcx、r8、r9 传,第 7 个开始走栈,返回值在 rax。
面试被问"函数调用的时候到底发生了什么",多数人只能背"压栈出栈"。你真读过一份,就能说到寄存器级——这比背十道八股有用。
第二,零 libc 的程序怎么活。Linux 上 rax = 1 就是 write,直接 syscall 进内核;Windows 上走 gs:[0x60] 拿到 PEB,遍历 kernel32 的导出表找函数地址,所以它不要 .lib。
链接器到底在干什么、动态链接为什么要 PLT,这些问题看一眼它的 runtime.s 就通了。
第三,报错应该长什么样。我故意写坏了两个文件,它给的是这种:
Rubix Compiler Error: Unterminated string literal
--> line 3
3 | let s = "abc
行号、源码、脱字符,Rust 那套学到了。但脱字符固定钉在第 1 列,不指真正出错的那个引号——诊断"够用",但没打磨。这个细节比它 19 项测试全过更能说明它的成熟度。
边界也要说清楚,别拿去上线
1902 Star,2 个 fork,1 个 watcher
这个数字比例很说明问题:收的人多,真动手的人少。它适合读,不适合当依赖。
语言本身很小:没有泛型,没有闭包,没有 GC,堆是 64KB 的 arena bump allocator;macOS 目标要交叉链接器,机器上没有就只给你一个 .o。
整数溢出是硬陷阱:9223372036854775807 再加 1,直接Rubix Runtime Error: integer overflow,退出码 1(走的 jo 指令)。说停就停,不给你回绕。
还有个细节:我写 let x = 10 忘了分号,它照样编过了。解析器宽松是好事,也是信号——说明边角上的用例还没被覆盖满。
总结——
它的价值不在"用汇编写了个语言",在于它把平时藏在黑盒里的三层掀开了:源码到汇编、汇编到目标文件、目标文件到可执行。
每一层你都能看到实物,每一层都能对上一个你背过的概念。
你每天敲的那几行,最后到底变成了什么?大多数时候我们不知道,也不需要知道。但偶尔花一个晚上把它看穿,你对"性能"和"抽象"的判断会往回调一格。
(本文数据均为 10 月 23 日在 4 核 Linux 机器上实测,binutils 2.42,gcc 13。)
你上一次认真看自己代码生成的汇编,是什么时候?
热门跟贴