gen-c: a fixed-array struct field is storage, not a pointer - #3447
Merged
Conversation
Closes #3446 Refs #3445, #3443 struct Holder { f : [4]u8, g : i32, } C uint8_t* f; sizeof(Holder) = 16 Rust pub f: [u8; 4], size_of = 8 Zig f: [4]u8, Verilog packed, 4*8 + 32 bits The field held NO STORAGE. `h.f[0] = 1` wrote through an uninitialised pointer, and the same struct had two different layouts depending on which backend produced it -- which is the assumption the whole project rests on. Sizes measured by compiling and printing, not inferred. 65 fields in 31 specs. The array-field path already existed for fields whose size the parser puts in `extra_size`; this spelling never reached it. MEASURED, over 582 generated C translation units: errors 3849 -> 3825 files that compile 296 -> 301 Per file: six better, one worse. The regression is named rather than netted away -- `specs/physics/zamolodchikov_4d_conjecture.t27` gains one error because a field that is now a real array is initialised from a function returning `double*`. That function is #3445: a fixed-array RETURN emits the address of a stack local, 18 sites in 14 files. Fixing the field made the other defect visible. THREE POSITIONS, THREE ANSWERS, and the narrowing is the design. A parameter wants `T x[static N]`, the spelling that checks the caller (#3435) -- pinned by a test here, because a field rewrite leaking into parameters would silently undo it. A field wants `T f[N]`. A return cannot be an array in C at all. #3445 is filed rather than fixed because the obvious repair was tried and measured WORSE: normalising `[N]T` into the `[T; N]` machinery took errors to 3878 and compiling files to 287, since the type becomes a struct while the indexing and the call sites do not. Built, measured, reverted. A MUTANT SURVIVED. Deleting the `;` guard in `c_array_field` changes nothing any test catches -- `[u8; 4]` reaches the emptiness check below it anyway. The guard is redundant today, kept as intent, and the position it was meant to protect now has its own test. Four other mutants died. tools/backend_parity_table.py gains return and field position. The parameter table alone hid both of these, and its own Verilog column was empty for six field rows until the matcher learned that a struct has two Verilog shapes -- one packed vector, or a `reg` per field. A gap in the tool, printed as a fact about the backend, which is the third time this session. 77 seals are refreshed in the same commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
gHashTag
enabled auto-merge (squash)
September 8, 2026 01:37
Contributor
Contributor
|
📓 NotebookLM Notebook linked to this PR
This notebook contains session context, decisions, and artifacts for this work. |
This was referenced Sep 8, 2026
Closed
gHashTag
added a commit
that referenced
this pull request
Sep 8, 2026
Closes #3448 var x : [4]GF16; -> GF16 x[4]; error: use of undeclared identifier 'GF16' The same element is `uint16_t` in a parameter (`uint16_t a[static 4]`) and in a struct field (`uint16_t f[4];`). Only the local path passed the t27 text through, because it gated on `is_primitive` -- and the note retiring that very gate sits two hundred lines below, on `param_type_to_c`: W583: this used to be gated on `is_primitive`, which lists only the integer scalars -- so `f32`, `f64`, `str`, `string` and `gf16` took the pass-through arm and reached C unmapped even after `type_to_c` learned them. Same sentence, same file, other position. Two more defects live in the same eight lines. `var x : []u8;` emitted `uint8_t x[];`, which is not a definition; and `var x : []const u8;` emitted `const u8* x`, because the qualifier lives INSIDE the element text and this path did not strip it. The first slice repair was wrong, and the corpus said so: rewriting every `[]T` local to a pointer broke `T x[] = { ... }`, which is legal C and takes its size from the initialiser list. +5 errors, nothing better, in the one file carrying the shape. `size.is_empty() && node.children.is_empty()` is that measurement kept as code. MEASURED, and this is the part that matters beyond this commit. CLANG'S DEFAULT -ferror-limit=20 HAS BEEN CENSORING EVERY TOTAL I HAVE PUBLISHED THIS SESSION. 141 files reach the limit. The corpus emits 15133 errors, not the 3849 reported. The per-file splits were sound, because they were counted per file; the totals were floors. this change 15133 -> 15126 3 files better, 0 worse #3447, re-measured 15188 -> 15133 published as 3849 -> 3825 The direction and the per-file conclusions stand; the magnitudes were understated. It surfaced because a repair that removes real errors showed a ZERO delta -- three files sitting at exactly 20 before and after. tools/backend_parity_table.py gains the fourth position. Its first probe there measured the wrong thing: `var x : T;` with no initialiser is declared by C and Rust and emitted by NEITHER Zig nor Verilog, so the table was reading their dead-code removal. Initialising from a parameter of the same type fixed it, and the self-check now covers all four positions. Four mutants, four dead. 14 seals refreshed in the same commit. Co-authored-by: lab <lab@example.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Owner
Author
|
Correction to the figures in this PR. The error totals above — Re-measured with
So this change removed 55 errors, not 24. The direction, the per-file split (six better, one worse) and the named regression were all counted per file and are unaffected. Found while measuring #3448, where a repair that removes real errors showed a zero delta — three files sitting at exactly 20 both before and after. |
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.
Closes #3446 · Refs #3445, #3443
uint8_t* f;pub f: [u8; 4],f: [4]u8,The field held no storage.
h.f[0] = 1wrote through an uninitialisedpointer, and the same struct had two layouts depending on which backend
produced it — which is the assumption the whole project rests on. Sizes
measured by compiling and printing
sizeof, not inferred.65 fields across 31 specs. The array-field path already existed for fields
whose size the parser puts in
extra_size; this spelling never reached it.Measured — 582 generated C translation units
Per file: six better, one worse, and the regression is named rather than
netted away.
specs/physics/zamolodchikov_4d_conjecture.t27gains one errorbecause a field that is now a real array is initialised from a function
returning
double*. That function is #3445 — fixing the field made the otherdefect visible.
Five files go from failing to clean:
fpga/hir(9→0),fpga/memory(5→0),tri/trees/octree(3→0),tri/trees/quadtree(3→0),fpga/stdlib(2→0).Three positions, three answers
The narrowing is the design:
T x[static N]T f[N]A test pins the parameter position, because a field rewrite leaking into
parameters would silently undo #3435.
#3445 is filed, not fixed — because the obvious repair is worse
Normalising
[N]Tinto the existing[T; N]machinery was built and measured:-Wreturn-stack-addressThe type becomes a struct while the indexing (
a[i]vsa.v[i]) and the callsites do not:
+56 incompatible pointer types passing 't27_arr_int32_t_3 *',+15 subscripted value is not an array. Built, measured, reverted.A mutant survived, and it is written down
Deleting the
;guard inc_array_fieldchanges nothing any test catches —[u8; 4]reaches the emptiness check below it anyway. The guard is redundanttoday, is kept as intent with a comment saying so, and the position it was
meant to protect now has its own test.
;guard removedFull suite: 2609 passed, 0 failed.
The tool
tools/backend_parity_table.pygains return and field position. The parametertable alone hid both of these findings.
Its own Verilog column was empty for six field rows until the matcher learned
that a struct has two Verilog shapes — one packed vector, or a
regperfield. A gap in the tool printed as a fact about the backend, which is the
third time this session; the self-check now covers all three positions.
77 seals refreshed in the same commit.