mirror of
https://github.com/nim-lang/Nim.git
synced 2026-09-01 19:33:42 +00:00
IC: the cg stage must not write emit's .c
`registerModuleCode` writes the module's `.c` and registers it for compilation.
Under `--icBackendStage:cg` it must do neither: `cg`'s product is the `.c.nif`,
the `.c` is rendered by `emit` from that artifact plus the GLOBAL merge
decision, and the compile registration belongs to `link`.
Writing it in `cg` puts a second, differently-filtered `.c` — `cg` can only
filter by the liveness its own process sees — at the exact path `emit` declares
as its nifmake output. Two stages claim one output, and the `.c` ends up newer
than `emit`'s own `.c.nif` input, so nifmake considers `emit` up to date and
skips it: `cg`'s unfiltered text goes to the linker and every emit-everywhere
body is defined twice ("multiple definition of eqdup__u727466987__OOZ…").
Nothing surfaces this today because `merge` rewrites the decision file on every
run and every `emit` lists it as an input, so all of them re-fire and overwrite
the stray file. The fire-all is therefore load-bearing, not the "insurance" it
is documented as — and it is what makes a content-stable merge decision
(219 fewer processes and ~13s less backend CPU per edit on a 219-module
program) impossible: that experiment is exactly how this was found.
No behaviour change on its own, since the fire-all still runs. Green: `bootic`
fixed point, 16/16 metamorphic IC tests, the 17-file `koch ic` suite, and 13/13
differential edit checks against `nim c`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2929,6 +2929,23 @@ proc genModuleCode(m: BModule; cf: var Cfile): string =
|
||||
proc registerModuleCode(m: BModule; cf: var Cfile; code: string) =
|
||||
## Second half of `writeModule`: writes the .c file if it changed and
|
||||
## registers it for compilation.
|
||||
##
|
||||
## NOT under the per-module backend's `cg` stage. There the `.c` belongs to
|
||||
## `emit`, which renders it from the `.c.nif` using the GLOBAL merge decision;
|
||||
## `cg` can only filter by the liveness its own process can see, so writing
|
||||
## here puts a second, differently-filtered `.c` at the very path `emit`
|
||||
## declares as its nifmake output. Two stages then claim one output, and the
|
||||
## `.c` ends up newer than `emit`'s own `.c.nif` input — so any build in which
|
||||
## `emit` is not forced to run anyway keeps `cg`'s unfiltered text and hands it
|
||||
## to the linker ("multiple definition of eqdup__…").
|
||||
##
|
||||
## Today nothing surfaces this: `merge` rewrites the decision file on every
|
||||
## run and every `emit` lists it as an input, so all of them re-fire and
|
||||
## overwrite the stray file. That makes the fire-all load-bearing rather than
|
||||
## the "insurance" it is documented as, and it silently blocks making the
|
||||
## decision content-stable. `cg`'s product is the `.c.nif`; the compile
|
||||
## registration is likewise the `link` stage's job.
|
||||
if m.config.cmd == cmdNifC and m.config.icBackendStage == "cg": return
|
||||
if code != "" or m.config.symbolFiles != disabledSf:
|
||||
when hasTinyCBackend:
|
||||
if m.config.cmd == cmdTcc:
|
||||
|
||||
Reference in New Issue
Block a user