From 8e5ba13fe3c827f2ae265104115f4c049b4ccc7b Mon Sep 17 00:00:00 2001 From: Araq Date: Sat, 20 Jun 2026 09:53:35 +0200 Subject: [PATCH] better type keys --- compiler/ast2nif.nim | 54 ------------------------------------------- compiler/options.nim | 2 +- compiler/typekeys.nim | 27 ++++++++++++++++++++++ 3 files changed, 28 insertions(+), 55 deletions(-) diff --git a/compiler/ast2nif.nim b/compiler/ast2nif.nim index e95d9b9fbc..2fa0ab9e4d 100644 --- a/compiler/ast2nif.nim +++ b/compiler/ast2nif.nim @@ -2236,60 +2236,6 @@ proc sealLoadedBackendEntities*(c: var DecodeContext) = for _, v in c.types: if v[0] != nil and v[0].state == Complete: v[0].state = Sealed -proc preloadLoweredDefs(c: var DecodeContext; n: var Cursor; thisModule: string; - localSyms: var Table[string, PSym]) = - ## One pre-scan over a `.t.nif` body that fully loads EVERY inline def it - ## carries before the body is decoded: suffix-less locals into `localSyms`, - ## backend-minted `@bk` syms into `c.syms`, `@bk` types into `c.types`. These - ## transform-created entities have NO module index entry, so a later reference - ## — in the body, or inside a loaded type that `typeKey`/codegen later walks - ## (where the index-based force-load would assert) — must find them already - ## loaded. The writer emits defs before uses, so a forward walk that loads each - ## in place suffices. - if n.kind != ParLe: - inc n - return - var depth = 0 - while true: - if n.kind == ParLe: - if n.tagId == sdefTag: - let nm = n.firstSon - if nm.kind == SymbolDef: - let symName = pool.syms[nm.symId] - let sn = parseSymName(symName) - if sn.module.len == 0: - if symName notin localSyms: - let module = moduleId(c, thisModule) - let val = addr c.mods[module].symCounter - inc val[] - let sym = PSym(itemId: itemId(module.int32, val[]), kindImpl: skStub, - name: c.cache.getIdent(sn.name), disamb: sn.count.int32, - state: Complete) - localSyms[symName] = sym - inc n - loadSymFromCursor(c, sym, n, thisModule, localSyms) - sym.state = c.loadedState - continue - elif sn.module.endsWith(BackendLocalMarker): - let sym = c.loadSymStub(nm.symId, thisModule, localSyms) - if sym.state == Partial: - sym.state = c.loadedState - inc n - loadSymFromCursor(c, sym, n, thisModule, localSyms) - continue - elif n.tagId == tdefTag: - let nm = n.firstSon - if nm.kind == SymbolDef and pool.syms[nm.symId].endsWith(BackendLocalMarker): - discard loadTypeStub(c, n, localSyms) - continue - inc depth - elif n.kind == ParRi: - dec depth - if depth == 0: - inc n - break - inc n - proc resolveHookSym*(c: var DecodeContext; symId: nifstreams.SymId): PSym proc repTagToOp(tagId: TagId): (bool, TTypeAttachedOp) = diff --git a/compiler/options.nim b/compiler/options.nim index 3d62b006b3..9f11d58ef8 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -29,7 +29,7 @@ const nimEnableCovariance* = defined(nimEnableCovariance) - icFormatVersion* = "16" + icFormatVersion* = "17" ## 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` diff --git a/compiler/typekeys.nim b/compiler/typekeys.nim index 435b927e3a..d5e0b52e09 100644 --- a/compiler/typekeys.nim +++ b/compiler/typekeys.nim @@ -139,6 +139,25 @@ proc maybeImported(c: var Context; s: PSym; conf: ConfigRef) {.inline.} = if s != nil and {sfImportc, sfExportc} * s.flagsImpl != {}: c.symKey(s, conf) +proc backendTypeName(t: PType; conf: ConfigRef): string = + ## Stable cross-module identity of a backend-minted (lower-stage) type: its + ## serialized `@bk` NIF name (mirrors ast2nif.nifTypeName). A closure-env + ## object/ref minted by the `lower` stage has NO stable STRUCTURAL key — its + ## captured-field types re-resolve to different modules in the producing vs the + ## consuming process (e.g. field `x0` → `int` in the producer, → the consumer's + ## alias in the consumer) — but this name (kind + item + home-module suffix) is + ## identical in both, because the consumer loads the producer's name verbatim. + ## Keying hooks by it makes producer `setAttachedOp` and consumer `getAttachedOp` + ## agree. The trailing `@bk` (= ast2nif.BackendLocalMarker) keeps it disjoint + ## from any normal type's structural key. + result = "`t" + result.addInt ord(t.kind) + result.add '.' + result.addInt t.uniqueId.item + result.add '.' + result.add modname(t.uniqueId.module, conf) + result.add "@bk" + proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef) = if t == nil: c.m.addEmpty() @@ -148,6 +167,14 @@ proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef assert c.tl != nil c.tl(t) + if t.uniqueId.isBackendMinted: + # Backend-minted (lower-stage) closure-env types key by their stable NIF name, + # never by structure (which diverges across the NIF boundary). An env `ref` + # that is itself NOT backend-minted still keys stably: it recurses here and + # reaches its `@bk` object, which short-circuits to a stable name. + c.m.addSymbol backendTypeName(t, conf) + return + case t.kind of tyGenericInvocation: for a in t.sonsImpl: