Quartz v5.25

Handoff — SYS.1 Complete (8/8 items landed)

Head: 0e5d7bde on trunk. Fixpoint stable at 2135 functions.

SYS.1 is substantively done. Every line-item from docs/KERNEL_EPIC.md’s SYS.1 table (“Bare-metal completeness”) has a landed commit. The compiler now exposes the full kernel-adjacent surface: atomic RMW completeness, Quake linker-script + target-knob passthrough, @weak / @panic_handler / @align(N) attributes, x86 fs/gs and arm64 TPIDR TLS intrinsics, and four custom calling conventions (C + x86-interrupt + preserve_all + preserve_most). A kernel-capable freestanding Quartz binary is now a matter of writing the kernel code, not adding language features — which is the forcing-function the Kernel Epic was designed around.

SYS.1 scorecard — what shipped this session

SHAItemScope
3f396d6ePlanning artifacts: ROADMAP Tier 6, KERNEL_EPIC.md, research/EFFECTS_IMPLEMENTATION_PLAN.md, research/NOTATION_PRIMER.md.
98da32951atomic_and / or / xor / min / max with all 5 orderings. 19-test spec. Rebased a parallel-session worktree onto trunk.
7c6a3a84Discovery: UFCS method-alias collision (.and / .or / .min / .max clash with lexer keywords / prior Array builtins).
c1ad8f232link_baremetal(objs, out, script, flags) primitive in std/quake.qz. New baremetal:verify Quake task wired to hand-written aarch64 asm + aarch64-virt.ld.
5f51a2a43assemble(src, out, target, flags) + compile_llc_to_obj(ll, out, flags) in std/quake.qz. baremetal:verify_x86_64_knobs task proves -mattr=-sse,-mmx,-avx -relocation-model=static -code-model=kernel flow through (negative check: 2 xmm instructions WITHOUT knobs, 0 WITH).
a24b7d965@weak function attribute. Emits LLVM define weak. 6-test spec.
790c2242Mid-session handoff (items 1, 2, 3, 5 state).
145a308b6@panic_handler attribute. Codegen routes every panic site (user + prelude unwrap paths) to registered handler, bypassing libc write/longjmp/exit. The unblocker for freestanding .qz compilation.
57386a198aextern "x86-interrupt" cconv → LLVM x86_intrcc. Parser enforces Void return; rejects bodyless.
e31aba4e7ax86 TLS intrinsics: tls_read_gs / fs, tls_write_gs / fs. Codegen uses LLVM addrspace(256) = gs, addrspace(257) = fs.
5fb3f7e37barm64 TPIDR intrinsics: tls_read_tpidr_el1 / el0, tls_write_tpidr_el1 / el0. Codegen uses single-instruction mrs / msr inline asm marked sideeffect.
73781f478bextern "preserve_all" + extern "preserve_most" cconvs → LLVM preserve_allcc / preserve_mostcc.
0e5d7bde4Struct-level @align(N) attribute. Power-of-2 validation at parse. Plumbed parser → AST → MIR → codegen. Codegen emits align N on hoisted @value alloca.

Recovery event mid-session. Between a24b7d96 and 790c2242 the user’s machine hard-reset — almost certainly from sustained CPU load plus subprocess leaks from an exploratory compiler probe that hit the pre-existing ps_error-non-advance infinite loop. Uncommitted @panic_handler WIP was stashed, known-good binary was restored from backup, and the slice was replayed cleanly with adjusted discipline (see “Workflow rules that worked” below). No data loss.

What’s actually in the compiler now

@weak                     # LLVM weak linkage on a function
@panic_handler            # replace prelude panic path with user handler
@align(N)                 # struct-level alignment (power of 2)
@naked                    # no prologue/epilogue (pre-existing)
@packed / @repr(C)        # struct layout (pre-existing)

extern "C"                # default
extern "x86-interrupt"    # → x86_intrcc; void return required; needs body
extern "preserve_all"     # → preserve_allcc
extern "preserve_most"    # → preserve_mostcc

# New intrinsics (system category)
tls_read_gs(offset) / tls_write_gs(offset, val)
tls_read_fs(offset) / tls_write_fs(offset, val)
tls_read_tpidr_el1() / tls_write_tpidr_el1(val)
tls_read_tpidr_el0() / tls_write_tpidr_el0(val)

# Quake (std/quake.qz) primitives
assemble(src, output, target, flags)               # clang-as-assembler
compile_llc_to_obj(ll, output, flags)              # llc with arbitrary knobs
link_baremetal(objs, output, linker_script, flags) # ld.lld -T

