From 8e0dd4bfb2feeb8d1adec78f8e00acf1dd6761d5 Mon Sep 17 00:00:00 2001 From: Araq Date: Sat, 13 Jun 2026 22:59:12 +0200 Subject: [PATCH] IC: emit-everywhere RTTI in the per-module cg stage (Phase 2b) genTypeInfoV2 has its own owner-routing: when the type's owner module is open for codegen it pushes the RTTI definition into that module and emits only an extern here. Under per-module cg every module except the target is loaded but unwritten, so the definition landed in a discarded backend module and the demanding module kept just an extern -> the RTTI symbol (e.g. an exception type's NTIv2) was defined nowhere and failed to link. Gate the owner-routing (and the reuse-cache shortcut) off when icBackendStage == "cg" so RTTI is emit-everywhere like procs and consts: every demanding module emits the 'd' definition and the merge stage dedups it to one owner. Validated end-to-end on a generic+exception program (shared box[int] instance across two sibling modules, a raised/caught custom exception, seq+string hooks): the per-module pipeline now links with no undefined/duplicate symbols and prints the same output as the whole-program build. Whole-program path unchanged (gate is per-module-cg only); koch ic thallo/tmiscs green. Co-Authored-By: Claude Opus 4.8 --- compiler/ccgtypes.nim | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index a472711143..25f5eaa52e 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -2024,13 +2024,19 @@ proc genTypeInfoV2(m: BModule; t: PType; info: TLineInfo): Rope = m.typeInfoMarkerV2[sig] = result let owner = t.skipTypes(typedescPtrs).itemId.module - if m.config.cmd == cmdNifC and result in m.g.graph.icCachedDataDefs: + # In the per-module backend (`cg`) RTTI is emit-everywhere like procs and + # consts: every demanding module emits the `'d'` definition (deduped to one + # owner by the merge stage). The owner-routing below would instead push the + # definition into the owner module's *unwritten* backend module (discarded in + # this process) and emit only an extern here, leaving the symbol undefined. + let perModuleCg = m.config.cmd == cmdNifC and m.config.icBackendStage == "cg" + if not perModuleCg and m.config.cmd == cmdNifC and result in m.g.graph.icCachedDataDefs: # already defined inside a reused TU from the previous run cgsym(m, "TNimTypeV2") declareNimType(m, "TNimTypeV2", result, owner) m.g.typeInfoMarkerV2[sig] = (str: result, owner: owner) return prefixTI(result) - if owner != m.module.position and myModuleOpenForCodegen(m, FileIndex owner): + if not perModuleCg and owner != m.module.position and myModuleOpenForCodegen(m, FileIndex owner): # make sure the type info is created in the owner module discard genTypeInfoV2(m.g.mods[owner], origType, info) # reference the type info as extern here