Handoff — Binary DSL Phase 2 Track B SHIPPED
Head: f1f82b6d on trunk. Fixpoint stable at 2111 functions.
Binary DSL test count: 112 (93 prior + 19 new). All green.
Phase 2 is now FULLY SHIPPED — Tracks A (computed fields), C (arrays), and B (discriminated unions) are all in trunk with real bidirectional codegen.
What shipped in Track B (3 commits)
| Commit | STEP | What |
|---|---|---|
f38e3e49 | B1 | Parser: match IDENT / N => { ... } end inside binary blocks. 2 new AST kinds (NODE_BINARY_UNION=98, NODE_BINARY_UNION_ARM=99). 6 parser tests. |
5105cc78 | B2 | Typecheck: flat-union struct registration + 4 new error codes (QZ0954–QZ0957). 7 typecheck tests. |
f1f82b6d | B3+B4+B5 | MIR plumbing (9 new parallel tables) + PACK/UNPACK codegen + TCP-options roundtrip spec. 6 end-to-end tests. New ParseError variant InvalidDiscriminant (tag=3). |
Surface
type TcpOption = binary {
kind: u8
match kind
0 => { } # END_OF_LIST
1 => { } # NOP
2 => { mss: u16be } # MSS option
8 => { tsval: u32be; tsecr: u32be } # Timestamps
end
}
TcpOption decodes via TcpOption.decode(bytes) returning
Result<TcpOption, ParseError>. Arm fields are flattened into the
outer struct (v1 design); got.mss / got.tsval / got.tsecr are
accessible alongside got.kind. Fields for inactive arms are zeroed.
Encode:
TcpOption { kind: 2, mss: 1460, tsval: 0, tsecr: 0 }.encode()→[0x02, 0x05, 0xB4](3 bytes).- Unknown discriminator at encode time is tolerated — just the prefix is emitted. Decode is strict (sees InvalidDiscriminant).
Decode error branches:
Err(UnexpectedEof)— buffer shorter than prefix, or shorter than prefix + matched arm’s byte length.Err(InvalidDiscriminant)— discriminator value doesn’t match any arm. Unit variant instd/binary.qz::ParseError.
v1 scope / out-of-scope
In:
- Fixed-width prefix (any mix of byte-aligned + sub-byte ints, including straddle).
- Arm fields are byte-aligned integer primitives (u8/u16/u32/u64 with explicit le/be suffix). Endianness per field.
- Empty arm bodies (
{ }) for TCP-NOP-style zero-byte variants. - Cross-arm field-name reuse with identical wire spec (merges into one struct slot). Conflicting specs → QZ0957.
- One union per block.
Out (future extensions; MIR tables already carry the info, code path would just need lifting):
- Sub-byte arm fields (e.g.
match kind / 1 => { flags: u4; pad: u4 }). - Variable-width arm fields (pstring, cstring, bytes, arrays).
- Variable-width prefix fields before a union.
- Multiple unions per block.
- Implicit-enum sugar for the decoded value (currently flat struct). The handoff doc discussed this under STEP B2 — it’s a pure ergonomics layer on top of the existing MIR metadata and can land later.
Files touched
self-hosted/frontend/node_constants.qz— 2 new NODE kindsself-hosted/frontend/ast.qz— ast_binary_union + ast_binary_union_armself-hosted/frontend/parser.qz— ps_parse_binary_union + call-site in ps_parse_binary_block_bodyself-hosted/middle/typecheck.qz— _tc_bin_push_field dedup helper + union-aware tc_register_binary_block_def + validatorself-hosted/backend/mir.qz— 9 new MirProgram tables + mir_register_binary_union + 6 accessorsself-hosted/backend/mir_lower.qz— mir_collect_binary_layouts strip-union-to-side-tables + mir_collect_structs flatten-arm-fieldsself-hosted/backend/cg_intrinsic_binary.qz— _cg_bin_emit_pack_union + _cg_bin_emit_unpack_union + 3 helpersstd/binary.qz— new InvalidDiscriminant variantspec/qspec/binary_union_parse_spec.qz(6 tests)spec/qspec/binary_union_typecheck_spec.qz(7 tests)spec/qspec/binary_union_spec.qz(6 end-to-end tests)
Pre-existing bug discovered (NOT a Track B regression)
to_str() crashes (SIGSEGV) on integer values ≥ ~300 million when
used inside string interpolation. Verified pre-existing — reproduces
with puts("x=#{0x12345678}") or puts("x=#{305419896}"). Smaller
values work, direct access (e.g. puts(to_str(x)) with x = 0x12345678)
also works. Only #{} interpolation with large ints crashes. Filing
for next session as a P1 compiler bug. Track B roundtrip tests use
small values (100/200) to dodge this.
Next session options
- Phase 2 Track B v2 — lift the arm-field scope restrictions. Sub-byte arm fields and pstring/bytes/arrays inside arms. The MIR tables already carry spec + width; the arm emitter just needs to call the variable-path helpers instead of inline byte loops.
- Implicit-enum sugar for union decode values. Currently arm
fields are flat on the outer struct; an implicit enum would make
match opt / Mss(mss) => ...ergonomic. Purely additive — the wire layout is untouched. - Phase 3 dogfood. Migrate compiler internals to the DSL — intmap header, channel layout, Future state machines, MIR-instruction encoding, AST-node layout. All the groundwork is now stable.
- Fix the to_str interpolation bug. P1 compiler bug filed above.
- Kernel Epic (SYS.1 atomic RMW). Tier 6 unikernel/bare-metal work from the April 17 planning session.
Safety rails
quake guardmandatory before every commit — pre-commit hook enforces it. Do not--no-verify.- Smokes (brainfuck + expr_eval) after every guard. Both green at head.
- Fix-specific backup from this session:
self-hosted/bin/backups/quartz-pre-binary-phase2-trackb-golden. - Full QSpec not safe in Claude Code PTY — use
quake qspec_file FILE=...for targeted runs.