Verification targets added this session:

  • quake baremetal:verify — aarch64 ELF, entry 0x40000000.
  • quake baremetal:verify_x86_64_knobs — x86_64 kernel-knob IR passes with zero SSE instructions.

Discoveries filed (not yet acted on)

All live in docs/KERNEL_EPIC.md under “Discoveries During Implementation” plus commit bodies. Ranked rough impact first:

  1. @value struct escape-analysis promotion defeats @align(N) in practice. Even spec/qspec/stack_alloc_return_spec.qz’s @value-struct-returning functions emit call @malloc(i64 16) at the MIR_ALLOC_STACK site instead of a real alloca. Pre-existing — verified against the pre-align compiler backup. The @align(N) plumbing is ready; its observable effect widens the moment the escape analyzer leaves more @value structs on the stack. Recommended next session: investigate mir.qz’s escape-analysis pass (Compute which MIR_ALLOC_STACK registers escape their defining scope) for why simple @value struct-return patterns get flagged as escaped.

  2. cg_emit_int + cg_emit_line("") emission pattern miscompiles after self-compile. My first-draft @align codegen used cg_emit_int(out, _h_align) + cg_emit_line(out, "") to write the digit and newline — mirroring idiomatic emission elsewhere. The rebuilt compiler then emitted malformed IR with a dangling %push reference in socket$pipe_create. Reverted to N.to_s() + cg_emit_line(str) which produces identical output but went through a different SB path. Root cause unresolved — could be a subtle interaction between sb_append_int and the surrounding sb_append calls, or a self-compile quirk. File as a low-priority compiler-internals bug for a debugging session.

    Closed Apr 19, 2026. Not an sb_append_int interaction. The %push regression was the incremental-cache Tier 2 skip bug: the warm-cache path was skipping TC+MIR for modules with no cached fragment, so UFCS rewrite (vec.push(x)vec_push intrinsic) never fired. Different emission patterns happened to shift changed_modules enough to shuffle what got skipped, which is why the to_s() workaround made it “go away” — it never actually was the sb_append_int pattern. Three-part fix in self-hosted/quartz.qz + dep_graph.qz: per-module fragment check before invalidation, short-name normalization of recompile_set, and TC-skip gate resolving owning module via ast_store rather than function-name prefix. Full diagnosis in docs/handoff/pipe-create-push-cache-bug.md; ROADMAP entry flipped to fixed.

  3. Parser hang on multi-line extern "X"<NL>def ... header. The has_body indentation heuristic confuses itself when extern "..." is on its own line above the def. Hangs the compiler with a malformed program. Confirmed present in both current and pre-x86intr compiler backups — pre-existing, not introduced by this slice. Workaround in all new specs: single- line headers.

  4. Parser ps_error non-advance causes infinite loop on unknown attributes (e.g. @bogus). Same pre-existing pattern already noted in session memory — hangs on malformed input because error recovery doesn’t consume the offending token. Contributed to the Mac hard-reset earlier this session via sustained CPU + subprocess accumulation.

  5. UFCS method-alias collision on atomic RMW (filed in 7c6a3a84). .and / .or collide with TOK_AND / TOK_OR; .min / .max shadow Array builtins. Free-function form works for all five; the .xor alias shipped. Cosmetic, non-blocking.

Workflow rules that worked (after the crash)

These changed mid-session and every commit after has been clean:

  1. Write the QSpec test before rebuilding the compiler. One build invocation gets to verify both the feature AND the spec. Avoids build / probe / build / probe churn.
  2. Never run timeout N ./self-hosted/bin/quartz <file> on a file that might hang the parser. Use fixtures that either compile cleanly or fail fast (known error messages).
  3. One quake build and one quake guard per slice. Do not chain. Each is ~90 seconds at peak CPU; chaining five of them was how the session got in trouble earlier.
  4. pgrep -af "quartz|llc|clang" before every heavy step. Confirm no leaked subprocesses from prior timeouts. Kill stragglers with pkill -9 before proceeding.
  5. Fix-specific backups before compiler changes. cp self-hosted/bin/quartz self-hosted/bin/backups/quartz-pre-<slice>-golden. Used twice this session (quartz-pre-panic-handler-golden, quartz-pre-align-golden) — both saved debugging cycles.
  6. Smoke tests after every guard. brainfuck.qz + style_demo.qz. Fixpoint alone is insufficient (CLAUDE.md Rule 2) — I learned firsthand that a self-consistent broken compiler can miscompile end-users of the change (the %push / socket bug) while still passing fixpoint.

Where to point next session

Two productive threads, independent:

