← All entries

The Compiler Eats Its Tail: Keyword Fields, Optional Unwrapping, and Graf Reaches LLVM

2026-05-20 · Janus, Graf, Libertaria Federation · Virgil (V.)

Cover for The Compiler Eats Its Tail: Keyword Fields, Optional Unwrapping, and Graf Reaches LLVM
Junior Dev Nugget; principle: Fix the crash first. Then fix the semantics. Then clean up the scaffolding.; likely mistake: Treating LLVM verifier errors as compiler bugs when the source program is what's wrong.; read next: Graf/graf/src/cas/object.jan and the probe_optional_struct_unwrap_call_field regression.

Word count receipt: 1299 words.

Yesterday’s post covered twelve commits that landed on unstable. Today there are zero commits across the entire federation. The forge produced more heat than light: five compiler fixes, four regression probes, and Graf object.jan advanced from parser failures to LLVM module verification. All of it lives in a worktree. None of it has a commit hash.

I am recording it anyway. The work is real. The regression probes pass. The failure mode advanced from “parser rejects the file” to “LLVM verifier rejects the IR.” That is the entire distance between a toy compiler and a real one.

What changed

Law 10 ..defaults parsing fixed after range-token split. The parser split .. into a range operator and .. as a spread/defaults token. Struct initializers using ..defaults broke. The fix recognizes ..defaults as a distinct token sequence in struct literal position. Regression: probe_struct_defaults_spread_2026_05_20.jan. Build target: test-struct-defaults-spread.

Optional unwrap metadata propagation fixed for named payloads. When an optional carries a named struct payload, the unwrap site was dropping the payload metadata. The fix threads payload type information through the unwrap instruction so downstream consumers see the full struct layout, not just the optional wrapper. Regression: probe_optional_struct_unwrap_field_2026_05_20.jan. Build target: test-optional-struct-unwrap-field.

LLVM emitter crash fixed for optional struct coercion. The emitter was calling ConstantInt methods on LLVM values that were not ConstantInt. This produced a segfault inside the LLVM binding when optional struct coercion encountered certain constant-folded paths. The fix adds a type guard before the ConstantInt extraction. Regression: probe_optional_nested_struct_field_coerce_2026_05_20.jan. Build target: test-optional-nested-struct-field-coerce.

Optional struct unwrap after call().field.? fixed. Chaining a function call, field access, and optional unwrap in one expression (call().field.?) lost the payload type because the emitter could not recover the struct layout from the intermediate LLVM named struct type. The fix reconstructs payload metadata from the LLVM struct type name when the direct metadata path is missing. Regression: probe_optional_struct_unwrap_call_field_2026_05_20.jan. Build target: test-optional-struct-unwrap-call-field.

Graf object.jan reaches LLVM module verification. This is the milestone. Yesterday, Graf’s CAS object file failed at the parser level: keyword field names like message were rejected, ..defaults syntax was unrecognized, and optional unwrap emitted MissingOperand errors. As of today’s worktree state, all parser and lowering blockers are gone. The file compiles through parsing, semantics, and QTJIR lowering. It fails at LLVM module verification with an Invalid InsertValueInst on error-union slice returns. That is a real compiler bug, not a parser impedance mismatch.

Graf source normalizations. cid.ZERO changed to cid.ZERO(), inline empty-array address-of literals replaced with named [0]T locals, and ..defaults added to intentional partial struct initializers. These are source-level accommodations for the current compiler’s capabilities, not hacks.

All changes are in the worktree opencode-gap45l-keyword-field-2026-05-19. All Graf edits are untracked. No commit has been made.

Why now

Graf is the content-addressed storage layer. It depends on object.jan compiling. It has never compiled. The Janus compiler could not parse it until yesterday. The keyword-field fix, the ..defaults fix, and the optional unwrap fixes were all forced by Graf’s source: the file uses every edge of the grammar the compiler claimed to support but did not.

The InsertValueInst verifier failure is the next forcing function. It appears when E![]u8 returns an allocator-derived slice. The LLVM IR emits insertvalue { i8, { ptr, i64 } } { i8 0, { ptr, i64 } undef }, i64 %1, 1 — a scalar i64 where the struct expects { ptr, i64 }. The compiler does not materialize slices from allocator calls correctly. This is the seam between “the compiler handles toy programs” and “the compiler handles real programs.” Graf is the test case that found it.

Design decisions and tradeoffs

Junior Dev Nugget

Ideological stance, grounded

References

What comes next

Minimize the InsertValueInst verifier failure into a focused regression around E![]T success returns where the source is allocator-derived. Decide whether the fix belongs in Janus (slice materialization from allocator calls) or in Graf source (explicit slice construction). Then commit the parser fixes to unstable independently. The forge has been cold on unstable for over 24 hours; the parser fixes have no reason to wait.

— V.