Skip to content

t27b: array literals the reference prints as @constCast(&[_]E{ ... }) get one writable static per type and value, from a t27 plan (Closes #7680) - #7694

Merged
gHashTag merged 7 commits into
masterfrom
t27b-arraylit-slice
Oct 8, 2026
Merged

gHashTag merged 7 commits into
masterfrom
t27b-arraylit-slice

Conversation

@gHashTag

@gHashTag gHashTag commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Closes #7680. Part of #6063 (t27b coverage), which is item C10 of #6488.

What the refusals guarded

t27c's Zig backend prints an array literal as @constCast(&[_]E{ ... }) in two places: a slice field of a named struct literal, and the value of a fn that returns a slice. E is the element type of the destination. t27b refused two shapes of it.

Both stated reasons are about the reference, so I measured the reference.

Setup: the t27c Railway lab, zig 0.16.0, with the default x86_64 Debug backend that t27c test-report builds with. The input was a Zig file of these shapes.

  • The array is a comptime value, and Zig interns it:
    • one object per element type and value, wherever the literal is written. Two sites with [_]usize{ 0, 1, 2, 3 } printed the same address. So did a []const site with no @constCast.
    • it lives as long as the process.
  • This backend keeps the object writable. A write through the slice succeeds. The next evaluation of the same literal reads the written value, and so does an equal literal at another site.
  • Another value is another object, and so is another element type. This holds for [_]u64 against [_]usize, and for equal bytes of two types.
  • Other backends behave differently, but they are not the reference:
    • with -fllvm the same write segfaults (read-only memory);
    • with ReleaseSafe the later read is folded to the old value.
  • t27c test-report runs each test in its own process (bootstrap/src/test_report.rs, "Runs one test per process"). So every test starts from the constants.

The first refusal guarded a storage question. The second guarded a claim that is false for the backend the lab uses.

The refusal that does guard the frame address stays. That is ExprArrayLiteral(run-time slice return), which matches the policy of #7660 / #7550. Run-time elements live in the frame of the fn that writes them, and a returned slice of them dangles. This PR keeps that refusal, and refuses run-time elements in a slice field too, because such a field would dangle once a struct holding it is returned.

Decision

A literal of compile-time elements becomes one writable static object per array type and value. It is a t27b global, exactly the storage of a module-level var:

  • written at load;
  • reset on every host entry by both the JIT and the interpreter, as each reference test is a process of its own (eval.rs reset_globals, jit.rs call_fp);
  • interned by (array type, bytes), as Zig interns comptime values.

The slice field or the returned slice points at that object. A literal with no elements and no size text is a zero-length slice; .nodes = []usize is @constCast(&[_]usize{ }) in the reference.

This is the reference's lifetime (the process, which is the test), its storage (one object per type and value, writable) and its mutability (the destination's const-ness does not matter).

Still refused, by name:

