作者先用 c2rust 工具 23 秒完成 7 万行 C 代码到 Rust 的自动转换,再用 AI 定位和修复翻译引入的 4 类系统性 bug,发现 AI 最适合处理模式固定的批量修改而非创造性翻译。
我需要把一个 C 程序迁移到 Rust。我的第一反应很简单:把它交给模型,一块一块地处理,然后检查输出。
然后我查了一下是否已经存在这样的工具。c2rust,来自 Immunant,正好做这个。在 73,095 行 doomgeneric 代码上,它用了 23.2 秒。
有趣的部分不是速度。而是模型最终还是做了真正的工作,只是完全不在翻译这个环节。
成本不是论据,否则就是在自欺欺人
我用 o200k_base tokenizer 测量了用模型翻译 DOOM 的 token 成本:550,595 个输入 token 和 1,896,319 个输出 token。这只需要几美元。这不是理由。
真正起决定作用的是其他三件事:
可复现。相同的输入,每次运行都得到完全相同的输出文件,逐字节一致。你可以 diff 对比。
系统性错误。翻译过程中产生了四个 bug。其中两个是同一个 bug 出现了 23 次。某一类错误只需修一次就够了。
所有错误都败得很响亮。每一个都在编译时崩溃或第一帧就挂掉了。如果 143,911 行中有一个零散的、静默的错误,找到它的成本比整个翻译还高。
DOOM 自带录制的 demo。每一个游戏决策都是定点计算,所以如果其中一个不同,回放就会失去同步,tic 计数也会停止匹配。这是第一道关卡,而且是免费的。
第二道关卡:我对引擎传给显示器的每一帧都做了哈希,两个版本都跑了一遍。11,113 帧,只有一帧不同。
我差点就把那一帧当作 c2rust 的唯一错误发布了。对照组救了我:我用原始 C 二进制文件对照自己跑了一遍。它在同一个像素上也不同。原始 DOOM 读取了一个未初始化内存的像素,而翻译忠实地复现了这一点。
如果你只和一次运行做对比,你就会把自己参照物的噪声报告成对方的 bug。
测量 unsafe 块内的代码行数,翻译出来的 zlib 有 47% 的代码在 unsafe 中,而 zlib-rs(由专业人员完成的重写)只有 31%。
数 unsafe 这个词出现的次数会得到相反的结论,而且这是一个有误导性的指标:翻译器把整个函数包装在一个块里,所以它在计数上得分更好,而实际上更差。
这些本来就不可比。zlib-rs 是重写,有重新设计的结构,类型系统在发挥作用。自动翻译做不到这些。它的目标是不改变程序。
不是翻译。而是在引擎已经计算出的结果之上生成调节细节。
每张纹理都经过一个上采样器,然后存储为一个单通道图,每个 texel 的均值恰好是 128。渲染器继续决定颜色和光照;这张图只负责在 texel 内部的变化:
color_final = color_doom × detail / 128
因为均值锚定在 128,增强效果证明不会偏移图像。在任意 texel 上取平均,它乘以 1。
而这张图同时也是高度图,所以它的梯度免费提供每个像素的浮雕效果,不需要额外字节,在打包时就烘焙进去了。
这个约束——引擎继续做决定——正是让结果做到增补而不是毁坏的原因。
1993 年的 C 构建和 Rust 翻译都被编译成 WebAssembly 并肩运行,加上 640×400 分辨率、逐像素光照的增强渲染器。
以上所有的 60 秒版本,游戏运行在背后:
完整的 write-up 包含四个翻译 bug、三个可移植性问题、那个在加宽结构体字段后静默清除半个屏幕的 memset,以及如何从 7215 到 3009 realtics 不重写游戏:
I translated DOOM from C to Rust without writing a line, then used AI for what it is actually good at