From b60a03a2beb00b1657cd3be07cdb7a1acb7e0731 Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 14 Jun 2026 13:02:23 +0200 Subject: [PATCH] more unnecessary workarounds removed --- compiler/ccgstmts.nim | 8 -- compiler/cgen.nim | 20 --- compiler/dce.nim | 263 -------------------------------------- compiler/main.nim | 7 +- compiler/modulegraphs.nim | 6 - compiler/nifbackend.nim | 172 ++----------------------- 6 files changed, 15 insertions(+), 461 deletions(-) delete mode 100644 compiler/dce.nim diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index b4518241e2..a30c1b1bf2 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -1980,14 +1980,6 @@ proc genAsgn(p: BProc, e: PNode, fastAsgn: bool) = loadInto(p, le, ri, a) proc genStmts(p: BProc, t: PNode) = - if p.config.cmd == cmdNifC and t.kind == nkSym and - t.sym.kind in {skProc, skFunc, skConverter, skIterator} and - not icDceLive(p.module, t.sym): - # Under IC a module's top-level routine definitions reappear as bare - # symbol statements in the loaded statement list and were generated - # eagerly. Skip the ones dce.nim proved unreachable; anything a live - # body references is still generated on demand via `genProc`. - return var a: TLoc = default(TLoc) let isPush = p.config.hasHint(hintExtendedContext) diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 37b55899b9..1b7979bb4a 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -904,16 +904,6 @@ proc initLocExprSingleUse(p: BProc, e: PNode): TLoc = result.flags.incl lfSingleUse expr(p, e, result) -proc icDceLive(m: BModule; sym: PSym): bool = - ## Under `nim nifc` the eagerly emitted top-level routine listing is - ## filtered through dce.nim's liveness result. Symbols generated on - ## demand (`genProc` from a use site) never consult this. - let g = m.g.graph - if not g.icDceEnabled or sym.itemId.isBackendMinted: - result = true - else: - result = globalName(sym, m.config) in g.icLiveNames - include ccgcalls, "ccgstmts.nim" proc initFrame(p: BProc, procname, filename: Rope): Rope = @@ -1719,13 +1709,6 @@ proc isActivated(prc: PSym): bool = prc.typ != nil proc genProc(m: BModule, prc: PSym) = if sfBorrow in prc.flags or not isActivated(prc): return - if m.config.cmd == cmdNifC and m.g.graph.icDceEnabled and - sfImportc notin prc.flags and not icDceLive(m, prc): - # Stage-2 readiness check: in the current single-process backend, demand - # always wins over the liveness analysis (we generate the proc anyway). - # But per-module codegen will have to trust the analysis, so a proc that - # is demanded yet not marked live is an analysis bug — report it. - m.g.graph.icDceMisses.incl globalName(prc, m.config) if sfForward in prc.flags: addForwardedProc(m, prc) fillProcLoc(m, prc.ast[namePos]) @@ -2897,9 +2880,6 @@ proc cgenWriteModules*(backend: RootRef, config: ConfigRef) = if cl.broken: stripCnifMarks(codes[i]) else: renderMarkedC(codes[i], cl.live, dropped) registerModuleCode(mods[i], cfs[i], rendered) - g.graph.icCDefs = cl.defs - g.graph.icCLiveDefs = cl.liveDefs - g.graph.icCDropped = dropped else: for m in cgenModules(g): m.writeModule() diff --git a/compiler/dce.nim b/compiler/dce.nim deleted file mode 100644 index 499f681cbf..0000000000 --- a/compiler/dce.nim +++ /dev/null @@ -1,263 +0,0 @@ -# -# -# The Nim Compiler -# (c) Copyright 2026 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## Dead code analysis over per-module NIF files — a port of Nimony's -## `hexer/dce1.nim`/`dce2.nim` ideas onto the `nifcore` API. -## -## Per module we collect, in a single token walk over its `.nif` file: -## - `roots`: symbols that are alive by construction — anything referenced -## from top-level init code (every module's init proc is always emitted), -## plus flag-based entry points (see below) -## - `uses`: edges `definition -> symbols referenced inside its body` -## -## A global mark&sweep over the union of all modules' graphs then yields the -## set of live symbols. -## -## The NIF files contain *semchecked* (unlowered) AST, so uses that only -## materialize during the backend's lowering passes are invisible to the -## token walk. Those are covered by conservative roots instead: -## - registered hooks and `$enum` procs (the `(rep* "key" sym)` entries): -## `injectdestructors` and magic lowering insert calls to them at codegen -## - `{.compilerproc.}` symbols: requested by name via `cgsym` -## - `{.exportc.}` symbols, methods and dispatchers: external entry points -## resp. reachable through dynamic dispatch only -## -## In the current single-process backend the result is consumed as a skip -## filter for the eagerly generated top-level routine listing -## (`ccgstmts.genStmts`); cgen's demand-driven `genProc` remains in place, -## so an analysis miss can only cost code size, never correctness. The same -## analysis is the building block for per-module incremental codegen later, -## where it has to stand on its own. - -import std / [tables, sets, os, assertions] -from std / strutils import rfind -import "../dist/nimony/src/lib" / nifcoreparse -import ast, options, pathutils -import ic / enum2nif - -type - DceContext = object - pool: Pool # shared literal pool: same name <=> same SymId everywhere - tags: TagPool # shared tag pool: tag ids fixed by the registrations below - uses: Table[SymId, HashSet[SymId]] - roots: HashSet[SymId] - stmtsTag, sdefTag, implTag, replayTag, importTag, includeTag: TagId - methodKindTag: TagId - hookTags: HashSet[TagId] - routineKindTags: HashSet[TagId] - offers: HashSet[SymId] # generic routine instances defined by the modules - broken: bool # a module failed to parse; the result must not be used - - DceStats* = object - instances*: int ## routine instance definitions across all modules - uniqueInstances*: int ## distinct instantiation keys (name.disamb) - ## `instances - uniqueInstances` = definitions a merge step would drop - -const - NoSym = SymId(0) # pool ids start at 1 - -proc symIdAt(c: Cursor): SymId {.inline.} = - # Every symbol in our NIFs is written with its `.disamb.modulesuffix`, so - # the name is always longer than nifcore's 3-byte inline-string cutoff and - # lands in the (shared) pool: pool ids are stable identities across all - # modules' token buffers. - assert not isInlineLit(c), "unexpectedly short NIF symbol name" - SymId(combinedPayload(c) shr 1) - -proc recordUse(ctx: var DceContext; sym, owner: SymId) = - if owner == NoSym: - ctx.roots.incl sym - else: - ctx.uses.mgetOrPut(owner, initHashSet[SymId]()).incl sym - -proc walkDef(ctx: var DceContext; c: var Cursor; owner: SymId; declarative: bool) - -proc walk(ctx: var DceContext; c: var Cursor; owner: SymId; declarative: bool) = - ## Generic walk. `owner == NoSym and not declarative` is init-code context: - ## symbol uses become roots. With an owner they become `uses` edges. In - ## declarative context (the listing after the `(implementation)` marker) - ## bare uses record nothing — only definitions found inside contribute. - case c.kind - of TagLit: - if c.cursorTagId == ctx.sdefTag: - walkDef(ctx, c, owner, declarative) - else: - c.loopInto: - walk(ctx, c, owner, declarative) - of Symbol: - if not declarative: - recordUse(ctx, symIdAt(c), owner) - inc c - else: - skip c - -proc walkDef(ctx: var DceContext; c: var Cursor; owner: SymId; declarative: bool) = - # Layout (ast2nif.writeSymDef): - # (sd SymbolDef (symkind ...) magic flags options offset ...) - # NB: no `return` inside `into` — it would skip the cursor rescoping. - c.into: - if c.kind == SymbolDef: - let self = symIdAt(c) - # An sdef is emitted at the symbol's *first reference*; in use - # positions that reference counts like a plain symbol use. - if not declarative: - recordUse(ctx, self, owner) - inc c - if c.hasMore: skip c # export marker: "x" or dot - var rooted = false - var isRoutine = false - if c.hasMore and c.kind == TagLit: # symbol kind tree - if c.cursorTagId == ctx.methodKindTag: - rooted = true # reachable via dynamic dispatch - isRoutine = c.cursorTagId in ctx.routineKindTags - c.loopInto: - walk(ctx, c, self, false) # guard sym/bitsize for vars - if c.hasMore: skip c # magic: ident or dot - if c.hasMore: # flags: ident or dot - if c.kind == Ident: - let fl = parse(TSymFlag, strVal(c)) - if sfExportc in fl or sfCompilerProc in fl or sfDispatcher in fl: - rooted = true - if isRoutine and sfFromGeneric in fl: - ctx.offers.incl self - skip c - if rooted: ctx.roots.incl self - # rest: options, offset, position, lib, type, owner, ast, loc, - # constraint, instantiatedFrom — all walked as the definition's body - while c.hasMore: - walk(ctx, c, self, false) - else: - # malformed sdef; consume defensively - while c.hasMore: - walk(ctx, c, owner, declarative) - -proc rootHookSyms(ctx: var DceContext; c: var Cursor) = - # (repdestroy "typekey" hookSym) and friends - c.loopInto: - if c.kind == Symbol: - ctx.roots.incl symIdAt(c) - inc c - else: - skip c - -proc analyzeNifFile(ctx: var DceContext; filename: string; - imports: var seq[string]) = - if not fileExists(filename): - ctx.broken = true - return - var buf = parseFromFile(filename, 1000, ctx.pool, ctx.tags) - var c = beginRead(buf) - if c.kind == TagLit and c.cursorTagId == ctx.stmtsTag: - var declarative = false - c.loopInto: - case c.kind - of TagLit: - let tag = c.cursorTagId - if tag == ctx.implTag: - # marks the start of the declarative listing (routines, type - # sections, consts); everything before it is init code - declarative = true - skip c - elif tag == ctx.importTag: - # (import . . "modsuffix") — the analysis discovers the module - # closure itself; the backend's own module list omits modules that - # are only reached through system or through demand-driven codegen - c.loopInto: - if c.kind == StrLit: - imports.add strVal(c) - inc c - else: - skip c - elif tag == ctx.replayTag or tag == ctx.includeTag: - skip c # compile directives and include info - elif tag in ctx.hookTags: - rootHookSyms(ctx, c) - elif tag == ctx.sdefTag: - # a definition listed at section level (globals before the marker, - # announced hooks after it): a declaration, not a use - walkDef(ctx, c, NoSym, true) - else: - walk(ctx, c, NoSym, declarative) - of Symbol: - inc c # bare re-listing of a written definition - else: - skip c # the stmts wrapper's flag/type dots - else: - ctx.broken = true - endRead(c) - -proc markLive(ctx: DceContext): HashSet[SymId] = - result = initHashSet[SymId]() - var work = newSeqOfCap[SymId](ctx.roots.len) - for r in ctx.roots: work.add r - while work.len > 0: - let s = work.pop() - if not result.containsOrIncl(s): - if ctx.uses.hasKey(s): - for dep in ctx.uses[s]: - if dep notin result: - work.add dep - -proc computeLiveSymbols*(conf: ConfigRef; seedFiles: openArray[string]; - live: var HashSet[string]; stats: var DceStats; - nifDeps: var Table[string, seq[string]]): bool = - ## Global liveness over a program's NIF modules: the seeds plus the - ## transitive closure of their `(import ...)` entries. On success fills - ## `live` with the NIF names (`name.disamb.modsuffix`) of every reachable - ## symbol and returns true. Returns false when any module could not be - ## analyzed — the caller must then treat everything as live. - ## `nifDeps` receives the import graph over NIF file paths — the full - ## closure including the modules the backend's own module list omits; - ## the artifact-reuse decision needs it for transitive invalidation. - var ctx = DceContext(pool: newPool(), tags: newTagPool()) - ctx.stmtsTag = ctx.tags.registerTag("stmts") - ctx.sdefTag = ctx.tags.registerTag("sd") - ctx.implTag = ctx.tags.registerTag("implementation") - ctx.replayTag = ctx.tags.registerTag("replay") - ctx.importTag = ctx.tags.registerTag("import") - ctx.includeTag = ctx.tags.registerTag("include") - ctx.methodKindTag = ctx.tags.registerTag("method") - for t in ["repdestroy", "repcopy", "repwasmoved", "repdup", "repsink", - "reptrace", "repdeepcopy", "repenumtostr"]: - ctx.hookTags.incl ctx.tags.registerTag(t) - for t in ["proc", "func", "iterator", "converter", "method"]: - ctx.routineKindTags.incl ctx.tags.registerTag(t) - var queue = newSeq[string](seedFiles.len) - for i in 0..= 0: name[0.. 0): + # Per-module backend stages (cg/emit/merge/link) run as many parallel child + # processes; each would print a `[SuccessX]` summary line that misleadingly + # reports `out: ` for a step that only wrote one + # `.c.nif`/`.c`. The driving `nim ic` (and koch) reports the real result. if optProfileVM in conf.globalOptions: echo conf.dump(conf.vmProfileData) genSuccessX(conf) diff --git a/compiler/modulegraphs.nim b/compiler/modulegraphs.nim index b262acb2c3..e145214bcb 100644 --- a/compiler/modulegraphs.nim +++ b/compiler/modulegraphs.nim @@ -68,16 +68,10 @@ type enumToStringProcs*: Table[ItemId, PSym] loadedEnumToStringProcs: Table[string, PSym] emittedTypeInfo*: Table[string, FileIndex] - icLiveNames*: HashSet[string] # NIF names of reachable symbols (dce.nim); - # filters the top-level listing under `nim nifc` - icDceEnabled*: bool - icDceMisses*: HashSet[string] # demand-generated but not marked live: - # analysis bugs that per-module codegen would hit instDisambs: Table[(int, int32), ItemId] # (name id, content disamb) -> # instance, for collision probing in # `setInstanceDisamb` icCnifFiles*: seq[string] # `.c.nif` artifacts written by this run - icCDefs*, icCLiveDefs*, icCDropped*: int # render-time DCE stats pendingMethodReplays*: seq[PSym] # method registrations loaded under # `nim nifc`, bucketed only after every # module is loaded (`flushMethodReplays`) diff --git a/compiler/nifbackend.nim b/compiler/nifbackend.nim index dae0ea67f1..4f7791fe15 100644 --- a/compiler/nifbackend.nim +++ b/compiler/nifbackend.nim @@ -23,7 +23,7 @@ when defined(nimPreviewSlimSystem): import std/assertions import ast, options, lineinfos, modulegraphs, cgendata, cgen, - pathutils, extccomp, msgs, modulepaths, idents, types, ast2nif, typekeys, dce, + pathutils, extccomp, msgs, modulepaths, idents, types, ast2nif, typekeys, cnif from cgmeth import generateIfMethodDispatchers import ic / replayer @@ -92,29 +92,6 @@ proc isMetaIter(t: PType, closure: RootRef): bool = # descriptor for what must remain a (ptr, len) parameter expansion t.kind in tyMetaTypes + {tyTyped, tyUntyped, tyNone, tyVarargs, tyOpenArray} -proc eagerHookCandidate(sym: PSym): bool = - ## Announced hooks that can actually be code-generated: generic hook - ## announcements and meta-typed ones (`varargs[typed]` etc.) are replay - ## information for sem, not code. - let typ = sym.typ - if typ == nil or containsGenericType(typ): return false - if typ.n == nil: return false - for i in 1.. 0: + else: rawMessage(g.config, errGenerated, - "per-module backend stage not implemented yet: " & g.config.icBackendStage) - return - - # Phase timing, enabled with `-d:icTimings` on the nifc command line. - let icTimings = isDefined(g.config, "icTimings") - var phaseStart = epochTime() - template phaseDone(name: string) = - if icTimings: - let now = epochTime() - stderr.writeLine "[icTime] " & name & ": " & - formatFloat(now - phaseStart, ffDecimal, 2) & "s" - phaseStart = now - - # Reset backend state - resetForBackend(g) - - var isKnownFile = false - let systemFileIdx = registerNifSuffix(g.config, "sysma2dyk", isKnownFile) - g.config.m.systemFileIdx = systemFileIdx - #msgs.fileInfoIdx(g.config, - # g.config.libpath / RelativeFile"system.nim") - - # Load system module first - it's always needed and contains essential hooks - var precompSys = PrecompiledModule(module: nil) - precompSys = moduleFromNifFile(g, systemFileIdx, {LoadFullAst, AlwaysLoadInterface}) - g.systemModule = precompSys.module - - # Load all modules in dependency order using stack traversal - # This must happen BEFORE any code generation so that hooks are loaded into loadedOps - var nifFiles: seq[string] = @[toNifFilename(g.config, systemFileIdx)] - let modules = loadModuleDependencies(g, mainFileIdx, nifFiles) - # build the method dispatch buckets now that every module is loaded - flushMethodReplays(g) - phaseDone "load (" & $ (modules.len + 1) & " modules)" - if modules.len == 0: - rawMessage(g.config, errGenerated, - "Cannot load NIF file for main module: " & toFullPath(g.config, mainFileIdx)) - return - - # Compute the global live set so that the top-level routine listing can be - # filtered (see `ccgstmts.genStmts`). On analysis failure everything stays - # alive — demand-driven `genProc` makes this a size optimization only. - var dceStats = DceStats() - var nifDeps = initTable[string, seq[string]]() - if not isDefined(g.config, "icNoDce"): - g.icDceEnabled = computeLiveSymbols(g.config, nifFiles, g.icLiveNames, - dceStats, nifDeps) - phaseDone "dce" - - # Set up backend modules for all modules that need code generation - for m in modules: - discard setupNifBackendModule(g, m.module) - if precompSys.module != nil: - discard setupNifBackendModule(g, precompSys.module) - - # System module is generated first if it exists - if precompSys.module != nil: - generateCodeForModule(g, precompSys) - - # Track which modules have been processed to avoid duplicates - var processed = initIntSet() - if precompSys.module != nil: - processed.incl precompSys.module.position - - # Generate code for all modules (skip system since it's already processed) - for m in modules: - if not processed.containsOrIncl(m.module.position): - generateCodeForModule(g, m) - - emitMethodDispatchers(g) - phaseDone "cgen" - - # during code generation of `main.nim` we can trigger the code generation - # of symbols in different modules so we need to finish these modules - # here later, after the above loop! - # Important: The main module must be finished LAST so that all other modules - # have registered their init procs before genMainProc uses them. - var mainModule: BModule = nil - for m in BModuleList(g.backend).mods: - if m != nil: - assert m.module != nil - if sfMainModule in m.module.flags: - mainModule = m - else: - finishModule g, m - if mainModule != nil: - finishModule g, mainModule - phaseDone "finish" - - if g.icDceEnabled and isDefined(g.config, "icDceCheck"): - var misses: seq[string] = @[] - for n in g.icDceMisses: misses.add n - sort misses - for n in misses: - stderr.writeLine "[icDce] MISS (generated on demand, not marked live): " & n - stderr.writeLine "[icDce] live: " & $g.icLiveNames.len & " misses: " & $misses.len & - " modules: " & $nifFiles.len - stderr.writeLine "[icDce] instances: " & $dceStats.instances & - " unique: " & $dceStats.uniqueInstances & - " mergeable: " & $(dceStats.instances - dceStats.uniqueInstances) - - # Write C files - cgenWriteModules(g.backend, g.config) - phaseDone "write" - - if isDefined(g.config, "icDceCheck") and g.icCnifFiles.len > 0: - stderr.writeLine "[icDceC] cdefs: " & $g.icCDefs & " live: " & $g.icCLiveDefs & - " dropped: " & $g.icCDropped - - # Run C compiler - if g.config.cmd != cmdTcc: - extccomp.callCCompiler(g.config) - phaseDone "cc+link" - if not g.config.hcrOn: - extccomp.writeJsonBuildInstructions(g.config, g.cachedFiles) + "the per-module NIF backend requires --icBackendStage:cg|merge|emit|link")