Skip to content
Tech News
← Back to articles

Show HN: DOOM in the kernel, or fibers in eBPF

read original get Learning eBPF by Liz Rice (O'Reilly) → more articles
Why This Matters

A developer has built BPF Capsule, a compiler and runtime that lets large C programs — demonstrated by running DOOM's game logic and rendering — execute inside the stock Linux eBPF verifier and JIT, with no kernel patches. It shows eBPF's famously restrictive limits (tiny stack, no recursion, provably finite loops) can be worked around by a compiler rather than by changing the kernel. That expands what kinds of application logic could plausibly live in kernel space.

Key Takeaways
Worth a Look

Learning eBPF by Liz Rice (O'Reilly) — If watching DOOM run through the stock BPF verifier and JIT made you want to understand how any of that is possible, this O'Reilly book walks through writing, loading, and attaching eBPF programs yourself. It covers the bpf() syscall, maps, and the verifier rules that make hacks like this so impressive. A great companion for turning "how did they do that" into your own kernel-side experiments.

See Learning eBPF by Liz Rice (O'Reilly) on Amazon → Affiliate link — we may earn a commission on purchases, at no extra cost to you. Product picked by AI based on this article; it is not a tested recommendation.

DOOM in the kernel, or fibers in eBPF

DOOM is not supposed to run inside eBPF. Linux should reject a program like that before executing its first instruction.

BPF has a tiny stack, five argument registers, and limited call depth. Recursion is forbidden. A loop must be finite not merely because the programmer says so, but in terms the verifier can prove. You cannot simply store a pointer in memory, load it later, and dereference it: the kernel must remember where it came from and what it is allowed to address.

And yet an unmodified Linux kernel accepts my BPF object, checks it with the stock verifier, and runs it through the stock JIT. DOOM initialization, game logic, and rendering all execute in the kernel. One game tick, including the complete frame, finishes in a single BPF invocation. Userspace supplies the WAD and keyboard input and gets back a pointer to the finished framebuffer.

First, a little context on eBPF. It lets user-supplied programs run inside the Linux kernel without a kernel module. A program is compiled to bytecode for a small register machine, loaded with the bpf(2) system call, and attached to a hook—for example, an incoming packet or a system-call tracepoint. The kernel's JIT compiles the bytecode to machine code, which runs whenever the hook fires. But before the program can run, the verifier must accept it. That is where the constraints above come from: code the verifier cannot prove safe is rejected. This check, rather than the bytecode itself, is what makes DOOM inside eBPF look impossible.

The project is called BPF Capsule. It is a compiler and runtime for large C programs inside ordinary BPF, with no kernel patches and no separate virtual machine in userspace. The oldest supported target profile is Linux 5.15. A profile determines which kernel capabilities the compiler may use. I have loaded and run the programs on both x86-64 and arm64.

Nobody needs games in the kernel, of course. But complex application logic is useful there: parsing packets, for example, or keeping statistics about them. When such a program does not fit eBPF's constraints, it has to be simplified and rewritten by hand until the verifier is satisfied. Capsule explores another path: it takes C, C++, or no_std Rust code and transforms it into a shape that stock Linux accepts.

DOOM is not the application here but a stress test for that approach. Lua, QuickJS, SQLite, zlib, wasm3, llama2.c, no_std Rust, and CPython 3.14 run on the same scheme today, and Lua and Python inspect live packets straight from XDP. This article follows the road from a hand-trimmed port through a slow interpreter to regions and fibers—and measures what they cost at run time.

You can try it with one command on any supported kernel. You need Nix and a WAD file — for obvious reasons the WAD is not in the repository — and the rest of the requirements are in the README:

$ sudo nix run github:ayles/bpf-capsule#doom -- /path/to/doom1.wad tty

... continue reading