构建Clang后端并将Doom移植到我的自定义字节码虚拟机
摘要
作者复活了他的自定义字节码虚拟机(UVM),并借助AI解析文本LLVM IR构建了一个Clang后端,成功将Doom移植到该虚拟机上。
<p><a href="https://lobste.rs/s/qjjoza/building_clang_backend_porting_doom_my">评论</a></p>
查看缓存全文
缓存时间: 2026/07/08 08:26
# 为我的自定义字节码虚拟机构建 Clang 后端并移植《毁灭战士》
来源:https://pointersgonewild.com/2026-07-07-building-a-clang-backend-and-porting-doom-to-my-custom-bytecode-vm/
早在2023年初(https://pointersgonewild.com/2023/02/24/building-a-minimalistic-virtual-machine/),我写过一篇关于UVM(https://github.com/maximecb/uvm)的文章,那是一个我当时正在捣鼓的极简栈式字节码虚拟机。当时我已经让UVM支持了浮点数、多线程,以及创建帧缓冲(https://pointersgonewild.com/2023/11/01/preparing-uvm-for-3d-graphics/)和产生音频输出的简单API。不幸的是,我后来失去了继续推进这个项目的动力,就把它搁置了。
我平时做不少业余编程,和很多人一样,我有在各个项目之间来回跳跃的倾向。有时候想到自己把一个项目留在了未完成的状态,会有点小伤感;但灵感重新回来、有动力继续搞老项目的时候,感觉也挺好的。最近我重新拾起了UVM,然后开始想,既然现在有了智能体AI,我能做点什么呢?
UVM最大的弱点之一在于:要给它写软件,要么得用虚拟机的自定义汇编语言,要么得用ncc——那是我自己写的一个C子集的玩具编译器。ncc这个名字的意思是“不是C编译器”(not a C compiler),因为我在语言上做了一些自由发挥。写这个玩具编译器我觉得挺有趣,但它的局限性和怪癖意味着它并不能直接用来编译现成的软件。不过一个月前,我开始琢磨:我能不能直接“氛围编码”(vibe code)一个UVM的Clang后端呢?如果UVM能支持Clang,我就能编译真正的软件了。说不定我还能让UVM跑起《毁灭战士》。
我稍微研究了一下,发现写一个Clang后端工作量相当大。而且很明显,Clang的设计目标就是针对物理硬件。它是为了给拥有固定数量寄存器和子寄存器(比如x86上的AX、EAX和RAX)的CPU编译而设计的。要让Clang为拥有无限寄存器文件或者消费栈式字节码的VM生成代码,存在很大的不匹配。但我也知道,Clang可以直接生成文本形式的LLVM IR,也许我可以转而消费这个IR。
有一个开源项目叫PureDOOM(https://github.com/Daivuk/PureDOOM/tree/master),它是一个单头文件实现的《毁灭战士》,设计得易于移植。我用Clang编译了它,然后导出了LLVM IR。接着我试图找一个Rust crate来解析这个IR。不幸的是,我的两个选择是:要么用那个把整个LLVM作为依赖拉进来的crate(依赖性巨大),要么用一个纯Rust的crate,但它已经过时,无法解析Clang 21的输出。文本形式LLVM IR的缺点在于它没有长期稳定性保证。虽然变化不大,但格式确实会变。
作为实验,我试着让Claude Opus 4.8用Rust写一个文本形式LLVM IR的解析器。它大约20分钟就完成了任务,生成的代码看起来相当不错,于是我决定从那里继续。结果几天内我就拥有了一个相当完整的Clang后端(或者应该叫它包装器?)。最终得到的编译器支持大部分C标准库,并且拥有一个兼容pthread的线程和互斥锁API。它主要的弱点是,它对你系统上安装的Clang版本敏感,但我认为应该可以通过调整解析器使其更灵活,并兼容更广泛的Clang版本。
这周我正在参加StartupFest大会的一个黑客马拉松,我决定最后做一下这个实验:尝试把《毁灭战士》编译到UVM上运行。借助Claude Code的帮助,我在一个多小时后就搞定了。剩下的时间我做了几轮优化工作。最初让《毁灭战士》编译成功时,在我的MacBook Air M5上只能跑大约27 FPS。这个帧率完全可以玩,但考虑到《毁灭战士》最初设计是在486上运行的,还是有点让人失望。经过几轮优化,我把它提升到了大约86 FPS。其中我修改了PureDOOM,使用调色板查找表,并且改进了帧缓冲上采样代码,用memcpy来复制像素行,而不是多次单独写出相同的像素行。
为了完整性,我还用Fable生成了一个MIDI合成器,这样我们就能听到《毁灭战士》那超带感的原声了,因为没这音乐游戏感觉就不一样了。我把uvm-doom(https://github.com/maximecb/uvm-doom)开源了,如果你想试试的话。如果遇到任何问题,欢迎在UVM仓库开一个issue,但请尽可能提供关于你配置的详细信息(例如操作系统和Clang版本)。至于下一步,我打算重构UVM,改用基于寄存器的指令集。人人都喜欢基于栈的解释器,因为它们某种程度上很直观,也许是一个很好的教学工具,但它们也会留下相当一部分性能没有充分发挥。
Copyright © 2011–2026 Maxime Chevalier-Boisvert。保留所有权利。
相似文章
56,000行DOOM代码,用我自创的语言编写
作者构建了一种名为bet的玩笑编程语言,通过LLVM编译,采用基于区域的内存管理,并且成功运行了完整的DOOM游戏(56,000行代码),无需代码审查,仅依赖测试。
字节码虚拟机在意外场景中的应用 (2024)
本文探讨了字节码虚拟机的出人意料的应用,特别是Linux内核中的eBPF以及编译后二进制文件中用于调试信息的DWARF表达式。
我将 obra/superpowers 移植到了 OpenClaw — 无 Claude Code 依赖
一位开发者将 superpowers AI辅助开发框架移植到 OpenClaw,移除了 Claude Code 依赖,并使用原生代理和任务跟踪重新设计了工作流程。
Blaise v0.10.0:本地后端、线程与增量编译
Blaise v0.10.0 通过 QBE 增加了本地后端支持、线程功能和增量编译,推动这款现代 Object Pascal 编译器向自托管和更广泛的平台支持迈进。
我在verl(一个RL后训练框架)里沉浸了数月,复刻了它,然后停止。写下了内部机制、复刻所需的工具开销以及一个棘手的NCCL错误。
深入探讨字节跳动verl强化学习后训练框架的内部机制,包括编排、单控制器模式以及一个棘手的NCCL错误修复。作者分享了复刻该框架和构建自定义工具的经验教训。