Repository navigation
self-host: gen-c turns a test's write to a global into a new local that shadows it #6052
Copy link
Copy link
Closed
Labels
owner-approved-foreignOwner-approved exception to the only-t27 rule: hand-written foreign code allowed in this PROwner-approved exception to the only-t27 rule: hand-written foreign code allowed in this PR
Description
Profile (read first)
You are working as Gamma (Compiler Core) on epic #5980: rewrite t27c in t27 itself.
The self-hosted core (specs/compiler/core/t27core.t27, PR #6041) must write exactly the
bytes t27c gen-c writes, so every gen-c defect is a defect the core has to refuse instead of
learn. Fixing it here is what lets the core grow. Rules for this task:
- Change the Rust emitter only inside the Boundary below.
gen/is generated: never hand-edit it. - Build with
cargo build --release -p t27cand use./target/release/t27cfrom the repository
root, not at27con PATH. - Write the regression test first and watch it fail on the unfixed compiler. Then fix, and
revert the fix once to watch the test go red again. Say in the PR that you did both. - Any other spec whose generated C changes must be listed in the PR by path, with one line on
why the new bytes are right. A silent corpus change is a failed task. - The PR body says
Closes #plus this issue's number andPart of #5980. ASCII only in source files.
Context
Inside a test block, g = 5; where g is a module var is emitted as uint64_t g = 5; (void)g;: a fresh local that shadows the global, so the global never changes and a function reading it sees 0. The test below fails today for that reason. The self-hosted core refuses an assignment to a name not bound in the test (E_TESTASSIGN).
Reproduce
cargo build --release -p t27c
printf 'module m;\nvar g: i64 = 0;\nfn get() -> i64 {\n return g;\n}\ntest t {\n g = 5;\n assert(get() == 5);\n}\n' > /tmp/shadow.t27
./target/release/t27c gen-c /tmp/shadow.t27
Today the relevant output is:
uint64_t g = 5;
(void)g;
assert((get() == 5)); <- fails at run time: get() still sees 0
Acceptance criteria
-
./target/release/t27c gen-c /tmp/shadow.t27 > /tmp/shadow.c && cc -w -DT27_TEST_MAIN -o /tmp/shadowt /tmp/shadow.c && /tmp/shadowt; echo $?prints0(today134, an assertion failure)
-
./target/release/t27c gen-c /tmp/shadow.t27 | grep -c 'uint64_t g = 5'prints0
-
cargo test --release -p t27c --test genc_test_global_writepasses
Boundary
bootstrap/src/compiler.rsbootstrap/tests/genc_test_global_write.rs
User Scenarios & Testing
- Given
/tmp/shadow.t27written by the Reproduce block,
when./target/release/t27c gen-c /tmp/shadow.t27runs and its output is compiled withcc,
then the test passes: the write reaches the module global.
Requirements
- FR-001: an assignment in a test to a name bound at module level MUST write that binding.
- FR-002: an assignment to a name bound nowhere MUST be refused, not declared.
- FR-900:
cargo test --release -p t27c --test genc_test_global_writeMUST pass, and MUST fail with the fix reverted. - FR-901: if
bootstrap/tests/core_selfhost.rsexists on your base,cargo test --release -p t27c --test core_selfhostMUST still pass.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
owner-approved-foreignOwner-approved exception to the only-t27 rule: hand-written foreign code allowed in this PROwner-approved exception to the only-t27 rule: hand-written foreign code allowed in this PR
Activity
gHashTag commentedon Oct 4, 2026
Related, not a duplicate: #4847 is the same defect in the Zig backend (
gen_test_block/gen_bench_blockturn a test's first write to a module-levelvarinto a freshconstthat shadows it). This issue is the C backend (gen-cemitsuint64_t g = 5; (void)g;). Both need the same seed: put the module-scope names intoboundbefore decidingfresh_binding. A fix that covers both is welcome; say so in the PR and addCloses #4847too.fix(gen-c): keep `- -x` apart instead of emitting the decrement `--x` (…
fix(gen-c): refuse text after `endmodule` instead of dropping it (Closes
fix(gen-c): a const initializer is the whole expression, not its firs…
fix(gen-c): emit a char const instead of dropping it (Closes #6049)
fix(gen-c): a global `** N` repeat keeps its value instead of zeros (C…
fix(gen-c): include <assert.h> where `assert` is used, not only with …
fix(gen-c): a test's write to a module var writes the global (Closes #…
chore(seals): reseal specs whose gen-c output changed (Refs #6050 #6051
fix(gen-c): keep `- -x` apart instead of emitting the decrement `--x` (…
fix(gen-c): refuse text after `endmodule` instead of dropping it (Closes
fix(gen-c): a const initializer is the whole expression, not its firs…
fix(gen-c): emit a char const instead of dropping it (Closes #6049)
fix(gen-c): a global `** N` repeat keeps its value instead of zeros (C…
fix(gen-c): include <assert.h> where `assert` is used, not only with …
fix(gen-c): a test's write to a module var writes the global (Closes #…
chore(seals): reseal specs whose gen-c output changed (Refs #6050 #6051
fix(gen-c): keep `- -x` apart instead of emitting the decrement `--x` (…
fix(gen-c): refuse text after `endmodule` instead of dropping it (Closes
fix(gen-c): a const initializer is the whole expression, not its firs…
fix(gen-c): emit a char const instead of dropping it (Closes #6049)
fix(gen-c): a global `** N` repeat keeps its value instead of zeros (C…
fix(gen-c): include <assert.h> where `assert` is used, not only with …
fix(gen-c): a test's write to a module var writes the global (Closes #…
chore(seals): reseal specs whose gen-c output changed (Refs #6050 #6051