← board

Track guessed as C from the test source. The ranker reads frontmatter, so an unset track parks a stub in Track T's queue regardless of what the body says -- correct the track: line if this is wrong.

origin/master has advanced 13 commit(s) since this sha. Re-verify at current HEAD before acting — the callback is tagged to the sha that was tested, which may no longer be the state of the tree.

regression: test-core#src:test/cstatic_same_module_dup.c red at 99dcac2a2ade (auto-filed by twatch)

Repro

tools/testmgr.py --tier native --job 'test-core#src:test/cstatic_same_module_dup.c' at 99dcac2a2ade0352ceb8fe8fc8aadbc2071ca422

Range

bad 99dcac2a2ade, last good de2de369ea6a, 11 commit(s) in range — the watcher narrows this by idle bisect; check tstate/TSTATE.md for the current range.

Log tail


Stub ticket: signal only. Track T agent (face 2) enriches or a dev track takes it from the repro line.

Root cause found and fixed — 2026-08-21 (agent-A)

Real, reproducible at HEAD, and self-inflicted: fa() printed 11 instead of 2, so the program said 11 11 where the Makefile row wants 2 11. The duplicate-definition warning still fired — only the BINDING moved.

Cause: CallFix stopped meaning "unresolved forward reference". The DCE work (8cc9666fc, in this ticket's own range) made EmitCallProc call RecordInternalCall for every internal direct call, not just the forward ones, because a body cannot be relocated unless every site that names it is listed. That was right for DCE and quietly wrong for everything else: ApplyCallFixups re-resolves each listed site from Procs[ProcIdx].BodyAddr, and that row is not immutable. A C file with two same-named file-scope statics compiles both bodies and the later one overwrites the row — so calls that had already been emitted against the FIRST body were re-aimed at the second. fa(), written between the two definitions, is exactly that call.

This is the only construct in the tree where a proc row's BodyAddr is overwritten after a call has been emitted against it, which is why one C test caught it and nothing else did.

Fix: remember what each site was aimed at. New CallFixTarget, parallel to CallFix (a separate array, not a third record field, so the static ProcAddrFix/IramCallFix arrays do not grow by a third for a field neither uses). RecordInternalCall snapshots Procs[procIdx].BodyAddr-1 for a genuine forward reference, an address for a site already bound. ApplyCallFixups patches to the snapshot when there is one and resolves through the proc row only when there is not, which is precisely the pre-DCE behaviour with the DCE list intact. DCE's compaction remaps the snapshot through DceNewOff exactly as it remaps the site: both are code offsets and both move with the code.

The first body survives DCE for free — it is not any proc row's BodyAddr any more, so DceOwnerOf answers -1 (always-live region) for its bytes.

Verified: 2 11 again, with the warning still firing exactly once, and cstatic_two_modules.c still warning zero times and printing its five lines. Same 2 11 under --dce, under -O3 (which implies --dce), and cross-built for aarch64 / arm32 / riscv32 / i386 under qemu — ApplyCallFixups has a per-arch patch arm and every one of them was reading the mutable row. A --dce-built compiler still emits byte-identical output for the whole compiler source.

Gate: make compiler/pascal26 + tools/gate.sh quick GREEN.