diff --git a/compiler/ast2nif.nim b/compiler/ast2nif.nim index cd0e8ecded..c3129c7798 100644 --- a/compiler/ast2nif.nim +++ b/compiler/ast2nif.nim @@ -198,8 +198,8 @@ type 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 - lowering: bool # serializing the `lower` stage's `.t.nif` (per-entry self-contained) - emittedFieldSyms: HashSet[ItemId] # lowering: derived env-field syms already def'd this entry + lowering: bool # serializing the `lower` stage's whole-module `.t.nif` + emittedFieldSyms: HashSet[ItemId] # lowering: derived env-field syms already def'd inTypeReclist: int # >0 while writing a type's OWN reclist: fields must be SELF-CONTAINED # defs (the type can be seek-loaded in isolation), not entry-deduped uses @@ -718,7 +718,6 @@ var implTag = registerTag("implementation") var reexpModTag = registerTag("reexpmod") var offerTag = registerTag("offer") var typeOfferTag = registerTag("toffer") -var loweredTag = registerTag("lowered") proc registerNifAstTags*() = ## (Re)registers ast2nif's NIF tags explicitly. The top-level `registerTag` @@ -751,7 +750,6 @@ proc registerNifAstTags*() = reexpModTag = registerTag("reexpmod") offerTag = registerTag("offer") typeOfferTag = registerTag("toffer") - loweredTag = registerTag("lowered") proc writeNode(w: var Writer; dest: var TokenBuf; n: PNode; forAst = false) = if n == nil: @@ -1518,71 +1516,6 @@ proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode; writeImplCookie(config, thisModule, dest, ifaceHex) writeEdgesFile(config, thisModule, implDeps) -proc collectLoweredLocals(w: var Writer; n: PNode) = - ## Record every transform-created local (a `Complete`, non-`@bk` sym owned by - ## the module being serialized) in `w.locals` so it is written SUFFIX-LESS as - ## an inline def. Pre-existing entities (params, result, callees) were sealed - ## before serialization, so they are skipped here and emit as index SymUses. - if n == nil: return - if n.kind == nkSym and n.sym != nil: - let s = n.sym - if not s.itemId.isBackendMinted and s.itemId.module == w.currentModule and - s.state == Complete: - w.locals.incl s.itemId - for i in 0 ..< n.safeLen: - collectLoweredLocals(w, n[i]) - -proc serializeLoweredBodies*(config: ConfigRef; ownerModule: int32; - entries: openArray[tuple[name: string; body: PNode]]; - hooks: openArray[LogEntry]; - outfile: string) = - ## Write the `lower` backend stage's `.t.nif`. Three sections, all covered by - ## the file's embedded index (which the `cg` loader registers as the module's - ## SECOND index/stream — see loadLoweredBodies): - ## - `(repdestroy/repcopy/... key sym)`: the type-bound ops the lower stage - ## lifted while transforming (closure-env `=destroy` etc.). `cg`'s - ## `registerLoadedHooks` re-attaches them so `injectDestructorCalls` (kept in - ## cg) resolves them via `getAttachedOp`'s key fallback. - ## - the hook ROUTINES as full `(sd)`: NEW in the lower stage (no `.s.nif` - ## signature), so serialize sig + transformed body whole; cg loads them via - ## the `.t.nif` index when the HookEntry's SymUse resolves, then demand-emits. - ## - owned routines' transformed bodies as `(lowered "" )`: only - ## the body (signature comes from the `.s.nif`); applyLoweredBodies sets it on - ## the existing sym. - ## A `@bk` closure-env type/sym referenced anywhere is emitted inline AND - ## indexed, so it resolves through the embedded index regardless of section. - var w = Writer(infos: LineInfoWriter(config: config), currentModule: ownerModule) - w.inProc = 1 # we are serializing routine *bodies* - w.lowering = true - var dest = createTokenBuf(256) - createStmtList(dest, NoLineInfo) - for op in hooks: - writeOp(w, dest, op) - var emittedHooks = initHashSet[int32]() - for op in hooks: - if op.sym != nil and op.sym.kindImpl in routineKinds and - not emittedHooks.containsOrIncl(op.sym.itemId.item): - w.emittedBackendTypes.clear() - w.emittedBackendSyms.clear() - w.emittedFieldSyms.clear() - w.locals.clear() - writeSymDef(w, dest, op.sym) - for e in entries: - # Each `(lowered ...)` entry is loaded independently, so it must be - # SELF-CONTAINED: reset the per-Writer backend-local (@bk) dedup so every - # entry re-emits the def of any closure-env type/sym it references. - w.emittedBackendTypes.clear() - w.emittedBackendSyms.clear() - w.emittedFieldSyms.clear() - w.locals.clear() - collectLoweredLocals(w, e.body) - dest.addParLe loweredTag, NoLineInfo - dest.addStrLit e.name - writeNode(w, dest, e.body) - dest.addParRi - dest.addParRi() - writeFile(dest, outfile) - # --------------------------- Loader (lazy!) ----------------------------------------------- proc nodeKind(n: Cursor): TNodeKind {.inline.} = @@ -1644,15 +1577,6 @@ type suffix: string contentStart: int # stream offset of the module body, so a full-AST load can # rewind after lazy symbol loads moved the cursor - # The module's `.t.nif` (the `lower` stage's transformed bodies): a SECOND - # embedded-index + stream consulted when the main `.s.nif` index misses. The - # transformed bodies' backend-minted (`@bk`) entities (closure-env types/syms, - # temporaries) live ONLY here; they are named with this module's suffix, so - # `createTypeStub`/`loadSymStub` look them up here on a `.s.nif` miss and load - # them from `tStream` (see applyLoweredBodies). "Just use NIF's embedded index." - hasTIndex: bool - tStream: nifstreams.Stream - tIndex: Table[string, NifIndexEntry] DecodeContext* = object infos: LineInfoWriter @@ -1688,8 +1612,8 @@ proc loadedState(c: DecodeContext): ItemState {.inline.} = if c.infos.config.cmd == cmdNifC: Complete else: Sealed proc cursorFromIndexEntry(c: var DecodeContext; module: FileIndex; entry: NifIndexEntry; - buf: var TokenBuf; fromT = false): Cursor = - let s = if fromT: addr c.mods[module].tStream else: addr c.mods[module].stream + buf: var TokenBuf): Cursor = + let s = addr c.mods[module].stream s.r.jumpTo entry.offset # A seek-load is self-contained: its tokens must decode their relative line # info against `entry.info` ALONE. The stream's `parents` stack can be left at @@ -1847,13 +1771,7 @@ proc createTypeStub(c: var DecodeContext; t: SymId): PType = let ii = addr c.mods[modFi].index var offs = ii[].getOrDefault(name) if offs.offset == 0: - # A backend-minted (`@bk`) type produced by the `lower` stage lives in this - # module's `.t.nif`, not its `.s.nif` index. Resolve it through the `.t.nif` - # embedded index (loadType picks `tStream` for a tIndex-only name). - if c.mods[modFi].hasTIndex: - offs = c.mods[modFi].tIndex.getOrDefault(name) - if offs.offset == 0: - raiseAssert "symbol has no offset: " & name + raiseAssert "symbol has no offset: " & name result = PType(itemId: id, uniqueId: id, kind: TTypeKind(k), state: Partial) c.types[name] = (result, offs) @@ -1949,15 +1867,6 @@ proc loadSymStub(c: var DecodeContext; t: SymId; thisModule: string; let id = if isBk: backendItemId(module.int32, val[]) else: itemId(module.int32, val[]) var offs = c.mods[module].index.getOrDefault(symAsStr) - if offs.offset == 0 and c.mods[module].hasTIndex: - # A sym produced by the `lower` stage lives in this module's `.t.nif`, not - # its `.s.nif` index. This covers BOTH backend-minted (`@bk`) entities - # (closure `:env` param, `:tmp`/`res` temporaries) AND module-homed derived - # closure-env FIELDS (e.g. a captured `i`): the latter have a normal - # module suffix but are added to the env object only in the backend, so - # they are indexed solely in `.t.nif`. Resolve through that embedded index - # (loadSym picks `tStream` for a tIndex-only name). - offs = c.mods[module].tIndex.getOrDefault(symAsStr) if offs.offset == 0: # Only module/package self-syms are never written as `(sd)` entries, so a # missing index offset means this is such a sym — typically the OWNER of an @@ -2093,11 +2002,7 @@ proc loadType*(c: var DecodeContext; t: PType) = else: typeToNifSym(t, c.infos.config) let modFi = t.itemId.module.FileIndex - # A name resolved through the `.t.nif` (tIndex) — a `lower`-stage closure-env - # type — must seek in `tStream`, not the `.s.nif` stream. - let fromT = c.mods[modFi].hasTIndex and not c.mods[modFi].index.hasKey(typeName) and - c.mods[modFi].tIndex.hasKey(typeName) - var n = cursorFromIndexEntry(c, modFi, c.types[typeName][1], buf, fromT = fromT) + var n = cursorFromIndexEntry(c, modFi, c.types[typeName][1], buf) var localSyms = initTable[string, PSym]() loadTypeFromCursor(c, n, t, localSyms) @@ -2199,10 +2104,7 @@ proc loadSym*(c: var DecodeContext; s: PSym) = var buf = createTokenBuf(30) let symsModule = s.itemId.module.FileIndex let nifname = globalName(s, c.infos.config) - # A `@bk` sym resolved through the `.t.nif` (tIndex) seeks in `tStream`. - let fromT = c.mods[symsModule].hasTIndex and not c.mods[symsModule].index.hasKey(nifname) and - c.mods[symsModule].tIndex.hasKey(nifname) - var n = cursorFromIndexEntry(c, symsModule, c.syms[nifname][1], buf, fromT = fromT) + var n = cursorFromIndexEntry(c, symsModule, c.syms[nifname][1], buf) expect n, ParLe if n.tagId != sdefTag: @@ -2220,110 +2122,22 @@ proc loadSym*(c: var DecodeContext; s: PSym) = inc n loadSymFromCursor(c, s, n, c.mods[symsModule].suffix, localSyms) -proc sealLoadedBackendEntities*(c: var DecodeContext) = - ## Before the `lower` stage serializes its transformed bodies, mark every - ## index-loaded sym/type `Sealed`. The backend loads them `Complete` (mutable - ## for the transform); without this, `writeNode`'s `shouldWriteSymDef` would - ## emit a duplicate `(sd)`/`(td)` def for a param/result/existing-local/owner - ## type — and the `cg` body-loader would then bind the body's `result` to a - ## FRESH sym instead of the one in `prc.ast[resultPos]` (the #6/#7 "result - ## cannot be captured" class). Sealed ⟹ SymUse ⟹ resolved via `cg`'s module - ## index. The transform-CREATED entities are absent from `c.syms`/`c.types` - ## (or are backend-minted), so they stay `Complete`/`@bk` and still get the - ## inline defs the loader's local-sym pre-scan needs. - for _, v in c.syms: - if v[0] != nil and v[0].state == Complete: v[0].state = Sealed - for _, v in c.types: - if v[0] != nil and v[0].state == Complete: v[0].state = Sealed - proc sealLoadedRoutines*(c: var DecodeContext) = - ## Whole-module-lowering variant of `sealLoadedBackendEntities`: seal ONLY the + ## Before `writeLoweredModule` re-serializes the lowered module, seal ONLY the ## module's ROUTINE syms. A `.t.nif` written by `writeLoweredModule` is the ## SOLE source the `cg` stage loads (there is no `.s.nif` fallback for its - ## bodies), so unlike the per-`(lowered)`-entry path, every type, global, param - ## and local must still emit a REAL def in it — only cross-routine references - ## may be `SymUse`s (each routine's def is emitted once, at module scope, by - ## the explicit stub loop). Types/globals stay `Complete` so `writeType`/ - ## `writeGlobals` emit them; routines become `Sealed` so a body referencing - ## another routine writes a `SymUse` resolved through the module index. + ## bodies), so every type, global, param and local must still emit a REAL def + ## in it — only cross-routine references may be `SymUse`s (each routine's def is + ## emitted once, at module scope, by the explicit stub loop). Types/globals stay + ## `Complete` so `writeType`/`writeGlobals` emit them; routines become `Sealed` + ## so a body referencing another routine writes a `SymUse` resolved through the + ## module index. for _, v in c.syms: if v[0] != nil and v[0].state == Complete and v[0].kindImpl in routineKinds: v[0].state = Sealed proc resolveHookSym*(c: var DecodeContext; symId: nifstreams.SymId): PSym -proc repTagToOp(tagId: TagId): (bool, TTypeAttachedOp) = - ## Map a `(rep…)` hook tag to its attached-op kind (and whether it IS one). - if tagId == repDestroyTag: (true, attachedDestructor) - elif tagId == repCopyTag: (true, attachedAsgn) - elif tagId == repWasMovedTag: (true, attachedWasMoved) - elif tagId == repDupTag: (true, attachedDup) - elif tagId == repSinkTag: (true, attachedSink) - elif tagId == repTraceTag: (true, attachedTrace) - elif tagId == repDeepCopyTag: (true, attachedDeepCopy) - else: (false, attachedDestructor) - -proc loadLoweredBodies*(c: var DecodeContext; module: FileIndex; suffix: string; - infile: string; loadBodies = true): - tuple[bodies: seq[tuple[name: string; body: PNode]]; hooks: seq[LogEntry]] = - ## Reconstruct the `lower` stage's `.t.nif`. Registers its embedded index as the - ## module's SECOND index/stream (`tIndex`/`tStream`) so every backend-minted - ## (`@bk`) closure-env entity resolves through NIF's own index — `createTypeStub`/ - ## `loadSymStub` fall through to it on a `.s.nif` miss, `loadType`/`loadSym` seek - ## `tStream`. Returns the owned routines' transformed bodies (the `(lowered …)` - ## entries) and the lifted type-bound ops (`(rep… key sym)`); the hook ROUTINES' - ## `(sd)` defs are loaded lazily through the index when their op's sym resolves. - result = (@[], @[]) - if not fileExists(infile): return - var tstream = nifstreams.open(infile) - let tindex = readEmbeddedIndex(tstream) # leaves the cursor at the content start - let contentStart = offset(tstream.r) - c.mods[module].tStream = tstream - c.mods[module].tIndex = tindex - c.mods[module].hasTIndex = true - # Parse the WHOLE content up front: the per-body loads below lazily seek - # `tStream` for `@bk` entities, which would otherwise clobber a live walk cursor. - var buf = createTokenBuf(256) - tstream.r.jumpTo contentStart - nifcursors.parse(tstream, buf, NoLineInfo) - var n = beginRead(buf) - if n.kind != ParLe: return - inc n # into (stmts -> flags dot - inc n # -> type dot - inc n # -> first entry or ParRi - while n.kind == ParLe: - if n.tagId == loweredTag: - if not loadBodies: - # A dependency: register its `.t.nif` (tIndex + hooks) so the consumer - # can destroy the dep's closures, but don't reconstruct its bodies (only - # the dep's OWN cg emits them). - skip n - continue - inc n # -> StringLit name - let name = pool.strings[n.litId] - inc n # -> body tree - var localSyms = initTable[string, PSym]() - var scanCursor = n - extractLocalSymsFromTree(c, scanCursor, suffix, localSyms) - let body = loadNode(c, n, suffix, localSyms) - result.bodies.add (name, body) - skipParRi n # close (lowered ...) - else: - let (isHook, op) = repTagToOp(n.tagId) - if isHook: - inc n # -> StringLit key - let key = pool.strings[n.litId] - inc n # -> Symbol sym - let sym = resolveHookSym(c, n.symId) - inc n - if sym != nil: - result.hooks.add LogEntry(kind: HookEntry, op: op, module: module.int, - key: key, sym: sym) - skipParRi n - else: - skip n # a hook routine's `(sd)` def — loaded lazily via the index - - template withNode(c: var DecodeContext; n: var Cursor; result: PNode; kind: TNodeKind; body: untyped) = let info = c.infos.oldLineInfo(n.info) inc n @@ -2420,8 +2234,7 @@ proc loadNode(c: var DecodeContext; n: var Cursor; thisModule: string; # dereferences a nil field type. Detect the unindexed case and FILL # the sym from the inline def instead. let m = moduleId(c, sn.module) - let indexed = c.mods[m].index.hasKey(symName) or - (c.mods[m].hasTIndex and c.mods[m].tIndex.hasKey(symName)) + let indexed = c.mods[m].index.hasKey(symName) if indexed: sym = c.loadSymStub(name.symId, thisModule, localSyms) skip n # skip the entire sdef for indexed symbols @@ -2598,9 +2411,9 @@ proc moduleSymbolStubs*(c: var DecodeContext; module: FileIndex): seq[PSym] = proc toNifFilename*(conf: ConfigRef; f: FileIndex): string = let suffix = moduleSuffix(conf, f) # The `cg`/`emit` backend stages load the lowered whole-module NIF (transformed - # bodies + lifted sigs baked in) when `-d:icWholeLowered` is on; the `lower` - # stage and the frontend (`cmdM`) still read the semchecked `.s.nif`. - if isDefined(conf, "icWholeLowered") and conf.cmd == cmdNifC and + # bodies + lifted sigs baked in); the `lower` stage and the frontend (`cmdM`) + # read the semchecked `.s.nif`. + if conf.cmd == cmdNifC and (conf.icBackendStage == "cg" or conf.icBackendStage == "emit"): let t = toGeneratedFile(conf, AbsoluteFile(suffix), ".t.nif").string if fileExists(t): @@ -2627,11 +2440,6 @@ proc resolveSym(c: var DecodeContext; symAsStr: string; alsoConsiderPrivate: boo # Try the format without module suffix let localKey = sn.name & "." & $sn.count & "." offs = c.mods[module].index.getOrDefault(localKey) - if offs.offset == 0 and c.mods[module].hasTIndex: - # A `lower`-stage entity (an `@bk` hook routine, OR a module-homed derived - # closure-env field added only in the backend) lives in the module's - # `.t.nif`, not its `.s.nif`: resolve it through the second (tIndex) index. - offs = c.mods[module].tIndex.getOrDefault(symAsStr) if offs.offset == 0: return nil if not alsoConsiderPrivate and offs.vis == Hidden: diff --git a/compiler/cnif.nim b/compiler/cnif.nim index ad51f47f8c..0caebd8682 100644 --- a/compiler/cnif.nim +++ b/compiler/cnif.nim @@ -358,52 +358,6 @@ proc readCnifHeads*(f: string): CnifHeads = endRead(c) result.valid = sawMeta and version == CnifVersion -proc writeLoweredArtifact*(outfile: string; entries: openArray[string]) = - ## The `.t.nif` "lowered" artifact: one `(lowered "" )` per - ## routine the module OWNS, written by the per-module `lower` backend stage - ## for the `cg` stage to read instead of re-deriving the transformed body - ## (re-derivation in each parallel `cg` process is what makes a closure - ## `:env`'s identity diverge across modules — see transf.transformBody). - ## - ## SKELETON: every body is the empty-marker `.` ("transformed body == sem - ## body"), so `cg` falls back to its own `transformBody` and output stays - ## byte-identical. The real transformed body fills this slot in a later step; - ## the `.` then means "unchanged by lowering" (the dedup Araq sketched). - var b = nifbuilder.open(outfile) - b.withTree "stmts": - for name in entries: - b.withTree "lowered": - b.addStrLit name - b.addEmpty() - b.close() - -proc readLoweredArtifact*(f: string): seq[string] = - ## The routine NIF names recorded in a `.t.nif`. (Bodies are not returned - ## yet: the skeleton records only empty-markers; reading proves the artifact - ## round-trips and that the `cg` rule depends on it.) - result = @[] - if not fileExists(f): return - var pool = newPool() - var tags = newTagPool() - let stmtsTag = tags.registerTag("stmts") - let loweredTag = tags.registerTag("lowered") - var buf = parseFromFile(f, 1000, pool, tags) - var c = beginRead(buf) - if c.kind != TagLit or c.cursorTagId != stmtsTag: - endRead(c) - return - c.loopInto: - if c.kind == TagLit and c.cursorTagId == loweredTag: - c.loopInto: - if c.kind == StrLit: - result.add strVal(c) - inc c - else: - skip c - else: - skip c - endRead(c) - type CnifLiveness* = object defs*: int ## proc definitions emitted across all modules diff --git a/compiler/deps.nim b/compiler/deps.nim index d8310a257c..da32f1fb21 100644 --- a/compiler/deps.nim +++ b/compiler/deps.nim @@ -942,17 +942,13 @@ proc generateBackendBuildFile(c: DepContext; forwardedArgs: seq[string]): string var cnifFiles = newSeq[string](c.nodes.len) var cFiles = newSeq[string](c.nodes.len) var tFiles = newSeq[string](c.nodes.len) - # Whole-module lowering writes a PROPER module NIF the cg/emit stages load via + # The `lower` stage writes a PROPER module NIF the cg/emit stages load via # `toNifFilename` (a `.s.nif` sibling), so its `.t.nif` lives at the suffix base - # (mirroring `semmedFile`), not next to the throwaway `.c`. The side-car path - # keeps the `.c`-relative name (loaded directly by `registerLoweredModule`). - let wholeMode = isDefined(c.config, "icWholeLowered") + # (mirroring `semmedFile`), not next to the throwaway `.c`. for i, node in c.nodes: cFiles[i] = backendCFile(c, node) cnifFiles[i] = cFiles[i] & ".nif" - tFiles[i] = - if wholeMode: nimcache / node.files[0].modname & ".t.nif" - else: cFiles[i] & ".t.nif" + tFiles[i] = nimcache / node.files[0].modname & ".t.nif" var b = nifbuilder.open(result) defer: b.close() @@ -1019,13 +1015,9 @@ proc generateBackendBuildFile(c: DepContext; forwardedArgs: seq[string]): string inputStr mainNif for n2 in c.nodes: inputStr c.semmedFile(n2.files[0]) - # Whole-module mode loads dependencies FROM their `.t.nif` (toNifFilename), so - # every module's lowered NIF must precede this cg rule; the side-car only needs - # this module's own `.t.nif`. - if wholeMode: - for j in 0 ..< c.nodes.len: inputStr tFiles[j] - else: - inputStr tFiles[i] + # cg loads dependencies FROM their `.t.nif` (toNifFilename), so every module's + # lowered NIF must precede this cg rule. + for j in 0 ..< c.nodes.len: inputStr tFiles[j] if node.id == 0: for j in 0 ..< c.nodes.len: if c.nodes[j].id != 0: @@ -1053,10 +1045,9 @@ proc generateBackendBuildFile(c: DepContext; forwardedArgs: seq[string]): string inputStr mainNif inputStr cnifFiles[i] inputStr mergeFile - # emit loads modules like cg (for getCFile/type resolution); under whole mode - # those come from the `.t.nif`s, so they must precede this rule. - if wholeMode: - for j in 0 ..< c.nodes.len: inputStr tFiles[j] + # emit loads modules like cg (for getCFile/type resolution); those come from + # the `.t.nif`s, so they must precede this rule. + for j in 0 ..< c.nodes.len: inputStr tFiles[j] outputStr cFiles[i] b.endTree() diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim index 7f5b00ee8b..a145458a90 100644 --- a/compiler/injectdestructors.nim +++ b/compiler/injectdestructors.nim @@ -245,7 +245,7 @@ proc genOp(c: var Con; t: PType; kind: TTypeAttachedOp; dest, ri: PNode): PNode let canon = c.graph.canonTypes.getOrDefault(h) if canon != nil: op = getAttachedOp(c.graph, canon, kind) - if (op == nil or op.ast.isGenericRoutine) and icLoweredBodies(c.graph.config): + if op == nil or op.ast.isGenericRoutine: # IC: injectDestructorCalls is demand-driven and runs HERE (cg), not in the # `lower` stage, so a structural, env-agnostic op the lower stage never had # reason to serialize — most often a closure PROC type's `=destroy`/`=sink` diff --git a/compiler/nifbackend.nim b/compiler/nifbackend.nim index 7431f9cad4..b08e3492e6 100644 --- a/compiler/nifbackend.nim +++ b/compiler/nifbackend.nim @@ -27,7 +27,6 @@ import ast, options, lineinfos, modulegraphs, cgendata, cgen, cnif from cgmeth import generateIfMethodDispatchers from transf import transformBody -from lambdalifting import getEnvParam, addHiddenParam, paramName import ic / replayer proc loadModuleDependencies(g: ModuleGraph; mainFileIdx: FileIndex; @@ -324,110 +323,17 @@ proc findTargetModule(g: ModuleGraph; modules: seq[PrecompiledModule]; cachedModuleSuffix(g.config, FileIndex precompSys.module.position) == suffix: return precompSys -proc findHiddenEnvParam(n: PNode; owner: PSym): PSym = - ## Locate the hidden env param (`:envP`) that belongs to `owner` in its loaded - ## transformed body, so it can be re-welded into `owner`'s signature. Matching - ## by `owner` is essential: a body can ALSO reference a callee's `:envP` (a - ## closure call passes the callee env), so the first `:envP` in DFS order is - ## not necessarily this proc's own. - if n == nil: return nil - if n.kind == nkSym: - if n.sym != nil and n.sym.kind == skParam and n.sym.name.s == paramName and - n.sym.owner == owner: - return n.sym - else: - for i in 0 ..< n.safeLen: - let r = findHiddenEnvParam(n[i], owner) - if r != nil: return r - return nil - -proc registerLoweredModule(g: ModuleGraph; m: PSym; applyBodies: bool) = - ## Load module `m`'s `.t.nif` and register its `lower`-stage output: its - ## embedded index (so its closure-env `@bk` entities resolve) and its lifted - ## type-bound ops (so any cg that DESTROYS one of `m`'s closures finds the env - ## `=destroy` via getAttachedOp). For the cg TARGET (`applyBodies`), also set - ## each owned routine's `transformedBody` so cg's `transformBody` short-circuits - ## (transf.nim:1383) instead of re-deriving. `injectDestructorCalls` stays in cg. - let modPos = m.position - let bmod = BModuleList(g.backend).mods[modPos] - if bmod == nil: return - let artifact = getCFile(bmod).string & ".t.nif" - if not fileExists(artifact): return - let suffix = cachedModuleSuffix(g.config, FileIndex modPos) - let (bodies, hooks) = loadLoweredBodies(ast.program, FileIndex modPos, suffix, - artifact, loadBodies = applyBodies) - registerLoadedHooks(g, hooks) - if not applyBodies: return - var byName = initTable[string, PSym]() - for s in moduleSymbolStubs(ast.program, FileIndex modPos): - # Owned routines AND nested closure routines (the `:anonymous` procs the - # lower stage emits as their own entries) — both are index-resolvable syms - # of this module whose transformed body the lower stage authored. - if s.kind in routineKinds and s.itemId.module == modPos: - byName[globalName(s, g.config)] = s - for (name, body) in bodies: - let s = byName.getOrDefault(name) - # `.s.nif` wins: only fill from `.t.nif` if sem did not already transform it. - if s != nil and body != nil and s.transformedBody == nil: - s.transformedBody = body - # Lambda-lift in the lower stage gave this proc a hidden `:envP` env param - # (a captured-var closure env), but cg loaded the PRE-lift signature from - # `.s.nif`. The transformed body references that `@bk` `:envP`; re-weld it - # into the proc's params so genProc assigns it a loc (else "param not - # init"). `transformBody` short-circuits on the cached body, skipping the - # lift that normally adds it. This applies to BOTH a true `ccClosure` proc - # (env arrives via the closure ABI `ClE_0`, needs `tfCapturesEnv`) and a - # plain nested `nimcall` proc that merely captures (env is a regular last - # param). Match the env param by owner — a body can also reference a - # callee's `:envP`. - if s.typ != nil and getEnvParam(s) == nil: - let ep = findHiddenEnvParam(body, s) - if ep != nil: - # From-source, `ast[paramsPos]` and `typ.n` are the SAME node, but a - # NIF-loaded routine has two distinct param nodes. genProc reads - # `typ.n`, so unify them first — else addHiddenParam appends to - # `ast[paramsPos]` and the env param never reaches genProc's loc setup. - if s.typ.n != nil: - s.ast[paramsPos] = s.typ.n - addHiddenParam(s, ep) - # The lower stage's lambda-lift converts EVERY captured nested proc to a - # closure (collectNestedClosureBodies only emits `ccClosure` entries), - # and the serialized call sites use the closure ABI. cg loaded the - # pre-lift signature, which for a proc only ever CALLED (never used as a - # value) is still `nimcall`. Re-apply the lift's `ccClosure` + - # `tfCapturesEnv` so closureSetup maps the env param to `ClE_0` and the - # calls match. - s.typ.callConv = ccClosure - incl(s.typ, {tfCapturesEnv}) - -proc applyLoweredBodies(g: ModuleGraph; modules: seq[PrecompiledModule]; - precompSys: PrecompiledModule; target: PrecompiledModule) = - ## Register every loaded module's `.t.nif` (env entities + lifted hooks), - ## applying transformed bodies only for the cg target. - if not icLoweredBodies(g.config): return # Stage 0 (lazy): nothing to apply - # Whole-module mode: the cg stage already loaded each module FROM its `.t.nif` - # (toNifFilename), so transformed bodies arrive via loadSymFromCursor and lifted - # hooks via moduleFromNifFile's registerLoadedHooks — no side-car to apply. - if isDefined(g.config, "icWholeLowered"): return - if precompSys.module != nil: - registerLoweredModule(g, precompSys.module, applyBodies = false) - for m in modules: - if m.module != nil: - registerLoweredModule(g, m.module, - applyBodies = (m.module.position == target.module.position)) - -proc collectNestedClosureBodies(g: ModuleGraph; idgen: IdGenerator; n: PNode; - owner: PSym; seen: var IntSet; - entries: var seq[tuple[name: string; body: PNode]]) = +proc setNestedClosureBodies(g: ModuleGraph; idgen: IdGenerator; n: PNode; + owner: PSym; seen: var IntSet) = ## A closure routine nested in `owner` (the `:anonymous` proc lambda-lifting ## minted, plus any deeper nesting) gets its captured-var→env rewrite produced - ## as part of the OWNER's `transformBody`, but only the owner's body is emitted - ## as a `(lowered)` entry. The nested proc itself IS index-resolvable (it has a - ## `.s.nif` sdef from sem, with its PRE-lift body), so cg loads that and - ## re-derives — and the capture mapping is gone (it accesses `x` directly - ## instead of `ClE_0->x0`). Walk the transformed body and emit each nested - ## closure routine's transformed body as its OWN `(lowered)` entry so - ## applyLoweredBodies installs it and cg reuses it verbatim. + ## as part of the OWNER's `transformBody`. The nested proc is a module-indexed + ## sym whose `.s.nif` sdef carries its PRE-lift body, so without help the whole + ## module re-serializer would write that pre-lift body and cg would lose the + ## capture mapping (it accesses `x` directly instead of `ClE_0->x0`). Walk the + ## owner's transformed body and cache each nested closure's transformed body on + ## its sym so `writeSymDef` serializes the lifted body into the routine's + ## 2-way-body slot. if n == nil: return if n.kind == nkSym: let s = n.sym @@ -437,11 +343,10 @@ proc collectNestedClosureBodies(g: ModuleGraph; idgen: IdGenerator; n: PNode; s.typ != nil and s.typ.callConv == ccClosure: if s.transformedBody == nil: s.transformedBody = transformBody(g, idgen, s, {}) - entries.add (globalName(s, g.config), s.transformedBody) - collectNestedClosureBodies(g, idgen, s.transformedBody, s, seen, entries) + setNestedClosureBodies(g, idgen, s.transformedBody, s, seen) else: for i in 0 ..< n.safeLen: - collectNestedClosureBodies(g, idgen, n[i], owner, seen, entries) + setNestedClosureBodies(g, idgen, n[i], owner, seen) proc reownFromTwin(n: PNode; twin, s: PSym) = ## Re-own to `s` every entity the frontend attributed to `s`'s forward-decl @@ -499,102 +404,72 @@ proc generateLowerStage(g: ModuleGraph; mainFileIdx: FileIndex) = rawMessage(g.config, errGenerated, "per-module lowering: no backend module for suffix: " & g.config.icBackendModule) return - let artifact = getCFile(tb).string & ".t.nif" - if icLoweredBodies(g.config): - # STAGE 1 (DEFAULT; `-d:icNoLowerBodies` opts out): transform every owned routine - # ONCE in this single process's id space and serialize the results, so `cg` - # reads them instead of re-deriving (the single-writer-per-owner that keeps - # closure-`:env` identity stable). `transformBody` with flags {} mirrors the - # cg call (cgen.nim:1409); we keep only its return value (it clears - # `transformedBody` for non-cached procs). `injectDestructorCalls` is NOT run - # — it stays in `cg` on the loaded body. - # Whole-module mode (`-d:icWholeLowered`): re-serialize the ENTIRE module as a - # proper indexed NIF (`writeLoweredModule`) with transformed bodies baked into - # the routine sdefs, instead of the per-routine `(lowered)` side-car. cg loads - # it through the normal module loader, so nested procs (incl. async state - # machines) arrive as real defs with their lifted bodies — no re-weld, no - # `(lowered)` entries. `entries` stays unused; we only need `transformedBody` - # set on each routine sym so `writeSymDef` serializes it. - let wholeMode = isDefined(g.config, "icWholeLowered") - var entries: seq[tuple[name: string; body: PNode]] = @[] - # `transformBody`/lambda-lifting LIFTS the closure env's type-bound ops - # (`=destroy` etc.) into `g.opsLog`; snapshot its length so we can serialize - # exactly the ops THIS stage created (not those loaded from `.s.nif`). - let opsLogStart = g.opsLog.len - for s in moduleSymbolStubs(ast.program, FileIndex modPos): - if ownsRuntimeRoutine(s, modPos): - # `.s.nif` wins: a routine already transformed during sem (CT eval / - # macro / VM transform) carries its lowered body in the `.s.nif` slot — - # don't re-transform it here, just leave its `.t.nif` entry empty. - if s.transformedBody != nil: continue - # A routine serialized as a forward-decl + impl pair (the writeSymDef - # "separate forward declaration and implementation" design) loads as TWO - # syms; the impl `s` we transform here can carry body entities (`result`, - # locals, nested routines) owned by its fwd-decl TWIN, not by `s`. - # lambda-lifting compares owners by reference → `illegalCapture` rejects a - # twin-owned `result` and the lifting pass can't find twin-owned locals in - # `s`'s env. Pervasive on chronos `{.async.}` methods. Re-own them to `s`, - # matching the single-sym non-IC case. Backend-only (the lowered body is a - # `.t.nif` artifact), so frontend effect/exception inference is untouched. - if s.ast != nil and s.ast.len > resultPos and - s.ast[resultPos].kind == nkSym and s.ast[resultPos].sym.owner != s: - reownFromTwin(s.ast, s.ast[resultPos].sym.owner, s) - let tbody = transformBody(g, tb.idgen, s, {}) - if wholeMode: - # Retain the transformed body on the sym so `writeSymDef` serializes it - # in the routine's `(sd)` 2-way-body slot. - s.transformedBody = tbody - else: - entries.add (globalName(s, g.config), tbody) - # Set `transformedBody` on nested ccClosure routines too (so a - # module-indexed nested closure gets its lifted body); in side-car mode - # this also appends their `(lowered)` entries. - var seenNested = initIntSet() - collectNestedClosureBodies(g, tb.idgen, tbody, s, seenNested, entries) - # Collect the hooks this stage lifted, and transform each hook ROUTINE's body - # too (it is itself lowered into NIFC). The hooks' `(sd)` + transformed body go - # into the `.t.nif`; `cg` re-attaches them so `injectDestructorCalls` resolves - # the loaded env's `=destroy`. Iterate to a fixpoint: a hook body can lift - # further hooks (a field's `=destroy`). - var hooks: seq[LogEntry] = @[] - var i = opsLogStart - while i < g.opsLog.len: - let e = g.opsLog[i] - if e.kind == HookEntry and e.sym != nil and e.sym.kind in routineKinds and - e.sym.transformedBody == nil: - hooks.add e - # Transform the hook routine's body and cache it on the sym so - # `writeSymDef` serializes it in the hook's `(sd)` transformed-body slot - # (`transformBody {}` returns the body but does not cache it). - e.sym.transformedBody = transformBody(g, tb.idgen, e.sym, {}) - inc i - if wholeMode: - # Re-serialize the whole module to its suffix-based `.t.nif` (the path - # `toNifFilename` resolves for the cg/emit stages). `writeLoweredModule` - # seals routines itself. - let suffix = cachedModuleSuffix(g.config, FileIndex modPos) - let wholeArtifact = toGeneratedFile(g.config, AbsoluteFile(suffix), ".t.nif").string - writeLoweredModule(ast.program, g.config, target, hooks, wholeArtifact) - if isDefined(g.config, "icDceCheck"): - stderr.writeLine "[icLowerWhole] " & extractFilename(wholeArtifact) & " " & - $hooks.len & " hooks" - else: - # Seal the index-loaded entities so their references in the bodies serialize - # as SymUses (resolved via the module index in cg), not duplicate defs. - sealLoadedBackendEntities(ast.program) - serializeLoweredBodies(g.config, modPos.int32, entries, hooks, artifact) - if isDefined(g.config, "icDceCheck"): - stderr.writeLine "[icLower] " & extractFilename(artifact) & " " & - $entries.len & " routines transformed, " & $hooks.len & " hooks" - else: - # DEFAULT (Stage 0, byte-neutral): record one empty-marker per owned routine. - # `cg` derives the transformed body itself, so output is unchanged; this only - # exercises the artifact + scheduling the transform-move builds on. - var names: seq[string] = @[] - for s in moduleSymbolStubs(ast.program, FileIndex modPos): - if ownsRuntimeRoutine(s, modPos): - names.add globalName(s, g.config) - writeLoweredArtifact(artifact, names) + # Transform every owned routine ONCE in this single process's id space and + # re-serialize the ENTIRE module as a proper indexed NIF (`writeLoweredModule`) + # with the transformed bodies baked into the routine `(sd)` entries. `cg` loads + # it through the normal module loader, so nested procs (incl. async state + # machines) arrive as real defs with their lifted bodies — no re-derivation. + # This single-writer-per-owner is what keeps closure-`:env` identity stable + # across the parallel `cg` processes (re-derivation per process was the root of + # the `:env` identity drift). `transformBody` with flags {} mirrors the cg call + # (cgen.nim); `injectDestructorCalls` is NOT run here — it stays in `cg` on the + # loaded body. + # + # `transformBody`/lambda-lifting LIFTS the closure env's type-bound ops + # (`=destroy` etc.) into `g.opsLog`; snapshot its length so we serialize exactly + # the ops THIS stage created (not those loaded from `.s.nif`). + let opsLogStart = g.opsLog.len + for s in moduleSymbolStubs(ast.program, FileIndex modPos): + if ownsRuntimeRoutine(s, modPos): + # `.s.nif` wins: a routine already transformed during sem (CT eval / macro / + # VM transform) carries its lowered body in the `.s.nif` slot — don't + # re-transform it here. + if s.transformedBody != nil: continue + # A routine serialized as a forward-decl + impl pair (writeSymDef's + # "separate forward declaration and implementation") loads as TWO syms; the + # impl `s` we transform here can carry body entities (`result`, locals, + # nested routines) owned by its fwd-decl TWIN, not by `s`. lambda-lifting + # compares owners by reference → `illegalCapture` rejects a twin-owned + # `result` and the lifting pass can't find twin-owned locals in `s`'s env. + # Pervasive on chronos `{.async.}` methods. Re-own them to `s`, matching the + # single-sym non-IC case. Backend-only, so frontend effect/exception + # inference is untouched. + if s.ast != nil and s.ast.len > resultPos and + s.ast[resultPos].kind == nkSym and s.ast[resultPos].sym.owner != s: + reownFromTwin(s.ast, s.ast[resultPos].sym.owner, s) + # Retain the transformed body on the sym so `writeSymDef` serializes it in + # the routine's `(sd)` 2-way-body slot. + s.transformedBody = transformBody(g, tb.idgen, s, {}) + # Cache the lifted body on nested ccClosure routines too, so a module-indexed + # nested closure serializes its lifted (capture-rewritten) body. + var seenNested = initIntSet() + setNestedClosureBodies(g, tb.idgen, s.transformedBody, s, seenNested) + # Collect the hooks this stage lifted, and transform each hook ROUTINE's body + # too (it is itself lowered into NIFC). The hooks' `(sd)` + transformed body go + # into the `.t.nif`; `cg` re-attaches them so `injectDestructorCalls` resolves + # the loaded env's `=destroy`. Iterate to a fixpoint: a hook body can lift + # further hooks (a field's `=destroy`). + var hooks: seq[LogEntry] = @[] + var i = opsLogStart + while i < g.opsLog.len: + let e = g.opsLog[i] + if e.kind == HookEntry and e.sym != nil and e.sym.kind in routineKinds and + e.sym.transformedBody == nil: + hooks.add e + # Transform the hook routine's body and cache it on the sym so `writeSymDef` + # serializes it in the hook's `(sd)` transformed-body slot (`transformBody + # {}` returns the body but does not cache it). + e.sym.transformedBody = transformBody(g, tb.idgen, e.sym, {}) + inc i + # Re-serialize the whole module to its suffix-based `.t.nif` (the path + # `toNifFilename` resolves for the cg/emit stages). `writeLoweredModule` seals + # routines itself. + let suffix = cachedModuleSuffix(g.config, FileIndex modPos) + let wholeArtifact = toGeneratedFile(g.config, AbsoluteFile(suffix), ".t.nif").string + writeLoweredModule(ast.program, g.config, target, hooks, wholeArtifact) + if isDefined(g.config, "icDceCheck"): + stderr.writeLine "[icLower] " & extractFilename(wholeArtifact) & " " & + $hooks.len & " hooks" proc generateCgStage(g: ModuleGraph; mainFileIdx: FileIndex) = ## Per-module backend codegen (`--icBackendStage:cg --icBackendModule:`): @@ -638,7 +513,10 @@ proc generateCgStage(g: ModuleGraph; mainFileIdx: FileIndex) = "per-module codegen: module not found for suffix: " & g.config.icBackendModule) return - applyLoweredBodies(g, modules, precompSys, target) + # The `lower` stage already wrote each module's transformed bodies + lifted + # hooks into its `.t.nif`, which the loaders above read directly (toNifFilename + # resolves the `.t.nif`); transformed bodies arrive via loadSymFromCursor and + # lifted hooks via moduleFromNifFile's registerLoadedHooks. Nothing to apply. generateCodeForModule(g, target) let bl = BModuleList(g.backend) # The main module also owns the whole-program method dispatchers + NimMain. @@ -780,8 +658,8 @@ proc generateLinkStage(g: ModuleGraph; mainFileIdx: FileIndex) = # `strutils.escape`) — so its body must be at link or that reference is # undefined. Link every emitted `.c` the merge decision says OWNS a LIVE symbol; # a node that owns nothing live (a Windows-only winsock node on Linux) is - # correctly skipped. Whole-module only: the side-car default is unchanged. - if isDefined(g.config, "icWholeLowered"): + # correctly skipped. + block: let nimcache = getNimcacheDir(g.config).string let decision = readMergeDecision(nimcache / MergeDecisionFile) if not decision.broken: diff --git a/compiler/options.nim b/compiler/options.nim index 2c930cca50..deb3752d23 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -29,7 +29,7 @@ const nimEnableCovariance* = defined(nimEnableCovariance) - icFormatVersion* = "20" + icFormatVersion* = "21" ## 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` @@ -786,13 +786,6 @@ template quitOrRaise*(conf: ConfigRef, msg = "") = else: quit(msg) # quits with QuitFailure -proc icLoweredBodies*(conf: ConfigRef): bool {.inline.} = - ## Whether the `nim ic` backend uses the EAGER per-module `lower` stage - ## (transformBody serialized to `.t.nif`, cg reuses it) instead of the lazy - ## Stage-0 path (cg re-derives every transformed body). This is now the - ## DEFAULT; `-d:icNoLowerBodies` opts back into the lazy path for A/B testing. - not isDefined(conf, "icNoLowerBodies") - proc importantComments*(conf: ConfigRef): bool {.inline.} = conf.cmd in cmdDocLike + {cmdIdeTools} proc usesWriteBarrier*(conf: ConfigRef): bool {.inline.} = conf.selectedGC >= gcRefc proc usesSso*(conf: ConfigRef): bool {.inline.} = conf.selectedStrings == stringSso