← board

The C driver misses the shared runtime finalisation step

compiler/cparser.inc:9535 calls EmitIoLockStubsForTarget;. That is now half of a step: EmitProgramRuntimeStubsForTarget (ir_codegen.inc) emits the signal runtime and the I/O lock stubs, and is what every other frontend driver calls — Pascal, NilPy, Rust, Zig, BASIC, Erlang, Ada, Whitespace.

Until it does, a C program has no signal runtime: BSS_SIG_* stays unallocated (all four offsets aliased onto BSS[0]) and --no-signals is a no-op because there is nothing to opt out of.

The fix

EmitProgramRuntimeStubsForTarget;   { was: EmitIoLockStubsForTarget }

Why Track A did not just do it

cparser.inc is Track C's file-lane and A must not edit it concurrently — the same rule that keeps lexer.inc safe. Filed from bug-a-only-the-pascal-driver-emits-the-signal-runtime, which did the other eight drivers.

Also worth a look while you are there

The C driver has its own per-target _start entry stub (cparser.inc:9312-9330 and the parallel riscv32/xtensa arms) with argc/argv loading and the initializer/finalizer calls. EmitProgramEntryForTarget (ir_codegen.inc) is the shared entry stub the other drivers now use; it does not yet cover C's shape. Whether C's stub should grow into it, or the shared one should grow an argc/argv+initializers mode, is row 2 of refactor-a-the-missing-layer-between-frontends-and-backends — a Track C call to make, not an A one.

Gate

C tests green + self-host byte-identical. A C binary gains ~400 bytes and --no-signals removes them again.

Outcome

Already fixed, two days before I picked it up: 2cba0af20 (2026-08-24, "the Rust, Zig and BASIC drivers take the shared prologue; C gains the signal runtime it never had") made the one-line change and left a fifteen-line comment at cparser.inc:9504 explaining it. The ticket was never moved.

Acceptance criterion verified at 5c5115038 — the ticket asks for "a C binary gains ~400 bytes and --no-signals removes them again":

int printf(const char *, ...);
int main(void) { printf("ok\n"); return 0; }
build code bss
default 224876 59368
--no-signals 224490 59360

386 bytes of code and 8 of BSS, so the signal runtime is emitted and --no-signals is no longer a no-op — which was the ticket's actual complaint. Runs clean.

The "also worth a look" section — whether C's per-arch _start stub should merge with EmitProgramEntryForTarget — is untouched and still open as row 2 of [[refactor-a-the-missing-layer-between-frontends-and-backends]]. 2cba0af20's comment says the same thing in the code: "Merging those two conventions is its own change; this is the step of the checklist that was simply missing."

Log