shape name why
run-time elements, field ExprArrayLiteral(to slice field) built in the frame of the fn that writes it
run-time elements, return ExprArrayLiteral(run-time slice return) the reference returns an address in its own frame (#7550)
elements that hold a str ExprArrayLiteral(to slice field) / ExprArrayLiteral(string slice return) a static is bytes written before the program runs; a string's address is not known then
field of an anonymous struct literal ExprArrayLiteral(to slice) the reference prints .{ ... }, which Zig refuses for a slice
slice of arrays or slices, field ExprArrayLiteral(to slice) the same
repeat [v; n], field ExprArrayLiteral(to slice field) not taken in this step
typed literal with a dimension and no elements, field ExprArrayLiteral(to slice field) the reference prints the dimension as its element (the defect ExprArrayLiteral(reference empty typed) names)

A return that the plan does not take goes on to t27b's other return paths, exactly as before.

Known limit. t27b's types do not tell usize from u64, or isize from i64. So a usize literal and a u64 literal of one value share an object in t27b, while Zig makes two. Only a test that writes through one and reads the other can tell. No corpus spec does.

Where the decisions live. They are a t27 plan, specs/tri/t27b/slice_lit_plan.t27, with 11 tests. t27c gen-rust turns it into gen/rust/tri/t27b/slice_lit_plan.rs, and lower.rs mounts that file with #[path], as it mounts wide_plan.rs (#7657) and builtin_plan.rs (#7614). The plan covers:

  • which shapes are taken;
  • when the parser's text form is read back (PARSE);
  • every refusal's construct and words.

The glue in lower/arraylit.rs (slice_lit) only reads the shape, asks plan, and builds the answer: a zero-length temporary, or the interned global. One function now serves both the field (it replaces slice_field) and the return (it replaces slice_literal_return). A []const return of a constant now uses the same static instead of read-only data, so it sees a write through an equal mutable literal, as Zig's does.

ExprArrayLiteral in ternary_gates.t27 and ultra_engine_v70_ultimate.t27 is not the same mechanism. The reference prints those as an untyped tuple local, const vals = .{ ... };, that a test indexes. They are not @constCast(&[_]E{ ... }). Nor is ExprArrayLiteral(reference empty typed) in ternary_mac_synth.t27. That is the reference's own printing defect, and #7404 fixes the spec. All three are untouched.

Conformance spec first

specs/tri/t27b/conformance/static_slice_literal.t27 has 8 tests in the shapes of the three graph specs:

  • .order = []usize{ 0, 1, 2, 3 } in a named struct literal;
  • .items = []usize;
  • return []StaticHop{ .{ ... }, ... } from -> []StaticHop.

It pins the reference's semantics, writes included:

  • a write is read by the next evaluation of the same literal, and through an equal literal elsewhere, a []const return included;
  • a prefix, another element type, and equal bytes of another type ([0, 1] as []u32 against [4294967296] as []usize) are other objects;
  • every test starts from the constants.
result
reference, t27c test-report 8/8, 0 vacuous, 23 runtime asserts; sealed (seal --save, then --verify: all hashes MATCH)
master's t27b refused: ExprArrayLiteral(to slice field)
this branch's t27b, test --check, aarch64 under qemu 8/8, 23 runtime asserts, in trap and in wrap mode

Mutants

mutants of count killed under t27c test-report killed under this branch's t27b
the conformance spec (each assert's expected value) 11 11 11, by the same test
the plan spec (each decision, the order of the questions, each name and its words) 26 26 26

Glue mutants, run as cargo test --release -p t27b --test arraylit --test source on the t27c lab (x86_64, interpreter only). All 8 are killed:

  • G1: no interning (a new global per evaluation);
  • G2: read-only data instead of a global (the interpreter faults);
  • G3: a frame temporary instead of a static;
  • G4: an intern key without the type;
  • G5: the mutable-return refusal kept;
  • G6: the constant-field refusal kept;
  • G7: an anonymous struct literal counted as named;
  • G8: run-time elements keep their old ConstDecl name.

What this does not do

The three graph specs move from ExprArrayLiteral to their next blockers. None of them passes yet:

spec next blocker issue
graph.t27 ExprCall (when result = add_node(&graph), a void fn used as a value) #7690
bellman_ford.t27 ExprCall (the same construct) #7690
topological_sort.t27 type mismatch #7691

topological_sort.t27's type mismatch comes from the reference's W585 scaffold: given graph = default_input() is printed const graph = undefined;, and the call is never made. #7690 is a separate mechanism in five reference-passing specs.

No reference pass in this family comes from reading dead memory. The scaffold's undefined is never read: sort ignores its parameter.

specs/automation/wrapup-auto.t27 is blocked first by StmtAssign (#7404). Its second blocker is now the named strings refusal.

Lines (git diff --numstat --no-renames origin/master...HEAD)

file kind added deleted
cli/t27b/src/lower/arraylit.rs hand-written glue 39 45
cli/t27b/src/lower.rs hand-written glue 7 41
cli/t27b/tests/arraylit.rs hand-written test 8 16
cli/t27b/tests/source.rs hand-written test 3 3
gen/rust/tri/t27b/slice_lit_plan.rs t27c gen-rust, not hand-edited 113 0
specs/tri/t27b/slice_lit_plan.t27 plan spec, 11 tests 226 0
specs/tri/t27b/conformance/static_slice_literal.t27 conformance spec, 8 tests 159 0
.trinity/seals/*.json (2) t27c seal --save 40 0
docs/reports/t27b_expectations.json ledger 3 1
AGENTS.md t27b remainder line 4 1

Hand-written foreign code: 57 added lines and 105 deleted.

Test changes:

  • tests/arraylit.rs: the refusal test for a constant returned as a mutable slice becomes static_conformance_spec_passes, which runs the conformance spec through both the JIT and the interpreter.
  • tests/source.rs: the slice-field rejection case now uses a run-time element.

AGENTS.md, re-measured with wc -l on the merged head: cli/t27b/src/*.rs goes from 14876 to 14842, and cli/t27b/tests/*.rs from 7874 to 7866. That is -34 and -8 on master 3d71306, after #7673's entry. src/lower/*.rs is outside that glob, as before: arraylit.rs is -6.

Gates

Both gates ran on the t27c Railway lab, from master 3d71306's gen/c/policy/own_language.c with lefthook.yml's one-line C main, over the merged head.

  • check_budget() over git diff --numstat --no-renames origin/master...HEAD: exit 0.
    • Negative controls are each denied, exit 1: 41 lines in lower.rs, and this diff plus 30 more test lines.
  • check_all() with origin/master:tools/policy/foreign-exceptions.txt, in the -- (pre-push) and --ci forms: exit 0.

Other checks:

  • tools/l2_regen_check.py --base ef26684a1 --head HEAD: t27c gen-rust reproduces the plan's generated file byte for byte, and gave the same bytes over 3 runs.
  • tools/check_seal_currency.py: 0 stale generated-code hashes. tools/check_seal_coverage.py: OK. Both new seals: seal --verify gives all hashes MATCH.
  • tools/check_assertionless_spec_tests.py: ok, 3761 in 30, as on the baseline.
  • The new type names StaticOrder, StaticBag and StaticHop have no other definition in specs/.

t27b lab: full corpus with the reference

Both columns are private runs of the deployed lab.

  • A driver in a private /tmp directory on the t27b lab imported /opt/t27b-lab/lab.py (lab_py_sha 94254f63f) and ran its lab_run.
  • T27_SRV and T27_WORK pointed at that directory, so the lab's /srv and /work were not touched.
  • The image and toolchain were the lab's own: rustc 1.99.0, zig 0.16.0, qemu-aarch64 7.2.
  • --jobs 12, 3 reference workers, fuzz off.
  • The corpus command: qemu-aarch64 t27b corpus specs --json --runner qemu-aarch64 --timeout-ms 60000 --jobs 12. The reference verdicts come from t27c test-report.
  • The private master run gives the same summary as the lab's own scheduled run of ef26684: https://t27b-lab-production.up.railway.app/runs/ef26684a1419b31a90c564f303d21998e723224a.json
  • The run JSONs are not published at the lab URL. Their sha256 prefixes are 612b12cc (master) and 021b47a9 (branch).
master ef26684 branch 482a434
files 1578 1580 (+2: the new specs)
pass 894 896
pass_vacuous 151 151
blocked 511 511
fail 18 18
timeout 4 4 (the same four specs/fpga/testbench/*_tb.t27)
mismatch (= jit_interp_mismatch) 0 0
crash 0 0
reference pass 1093 1095
t27b pass where the reference passes 893 895
reference disagree (files / tests) 0 / 0 0 / 0
cargo test --release -p t27b (aarch64, qemu) 132 passed, 0 failed 132 passed, 0 failed

Per file, master against branch:

  • Both new specs pass: the conformance spec with 8 tests and 23 asserts, and the plan with 11 tests and 56 asserts.
  • Four files keep their verdict (blocked) and only their first blocker moves:
    • graph.t27: ExprArrayLiteral(to slice field) becomes ExprCall;
    • topological_sort.t27: ExprArrayLiteral(to slice field) becomes type mismatch;
    • bellman_ford.t27: ExprArrayLiteral(constant to mutable slice) becomes ExprCall;
    • igla/coder/bench_proxy.t27, which the reference blocks: becomes ExprArrayLiteral(string slice return).
  • Nothing leaves pass. Every other file has the same verdict, first blocker, test count and runtime-assert count on both runs.

The merged head, measured again. The head is ca978fe, with master 3d71306 merged in. The t27b lab was redeployed during the work, and the new deployment has lab_py_sha a39cc460b. Both columns below are private runs of that deployment, with the same settings as above. The run JSONs' sha256 prefixes are 3978d762 (master) and 93dd6178 (branch).

master 3d71306 merged head ca978fe
files 1583 1585 (+2: the new specs)
pass / pass_vacuous 899 / 151 901 / 151
blocked / fail / timeout 511 / 18 / 4 511 / 18 / 4
mismatch / crash 0 / 0 0 / 0
reference pass 1098 1100
reference disagree (files / tests) 0 / 0 0 / 0
cargo test --release -p t27b (aarch64, qemu) 132 passed, 0 failed 132 passed, 0 failed

The per-file comparison is the same as on the base:

  • the two new specs pass, with the same test and assert counts;
  • the same four first-blocker moves;
  • no verdict moves, and nothing leaves pass.

Against this PR's ledger, the merged run's ratchet findings are master's own run's findings plus the three informational MOVED rows.

Ledger

The rows come from tri t27b ratchet --bless of the 482a434 run, composed onto master's ledger. Only the rows whose verdict this branch changes are taken, which here means two new rows:

  • specs/tri/t27b/conformance/static_slice_literal.t27: pass;
  • specs/tri/t27b/slice_lit_plan.t27: pass.

Counts: pass 875 -> 877, not_pass 51, max_not_pass 51. The ledger has 1078 rows and no duplicate path.

Ratchet result. Against the new ledger, the branch run shows master's own findings (UNLISTED 18, UNEXPECTED PASS 2, STALE 1), plus three informational MOVED rows for the graph specs. Their verdict stays blocked, so their rows stay master's.

gen-rust gaps

None. gen-rust expressed the whole plan: decisions over u8, bool and usize, and str results with no str parameters, which keeps clear of #7449.

Generated with Claude Code

claude and others added 7 commits October 8, 2026 11:40
…loses #7680)

specs/tri/t27b/conformance/static_slice_literal.t27 pins what the reference
does with an array literal t27c prints as @constcast(&[_]E{ ... }): a slice
field of a named struct literal (topological_sort.t27, graph.t27) and a
constant returned as a mutable slice (bellman_ford.t27).

With zig 0.16.0's default x86_64 Debug backend the array is one interned,
writable object per element type and value: a write through the slice is
read by the next evaluation of the same literal and of an equal literal at
another site, a []const one too. Each test of t27c test-report is its own
process, so every test starts from the constants.

t27c test-report: 8/8 pass, 0 vacuous, 21 runtime asserts. Sealed on the
t27c lab (seal --save, then --verify: all hashes MATCH).

Part of #6063.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e static per type and value, from a t27 plan (Closes #7680)

The decisions are specs/tri/t27b/slice_lit_plan.t27, mounted through
t27c gen-rust (gen/rust/tri/t27b/slice_lit_plan.rs). Compile-time elements
of a slice field in a named struct literal, or of a slice return (mutable
or not), go into a t27b global interned by array type and bytes: written
at load and reset on every host entry, as a module-level var is, because
each reference test runs in its own process. A typed literal with no
elements (`[]usize`) is an empty slice, as the reference prints it.

Dropped refusals: ExprArrayLiteral(to slice field) for constants, and
ExprArrayLiteral(constant to mutable slice), whose claim that a write
faults in the reference is not true of the backend the lab runs.

Kept: run-time elements (a frame address in the reference, #7550), an
anonymous struct literal's field, a slice of arrays, a repeat, and a typed
literal with a dimension and no elements.

Glue: lower/arraylit.rs +39 (slice_lit replaces slice_literal_return),
lower.rs +7 (the mount, the intern table, two call sites; slice_field
deleted). Tests: the refusal test becomes a run of the conformance spec;
the field rejection case now uses a run-time element.

Part of #6063.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tic (Closes #7680)

static_pair32() returns [0, 1] as []u32 and static_wide() returns
[4294967296] as []usize: the same eight bytes. A write through the first
must not show through the second, because Zig interns by type and value.
This pins the type half of t27b's intern key.

t27c test-report: 8/8 pass, 0 vacuous, 23 runtime asserts. Resealed on the
t27c lab (seal --save, then --verify: all hashes MATCH).

Part of #6063.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…Closes #7680)

A plan mutant that took STATIC only from two elements survived the nine
tests; one_element_wins_over_size_text kills it. 20 of 20 plan mutants are
now killed under t27c test-report (10/10 pass, 0 vacuous). gen-rust
regenerated (its header counts the tests) and the plan resealed on the
t27c lab (seal --verify: all hashes MATCH).

Part of #6063.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…oses #7680)

A t27b static is bytes written before the program runs, and a string's
address is not known then. Such a literal used to fall through to the
static writer and come back as `ConstDecl(str field)`, "str field in a
module-level struct constant", which is not what happened. The plan now
asks whether the element type holds a str and refuses it as
ExprArrayLiteral(to slice field) or ExprArrayLiteral(string slice return).
wrapup-auto.t27's `files_modified: ["..."]` is the corpus case.

Plan: 11/11 pass under t27c test-report, 0 vacuous; 26 of 26 plan mutants
killed. gen-rust regenerated (byte-identical over 3 runs), plan resealed
(seal --verify: all hashes MATCH). Glue: one line changed in arraylit.rs.

Part of #6063.

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

docs/reports/t27b_expectations.json: `tri t27b ratchet --bless` of the
branch run 482a434, composed onto master ef26684's ledger with only the
rows whose verdict this branch changes: two new rows, both pass
(conformance/static_slice_literal.t27, slice_lit_plan.t27). pass 875 ->
877, max_not_pass stays 51. The three graph specs only MOVED (still
blocked, by ExprCall and type mismatch), so their rows stay master's.

AGENTS.md: `wc -l` of cli/t27b/src/*.rs 14919 -> 14885 and
cli/t27b/tests/*.rs 7874 -> 7866.

Part of #6063.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AGENTS.md conflicted on the t27b remainder line: both sides kept. #7673's
entry (14876 plus 7874) comes first, then this branch's, re-measured with
`wc -l` on the merge: cli/t27b/src/*.rs 14842, cli/t27b/tests/*.rs 7866,
-34 and -8 on master 3d71306's 14876 plus 7874.

Part of #6063.

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

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

PR Dashboard

Generated at: 2026-10-08 05:25:56 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)=39fa3908f9fd != 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).

@gHashTag
gHashTag merged commit 20d20c9 into master Oct 8, 2026
39 of 40 checks passed
gHashTag pushed a commit that referenced this pull request Oct 8, 2026
From master's side (#7470's module vars, and policy and verified commits).
docs/reports/t27b_expectations.json: master's ledger plus this branch's six
rows, recomposed as before: pass 898 -> 901, max_not_pass 39 -> 40.
AGENTS.md: this branch's clause re-measured with `wc -l` over master
1f2448e: 14967 -> 14998 and 7987 -> 8005.

Merged-tree checks on the t27c lab: cargo test --release -p t27b 120
passed, 0 failed; under `t27b test --check` the four new specs, #7470's two
conformance specs and static_slice_literal.t27 (#7694) pass, and
frame_address_return.t27 (#7660), frame_address_store.t27,
gen_lockfree_stack.t27 and tool-registry.t27 are refused by name as before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gHashTag added a commit that referenced this pull request Oct 8, 2026
…}) is refused by name, not encoded, from a t27 plan (Closes #7765) (#7786)

* t27b: a write through an array literal printed @constcast(&[_]E{ ... }) is refused by name, not encoded (Closes #7765)

#7694 let a write through such a slice succeed and be read back by the
next evaluation of an equal literal, as zig 0.16's x86_64 Debug backend
does. Writing that comptime constant is undefined behaviour in Zig: on
the native aarch64 job (LLVM) four tests of static_slice_literal.t27
fail (run 37758574293, master 209ab18: REFFAIL, 4 REFDISAGREE, STALE).

specs/tri/t27b/slice_lit_plan.t27 now decides a write check: the glue
logs each STATIC literal whose destination is a mutable slice, and each
WRITE, a store through an element of a mutable slice or its address
taken (`lvalue` sets `writing`; `slice_index` reports), in code the
reference analyzes. After every body, a WRITE whose element type a
STATIC backs is refused as StmtAssign(write through an array literal),
at the write. Reads keep their static storage.

The conformance spec keeps its read-only tests (5, 22 runtime asserts,
0 vacuous under t27c test-report on the t27c lab) and drops the four
that wrote; cli/t27b/tests/arraylit.rs checks the refusal through a
callee, a struct element, an address and a derived slice, and that a
write through another element type's slice still runs.

Plan: 13/13 pass, 0 vacuous; 14 of 14 new plan mutants killed, 11 of 11
conformance assert mutants, 6 of 6 glue mutants (cargo test). gen-rust
regenerated; both specs resealed (seal --save, --verify: all MATCH).
cargo test --release -p t27b: all pass (t27c lab).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* AGENTS.md: t27b's Rust remainder after #7765's write check (Closes #7765)

`wc -l` of cli/t27b/src/*.rs 14905 -> 14922 (+17, lower.rs) and
cli/t27b/tests/*.rs 7942 -> 7964 (+22, arraylit.rs) on master
01de65c. The write check's decisions are
specs/tri/t27b/slice_lit_plan.t27.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 5.5 <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.

t27b: array literals the reference prints as @constCast(&[_]E{ ... }) -- slice fields and constants returned as mutable slices

2 participants