IC: bugfixes

This commit is contained in:
Araq
2026-06-08 17:25:48 +02:00
parent da419ea8c0
commit fac1eefd99
4 changed files with 48 additions and 6 deletions

View File

@@ -284,7 +284,14 @@ proc writeTypeDef(w: var Writer; dest: var TokenBuf; typ: PType) =
proc writeType(w: var Writer; dest: var TokenBuf; typ: PType) =
if typ == nil:
dest.addDotToken()
elif typ.itemId.module == w.currentModule and typ.state == Complete:
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).
typ.state = Sealed
writeTypeDef(w, dest, typ)
else:

View File

@@ -290,9 +290,22 @@ proc setAttachedOp*(g: ModuleGraph; module: int; t: PType; op: TTypeAttachedOp;
# Use key-based deduplication for opsLog because different type objects
# (e.g. canon vs orig) can have different itemIds but same structural key
if key notin g.loadedOps[op]:
# Hooks should be written to the module where the type is defined,
# not the module that triggered the registration
let ownerModule = if t.sym != nil: t.sym.itemId.module.int else: module
# For a *nominal*, non-instantiated type the hook belongs to the module
# that defines the type (so it is emitted once there). But for a generic
# instance or a structural type (ref/ptr/seq/... over an instance) there is
# no honest definition site: the generic's home module is upstream of the
# type arguments and structurally blind to the instance, so it can never
# realize the hook. Such hooks can only be produced by the *instantiating*
# module — which is exactly the one running now (`module`). Stamping them
# with the type's def module produced a `LogEntry` that no module ever
# writes (the def module never instantiates it; the instantiating module's
# writer skips it because `op.module != thisModule`), so the hook was lost
# and codegen failed with "'=destroy' operator not found". Duplicate
# registrations across instantiating modules are reconciled deterministically
# at load time (see the HookEntry replay in `replayStateChanges`).
let nominal = t.sym != nil and t.kind in {tyObject, tyEnum, tyDistinct} and
tfFromGeneric notin t.flags
let ownerModule = if nominal: t.sym.itemId.module.int else: module
g.opsLog.add LogEntry(kind: HookEntry, op: op, module: ownerModule, key: key, sym: value)
g.loadedOps[op][key] = value
g.attachedOps[op][t.itemId] = value
@@ -691,7 +704,16 @@ when not defined(nimKochBootstrap):
for x in result.logOps:
case x.kind
of HookEntry:
g.loadedOps[x.op][x.key] = x.sym
# The same structural hook may be serialized by several instantiating
# modules (a generic/structural instance has no single def site, so each
# using module owns its copy). Pick one deterministic program-wide winner
# by the smaller owning-module name, so every lookup resolves to the same
# sym regardless of module load order.
let existing = g.loadedOps[x.op].getOrDefault(x.key)
if existing == nil or
cachedModuleSuffix(g.config, x.sym.itemId.module.FileIndex) <
cachedModuleSuffix(g.config, existing.itemId.module.FileIndex):
g.loadedOps[x.op][x.key] = x.sym
of ConverterEntry:
g.ifaces[fileIdx.int].converters.add x.sym
of MethodEntry:

View File

@@ -446,6 +446,12 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
header[i] = x
propagateToOwner(header, x)
else:
# Honor the same copy-before-mutate invariant as the branch above: never
# mutate the original invocation type `t` in place. Besides being cleaner,
# under IC `t` may be a loaded dep type (Sealed/immutable), and mutating it
# would assert. The flags propagated here end up on `header`, which is what
# is used downstream (`result.flags = header.flags`).
if header == t: header = instCopyType(cl, t)
propagateToOwner(header, x)
if header != t:

View File

@@ -147,7 +147,14 @@ proc typeKey(c: var Context; t: PType; flags: set[ConsiderFlag]; conf: ConfigRef
else:
symKey(c, t.symImpl, conf)
of tyGenericInst:
if sfInfixCall in t.sonsImpl[0].symImpl.flagsImpl:
# The generic head (son[0]) may be a lazily-loaded stub under IC; ensure it
# is materialised before peeking at its symbol. A nil sym means this is not
# an imported C++ generic, so fall through to the normal `skipModifierB`.
var base = t.sonsImpl[0]
if base.state == Partial:
assert c.tl != nil
c.tl(base)
if base.symImpl != nil and sfInfixCall in base.symImpl.flagsImpl:
# This is an imported C++ generic type.
# We cannot trust the `lastSon` to hold a properly populated and unique
# value for each instantiation, so we hash the generic parameters here: