refactoring: better IC + no unique Id (#26137)

This commit is contained in:
Andreas Rumpf
2026-08-25 11:59:19 +02:00
committed by GitHub
parent 7f120229e8
commit 8ca7b75b8b
33 changed files with 736 additions and 189 deletions

View File

@@ -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

View File

@@ -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 <proc (){.closure, gcsafe.}> 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.<disamb>.<module>`,
# 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)

View File

@@ -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):

View File

@@ -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 = {}

View File

@@ -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:

View File

@@ -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)

View File

@@ -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)

View File

@@ -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:

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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!)

View File

@@ -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)

View File

@@ -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

View File

@@ -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:

View File

@@ -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)

View File

@@ -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]

View File

@@ -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..<n[paramsPos].len:
@@ -2431,7 +2431,7 @@ proc semCppMember(c: PContext; s: PSym; n: PNode) =
initializerCall.add val
inc j
if isInitializer:
c.graph.initializersPerType[typ.itemId] = initializerCall
c.graph.initializersPerType[typ.bindingId] = initializerCall
proc semMethodPrototype(c: PContext; s: PSym; n: PNode) =
if s.isGenericRoutine:

View File

@@ -1379,7 +1379,7 @@ proc liftParamType(c: PContext, procKind: TSymKind, genericParams: PNode,
for i in 0..<paramType.len - 1:
if paramType[i].kind == tyStatic:
var staticCopy = paramType[i].exactReplica(c.idgen)
var staticCopy = copyType(paramType[i], c.idgen, paramType[i].owner)
staticCopy.incl tfInferrableStatic
result.rawAddSon staticCopy
else:
@@ -2481,7 +2481,7 @@ proc semTypeNode(c: PContext, n: PNode, prev: PType): PType =
# bugfix: keep the fresh id for aliases to integral types:
if s.typ.kind notin {tyBool, tyChar, tyInt..tyInt64, tyFloat..tyFloat128,
tyUInt..tyUInt64}:
prev.itemId = s.typ.itemId
prev.bindingId = s.typ.bindingId
result = prev
of nkSym:
let s = getGenSym(c, n.sym)

View File

@@ -376,8 +376,8 @@ proc lookupTypeVar(cl: var TReplTypeVars, t: PType): PType =
result = cl.typeMap.lookup(t)
when defined(icDbgRefc):
if t.kind in {tyGenericParam, tyTypeDesc}:
echo "[icBind] lookup ", t.kind, " ", typeToString(t), " uid=", t.uniqueId.module, ".",
t.uniqueId.item, " itemId=", t.itemId.module, ".", t.itemId.item,
echo "[icBind] lookup ", t.kind, " ", typeToString(t), " itemId=", t.itemId.module, ".",
t.itemId.item, " bindingId=", t.bindingId.module, ".", t.bindingId.item,
" state=", t.state, " flags=", t.flags, " -> ",
(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..<result.kidsLen:
var r = result[i]
if r != nil:
@@ -755,7 +755,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
of tyGenericInst, tyUserTypeClassInst:
bailout()
result = instCopyType(cl, t)
cl.localCache[t.itemId] = result
cl.localCache[t.bindingId] = result
for i in FirstGenericParamAt..<result.kidsLen:
result[i] = replaceTypeVarsT(cl, result[i])
propagateToOwner(result, result.last)
@@ -770,7 +770,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
result = instCopyType(cl, t)
result.size = -1 # needs to be recomputed
#if not cl.allowMetaTypes:
cl.localCache[t.itemId] = result
cl.localCache[t.bindingId] = result
let propagateInstValue = isInstValue and isRefPtrObject(t)
for i, resulti in result.ikids:
@@ -819,7 +819,7 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
result = t
# Slow path, we have some work to do. CRUCIAL: only ever mutate a type that
# is LOCAL to the module we are instantiating in (`uniqueId.module ==
# is LOCAL to the module we are instantiating in (`itemId.module ==
# idgen.module`). A type loaded from another module's NIF (foreign) already
# had its object branches resolved when it was originally compiled; mutating
# it in place here is an old→new heap write that re-homes the loaded type to
@@ -828,11 +828,11 @@ proc replaceTypeVarsTAux(cl: var TReplTypeVars, t: PType, isInstValue = false):
# prior `state != Sealed` guard was insufficient: a freshly-LOADED type is
# `Complete`, not `Sealed` (`Sealed` only means "already re-written to a NIF").
if t.kind == tyRef and t.hasElementType and t.elementType.kind == tyObject and
t.elementType.n != nil and t.elementType.uniqueId.module == cl.c.idgen.module.int:
t.elementType.n != nil and t.elementType.itemId.module == cl.c.idgen.module.int:
discard replaceObjBranches(cl, t.elementType.n)
elif result.n != nil and t.kind == tyObject and result.state != Sealed and
result.uniqueId.module == cl.c.idgen.module.int:
result.itemId.module == cl.c.idgen.module.int:
# Invalidate the type size as we may alter its structure
result.size = -1
result.n = replaceObjBranches(cl, result.n)

View File

@@ -150,7 +150,7 @@ proc hashType(c: var MD5Context, t: PType; flags: set[ConsiderFlag]; conf: Confi
if hashDepth > 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

View File

@@ -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)

View File

@@ -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..<n.len:
result = result !& hashTree(n[i])

View File

@@ -172,9 +172,9 @@ proc backendTypeName(t: PType; conf: ConfigRef): string =
result = "`t"
result.addInt ord(t.kind)
result.add '.'
result.addInt t.uniqueId.item
result.addInt t.itemId.item
result.add '.'
result.add modname(t.uniqueId.module, conf)
result.add modname(t.itemId.module, conf)
result.add "@bk"
proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef) =
@@ -186,7 +186,7 @@ proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef
assert c.tl != nil
c.tl(t)
if t.uniqueId.isBackendMinted:
if t.itemId.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
@@ -335,9 +335,9 @@ proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef
# mutation that an assertion deeper in `treeKey` left unrestored would
# corrupt the type. `symKey` above already emitted the type's identity,
# so on a back-reference we simply stop.
if not containsOrIncl(c.visited, t.itemId):
if not containsOrIncl(c.visited, t.bindingId):
c.treeKey(t.nImpl, flags + {CoHashTypeInsideNode}, conf)
c.visited.excl t.itemId
c.visited.excl t.bindingId
else:
c.m.addIdent "´empty"
# Object inheritance is part of identity: key the base class too.

