diff --git a/compiler/ast.nim b/compiler/ast.nim index a04fc0fdc6..8f48dacef8 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -509,7 +509,8 @@ proc getPIdent*(a: PNode): PIdent {.inline.} = of nkOpenSymChoice, nkClosedSymChoice, nkOpenSym: a.sons[0].sym.name else: nil -template id*(a: PType | PSym): int = toId(a.itemId) +template id*(a: PSym): int = toId(a.itemId) +template id*(a: PType): int = toId(a.bindingId) type IdGenerator* = ref object # unfortunately, we really need the 'shared mutable' aspect here. @@ -1097,7 +1098,7 @@ proc newType*(kind: TTypeKind; idgen: IdGenerator; owner: PSym; son: sink PType let id = nextTypeId idgen result = PType(kind: kind, ownerFieldImpl: owner, sizeImpl: defaultSize, alignImpl: defaultAlignment, itemId: id, - uniqueId: id, sonsImpl: @[]) + bindingId: id, sonsImpl: @[]) if son != nil: assert kind != tyProc result.sonsImpl.add son @@ -1173,18 +1174,23 @@ proc copyType*(t: PType, idgen: IdGenerator, owner: PSym): PType = result.symImpl = t.sym # backend-info should not be copied proc exactReplica*(t: PType; idgen: IdGenerator): PType = - ## Replica that KEEPS `itemId` — the generic-param binding tables - ## (`LayeredIdTable`) key on it, so the copy must keep matching its - ## original — but mints a FRESH `uniqueId`: uniqueId is the SERIALIZATION - ## identity (NIF type names key on it) and must be unique per instance. - ## Replicas sharing the original's uniqueId serialized as duplicate defs - ## under one NIF name; the loader collapsed them into a single type, + ## Copy that INHERITS `bindingId` — the generic-param binding tables + ## (`LayeredIdTable`) key on it, so the copy must keep matching its original + ## there — while getting its own `itemId`, like every other type. The two + ## remaining callers are `semtypinst.instCopyType` (a partially instantiated + ## meta type must still bind in the next instantiation round) and the + ## `tfUnresolved` typedesc replica in `semtypes.semTypeIdent`; everything + ## else that used to come through here is a plain `copyType`. + ## + ## Do not "simplify" this to share `itemId` as well: `itemId` is the + ## serialization identity, and replicas sharing it serialized as duplicate + ## defs under one NIF name, which the loader collapsed into a single type — ## losing their flag differences (use-site `tfUnresolved` typedescs) or ## their structure (meta instance bodies shadowing a generic's canonical ## body). result = PType(kind: t.kind, ownerFieldImpl: t.owner, sizeImpl: defaultSize, - alignImpl: defaultAlignment, itemId: t.itemId, - uniqueId: nextTypeId(idgen)) + alignImpl: defaultAlignment, itemId: nextTypeId(idgen), + bindingId: t.bindingId) assignType(result, t) result.symImpl = t.sym # backend-info should not be copied diff --git a/compiler/ast2nif.nim b/compiler/ast2nif.nim index 6f4a0450a6..135a14a20b 100644 --- a/compiler/ast2nif.nim +++ b/compiler/ast2nif.nim @@ -46,31 +46,31 @@ const BackendLocalMarker* = "@bk" ## 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 - ## its `uniqueId.module` is unresolvable. Such a type has no fields and an + ## its `itemId.module` is unresolvable. Such a type has no fields and an ## identity that is fully captured by its kind, so we serialize it with this ## sentinel and reconstruct it on load (see `createTypeStub`) without ever ## touching a `.nif` file. A real `moduleSuffix` never starts with '@'. proc typeToNifSym(typ: PType; config: ConfigRef): string = - # NOTE: uniqueId is the serialization identity and is unique per instance — - # `exactReplica` keeps only itemId shared with its original (see ast.nim) - assert not typ.uniqueId.isBackendMinted + # NOTE: `itemId` is THE identity of a type and is unique per instance, so a + # NIF type name is too. (A replica shares only `bindingId`, see ast.nim.) + assert not typ.itemId.isBackendMinted result = "`t" result.addInt ord(typ.kind) result.add '.' - result.addInt typ.uniqueId.item + result.addInt typ.itemId.item result.add '.' - if typ.uniqueId.module < 0: + if typ.itemId.module < 0: result.add SysModuleSuffix else: - result.add modname(typ.uniqueId.module, config) + result.add modname(typ.itemId.module, config) proc icNifTypeName*(typ: PType; config: ConfigRef): string = ## The serialized NIF name of a type, recorded next to RTTI data ## definitions in the cnif artifact so a later run can re-demand the ## typeinfo when a reused TU still references it (the def-retention ## check). Backend-minted types have no NIF name. - if typ != nil and not typ.uniqueId.isBackendMinted: + if typ != nil and not typ.itemId.isBackendMinted: result = typeToNifSym(typ, config) else: result = "" @@ -208,11 +208,13 @@ const hiddenTypeTagName = "ht" symDefTagName = "sd" typeDefTagName = "td" + bindingIdTagName = "bid" var sdefTag = registerTag(symDefTagName) tdefTag = registerTag(typeDefTagName) hiddenTypeTag = registerTag(hiddenTypeTagName) + bindingIdTag = registerTag(bindingIdTagName) type Writer = object @@ -236,6 +238,7 @@ type 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 + emittedCanonTypes: Table[string, int32] # canonical type name -> itemId.item of the def proc isLocalSym(sym: PSym): bool {.inline.} = @@ -312,7 +315,7 @@ proc toNifSymName(w: var Writer; sym: PSym): string = # first for both, so one proc's `:env` gets the OTHER proc's env type # (mismatched-pointer C, "has no member colonup_" at link). `itemId.item` is # unique per `@bk` sym (both are emitted as defs, see writeSym), mirroring - # how `@bk` TYPES already key off `uniqueId.item` (nifTypeName). The loader + # how `@bk` TYPES already key off `itemId.item` (nifTypeName). The loader # copies this back into `disamb` (sn.count), so `globalName` round-trips. result = sym.name.s result.add '.' @@ -481,15 +484,378 @@ proc writeLoc(w: var Writer; dest: var IcBuilder; loc: TLoc) = writeFlags(dest, loc.flags) # TLocFlags dest.addStrLit loc.snippet -proc nifTypeName(w: Writer; typ: PType): string = +const + CanonTypeKinds = {tyVar, tyLent, tySink, tyTuple, tyRef, tyPtr, tySequence, + tyOpenArray, tyVarargs, tySet, tyUncheckedArray, tyArray, + tyRange, tyProc} + ## Anonymous types whose NIF name is derived from what they ARE -- their + ## content, or for a routine's signature the routine -- rather than from + ## `itemId.item`, the module-wide type-mint counter. + ## + ## The counter is assigned in sem order, so creating ONE extra type renumbers + ## every type minted after it. Types declared in a `type` section are minted + ## before any routine body, so they are stable; but the `var T` / `lent T` / + ## tuple wrappers sem mints for routine signatures are not, and those are + ## exactly what an importer references by name. Inserting a private proc at + ## the top of `ast.nim` shifted 1362 of its 1800 type names by +1, which + ## rewrote the `.s.bif` of 81 modules that had not changed at all. + ## + ## Restricted to anonymous wrappers on purpose. A nominal type must NOT be + ## content-addressed: `exactReplica` deliberately mints a fresh `itemId` + ## for a structurally identical copy so the two stay distinguishable, and + ## collapsing them loses the flag or body difference they were split over. + ## + ## `tyProc` is in the set, but it never takes the content key: a proc type + ## that is some routine's SIGNATURE is named after that routine (see + ## `sigRoutineOf`), and any other proc type keeps its counter. Content-keying + ## a signature is what "everything `writeTypeDef` serializes" gets wrong, + ## because a signature's identity is not in its content at all -- it is in + ## the PARAM SYMBOLS the routine's body refers to. + ## Merging two signatures leaves the survivor's params in the loser's + ## `typ.n`, its body then references params the C backend never declared, and + ## codegen dies with "expr: param not init". Measured on a hello-world under + ## `nim ic`: 238 proc-type merges, every single one a lifted `=sink` hook + ## (`(dest: var T, src: T)`, two hooks minted for the same T, identical in + ## everything `writeTypeDef` writes) -- so no amount of extra content in the + ## key would ever have separated them. + ## + ## Effect on `msgs.nim`, the module the wrapper pass could not help: an + ## insert at the top moved 259 of its 530 type names, 153 of them proc types. + ## All 153 now hold still and 106 names move. What is left is other kinds + ## still on the counter -- `tyInt`, `tyTypeDesc`, `tyDistinct` -- plus the + ## content-named wrappers that cascade off them. + +const + CanonLitCopyKinds = {tyInt, tyFloat} + ## Kinds that sem COPIES per module out of `system`, keeping the ORIGINAL's + ## `sym`: the int literal types (`semdata.getIntLitType`, + ## `semfold.getIntLitTypeG`) and the plain copies `magicsys.skipIntLit` + ## makes of them when a literal type reaches a parameter. `tyFloat` is here + ## because `skipIntLit` accepts it, not because anything mints one today -- + ## every one of the copies measured below is a `tyInt`. `getIntLitType` caches only the small values, so everything + ## else mints a fresh type per occurrence: `nilcheck.nim` alone writes 132 + ## and the whole compiler writes 20107 -- 91% of every per-module copy in + ## the build, and each one holds a mint-counter name that an insert + ## anywhere above it shifts. + ## + ## They are the last mover that actually BREAKS a build rather than just + ## churning bytes. Inserting a proc at the top of `nilcheck.nim` renamed one + ## of them and `pipelines.t.bif` -- cached, not re-sem'd, because the iface + ## cookie is order-insensitive since `fab55cff6` -- still pointed at the old + ## name: `symbol has no offset: t31.4199.nilrwrcn11`. That reproduces on + ## `2bed712f6` and not on `901ca7905`. + ## + ## Merging two of these is safe in a way merging a nominal type is not: the + ## key carries the flags, the size and the literal in `n` -- everything + ## `isIntLit` and `sameType` look at -- plus the `sym`, which is what keeps + ## a copy of plain `int` apart from a copy of an int-shaped alias like + ## posix's `Off`. Merging is in fact what `getIntLitType`'s own cache + ## already does for the values it covers. + ## + ## The test is deliberately a MODULE comparison and not `sym.typ != typ`; + ## the comment on `isCanonType` records what that cost. It also means a copy + ## minted while compiling `system` itself is not covered -- sym and type + ## agree on the module there -- which is fine: nothing above `system` can + ## shift its counter. + ## + ## The OTHER copies are deliberately not here. 1375 are `tyObject` -- a + ## generic instance's body -- and 184 `tySequence`, both nominal: their + ## identity is the declaration, not the content, and collapsing two of them + ## loses exactly what `exactReplica` exists to keep apart. + +proc hasDerivedSize(typ: PType): bool {.inline.} = + ## True for a type whose `size`/`align`/`paddingAtEnd` are a pure function of + ## the structure that is serialized with it, so a consumer can recompute them + ## and no measurement needs to cross the NIF boundary. That is every ANONYMOUS + ## structural wrapper: `{.size.}`/`{.align.}` are pragmas on a type + ## DECLARATION, so a type without a `sym` cannot carry one, and the remaining + ## kinds (an object's field offsets, an enum's declared size) are excluded. + typ.kind in CanonTypeKinds and typ.symImpl == nil + +const CanonIdBias = 0x4000_0000'i32 + ## Canonical ids live above every mint counter so a content hash can never + ## collide with the `itemId.item` of a same-kind type that kept its counter + ## (a replica, or a wrapper whose son is backend-minted). + +proc canonHash(s: string): int32 = + ## FNV-1a, hand-rolled on purpose: this value goes into on-disk NIF names, so it + ## must not change when the host `std/hashes` does -- a shifted hash would + ## renumber every cached module the way the mint counter used to. + var h = 0x811C9DC5'u32 + for ch in s: + h = h xor uint32(ord(ch)) + h = h * 0x01000193'u32 + result = int32(h and 0x3FFF_FFFF'u32) or CanonIdBias + +var canonTypeIds: Table[ItemId, int32] + ## Memo for `canonicalTypeItem`, deliberately PROCESS-global rather than + ## per-`Writer`. A type's mutable fields (its flag set, chiefly) can still be + ## growing while an `--icGroup` cycle writes one member's NIF after another's, + ## and the two writers must not disagree about its name. Pinning the id at its + ## first computation makes the process self-consistent; across processes the + ## question does not arise, since a consumer LOADS the id out of the name. + +var canonClaims: Table[ItemId, string] + ## `(module, canonical id) -> the key that minted it`, so a hash COLLISION + ## cannot silently merge two unrelated types. `canonHash` has 30 usable bits; + ## across a module's ~2000 types a birthday collision is unlikely but not + ## negligible, and until now it would have been a miscompile rather than a + ## wasted slot. A second, DIFFERENT key landing on a taken id falls back to + ## `itemId.item`, which is safe for exactly the reason the re-entrancy guard + ## below is: the mint counter is unique within the module, so nothing else can + ## be wearing that name. + +var canonSigOwners: Table[string, ItemId] + ## `signature key -> the one PType allowed to wear it`. A signature name is an + ## IDENTITY, not a digest: two `PType`s that agree on it are NOT + ## interchangeable, so unlike a content key it must never be shared. A routine + ## has one type at a time, yet `prc.typ` is REPLACED in places (a forward + ## declaration adopting its prototype, `instantiateProcType` overwriting the + ## signature it copied), and the previous occupant can still be reachable and + ## still get written. The first claimant keeps the name; a later one keeps its + ## counter. + +proc canonicalTypeItem(w: var Writer; typ: PType): int32 +proc nifTypeName(w: var Writer; typ: PType): string +proc addNodeKey(w: var Writer; key: var string; n: PNode) + +proc claimCanonId(typ: PType; key: string; h: int32): int32 = + ## Hand out `h` unless another key already holds it in this module. + let slot = itemId(typ.itemId.module, h) + canonClaims.withValue(slot, prev): + return (if prev[] == key: h else: typ.itemId.item) + do: + canonClaims[slot] = key + return h + +proc sigRoutineOf(typ: PType): PSym = + ## The routine whose signature `typ` is, or nil for a proc type that is merely + ## a value's type (`var cb: proc (x: int)`). + ## + ## Every routine owns its own signature: sem does it through `getCurrOwner`, + ## and so do the synthesizers -- generic instantiation + ## (`seminst.instantiateProcType`), the lifted type-bound hooks, `$` for enums + ## and the backend's rtti/globals procs. The `o.typ == typ` confirmation is + ## what separates a routine's own signature from an anonymous proc type minted + ## inside its body (same owner, different type). + result = nil + let o = typ.ownerFieldImpl + if o != nil and o.kindImpl in routineKinds and o.typImpl == typ: + result = o + +proc isCanonType(w: Writer; typ: PType): bool = + ## True only when the id must be OVERRIDDEN, i.e. for a wrapper minted in THIS + ## process. Note what is deliberately absent: any test against + ## `w.currentModule`. An `--icGroup` cycle compiles several modules from source + ## in one process and writes a NIF for each, so while writing member A a type + ## owned by member B is minted, not loaded -- keying on `currentModule` would + ## have A reference `B`'s type by its mint counter while B's own NIF def'd it + ## under the content id, leaving a dangling `symbol has no offset`. + ## + ## The three other cases need no override and are excluded here, each landing + ## on `typeToNifSym`, which reproduces the owner's name byte for byte: + ## * loaded and content-named -> `itemId.item` already IS the content id + ## (>= CanonIdBias), which is exactly what `typeToNifSym` prints; + ## * loaded `exactReplica` -> `bindingId != itemId` marks it a replica, + ## which is never content-named (see CanonTypeKinds), so it keeps its counter; + ## * a `Partial` stub -> its id is already final; for a wrapper the + ## `sonsImpl` test below rejects it, and a literal copy is decided purely + ## from the two module ids its NIF name already carries. + ## + ## Depends on nothing that changes during a write -- in particular not on + ## `typ.state`, which flips to `Sealed` the moment the def is emitted -- so a + ## type's def site and every reference to it agree. + if typ.itemId.isBackendMinted or typ.itemId.item >= CanonIdBias or + typ.bindingId != typ.itemId: # a replica is never content-named + result = false + elif typ.kind in CanonTypeKinds: + # An anonymous wrapper. A `sym` means the type was DECLARED and is nominal. + result = typ.symImpl == nil and typ.sonsImpl.len > 0 + elif typ.kind in CanonLitCopyKinds: + # A per-module literal copy: it wears `system.int`'s `sym` but was minted + # into ANOTHER module's id space, so the sym and the type disagree about + # which module they belong to. A declaration never does -- `type Off = int` + # in posix owns both halves -- and neither does `system.int` itself. + # + # The obvious test, `typ.sym.typ != typ` ("the sym's own type is the + # original, so this is a copy"), is wrong ACROSS THE NIF BOUNDARY and cost a + # cold build: a loaded `Off` sym is a stub whose `typImpl` is still nil, so + # posix named its `Off` by the counter while `os` read the same type as a + # copy and referenced a content id posix never wrote -- `symbol has no + # offset: t31.2136968888.pos7l6hwt`. Both halves of this test come off the + # NIF name, so a consumer and the owner always agree. + result = typ.symImpl != nil and + typ.symImpl.itemId.module != typ.itemId.module + else: + result = false + +proc addNodeKey(w: var Writer; key: var string; n: PNode) = + ## Structural digest of a type's `n` node, for the kinds whose identity lives + ## there: a `tyRange`'s bounds and a `tyProc`'s formal params. A symbol + ## contributes its bare NAME -- never its NIF name, whose `disamb` is itself a + ## mint counter and would defeat the whole point -- plus the NIF name of its + ## TYPE. The type is not optional: a `tyProc`'s parameters live only here, not + ## in `sonsImpl`, so hashing names alone collapsed a generic `==[Enum]` onto + ## its own `FileInfoKind` instance (both have parameters `x`, `y` returning + ## `bool`) and `n.kind == nkSym` stopped compiling. + if n == nil: + key.add '~' + return + key.add '(' + key.addInt ord(n.kind) + case n.kind + of nkCharLit..nkUInt64Lit: + key.add ' ' + key.addInt n.intVal + of nkFloatLit..nkFloat128Lit: + key.add ' ' + key.add $cast[uint64](n.floatVal) + of nkStrLit..nkTripleStrLit: + key.add ' ' + key.add n.strVal + of nkSym: + key.add ' ' + if n.sym != nil: + key.add n.sym.name.s + key.add ':' + if n.sym.typImpl != nil: key.add nifTypeName(w, n.sym.typImpl) + of nkIdent: + key.add ' ' + if n.ident != nil: key.add n.ident.s + else: + for i in 0 ..< n.len: + addNodeKey(w, key, n[i]) + key.add ')' + +proc canonicalTypeItem(w: var Writer; typ: PType): int32 = + ## Stable id for a type that would otherwise wear the mint counter: its + ## CONTENT for a wrapper or a literal copy, and for a proc type that is a + ## routine's signature, the routine that owns it. + ## + ## SOUNDNESS RULE: the key must cover everything `writeTypeDef` serializes + ## except the id itself, so that two types sharing a name would have been + ## written identically anyway. Skimping on that is not a missed optimisation, + ## it is a miscompile: a key over `eqTypeFlags` alone merged two proc types + ## differing only in `tfUnresolved`, and `sizeof(uint64)` stopped resolving. + ## The owner is in the key too, which keeps distinct-but-identical wrappers in + ## different routines apart -- order-independence is the goal here, merging is + ## not, and merging is where every bug in this scheme has come from. + ## + ## Son NIF NAMES are the right currency for the recursive part: they are what + ## actually lands in the file, they are already stable for anything declared in + ## a `type` section, and recursion bottoms out on nominal types, which keep + ## their counter names. + canonTypeIds.withValue(typ.itemId, cached): + return cached[] + # Re-entrancy guard: a son that leads back here sees the mint counter, and this + # type still gets a deterministic (if less stable) id. + canonTypeIds[typ.itemId] = typ.itemId.item + + # A routine's signature is named after the ROUTINE, not after its content. + # Content cannot work here (see CanonTypeKinds): two `=sink` hooks for one type + # agree in every serialized byte yet own different param symbols, and the + # merged loser's body loses its parameters. The routine's own NIF name is both + # unique -- a routine has one signature -- and stable, since `disamb` counts + # per identifier rather than per module, which is the whole point. It is also + # stable across a signature CHANGE: adding a parameter or an effect no longer + # renames the type, so importers keep their references and only the iface + # cookie (which reads the signature itself) notices. + let sigRoutine = if typ.kind == tyProc: sigRoutineOf(typ) else: nil + if sigRoutine != nil: + var sigKey = "sig|" + sigKey.add modname(typ.itemId.module, w.infos.config) + sigKey.add '|' + sigKey.add toNifSymName(w, sigRoutine) + var taken = false + canonSigOwners.withValue(sigKey, holder): + taken = holder[] != typ.itemId + do: + canonSigOwners[sigKey] = typ.itemId + if not taken: + result = claimCanonId(typ, sigKey, canonHash(sigKey)) + canonTypeIds[typ.itemId] = result + return result + # Someone else is already this routine's signature; keep the mint counter. + return typ.itemId.item + elif typ.kind == tyProc: + # An anonymous proc type -- a parameter's or a variable's `proc (x: int)`. + # It keeps the mint counter, and `nifTypeName` then prints exactly what + # `typeToNifSym` would. Content-keying it looked harmless and is not: the + # `raises`/`tags` effects that separate two otherwise identical proc types + # live as `nkType` nodes under `n[0]`'s `nkEffectList`, and `addNodeKey` + # hashes a node's kind and children but never its TYPE, so every effect set + # digests the same. Enabling it collapsed two `proc () {.closure.}` params in + # `seqs_v2.yrcMutatorLock` and `tests/ic/tmeta_async` stopped compiling with + # "type mismatch: got but expected 'proc + # (){.closure, gcsafe.}' .raise effects differ". Teaching `addNodeKey` about + # node types would fix that particular merge, but there is nothing to win: + # the churn this whole scheme exists to remove is in the SIGNATURES an + # importer references, and those are handled above. + return typ.itemId.item + + var key = newStringOfCap(96) + key.addInt ord(typ.kind) + key.add '|' + for f in typ.flagsImpl: + key.addInt ord(f) + key.add ',' + key.add '|' + key.addInt ord(typ.callConvImpl) + key.add '|' + # size/align/paddingAtEnd are deliberately ABSENT. They are filled in lazily, + # so hashing them would make a type's NAME depend on whether anyone had asked + # for its `sizeof` yet -- and they are not serialized for these kinds either + # (see `hasDerivedSize` in writeTypeDef), so there is nothing to distinguish: + # everything they are computed FROM is in this key already. + if typ.typeInstImpl != nil: key.add nifTypeName(w, typ.typeInstImpl) + # The `sym` is load-bearing for a literal copy and nil for every wrapper, so + # adding it leaves the wrapper keys byte-identical. It has to be here: a copy + # of plain `int` and a copy of an int-shaped alias such as posix's `Off` agree + # on kind, flags and size, and the sym is all that tells them apart. + if typ.symImpl != nil: + key.add '$' + key.add toNifSymName(w, typ.symImpl) + if typ.ownerFieldImpl != nil: + key.add '<' + key.add toNifSymName(w, typ.ownerFieldImpl) + for son in typ.sonsImpl: + key.add '#' + if son == nil: key.add '.' + else: key.add nifTypeName(w, son) + # `n` carries a tuple's field names, a range's bounds and a proc's formal + # params -- all of them serialized, so all of them part of the key. + addNodeKey(w, key, typ.nImpl) + result = claimCanonId(typ, key, canonHash(key)) + when defined(icLitDbg): + if typ.kind in CanonLitCopyKinds: + stderr.writeLine "[litkey] cur=" & modname(w.currentModule, w.infos.config) & + " idmod=" & modname(typ.itemId.module, w.infos.config) & + " id=" & $typ.itemId.item & " state=" & $typ.state & + " id=" & $result & " key=" & key + canonTypeIds[typ.itemId] = result + +proc nifTypeName(w: var 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: + ## BackendLocalMarker); an anonymous wrapper this module OWNS is content-named + ## (see CanonTypeKinds); everything else uses `typeToNifSym`. + ## + ## Only owned types are content-named, and that is enough: an importer holds + ## the type as a stub built BY `tryCreateTypeStub` FROM this name, so its + ## `itemId.item` already carries the content id and `typeToNifSym` reproduces + ## the name without recomputing anything. + if isCanonType(w, typ): result = "`t" result.addInt ord(typ.kind) result.add '.' - result.addInt typ.uniqueId.item + result.addInt canonicalTypeItem(w, typ) + result.add '.' + result.add modname(typ.itemId.module, w.infos.config) + elif typ.itemId.isBackendMinted: + result = "`t" + result.addInt ord(typ.kind) + result.add '.' + result.addInt typ.itemId.item result.add '.' result.add modname(w.currentModule, w.infos.config) result.add BackendLocalMarker @@ -504,19 +870,48 @@ proc writeTypeDef(w: var Writer; dest: var IcBuilder; typ: PType) = #dest.addIdent toNifTag(typ.kind) writeFlags(dest, typ.flagsImpl) dest.addIdent toNifTag(typ.callConvImpl) - dest.addIntLit typ.sizeImpl - dest.addIntLit typ.alignImpl - dest.addIntLit typ.paddingAtEndImpl - dest.addIntLit typ.itemId.item # nonUniqueId - # `exactReplica` keeps the canonical type's itemId (binding-table key) - # while minting a fresh uniqueId (the NIF name): when the two halves - # name different modules, the loader cannot reconstruct itemId.module - # from the type's name — serialize it explicitly - if typ.itemId.module != typ.uniqueId.module and - not typ.itemId.isBackendMinted: - dest.addStrLit modname(typ.itemId.module, w.infos.config) + if hasDerivedSize(typ): + # Do not export a MEASUREMENT. `size`/`align`/`paddingAtEnd` are filled in + # lazily by `computeSizeAlign`, so writing what this process happened to + # have measured makes a module's bytes depend on WHEN some other module + # asked for a `sizeof` -- churn for the interface cookie, and outright + # non-determinism for a content-named type, whose def two writers may + # reach in either order (that is what once cost `koch bootic` its fixed + # point). For these kinds the values are derived from the structure that + # is serialized anyway, so hand the consumer the unmeasured sentinel and + # let it compute them exactly like it would for a from-source type. + dest.addIntLit defaultSize + dest.addIntLit defaultAlignment + dest.addIntLit 0 else: + dest.addIntLit typ.sizeImpl + dest.addIntLit typ.alignImpl + dest.addIntLit typ.paddingAtEndImpl + # `bindingId`, the generic binding-table key (see astdef.TType). It equals + # `itemId` for everything except an `exactReplica`, and the loader rebuilds + # `itemId` from the NIF name -- so for a content-named type this must be the + # SAME id the name carries (`bindingId == itemId` is what made it eligible, + # and writing the mint counter here would keep the byte churn the content + # scheme exists to remove). + # `bindingId` (see astdef.TType): the generic binding-table key. Only an + # `exactReplica` has one that differs from its own `itemId`, and `itemId` is + # exactly what the loader rebuilds from the type's NIF name -- so everything + # else would only repeat what the name already says. Emit the node solely + # when it carries information (18 of 11894 type defs in a `nim ic` build of + # tests/ic/timp), TAGGED rather than positional: the interface cookie has to + # drop this field (it is a module-wide mint counter, so hashing it made the + # cookie depend on declaration ORDER -- one line added to ast.nim cost 98 + # re-sems), and a tag lets `hashRegion` skip it by name instead of counting + # tokens into this tree and silently mis-skipping when the layout changes. + if typ.bindingId == typ.itemId: dest.addDotToken + else: + dest.buildTree bindingIdTag: + dest.addIntLit typ.bindingId.item + # a replica of a FOREIGN type: the module half is not in the name either + if typ.bindingId.module != typ.itemId.module and + not typ.bindingId.isBackendMinted: + dest.addStrLit modname(typ.bindingId.module, w.infos.config) writeType(w, dest, typ.typeInstImpl) #if typ.kind in {tyProc, tyIterator} and typ.nImpl != nil and typ.nImpl.kind != nkFormalParams: @@ -553,25 +948,43 @@ proc writeTypeDef(w: var Writer; dest: var IcBuilder; typ: PType) = proc writeType(w: var Writer; dest: var IcBuilder; typ: PType) = if typ == nil: dest.addDotToken() - elif typ.uniqueId.isBackendMinted: + elif typ.itemId.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((ord(typ.kind).int32, typ.uniqueId.item)): + if not w.emittedBackendTypes.containsOrIncl((ord(typ.kind).int32, typ.itemId.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 - # `uniqueId`, so the module that *created* the type (uniqueId.module) must be - # the one that emits its definition. `itemId.module` can be reassigned and - # diverge from `uniqueId.module`; gating on it filed the def in the wrong - # module (or nowhere), leaving dangling references (e.g. `symbol has no - # offset` for a `pointer` type whose itemId.module drifted away). + elif typ.itemId.module == w.currentModule and typ.state == Complete and + isCanonType(w, typ) and w.emittedCanonTypes.hasKey(nifTypeName(w, typ)): + # A content-named wrapper whose name this module already def'd. Two distinct + # `PType`s can share one content id -- sem mints a fresh `lent PNode` per + # signature -- and they are interchangeable by construction (same kind, same + # `sameType` flags, same sons), so the duplicate folds into a reference + # rather than emitting a second def under a name that already has one. + when defined(icCanonDbg): + let cn = nifTypeName(w, typ) + if w.emittedCanonTypes[cn] != typ.itemId.item: + var sons = "" + for so in typ.sonsImpl: + sons.add (if so == nil: "." else: nifTypeName(w, so)) & " " + stderr.writeLine "[canon-collide] " & cn & " kind=" & $typ.kind & + " uidA=" & $w.emittedCanonTypes[cn] & " uidB=" & $typ.itemId.item & + " flags=" & $typ.flagsImpl & " sons=" & sons & + " owner=" & (if typ.ownerFieldImpl != nil: typ.ownerFieldImpl.name.s else: "-") + dest.addSymUse pool.syms.getOrIncl(nifTypeName(w, typ)), NoLineInfo + elif typ.itemId.module == w.currentModule and typ.state == Complete: + # Ownership for serialization is `itemId.module`, the module that CREATED + # the type: the NIF name (`typeToNifSym`) and the loader (`createTypeStub`) + # both key off `itemId`. Never gate this on `bindingId`, which a replica + # inherits from another module -- that filed defs in the wrong module (or + # nowhere), leaving dangling references (`symbol has no offset` for a + # `pointer` type whose id had drifted away). typ.state = Sealed if restoresWrittenState(w.infos.config): w.writtenTypes.add typ + if isCanonType(w, typ): w.emittedCanonTypes[nifTypeName(w, typ)] = typ.itemId.item writeTypeDef(w, dest, typ) else: dest.addSymUse pool.syms.getOrIncl(nifTypeName(w, typ)), NoLineInfo @@ -931,6 +1344,7 @@ proc registerNifAstTags*() = sdefTag = registerTag(symDefTagName) tdefTag = registerTag(typeDefTagName) hiddenTypeTag = registerTag(hiddenTypeTagName) + bindingIdTag = registerTag(bindingIdTagName) replayTag = registerTag("replay") repConverterTag = registerTag("repconverter") repDestroyTag = registerTag("repdestroy") @@ -1411,6 +1825,18 @@ proc hashRegion(s: var Sha1State; c: var CookieCtx; flat: seq[CookieTok]; i = skipTo continue let t = flat[i] + if t.kind == ckParLe and t.tag == bindingIdTagName: + # A type's `bindingId` is a module-wide mint COUNTER: creating one extra + # type renumbers every type minted after it, so hashing it made the + # interface cookie depend on declaration ORDER -- inserting a private proc + # at the top of a module changed the cookie of a module whose interface had + # not changed and invalidated every importer (measured: 98 re-sems for one + # line added to ast.nim). The type's real identity survives without it: its + # structure is hashed here, and its nominal identity rides on `typ.symImpl`, + # a literal cross-region name. + s.update " #" # placeholder: keeps the field's presence, drops its value + i = nextTree(flat, i) + continue if t.kind in {ckSym, ckSymDef}: let sym = t.sym let name = t.name @@ -1473,9 +1899,31 @@ proc cookieSd(s: var Sha1State; c: var CookieCtx; flat: seq[CookieTok]; start: i if ok: skipFrom = p skipTo = nextTree(flat, p) + # Drop `resultPos` (son 7) as well -- sem appends it right after the + # body, and it is a bare REFERENCE to the routine's `result` symbol + # whose def lives inside the body we just skipped. Unresolvable to a + # region ordinal, it hashes as the literal `result..`, + # and that disamb is a module-wide per-name counter that shifts when any + # routine is inserted above this one. Nothing importer-visible is lost: + # the return type is already carried by the routine's proc type. + if skipTo < astEnd - 1: + skipTo = nextTree(flat, skipTo) # non-routine kinds (consts carry their value, types their structure incl. # default field values): hash everything. - hashRegion(s, c, flat, start, result, skipFrom, skipTo, keepFirstDefLiteral = true) + # + # One more thing comes off the end for routines: `writeSymDef` closes every + # `sd` with the TRANSFORMED-body slot (a plain dot for non-routines), and for + # a routine that slot holds a fully LOWERED body -- closure envs, `chckrange`, + # inlined `instantiationInfo` tuples and with them SOURCE LINE NUMBERS. A + # dependent's sem never reads it (the loader deliberately skips the slot under + # `cmdM`: "a dependent needs no foreign lowered body"), so hashing it only made + # the interface cookie shift whenever a line was inserted anywhere above the + # routine. Stop the region before that slot and close the tree by hand. + var theEnd = result + if kind in routineKinds and i < result - 1: + theEnd = i + hashRegion(s, c, flat, start, theEnd, skipFrom, skipTo, keepFirstDefLiteral = true) + if theEnd != result: s.update ")" proc scanStmtsForCookie(s: var Sha1State; c: var CookieCtx; flat: seq[CookieTok]) = ## Walks the whole written module, hashing only the importer-visible pieces; @@ -1704,7 +2152,15 @@ proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode; w.deps.addSymUse pool.syms.getOrIncl(w.toNifSymName(off.inst)), NoLineInfo w.deps.addIntLit off.genericParamsCount for ct in off.concreteTypes: - w.deps.addSymUse pool.syms.getOrIncl(typeToNifSym(ct, w.infos.config)), NoLineInfo + # `nifTypeName`, NOT `typeToNifSym`: the def this offer has to resolve + # against was emitted under the CANONICAL name, and `typeToNifSym` prints + # the raw mint counter. The mismatch is silent -- the loader is + # best-effort and just drops the offer -- so the consumer re-instantiates + # the generic in its own scope and fails wherever that scope differs + # (`tests/ic/ttransitiveoffer`: `TScopeSize` ambiguous between two + # imports). `fromRaw(64)` reaches it through an `int` literal copy, but + # every wrapper kind has been exposed to this since `2bed712f6`. + w.deps.addSymUse pool.syms.getOrIncl(nifTypeName(w, ct)), NoLineInfo w.deps.addParRi # Record this module's own absolute source path. The NIF suffix is a hash of # the (relative) path (gear2/modnames.moduleSuffix) and is NOT reversible, so @@ -1746,7 +2202,7 @@ proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode; # directly (cf. `loadImport`, which carries module suffixes the same way). w.deps.addParLe typeOfferTag, NoLineInfo w.deps.addStrLit w.toNifSymName(off.generic) - w.deps.addStrLit typeToNifSym(off.inst, w.infos.config) + w.deps.addStrLit nifTypeName(w, off.inst) # canonical name, see above w.deps.addParRi # OWNER MUST EMIT: a type reachable only through an offered instance — the @@ -1760,10 +2216,10 @@ proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode; # sons) only for an own, still-Complete type; an already-Sealed one is skipped. for off in genericOffers: for ct in off.concreteTypes: - if ct != nil and ct.uniqueId.module == w.currentModule and ct.state == Complete: + if ct != nil and ct.itemId.module == w.currentModule and ct.state == Complete: writeType(w, bottom, ct) for off in typeOffers: - if off.inst != nil and off.inst.uniqueId.module == w.currentModule and + if off.inst != nil and off.inst.itemId.module == w.currentModule and off.inst.state == Complete: writeType(w, bottom, off.inst) @@ -2192,7 +2648,7 @@ proc reconstructSysType(c: var DecodeContext; name: string; k: int; itemVal: int result = c.types.getOrDefault(name)[0] if result == nil: let id = itemId(-1'i32, itemVal) - result = PType(itemId: id, uniqueId: id, kind: TTypeKind(k), state: Complete) + result = PType(itemId: id, bindingId: id, kind: TTypeKind(k), state: Complete) if TTypeKind(k) == tyNil: result.sizeImpl = c.infos.config.target.ptrSize result.alignImpl = int16 c.infos.config.target.ptrSize @@ -2262,7 +2718,7 @@ proc tryCreateTypeStub(c: var DecodeContext; name: string): PType = let modFi = id.module.FileIndex if not hasTypeOffset(c, modFi, name): return nil - result = PType(itemId: id, uniqueId: id, kind: TTypeKind(k), state: Partial) + result = PType(itemId: id, bindingId: id, kind: TTypeKind(k), state: Partial) # `loadType` re-resolves the buffer via `typeCursor`, so the cached entry is a # don't-care for types — store the primary one if any (else a 0-offset stub). c.types[name] = (result, c.mods[modFi].index.getOrDefault(name)) @@ -2481,14 +2937,19 @@ proc loadTypeFromCursor(c: var DecodeContext; n: var Cursor; t: PType; localSyms loadField t.sizeImpl loadField t.alignImpl loadField t.paddingAtEndImpl - t.itemId = itemId(t.itemId.module, loadAtom(int32, n)) # nonUniqueId - if n.kind == StrLit: - # itemId.module differs from uniqueId.module (an `exactReplica` of a - # foreign type): restore the canonical module half - t.itemId = itemId(int32(moduleId(c, strVal(n))), t.itemId.item) - skip n - elif n.kind == DotToken: + if n.kind == DotToken: + # no `(bid ...)`: not a replica, so the binding id is the type's own id, + # which `createTypeStub` already took from the name skip n + else: + n.into: + t.bindingId = itemId(t.bindingId.module, loadAtom(int32, n)) + # `hasMore` first: inside `into` the module half is optional, and asking + # a spent cursor for its `kind` asserts + if n.hasMore and n.kind == StrLit: + # a replica of a foreign type: restore the module half too + t.bindingId = itemId(int32(moduleId(c, strVal(n))), t.bindingId.item) + skip n t.typeInstImpl = loadTypeStub(c, n, localSyms) t.nImpl = loadNode(c, n, typesModule, localSyms) @@ -2508,11 +2969,13 @@ proc loadType*(c: var DecodeContext; t: PType) = # canonical `typeToNifSym` (which asserts non-`@bk`). Reconstruct that name so a # Partial `@bk` stub that escaped the inline pre-scan can still be force-loaded. let typeName = - if t.uniqueId.isBackendMinted: - "`t" & $ord(t.kind) & "." & $t.uniqueId.item & "." & + if t.itemId.isBackendMinted: + "`t" & $ord(t.kind) & "." & $t.itemId.item & "." & modname(t.itemId.module, c.infos.config) & BackendLocalMarker else: typeToNifSym(t, c.infos.config) + # `itemId`, not `bindingId`: the name just built above is the type's own NIF + # name, so it must be looked up in the module that owns that name. let modFi = t.itemId.module.FileIndex # `typeCursor` resolves to the primary `.t.bif` (`@bk` env types) or falls back to # the `.s.bif` companion (frontend type defs, which `.t.bif` no longer carries). @@ -2871,11 +3334,26 @@ proc loadNode(c: var DecodeContext; n: var Cursor; thisModule: string; # `ast[bodyPos].kind != nkEmpty` checks need no load) whose children are # materialized on demand (see `materializeLazyBody`, driven by the `len` # hook). An empty body is a single node — not worth deferring. + # + # ONLY an `nkStmtList` body is deferred, and that restriction is what keeps + # the placeholder a WELL-FORMED node. The compiler's most basic invariant is + # that a node's kind implies its arity: every `case n.kind` is entitled to + # reach `n[0]`/`n[1]` without asking `len` first, and hundreds do. A + # childless placeholder claiming to be an `nkAsgn` breaks that — `x = s` as a + # nested proc's whole body IndexDefect'd in `trees.getPotentialWrites`, which + # does exactly `n[0]`/`n[1]` under `of nkAsgn`. A childless `nkStmtList` is + # legal, so no such reader can be surprised. + # + # Hooking `[]` instead would not close this: `sons` is a public field with + # ~35 direct uses in the compiler, plus `firstSon`/`secondSon`/`lastSon`, + # and none of them route through `[]`. Nor does the restriction cost much: + # `nkStmtList` is 82.5% of the 278_604 bodies a `nim ic` of the compiler + # defers, and 13.1% of the rest are one-line `nkAsgn` bodies. c.withNode n, result, kind: var idx = 0 while n.hasMore: if idx == bodyPos and n.kind == TagLit and - n.nodeKind notin {nkEmpty, nkNone}: + n.nodeKind == nkStmtList: let info = c.infos.oldLineInfo(n.info, cursorPool(n)) let ph = newNodeI(n.nodeKind, info) ph.flags.incl nfLazyBody @@ -3494,20 +3972,21 @@ proc writeLoweredModule*(c: var DecodeContext; config: ConfigRef; w.deps.addSymUse pool.syms.getOrIncl(w.toNifSymName(off.inst)), NoLineInfo w.deps.addIntLit off.genericParamsCount for ct in off.concreteTypes: - w.deps.addSymUse pool.syms.getOrIncl(typeToNifSym(ct, w.infos.config)), NoLineInfo + # Canonical name, see `writeNifModule`'s copy of this loop. + w.deps.addSymUse pool.syms.getOrIncl(nifTypeName(w, ct)), NoLineInfo w.deps.addParRi for off in precomp.typeOffers: w.deps.addParLe typeOfferTag, NoLineInfo w.deps.addStrLit w.toNifSymName(off.generic) - w.deps.addStrLit typeToNifSym(off.inst, w.infos.config) + w.deps.addStrLit nifTypeName(w, off.inst) # canonical name, see above w.deps.addParRi # OWNER MUST EMIT offered types this module owns (see writeNifModule). for off in precomp.genericOffers: for ct in off.concreteTypes: - if ct != nil and ct.uniqueId.module == w.currentModule and ct.state == Complete: + if ct != nil and ct.itemId.module == w.currentModule and ct.state == Complete: writeType(w, bottom, ct) for off in precomp.typeOffers: - if off.inst != nil and off.inst.uniqueId.module == w.currentModule and + if off.inst != nil and off.inst.itemId.module == w.currentModule and off.inst.state == Complete: writeType(w, bottom, off.inst) diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim index 1851e7895f..ba4396a335 100644 --- a/compiler/astalgo.nim +++ b/compiler/astalgo.nim @@ -639,9 +639,14 @@ proc getOrDefault*[T](t: TIdTable[T], key: ItemId): T = if index >= 0: result = t.data[index].val else: result = default(T) -template idTableGet*[T](t: TIdTable[T], key: PType | PSym): T = +template idTableGet*[T](t: TIdTable[T], key: PSym): T = getOrDefault(t, key.itemId) +template idTableGet*[T](t: TIdTable[T], key: PType): T = + ## Type-keyed tables are BINDING tables: an `exactReplica` must find what its + ## original bound, hence `bindingId` and not the type's own identity. + getOrDefault(t, key.bindingId) + proc idTableRawInsert[T](data: var TIdPairSeq[T], key: ItemId, val: T) = var h: Hash let keyId = toId(key) @@ -672,9 +677,12 @@ proc `[]=`*[T](t: var TIdTable[T], key: ItemId, val: T) = idTableRawInsert(t.data, key, val) inc(t.counter) -template idTablePut*[T](t: var TIdTable[T], key: PType | PSym, val: T) = +template idTablePut*[T](t: var TIdTable[T], key: PSym, val: T) = t[key.itemId] = val +template idTablePut*[T](t: var TIdTable[T], key: PType, val: T) = + t[key.bindingId] = val + iterator idTablePairs*[T](t: TIdTable[T]): tuple[key: ItemId, val: T] = for i in 0..high(t.data): if not isNil(t.data[i].key): diff --git a/compiler/astdef.nim b/compiler/astdef.nim index 338e9d5a51..11f9a4474d 100644 --- a/compiler/astdef.nim +++ b/compiler/astdef.nim @@ -784,11 +784,16 @@ type # same id; there may be multiple copies of a type # in memory! # Keep in sync with PackedType - itemId*: ItemId + itemId*: ItemId # THE identity of this type: unique per instance, forever. + # Names the type in the NIF cache and decides which + # module owns its definition. kind*: TTypeKind # kind of type state*: ItemState - uniqueId*: ItemId # due to a design mistake, we need to keep the real ID here as it - # is required by the --incremental:on mode. + bindingId*: ItemId # the id of the type this one is a REPLICA of (its own + # `itemId` when it is not a replica). Only the generic + # binding tables (`LayeredIdTable` & friends) key on it: + # `exactReplica` produces a copy that must keep matching + # its original in those tables. Never an identity. callConvImpl*: TCallingConvention # for procs flagsImpl*: TTypeFlags # flags of the type sonsImpl*: TTypeSeq # base types, etc. @@ -1091,7 +1096,7 @@ proc forcePartial*(s: PSym) = proc forcePartial*(t: PType) = ## Resets all impl-fields to their default values and sets state to Partial. ## This is useful for creating a stub type that can be lazily loaded later. - ## The fields itemId, kind, uniqueId are preserved. + ## The fields itemId, kind, bindingId are preserved. t.state = Partial t.callConvImpl = ccNimCall t.flagsImpl = {} diff --git a/compiler/cbuilderdecls.nim b/compiler/cbuilderdecls.nim index eb6dd3d627..26a42028e6 100644 --- a/compiler/cbuilderdecls.nim +++ b/compiler/cbuilderdecls.nim @@ -326,7 +326,7 @@ proc startStruct(obj: var Builder; m: BModule; t: PType; name: string; baseType: # rest of the options add a field or don't need it due to inheritance, # we need to add the dummy field for uncheckedarray ahead of time # so that it remains trailing - if t.itemId notin m.g.graph.memberProcsPerType and + if t.bindingId notin m.g.graph.memberProcsPerType and t.n != nil and t.n.len == 1 and t.n[0].kind == nkSym and t.n[0].sym.typ.skipTypes(abstractInst).kind == tyUncheckedArray: # only consists of flexible array field, add *initial* dummy field @@ -341,7 +341,7 @@ proc startStruct(obj: var Builder; m: BModule; t: PType; name: string; baseType: proc finishStruct(obj: var Builder; m: BModule; t: PType; info: StructBuilderInfo) = if info.baseKind == bcNone and info.preFieldsLen == obj.buf.len and - t.itemId notin m.g.graph.memberProcsPerType: + t.bindingId notin m.g.graph.memberProcsPerType: # no fields were added, add dummy field obj.addField(name = "dummy", typ = CChar) if info.named: diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim index eac004a3b0..1202aa0536 100644 --- a/compiler/ccgcalls.nim +++ b/compiler/ccgcalls.nim @@ -394,7 +394,7 @@ proc genArg(p: BProc, n: PNode, param: PSym; call: PNode; result: var Builder; n # variable. Thus, we create a temporary pointer variable instead. let needsIndirect = mapType(p.config, n.firstSon.typ, mapTypeChooser(n.firstSon) == skParam) != ctArray if needsIndirect: - n.typ = n.typ.exactReplica(p.module.idgen) + n.typ = copyType(n.typ, p.module.idgen, n.typ.owner) n.typ.incl tfVarIsPtr a = initLocExprSingleUse(p, n) a = withTmpIfNeeded(p, a, needsTmp) diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index ba7e1500e3..3af6e3451e 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -1048,7 +1048,7 @@ proc genRecordField(p: BProc, e: PNode, d: var TLoc) = if p.module.compileToCpp and e.kind == nkDotExpr and e[1].kind == nkSym and e[1].typ.kind == tyPtr: # special case for C++: we need to pull the type of the field as member and friends require the complete type. let typ = e[1].typ.elementType - if typ.itemId in p.module.g.graph.memberProcsPerType: + if typ.bindingId in p.module.g.graph.memberProcsPerType: discard getTypeDesc(p.module, typ) genRecordFieldAux(p, e, d, a) diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index 36238d717d..a7416e0815 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -742,8 +742,8 @@ proc mangleRecFieldName(m: BModule; field: PSym): Rope = proc hasCppCtor(m: BModule; typ: PType): bool = result = false - if m.compileToCpp and typ != nil and typ.itemId in m.g.graph.memberProcsPerType: - for prc in m.g.graph.memberProcsPerType[typ.itemId]: + if m.compileToCpp and typ != nil and typ.bindingId in m.g.graph.memberProcsPerType: + for prc in m.g.graph.memberProcsPerType[typ.bindingId]: if sfConstructor in prc.flags: return true @@ -752,8 +752,8 @@ proc genCppParamsForCtor(p: BProc; call: PNode; didGenTemp: var bool): string proc genCppInitializer(m: BModule, prc: BProc; typ: PType; didGenTemp: var bool): string = #To avoid creating a BProc per test when called inside a struct nil BProc is allowed result = "{}" - if typ.itemId in m.g.graph.initializersPerType: - let call = m.g.graph.initializersPerType[typ.itemId] + if typ.bindingId in m.g.graph.initializersPerType: + let call = m.g.graph.initializersPerType[typ.bindingId] if call != nil: var p = prc if p == nil: @@ -833,8 +833,8 @@ proc genMemberProcHeader(m: BModule; prc: PSym; result: var Builder; asPtr: bool proc addRecordFields(result: var Builder; m: BModule; typ: PType, check: var IntSet) = genRecordFieldsAux(m, typ.n, typ, check, result) - if typ.itemId in m.g.graph.memberProcsPerType: - let procs = m.g.graph.memberProcsPerType[typ.itemId] + if typ.bindingId in m.g.graph.memberProcsPerType: + let procs = m.g.graph.memberProcsPerType[typ.bindingId] var isDefaultCtorGen, isCtorGen: bool = false for prc in procs: if sfConstructor in prc.flags: @@ -1780,7 +1780,7 @@ proc generateRttiDestructor(g: ModuleGraph; typ: PType; owner: PSym; kind: TType dest.typ = getSysType(g, info, tyPointer) - result.typ = newProcType(info, idgen, owner) + result.typ = newProcType(info, idgen, result) result.typ.addParam dest var n = newNodeI(nkProcDef, info, bodyPos+1) @@ -2070,7 +2070,7 @@ proc genTypeInfoV2(m: BModule; t: PType; info: TLineInfo): Rope = result = "NTIv2$1_" % [rope($sig)] m.typeInfoMarkerV2[sig] = result - let owner = t.skipTypes(typedescPtrs).itemId.module + let owner = t.skipTypes(typedescPtrs).bindingId.module # In the per-module backend (`cg`) RTTI is emit-everywhere like procs and # consts: every demanding module emits the `'d'` definition (deduped to one # owner by the merge stage). The owner-routing below would instead push the @@ -2173,7 +2173,7 @@ proc genTypeInfoV1(m: BModule; t: PType; info: TLineInfo): Rope = declareNimType(m, "TNimType", result, old.int) return prefixTI(result) - var owner = t.skipTypes(typedescPtrs).itemId.module + var owner = t.skipTypes(typedescPtrs).bindingId.module # In the per-module backend (`cg`) V1 RTTI is emit-everywhere like procs, # consts and V2 type info: every demanding module emits the `'d'` definition # (deduped to one owner by the merge stage). The owner-routing below would @@ -2289,8 +2289,8 @@ proc genTypeSection(m: BModule, n: PNode) = # declarations where the type is already written separately before the initializer. proc genCppConstructorExpr(m: BModule, prc: BProc; typ: PType; didGenTemp: var bool): Snippet = var params = "" - if typ.itemId in m.g.graph.initializersPerType: - let call = m.g.graph.initializersPerType[typ.itemId] + if typ.bindingId in m.g.graph.initializersPerType: + let call = m.g.graph.initializersPerType[typ.bindingId] if call != nil: var p = prc if p == nil: diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 8566c62632..8c4fbe87c4 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -2841,7 +2841,7 @@ proc generateLibraryDestroyGlobals(graph: ModuleGraph; m: BModule; body: PNode; let prefixedName = m.config.nimMainPrefix & "NimDestroyGlobals" let procname = getIdent(graph.cache, prefixedName) result = newSym(skProc, procname, m.idgen, m.module.owner, m.module.info) - result.typ = newProcType(m.module.info, m.idgen, m.module.owner) + result.typ = newProcType(m.module.info, m.idgen, result) result.typ.callConv = ccCDecl backendEnsureMutable result incl result.flagsImpl, sfExportc diff --git a/compiler/cgmeth.nim b/compiler/cgmeth.nim index e106a674b6..d6291d1cab 100644 --- a/compiler/cgmeth.nim +++ b/compiler/cgmeth.nim @@ -197,10 +197,10 @@ proc methodDef*(g: ModuleGraph; idgen: IdGenerator; s: PSym) = if witness.isNil: witness = g.methods[i].methods[0] # create a new dispatcher: # stores the id and the position - if s.typ.firstParamType.skipTypes(skipPtrs).itemId notin g.bucketTable: - g.bucketTable[s.typ.firstParamType.skipTypes(skipPtrs).itemId] = 1 + if s.typ.firstParamType.skipTypes(skipPtrs).bindingId notin g.bucketTable: + g.bucketTable[s.typ.firstParamType.skipTypes(skipPtrs).bindingId] = 1 else: - g.bucketTable.inc(s.typ.firstParamType.skipTypes(skipPtrs).itemId) + g.bucketTable.inc(s.typ.firstParamType.skipTypes(skipPtrs).bindingId) g.methods.add((methods: @[s], dispatcher: createDispatcher(s, g, idgen))) logMethodDef(g, s) #echo "adding ", s.info diff --git a/compiler/concepts.nim b/compiler/concepts.nim index 89a301e03f..dc99bcaa5f 100644 --- a/compiler/concepts.nim +++ b/compiler/concepts.nim @@ -205,7 +205,7 @@ proc matchConceptToImpl(c: PContext, f, potentialImpl: PType; m: var MatchCon): # Cycle detection: track (concept, type) pairs to prevent infinite recursion. # Returns true on cycle (coinductive semantics) to support co-dependent concepts. - let pair: ConceptTypePair = (concpt.itemId, potentialImpl.itemId) + let pair: ConceptTypePair = (concpt.bindingId, potentialImpl.bindingId) if pair in m.marker: return true m.marker.incl pair diff --git a/compiler/enumtostr.nim b/compiler/enumtostr.nim index 9210e8db2e..8e05b883d2 100644 --- a/compiler/enumtostr.nim +++ b/compiler/enumtostr.nim @@ -14,7 +14,7 @@ proc genEnumToStrProc*(t: PType; info: TLineInfo; g: ModuleGraph; idgen: IdGener let res = newSym(skResult, getIdent(g.cache, "result"), idgen, result, info) res.typ = getSysType(g, info, tyString) - result.typ = newType(tyProc, idgen, t.owner) + result.typ = newType(tyProc, idgen, result) result.typ.n = newNodeI(nkFormalParams, info) rawAddSon(result.typ, res.typ) result.typ.n.add newNodeI(nkEffectList, info) diff --git a/compiler/layeredtable.nim b/compiler/layeredtable.nim index 83f4d9ad28..9e1a03630d 100644 --- a/compiler/layeredtable.nim +++ b/compiler/layeredtable.nim @@ -82,7 +82,7 @@ proc lookup(typeMap: ref LayeredIdTableObj, key: ItemId): PType = template lookup*(typeMap: ref LayeredIdTableObj, key: PType): PType = ## recursively looks up binding of `key` in all parent layers - lookup(typeMap, key.itemId) + lookup(typeMap, key.bindingId) when not useRef: proc lookup(typeMap: LayeredIdTableObj, key: ItemId): PType {.inline.} = @@ -91,11 +91,11 @@ when not useRef: result = lookup(typeMap.nextLayer, key) template lookup*(typeMap: LayeredIdTableObj, key: PType): PType = - lookup(typeMap, key.itemId) + lookup(typeMap, key.bindingId) proc put(typeMap: var LayeredIdTable, key: ItemId, value: PType) {.inline.} = typeMap.topLayer[key] = value template put*(typeMap: var LayeredIdTable, key, value: PType) = ## binds `key` to `value` only in current layer - put(typeMap, key.itemId, value) + put(typeMap, key.bindingId, value) diff --git a/compiler/liftdestructors.nim b/compiler/liftdestructors.nim index caba4b2600..4e2063888a 100644 --- a/compiler/liftdestructors.nim +++ b/compiler/liftdestructors.nim @@ -718,7 +718,7 @@ proc useSeqOrStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) = when defined(icDbg): if t.destructor == nil: echo "MISSING destructor: ", typeToString(t), " kind=", t.kind, - " itemId=", t.itemId, " uniqueId=", t.uniqueId, " state=", t.state, + " itemId=", t.itemId, " bindingId=", t.bindingId, " state=", t.state, " owner=", (if t.owner != nil: t.owner.name.s else: "nil") doAssert t.destructor != nil body.add destructorCall(c, t.destructor, x) @@ -1233,7 +1233,7 @@ proc symDupPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttache res.typ = typ src.typ = typ - result.typ = newType(tyProc, idgen, owner) + result.typ = newType(tyProc, idgen, result) result.typ.n = newNodeI(nkFormalParams, info) rawAddSon(result.typ, res.typ) result.typ.n.add newNodeI(nkEffectList, info) @@ -1279,7 +1279,8 @@ proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp else: src.typ = typ - result.typ = newProcType(info, idgen, owner) + # the hook OWNS its signature, like any routine sem'd from source + result.typ = newProcType(info, idgen, result) result.typ.addParam dest if kind notin {attachedDestructor, attachedWasMoved}: result.typ.addParam src diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim index 6e8715c837..4cdeac0348 100644 --- a/compiler/modulegraphs.nim +++ b/compiler/modulegraphs.nim @@ -343,8 +343,8 @@ iterator procInstCacheItems*(g: ModuleGraph; s: PSym): PInstantiation = proc getAttachedOp*(g: ModuleGraph; t: PType; op: TTypeAttachedOp): PSym = ## returns the requested attached operation for type `t`. Can return nil ## if no such operation exists. - if g.attachedOps[op].contains(t.itemId): - result = g.attachedOps[op][t.itemId] + if g.attachedOps[op].contains(t.bindingId): + result = g.attachedOps[op][t.bindingId] elif g.config.cmd in {cmdNifC, cmdM}: # Fall back to key-based lookup for NIF-loaded hooks let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback) @@ -373,7 +373,7 @@ proc setAttachedOp*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp; # references derived env-field syms that no module's NIF defines if g.loadedOps[op].getOrDefault(key) == nil: g.loadedOps[op][key] = value - g.attachedOps[op][t.itemId] = value + g.attachedOps[op][t.bindingId] = value return let existing = g.loadedOps[op].getOrDefault(key) if existing == nil: @@ -411,7 +411,7 @@ proc setAttachedOp*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp; break if not updated: g.opsLog.add LogEntry(kind: HookEntry, op: op, module: module, key: key, sym: value) - g.attachedOps[op][t.itemId] = value + g.attachedOps[op][t.bindingId] = value proc setAttachedOp*(g: ModuleGraph; module: int; typeId: ItemId; op: TTypeAttachedOp; value: PSym) = ## Overload that takes ItemId directly, useful for registering hooks from NIF index. @@ -419,7 +419,7 @@ proc setAttachedOp*(g: ModuleGraph; module: int; typeId: ItemId; op: TTypeAttach proc setAttachedOpPartial*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp; value: PSym) = ## we also need to record this to the packed module. - g.attachedOps[op][t.itemId] = value + g.attachedOps[op][t.bindingId] = value proc completePartialOp*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp; value: PSym) {.inline.} = discard @@ -441,19 +441,19 @@ proc addNifReplayAction*(g: ModuleGraph; module: int32; n: PNode) = g.nifReplayActions.mgetOrPut(module, @[]).add n iterator getMethodsPerType*(g: ModuleGraph; t: PType): PSym = - if g.methodsPerType.contains(t.itemId): - for it in mitems g.methodsPerType[t.itemId]: + if g.methodsPerType.contains(t.bindingId): + for it in mitems g.methodsPerType[t.bindingId]: yield it proc getToStringProc*(g: ModuleGraph; t: PType): PSym = - result = g.enumToStringProcs.getOrDefault(t.itemId) + result = g.enumToStringProcs.getOrDefault(t.bindingId) if result == nil and g.config.cmd in {cmdNifC, cmdM}: let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback) result = g.loadedEnumToStringProcs.getOrDefault(key) assert result != nil proc setToStringProc*(g: ModuleGraph; t: PType; value: PSym) = - g.enumToStringProcs[t.itemId] = value + g.enumToStringProcs[t.bindingId] = value let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback) # Stamp with the module that owns the generated proc, not the enum's def # module: the def module's process may never have generated it (same @@ -461,12 +461,12 @@ proc setToStringProc*(g: ModuleGraph; t: PType; value: PSym) = g.opsLog.add LogEntry(kind: EnumToStrEntry, module: value.itemId.module.int, key: key, sym: value) iterator methodsForGeneric*(g: ModuleGraph; t: PType): (int, PSym) = - if g.methodsPerGenericType.contains(t.itemId): - for it in mitems g.methodsPerGenericType[t.itemId]: + if g.methodsPerGenericType.contains(t.bindingId): + for it in mitems g.methodsPerGenericType[t.bindingId]: yield (it[0], it[1]) proc addMethodToGeneric*(g: ModuleGraph; module: int; t: PType; col: int; m: PSym) = - g.methodsPerGenericType.mgetOrPut(t.itemId, @[]).add (col, m) + g.methodsPerGenericType.mgetOrPut(t.bindingId, @[]).add (col, m) let key = typeKey(t, g.config, loadTypeCallback, loadSymCallback) let ownerModule = if t.sym != nil: t.sym.itemId.module.int else: module g.opsLog.add LogEntry(kind: MethodEntry, module: ownerModule, key: key, sym: m) diff --git a/compiler/options.nim b/compiler/options.nim index ad3163e842..69b28ab6f3 100644 --- a/compiler/options.nim +++ b/compiler/options.nim @@ -29,7 +29,7 @@ const nimEnableCovariance* = defined(nimEnableCovariance) - icFormatVersion* = "30" + icFormatVersion* = "34" ## 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` @@ -54,6 +54,16 @@ const ## id, so its hash is stable across the NIF boundary (was breaking ## nim-serialization's auto-serialization lookup under IC). The sem-NIF ## macrocache entries and baked generic-instance bodies hold the old hashes. + ## v7 (=31): anonymous wrapper types (`var T`, `lent T`, `sink T`, tuples) + ## are named by their CONTENT instead of `itemId.item`, the module-wide + ## type-mint counter (see ast2nif.CanonTypeKinds). Old caches name the same + ## type differently, so every `.s.bif` reference would dangle. + ## v8 (=32): the same for `tyProc`, except that a proc type which is a + ## routine's SIGNATURE is named after that routine rather than by content + ## (see ast2nif.sigRoutineOf). Renames types, so old caches dangle again. + ## v9 (=33): and for the per-module `int`/`float` LITERAL COPIES (see + ## ast2nif.CanonLitCopyKinds), the last mover that broke a build outright + ## (`symbol has no offset` out of a cached `.t.bif`). Renames types again. type # please make sure we have under 32 options # (improves code efficiency a lot!) diff --git a/compiler/pipelines.nim b/compiler/pipelines.nim index 29959fb76f..fbfdb948f7 100644 --- a/compiler/pipelines.nim +++ b/compiler/pipelines.nim @@ -305,7 +305,7 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator var typeOffers: seq[tuple[generic: PSym; inst: PType]] = @[] for genItemId, instList in graph.typeInstCache: for inst in instList: - if inst != nil and inst.uniqueId.module == module.position and + if inst != nil and inst.itemId.module == module.position and inst.kidsLen > 0 and inst[0] != nil and inst[0].kind == tyGenericBody and inst[0].sym != nil: typeOffers.add (inst[0].sym, inst) diff --git a/compiler/semcall.nim b/compiler/semcall.nim index 26c459f6d8..b5b1d4d06e 100644 --- a/compiler/semcall.nim +++ b/compiler/semcall.nim @@ -77,7 +77,7 @@ proc isAttachableRoutineTo(prc: PSym, arg: PType): bool = # has default value, parameter is not considered in type attachment continue let t = nominalRoot(prc.typ[i]) - if t != nil and t.itemId == arg.itemId: + if t != nil and t.bindingId == arg.bindingId: # parameter `i` is a nominal type in this module # attachable if the nominal root `t` has the same id as `arg` return true @@ -735,10 +735,10 @@ proc indexTypesMatch(c: PContext, f, a: PType, arg: PNode): PNode = when defined(icDbg): if result == nil and f != nil and a != nil and f.kind == tyEnum: echo "INDEXMISMATCH f=", typeToString(f), " itemId=", f.itemId, - " uniqueId=", f.uniqueId, " mod=", toFullPath(c.config, f.itemId.module.FileIndex), + " bindingId=", f.bindingId, " mod=", toFullPath(c.config, f.itemId.module.FileIndex), " sym=", (if f.sym != nil: $f.sym.itemId else: "nil"), " state=", f.state let a2 = a.skipTypes({tyRange}) - echo " a=", typeToString(a), " itemId=", a2.itemId, " uniqueId=", a2.uniqueId, + echo " a=", typeToString(a), " itemId=", a2.itemId, " bindingId=", a2.bindingId, " mod=", toFullPath(c.config, a2.itemId.module.FileIndex), " sym=", (if a2.sym != nil: $a2.sym.itemId else: "nil"), " state=", a2.state diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim index d7bfa76eb0..7fb26fbeb5 100644 --- a/compiler/semexprs.nim +++ b/compiler/semexprs.nim @@ -2187,7 +2187,7 @@ proc semProcBody(c: PContext, n: PNode; expectedType: PType = nil): PNode = echo "[icMetaRet] meta result type for ", c.p.owner.name.s, ": ", typeToString(c.p.resultSym.typ), " kind=", c.p.resultSym.typ.kind, " flags=", c.p.resultSym.typ.flags, - " uid=", c.p.resultSym.typ.uniqueId.module, ".", c.p.resultSym.typ.uniqueId.item, + " itemId=", c.p.resultSym.typ.itemId.module, ".", c.p.resultSym.typ.itemId.item, " state=", c.p.resultSym.typ.state if isEmptyType(result.typ): # we inferred a 'void' return type: diff --git a/compiler/seminst.nim b/compiler/seminst.nim index 284b67e606..1b6d4a4138 100644 --- a/compiler/seminst.nim +++ b/compiler/seminst.nim @@ -349,7 +349,7 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable, when defined(icDbgRefc): echo "[icInst] ", prc.name.s, " param ", oldParam.name.s, ": ", typeToString(resulti), " (kind=", resulti.kind, - " uid=", resulti.uniqueId.module, ".", resulti.uniqueId.item, + " itemId=", resulti.itemId.module, ".", resulti.itemId.item, " flags=", resulti.flags, ") -> ", typeToString(paramType), " (kind=", paramType.kind, ")" @@ -407,6 +407,10 @@ proc instantiateProcType(c: PContext, pt: LayeredIdTable, eraseVoidParams(result) skipIntLiteralParams(result, c.idgen) + # The signature belongs to the INSTANCE, not to the generic it was copied + # from: `instCopyType` above kept the generic's owner, and every parameter has + # already been re-owned with `setOwner(param, prc)`. + setOwner(result, prc) prc.typ = result popInfoContext(c.config) diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim index 1bc5bac628..7cd1986bf5 100644 --- a/compiler/sempass2.nim +++ b/compiler/sempass2.nim @@ -109,7 +109,7 @@ proc getObjDepth(t: PType): (int, ItemId) = x = skipTypes(x, skipPtrs) if x.kind != tyObject: return (-3, default(ItemId)) - stack.add x.itemId + stack.add x.bindingId x = x.baseClass inc(result[0]) result[1] = stack[^2] diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim index efbd6b574b..9b9e8a75b9 100644 --- a/compiler/semstmts.nim +++ b/compiler/semstmts.nim @@ -1819,7 +1819,7 @@ proc typeSectionFinalPass(c: PContext, n: PNode) = var reified = semTypeNode(c, typeNode, nil) assert reified != nil assignType(typ, reified) - typ.itemId = reified.itemId # same id + typ.bindingId = reified.bindingId # same id if containsForwardType(typ): c.forwardTypeUpdates.add (owner, typ, typeNode) elif not remainingOwners.missingOrExcl(owner.id): @@ -2409,7 +2409,7 @@ proc semCppMember(c: PContext; s: PSym; n: PNode) = if typ.kind != tyObject: localError(c.config, n.info, pragmaName & " must be either ptr to object or object type.") if sameOwners(typ.owner, s.owner) and sameOwners(c.module, s.owner): - c.graph.memberProcsPerType.mgetOrPut(typ.itemId, @[]).add s + c.graph.memberProcsPerType.mgetOrPut(typ.bindingId, @[]).add s else: localError(c.config, n.info, pragmaName & " procs must be defined in the same scope as the type they are virtual for and it must be a top level scope") @@ -2417,7 +2417,7 @@ proc semCppMember(c: PContext; s: PSym; n: PNode) = localError(c.config, n.info, pragmaName & " procs are only supported in C++") else: var typ = s.typ.returnType - if typ != nil and typ.kind == tyObject and typ.itemId notin c.graph.initializersPerType: + if typ != nil and typ.kind == tyObject and typ.bindingId notin c.graph.initializersPerType: var initializerCall = newTree(nkCall, newSymNode(s)) var isInitializer = n[paramsPos].len > 1 for i in 1.. ", (if result != nil: typeToString(result) else: "MISS"), " allowMeta=", cl.allowMetaTypes @@ -423,7 +423,7 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType = var header = t # search for some instantiation here: if cl.allowMetaTypes: - result = getOrDefault(cl.localCache, t.itemId) + result = getOrDefault(cl.localCache, t.bindingId) else: result = searchInstTypes(cl.c.graph, t) @@ -473,7 +473,7 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType = if not cl.allowMetaTypes: cacheTypeInst(cl.c, result) else: - cl.localCache[t.itemId] = result + cl.localCache[t.bindingId] = result let oldSkipTypedesc = cl.skipTypedesc cl.skipTypedesc = true @@ -647,7 +647,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false): # type # Vector[N: static[int]] = array[N, float64] # TwoVectors[Na, Nb: static[int]] = (Vector[Na], Vector[Nb]) - result = getOrDefault(cl.localCache, t.itemId) + result = getOrDefault(cl.localCache, t.bindingId) if result != nil: return result inc cl.recursionLimit @@ -739,7 +739,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false): return bailout() result = instCopyType(cl, t) - cl.localCache[t.itemId] = result + cl.localCache[t.bindingId] = result for i in FirstGenericParamAt.. hashMaxDepth: hashMaxDepth = hashDepth if hashCalls >= 500_000_000 and hashCalls <= 500_000_300: echo "HASHLOOP n=", hashCalls, " d=", hashDepth, " kind=", t.kind, " id=", t.itemId, - " uniq=", t.uniqueId, " sym=", (if t.sym != nil: t.sym.name.s else: "NIL"), + " bindingId=", t.bindingId, " sym=", (if t.sym != nil: t.sym.name.s else: "NIL"), " state=", t.state, " owner=", (if t.owner != nil: t.owner.name.s else: "NIL") elif hashCalls == 500_000_301: echo "HASHLOOP maxDepth=", hashMaxDepth diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 139b2a0a7d..8ef5c4e974 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -137,8 +137,8 @@ proc put(c: var TCandidate, key, val: PType) {.inline.} = echo "binding ", key, " -> ", val when defined(icDbgRefc): if key.kind in {tyGenericParam, tyTypeDesc}: - echo "[icBind] put ", key.kind, " ", typeToString(key), " uid=", key.uniqueId.module, ".", - key.uniqueId.item, " itemId=", key.itemId.module, ".", key.itemId.item, + echo "[icBind] put ", key.kind, " ", typeToString(key), " itemId=", key.itemId.module, ".", + key.itemId.item, " bindingId=", key.bindingId.module, ".", key.bindingId.item, " state=", key.state, " -> ", typeToString(val) put(c.bindings, key, val.skipIntLit(c.c.idgen)) @@ -913,16 +913,14 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType = case typ.kind of tyStatic: param = paramSym skConst - param.typ = typ.exactReplica(m.c.idgen) - #copyType(typ, c.idgen, typ.owner) + param.typ = copyType(typ, m.c.idgen, typ.owner) if typ.n == nil: param.typ.incl tfInferrableStatic else: param.ast = typ.n of tyFromExpr: param = paramSym skVar - param.typ = typ.exactReplica(m.c.idgen) - #copyType(typ, c.idgen, typ.owner) + param.typ = copyType(typ, m.c.idgen, typ.owner) else: param = paramSym skType param.typ = if typ.isMetaType: @@ -974,8 +972,7 @@ proc matchUserTypeClass*(m: var TCandidate; ff, a: PType): PType = if ff.kind == tyUserTypeClassInst: result = generateTypeInstance(c, m.bindings, typeClass.sym.info, ff) else: - result = ff.exactReplica(m.c.idgen) - #copyType(ff, c.idgen, ff.owner) + result = copyType(ff, m.c.idgen, ff.owner) result.n = checkedBody @@ -1239,11 +1236,17 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, tfConceptMatchedTypeSym notin aOrig.flags template skipTypeCursor(it, kinds: untyped) = + # `ast.last`, not a hand-inlined copy of it. What this replaces was `last`'s + # body verbatim MINUS its `if state == Partial: loadType` line -- and that + # line is the whole point: a NIF-loaded stub answers `kind` off its NIF name + # while `sonsImpl` is still EMPTY, so `sonsImpl[^1]` raised IndexDefect. + # nimbus-eth2 died on it in the very first `nim ic` pass, inside the `x is T` + # under a chronos `{.async.}` iterator's `when`. The second call site below + # is unguarded and runs on EVERY `typeRel`, so this is not a concept-only + # corner: a probe counts 195 Partial `tyVar`/`tyLent` arrivals across one + # nimbus frontend, each of which was an IndexDefect waiting for its turn. while it.kind in kinds: - if it.kind == tyProc and it.nImpl.len > 1: - it = it.nImpl[^1].sym.typ - else: - it = it.sonsImpl[^1] + it = it.last var aOrig {.cursor.} = aOrig if useTypeLoweringRuleInTypeClass: @@ -2689,7 +2692,7 @@ proc staticAwareTypeRel(m: var TCandidate, f: PType, arg: var PNode): TTypeRelat # The ast of the type does not point to the symbol. # Without this we will never resolve a `static proc` with overloads let copiedNode = copyNode(arg) - copiedNode.typ = exactReplica(copiedNode.typ, m.c.idgen) + copiedNode.typ = copyType(copiedNode.typ, m.c.idgen, copiedNode.typ.owner) copiedNode.typ.n = arg arg = copiedNode typeRel(m, f, arg.typ) diff --git a/compiler/treetab.nim b/compiler/treetab.nim index fd6db77fa6..12e796d483 100644 --- a/compiler/treetab.nim +++ b/compiler/treetab.nim @@ -27,7 +27,7 @@ proc hashTree*(n: PNode): Hash = of nkCharLit..nkUInt64Lit: result = result !& hash(n.intVal) of nkFloatLit..nkFloat64Lit: result = result !& hash(cast[uint64](n.floatVal)) of nkStrLit..nkTripleStrLit: result = result !& hash(n.strVal) - of nkType, nkNilLit: result = result !& hash(n.typ.itemId) + of nkType, nkNilLit: result = result !& hash(n.typ.bindingId) else: for i in 0..= y.depth: 1 else: -1 ) - for item in g.objectTree[baseType.itemId]: - if item.value.itemId notin itemTable: - itemTable[item.value.itemId] = newSeq[PSym](methodIndexLen) + for item in g.objectTree[baseType.bindingId]: + if item.value.bindingId notin itemTable: + itemTable[item.value.bindingId] = newSeq[PSym](methodIndexLen) var mIndex = 0 # here is the correpsonding index - if baseType.itemId notin rootItemIdCount: - rootItemIdCount[baseType.itemId] = 1 + if baseType.bindingId notin rootItemIdCount: + rootItemIdCount[baseType.bindingId] = 1 else: - mIndex = rootItemIdCount[baseType.itemId] - rootItemIdCount.inc(baseType.itemId) + mIndex = rootItemIdCount[baseType.bindingId] + rootItemIdCount.inc(baseType.bindingId) for idx in 0..= y.depth: 1 else: -1 ) - for item in g.objectTree[baseType.itemId]: - if item.value.itemId notin itemTable: - itemTable[item.value.itemId] = newSeq[PSym](methodIndexLen) + for item in g.objectTree[baseType.bindingId]: + if item.value.bindingId notin itemTable: + itemTable[item.value.bindingId] = newSeq[PSym](methodIndexLen) var mIndex = 0 # here is the correpsonding index - if baseType.itemId notin rootItemIdCount: - rootItemIdCount[baseType.itemId] = 1 + if baseType.bindingId notin rootItemIdCount: + rootItemIdCount[baseType.bindingId] = 1 else: - mIndex = rootItemIdCount[baseType.itemId] - rootItemIdCount.inc(baseType.itemId) + mIndex = rootItemIdCount[baseType.bindingId] + rootItemIdCount.inc(baseType.bindingId) for idx in 0..