mirror of
https://github.com/nim-lang/Nim.git
synced 2026-08-05 15:08:44 +00:00
IC: type-offers because why not, there is no end in sight
This commit is contained in:
@@ -609,6 +609,7 @@ var importTag = registerTag("import")
|
||||
var implTag = registerTag("implementation")
|
||||
var reexpModTag = registerTag("reexpmod")
|
||||
var offerTag = registerTag("offer")
|
||||
var typeOfferTag = registerTag("toffer")
|
||||
|
||||
proc registerNifAstTags*() =
|
||||
## (Re)registers ast2nif's NIF tags explicitly. The top-level `registerTag`
|
||||
@@ -640,6 +641,7 @@ proc registerNifAstTags*() =
|
||||
implTag = registerTag("implementation")
|
||||
reexpModTag = registerTag("reexpmod")
|
||||
offerTag = registerTag("offer")
|
||||
typeOfferTag = registerTag("toffer")
|
||||
|
||||
proc writeNode(w: var Writer; dest: var TokenBuf; n: PNode; forAst = false) =
|
||||
if n == nil:
|
||||
@@ -1256,6 +1258,7 @@ proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode;
|
||||
genericOffers: seq[tuple[generic, inst: PSym;
|
||||
concreteTypes: seq[PType];
|
||||
genericParamsCount: int]] = @[];
|
||||
typeOffers: seq[tuple[generic: PSym; inst: PType]] = @[];
|
||||
resolvedImportDeps: seq[FileIndex] = @[]) =
|
||||
var w = Writer(infos: LineInfoWriter(config: config), currentModule: thisModule)
|
||||
var content = createTokenBuf(300)
|
||||
@@ -1325,6 +1328,47 @@ proc writeNifModule*(config: ConfigRef; thisModule: int32; n: PNode;
|
||||
w.deps.addSymUse pool.syms.getOrIncl(typeToNifSym(ct, w.infos.config)), NoLineInfo
|
||||
w.deps.addParRi
|
||||
|
||||
# Generic TYPE-instance OFFERS: the `tyGenericInst` types this module created
|
||||
# (e.g. `HashArray[8192, Gwei]`). Non-IC keeps ONE such instance in the global
|
||||
# `typeInstCache`, so a structural bound computed at the first instantiation
|
||||
# site (e.g. an `array[…]` bound that depends on a `mixin`/`compiles()` whose
|
||||
# resolution differs by import scope) is frozen and reused everywhere. A
|
||||
# separate `nim m` process never repopulates `typeInstCache` from NIFs, so it
|
||||
# re-instantiates in its own scope and can compute a DIFFERENT bound (the SSZ
|
||||
# `dataPerChunk` divergence). The loader rebuilds `g.typeInstCache` from these
|
||||
# so `semtypinst.searchInstTypes` hits and reuses the baked instance.
|
||||
# Layout: (toffer <genericBodySym> <instType>).
|
||||
for off in typeOffers:
|
||||
# Carry the generic body sym and the instance type as STRING LITERALS, not
|
||||
# SymUse tokens: `addSymUse` rewrites a same-module reference into the NIF
|
||||
# "local form" (suffix stripped, resolved by the content loader against the
|
||||
# module being read), but this offer lives in the `deps` header and is read
|
||||
# by a CONSUMER with no such module context. The full names round-trip
|
||||
# verbatim as strings and `createTypeStub`/`resolveHookSym` resolve them
|
||||
# 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.addParRi
|
||||
|
||||
# OWNER MUST EMIT: a type reachable only through an offered instance — the
|
||||
# `concreteTypes` of an offered proc instance (e.g. chronicles `writeValue[T]`,
|
||||
# where `T` is this module's own object type) or an offered generic type
|
||||
# instance — may never be reached by the normal top-level serialization above.
|
||||
# If this module OWNS such a type, force-emit its typedef so that a consumer
|
||||
# which reuses the offer can resolve the cross-module SymUse to it. Without this
|
||||
# the consumer writes `t<k>.<i>.<thisSuffix>` and the loader asserts
|
||||
# `symbol has no offset`. `writeType` emits the def (and recurses into owned
|
||||
# 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:
|
||||
writeType(w, bottom, ct)
|
||||
for off in typeOffers:
|
||||
if off.inst != nil and off.inst.uniqueId.module == w.currentModule and
|
||||
off.inst.state == Complete:
|
||||
writeType(w, bottom, off.inst)
|
||||
|
||||
# the implTag is used to tell the loader that the
|
||||
# bottom of the file is the implementation of the module:
|
||||
content.addParLe implTag, NoLineInfo
|
||||
@@ -2268,6 +2312,11 @@ type
|
||||
## generic instances this module created; modulegraphs.nim rebuilds
|
||||
## `procInstCache` from them so a consumer reuses the instance instead of
|
||||
## re-instantiating it in its own (operator-blind) module scope.
|
||||
typeOffers*: seq[tuple[generic: PSym; inst: PType]]
|
||||
## generic TYPE instances this module created; modulegraphs.nim rebuilds
|
||||
## `typeInstCache` from them so a consumer reuses the baked instance
|
||||
## (e.g. a `mixin`/`compiles()`-dependent array bound) instead of
|
||||
## re-instantiating it with a different bound in its own scope.
|
||||
|
||||
proc loadImport(c: var DecodeContext; s: var Stream; deps: var seq[ModuleSuffix]; tok: var PackedToken) =
|
||||
tok = next(s) # skip `(import`
|
||||
@@ -2445,6 +2494,35 @@ proc processTopLevel(c: var DecodeContext; s: var Stream; flags: set[LoadFlag];
|
||||
t = next(s)
|
||||
if ok and genSym != nil and instSym != nil:
|
||||
result.genericOffers.add (genSym, instSym, cts, paramsCount)
|
||||
elif t.tagId == typeOfferTag:
|
||||
# (toffer "<genericBodySym>" "<instType>") — see the writer. The two
|
||||
# full names arrive as string literals; intern them and resolve to a
|
||||
# PSym/PType, then FULLY load the instance (its array bounds/fields) so
|
||||
# `searchInstTypes` can match its params (a Partial stub has empty kids).
|
||||
# Best-effort: a type that fails to resolve drops the offer.
|
||||
t = next(s) # skip (toffer
|
||||
var genName, instName = ""
|
||||
var idx = 0
|
||||
while t.kind != ParRi and t.kind != EofToken:
|
||||
if t.kind == StringLit:
|
||||
if idx == 0: genName = pool.strings[t.litId]
|
||||
elif idx == 1: instName = pool.strings[t.litId]
|
||||
inc idx
|
||||
t = next(s)
|
||||
if t.kind != ParRi:
|
||||
raiseAssert "expected ParRi in toffer entry of module " & suffix
|
||||
t = next(s)
|
||||
if genName.len > 0 and instName.len > 0:
|
||||
# Resolving/loading these entities lazily reads from the SAME stream we
|
||||
# are iterating, moving its cursor — save/restore around it (cf. the
|
||||
# export-list handling above).
|
||||
let saved = offset(s.r)
|
||||
let genSym = resolveHookSym(c, pool.syms.getOrIncl(genName))
|
||||
let inst = tryCreateTypeStub(c, pool.syms.getOrIncl(instName))
|
||||
if genSym != nil and inst != nil:
|
||||
loadType(c, inst)
|
||||
result.typeOffers.add (genSym, inst)
|
||||
s.r.jumpTo(saved)
|
||||
elif t.tagId == implTag:
|
||||
cont = false
|
||||
elif LoadFullAst in flags or t.tagId == letTag or t.tagId == varTag:
|
||||
|
||||
@@ -957,6 +957,22 @@ when not defined(nimKochBootstrap):
|
||||
if n.kind == nkReplayAction and n.len >= 1 and n[0].kind == nkStrLit and
|
||||
n[0].strVal in ["put", "inc", "add", "incl"]:
|
||||
g.transitiveReplayActions.add n
|
||||
# Rebuild generic TYPE- and PROC-instance offers across the WHOLE closure,
|
||||
# not just direct imports (`moduleFromNifFile`). An instance is frozen at
|
||||
# the FIRST module to create it (in a scope where its body's symbols
|
||||
# resolve unambiguously); a consumer many imports away must REUSE it rather
|
||||
# than re-instantiate in its own scope, which may resolve a body symbol
|
||||
# differently — a divergent `compiles()`-dependent array bound (SSZ
|
||||
# `HashArray[8192, Gwei]`, type offer), or an ambiguous unqualified ident
|
||||
# leaked from an unrelated import (`fromRaw` -> `SkRawPublicKeySize` from
|
||||
# both `secp` and `secp256k1`, proc offer). Direct-only rebuild left the
|
||||
# deep offer invisible when the clean instance lives a transitive hop away.
|
||||
for off in precomp.typeOffers:
|
||||
g.typeInstCache.mgetOrPut(off.generic.itemId, @[]).add off.inst
|
||||
for off in precomp.genericOffers:
|
||||
g.procInstCache.mgetOrPut(off.generic.itemId, @[]).add PInstantiation(
|
||||
sym: off.inst, concreteTypes: off.concreteTypes,
|
||||
genericParamsCount: off.genericParamsCount, compilesId: 0)
|
||||
for d in precomp.deps: stack.add d
|
||||
|
||||
proc materializeReexportedModule(g: ModuleGraph; mname, msuffix: string): PSym =
|
||||
@@ -1031,6 +1047,14 @@ when not defined(nimKochBootstrap):
|
||||
sym: off.inst, concreteTypes: off.concreteTypes,
|
||||
genericParamsCount: off.genericParamsCount, compilesId: 0)
|
||||
|
||||
# Rebuild `typeInstCache` from this module's generic TYPE-instance OFFERS so a
|
||||
# consumer's `searchInstTypes` reuses the baked instance (e.g. an SSZ
|
||||
# `HashArray` whose array bound depends on import-scope-sensitive `compiles()`)
|
||||
# rather than re-instantiating it with a divergent bound — see ast2nif's
|
||||
# `(toffer …)`. Keyed by the generic body sym's itemId, as `searchInstTypes`.
|
||||
for off in result.typeOffers:
|
||||
g.typeInstCache.mgetOrPut(off.generic.itemId, @[]).add off.inst
|
||||
|
||||
# Mark module as cached
|
||||
g.cachedMods.incl fileIdx.int
|
||||
g.hookClosure.incl fileIdx.int
|
||||
|
||||
@@ -29,7 +29,7 @@ const
|
||||
|
||||
nimEnableCovariance* = defined(nimEnableCovariance)
|
||||
|
||||
icFormatVersion* = "6"
|
||||
icFormatVersion* = "9"
|
||||
## 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`
|
||||
|
||||
@@ -289,6 +289,16 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
|
||||
if not hasNil:
|
||||
genericOffers.add (inst.sym.instantiatedFrom, inst.sym,
|
||||
inst.concreteTypes, inst.genericParamsCount)
|
||||
# Generic TYPE-instance OFFERS: every `tyGenericInst` THIS module created,
|
||||
# so a consumer reuses its baked structure (array bounds etc.) rather than
|
||||
# re-instantiating with a scope-divergent bound. See ast2nif.writeNifModule.
|
||||
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
|
||||
inst.kidsLen > 0 and inst[0] != nil and
|
||||
inst[0].kind == tyGenericBody and inst[0].sym != nil:
|
||||
typeOffers.add (inst[0].sym, inst)
|
||||
# The module's REAL resolved direct imports (incl. macro/template-generated
|
||||
# ones with no surviving syntactic node). Passed to writeNifModule so the
|
||||
# NIF `deps` section is complete (the backend closure walk needs it), and
|
||||
@@ -296,7 +306,7 @@ proc processPipelineModule*(graph: ModuleGraph; module: PSym; idgen: IdGenerator
|
||||
let resolvedImportDeps = graph.importDeps.getOrDefault(module.position.FileIndex, @[])
|
||||
writeNifModule(graph.config, module.position.int32, topLevelStmts, graph.opsLog,
|
||||
replayActions, implDeps, reexportedModuleSyms(graph, module),
|
||||
genericOffers, resolvedImportDeps)
|
||||
genericOffers, typeOffers, resolvedImportDeps)
|
||||
# The module's REAL direct imports (incl. macro-generated) for `nim ic`'s
|
||||
# graph re-derivation; see ast2nif.writeSemDeps / semdata.addImportFileDep.
|
||||
var semDepPaths: seq[string] = @[]
|
||||
|
||||
2
koch.nim
2
koch.nim
@@ -618,7 +618,7 @@ proc runIcTestFile(inp: string) =
|
||||
# which exercises the NIF import/load path the single-file tests do not.
|
||||
const icSuite = ["thallo", "tconverter", "timp", "tmiscs", "tparseutils",
|
||||
"tcompiletimeglobal", "tsighashstable", "tpureenum", "tgenericoffer",
|
||||
"tconverterreexport"]
|
||||
"tconverterreexport", "ttypeoffer", "ttransitiveoffer"]
|
||||
|
||||
proc icTest(args: string) =
|
||||
temp("")
|
||||
|
||||
Reference in New Issue
Block a user