mirror of
https://github.com/nim-lang/Nim.git
synced 2026-09-03 04:13:40 +00:00
IC: don't build interface tables for dep-of-a-dep module loads
`loadTransitiveHooks` loads a module only to register its hooks, macro-cache replay and generic-instance offers. It is a dep-of-a-dep, not an import, so none of its symbols are visible to the module being semchecked — yet it went through the full `loadNifModule`, which builds the interface string tables by calling `loadSymFromIndexEntry` on every index entry, and `processTopLevel`, whose `export` branch calls `resolveSym` on every exported symbol. Both write into an `interf`/`interfHidden` pair that is scratch, shared across all iterations, and never read. Their only other effect is warming the name-keyed `c.syms` cache, which `resolveSym` refills lazily from the same index on a miss. New `LoadFlag.SkipInterfaceTables`, set only by `loadTransitiveHooks`, gates both. Measured on a 219-module program: a one-line edit rebuild goes 5.58s -> 3.21s (-42%), and the compiler's own cold IC build 202s -> 164s (-19%), since every `nim m` in a cold build pays this too. Profile of the `nim m` before: direct imports 17 modules/1.40s, transitive hooks 209 modules/2.99s, re-exports 39/0.23s. This is NOT byte-inert, unlike the previous commit: skipping the eager stub creation shifts the per-module load-order counter, so lazily-created stubs get different serialized item ids and 44 of 238 `.s.bif` change. The artifacts stay deterministic (238/238 reproducible across two independent cold builds) and 16/16 metamorphic tests pass, including `clean cache == incremental cache` and `a no-op edit changes no artifact` — so an existing nimcache re-sems those modules once and then converges. Also green: the 17-file `koch ic` suite, 13/13 differential edit checks against `nim c`, both `bootic` fixed points (debug and release), and 482 tests across tests/arc, destructor, closure, iter, gc, async with a failure set identical to the parent commit's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -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 =
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user