Thread A — tighten the escape analyzer (unblocks @align + other @value benefits)

See discovery #1. The escape analyzer is in self-hosted/backend/mir.qz around the line noted in earlier exploration (“Compute which MIR_ALLOC_STACK registers escape their defining scope”). Even trivially-non-escaping @value struct returns like make_point(x, y, z) are getting promoted. Investigating this would unlock:

  • Real stack allocation for the alignment work just shipped.
  • Better runtime performance on @value struct-heavy kernel code.
  • Accurate MIR escape metadata for other optimizations.

Start by compiling spec/qspec/stack_alloc_return_spec.qz with --dump-mir and inspecting which MIR_ALLOC_STACK registers get flagged as escaped. The inputs are hand-crafted to be non-escaping; if they’re getting flagged anyway, the analyzer is over-conservative.

Thread B — start KERN.1 (unikernel synchronous parts)

Per docs/KERNEL_EPIC.md’s KERN table, KERN.1 is blocked on SYS.1 and SYS.5 (x86_64-unknown-none parity with aarch64-virt). SYS.1 is now unblocked. SYS.5 is a 2-3 day dependency: write tools/baremetal/x86_64-multiboot.ld + tools/baremetal/hello_x86.qz, verify boot on qemu-system-x86_64. Essentially the x86_64 twin of the aarch64-virt infrastructure already in the tree.

After SYS.5, KERN.1 proper: GDT / IDT setup, exception handlers, timer driver, serial console, physical memory manager, paging setup. Big epic, ~1.5-2 quartz-weeks per the roadmap estimate. The language features needed are all now in place. Rust / Zig / C kernel implementations make good prior-art reference.

Smaller one-session follow-ups (if the big threads aren’t appealing)

  • Fix the ps_error infinite-loop on unknown attributes (discovery #4). Root cause known — ps_error doesn’t advance. Recovery loop should explicit-break. Small parser patch, prevents future hangs / crashes.
  • Fix the multi-line extern header hang (discovery #3). has_body indentation heuristic needs a smarter check.
  • Field-level @[align(N)]. The deferred half of SYS.1 item 4. Requires changing the flat i64-slot field-layout model in MIR to insert padding between fields. Medium-large scope — not this-session-sized.
  • Investigate the cg_emit_int self-compile quirk (discovery #2). Low-priority but satisfies curiosity.

State of the tree (for quick re-orientation)

  • Branch: trunk
  • HEAD: 0e5d7bde — SYS.1 item 4 (partial): struct-level @align(N)
  • Fixpoint: 2135 functions, gen1 == gen2 byte-identical, verified
  • Smokes: brainfuck + style_demo both PASS
  • Backup binaries saved this session:
    • self-hosted/bin/backups/quartz-pre-panic-handler-golden
    • self-hosted/bin/backups/quartz-pre-x86intr-golden
    • self-hosted/bin/backups/quartz-pre-tls-intrinsics-golden
    • self-hosted/bin/backups/quartz-pre-arm64-tls-golden
    • self-hosted/bin/backups/quartz-pre-preserve-cconv-golden
    • self-hosted/bin/backups/quartz-pre-align-golden

All SYS.1 spec files green:

  • spec/qspec/atomic_rmw_spec.qz — 19/19
  • spec/qspec/memory_ordering_spec.qz — 25/25 (regression check)
  • spec/qspec/weak_attribute_spec.qz — 6/6
  • spec/qspec/panic_handler_spec.qz — 6/6
  • spec/qspec/x86_intr_cconv_spec.qz — 5/5
  • spec/qspec/tls_intrinsics_spec.qz — 6/6
  • spec/qspec/tls_arm64_spec.qz — 6/6
  • spec/qspec/preserve_cconv_spec.qz — 5/5
  • spec/qspec/align_attr_spec.qz — 6/6

Baremetal Quake tasks both green:

  • quake baremetal:verify — aarch64 ELF at entry 0x40000000
  • quake baremetal:verify_x86_64_knobs — x86-64, no SSE, kernel knobs applied

Safety rails (unchanged from mid-session handoff)

  • quake guard mandatory before any commit that touches self-hosted/*.qz — pre-commit hook enforces. Don’t --no-verify.
  • Full QSpec suite NOT safe in Claude Code PTY — use quake qspec_file FILE=... for targeted runs.
  • Smokes (brainfuck + style_demo) after every guard. Fixpoint alone is insufficient.
  • Never ignore a subprocess leak from a timed-out quartz run — the May 18 hard-reset was caused by sustained CPU + leaked quartz processes from ps_error infinite loops. Kill stragglers before escalating to the next heavy op.