Repository navigation
Self-host: gen-c writes defer as an unsupported comment and the deferred call never runs #7352
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) 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.
Rules for this task:
- Change gen-c 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 spec that stops compiling must be listed in the PR by path with its fix. 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
gen-c does not lower defer. A defer CALL; statement, the only form the corpus uses (13 specs,
e.g. defer BogatyrRegistry_deinit(®istry);), is written to C as the comment
/* unsupported: StmtExpr */; and the deferred call never runs. Exit status is 0 and the C
compiles, so a test that depends on the deferred call traps at run time, or worse, passes while
the cleanup it claims never happened. defer { ... } is a parse error ("Unexpected token in
expression: LBrace").
Zig runs deferred statements at every exit of the enclosing block (fall-through, return,
break, continue) in reverse order of declaration. A lossless lowering must do the same; if
that is out of scope, gen-c must refuse defer with an error instead of writing a comment.
The core (P1 slice 3) refuses defer with E_SYNTAX.
Reproduce
cargo build --release -p t27c
printf 'module m;\nvar n: i64 = 0;\nfn inc() -> void { n = n + 1; }\nfn f() -> void {\n defer inc();\n}\ntest t { f(); assert(n == 1); }\n' > /tmp/d.t27
./target/release/t27c gen-c /tmp/d.t27 > /tmp/d.c; echo "gen-c exit $?"
grep -c unsupported /tmp/d.c
cc -std=c99 -DT27_TEST_MAIN /tmp/d.c -o /tmp/d && /tmp/d; echo "run exit $?"
Today: gen-c exit 0, 1, and the run aborts on assert(n == 1).
Acceptance criteria
-
- Either the Reproduce run exits 0 with no
unsupportedcomment in the C, or gen-c exits non-zero namingdeferand its line
- Either the Reproduce run exits 0 with no
-
- If
deferis lowered: a fn withdefer a(); defer b();runsbbeforea, and a deferred call runs on an earlyreturn, onbreakand oncontinueinside awhilebody
- If
-
cargo test --release -p t27c --test genc_deferpasses
Boundary
bootstrap/src/compiler.rsbootstrap/tests/genc_defer.rs
User Scenarios & Testing
- Given a fn whose first statement is
defer inc();and which returns early,
when./target/release/t27c gen-clowers it and the C runs,
theninchas run exactly once on each path, or gen-c refused the spec.
Requirements
- FR-001: gen-c MUST NOT write
/* unsupported: ... */in place of a statement and exit 0. - FR-002: a lowered
deferMUST run at every exit of its block, in reverse order. - FR-900:
cargo test --release -p t27c --test genc_deferMUST pass, and MUST fail with the fix reverted. - FR-901:
cargo test --release -p t27c --test core_selfhostand the corpus suite MUST still pass.
Part of #5980
Activity
t27core: nested arrays and list initializers, byte-identical to gen-c (…
seals: reseal the 7 specs whose C output now runs their defers (#7352)
seal: reseal main_zig_handwritten for the defer lowering (#7352)
gHashTag commentedon Oct 7, 2026
The fix in #7406 is parked as a draft: it is over the foreign-line budget (#7371). compiler.rs +189, tests +207, 396 in total, against limits of 40 per file and 80 per PR. Details are on #7406.