Skip to content

t27b: a local bound to the W585 scaffold is never called, as in the reference, and refused by name where it would be read (Closes #7691) - #7750

Merged
gHashTag merged 1 commit into
masterfrom
fix/t27b-scaffold-local
Oct 8, 2026
Merged

gHashTag merged 1 commit into
masterfrom
fix/t27b-scaffold-local

Conversation

@gHashTag

@gHashTag gHashTag commented Oct 8, 2026 •

Copy link
Copy Markdown
Owner

Closes #7691. Part of #6063 (t27b coverage), which is item C10 of #6488. Builds on #7725 (#7690), which this branch starts from.

What the reference does

W585 (collect_scaffold_locals in bootstrap/src/compiler.rs, Zig backend). In each fn or test body, a local bound to default_input() or valid_input() with no arguments, whose name is then passed as a bare argument to a declared fn, is printed const x = undefined;. The helper is never called, whether or not the spec declares it, and no type is printed. Zig coerces that undefined to each parameter it reaches.

Measured on the t27c lab (t27c test-report, zig 0.16.0, x86_64 Debug), one probe per shape:

shape reference
topological_sort.t27: given graph = default_input(), then sort(graph), which never reads graph pass (2/2)
a declared default_input() returning 7, then is_seven(input) with return x == 7;, and then r == false pass: the callee reads dead memory (0xAA bytes), so a claim false by the spec's own definitions passes
var input = default_input(); passed to such a fn blocked: "variable of type '@typeof(undefined)' must be const or comptime"
const input = default_input(); passed to such a fn, then assert(input == 7) blocked: "use of undefined value here causes illegal behavior"
const input = default_input(1); (an argument) passed to such a fn an ordinary call: the helper runs

t27b called the helper. For topological_sort.t27 that gave a Graph where sort declares *const Graph, refused as type mismatch at lines 56 and 62. For a helper with an effect or a trap, t27b and the reference would split.

Decision, with the evidence

Both halves of the issue, by shape:

  • Agree where nobody reads the value (SCAFFOLD). The helper is not called. The local holds a value of the first consumer's parameter type that nobody reads: zero in a register, unwritten memory for an aggregate, which t27b's interpreter would fault on if anything did read it.
  • Refuse by name where the reference's undefined would be read, so t27b never passes a spec on a value the reference never had:
refusal when
StmtLocal(scaffold read by callee) a consumer's body names that parameter at all
StmtLocal(scaffold read) the body mentions the local other than in its declarations and those arguments
StmtLocal(scaffold var) var x = default_input(); (Zig refuses the reference's var x = undefined;)
StmtLocal(scaffold typed) a typed local; no corpus spec writes one, so it is not taken
  • Not mine (NOT_MINE): a helper that no declared fn is passed, or one given arguments. It is lowered as written and called, as the reference calls it.

The reference defect is filed, not copied: #7733 (Part of #5980). A declared helper is never called, so a callee that reads the scaffold reads dead memory and a false assert passes. t27b refuses exactly that shape.

Where the decisions live. They are a t27 plan, specs/tri/t27b/scaffold_plan.t27, with 7 tests:

  • the helper names (HELPERS, split by the glue as libm_plan's BUILTINS is);
  • the order of the questions;
  • each refusal's construct and words.

t27c gen-rust turns it into gen/rust/tri/t27b/scaffold_plan.rs, and lower.rs mounts that file with #[path], next to void_bind_plan.rs (#7725). The glue only collects the facts over a body in begin_body, as slice_locals mirrors collect_slice_locals:

  • which names are bound to a helper with no arguments;
  • which declared fns they are passed to bare, and at which parameter;
  • whether a consumer's body names that parameter;
  • whether the body mentions the name anywhere else.

local_with then asks plan and builds the answer.

Conformance spec first

specs/tri/t27b/conformance/scaffold_local.t27 has 8 tests:

  • topological_sort.t27's two tests, through a const pointer sort never reads;
  • bellman_ford.t27's shape: a slice passed to a void fn that ignores it but has an effect of its own. The local's type comes from that consumer, not from the helper, which returns a struct;
  • a struct by value, never read;
  • valid_input() declared nowhere, as in the generated tests: the reference needs no declaration, and neither does t27b now;
  • one scaffold passed to two calls;
  • a nested call sc_is_valid(sc_sort(g));
  • a scaffold in a fn body.

default_input is declared with an effect on SC_CALLS, and every test asserts SC_CALLS or a result at run time. So the spec pins that the helper is never called.

result
reference, t27c test-report 8/8, 0 vacuous, 16 runtime asserts; sealed (seal --save, then --verify: all hashes MATCH)
master's t27b refused: type mismatch (the helper's struct where sc_sort declares a const pointer)
this branch's t27b 8/8, 16 runtime asserts (cargo test -p t27b --test tail, and the signed lab run below)

The plan spec: t27c test-report 7/7, 0 vacuous, 32 runtime asserts; sealed and verified.

Mutants

mutants of count killed under t27c test-report killed under this branch's t27b
the plan spec (tri mutate spec: drop-guard 9, flip-cmp 10, swap-logic 2, ret-default 2) 23 23 -
the conformance spec, by hand: each assert's expected value (13) and each fn body (7) 20 20 20, the same failing tests in every mutant

tri mutate spec cannot run the conformance spec: it runs every test in one zig test process, and SC_CALLS carries over from test to test.

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

  • S1: the helper is called anyway;
  • S2: a callee's read is ignored;
  • S3: a read elsewhere is ignored;
  • S4: a var is not refused;
  • S5: a typed local is not refused;
  • S6: a register value is left unwritten (the interpreter faults on the read);
  • S7: the parameter index is shifted;
  • S8: any identifier argument makes a consumer;
  • S9: a helper given arguments counts as the scaffold;
  • S10: the name is not bound.

Corpus, lowered by master's t27b and by this branch's (t27c lab, t27b test --blockers, first construct of each of 1599 files)

Six files change, all toward the reference:

file reference master's first blocker this branch
specs/tri/graph/topological_sort.t27 pass type mismatch lowers; 2/2 pass
specs/tri/t27b/conformance/scaffold_local.t27 pass type mismatch lowers; 8/8 pass
specs/ml/activation/relu_activation.t27 blocked ExprCall(undeclared fn) StmtLocal(scaffold read by callee)
specs/tri/sort/heap_sort.t27 blocked ExprCall(undeclared fn) StmtLocal(scaffold read by callee)
specs/tri/utils/random.t27 blocked ExprCall(undeclared fn) StmtLocal(scaffold read by callee)
specs/ml/layers/dropout_layer.t27 blocked ExprCall(undeclared fn) StmtLocal(scaffold read)

bellman_ford.t27 keeps its verdict, pass_vacuous: its first test no longer calls default_input(), as the reference does not.

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

file kind added deleted
cli/t27b/src/lower.rs hand-written glue 39 0
cli/t27b/tests/tail.rs hand-written test 22 0
gen/rust/tri/t27b/scaffold_plan.rs t27c gen-rust, not hand-edited 75 0
specs/tri/t27b/scaffold_plan.t27 plan spec, 7 tests 150 0
specs/tri/t27b/conformance/scaffold_local.t27 conformance spec, 8 tests 127 0
.trinity/seals/*.json (2) t27c seal --save 40 0
docs/reports/t27b_expectations.json ledger 6 4
AGENTS.md t27b remainder line 4 1

Hand-written foreign code: 61 added lines, 0 deleted (39 in lower.rs, 22 in tests/tail.rs). That is under the #7371 cap of 40 per file and 80 per PR. Both files are on master's tools/policy/foreign-exceptions.txt; this PR does not change that file and needs no label.

AGENTS.md, re-measured with wc -l: cli/t27b/src/*.rs goes from 14905 to 14944, and cli/t27b/tests/*.rs from 7942 to 7964, on master d42df2b.

Ledger: topological_sort.t27 blocked -> pass, and two new rows (both specs pass). pass 891 -> 894, not_pass 42 -> 41, max_not_pass 42 -> 41. The four other files above are not ledger rows, because the reference blocks them.

Gates

Both ran on the t27c lab from master d42df2b's gen/c/policy/own_language.c, with lefthook.yml's one-line C main:

  • check_budget() over git diff --numstat --no-renames origin/master...HEAD: exit 0. Negative controls each exit 1: 41 lines in lower.rs; 42 in tail.rs (81 in the PR).
  • check_all() with origin/master:tools/policy/foreign-exceptions.txt, -- and git diff --name-status origin/master...HEAD: exit 0.

Other checks:

  • cargo test --release -p t27b on the t27c lab (x86_64, interpreter only): 116 passed, 0 failed.
  • t27c gen-rust of the plan gives the same bytes at 927b1ef and at d42df2b (sha256 1cbbe8ac...), which is the seal's gen_hash_rust.

t27b lab: signed receipts (#7686)

Head 8f19582 against its merge-base, master d42df2b (#7725). Both are signed runs of the deployed t27b lab (lab_py_sha 4e9f106db, rustc 1.99.0, zig 0.16.0, qemu-aarch64 7.2). The head run was requested with a fresh 32-byte challenge.

t27c corpus-receipt compare BASE HEAD --challenge-head <mine>, run on the t27c lab from a master checkout (01de65c):

base "d42df2b069bedf6dc204fccd230fc15654c03752" AUTH_MISSING_NONE AUTHOR leaves-bound true
head "8f1958299e28ef370e22f1cebda56c9b16cffcac" AUTH_MISSING_NONE FRESH leaves-bound true
  lane improved specs/tri/graph/topological_sort.t27
  lane improved specs/tri/t27b/conformance/scaffold_local.t27
  lane improved specs/tri/t27b/scaffold_plan.t27
totals false inputs false verdicts false outputs false: IMPROVED_ONLY

Exit 3, IMPROVED_ONLY.

base d42df2b head 8f19582
files 1597 1599 (+2: the new specs)
t27b pass / pass_vacuous 921 / 152 924 / 152
t27b fail 19 19
jit_interp_mismatch / crash 0 / 0 0 / 0
reference pass 1112 1114
reference disagree (files / tests) 0 / 0 0 / 0
cargo test --release -p t27b (aarch64, qemu) - 136 passed, 0 failed
  • Improved: topological_sort.t27, blocked (type mismatch) -> pass with 2 tests and 2 runtime asserts, as under the reference; the conformance spec (8 tests, 16 asserts) and the plan (7 tests, 32 asserts).
  • Same verdict, first blocker moved (the reference blocks all four, so the receipt's verdict leaves do not change): relu_activation.t27, heap_sort.t27 and random.t27 -> StmtLocal(scaffold read by callee); dropout_layer.t27 -> StmtLocal(scaffold read).
  • bellman_ford.t27 stays pass_vacuous; its first test no longer calls default_input().
  • Ratchet: the head has no finding the base lacks; the base's MOVED row for topological_sort.t27 is gone, because the ledger now has its row.

Master has moved to 01de65c since the merge-base. Its two commits (#7761, #7764) touch neither t27b, nor AGENTS.md, nor the ledger, nor the gate files, so the branch is not re-merged. check_budget() and check_all() read the same inputs and still exit 0.

The t27b-native-ratchet check is red on master too, for findings this PR does not add (UNEXPECTED PASS 2, UNLISTED 29, STALE 1). Its one REFDISAGREE file, static_slice_literal.t27, comes from #7694: native arm64 Zig faults on the write that the x86_64 reference allows.

gen-rust gaps

None. The plan is decisions over bool and u8, str results, and one str constant, with no str parameters (#7449).

Generated with Claude Code

…eference, and refused by name where it would be read (Closes #7691)

The reference prints `given x = default_input()` (or `valid_input()`), when x
is passed bare to a declared fn, as `const x = undefined;` and never calls
the helper (W585, `collect_scaffold_locals`). t27b called it, and refused
topological_sort.t27 with `type mismatch`.

Now t27b agrees where nobody reads the value: the helper is not called, and
x holds a value of the first consumer's parameter type (zero in a register,
unwritten memory for an aggregate). Every shape where the reference's
`undefined` would be read is refused by name: a callee that names the
parameter (`StmtLocal(scaffold read by callee)`, where the reference reads
dead memory and can pass a false assert, #7733), any other read
(`StmtLocal(scaffold read)`), a `var` (`StmtLocal(scaffold var)`, which Zig
refuses) and a typed local (`StmtLocal(scaffold typed)`, not taken).

The decisions are specs/tri/t27b/scaffold_plan.t27 (7 tests), generated with
`t27c gen-rust` to gen/rust/tri/t27b/scaffold_plan.rs and mounted in
cli/t27b/src/lower.rs. The conformance spec is
specs/tri/t27b/conformance/scaffold_local.t27 (8 tests, `t27c test-report`
8/8, 0 vacuous). Glue: lower.rs +39, tests/tail.rs +22. Ledger:
topological_sort.t27 passes; the two new specs are new rows.

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 10:03:44 UTC

Summary

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

These columns do not partition: 3 + 46 + 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 2781ae3 into master Oct 8, 2026
40 of 41 checks passed
gHashTag pushed a commit that referenced this pull request Oct 8, 2026
Master moved again (#7750 added the W585 scaffold fields `fns` and
`scaffold` to `Lower`, next to this PR's `str_globals`).

- cli/t27b/src/lower.rs: both sides' fields kept, in the struct and in
  lower_mode's initializer; this PR's diff against master is unchanged
  (+35 -12).
- Ledger: master's (579bc55), with only this PR's five rows, as the
  bless of the lab run of babedd9 gives them (xc7a100t_minimal and
  similarity_search -> pass, the registry's blocker -> VarDecl(module,
  pointer/slice), str_module_var and undefined_module_var new pass).

Closes #7448

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gHashTag pushed a commit that referenced this pull request Oct 8, 2026
Master moved (#7747, #7725, #7750 and #7470 landed); the AGENTS.md debt
line conflicted. Master's history is kept and this PR's entry is
re-measured with `wc -l` on this tree: master 1f2448e has
cli/t27b/src/*.rs 14967, src/*/*.rs 1222 and tests/*.rs 7987; after this
PR 14995 (+28 glue) and 1166 (-56, lower/lencall.rs deleted), tests
unchanged. The two lanes that moved the numbers without an entry, #7459
(+31, +39) and #7448 (+23, +23), get one, so the chain is continuous.
The exception list and lower.rs merged cleanly.

Closes #7524

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
gHashTag pushed a commit that referenced this pull request Oct 8, 2026
The second half of #7422 (a test that writes a local it declared) landed
on master differently, in #7671 (ef26684). This branch keeps only what
master still lacks, the shadowing local (#7668); see the earlier merge
ac867a9 for the parts dropped as master's already. Master has since
moved (#7414, #7745, #7459, #7747, #7725, #7750, #7470).

- tools/policy/foreign-exceptions.txt: both approval comments kept
  (#7448 and this PR's #7668) above the shared lower.rs / source.rs
  entries; no entry added.
- lower.rs and source.rs merged cleanly (this PR: lower.rs +34 -5,
  source.rs +14 -1).
- Ledger: master's (1f2448e), with only this PR's rows:
  zig_test_shadowing blocked -> pass, shadow_module_var new pass; to be
  confirmed by the lab run of this head.

Closes #7668

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: the reference's W585 scaffold prints given x = default_input() as undefined and never calls it

2 participants