Skip to content

seams: a record inside a packet, the cook's own header, Elixir's bits consumed, §4.8 struck - #973

Draft
gafferongames wants to merge 2 commits into
fixed-table-formfrom
rowan/seams-carrier-cook-bits
Draft

gafferongames wants to merge 2 commits into
fixed-table-formfrom
rowan/seams-carrier-cook-bits

Conversation

@gafferongames

Copy link
Copy Markdown
Contributor

Four RULINGS from Rowan, final. Mostly docs, with two small code items.

(8) A fixed record inside a packet — the hash rides, and the packet's reader validates it like a file's

docs/FIXED-FORM-ALGORITHM.md §2 said only that inside a packet no form byte is written, and docs/SPEC-TABLES.md §3 said "no form byte, no header and no layout of its own" — which left two things unwritten: whether the eight-byte layout hash rides, and who validates the record.

Ruled: the record rides as the record exactly as it sits in a file, hash + body — no form byte, no file header, no layout bytes. The layout is not on the wire; the reader takes it from its own lineage, named by the record's hash. The packet's reader validates that one record exactly as §5.3 validates a file's — select by hash, the floor, the byte comparison (against the lock's own layout, there being no layout bytes to compare), the plan and record size, the pre-pass over the one record, the landing through the identity lane or the plan — refusing by the same names. A packet carries no count and no rest, so §5.3 steps 1–4, 9 and 10 have nothing to do.

Written into:

  • docs/SPEC-TABLES.md §3.4's framing table as a fourth carrier row ("INSIDE A PACKET"), and its heading is no longer "IN ALL THREE CARRIERS"
  • docs/SPEC-TABLES.md §3's packet bullet, rewritten — it previously called the construct "the MESSAGE form by rule" and said no layout of its own, which read as the hash not riding
  • docs/FIXED-FORM-ALGORITHM.md §2 and §5.3

Does any packet emitter already carry a fixed record? No. There is no fixed-record field kind in any of the nine packet emitters — every layout-hash emitter lives in a *table package — and internal/check refuses the field one rule earlier (a table, fixed or not, in a type body is "is a table, not a wire type"; pinned by TestTableRefusals and internal/check/fixedclosurerows_test.go). So the rule is ahead of the code, and it is recorded as a ruling: §5.9 #49.

(9) A fixed cook is not a form-3 record

§3.4 justified the sixteen pad bytes "so that the bytes do not move again when the cook and the block form join the registry under the same header". Struck — here and in §3's twin of the same sentence, and in ir/fixedform.go's comment.

The cook keeps its own 64-byte header with SCHMCOOK at byte 0 (§7.1) and the block form its generated SCHMABLK prologue (§19.1). A magic that must be read bytewise at offset 0 to refuse a foreign byte order cannot share a header with a form byte at offset 0, so neither accelerator will ever adopt those sixteen bytes; what the registry reserves 4 and 5 for is a number and nothing else.

New §3.4 block THE THREE PROJECTIONS ARE INDEPENDENT FLATTENINGS states the three with their first bytes side by side, and closes the memory note's open question ("fixed cook = form-3 record?") with no: the wire form's hash selects a layout out of a lineage and reads a stranger's bytes backward; the accelerators' magic refuses a foreign byte order and their build version a foreign build outright.

The pad bytes' real reason: none remains. The records do not begin at a multiple of sixteen — the variable-length layout sits between them and the header — so the padding buys no alignment today. It stays for wire stability and nothing else: those bytes are written and read by shipped legs.

(13) Elixir reports bits consumed

read_<name>/2 returned {:ok, value} | :error and threw the count away (_ = bits_read), where SPEC §5 says Read reports bits consumed.

  • New: read_<name>_bits(data, num_bits){:ok, value, bits_read} | :error
  • Kept: read_<name>/2, now delegating to it with the count dropped — same verdict, same refusals
  • Both emitted for every type and every union (internal/codegen/elixir); the head wraps its guard when the one-line form is past the formatter's width, so mix format --check-formatted stays green
  • The name is claimed in internal/check's registry beside read_<name>, so a type FrameBits next to a type Frame is refused rather than silently taking the same module binding
  • Test: test/elixir/suite.exs frames two ProbeHeaders in one buffer and reads the second from div(bits_read + 7, 8) bytes in, asserts the count equals measure_probe_header, asserts read_<name>/2 is the same read with the count dropped, and repeats it on a seven-bit union where the count is not a whole number of bytes — plus a refusal vector that returns :error with no count
  • docs/SPEC.md §6.1 carries the Elixir row: the consumed size is a second entry and not a second argument, a binary carrying no position

