mirror of
https://github.com/nim-lang/Nim.git
synced 2026-08-05 15:08:44 +00:00
IC: big steps forward
This commit is contained in:
@@ -26,6 +26,13 @@ import typekeys
|
||||
import ic / [enum2nif]
|
||||
|
||||
const SysModuleSuffix* = "@sys"
|
||||
const BackendLocalMarker* = "@bk"
|
||||
## Suffix marker for a PROCESS-LOCAL backend-minted entity (a closure `:env`
|
||||
## type/obj/field/hidden-param minted while the VM compiles a routine body to
|
||||
## run a macro). Such entities have no stable cross-process identity, so each
|
||||
## module that references one emits its OWN module-local def named
|
||||
## `…<thisModuleSuffix>@bk` and the loader homes it to the reading module with
|
||||
## a `backendItemId` (disjoint from real ids). See transf.transformBody.
|
||||
## Reserved module-suffix sentinel for module-less magic singleton types — the
|
||||
## `nil` type is created via `newSysType` with the graph idgen, whose `module`
|
||||
## can be `-1` (e.g. during VM const-eval before a real module is current), so
|
||||
@@ -189,6 +196,9 @@ type
|
||||
#writtenSyms: seq[PSym] # symbols written in this module, to be unloaded later
|
||||
writtenPackages: HashSet[string]
|
||||
depSuffixes: HashSet[string] # module suffixes already emitted as `(import ...)` deps
|
||||
emittedBackendTypes: HashSet[int32] # backend-local type items already def'd this module
|
||||
emittedBackendSyms: HashSet[int32] # backend-local sym items already def'd this module
|
||||
|
||||
|
||||
proc isLocalSym(sym: PSym): bool {.inline.} =
|
||||
## Every symbol is emitted as a *global* (module-suffixed) name so that its
|
||||
@@ -217,7 +227,17 @@ const
|
||||
proc toNifSymName(w: var Writer; sym: PSym): string =
|
||||
## Generate NIF name for a symbol: local names are `ident.disamb`,
|
||||
## global names are `ident.disamb.moduleSuffix`
|
||||
assert not sym.itemId.isBackendMinted
|
||||
if sym.itemId.isBackendMinted:
|
||||
# Process-local backend sym (closure env field / hidden `:env` param minted
|
||||
# during a VM transform): re-home to the current module with the `@bk`
|
||||
# marker so each referencing module self-contains it. See transformBody.
|
||||
result = sym.name.s
|
||||
result.add '.'
|
||||
result.addInt sym.disamb
|
||||
result.add '.'
|
||||
result.add modname(w.currentModule, w.infos.config)
|
||||
result.add BackendLocalMarker
|
||||
return
|
||||
result = sym.name.s
|
||||
if sym.kindImpl == skPackage:
|
||||
result.add PkgMarker
|
||||
@@ -239,6 +259,11 @@ proc globalName*(sym: PSym; config: ConfigRef): string =
|
||||
result.addInt sym.disamb
|
||||
result.add '.'
|
||||
result.add modname(sym.itemId.module, config)
|
||||
# A loaded process-local backend sym keeps its `@bk` marker in the NIF name
|
||||
# (the index/`c.syms` tables are keyed by it); mirror toNifSymName so name-based
|
||||
# lookups via globalName don't miss (KeyError `:env.N.<mod>` without the marker).
|
||||
if sym.itemId.isBackendMinted:
|
||||
result.add BackendLocalMarker
|
||||
|
||||
type
|
||||
ParsedSymName* = object
|
||||
@@ -312,9 +337,24 @@ proc writeLoc(w: var Writer; dest: var TokenBuf; loc: TLoc) =
|
||||
writeFlags(dest, loc.flags) # TLocFlags
|
||||
dest.addStrLit loc.snippet
|
||||
|
||||
proc nifTypeName(w: Writer; typ: PType): string =
|
||||
## NIF name of a type as written by THIS module. A process-local backend env
|
||||
## type is re-homed to the current module with the `@bk` marker (see
|
||||
## BackendLocalMarker); everything else uses the canonical `typeToNifSym`.
|
||||
if typ.uniqueId.isBackendMinted:
|
||||
result = "`t"
|
||||
result.addInt ord(typ.kind)
|
||||
result.add '.'
|
||||
result.addInt typ.uniqueId.item
|
||||
result.add '.'
|
||||
result.add modname(w.currentModule, w.infos.config)
|
||||
result.add BackendLocalMarker
|
||||
else:
|
||||
result = typeToNifSym(typ, w.infos.config)
|
||||
|
||||
proc writeTypeDef(w: var Writer; dest: var TokenBuf; typ: PType) =
|
||||
dest.buildTree tdefTag:
|
||||
dest.addSymDef pool.syms.getOrIncl(typeToNifSym(typ, w.infos.config)), NoLineInfo
|
||||
dest.addSymDef pool.syms.getOrIncl(nifTypeName(w, typ)), NoLineInfo
|
||||
dest.addDotToken # always private for the index generator
|
||||
|
||||
#dest.addIdent toNifTag(typ.kind)
|
||||
@@ -352,6 +392,15 @@ proc writeTypeDef(w: var Writer; dest: var TokenBuf; typ: PType) =
|
||||
proc writeType(w: var Writer; dest: var TokenBuf; typ: PType) =
|
||||
if typ == nil:
|
||||
dest.addDotToken()
|
||||
elif typ.uniqueId.isBackendMinted:
|
||||
# Process-local closure env (see transf.transformBody): emit a MODULE-LOCAL
|
||||
# `@bk` def the first time it is reached in this module, reference it after.
|
||||
# Per-Writer dedup (NOT the shared `state`), since every referencing module
|
||||
# must emit its own copy.
|
||||
if not w.emittedBackendTypes.containsOrIncl(typ.uniqueId.item):
|
||||
writeTypeDef(w, dest, typ)
|
||||
else:
|
||||
dest.addSymUse pool.syms.getOrIncl(nifTypeName(w, typ)), NoLineInfo
|
||||
elif typ.uniqueId.module == w.currentModule and typ.state == Complete:
|
||||
# Ownership for serialization is decided by `uniqueId`, not `itemId`: the NIF
|
||||
# name (`typeToNifSym`) and the loader (`createTypeStub`) both key off
|
||||
@@ -363,7 +412,7 @@ proc writeType(w: var Writer; dest: var TokenBuf; typ: PType) =
|
||||
typ.state = Sealed
|
||||
writeTypeDef(w, dest, typ)
|
||||
else:
|
||||
dest.addSymUse pool.syms.getOrIncl(typeToNifSym(typ, w.infos.config)), NoLineInfo
|
||||
dest.addSymUse pool.syms.getOrIncl(nifTypeName(w, typ)), NoLineInfo
|
||||
|
||||
proc writeBool(dest: var TokenBuf; b: bool) =
|
||||
dest.buildTree (if b: "true" else: "false"):
|
||||
@@ -471,6 +520,13 @@ proc shouldWriteSymDef(w: var Writer; sym: PSym): bool {.inline.} =
|
||||
proc writeSym(w: var Writer; dest: var TokenBuf; sym: PSym) =
|
||||
if sym == nil:
|
||||
dest.addDotToken()
|
||||
elif sym.itemId.isBackendMinted:
|
||||
# Process-local backend sym (closure env field / hidden `:env` param): emit a
|
||||
# MODULE-LOCAL `@bk` def the first time, reference it after. Per-Writer dedup.
|
||||
if not w.emittedBackendSyms.containsOrIncl(sym.itemId.item):
|
||||
writeSymDef(w, dest, sym)
|
||||
else:
|
||||
dest.addSymUse pool.syms.getOrIncl(w.toNifSymName(sym)), NoLineInfo
|
||||
elif shouldWriteSymDef(w, sym):
|
||||
sym.state = Sealed
|
||||
writeSymDef(w, dest, sym)
|
||||
@@ -495,8 +551,16 @@ proc writeSymNode(w: var Writer; dest: var TokenBuf; n: PNode; sym: PSym) =
|
||||
var nodeTyp = n.typField
|
||||
if nodeTyp == nil and nfLazyType in n.flags:
|
||||
nodeTyp = sym.typImpl
|
||||
if shouldWriteSymDef(w, sym):
|
||||
sym.state = Sealed
|
||||
# Backend-minted syms (process-local closure `:env` param/fields) are emitted
|
||||
# as MODULE-LOCAL `@bk` defs the first time reached this module (per-Writer
|
||||
# dedup shared with `writeSym`), regardless of module: their itemId.module is
|
||||
# the systemModule of `vmTransfIdgen`, so `shouldWriteSymDef` (which gates on
|
||||
# currentModule) would otherwise only ever emit a SymUse → dangling def.
|
||||
let wantDef =
|
||||
if sym.itemId.isBackendMinted: not w.emittedBackendSyms.containsOrIncl(sym.itemId.item)
|
||||
else: shouldWriteSymDef(w, sym)
|
||||
if wantDef:
|
||||
if not sym.itemId.isBackendMinted: sym.state = Sealed
|
||||
if nodeTyp != n.sym.typImpl:
|
||||
dest.buildTree hiddenTypeTag, trLineInfo(w, n.info):
|
||||
writeType(w, dest, nodeTyp)
|
||||
@@ -1617,7 +1681,10 @@ proc tryCreateTypeStub(c: var DecodeContext; t: SymId): PType =
|
||||
let suffix = name.substr(i)
|
||||
if suffix == SysModuleSuffix:
|
||||
return reconstructSysType(c, name, k, itemVal)
|
||||
let id = itemId(moduleId(c, suffix).int32, itemVal)
|
||||
let isBk = suffix.endsWith(BackendLocalMarker)
|
||||
let realSuffix = if isBk: suffix[0 ..< suffix.len - BackendLocalMarker.len] else: suffix
|
||||
let modIdx = moduleId(c, realSuffix).int32
|
||||
let id = if isBk: backendItemId(modIdx, itemVal) else: itemId(modIdx, itemVal)
|
||||
let ii = addr c.mods[id.module.FileIndex].index
|
||||
let offs = ii[].getOrDefault(name)
|
||||
if offs.offset == 0:
|
||||
@@ -1644,7 +1711,10 @@ proc createTypeStub(c: var DecodeContext; t: SymId): PType =
|
||||
let suffix = name.substr(i)
|
||||
if suffix == SysModuleSuffix:
|
||||
return reconstructSysType(c, name, k, itemVal)
|
||||
let id = itemId(moduleId(c, suffix).int32, itemVal)
|
||||
let isBk = suffix.endsWith(BackendLocalMarker)
|
||||
let realSuffix = if isBk: suffix[0 ..< suffix.len - BackendLocalMarker.len] else: suffix
|
||||
let modIdx = moduleId(c, realSuffix).int32
|
||||
let id = if isBk: backendItemId(modIdx, itemVal) else: itemId(modIdx, itemVal)
|
||||
let ii = addr c.mods[id.module.FileIndex].index
|
||||
let offs = ii[].getOrDefault(name)
|
||||
if offs.offset == 0:
|
||||
@@ -1732,10 +1802,16 @@ proc loadSymStub(c: var DecodeContext; t: SymId; thisModule: string;
|
||||
# Global symbol - look up in index for lazy loading
|
||||
result = c.syms.getOrDefault(symAsStr)[0]
|
||||
if result == nil:
|
||||
let module = moduleId(c, sn.module)
|
||||
# A process-local backend sym (closure env field / `:env` param) is named
|
||||
# `…<thisModuleSuffix>@bk`: home it to that module with a backendItemId so it
|
||||
# stays disjoint from the loader's real per-module id space (see toNifSymName).
|
||||
let isBk = sn.module.endsWith(BackendLocalMarker)
|
||||
let realMod = if isBk: sn.module[0 ..< sn.module.len - BackendLocalMarker.len]
|
||||
else: sn.module
|
||||
let module = moduleId(c, realMod)
|
||||
let val = addr c.mods[module].symCounter
|
||||
inc val[]
|
||||
let id = itemId(module.int32, val[])
|
||||
let id = if isBk: backendItemId(module.int32, val[]) else: itemId(module.int32, val[])
|
||||
|
||||
let offs = c.getOffset(module, symAsStr)
|
||||
let (stubKind, stubName) = stubKindAndName(c.cache, sn.name)
|
||||
@@ -2122,11 +2198,14 @@ proc loadSymFromIndexEntry(c: var DecodeContext; module: FileIndex;
|
||||
if result == nil:
|
||||
let symAsStr = nifName
|
||||
let sn = parseSymName(symAsStr)
|
||||
let symModule = moduleId(c, if sn.module.len > 0: sn.module else: thisModule)
|
||||
let rawMod = if sn.module.len > 0: sn.module else: thisModule
|
||||
let isBk = rawMod.endsWith(BackendLocalMarker)
|
||||
let realMod = if isBk: rawMod[0 ..< rawMod.len - BackendLocalMarker.len] else: rawMod
|
||||
let symModule = moduleId(c, realMod)
|
||||
let val = addr c.mods[symModule].symCounter
|
||||
inc val[]
|
||||
|
||||
let id = itemId(symModule.int32, val[])
|
||||
let id = if isBk: backendItemId(symModule.int32, val[]) else: itemId(symModule.int32, val[])
|
||||
let (stubKind, stubName) = stubKindAndName(c.cache, sn.name)
|
||||
result = PSym(itemId: id, kindImpl: stubKind, name: stubName, disamb: sn.count.int32, state: Partial)
|
||||
c.syms[symAsStr] = (result, entry)
|
||||
@@ -2199,7 +2278,10 @@ proc resolveSym(c: var DecodeContext; symAsStr: string; alsoConsiderPrivate: boo
|
||||
let sn = parseSymName(symAsStr)
|
||||
if sn.module.len == 0:
|
||||
return nil # Local symbols shouldn't be hooks
|
||||
let module = moduleId(c, sn.module)
|
||||
let isBk = sn.module.endsWith(BackendLocalMarker)
|
||||
let realMod = if isBk: sn.module[0 ..< sn.module.len - BackendLocalMarker.len]
|
||||
else: sn.module
|
||||
let module = moduleId(c, realMod)
|
||||
# Look up the symbol in the module's index
|
||||
# Try both formats: with module suffix (e.g., "foo.0.modulename") and without (e.g., "foo.0.")
|
||||
# NIF spec allows local symbols to be stored without module suffix
|
||||
@@ -2215,7 +2297,7 @@ proc resolveSym(c: var DecodeContext; symAsStr: string; alsoConsiderPrivate: boo
|
||||
# Create a stub symbol
|
||||
let val = addr c.mods[module].symCounter
|
||||
inc val[]
|
||||
let id = itemId(int32(module), val[])
|
||||
let id = if isBk: backendItemId(int32(module), val[]) else: itemId(int32(module), val[])
|
||||
result = PSym(itemId: id, kindImpl: skProc, name: c.cache.getIdent(sn.name),
|
||||
disamb: sn.count.int32, state: Partial)
|
||||
c.syms[symAsStr] = (result, offs)
|
||||
|
||||
@@ -210,7 +210,62 @@ proc lookupInRecord(n: PNode, id: ItemId): PSym =
|
||||
if matchesDerivedFieldId(n.sym.itemId, id): result = n.sym
|
||||
else: discard
|
||||
|
||||
proc lookupCapturedField(n: PNode, s: PSym): PSym =
|
||||
## Find an env field that `addField` would have produced for the captured
|
||||
## local `s`. Used as a fallback when the derived-itemId match fails because
|
||||
## `s` is a macro-generated gensym whose process-local id diverges from the
|
||||
## loaded env field's (see `addField`). `addField` always names a field
|
||||
## `s.name & $field.position`, so that pair uniquely identifies the field for a
|
||||
## local of this name without relying on the (unstable) item id.
|
||||
result = nil
|
||||
case n.kind
|
||||
of nkRecList:
|
||||
for i in 0..<n.len:
|
||||
result = lookupCapturedField(n[i], s)
|
||||
if result != nil: return
|
||||
of nkRecCase:
|
||||
if n[0].kind != nkSym: return
|
||||
result = lookupCapturedField(n[0], s)
|
||||
if result != nil: return
|
||||
for i in 1..<n.len:
|
||||
case n[i].kind
|
||||
of nkOfBranch, nkElse:
|
||||
result = lookupCapturedField(lastSon(n[i]), s)
|
||||
if result != nil: return
|
||||
else: discard
|
||||
of nkSym:
|
||||
if n.sym.kind == skField and n.sym.name.s == s.name.s & $n.sym.position:
|
||||
result = n.sym
|
||||
else: discard
|
||||
|
||||
proc addField*(obj: PType; s: PSym; cache: IdentCache; idgen: IdGenerator): PSym =
|
||||
# Idempotent w.r.t. the captured symbol (mirrors `addUniqueField`): re-lifting
|
||||
# a LOADED routine re-derives its transformed body (never serialized under IC)
|
||||
# and re-captures the same locals, but the env object loaded from the NIF
|
||||
# already carries their fields. Re-adding would duplicate the field and, worse,
|
||||
# mutate a Sealed loaded type via `propagateToOwner` (the `t.state != Sealed`
|
||||
# crash). Return the existing field instead.
|
||||
let existing = lookupInRecord(obj.n, s.itemId)
|
||||
if existing != nil:
|
||||
return existing
|
||||
# Re-lifting a LOADED routine during a VM transform (its transformed body is
|
||||
# re-derived per process, never serialized) re-captures the same locals, but
|
||||
# for a macro-generated gensym (e.g. libp2p `p2pProtocolBackendImpl`'s
|
||||
# `msgVar`) its process-local id diverges from the one baked into the loaded
|
||||
# env field, so the id match above misses. Reuse the existing same-named field
|
||||
# rather than appending a divergent duplicate, which keeps the re-derived
|
||||
# closure consistent (else a stale `:env` access reaches `cannotEval`).
|
||||
# Confined to a loaded (Sealed) env: in a freshly built env ids are consistent,
|
||||
# and two distinct same-named captures legitimately get distinct fields there.
|
||||
if obj.state == Sealed:
|
||||
let byName = lookupCapturedField(obj.n, s)
|
||||
if byName != nil:
|
||||
return byName
|
||||
# Genuinely new field. Under IC the env may be a loaded Sealed type whose
|
||||
# transform-time mutation is process-local (the body is discarded after the
|
||||
# macro runs), so downgrade it to mutable instead of crashing on
|
||||
# `t.state != Sealed` (mirrors `markAsClosure`).
|
||||
unsealForTransform(obj)
|
||||
# because of 'gensym' support, we have to mangle the name with its ID.
|
||||
# This is hacky but the clean solution is much more complex than it looks.
|
||||
var field = newSym(skField, getIdent(cache, s.name.s & $obj.n.len),
|
||||
|
||||
@@ -146,11 +146,19 @@ type
|
||||
cacheSeqs*: Table[string, PNode] # state that is shared to support the 'macrocache' API; IC: implemented
|
||||
cacheCounters*: Table[string, BiggestInt] # IC: implemented
|
||||
cacheTables*: Table[string, BTree[string, PNode]] # IC: implemented
|
||||
transitiveReplayActions*: seq[PNode] # macro-cache replay actions collected from
|
||||
# the transitive import closure of a NIF-loaded module (loadTransitiveHooks);
|
||||
# the caller (pipelines) replays them so a dependency's macrocache state — e.g.
|
||||
# nim-serialization's flavor registration — reaches a module that imports it
|
||||
# only indirectly. Drained per moduleFromNifFile call.
|
||||
pendingNifInit*: seq[tuple[module: PSym; topLevel: PNode]]
|
||||
# EVERY module loaded from a NIF — whether a direct import (moduleFromNifFile)
|
||||
# or only a dep-of-a-dep (loadTransitiveHooks) — is recorded here with its
|
||||
# serialized top-level AST. The sem driver drains it once
|
||||
# (pipelines.finalizeLoadedModules) and applies the module's VM-level load
|
||||
# effects UNIFORMLY: macro-cache replay (std/macrocache put/inc/add/incl) and
|
||||
# eager `{.compileTime.}` global init. This is the single place "what a loaded
|
||||
# module does to global state" lives, so a transitively-reached module — which
|
||||
# never passes through compilePipelineModule — gets the SAME treatment as a
|
||||
# direct import instead of silently skipping it (its macrocache state would be
|
||||
# lost; its CT globals would stay nil and a macro splicing one, e.g.
|
||||
# chronicles' `chroniclesBlockName`, emits `break nil` / `nil == 0`). To add a
|
||||
# new per-load VM effect, extend the drain — never a parallel buffer.
|
||||
passes*: seq[TPass]
|
||||
pipelinePass*: PipelinePass
|
||||
onDefinition*: proc (graph: ModuleGraph; s: PSym; info: TLineInfo) {.nimcall.}
|
||||
@@ -160,6 +168,9 @@ type
|
||||
strongSemCheck*: proc (graph: ModuleGraph; owner: PSym; body: PNode) {.nimcall.}
|
||||
compatibleProps*: proc (graph: ModuleGraph; formal, actual: PType): bool {.nimcall.}
|
||||
idgen*: IdGenerator
|
||||
vmTransfIdgen*: IdGenerator # process-local backend idgen for closure envs
|
||||
# minted while the VM compiles a routine body
|
||||
# (inVMTransform); see lambdalifting / ast2nif @bk
|
||||
operators*: Operators
|
||||
|
||||
cachedFiles*: StringTableRef
|
||||
@@ -949,14 +960,17 @@ when not defined(nimKochBootstrap):
|
||||
if not g.hookClosure.containsOrIncl(fileIdx.int):
|
||||
let precomp = loadNifModule(ast.program, suffix, interf, interfHidden, {})
|
||||
registerLoadedHooks(g, precomp.logOps)
|
||||
# Collect the dependency's macro-cache replay actions (put/inc/add/incl)
|
||||
# so the importer being compiled also sees macrocache state registered
|
||||
# by a transitively-imported module. Pragma replay actions are a backend
|
||||
# concern and are intentionally not collected here.
|
||||
for n in precomp.topLevel:
|
||||
if n.kind == nkReplayAction and n.len >= 1 and n[0].kind == nkStrLit and
|
||||
n[0].strVal in ["put", "inc", "add", "incl"]:
|
||||
g.transitiveReplayActions.add n
|
||||
# Record this transitively-loaded module so the sem driver applies its
|
||||
# VM-level load effects (macro-cache replay + `{.compileTime.}` global init)
|
||||
# exactly as for a direct import — see `pendingNifInit`. A throwaway module
|
||||
# symbol (same shape as moduleFromNifFile's) gives the drain an idgen/info
|
||||
# context; it is not registered, so a later direct import still loads fully.
|
||||
if g.config.cmd == cmdM:
|
||||
let m = PSym(kindImpl: skModule, itemId: itemId(int32(fileIdx), 0'i32),
|
||||
name: getIdent(g.cache, splitFile(toFullPath(g.config, fileIdx)).name),
|
||||
infoImpl: newLineInfo(fileIdx, 1, 1), positionImpl: int(fileIdx))
|
||||
setOwner(m, getPackage(g.config, g.cache, fileIdx))
|
||||
g.pendingNifInit.add (m, precomp.topLevel)
|
||||
# Rebuild generic TYPE- and PROC-instance offers across the WHOLE closure,
|
||||
# not just direct imports (`moduleFromNifFile`). An instance is frozen at
|
||||
# the FIRST module to create it (in a scope where its body's symbols
|
||||
@@ -1086,6 +1100,10 @@ when not defined(nimKochBootstrap):
|
||||
# walks the closure in nifbackend.loadModuleDependencies.)
|
||||
if g.config.cmd == cmdM:
|
||||
loadTransitiveHooks(g, result.deps)
|
||||
# Record the directly-loaded module for the same VM-level load effects as its
|
||||
# transitive deps (`pendingNifInit`). AFTER loadTransitiveHooks so the drain
|
||||
# applies deps before the dependent (macro-cache order).
|
||||
g.pendingNifInit.add (m, result.topLevel)
|
||||
|
||||
proc configComplete*(g: ModuleGraph) =
|
||||
#rememberStartupConfig(g.startupPackedConfig, g.config)
|
||||
|
||||
@@ -29,7 +29,7 @@ const
|
||||
|
||||
nimEnableCovariance* = defined(nimEnableCovariance)
|
||||
|
||||
icFormatVersion* = "9"
|
||||
icFormatVersion* = "10"
|
||||
## Version of the IC cache format (the sem-NIF module layout written by
|
||||
## ast2nif.nim plus the iface/impl/edges side files). Bump it whenever
|
||||
## that layout changes: `commandIc` wipes a nimcache whose `ic.version`
|
||||
|
||||
@@ -352,6 +352,31 @@ proc initLoadedCompileTimeGlobals(graph: ModuleGraph; module: PSym; topLevel: PN
|
||||
sect.add s.ast
|
||||
setupCompileTimeVar(module, idgen, graph, sect)
|
||||
|
||||
proc finalizeLoadedModules(graph: ModuleGraph) =
|
||||
## Apply the VM-level load effects of every module just loaded from a NIF —
|
||||
## direct import OR dep-of-a-dep, both collected in `graph.pendingNifInit` by the
|
||||
## loader (modulegraphs.moduleFromNifFile / loadTransitiveHooks). This is the ONE
|
||||
## place that knows what loading a module does to global VM state, so a
|
||||
## transitively-reached module (which never passes through this proc's caller)
|
||||
## gets identical treatment. Modules are in dependency order (deps before
|
||||
## dependents), which is the correct macro-cache replay order.
|
||||
## 1. macro-cache replay: std/macrocache put/inc/add/incl recorded in the
|
||||
## module's top level (pragma replay actions are a backend concern, skipped).
|
||||
## 2. eager `{.compileTime.}` global init (see initLoadedCompileTimeGlobals).
|
||||
## To add a new per-load effect, extend this proc — do not add a parallel buffer.
|
||||
if graph.pendingNifInit.len == 0: return
|
||||
for (m, topLevel) in graph.pendingNifInit:
|
||||
if topLevel == nil: continue
|
||||
var replayList = newNodeI(nkStmtList, m.info)
|
||||
for n in topLevel:
|
||||
if n.kind == nkReplayAction and n.len >= 1 and n[0].kind == nkStrLit and
|
||||
n[0].strVal in ["put", "inc", "add", "incl"]:
|
||||
replayList.add n
|
||||
if replayList.len > 0:
|
||||
replayStateChanges(m, graph, replayList)
|
||||
initLoadedCompileTimeGlobals(graph, m, topLevel)
|
||||
graph.pendingNifInit.setLen 0
|
||||
|
||||
proc compilePipelineModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymFlags; fromModule: PSym = nil): PSym =
|
||||
var flags = flags
|
||||
if fileIdx == graph.config.projectMainIdx2: flags.incl sfMainModule
|
||||
@@ -414,33 +439,12 @@ proc compilePipelineModule*(graph: ModuleGraph; fileIdx: FileIndex; flags: TSymF
|
||||
if sfSystemModule in flags:
|
||||
graph.systemModule = result
|
||||
partialInitModule(result, graph, fileIdx, AbsoluteFile(toFullPath(graph.config, fileIdx)))
|
||||
# Replay the module's recorded state changes: macro-cache operations
|
||||
# (std/macrocache puts/incs/adds/incls) plus a few pragmas. The loader
|
||||
# parsed them into `precomp.topLevel` (mixed with other top-level nodes),
|
||||
# so filter to the replay actions. A loaded module's `ast` is never
|
||||
# rebuilt, so this used to be skipped (`result.ast == nil`) and a
|
||||
# NIF-loaded module's macro cache was lost — e.g. nim-serialization's
|
||||
# flavor registration became invisible to dependents (`DefaultFlavor:
|
||||
# automatic serialization is not enabled`).
|
||||
var replayList = newNodeI(nkStmtList, result.info)
|
||||
for n in precomp.topLevel:
|
||||
# Only macro-cache ops (put/inc/add/incl). The pragma replay actions
|
||||
# (compile/link/passc/hint/...) are a backend/link concern handled by
|
||||
# the nifc closure, and re-emitting a loaded module's hints/warnings on
|
||||
# every import would be wrong — so they are deliberately skipped here.
|
||||
if n.kind == nkReplayAction and n.len >= 1 and n[0].kind == nkStrLit and
|
||||
n[0].strVal in ["put", "inc", "add", "incl"]:
|
||||
replayList.add n
|
||||
# Plus the macro-cache actions of the module's transitive import closure
|
||||
# (collected by the moduleFromNifFile call above via loadTransitiveHooks),
|
||||
# so a flavor/type registered in an indirectly-imported module is visible.
|
||||
for n in graph.transitiveReplayActions: replayList.add n
|
||||
graph.transitiveReplayActions.setLen 0
|
||||
if replayList.len > 0:
|
||||
replayStateChanges(result, graph, replayList)
|
||||
# Fill the VM slots of the module's `{.compileTime.}` globals now (sem
|
||||
# would have, but a NIF-loaded module is never semchecked).
|
||||
initLoadedCompileTimeGlobals(graph, result, precomp.topLevel)
|
||||
# Apply the VM-level load effects of this module AND every dep it pulled in
|
||||
# (moduleFromNifFile recorded them all in graph.pendingNifInit): macro-cache
|
||||
# replay (else a NIF-loaded module's macro cache is lost — e.g.
|
||||
# nim-serialization flavor registration) and eager `{.compileTime.}` global
|
||||
# init. Uniform for direct and transitive deps — see finalizeLoadedModules.
|
||||
finalizeLoadedModules(graph)
|
||||
return result # Return early, don't process from source
|
||||
let path = toFullPath(graph.config, fileIdx)
|
||||
let filename = AbsoluteFile path
|
||||
@@ -537,6 +541,11 @@ proc compilePipelineProject*(graph: ModuleGraph; projectFileIdx = InvalidFileIdx
|
||||
localError(graph.config, unknownLineInfo,
|
||||
"nim m requires precompiled NIF for system module (expected: " & nifPath & ")")
|
||||
return
|
||||
# Apply system's (and its deps') load effects now: the main module is
|
||||
# compiled from source and never re-enters the moduleFromNifFile drain for
|
||||
# system, so without this its macro-cache / CT globals would wait until the
|
||||
# first NIF import is processed. See finalizeLoadedModules.
|
||||
finalizeLoadedModules(graph)
|
||||
discard graph.compilePipelineModule(projectFile, {sfMainModule})
|
||||
else:
|
||||
graph.compilePipelineSystemModule()
|
||||
|
||||
@@ -480,7 +480,11 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
|
||||
else:
|
||||
header = instCopyType(cl, t)
|
||||
|
||||
result = newType(tyGenericInst, cl.c.idgen, t.genericHead.owner, son = header.genericHead)
|
||||
# The instantiating module owns the instance (and announces it as an offer):
|
||||
# the generic body's module (`t.genericHead.owner`) has no business owning a
|
||||
# type that references instantiation-site types — that is the IC parent->child
|
||||
# heap leak the write-barrier surfaces.
|
||||
result = newType(tyGenericInst, cl.c.idgen, cl.c.module, son = header.genericHead)
|
||||
result.flags = header.flags
|
||||
# be careful not to propagate unnecessary flags here (don't use rawAddSon)
|
||||
# ugh need another pass for deeply recursive generic types (e.g. PActor)
|
||||
|
||||
@@ -1386,7 +1386,21 @@ proc transformBody*(g: ModuleGraph; idgen: IdGenerator; prc: PSym; flags: Transf
|
||||
result = getBody(g, prc)
|
||||
else:
|
||||
prc.transformedBody = newNode(nkEmpty) # protects from recursion
|
||||
var c = openTransf(g, prc.getModule, "", idgen, flags)
|
||||
# Lambda-lifting a routine body while the VM compiles it (to run a macro
|
||||
# under `nim ic`) mints a closure `:env` (type + obj + fields + hidden param)
|
||||
# that the lift welds into the routine's serialized signature. Such an env is
|
||||
# a PROCESS-LOCAL artifact (its item number is per-process-sequential), so a
|
||||
# reference to it must never carry a stable cross-module identity — otherwise
|
||||
# a consumer resolves it against a canonical NIF built by a different process
|
||||
# that has no matching def ('symbol has no offset', e.g. Nimbus t17.275).
|
||||
# Lift in the backend (process-local) id space; ast2nif then emits these as
|
||||
# module-local `@bk` defs (mirrors setAttachedOp's inVMTransform handling).
|
||||
var liftIdgen = idgen
|
||||
if g.inVMTransform > 0 and g.config.cmd == cmdM:
|
||||
if g.vmTransfIdgen == nil:
|
||||
g.vmTransfIdgen = idGeneratorForBackend(g.systemModule)
|
||||
liftIdgen = g.vmTransfIdgen
|
||||
var c = openTransf(g, prc.getModule, "", liftIdgen, flags)
|
||||
result = liftLambdas(g, prc, getBody(g, prc), c.tooEarly, c.idgen, flags)
|
||||
result = processTransf(c, result, prc)
|
||||
liftDefer(c, result)
|
||||
|
||||
Reference in New Issue
Block a user