IC: NimMain init orchestration for the per-module backend (Phase 2b)

In the per-module model each module's init/datInit proc is generated by its
own cg process, but NimMain (emitted with the main module) must call them all.
Previously the main module's cg only registered its own init, so a/b's globals
stayed at their defaults -- the linked exe ran but printed wrong values.

When the cg target is the main module, register every other loaded module's
init/datInit into NimMain via registerReusedModuleToMain (exported), reading
each module's initRequired/datInitRequired from its .c.nif meta head. This is
the same no-codegen registration the whole-program backend uses for reused
TUs; it requires the main module's cg to run last, after every other .c.nif
exists (the per-module nifmake graph will order it so). Modules without init
code have no .c.nif and register nothing.

Validated: the 3-module diamond now builds end-to-end through the per-module
pipeline (cg all with main last, merge, emit all, cc, link) and the resulting
executable prints the correct "16 23" -- matching the whole-program build.
koch ic thallo green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Araq
2026-06-13 22:52:01 +02:00
parent 3df66836d8
commit b3305a6568
2 changed files with 11 additions and 2 deletions

View File

@@ -2270,7 +2270,7 @@ proc registerReusedInit*(g: BModuleList; moduleBase: string;
g.mainModProcs.finishProcHeaderAsProto()
g.otherModsInit.addCallStmt(init)
proc registerReusedModuleToMain(g: BModuleList; m: BModule;
proc registerReusedModuleToMain*(g: BModuleList; m: BModule;
initRequired, datInitRequired: bool) =
## `registerModuleToMain` for a module whose cached translation unit is
## reused: the init/datInit presence comes from the artifact's meta head

View File

@@ -569,10 +569,19 @@ proc generateCgStage(g: ModuleGraph; mainFileIdx: FileIndex) =
return
generateCodeForModule(g, target)
let bl = BModuleList(g.backend)
# The main module also owns the whole-program method dispatchers + NimMain.
if sfMainModule in target.module.flags:
emitMethodDispatchers(g)
let bl = BModuleList(g.backend)
# NimMain (generated when the main module is finished) must call every other
# module's init/datInit. Those translation units are produced by their own
# `cg` processes, so the calls are registered here from each `.c.nif` meta
# head — which is why the main module's `cg` runs last, after every other
# `.c.nif` exists. Modules without init code (no `.c.nif`) register nothing.
for m in bl.mods:
if m != nil and sfMainModule notin m.module.flags:
let heads = readCnifHeads(getCFile(m).string & ".nif")
registerReusedModuleToMain(bl, m, heads.initRequired, heads.datInitRequired)
let tb = bl.mods[target.module.position]
if tb != nil:
finishModule(g, tb)