Skip to content

gen-c: a repeat [v] ** N holds v in every element, or is refused (Closes #11181) - #11192

Merged
gHashTag merged 3 commits into
masterfrom
t27c/gen-c-repeat-l1
Oct 20, 2026
Merged

gHashTag merged 3 commits into
masterfrom
t27c/gen-c-repeat-l1

Conversation

@gHashTag

@gHashTag gHashTag commented Oct 20, 2026 •

Copy link
Copy Markdown
Owner

Closes #11181

t27c gen-c wrote uint64_t a[4] = /* repeat: { 7 } ** 4 */ {0}; for var a : [4]u64 = [7] ** 4;: four zeros, exit 0, so the generated C computed other values than the spec, t27c test-report and t27b. It is not u64-specific. On a 408-cell probe matrix 137 cells were wrong on master and none are now (4 cells still do not compile, loudly: a repeat in argument or return position, a string element type, and a call element on clang; see below).

Probe matrix

Master e427d38 (compiler.rs is byte-identical to 16c7dcf, where the issue was reproduced). Every cell is a spec with a test, t27c gen-c, cc -std=gnu11 -w -DT27_TEST_MAIN, run. Types u8, u16, u32, u64, i32, f64, bool; values non-zero (5, 300, 70000, 7, -3, 1.5, true) and zero; N = 1, 4, 64, 4096. Each row is 28 cells per value kind.

context non-zero, master non-zero, this PR zero, master and PR
local var a : [N]T = [v] ** N; 24 wrong 0 28 ok
local const a : [N]T = [v] ** N; 24 wrong 0 28 ok
module constant pub const A : [N]T = [v] ** N; 28 wrong 0 28 ok
struct literal field S{ .a = [v] ** N } 24 wrong 0 28 ok
element written [@as(T, v)] ** N 28 wrong 0 28 ok
typed [_]T{v} ** N (control) 0 0 28 ok
[v; N] (control) 0 0 28 ok

The 4 non-wrong cells in three rows are i32 -3: a leading minus makes the element an expression, which the parser keeps as a child, so master had it right there but not in a module constant. Eight more shapes, one cell each: [0xFF], [1_000], a named element [K], a parameter [x], an expression [D + 2], a call [two()], a named count, an expression count (D * D) were all wrong on master; so was a non-zero struct element (one of 11 extra edge cells, [_]P{P{ .x = 7, .y = 1 }} ** 4), which now builds and runs. Nested [[1] ** 3] ** 2 (local, @as, module constant) did not compile on master (a /* */ comment nested in a /* */ one) and is correct now. Not changed, and loud on master and here: a repeat in argument or return position is still bare braces (a C error); a numeric power a ** b is written as a repeat (see #7427, not touched).

Cause

The repeat arm of the C emitter (#6050, "any other single value takes the GNU range") only ever looked at lhs.children.len() == 1. The parser gives the sole element of [v] two shapes. When v is not a literal or a bare name, it is a child of the array literal. A bare literal or name between the brackets cannot be told from an array dimension ([4]u8), so the parser leaves it as text in extra_size and the node has no child: the arm saw no element and wrote {0}. A module-level literal keeps text for every shape. On top of that an allow-list (c_repeat_value_lowers) sent calls, @as, expressions and nested repeats to {0} as well. gen-rust already reads the text shape ("A sole literal or name element arrives as bracket TEXT"); the C arm never did. This is a bug, not a documented trade-off: #6050 chose the range on purpose and the branch works whenever the element is a child.

Fix (bootstrap/src/compiler.rs, hand Rust net -2 lines)

  • c_repeat_list returns the values of a repeat for either shape: the children, or the kept text parsed again (whole, then comma by comma).
  • The arm writes {0} for a zero and the GNU designated range { [0 ... (n) - 1] = v } for one other value; v goes through gen_c_expr, so _ digit separators, names, casts and calls print as everywhere else.
  • Zero stays {0} and now looks through @as(T, 0), a struct of zeros and a repeat of zeros, so no zero output changed (below).
  • A nested repeat no longer nests /* */: the outer source comment is dropped when it would contain another.
  • [a, b] ** n repeats a list, which a designated range cannot hold. It was {0}; compile_c now refuses it like the switch without else (self-host: gen-c lowers a switch with no else arm to 0 for the missing case #7308): a repeat of several values ([a, b] ** n) has no C initializer (#11181), exit non-zero, unless every element is zero. The module-level spelling (text only) is caught too.

There is no spec for the emitter's repeat rule, so the rule stays in Rust; the diff is +40/-42 in compiler.rs and the own-language gate passes (below). FROZEN_HASH carries the new digest.

Test, red first

specs/compiler/c_array_repeat_wrapper.t27 gains the repeat operator in every scalar type, hex, named element and count, @as, parameter, module constant, struct field, 4096 elements, nested, and a zero control (3 tests, 12 + 9 + 2 asserts). Its existing Rust harness (bootstrap/tests/c_array_repeat_wrapper.rs) already compiles the spec's own tests with cc -std=gnu11 -Werror and runs them, so no Rust test was added. On master the spec's C does not compile (nested comment); with the nested function removed it compiles and the process traps on the first assertion (exit 133). The issue's probe on master: Assertion failed: ((third() == 7)), exit 134; on this branch All 1 tests passed., exit 0, and the line is uint64_t a[4] = /* repeat: { 7 } ** 4 */ { [0 ... (4) - 1] = 7 };. The same spec passes t27c test-report (6 of 6, 30 runtime asserts, 0 vacuous) and t27b test (6 passed), so spec, Zig, t27b and C now agree.

Negative controls are core_selfhost data fixtures, no Rust: both_refuse/list_repeat.t27 and list_repeat_bare.t27 (gen-c and the core must both refuse; on master gen-c wrote {0}), and genc/list_repeat_zero.t27 (a list of zeros still builds and runs). Value 0 stays {0}: the diff of gen-c output over all 2857 tracked .t27 files, master binary against this one, has 2788 byte-identical, and in the other 65 changed files every changed hunk is {0} to a range (164) or a dropped nested comment (10).

Corpus effect (t27c gen-c over the 2857 tracked .t27 files)

  • 2788 identical; 66 changed: 6 core_selfhost fixtures, the spec above, and 59 specs (ml/, tri/crypto, tri/collections, port/trinity gen_, analog/linear, igla/coder, ...). 57 of those 59 have a seal whose gen_hash_c moved and are re-saved with t27c seal --save; the other 2 (lab/lab_t27b_cache, tools/t27o/spice_convert) still verify. The three refused specs are re-saved with --force, which records gen_hash_c=none, the state 4 other seals already hold. In all, 96 seal files in 61 specs (twins included), and t27c seal --verify says all hashes MATCH for each re-saved one. My first selection of seals was wrong: I had set 13 specs aside as "already stale on master" because their t27c gen-c <absolute path> hash differed from the seal, but t27c seal hashes a use-resolving spec differently, and t27c seal --verify on master says MATCH for all 13. CI's coverage and spec-guards caught it (20 seals); the second commit (b347c92) re-saves them.
  • Of the 65 changed files, 57 do not compile with cc before or after for other reasons, 6 compile both ways, 1 (a fixture) now compiles. One regression of the loud kind: specs/port/bootstrap/src/compiler/HirIpCatalog_new.t27 has [empty_core()] ** 32; master wrote zeros (its own native tests aborted), this branch writes the range, which gcc builds and clang refuses (cannot compile this GNU array range designator extension yet: a range over a call). The existing [v; N] lowering has the same limit. Pure expressions (names, casts, arithmetic) build on clang.
  • Three specs now fail gen-c because they hold a repeat of a list, which was written as zeros with exit 0: specs/ternary/hybrid_arithmetic.t27, specs/tri/t27b/conformance/array_repeat_text.t27, specs/tri/t27b/conformance/big_repeat_global.t27. They are in docs/reports/suite_expectations.json (phase gen-c, expires 2026-11-30, 66 to 69 entries, a hand edit with the reason in _why_entries_rose, as corpus: ledger the six port-wave codegen refusals, max_entries 75 -> 83 #9303 did), so the corpus ratchet should not call them new failures. That is the one edit outside the compiler that needs a reviewer's eye.
  • git grep -l "/\* repeat:" -- gen prints 0 files before and after. No committed gen/c file belongs to a changed spec.

What each re-seal states (old gen-c against new, per spec, hunks counted)

Re-sealing is a statement that the new output is the one wanted. For the 11 specs below every changed hunk is one of two things and nothing else (0 other): a {0} initializer replaced by the GNU range holding the value the spec writes, or an outer /* repeat: ... */ comment dropped because it nested another (the old C did not compile there; the new one is {0}, the same all-zero value). The 3 refused specs now fail gen-c with a repeat of several values ([a, b] ** n) has no C initializer (#11181), and their seals record gen_hash_c=none.

spec hunks what the new line holds
ml/recurrent/bilstm 2 range float h_out[3] = 9.0 in all 3
ml/optimizer/lamb 2 range m[100], v[100] = 9.0
ml/rl/ppo_critic 2 range out[3] = 9.0
tri/io/fs 3 range const char* out[8] and [3] = "" (were NULL)
analog/linear_zoh 2 range ZRC_NV[1] = 818234 (the constant the spec writes)
depin/prove 7 comment uint8_t weights/result[16][16] = {0} (was a nested comment)
effects/capability 1 range wide[600] = 65
effects/fs_cap 2 range wide[65] = 66, full[64] = 67
effects/replay_oracle 2 range got[3] = 9
hosting/sybil 2 range true_tier[4] = TIER_PUBLIC
verified/corpus_merkle 1 range, 1 comment b[1] = 9; d[5][32] = {0}
ternary/hybrid_arithmetic refused [_]i8{1, 0, -1, 1} ** 8 as an argument
tri/t27b/conformance/array_repeat_text refused [1, 2] ** 3 and [SEED, minus_two()] ** 2
tri/t27b/conformance/big_repeat_global refused [_]u16{ 1, 2 } ** PAIRS

t27c --version of the compiler used: 0.5.2, built from this branch's compiler.rs (digest in FROZEN_HASH).

Gates and results

  • Own-language: git diff --numstat --no-renames BASE...HEAD | budget (compiled from the merge base's gen/c/policy/own_language.c, as the workflow does): exit 0. compiler.rs +40/-42, FROZEN_HASH +1/-1; the per-file limit is 40 added. The path gate (check_all, exceptions from BASE) exit 0.
  • FROZEN_HASH = 08d882e79f8da0cfb8abc0f98e95de792580dc88d5dd1cb9bae8bd6385e86b72 = shasum -a 256 bootstrap/src/compiler.rs.
  • cargo check -p t27c --all-targets: exit 0. cargo build --release -p t27c: ok. cargo test -p t27c --test X for c_array_repeat_wrapper (6 passed), core_selfhost (1 passed: gen-c builds the core, fixpoint, fixtures, refusals, corpus differential), genc_digit_separators (4), c_module_array_consts (5), c_local_array_types (6), c_struct_array_fields (7), c_auto_type_array_literal (4), c_array_literal_arguments (7).
  • Findings of the first CI run (a02b3e6), all fixed in the next two commits: tri types ratchet "+ Row NEW conflict" (the test's struct Row shared its name with Row in queen-registry.t27; renamed RepeatCells, 2494746); coverage (tools/check_seal_coverage.py) and spec-guards (tools/check_seal_currency.py) on the seals above (b347c92). Now, run locally: tri types ratchet CLEAN (ledger 107, observed 107), tri types classified OK, tools/check_seal_coverage.py OK (2939 seals, 2826 hold, 113 known-broken), tools/check_seal_currency.py 0 stale generated-code hashes, validate-seals --pr-files ok. Verilog widths (absurd-width ledger) also failed on that run: it names specs/widgets/bit_diff.t27 and specs/widgets/lut_treemap.t27 (on master, gen-verilog widths of 2097151 bits), which this diff does not touch. On the final head b347c92 the only red checks are that one and t27b-native-ratchet (UNLISTED specs/widgets/vcd_wrapped.t27 and vcd_wrapped_logic.t27, STALE specs/ml/train_hslm.t27: ledger vs master drift, none of them in this diff); coverage, spec-guards, Corpus ratchet, own-language, test-ratchet, build-and-test and the rest are green.
  • python3 -I tools/dupe_scan.py: master 1632 of 17570 bodies in 632 groups, branch 1632 of 17571 in 632 groups, "ok: no new duplicate body" on both. tools/check_devhome_paths.py ok; added lines are ASCII; no local paths. tri hooks pre-commit and l1-check pass.

Not run, and what stays open

🤖 Generated with Claude Code

claude and others added 2 commits October 20, 2026 07:46
 #11181)

t27c gen-c wrote `uint64_t a[4] = /* repeat: { 7 } ** 4 */ {0};` for `var a : [4]u64 = [7] ** 4;`: four zeros, exit 0, so the generated C computed other values than the spec and t27b. Cause: the C emitter's repeat arm (#6050) takes the GNU range only when the sole element reaches it as a CHILD of the array literal. A bare literal or name between the brackets (`[7]`, `[K]`) reads as an array dimension, so the parser keeps only its text (extra_size) and the arm saw no element; a module-level literal keeps text for every shape. Elements that were calls (`[@as(u8, 5)]`, `[f()]`), expressions or nested repeats were excluded by an allow-list and also fell to `{0}`. Measured on a 408-cell probe matrix: 137 cells wrong on master, 0 after.

Fix, in bootstrap/src/compiler.rs, hand Rust net -2 lines (+40/-42): c_repeat_list reads the elements of either parser shape (children, or the kept text parsed again); the arm writes `{0}` for a zero and the GNU range for one other value; zero now looks through @as, a struct of zeros and a repeat of zeros, so no zero output changes (2788 of 2857 tracked specs byte-identical); a nested repeat drops the outer source comment instead of nesting `/* */`; a repeat of a LIST (`[a, b] ** n`), which a designated range cannot hold, is refused like a switch without else (#7308). Three specs hold one and are ledgered in docs/reports/suite_expectations.json (66 -> 69, phase gen-c).

Test, written first and red on master: specs/compiler/c_array_repeat_wrapper.t27 gains the repeat operator in every scalar type, hex, named, @as, parameter, module constant, struct field, 4096 elements and nested; its existing Rust harness compiles and runs it (cc -std=gnu11 -Werror), and t27c test-report and t27b test both pass it. The refusal and the zero-list control are core_selfhost fixtures (both_refuse/, genc/), so no Rust test grew. FROZEN_HASH moved with compiler.rs; 47 specs whose gen_hash_c moved are re-saved (76 seal files, all verify MATCH).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…11181)

The corpus ratchet's type-name step (tri types ratchet) failed on the first push of this branch: "+ Row NEW conflict". The struct Row added to specs/compiler/c_array_repeat_wrapper.t27 shares its name with the Row of specs/port/browseros/trios/agent-server/apps/server/src/api/routes/queen-registry.t27, and a type name with two definitions is a new conflict. RepeatCells is defined once in the tree. Run locally after the rename: tri types ratchet is CLEAN (ledger 107, observed 107), tri types classified is OK, the spec still passes t27c test-report (6 of 6, 0 vacuous), t27b test (6 passed) and `cargo test -p t27c --test c_array_repeat_wrapper`; the seal is re-saved (verify: all hashes MATCH, validate-seals ok).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-20 00:58:02 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 48
PRs with All Checks Green 2
READY 2
FAILING 48
PENDING 0
NO CHECKS YET 0

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=08d882e79f8d != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

…-guards found, and record gen_hash_c=none for the 3 specs gen-c now refuses (Closes #11181)

The first CI run of this branch failed `coverage` (tools/check_seal_coverage.py: 20 seals "gen-drift") and `spec-guards` (tools/check_seal_currency.py: 16 stale gen_hash_c). Cause: the 13 specs I had set aside as "already stale on master" were not. I had decided that by hashing the output of `t27c gen-c <absolute path>`; for a spec that resolves a `use`, that is not what `t27c seal` hashes, and `t27c seal --verify` on the master binary says "all hashes MATCH" for all 13. With this branch's compiler 11 of them (analog/linear_zoh, depin/prove, effects/capability, effects/fs_cap, effects/replay_oracle, hosting/sybil, ml/optimizer/lamb, ml/recurrent/bilstm, ml/rl/ppo_critic, tri/io/fs, verified/corpus_merkle) mismatch on gen_hash_c only; lab/lab_t27b_cache and tools/t27o/spice_convert still match.

Re-saved with `t27c seal --save` (11 specs, 16 seal files incl. twins; only gen_hash_c, built_by, sealed_at and the test runner digest move). The 3 specs gen-c now refuses (ternary/hybrid_arithmetic, tri/t27b/conformance/array_repeat_text, tri/t27b/conformance/big_repeat_global) are re-saved with `--force`, which records gen_hash_c=none and leaves the zig, rust and verilog hashes and the test results as they were; that is what the compiler produces for them now, and the same state 4 other seals already record.

Run locally with the branch compiler after this: tools/check_seal_coverage.py ends "OK: 2939 seals, 2826 hold, 113 known-broken", tools/check_seal_currency.py reports 0 stale generated-code hashes (2837 current, 8 sealed with gen_hash=none by its count). `t27c suite --ratchet --corpus-only` was already CLEAN.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-20 01:10:02 UTC

Summary

Status Count
Total Open PRs 50
PRs with Failing Checks 47
PRs with All Checks Green 3
READY 2
FAILING 47
PENDING 0
NO CHECKS YET 0

These columns do not partition: 2 + 47 + 0 + 0 = 49, and there are 50 open PRs. A PR is being counted twice or not at all.

Seal Status

  • ⚠️ STALE -- sha256(compiler.rs)=08d882e79f8d != manifest seal=87e5cbd3ad94.
    The committed NMSE numbers were certified against an older compiler.rs.
    Run scripts/reseal-check.sh locally for the two-step reseal command (advisory; not a merge gate).

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.

[bug] gen-c: a repeat array [v] ** N with v != 0 is written as {0}; the generated C computes other values than the spec and t27b

2 participants