diff --git a/compiler/ast2nif.nim b/compiler/ast2nif.nim index 5e1e482390..a6842ff530 100644 --- a/compiler/ast2nif.nim +++ b/compiler/ast2nif.nim @@ -2545,6 +2545,18 @@ proc cursorFromIndexEntry(c: var DecodeContext; module: FileIndex; entry: NifInd type LoadFlag* = enum LoadFullAst, AlwaysLoadInterface + SkipInterfaceTables + ## Do not eagerly build the module's interface string tables. Set by + ## `modulegraphs.loadTransitiveHooks`, which loads a module only to + ## register its hooks / macro-cache replay / generic-instance offers and + ## throws the tables away — the module is a dep-of-a-dep, not an import, so + ## none of its symbols are visible to the module being semchecked. + ## + ## The eager pass calls `loadSymFromIndexEntry` for EVERY index entry, and + ## its only other effect is pre-populating the name-keyed `c.syms` cache — + ## which `resolveSym` fills lazily on a miss anyway, straight from the same + ## index. So for these loads it is pure work: on a 219-module program a + ## one-line edit paid it 209 times over. proc isGlobalIndexSym(s, dottedSuffix: string): bool = ## Mirror of `nifbuilder.addSymbolDefRetIsGlobal` / `bif.isGlobalSymbol`: a sym @@ -3834,6 +3846,13 @@ proc processTopLevel(c: var DecodeContext; cur: var Cursor; flags: set[LoadFlag] elif tagIs(cur, "reppureenum"): loadLogOp(c, result.logOps, cur, PureEnumEntry, attachedTrace, module) elif tagIs(cur, "repcppmember"): loadLogOp(c, result.logOps, cur, CppMemberEntry, attachedTrace, module) elif tagIs(cur, "export"): + if SkipInterfaceTables in flags: + # Same reason the interface tables are skipped: `interf` is a scratch + # table this caller throws away, so every `resolveSym` here (one per + # exported symbol, plus `addReexportedEnumFields`) only warms the + # name-keyed `c.syms` cache that `resolveSym` refills lazily on a miss. + skip cur + continue cur.into: while cur.hasMore and cur.kind == DotToken: skip cur # flags / type while cur.hasMore: @@ -3973,7 +3992,8 @@ proc loadNifModule*(c: var DecodeContext; suffix: ModuleSuffix; interf, interfHi # Populate interface tables from the NIF index structure # Symbols are created as stubs (Partial state) and will be loaded lazily via loadSym # Use exports collected by processTopLevel - populateInterfaceTablesFromIndex(c, module, interf, interfHidden, string(suffix)) + if SkipInterfaceTables notin flags: + populateInterfaceTablesFromIndex(c, module, interf, interfHidden, string(suffix)) proc loadNifModule*(c: var DecodeContext; f: FileIndex; interf, interfHidden: var TStrTable; flags: set[LoadFlag] = {}): PrecompiledModule = diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim index a0d8250938..55a5293674 100644 --- a/compiler/modulegraphs.nim +++ b/compiler/modulegraphs.nim @@ -1035,7 +1035,13 @@ when not defined(nimKochBootstrap): var isKnownFile = false let fileIdx = g.config.registerNifSuffix(string suffix, isKnownFile) if not g.hookClosure.containsOrIncl(fileIdx.int): - let precomp = loadNifModule(ast.program, suffix, interf, interfHidden, {}) + # `SkipInterfaceTables`: `interf`/`interfHidden` here are scratch tables + # shared by every iteration and never read — this module is a + # dep-of-a-dep, so none of its symbols are visible to the module being + # semchecked. Building them called `loadSymFromIndexEntry` for every + # index entry of every closure member. + let precomp = loadNifModule(ast.program, suffix, interf, interfHidden, + {SkipInterfaceTables}) registerLoadedHooks(g, precomp.logOps) # Record this transitively-loaded module so the sem driver applies its # VM-level load effects (macro-cache replay + `{.compileTime.}` global init)