tables: a float rides as its bit pattern, pinned; the tool's cook-check carries the map slot and the list halves (#480, #380) - #678
Merged
Conversation
The page said a float32's payload is 32 raw IEEE-754 bits and nowhere said
those bits ride UNCHANGED. `grep -n NaN docs/SPEC-TABLES.md` found the widened
rung's aside and nothing else; PORTING.md had no row; no corpus instance
carried a signalling NaN. A backend holding a float32 in a wider cell quiets
one through the hardware conversion, and the byte identity nine languages hold
over one corpus fails on exactly those values.
The RED, before the fix: the tool's own cook is one such backend.
`TestACookCarriesAFloatBitPattern` over the new pin reported
the signalling slot holds 0x7FC00001 and the pattern is 0x7F800001
the negative slot holds 0xFFE5A5A5 and the pattern is 0xFFA5A5A5
the wire did not survive the cook: 127 bytes back, 127 pinned
because internal/tablecook/region.go wrote `math.Float32bits( float32( cell.F ) )`
and uncook.go read `float64( math.Float32frombits( ... ) )` — a float64 cell
with a conversion at each end.
The sentence: SPEC.md §4.3 gains the wire-model bullet and the float32/float64
rows say it; SPEC-TABLES.md §3 gains the paragraph beside the kind-10/11
payload row. PORTING.md gains M21, the technique with its nine cells — cpp and
the tool carried, the eight ports a row on #366.
The pin: test/tables/F1.schema and F2.schema, the second the same root with
every float32 respelled float64 (§4's float rung). test/tables/main.cpp writes
testdata/wire/tables/floats_nan.bin and reloads it with every other golden, so
the big-endian leg covers it too. The patterns are a signalling NaN
(0x7F800001), a NaN with a mantissa past the quiet bit (0x7FC0DEAD), a negative
signalling one (0xFFA5A5A5), two array elements, and both double widths.
The claim and its control: test/tables/floatnan_main.cpp asserts the writer's
bytes, the identity read and the widened read; `make
tables-float-nan-negative-control` puts the hardware conversion back in
TableWidenF32 through an overlay, regenerates only F1/F2 from it, and the pin
goes red on the payload — 0x7ff0000020000000 read back as 0x7ff8000020000000,
the quiet bit the conversion set.
The Go oracle passes the pin too (internal/tablewire/floatnan_test.go), and the
tool's cook now carries it: WidenF32 and NarrowF32 are exported from
internal/tablewire so one pair serves the wire engine and the cook engine.
THE EIGHT PORTS ARE NOT THIS PR'S. Their legs are a row on #366, and PORTING.md
M21's cells say so.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…uncook carry the list (#380) Two clauses SPEC-TABLES.md listed as owed, and the tool refused both by name. The RED, quoted from the tree before this commit. internal/tablecook/check.go:212, on the cook the C++ reference wrote for `Army` (make tables-lists): node 1 (Army at offset 0): Army.squads: Squad.roster: a map slot, and `cook-check` carries no map-slot clause yet (docs/SPEC-TABLES.md §7.4): the C++ reference reads the cook (--lang cpp), and the tool's clause is schema#380's next PR compiler/tableslists.go:58, through `schema uncook` over the same file: unit declares an unbounded array in a table closure (...): the tool's WIRE and TEXT halves carry the construct and `cook-check` reads one, and its COOK half does not, so this command would lay out a region short of the element arrays rather than refusing. The C++ reference carries the cook THE MAP-SLOT CLAUSE (§7.4, check.go). The four clauses a list's slot already took — containment, alignment, fit and no overlap — are now ONE helper, `placedArray`, because the two slots are one shape and lists and maps are one population in a node's extent. On top of it the map takes the fifth a list has no analogue for: the keys read ASCENDING WITH NO REPEAT, at the key's own signedness for an integer and bytewise against the used length for a bounded string, because a cook a `Find` cannot search is a forgery. `./bin/schema cook-check --root Army build/lists-cooks/army.cook tables/lists` now reports ok, and `make tables-lists` asserts that instead of the refusal. THE LIST'S COOK AND UNCOOK HALVES (§2.9, §7.6). internal/tablecook/extent.go is the node extent in the tool: `recordHasExtent`, the record's storage rounded to the region's alignment floor, and ONE pre-order carve walk with its writing half switched off for the measure, so the two cannot drift. Layout sizes an extent-bearing node from the instance it walked; Write fills each slot's sixteen bytes with the self-relative delta and the count and holds the extent written to the extent measured; Uncook reads them back. THE TWO WRITERS MEET, which is the evidence that matters: `make tables-lists-tool-cook` has the generated `SaveCook` land on the tool's own bytes EXACTLY, in both byte orders, over test/tables/L1.schema — a list-bearing unit with no map in it, because the tool's cook still refuses a map-bearing one. 352 bytes, little and big. The CLI round trip runs beside it. CONTROLS. tables-maps-cook-check-negative-control: the SHARED containment sabotage must turn the map's test red beside the list's, and the keys' ascending test gets its own sabotage, because a shared control would leave the fifth clause untested. tables-lists-tool-cook-negative-control: the carve lays an array unaligned and the extent's shape goes red. THE PAGE. SPEC-TABLES.md §7.4 clause 4 loses "Until schema#380 lands this clause in the tool"; §21's maps bullet loses "which is the one piece still owed" and now names the tool's COOK and UNCOOK halves as what remains for a MAP; §21's unbounded-arrays bullet loses "the tool's COOK and UNCOOK halves are owed beside the map's". PORTING.md M19 carries the cook-check clause and its two controls. WHAT IS STILL OWED, unchanged: the tool's COOK and UNCOOK halves for a MAP. The map adds the sort, the entry array's key order and the two reader events to the extent the list's halves already carry, and refuseToolMaps still names a map-bearing unit at those two surfaces. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
golangci-lint v2.12.2 on the PR, both in internal/tablewire/floatnan_test.go: godot on a declaration comment that did not end in a period, and unconvert on a `uint32(...)` around a value already `uint32`. No behaviour moved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`go run .../modernize@v0.23.0 ./...`, which the lint job runs after
golangci-lint:
internal/tablecook/check.go:379:5: if statement can be modernized using max
internal/tablecook/check.go:390:5: if statement can be modernized using min
Both are clamps in the key comparison, now min/max. Nothing moved: the local
run of the same analyzer is silent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…haring the helper The rule's third place was 'a text cell'; the text writer refuses every non-finite float and the reader counts a NaN token as a kind mismatch, so no NaN payload rides through text. The reference sentence named decode.go's kind-10 arm as a reader of the exported pair; it tests the predicate inline and calls the shared helper, and the page now says so. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two gaps in the C++ column's contraction pass, red first, in one PR.
Closes #480
Part of #380 (the tool's cook and uncook halves for a map stay owed there, and
refuseToolMapspoints at it)1. A float rides as its bit pattern (#480)
The gap. A float32 NaN payload rides as its 32 bits in every port, and the page never said so.
grep -n NaN docs/SPEC-TABLES.mdfound the widened rung's aside about payloads being exactly representable and nothing that made the bits a RULE; docs/PORTING.md had no row; no corpus instance carried a signalling NaN. A backend holding a float32 in a wider cell quiets one through the hardware conversion, and the byte identity nine languages hold over one corpus fails on exactly those values.The red. The tool's own cook is one such backend. Against the new pin, before the fix:
internal/tablecook/region.gowrotemath.Float32bits( float32( cell.F ) )anduncook.goreadfloat64( math.Float32frombits( ... ) )— a float64 cell with a conversion at each end.The fix.
docs/SPEC.md§4.3 gains a wire-model bullet and itsfloat32/float64rows say it;docs/SPEC-TABLES.md§3 gains the paragraph beside the kind-10/11 payload row.docs/PORTING.mdM21, with its nine cells: cpp and the tool carried, the eight ports❌ #366.test/tables/F1.schemaandF2.schema(the same root one generation on, every float32 respelled float64 — §4's float rung).testdata/wire/tables/floats_nan.bincarries a signalling NaN0x7F800001, a NaN with a mantissa past the quiet bit0x7FC0DEAD, a negative signalling one0xFFA5A5A5, two signalling array elements, and two double patterns.test/tables/main.cppwrites it and reloads it with every other golden, so the big-endian leg covers it.test/tables/floatnan_main.cppasserts the writer's bytes, the identity read and the widened read.make tables-float-nan-negative-controlputs the hardware conversion back inTableWidenF32through an overlay, regenerates only F1/F2 from the sabotaged emitter, and the pin goes red on the payload:0x7ff0000020000000read back as0x7ff8000020000000, the quiet bit the conversion set.TestOracleCarriesTheFloatBitPattern,TestOracleWidensTheFloatBitPattern) and the tool's cook (TestACookCarriesAFloatBitPattern).WidenF32/NarrowF32are exported frominternal/tablewireso one pair serves both.The text form is unreachable by design. #480 named wire, text and cook rows; the text writer refuses every non-finite float and the reader counts a NaN token as a kind mismatch (§16.2), so there is no text row to carry, and M21 says so.
The eight ports are not this PR's. Their legs are a row on #366, and M21's cells say so.
2. #380's residue: the tool's cook-check and the list halves
The red, quoted from the tree.
internal/tablecook/check.go:212, on the cook the C++ reference wrote forArmy:compiler/tableslists.go:58, throughschema uncookover the same file:The map-slot clause. The four clauses a list's slot already took are one helper now,
placedArray, because the two slots are one shape and lists and maps are one population in a node's extent. On top of it the map takes the fifth: the keys read ascending with no repeat, at the key's own signedness for an integer and bytewise against the used length for a bounded string.The list's cook and uncook halves.
internal/tablecook/extent.gois the node extent in the tool: one pre-order carve walk with its writing half switched off for the measure, so the two cannot drift.Layoutsizes an extent-bearing node from the instance it walked,Writefills each slot's sixteen bytes and holds the extent written to the extent measured,Uncookreads them back.The two writers meet.
make tables-lists-tool-cookhas the generatedSaveCookland on the tool's own bytes exactly, 352 bytes, in both byte orders, overtest/tables/L1.schema— a list-bearing unit with no map in it. The CLI round trip runs beside it.Controls.
tables-maps-cook-check-negative-control(the shared containment sabotage must turn the map's test red beside the list's, and the keys' ascending clause gets its own, because a shared control would leave the fifth clause untested) andtables-lists-tool-cook-negative-control(the carve lays an array unaligned).The page lines retired. §7.4 clause 4 loses "Until schema#380 lands this clause in the tool"; §21's maps bullet loses "which is the one piece still owed"; §21's unbounded-arrays bullet loses "the tool's COOK and UNCOOK halves are owed beside the map's".
Still owed, and unchanged: the tool's COOK and UNCOOK halves for a MAP. A map adds the sort, the entry array's key order and the two reader events to the extent the list's halves already carry.
refuseToolMapsstill names a map-bearing unit at those two surfaces, and the page now says that is what remains.Goldens
make update-goldensadds exactly one file:testdata/wire/tables/floats_nan.bin. No conformance json, report or cook-write fixture moved.The six maps headers
make update-goldensonce wanted to move, and the two gates (tables-json-list-walk,tables-block-zero-cost) that were red on this branch's base, were main's: #668 re-pinned the headers and #674 derives the list-walk set from the IR. Merged with main both gates exit 0; on the bare branch head they still exit 2, which is the base, not this diff.Everything this PR does own was run green locally:
go test ./...,make check,make fmt,make tables-float-nan,make tables-float-nan-negative-control,make tables-lists,make tables-lists-tool-cook,make tables-lists-negative-controls(twelve controls, each red on its own sabotage),make tables-maps,make tables-maps-cook-check-negative-control,make tables-cook-cli,make tables-cook-write,make tables-cook-open, and the wire fuzzer over the new units (0 divergencesacross 143,309 mutants and 44 roots).🤖 Generated with Claude Code