IC: a backend stage stops building the interface half it cannot read

`populateInterfaceTablesFromIndex` stubbed a `PSym` for every global symbol a
module declares and inserted it into `interf` and/or `interfHidden`. On a
68-module target that is 2.37M stubs across the build — and 2.04M of them go
into `interfHidden` alone, which a `cg`/`emit` stage has no way to reach.

`interfHidden` is selected exclusively by `modulegraphs.interfSelect`, and only
when `optImportHidden` is in the module's options. That flag is set in exactly
one place — `importer.importModuleAs`, i.e. during sem. A backend stage builds
its module symbols in `moduleFromNifFile` and never imports anything, so the
flag cannot be set and the table cannot be selected. Skipping the hidden-only
branch there is unobservable, not merely unlikely to be observed.

Exported symbols still go into both tables. That is 0.33M inserts against the
2.0M saved, and it leaves `interfHidden` a coherent view — a module with no
hidden symbols — rather than an empty one, should anything ever consult it.

    interface tables   1401ms -> 863ms
    loadDepClosure     2771ms -> 2309ms
    cold --ic:on build  9.38s -> 8.91s

Skipping the tables WHOLESALE in a backend stage does not work, and the failure
is worth recording since it is the obvious next thought: the exported half is
what `magicsys.getSysSym` resolves through, so the stage dies with "system
module needs: pointer". Skipping them in the FRONTEND is worse still — `nim m`
loses `defined`.

Verified: both configurations build; `tests/ic` 40/40; 67/67 generated `.c`
byte-identical, cursor still identical to `PNode`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMyRHByv7hhaQJ4Pa1bHbE
This commit is contained in:
araq
2026-08-30 23:42:06 +02:00
parent 2605cd3213
commit 20a3375571
2 changed files with 21 additions and 2 deletions

View File

@@ -3688,13 +3688,32 @@ proc populateInterfaceTablesFromIndex(c: var DecodeContext; module: FileIndex;
var indexTab = move c.mods[module].index
# Add all symbols to interf (exported interface) and interfHidden
# A BACKEND stage cannot read the hidden-only half, so it does not build it.
# `interfHidden` is reached exclusively through `modulegraphs.interfSelect`,
# which picks it only when `optImportHidden` is in the module's options — and
# that flag is set in exactly one place, `importer.importModuleAs`, i.e.
# during sem. `cg`/`emit` build their module symbols in `moduleFromNifFile`
# and never import anything, so the flag cannot be set and the table cannot be
# selected. The saving is the bulk of this proc: on a 68-module target the
# hidden branch stubs 2.0M symbols against the exported branch's 0.33M.
#
# Exported symbols are still added to BOTH tables. That costs little beside
# the above and leaves `interfHidden` a coherent view (a module with no hidden
# symbols) rather than an empty one, should anything ever consult it.
let conf = c.infos.config
let backendStage = conf.cmd == cmdNifC and
(conf.icBackendStage == "cg" or conf.icBackendStage == "emit")
prof pIfaceModules
for nifName, entry in indexTab:
if entry.vis == Exported:
prof pIfaceExported
let sym = loadSymFromIndexEntry(c, module, nifName, entry, thisModule)
if sym != nil:
strTableAdd(interf, sym)
strTableAdd(interfHidden, sym)
elif not nifName.startsWith("`t"):
elif not backendStage and not nifName.startsWith("`t"):
prof pIfaceHidden
# do not load types, they are not part of an interface but an implementation detail!
#echo "LOADING SYM ", nifName, " ", entry.offset
let sym = loadSymFromIndexEntry(c, module, nifName, entry, thisModule)

View File

@@ -31,7 +31,7 @@ when defined(icBNodeProf):
ProfSlot* = enum
pKind, pTagKindHit, pTagKindMiss, pAstChildren, pSkip, pSon, pLen,
pLastSon, pIterYield, pSym, pTyp, pTypTagLit, pOrigin, pNilType,
pGenBodyCalls, pInfo
pGenBodyCalls, pInfo, pIfaceExported, pIfaceHidden, pIfaceModules
TimeSlot* = enum
tLoadClosure, tModuleId, tBifLoad, tPosIndex, tTopLevel, tInterfTables,
tTransform, tHandOff, tGenBody, tAnalyses,