d627786Complete M11 GC, tail calls, and Lux GPU driver
Fifteen files reshape the bytecode, verifier, interpreter, JIT, process heap, ports, virtio transport, self-tests, and QEMU harness.
A Lux process encoded every GPU command, owned the framebuffer, and bounced the white band across the screen. Yggdrasil moved opaque buffers—and stayed out of the way.
y 118–157
Two repositories moved as one vertical slice: runtime, language, device surface, driver, and acceptance harness.
d627786Fifteen files reshape the bytecode, verifier, interpreter, JIT, process heap, ports, virtio transport, self-tests, and QEMU harness.
4c818c9Tail-aware codegen, lazy boolean lowering, kernel intrinsics, three executable proofs, and all 129 Lux tests passing.
The GPU demo is interesting because it draws. It is convincing because it keeps drawing. That demanded two runtime properties Yggdrasil did not have the morning M11 began: a constant native stack and memory that comes back.
TAIL_CALL and TAIL_CALL_EXT do not recurse in native code. The
running engine stashes the next target and arguments, then returns a reserved tag-7
sentinel. modload::invoke catches it, resolves the current module version,
and dispatches again from a loop. Hot-code migration still happens at the same boundary;
the C stack simply stops growing.
When a bump span fills, the owning process adds another—doubling toward its quota. Existing pointers stay valid for interpreter and JIT code.
Stashed arguments are the complete live set. Cheney evacuation follows tuples, lists, maps, and binaries; forwarding words preserve shared structure.
[ok] lux loop: 100k tail-recursive iterations in bounded memory
The loop allocates a list and a map every turn with a heap cap small enough to kill the old runtime in roughly 200 iterations.
M11 does not add a friendly GPU API. It adds the small set of primitives from which a driver can be written without asking the kernel to understand its protocol.
PORT_SUBMIT2all-register SQE submissionport · op · arg0 · arg1 · tagBUF_NEW / WRITE / READmutable fixed-size kernel blobsoff-heap · pinned · GC-immunereceive after Ntimer-wheel sleepframe pacing without a busy loop{port_reply, …}completion as a messageordinary Lux receive semantics
The single protocol-shaped assist is OP_CTRL_ATTACH. Lux creates the
RESOURCE_ATTACH_BACKING prefix; the kernel appends one physical-address
record for the pinned buffer because verified bytecode must never see guest physical
addresses. Everything else remains opaque.
LUX-PORT-OK
A Lux process opens the serial port and emits the marker byte by byte through PORT_SUBMIT2. No new serial driver code was needed.
gpu_demo.lux is 212 lines of language-level driver: little-endian encoding,
response parsing, resource setup, framebuffer construction, presentation, and a paced
animation loop.
The harness expects white at (160, 138) and dark blue at (160, 60) and (160, 185). This image is that artifact—not a reconstruction.
receive after
Lux encodes each struct as a byte list, converts it to a binary once, submits it through
the raw GPU port, and parses the response. The render loop mutates only the changed 40
rows with buf_write, transfers the resource, flushes, sleeps, and tail-calls
the next frame.
let _draw = ygg::buf_write(backing, y * 1280, band)
let _shown = present(port, 2)
receive {
after 16 => anim_step(port, backing, band, erase, y, dy, n)
}
cargo xtask watchBoots the Lux GPU driver under QEMU with a visible GTK display.
The last Lux commit is not named after graphics. It is named after short-circuit booleans—and that is not incidental.
ok && anim_go(…)Evaluate both sides like ordinary calls; guarded recursion can stay nested and accumulate frame garbage.
case ok { true → anim_go(…) }Lower &&/|| to andalso/orelse semantics so the recursive branch remains in tail position.
A collector with exact roots is only as good as the compiler’s ability to reach those root boundaries.
With lazy lowering, guarded render loops return to the outer trampoline. That gives the collector the tiny root set it was designed around instead of leaving per-frame garbage trapped inside a strict caller.
Tail-recursive Lux iterations complete inside a deliberately bounded heap.
constant stack + compactionEvery Lux test passes, including trampoline-aware backend coverage.
cargo testRed, green, white rectangle, green margin, and blue are probed after screendump.
headless assertionBand center, background above, and background below are checked at frame 60.
headless assertionThe headless animation runs without a quota death or kernel panic.
soakThe complete two-boot suite still covers TCP echo, pcap, disk persistence, JIT, and SMP.
cargo xtask test[ok] lux loop: 100k tail-recursive iterations in bounded memory
LUX-PORT-OK
[ok] lux port: serial written via PORT_SUBMIT2
[ok] lux gpu: scene rendered via virtio-gpu
[ok] lux gpu: animation played via buf_write
[selftest] all passed
Nested calls and native drivers can retain invisible terms. Anywhere-GC needs stack maps; until then those paths grow within quota instead of moving unsafely.
The port pump waits on the control queue. Fence- and interrupt-driven completion is the next performance step, not a new architecture.
Virgl commands already fit the same opaque OP_CTRL surface, but QEMU needs virtio-gpu-gl and a GL-capable display to accept contexts.
CTX_CREATE, capset queries, resource creation, and SUBMIT_3D are just more command buffers a Lux process can encode and send through the port it already owns.