← board

Two same-named file-scope statics share one Procs row, so neither can have a symbol

What was measured

--emit-obj --function-sections on a C translation unit that includes <stdio.h>, <stdlib.h> and <string.h> and calls snprintf, strlen and atoi:

function-sections: CallFix 1089  relocated 1083  pinned-target 6  undefined 0  no-symbol 0  (pinned: sysret sysret sysret sysret sysret sysret)
function-sections: ProcAddrFix 0  relocated 0  baked 0
function-sections: MethodFix 5  relocated 5  baked 0

Six call SITES, one callee. A translation unit with no crtl pull at all (int f(int x) { return x + 1; }) is at pinned-target 0, and so is a Pascal object — so this is specific to two crtl modules being preprocessed into one buffer.

Why it is not a bug today

ObjCallFixIsRelocatable refuses exactly the shape where CallFixTarget[i] (the snapshot of BodyAddr taken when the site was recorded) disagrees with the row's current BodyAddr. Refusing is right: relocating against the one symbol would aim a call at the SECOND body, which is how test/cstatic_same_module_dup.c once went from 2 11 to 11 11. The baked displacement uses the snapshot and reaches the correct body.

Why it blocks the goal

feature-a-every-emit-obj-object-links-its-own-full-copy-of-crtl-so-n-objects-cost-n-runtimes step 2 wants real per-function sections. A baked displacement is computed for this object's own copy of the callee, so a linker that moves, drops or replaces that copy leaves the jump pointing at whatever moves in. Six sites is six opportunities for a silent wrong call, and there is no symbol to relocate against because the first body does not have one — one row, one ObjProcSymIdx.

Everything else is already at zero. .rela.text, .rela.data, .rela.init_array and .rela.fini_array name the .text section symbol zero times in both a Pascal and a C object as of f15ea507e. This is what is left.

The shape of the fix

A file-scope static needs a Procs row per (C module, name), not per name. ProcCModule[procIdx] already records which module a body came from — it is the term the duplicate-definition warning uses to stay quiet about exactly this pair (cparser.inc, "a FOURTH term: both bodies must come from the SAME C module"). So the attribution exists; what does not is a second row to hang it on.

Check before designing: CProcHasLocalDef, ProcCStaticLink, ProcObjRuntimeCopy and ProcUnitIdx are all per-row too, so today the second body's attributes silently overwrite the first's. Splitting the row fixes those as a side effect — which also means a reader should not assume the current values are right for the first body.

Do not "fix" this by renaming. The two functions are legally distinct and gcc compiles them; mangling the name to make them unique would be a compiler-appeasement workaround in the frontend, and the symbol the object exports must stay LOCAL either way.

RESOLVED 2026-09-05 — and this ticket's own reading needed correcting

Fixed by a Procs row per (C module, name) for file-scope statics, plus module-aware C name resolution (CFindProcFromModule) so calls, address-taking and redeclaration all reach the asking module's own body. FindProc itself is untouched: its "representative of a same-named set" contract is load-bearing for pyparser and the Pascal parser.

The "not a wrong-answer bug today" claim was true of CALLS and false of FUNCTION POINTERS. Calls were safe because each site keeps a CallFixTarget snapshot and stays BAKED. Taking a static's ADDRESS never goes through that snapshot. Ablated at 47618f77c240 on a probe whose two module bodies return 1 and 2 (crtl's real pair are byte-identical and cannot discriminate):

  1 1 / 2 2 / 3 2 / 4 2 / 5 0     pre-fix
  1 1 / 2 2 / 3 1 / 4 2 / 5 1     fixed, and gcc's multi-TU answer

Row 3: module A's own function pointer called module B's body. A correct premise with an unstated boundary, which is why re-reading the ticket kept confirming it and only the ablation found it.

The object half, which is what was filed. pinned-target 6 -> 0 on the census, every other family already at zero, and two LOCAL who symbols at different addresses where there was one. Per-function sections is unblocked.

Corpora, all on binary 10492cae86d8: c-testsuite conformance 220/220; c_corpus_probe.sh 3/3 identical; lua 6/6; sqlite amalgamation (unity build, one TU, hundreds of file-scope statics) compiles and its output matches external libsqlite3 byte-for-byte.

The SIBLING ARM IS NOT FIXED and is filed separately as bug-c-two-same-named-file-scope-static-variables-share-one-syms-row-and-alias: static VARIABLES have no SymCModule at all, so two in different modules are ONE variable — pxx 1 2 / 2 2 / 3 70 / 4 70 against gcc's 1 1 / 2 2 / 3 70 / 4 2, i.e. writing module A's copy changes module B's. Zero instances in crtl today. Not widened into this fix because variables resolve through FindSym, which cparser calls 28 times against FindProc's 3.

Log