mirror of
https://github.com/nim-lang/Nim.git
synced 2026-06-14 15:43:45 +00:00
DCE used properly: The big collapse
This commit is contained in:
@@ -92,14 +92,11 @@ proc sharedInstanceCName(m: BModule; s: PSym): string =
|
||||
(s.disamb and InstanceDisambBit) != 0'i32 and
|
||||
s.typ != nil and s.typ.callConv != ccInline and not m.hcrOn and
|
||||
{sfImportc, sfExportc, sfCodegenDecl} * s.flags == {}:
|
||||
let candidate = s.name.s.mangle & "_i" & $s.disamb
|
||||
let compat = typeToString(s.typ)
|
||||
let existing = m.g.graph.icSharedSigs.getOrDefault(candidate)
|
||||
if existing.len == 0:
|
||||
m.g.graph.icSharedSigs[candidate] = compat
|
||||
result = candidate
|
||||
elif existing == compat:
|
||||
result = candidate
|
||||
# The content-derived `disamb` is unique per process (collision-probed in
|
||||
# `setInstanceDisamb`), so the mint-site-independent `_i<disamb>` name is
|
||||
# safe to use directly; identical instances across modules collide on it
|
||||
# exactly and the merge stage keeps one.
|
||||
result = s.name.s.mangle & "_i" & $s.disamb
|
||||
|
||||
proc isSharedInstanceCName(m: BModule; s: PSym): bool =
|
||||
m.config.cmd == cmdNifC and s.kind in routineKinds and
|
||||
@@ -2030,12 +2027,6 @@ proc genTypeInfoV2(m: BModule; t: PType; info: TLineInfo): Rope =
|
||||
# definition into the owner module's *unwritten* backend module (discarded in
|
||||
# this process) and emit only an extern here, leaving the symbol undefined.
|
||||
let perModuleCg = m.config.cmd == cmdNifC and m.config.icBackendStage == "cg"
|
||||
if not perModuleCg and m.config.cmd == cmdNifC and result in m.g.graph.icCachedDataDefs:
|
||||
# already defined inside a reused TU from the previous run
|
||||
cgsym(m, "TNimTypeV2")
|
||||
declareNimType(m, "TNimTypeV2", result, owner)
|
||||
m.g.typeInfoMarkerV2[sig] = (str: result, owner: owner)
|
||||
return prefixTI(result)
|
||||
if not perModuleCg and owner != m.module.position and myModuleOpenForCodegen(m, FileIndex owner):
|
||||
# make sure the type info is created in the owner module
|
||||
discard genTypeInfoV2(m.g.mods[owner], origType, info)
|
||||
@@ -2124,16 +2115,6 @@ proc genTypeInfoV1(m: BModule; t: PType; info: TLineInfo): Rope =
|
||||
else:
|
||||
template dbgNti(branch: string) = discard
|
||||
|
||||
if m.config.cmd == cmdNifC and result in m.g.graph.icCachedDataDefs:
|
||||
dbgNti "extern:cachedDataDefs"
|
||||
# already defined inside a reused TU from the previous run
|
||||
cgsym(m, "TNimType")
|
||||
cgsym(m, "TNimNode")
|
||||
declareNimType(m, "TNimType", result, t.skipTypes(typedescPtrs).itemId.module)
|
||||
m.g.typeInfoMarker[sig] = (str: result,
|
||||
owner: t.skipTypes(typedescPtrs).itemId.module)
|
||||
return prefixTI(result)
|
||||
|
||||
let old = m.g.graph.emittedTypeInfo.getOrDefault($result)
|
||||
if old != FileIndex(0):
|
||||
dbgNti "extern:emittedTypeInfo"
|
||||
|
||||
@@ -93,15 +93,6 @@ proc findPendingModule(m: BModule, s: PSym): BModule =
|
||||
var ms = getModule(s)
|
||||
result = m.g.mods[ms.position]
|
||||
|
||||
proc isReusedTU(m: BModule): bool =
|
||||
## Whether this module's cached translation unit is reused — either as a
|
||||
## loaded backend module or purely at the file level (a module the
|
||||
## backend never loaded whose BModule demand-driven codegen created).
|
||||
m.config.cmd == cmdNifC and
|
||||
(m.module.position in m.g.graph.icReusedModules or
|
||||
(m.g.graph.icFileReusedCnames.len > 0 and
|
||||
getCFile(m).string in m.g.graph.icFileReusedCnames))
|
||||
|
||||
proc icNifName(m: BModule; s: PSym): string =
|
||||
## The serialized NIF name of `s`, recorded next to its C name in the cnif
|
||||
## artifact so a later run can re-demand the definition when a reused TU
|
||||
@@ -121,23 +112,6 @@ proc icNifName(m: BModule; t: PType): string =
|
||||
else:
|
||||
result = ""
|
||||
|
||||
proc redirectToLiveModule(m: BModule, q: BModule): BModule =
|
||||
## A module whose cached translation unit is reused never generates code,
|
||||
## so a definition that `findPendingModule` routes into it must be emitted
|
||||
## elsewhere: into the demanding module, or — when the demander is itself
|
||||
## reused (demands raised while wiring up a reused module's init call) —
|
||||
## into the main module, which is always regenerated.
|
||||
result = q
|
||||
if q != nil and m.config.cmd == cmdNifC and isReusedTU(q):
|
||||
if not isReusedTU(m):
|
||||
result = m
|
||||
else:
|
||||
result = nil
|
||||
for cand in m.g.mods:
|
||||
if cand != nil and sfMainModule in cand.module.flags:
|
||||
result = cand
|
||||
break
|
||||
if result == nil: result = m
|
||||
|
||||
proc emitsBodyInThisModule(m: BModule, prc: PSym): bool =
|
||||
## Per-module backend codegen is concerned with ONE module: it emits the
|
||||
@@ -1414,29 +1388,6 @@ proc genProcLvl3*(m: BModule, prc: PSym) =
|
||||
# Any demand before that point yields a prototype.
|
||||
genProcPrototype(m, prc)
|
||||
return
|
||||
# inline procs are emitted into every using TU; they are never shared
|
||||
# across translation units, so cached/cross-TU dedup must not touch
|
||||
# them. Dispatchers always (re)define in main, never from the cache.
|
||||
let key = stripCnifMarks(prc.loc.snippet)
|
||||
if (prc.typ == nil or prc.typ.callConv != ccInline) and
|
||||
sfDispatcher notin prc.flags:
|
||||
if key in m.g.graph.icCachedCDefs:
|
||||
# already defined inside a reused TU from the previous run
|
||||
genProcPrototype(m, prc)
|
||||
return
|
||||
# one definition program-wide: the first claimant's TU embeds it,
|
||||
# everyone else declares it. The claim records the TU as well: with
|
||||
# redirects the same symbol can be demanded into several TUs. Home
|
||||
# emissions must claim too — a hook's demand routing goes through the
|
||||
# type-owner module (`findPendingModule` walks `s.owner`) while its
|
||||
# eager emission uses the announcing module's TU; when the owner TU
|
||||
# is reused, the very same symbol reaches this point through both
|
||||
# paths and only the registry serializes them.
|
||||
let claim = (sym: prc.itemId, tu: m.module.position)
|
||||
if m.g.graph.icSharedDefOwner.hasKeyOrPut(key, claim) and
|
||||
m.g.graph.icSharedDefOwner[key] != claim:
|
||||
genProcPrototype(m, prc)
|
||||
return
|
||||
if prc.itemId.module != m.module.position and
|
||||
not isBackendMinted(prc.itemId) and
|
||||
(prc.typ == nil or prc.typ.callConv != ccInline) and
|
||||
@@ -1581,12 +1532,11 @@ proc genProcLvl3*(m: BModule, prc: PSym) =
|
||||
sfDispatcher notin prc.flags:
|
||||
# A unique program-wide definition: external linkage, so exactly one
|
||||
# translation unit may embed its body and everyone else declares it.
|
||||
# In the whole-program backend `icSharedDefOwner` (first claimant wins)
|
||||
# enforces this in process; in the per-module backend each module's `cg`
|
||||
# process emits the body (emit-everywhere), so this flag tells the merge
|
||||
# stage which definitions to assign a single owner and prototype in the
|
||||
# rest. The complement — inline procs and method dispatchers — is emitted
|
||||
# into every using TU (`static`/main-only) and must never be deduplicated.
|
||||
# Each module's `cg` process emits the body (emit-everywhere); this flag
|
||||
# tells the merge stage which definitions to assign a single owner and
|
||||
# prototype in the rest. The complement — inline procs and method
|
||||
# dispatchers — is emitted into every using TU (`static`/main-only) and
|
||||
# must never be deduplicated.
|
||||
defFlags.add 'u'
|
||||
if not hasCnifMarks(prc.loc.snippet):
|
||||
# The C name was not minted through `fillBackendName` (e.g. set by an
|
||||
@@ -1710,7 +1660,7 @@ proc genProcLvl2(m: BModule, prc: PSym) =
|
||||
genProcLvl3(m, prcCopy)
|
||||
else:
|
||||
let m2 = if m.config.symbolFiles != disabledSf: m
|
||||
else: redirectToLiveModule(m, findPendingModule(m, prc))
|
||||
else: findPendingModule(m, prc)
|
||||
fillProcLoc(m2, prc.ast[namePos])
|
||||
#elif {sfExportc, sfImportc} * prc.flags == {}:
|
||||
# # reset name to restore consistency in case of hashing collisions:
|
||||
@@ -1720,7 +1670,7 @@ proc genProcLvl2(m: BModule, prc: PSym) =
|
||||
genProcPrototype(m, prc)
|
||||
genProcLvl3(m, prc)
|
||||
elif sfImportc notin prc.flags:
|
||||
var q = redirectToLiveModule(m, findPendingModule(m, prc))
|
||||
var q = findPendingModule(m, prc)
|
||||
fillProcLoc(q, prc.ast[namePos])
|
||||
# generate a getProc call to initialize the pointer for this
|
||||
# externally-to-the-current-module defined proc, also important
|
||||
@@ -1750,23 +1700,8 @@ proc requestConstImpl(p: BProc, sym: PSym) =
|
||||
if genConstSetup(p, sym):
|
||||
let m = p.module
|
||||
# declare implementation:
|
||||
var q = findPendingModule(m, sym)
|
||||
var defineIt = true
|
||||
if m.config.cmd == cmdNifC:
|
||||
if stripCnifMarks(sym.loc.snippet) in m.g.graph.icCachedDataDefs:
|
||||
# already defined inside a reused TU from the previous run
|
||||
defineIt = false
|
||||
else:
|
||||
let q2 = redirectToLiveModule(m, q)
|
||||
if q2 != q:
|
||||
# redirected definition: one TU program-wide embeds it
|
||||
q = q2
|
||||
let key = stripCnifMarks(sym.loc.snippet)
|
||||
let claim = (sym: sym.itemId, tu: q2.module.position)
|
||||
if m.g.graph.icSharedDefOwner.hasKeyOrPut(key, claim) and
|
||||
m.g.graph.icSharedDefOwner[key] != claim:
|
||||
defineIt = false
|
||||
if defineIt and q != nil and not containsOrIncl(q.declaredThings, sym.id):
|
||||
let q = findPendingModule(m, sym)
|
||||
if q != nil and not containsOrIncl(q.declaredThings, sym.id):
|
||||
assert q.initProc.module == q
|
||||
genConstDefinition(q, p, sym)
|
||||
# declare header:
|
||||
@@ -1802,17 +1737,6 @@ proc requestProcDef*(m: BModule, prc: PSym) =
|
||||
## code had referenced it.
|
||||
genProc(m, prc)
|
||||
|
||||
proc requestAnyDef*(m: BModule, sym: PSym) =
|
||||
## Demand entry for the def-retention check: a definition that lived in
|
||||
## this TU in the previous run and that a reused TU still references must
|
||||
## keep being emitted. Routines and constants can be re-demanded; symbols
|
||||
## of any other kind never get here (the check un-reuses the referencing
|
||||
## TU instead).
|
||||
case sym.kind
|
||||
of routineKinds: genProc(m, sym)
|
||||
of skConst: requestConstImpl(m.initProc, sym)
|
||||
else: discard
|
||||
|
||||
proc genVarPrototype(m: BModule, n: PNode) =
|
||||
#assert(sfGlobal in sym.flags)
|
||||
let sym = n.sym
|
||||
@@ -2269,25 +2193,6 @@ proc registerModuleToMain(g: BModuleList; m: BModule) =
|
||||
else:
|
||||
g.otherModsInit.addCallStmt(init)
|
||||
|
||||
proc registerReusedInit*(g: BModuleList; moduleBase: string;
|
||||
initRequired, datInitRequired: bool) =
|
||||
## init/datInit registration for a translation unit that is reused purely
|
||||
## from its cached files (the module is not even loaded); the names are
|
||||
## reconstructed from the module's mangled base name recorded in the
|
||||
## artifact's meta head.
|
||||
if datInitRequired:
|
||||
let datInit = moduleBase & "DatInit000"
|
||||
g.mainModProcs.addDeclWithVisibility(Private):
|
||||
g.mainModProcs.addProcHeader(ccNimCall, datInit, CVoid, cProcParams())
|
||||
g.mainModProcs.finishProcHeaderAsProto()
|
||||
g.mainDatInit.addCallStmt(datInit)
|
||||
if initRequired:
|
||||
let init = moduleBase & "Init000"
|
||||
g.mainModProcs.addDeclWithVisibility(Private):
|
||||
g.mainModProcs.addProcHeader(ccNimCall, init, CVoid, cProcParams())
|
||||
g.mainModProcs.finishProcHeaderAsProto()
|
||||
g.otherModsInit.addCallStmt(init)
|
||||
|
||||
proc registerReusedModuleToMain*(g: BModuleList; m: BModule;
|
||||
initRequired, datInitRequired: bool) =
|
||||
## `registerModuleToMain` for a module whose cached translation unit is
|
||||
@@ -2945,32 +2850,12 @@ proc genForwardedProcs(g: BModuleList) =
|
||||
# Note: ``genProcLvl2`` may add to ``forwardedProcs``
|
||||
while g.forwardedProcs.len > 0:
|
||||
let prc = g.forwardedProcs.pop()
|
||||
var m = g.mods[prc.itemId.module]
|
||||
if isReusedTU(m):
|
||||
# the home TU is reused; emit through the main module instead
|
||||
for cand in g.mods:
|
||||
if cand != nil and sfMainModule in cand.module.flags:
|
||||
m = cand
|
||||
break
|
||||
let m = g.mods[prc.itemId.module]
|
||||
if sfForward in prc.flags:
|
||||
internalError(m.config, prc.info, "still forwarded: " & prc.name.s)
|
||||
|
||||
genProcLvl2(m, prc)
|
||||
|
||||
proc reuseCachedModule(g: BModuleList; m: BModule) =
|
||||
## The module's cached `.c`/`.o`/artifact are reused: register the cached
|
||||
## object file for the link, keep the cached artifact in the liveness
|
||||
## inputs and replay the module's init registration from the artifact's
|
||||
## meta head.
|
||||
let cfile = getCFile(m)
|
||||
var cf = Cfile(nimname: m.module.name.s, cname: cfile,
|
||||
obj: completeCfilePath(m.config, toObjFile(m.config, cfile)),
|
||||
flags: {CfileFlag.Cached})
|
||||
addFileToCompile(m.config, cf)
|
||||
g.graph.icCnifFiles.add cfile.string & ".nif"
|
||||
let meta = g.graph.icReusedMeta.getOrDefault(m.module.position)
|
||||
registerReusedModuleToMain(g, m, meta.initRequired, meta.datInitRequired)
|
||||
|
||||
proc cgenWriteModules*(backend: RootRef, config: ConfigRef) =
|
||||
let g = BModuleList(backend)
|
||||
g.config = config
|
||||
@@ -2980,18 +2865,6 @@ proc cgenWriteModules*(backend: RootRef, config: ConfigRef) =
|
||||
# order anyway)
|
||||
genForwardedProcs(g)
|
||||
|
||||
# translation units reused purely from cached files (modules the backend
|
||||
# never loaded): link their objects, keep their artifacts in the liveness
|
||||
# inputs, replay their init registration. NB: these TUs rarely have init
|
||||
# code (they were demand-only in the producing run as well).
|
||||
for fr in g.graph.icFileReused:
|
||||
var cf = Cfile(nimname: splitFile(fr.cname).name, cname: AbsoluteFile fr.cname,
|
||||
obj: completeCfilePath(config, toObjFile(config, AbsoluteFile fr.cname)),
|
||||
flags: {CfileFlag.Cached})
|
||||
addFileToCompile(config, cf)
|
||||
g.graph.icCnifFiles.add fr.cname & ".nif"
|
||||
registerReusedInit(g, fr.moduleBase, fr.initRequired, fr.datInitRequired)
|
||||
|
||||
if config.cmd == cmdNifC and not isDefined(config, "icNoCDce"):
|
||||
# Two-phase write: produce every module's marked text and artifact
|
||||
# first, then compute global liveness over the artifacts and render
|
||||
@@ -3002,11 +2875,6 @@ proc cgenWriteModules*(backend: RootRef, config: ConfigRef) =
|
||||
var cfs: seq[Cfile] = @[]
|
||||
var codes: seq[string] = @[]
|
||||
for m in cgenModules(g):
|
||||
if m.module.position in g.graph.icReusedModules:
|
||||
reuseCachedModule(g, m)
|
||||
continue
|
||||
if isReusedTU(m):
|
||||
continue # file-level reused: registered before this loop already
|
||||
let cfile = getCFile(m)
|
||||
var cf = Cfile(nimname: m.module.name.s, cname: cfile,
|
||||
obj: completeCfilePath(m.config, toObjFile(m.config, cfile)), flags: {})
|
||||
@@ -3026,11 +2894,6 @@ proc cgenWriteModules*(backend: RootRef, config: ConfigRef) =
|
||||
g.graph.icCDropped = dropped
|
||||
else:
|
||||
for m in cgenModules(g):
|
||||
if config.cmd == cmdNifC and m.module.position in g.graph.icReusedModules:
|
||||
reuseCachedModule(g, m)
|
||||
elif isReusedTU(m):
|
||||
discard # file-level reused: registered before this loop already
|
||||
else:
|
||||
m.writeModule()
|
||||
m.writeModule()
|
||||
writeMapping(config, g.mapping)
|
||||
if g.generatedHeader != nil: writeHeader(g.generatedHeader)
|
||||
|
||||
@@ -486,8 +486,7 @@ proc computeMergeDecision*(files: openArray[string]): MergeDecision =
|
||||
## Each `cg` process emits the body of every definition it demands
|
||||
## (emit-everywhere), so the same externally-linked definition appears in
|
||||
## several artifacts. A `'u'` flag on the `(cdef ...)` marks those that need
|
||||
## exactly one owner (the whole-program backend's `icSharedDefOwner`
|
||||
## invariant, here recomputed across processes); the owner is the
|
||||
## exactly one owner, assigned here across processes: the owner is the
|
||||
## lexicographically smallest artifact that emits it — a pure function of the
|
||||
## claimant set, hence stable across rebuilds. Definitions without `'u'`
|
||||
## (inline procs, dispatchers) are `static`/main-only and emitted into every
|
||||
|
||||
@@ -78,37 +78,9 @@ type
|
||||
# `setInstanceDisamb`
|
||||
icCnifFiles*: seq[string] # `.c.nif` artifacts written by this run
|
||||
icCDefs*, icCLiveDefs*, icCDropped*: int # render-time DCE stats
|
||||
icSharedSigs*: Table[string, string] # shared instance C name -> signature
|
||||
# (collision guard for the 30-bit hash)
|
||||
icSharedDefOwner*: Table[string, tuple[sym: ItemId, tu: int]]
|
||||
# shared instance C name -> the symbol and
|
||||
# the TU (module position) embedding the
|
||||
# single program-wide definition
|
||||
icReusedModules*: IntSet # module positions whose cached `.c`/`.o`
|
||||
# is reused: codegen is skipped for them
|
||||
icCachedCDefs*: HashSet[string] # C names of proc definitions inside
|
||||
# reused TUs (from their artifacts' cdef heads)
|
||||
icCachedDataDefs*: HashSet[string] # C names of data definitions (consts,
|
||||
# globals, RTTI) inside reused TUs
|
||||
icReusedMeta*: Table[int, tuple[initRequired, datInitRequired: bool]]
|
||||
icFileReused*: seq[tuple[cname, moduleBase: string;
|
||||
initRequired, datInitRequired: bool]]
|
||||
# TUs reused purely from cached files: modules the backend never
|
||||
# loads (reached only through system or demand-driven codegen)
|
||||
icFileReusedCnames*: HashSet[string] # their .c paths, so demand-created
|
||||
# BModules for them never write anything
|
||||
pendingMethodReplays*: seq[PSym] # method registrations loaded under
|
||||
# `nim nifc`, bucketed only after every
|
||||
# module is loaded (`flushMethodReplays`)
|
||||
icPreserveDefs*: Table[int, seq[PSym]] # module position -> symbols whose
|
||||
# definitions the TU must keep emitting:
|
||||
# they were in its previous artifact and a
|
||||
# reused TU still references them (the
|
||||
# backend def-migration check)
|
||||
icPreserveTypeInfos*: Table[int, seq[PType]] # the same for RTTI data
|
||||
# definitions, which are type-driven: the
|
||||
# type whose `genTypeInfo` the TU must
|
||||
# re-demand
|
||||
icImplDeps*: IntSet # NeedsImpl edge tracking under `nim m`:
|
||||
# module ids (FileIndex) whose routine BODIES
|
||||
# this compilation consumed at compile time.
|
||||
@@ -826,10 +798,7 @@ proc getModule*(g: ModuleGraph; fileIdx: FileIndex): PSym =
|
||||
result = nil
|
||||
|
||||
proc moduleOpenForCodegen*(g: ModuleGraph; m: FileIndex): bool {.inline.} =
|
||||
## A module whose cached translation unit is reused does not accept new
|
||||
## definitions: anything that would be emitted into it must be emitted
|
||||
## into the demanding module instead.
|
||||
result = m.int notin g.icReusedModules
|
||||
result = true
|
||||
|
||||
proc recordIcImplDep*(g: ModuleGraph; s: PSym) =
|
||||
## NeedsImpl edge tracking, see `icImplDeps`. Called from the compile-time
|
||||
|
||||
@@ -86,338 +86,6 @@ proc setupNifBackendModule(g: ModuleGraph; module: PSym): BModule =
|
||||
g.backend = cgendata.newModuleList(g)
|
||||
result = cgen.newModule(BModuleList(g.backend), module, g.config, idGeneratorForBackend(module))
|
||||
|
||||
proc enforceDefRetention(g: ModuleGraph; mainPos: int;
|
||||
reusedHeads: var Table[int, CnifHeads];
|
||||
fileCandidates: var seq[tuple[cname: string, heads: CnifHeads]];
|
||||
staleArtifacts: seq[string];
|
||||
loadedArtifacts: seq[tuple[pos: int, artifact: string]];
|
||||
icDebug: bool) =
|
||||
## The backend def-migration check ("previously defined, still referenced").
|
||||
##
|
||||
## A definition can live in a TU other than its symbol's home module:
|
||||
## redirected defs, shared `_i<hash>` instances, demand-generated hooks.
|
||||
## When that TU regenerates and the demand chain that put the definition
|
||||
## there does not re-arise (its demanders sit in reused TUs now), the
|
||||
## definition vanishes while the reused TUs still call it — a guaranteed
|
||||
## link error that only a cold rebuild heals.
|
||||
##
|
||||
## For every definition in a regenerating TU's *previous* artifact that
|
||||
## some reused TU references (its `cref` head) and that no reused TU
|
||||
## defines, the recorded NIF name is resolved against the current sem
|
||||
## state and the symbol is re-demanded into the TU the definition lived
|
||||
## in (`icPreserveDefs`, consumed by `generateCodeForModule`). When the
|
||||
## symbol no longer exists — only possible with an incoherent cache,
|
||||
## e.g. mixed compiler generations or renamed RTTI — the referencing TUs
|
||||
## lose their reuse instead and regenerate; their old definitions then
|
||||
## become sources for the same check, hence the fixpoint loop.
|
||||
if mainPos < 0: return
|
||||
# Sources: the previous artifacts of every TU that regenerates this run.
|
||||
# `ownTU` marks sources whose module runs full codegen this run (loaded
|
||||
# backend modules): only those re-emit their global variables and their
|
||||
# live top-level listing by themselves. Definitions in stale unclaimed
|
||||
# artifacts have no TU of their own; the main module (always regenerated)
|
||||
# hosts their re-demands.
|
||||
var sources: seq[tuple[target: int, ownTU: bool, heads: CnifHeads]] = @[]
|
||||
for la in loadedArtifacts:
|
||||
if la.pos notin g.icReusedModules:
|
||||
let h = readCnifHeads(la.artifact)
|
||||
if h.valid: sources.add (la.pos, true, h)
|
||||
for a in staleArtifacts:
|
||||
let h = readCnifHeads(a)
|
||||
if h.valid: sources.add (mainPos, false, h)
|
||||
if sources.len == 0: return
|
||||
|
||||
while true:
|
||||
# what the surviving reused TUs define and reference
|
||||
var cachedDefs = initHashSet[string]()
|
||||
template addDefs(h: CnifHeads) =
|
||||
for d in h.cdefs: cachedDefs.incl d.cname
|
||||
for d in h.cdata: cachedDefs.incl d.cname
|
||||
for h in reusedHeads.values: addDefs(h)
|
||||
for fc in fileCandidates: addDefs(fc.heads)
|
||||
# referencing TUs per name: loaded modules by position, file-level
|
||||
# TUs by -(index+1)
|
||||
var refdBy = initTable[string, seq[int]]()
|
||||
for pos, h in reusedHeads.pairs:
|
||||
for r in h.crefs: refdBy.mgetOrPut(r, @[]).add pos
|
||||
for i, fc in fileCandidates.pairs:
|
||||
for r in fc.heads.crefs: refdBy.mgetOrPut(r, @[]).add -(i+1)
|
||||
|
||||
# modules whose TU runs full codegen this run: definitions they still
|
||||
# own are re-emitted with them (globals through the serialized var
|
||||
# sections unconditionally, live routines through the top-level listing)
|
||||
var regenSuffixes = initHashSet[string]()
|
||||
for src in sources.items:
|
||||
if src.ownTU:
|
||||
let base = extractFilename(src.heads.semmedNif)
|
||||
if base.len > 4: regenSuffixes.incl base[0..^5] # strip ".nif"
|
||||
|
||||
# Every definition of a regenerating TU's previous artifact must stay
|
||||
# defined ("previously defined => still defined"): references to it can
|
||||
# be invisible (compilerproc/exportc names are not cnif-marked), so the
|
||||
# preserve decision must NOT depend on the cref index — that one only
|
||||
# targets the un-reuse fallback. Demand-side dedup (`declaredThings`,
|
||||
# the cached/claim shortcuts) makes redundant re-demands cheap.
|
||||
clear g.icPreserveDefs
|
||||
clear g.icPreserveTypeInfos
|
||||
var unreuse = initHashSet[int]()
|
||||
for src in sources.items:
|
||||
template check(defseq) =
|
||||
for d in defseq:
|
||||
if d.cname notin cachedDefs:
|
||||
if d.nifname.startsWith("`t"):
|
||||
# an RTTI definition: re-demand is type-driven (`genTypeInfo`),
|
||||
# there is no symbol to resolve
|
||||
let typ = resolveGlobalType(ast.program, d.nifname)
|
||||
if typ != nil:
|
||||
g.icPreserveTypeInfos.mgetOrPut(src.target, @[]).add typ
|
||||
if icDebug:
|
||||
stderr.writeLine "[icRetain] preserve typeinfo " & d.cname
|
||||
continue
|
||||
elif d.cname in refdBy:
|
||||
# type vanished: un-reuse the TUs that still reference it
|
||||
for tu in refdBy[d.cname]: unreuse.incl tu
|
||||
if icDebug:
|
||||
stderr.writeLine "[icRetain] cannot re-demand typeinfo " &
|
||||
d.cname & "; un-reusing referencing TUs"
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
var sym: PSym = nil
|
||||
if d.nifname.len > 0:
|
||||
sym = resolveGlobalSym(ast.program, d.nifname)
|
||||
# NB: reading `sym.kind` forces the lazy stub, so the kind is real
|
||||
var action = 0 # un-reuse any visibly referencing TUs
|
||||
if sym != nil:
|
||||
if sfDispatcher in sym.flags:
|
||||
# method dispatchers are synthesized from the whole
|
||||
# program's method set and emitted into the main TU on
|
||||
# every run; re-demanding the serialized sym would emit
|
||||
# its (empty) serialized body
|
||||
action = 1
|
||||
elif sym.kind in routineKinds or sym.kind == skConst:
|
||||
# routine and const definitions exist only by demand (the
|
||||
# serialized top level holds just the eager init statements,
|
||||
# not a proc listing!), and reused TUs never demand — so
|
||||
# every definition of the previous artifact whose symbol
|
||||
# still exists is re-demanded explicitly
|
||||
action = 2
|
||||
elif sym.kind in {skVar, skLet} and
|
||||
parseSymName(d.nifname).module in regenSuffixes:
|
||||
action = 1 # globals are re-emitted with their module's
|
||||
# eager top-level statements
|
||||
case action
|
||||
of 1: discard
|
||||
of 2:
|
||||
g.icPreserveDefs.mgetOrPut(src.target, @[]).add sym
|
||||
if icDebug:
|
||||
stderr.writeLine "[icRetain] preserve " & d.cname
|
||||
else:
|
||||
if d.cname in refdBy:
|
||||
for tu in refdBy[d.cname]: unreuse.incl tu
|
||||
if icDebug:
|
||||
stderr.writeLine "[icRetain] cannot re-demand " & d.cname &
|
||||
" (nif: " & d.nifname & "); un-reusing referencing TUs"
|
||||
# no visible references and no symbol to re-demand: a deleted
|
||||
# definition; invisible (unmarked) references would stem from
|
||||
# exportc-style names whose callers re-sem on deletion anyway
|
||||
check(src.heads.cdefs)
|
||||
check(src.heads.cdata)
|
||||
if unreuse.len == 0: break
|
||||
# un-reused TUs regenerate; their previous definitions become sources
|
||||
# for the next round
|
||||
var dropFile: seq[int] = @[]
|
||||
for tu in unreuse.items:
|
||||
if tu >= 0:
|
||||
if tu in reusedHeads:
|
||||
g.icReusedModules.excl tu
|
||||
g.icReusedMeta.del tu
|
||||
sources.add (tu, true, reusedHeads[tu])
|
||||
reusedHeads.del tu
|
||||
else:
|
||||
dropFile.add -(tu+1)
|
||||
sort dropFile
|
||||
for j in countdown(dropFile.high, 0):
|
||||
# an un-reused file-level TU has no backend module: only demand-driven
|
||||
# codegen recreates its content, so its definitions are re-demanded
|
||||
# through the main module (ownTU = false)
|
||||
sources.add (mainPos, false, fileCandidates[dropFile[j]].heads)
|
||||
fileCandidates.delete dropFile[j]
|
||||
|
||||
proc computeModuleReuse(g: ModuleGraph; modules: seq[PrecompiledModule];
|
||||
precompSys: PrecompiledModule;
|
||||
nifDeps: Table[string, seq[string]]) =
|
||||
## Decides which modules' cached translation units can be reused: codegen
|
||||
## is skipped for them and their `.c`/`.o`/artifact files are used as is.
|
||||
##
|
||||
## A module is reusable when the newest semmed NIF in its transitive
|
||||
## import closure is older than its `.c.nif` artifact — so neither the
|
||||
## module itself nor anything that can influence its generated C (type
|
||||
## layouts of dependencies in particular) has changed — and the cached
|
||||
## artifact, `.c` and `.o` files are all present. The main module is
|
||||
## always regenerated: it carries NimMain's init-call list and the method
|
||||
## dispatchers, which depend on the whole program.
|
||||
##
|
||||
## A regenerated module may still demand entities that live in a reused
|
||||
## TU: definitions already inside the cached TU become prototypes (see
|
||||
## `genProcLvl3`/`genTypeInfo*` and the artifact's cdef/cdata heads),
|
||||
## fresh demands are redirected into the demanding TU
|
||||
## (`redirectToLiveModule`).
|
||||
if not g.icDceEnabled or isDefined(g.config, "icNoReuse") or
|
||||
g.config.hcrOn or g.config.symbolFiles != disabledSf:
|
||||
return
|
||||
let icDebug = isDefined(g.config, "icTimings")
|
||||
# newest mtime in every NIF file's transitive import closure, via
|
||||
# fixpoint iteration (the import graph can contain cycles). The implicit
|
||||
# system import is not part of the NIF import lists, so system counts as
|
||||
# a dependency of every module.
|
||||
let systemNif = toNifFilename(g.config, g.config.m.systemFileIdx)
|
||||
var maxTime = initTable[string, Time]()
|
||||
for f in nifDeps.keys:
|
||||
maxTime[f] = getLastModificationTime(f)
|
||||
var changed = true
|
||||
while changed:
|
||||
changed = false
|
||||
for f, deps in nifDeps:
|
||||
var newest = maxTime[f]
|
||||
if systemNif in maxTime and maxTime[systemNif] > newest and f != systemNif:
|
||||
newest = maxTime[systemNif]
|
||||
for d in deps:
|
||||
if d in maxTime and maxTime[d] > newest: newest = maxTime[d]
|
||||
if newest > maxTime[f]:
|
||||
maxTime[f] = newest
|
||||
changed = true
|
||||
|
||||
# Fine-grained reuse gate, mirroring the m-step's cookie gating: a TU's
|
||||
# generated C depends on its own semmed NIF, on every direct import's
|
||||
# *interface* (type layouts, signatures, const values, inline-semantics
|
||||
# bodies — all hashed into the `.iface.nif` cookie, whose hash chains the
|
||||
# direct deps' cookies and is therefore transitively sensitive), on the
|
||||
# implicit system import, and on the *implementations* of the modules
|
||||
# whose routine bodies the TU physically embeds (redirected defs, shared
|
||||
# instances, hooks — recorded in the artifact's cdeps head, gated on
|
||||
# `.impl.nif`). The cookie sidecars are written OnlyIfChanged by the
|
||||
# m-step, so a body-only edit in a leaf module leaves every dependent's
|
||||
# iface input untouched and only the edited module's TU regenerates.
|
||||
# When a sidecar is missing (`-d:icNoIfaceGate` m-runs, foreign caches)
|
||||
# the gate falls back to the transitive NIF-mtime closure; `-d:icCoarseReuse`
|
||||
# forces that fallback.
|
||||
let coarseReuse = isDefined(g.config, "icCoarseReuse")
|
||||
var inputTimes = initTable[string, Time]()
|
||||
proc sidecarOf(nifFile, kind: string): string =
|
||||
nifFile[0..^5] & "." & kind & ".nif" # "<dir>/<suffix>.nif" -> "<dir>/<suffix>.<kind>.nif"
|
||||
proc staleVsArtifact(nifFile: string; artTime: Time; heads: CnifHeads): string =
|
||||
## Empty when every input is older than the artifact, else the reason.
|
||||
if nifFile notin inputTimes:
|
||||
inputTimes[nifFile] = getLastModificationTime(nifFile)
|
||||
if inputTimes[nifFile] > artTime:
|
||||
return "semmed NIF newer than artifact"
|
||||
var missingSidecar = coarseReuse
|
||||
if not missingSidecar:
|
||||
block fine:
|
||||
var inputs = @[sidecarOf(systemNif, "iface")]
|
||||
for dep in nifDeps.getOrDefault(nifFile):
|
||||
inputs.add sidecarOf(dep, "iface")
|
||||
for s in heads.cdeps:
|
||||
inputs.add getNimcacheDir(g.config).string / s & ".impl.nif"
|
||||
for inp in inputs:
|
||||
if inp notin inputTimes:
|
||||
if fileExists(inp):
|
||||
inputTimes[inp] = getLastModificationTime(inp)
|
||||
else:
|
||||
missingSidecar = true
|
||||
break fine
|
||||
if inputTimes[inp] > artTime:
|
||||
return "cookie newer than artifact: " & inp
|
||||
return ""
|
||||
if maxTime.getOrDefault(nifFile, artTime) > artTime:
|
||||
return "dependency closure newer than artifact (coarse)"
|
||||
return ""
|
||||
|
||||
let bl = BModuleList(g.backend)
|
||||
var handledArtifacts = initHashSet[string]()
|
||||
var loadedArtifacts: seq[tuple[pos: int, artifact: string]] = @[]
|
||||
var reusedHeads = initTable[int, CnifHeads]() # loaded reused modules
|
||||
var mainPos = -1
|
||||
for i in 0..modules.len:
|
||||
let pm = if i < modules.len: modules[i] else: precompSys
|
||||
if pm.module == nil: continue
|
||||
let pos = pm.module.position
|
||||
let bmod = bl.mods[pos]
|
||||
if bmod == nil: continue
|
||||
let artifact = getCFile(bmod).string & ".nif"
|
||||
# claimed by a loaded module — regenerated or reused, but never
|
||||
# eligible for the file-level reuse below
|
||||
handledArtifacts.incl artifact
|
||||
loadedArtifacts.add (pos, artifact)
|
||||
if sfMainModule in pm.module.flags:
|
||||
mainPos = pos
|
||||
continue
|
||||
let nifFile = toNifFilename(g.config, FileIndex pos)
|
||||
template reject(reason: string) =
|
||||
if icDebug:
|
||||
stderr.writeLine "[icReuse] regen " & cachedModuleSuffix(g.config, FileIndex pos) &
|
||||
": " & reason
|
||||
continue
|
||||
if nifFile notin maxTime: reject("not in dce closure: " & nifFile)
|
||||
let cfile = getCFile(bmod)
|
||||
let obj = completeCfilePath(g.config, toObjFile(g.config, cfile))
|
||||
if not fileExists(artifact): reject("no artifact " & artifact)
|
||||
if not fileExists(cfile.string): reject("no C file")
|
||||
if not fileExists(obj.string): reject("no object file")
|
||||
let heads = readCnifHeads(artifact)
|
||||
if not heads.valid: reject("artifact has no meta head")
|
||||
let staleReason = staleVsArtifact(nifFile, getLastModificationTime(artifact), heads)
|
||||
if staleReason.len > 0: reject(staleReason)
|
||||
g.icReusedModules.incl pos
|
||||
g.icReusedMeta[pos] = (heads.initRequired, heads.datInitRequired)
|
||||
reusedHeads[pos] = heads
|
||||
|
||||
# Translation units of modules the backend module list does not even
|
||||
# contain (reached only through system's imports or demand-driven
|
||||
# codegen): their artifacts are self-describing, so they can be reused
|
||||
# purely at the file level. When one of them is stale, its import
|
||||
# closure is stale too, so every TU that could reference it regenerates
|
||||
# and demand recreates the definitions.
|
||||
var fileCandidates: seq[tuple[cname: string, heads: CnifHeads]] = @[]
|
||||
var staleArtifacts: seq[string] = @[]
|
||||
for artifact in walkFiles(getNimcacheDir(g.config).string / "*.c.nif"):
|
||||
if artifact in handledArtifacts: continue
|
||||
let heads = readCnifHeads(artifact)
|
||||
if not heads.valid or heads.semmedNif.len == 0 or heads.moduleBase.len == 0:
|
||||
continue
|
||||
if heads.semmedNif notin maxTime:
|
||||
# not part of this program anymore (e.g. a removed module), but its
|
||||
# definitions may still be referenced by reused TUs
|
||||
staleArtifacts.add artifact
|
||||
continue
|
||||
let cname = artifact[0..^5] # strip ".nif"
|
||||
let obj = completeCfilePath(g.config, toObjFile(g.config, AbsoluteFile cname))
|
||||
if not (fileExists(cname) and fileExists(obj.string)) or
|
||||
staleVsArtifact(heads.semmedNif,
|
||||
getLastModificationTime(artifact), heads).len > 0:
|
||||
staleArtifacts.add artifact
|
||||
continue
|
||||
fileCandidates.add (cname, heads)
|
||||
|
||||
if not isDefined(g.config, "icNoRetain"):
|
||||
enforceDefRetention(g, mainPos, reusedHeads, fileCandidates, staleArtifacts,
|
||||
loadedArtifacts, icDebug)
|
||||
|
||||
# The cached-definition sets and the file-level reuse list reflect the
|
||||
# TUs that survived the retention check.
|
||||
for heads in reusedHeads.values:
|
||||
for d in heads.cdefs: g.icCachedCDefs.incl d.cname
|
||||
for d in heads.cdata: g.icCachedDataDefs.incl d.cname
|
||||
for fc in fileCandidates:
|
||||
g.icFileReused.add (fc.cname, fc.heads.moduleBase,
|
||||
fc.heads.initRequired, fc.heads.datInitRequired)
|
||||
g.icFileReusedCnames.incl fc.cname
|
||||
for d in fc.heads.cdefs: g.icCachedCDefs.incl d.cname
|
||||
for d in fc.heads.cdata: g.icCachedDataDefs.incl d.cname
|
||||
if icDebug and g.icFileReused.len > 0:
|
||||
stderr.writeLine "[icReuse] file-level reused TUs: " & $g.icFileReused.len
|
||||
|
||||
proc isMetaIter(t: PType, closure: RootRef): bool =
|
||||
# openArray/varargs hooks are sem bookkeeping: no real flow ever demands
|
||||
# them, and generating one pollutes the TU's type cache with a struct
|
||||
@@ -561,15 +229,6 @@ proc generateCodeForModule(g: ModuleGraph; precomp: PrecompiledModule) =
|
||||
" typ: " & typeToString(op.sym.typ) & " in " & precomp.module.name.s
|
||||
requestProcDef(bmod, op.sym)
|
||||
|
||||
# Definitions this TU embedded in the previous run that reused TUs still
|
||||
# reference must keep being emitted (see `enforceDefRetention`).
|
||||
if g.icPreserveDefs.hasKey(moduleId):
|
||||
for sym in g.icPreserveDefs[moduleId]:
|
||||
requestAnyDef(bmod, sym)
|
||||
if g.icPreserveTypeInfos.hasKey(moduleId):
|
||||
for t in g.icPreserveTypeInfos[moduleId]:
|
||||
discard genTypeInfo(g.config, bmod, t, unknownLineInfo)
|
||||
|
||||
proc loadBackendModules(g: ModuleGraph; mainFileIdx: FileIndex):
|
||||
tuple[modules: seq[PrecompiledModule], precompSys: PrecompiledModule,
|
||||
nifFiles: seq[string]] =
|
||||
@@ -775,8 +434,8 @@ proc generateMergeStage(g: ModuleGraph) =
|
||||
## wrote, computes the global live set and — for each `'u'`-flagged unique
|
||||
## definition that several `cg` processes emitted (emit-everywhere) — the one
|
||||
## artifact allowed to embed its body, and writes the decision the `emit`
|
||||
## stages consume. This replaces the whole-program backend's in-process
|
||||
## `icSharedDefOwner`/DCE coordination with a cross-process rule.
|
||||
## stages consume — the cross-process replacement for what used to be
|
||||
## in-process first-claimant/DCE coordination.
|
||||
let nimcache = getNimcacheDir(g.config).string
|
||||
var files: seq[string] = @[]
|
||||
for artifact in walkFiles(nimcache / "*.c.nif"):
|
||||
@@ -942,21 +601,9 @@ proc generateCode*(g: ModuleGraph; mainFileIdx: FileIndex) =
|
||||
if precompSys.module != nil:
|
||||
discard setupNifBackendModule(g, precompSys.module)
|
||||
|
||||
# Decide which modules' cached translation units can be reused
|
||||
computeModuleReuse(g, modules, precompSys, nifDeps)
|
||||
phaseDone "reuse (" & $g.icReusedModules.len & " modules reused)"
|
||||
|
||||
template generateOrReuse(precomp: PrecompiledModule) =
|
||||
if precomp.module.position in g.icReusedModules:
|
||||
# no code generation, but the recorded compile/link directives
|
||||
# (passl/passc/...) still apply to this build
|
||||
replayBackendActions(g, precomp.module, precomp.topLevel)
|
||||
else:
|
||||
generateCodeForModule(g, precomp)
|
||||
|
||||
# System module is generated first if it exists
|
||||
if precompSys.module != nil:
|
||||
generateOrReuse(precompSys)
|
||||
generateCodeForModule(g, precompSys)
|
||||
|
||||
# Track which modules have been processed to avoid duplicates
|
||||
var processed = initIntSet()
|
||||
@@ -966,7 +613,7 @@ proc generateCode*(g: ModuleGraph; mainFileIdx: FileIndex) =
|
||||
# Generate code for all modules (skip system since it's already processed)
|
||||
for m in modules:
|
||||
if not processed.containsOrIncl(m.module.position):
|
||||
generateOrReuse(m)
|
||||
generateCodeForModule(g, m)
|
||||
|
||||
emitMethodDispatchers(g)
|
||||
phaseDone "cgen"
|
||||
|
||||
@@ -23,6 +23,4 @@ proc prepareConfigNotes*(graph: ModuleGraph; module: PSym) =
|
||||
graph.config.notes = graph.config.foreignPackageNotes
|
||||
|
||||
proc moduleHasChanged*(graph: ModuleGraph; module: PSym): bool {.inline.} =
|
||||
# under `nim nifc` a module whose cached translation unit is reused
|
||||
# does not generate code; the set is empty for every other command
|
||||
result = module.position notin graph.icReusedModules
|
||||
result = true
|
||||
|
||||
Reference in New Issue
Block a user