mirror of
https://github.com/nim-lang/Nim.git
synced 2026-08-26 00:21:42 +00:00
refactoring: better IC + no unique Id (#26137)
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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!)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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.
|
||||
|
||||
2
koch.nim
2
koch.nim
@@ -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
11
tests/ic/mnestasgn.nim
Normal 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
18
tests/ic/tnestasgn.nim
Normal 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")
|
||||
Reference in New Issue
Block a user