View File

@@ -90,30 +90,30 @@ proc collectVTableDispatchers*(g: ModuleGraph) =
sortBucket(g.methods[bucket].methods, relevantCols)
let base = g.methods[bucket].methods[^1]
let baseType = base.typ.firstParamType.skipTypes(skipPtrs-{tyTypeDesc})
if baseType.itemId in g.objectTree and not containGenerics(baseType, g.objectTree[baseType.itemId]):
let methodIndexLen = g.bucketTable[baseType.itemId]
if baseType.itemId notin itemTable: # once is enough
if baseType.bindingId in g.objectTree and not containGenerics(baseType, g.objectTree[baseType.bindingId]):
let methodIndexLen = g.bucketTable[baseType.bindingId]
if baseType.bindingId notin itemTable: # once is enough
rootTypeSeq.add baseType
itemTable[baseType.itemId] = newSeq[PSym](methodIndexLen)
itemTable[baseType.bindingId] = newSeq[PSym](methodIndexLen)
sort(g.objectTree[baseType.itemId], cmp = proc (x, y: tuple[depth: int, value: PType]): int =
sort(g.objectTree[baseType.bindingId], cmp = proc (x, y: tuple[depth: int, value: PType]): int =
if x.depth >= 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..<g.methods[bucket].methods.len:
let obj = g.methods[bucket].methods[idx].typ.firstParamType.skipTypes(skipPtrs)
itemTable[obj.itemId][mIndex] = g.methods[bucket].methods[idx]
itemTable[obj.bindingId][mIndex] = g.methods[bucket].methods[idx]
g.addDispatchers genVTableDispatcher(g, g.methods[bucket].methods, mIndex)
else: # if the base object doesn't have this method
g.addDispatchers genIfDispatcher(g, g.methods[bucket].methods, relevantCols, g.idgen)
@@ -128,40 +128,40 @@ proc sortVTableDispatchers*(g: ModuleGraph) =
sortBucket(g.methods[bucket].methods, relevantCols)
let base = g.methods[bucket].methods[^1]
let baseType = base.typ.firstParamType.skipTypes(skipPtrs-{tyTypeDesc})
if baseType.itemId in g.objectTree and not containGenerics(baseType, g.objectTree[baseType.itemId]):
let methodIndexLen = g.bucketTable[baseType.itemId]
if baseType.itemId notin itemTable: # once is enough
rootTypeSeq.add baseType.itemId
itemTable[baseType.itemId] = newSeq[PSym](methodIndexLen)
if baseType.bindingId in g.objectTree and not containGenerics(baseType, g.objectTree[baseType.bindingId]):
let methodIndexLen = g.bucketTable[baseType.bindingId]
if baseType.bindingId notin itemTable: # once is enough
rootTypeSeq.add baseType.bindingId
itemTable[baseType.bindingId] = newSeq[PSym](methodIndexLen)
sort(g.objectTree[baseType.itemId], cmp = proc (x, y: tuple[depth: int, value: PType]): int =
sort(g.objectTree[baseType.bindingId], cmp = proc (x, y: tuple[depth: int, value: PType]): int =
if x.depth >= 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..<g.methods[bucket].methods.len:
let obj = g.methods[bucket].methods[idx].typ.firstParamType.skipTypes(skipPtrs)
if obj.itemId notin itemTable:
itemTable[obj.itemId] = newSeq[PSym](methodIndexLen)
itemTable[obj.itemId][mIndex] = g.methods[bucket].methods[idx]
if obj.bindingId notin itemTable:
itemTable[obj.bindingId] = newSeq[PSym](methodIndexLen)
itemTable[obj.bindingId][mIndex] = g.methods[bucket].methods[idx]
for baseType in rootTypeSeq:
g.setMethodsPerType(baseType, itemTable[baseType])
for item in g.objectTree[baseType]:
let typ = item.value.skipTypes(skipPtrs)
let idx = typ.itemId
let idx = typ.bindingId
for mIndex in 0..<itemTable[idx].len:
if itemTable[idx][mIndex] == nil:
let parentIndex = typ.baseClass.skipTypes(skipPtrs).itemId
let parentIndex = typ.baseClass.skipTypes(skipPtrs).bindingId
itemTable[idx][mIndex] = itemTable[parentIndex][mIndex]
g.setMethodsPerType(idx, itemTable[idx])

View File

@@ -210,8 +210,10 @@ Edge cases (and why the machinery exists)
- **`nil` sons of loaded ASTs.** NIF dot-tokens load as `nil` where from-source
ASTs have `nkEmpty`; several passes gained `nil` guards.
- **Sealed loaded types.** Loaded types are `Sealed`; sem/transform mutate via
`unsealForTransform`/`exactReplica(idgen)` (the latter mints a fresh `uniqueId`
so serialized replicas don't collapse).
`unsealForTransform`/`copyType`, or -- where the copy must still answer to the
original in the generic binding tables -- `exactReplica(idgen)`, which gives the
copy its own `itemId` (so serialized replicas don't collapse) while inheriting
the original's `bindingId`.
- **Methods/RTTI ownership.** RTTI and type-bound hooks are emit-everywhere at
`cg` and deduplicated by the `merge` stage, like generic instances; the main
module's `cg` owns the whole-program method dispatchers.

View File

@@ -625,7 +625,7 @@ proc runIcTestFile(inp: string) =
const icSuite = ["thallo", "tconverter", "timp", "tmiscs", "tparseutils",
"tcompiletimeglobal", "tsighashstable", "tpureenum", "tgenericoffer",
"tconverterreexport", "ttypeoffer", "ttransitiveoffer",
"tmodsymref", "tmethupref", "temit", "ttraitparam"]
"tmodsymref", "tmethupref", "temit", "ttraitparam", "tnestasgn"]
proc icTest(args: string) =
temp("")

11
tests/ic/mnestasgn.nim Normal file
View File

@@ -0,0 +1,11 @@
# Helper for tnestasgn.nim: a `sink`-param routine containing a nested proc
# whose ENTIRE body is a single assignment, so the body node is a bare `nkAsgn`
# rather than an `nkStmtList` — the shape that used to be deferred behind a
# childless placeholder of that same kind.
proc consume*(s: sink string) =
var x = ""
proc setIt() =
x = s
setIt()
echo x

18
tests/ic/tnestasgn.nim Normal file
View File

@@ -0,0 +1,18 @@
discard """
output: '''hi'''
"""
# Regression test, minimized from a nimbus-eth2 `nim ic` crash by
# https://github.com/nim-lang/Nim/pull/26106 (the only one of that PR's eight
# repros that reproduces on its own base).
#
# A NIF-loaded routine's body is installed as a `nfLazyBody` placeholder. The
# placeholder used to carry the REAL body kind while holding no children, which
# breaks the compiler's most basic invariant — a node's kind implies its arity.
# `trees.getPotentialWrites` walks the outer routine because it has a `sink`
# parameter, reaches the nested proc's body under `of nkAsgn`, and reads
# `n[0]`/`n[1]` as every such reader is entitled to: "index out of bounds, the
# container is empty [IndexDefect]".
import mnestasgn
consume("hi")