(14) §4.8's packet rule for non-type union arms is struck

§4.8 fixed the packet wire for a non-type arm — "The packet wire's rule is nevertheless fixed, so no port guesses it" — and no leg implemented it, a type body being unable to declare such an arm at all. One sentence remains: such arms are refused by name today and the wire rule is unassigned until a schema needs it.

The checker already refuses them by name, in two places (internal/check/check.go — the type-body refusal and checkTableArmsReached), pinned by TestTableRefusals, TestDiagnostics and TestScalarArmInTypeBodyIsRefusedByCheck. What had no pin was the page, so TestPagesPlaceTheVoidArmOutsideTheTableClosureClass now pins the strike in both directions — the struck sentence must be gone and the new one present — so the rule cannot come back quietly.

Gates

  • gofmt -l . — clean
  • go test ./compiler/ ./internal/check/ ./ir/ ./internal/goldens/ — green (-run 'DocExamples|Pages' included)
  • mix format --check-formatted over every generated Elixir file — green
  • test/elixir/main.exs (the packet subset, with the new bits-consumed test) — OK
  • regen + goldens for Elixir: generated/elixir, generated/elixir-ludicrous, generated/bench/elixir, testdata/golden/{elixir,ludicrous/elixir,packet-wide/elixir}

Follow-up, not in this PR

Four table-leg runtime emitters still repeat the struck justification in a comment they emit into generated code (internal/codegen/{ctable,cpptable,darttable,elixirtable}/fixedruntime.go). Fixing them churns each leg's committed table goldens, which is a wider regen than this PR's scope; the Go-side comment in ir/fixedform.go is fixed here.

🤖 Generated with Claude Code

… consumed, §4.8 struck

Four rulings, mostly docs with two small code items.

(8) A FIXED RECORD INSIDE A PACKET. §3.4 had a three-carrier framing table and
§3 said "no form byte, no header and no layout of its own" — which left whether
the eight-byte hash rides, and who validates, unwritten. It rides: the record is
the record exactly as it sits in a file, hash + body, with no form byte, no file
header and no layout bytes, and the packet's reader validates it exactly as §5.3
validates a file's, refusing by the same names. Written into §3.4's framing table
as a fourth carrier row, into §3's packet bullet, into FIXED-FORM-ALGORITHM §2
and §5.3. No packet emitter has a fixed-record field kind today and the checker
refuses the field one rule earlier, so the rule is ahead of the code: recorded as
§5.9 #49.

(9) A FIXED COOK IS NOT A FORM-3 RECORD. The sentence justifying the sixteen pad
bytes by the cook and the block form joining the registry under the same header
is struck, in §3.4 and in §3's twin of it. The cook keeps its 64-byte header with
SCHMCOOK at byte 0 and the block form its SCHMABLK prologue; a magic that must be
read bytewise at offset 0 cannot share a header with a form byte at offset 0. New
§3.4 block THE THREE PROJECTIONS states the three as independent flattenings. No
second reason for the padding remains — the records do not begin at a multiple of
sixteen, the layout between them and the header being variable-length — so it
stays for wire stability and nothing else.

(13) ELIXIR BITS CONSUMED. The packet reader discarded bits_read where §5 says
Read reports it. read_<name>_bits/2 now returns {:ok, value, bits_read} | :error
and read_<name>/2 delegates to it with the count dropped; both are emitted for
every type and every union, the verdict and refusals identical. The name is
claimed in internal/check's registry beside read_<name>, so a `type FrameBits`
beside a `type Frame` is refused rather than silently taking the binding.
test/elixir frames two objects in one buffer and reads the second from the
ceiling of the first's count, including a seven-bit union where the count is not
a whole number of bytes. SPEC §6.1 carries the Elixir row.

(14) §4.8's PACKET RULE FOR NON-`type` UNION ARMS IS STRUCK. No leg implemented
it and no `type` body can declare such an arm, so the page said what nothing
read. One sentence remains: such arms are refused by name today and the wire rule
is unassigned until a schema needs it. The refusal is already pinned by
internal/check's TestTableRefusals and TestDiagnostics and by
TestScalarArmInTypeBodyIsRefusedByCheck; the pages test now pins the strike so
the rule cannot come back quietly.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant