IC: progress

This commit is contained in:
Araq
2026-06-18 20:32:48 +02:00
parent 88c23f8519
commit b9af129e5f
11 changed files with 82 additions and 3 deletions

View File

@@ -824,6 +824,10 @@ proc newSymNode*(sym: PSym): PNode =
result = newNode(nkSym)
result.sym = sym
result.typField = sym.typ
if result.typField == nil and nifcBackendActive:
# See the two-arg overload in astdef: in the NIF backend cg stage a sym node
# built from a not-yet-typed stub must track the symbol's type lazily.
result.flags.incl nfLazyType
result.info = sym.info
proc newOpenSym*(n: PNode): PNode {.inline.} =

View File

@@ -1571,6 +1571,14 @@ proc cursorFromIndexEntry(c: var DecodeContext; module: FileIndex; entry: NifInd
buf: var TokenBuf): Cursor =
let s = addr c.mods[module].stream
s.r.jumpTo entry.offset
# A seek-load is self-contained: its tokens must decode their relative line
# info against `entry.info` ALONE. The stream's `parents` stack can be left at
# depth >1 by a prior non-seek read (e.g. loadNifModule reads `(stmts`/
# `(implementation` without consuming their `)`), and `parse` only overwrites
# parents[0] while `rawNext` reads parents[^1] — so a stale top entry (the last
# symbol decoded) would become the base, corrupting the decoded line info.
# Collapse the stack so parse's parentInfo is the sole base.
s[].parents.setLen 1
nifcursors.parse(s[], buf, entry.info)
result = cursorAt(buf, 0)
@@ -1813,7 +1821,20 @@ proc loadSymStub(c: var DecodeContext; t: SymId; thisModule: string;
inc val[]
let id = if isBk: backendItemId(module.int32, val[]) else: itemId(module.int32, val[])
let offs = c.getOffset(module, symAsStr)
let offs = c.mods[module].index.getOrDefault(symAsStr)
if offs.offset == 0:
# Only module/package self-syms are never written as `(sd)` entries, so a
# missing index offset means this is such a sym — typically the OWNER of an
# `include`d symbol, or a re-exported module qualifier left dangling in a
# dead `when`-branch (`inlineasm.arm64.x`). Synthesize a resolvable skModule
# stub (itemId item-0 = the module self-sym) instead of asserting "symbol
# has no offset". `Complete` so accessors never try to lazy-load it; a real
# `newLineInfo` so an owner/decl reference renders a path, not `???`.
result = PSym(itemId: itemId(module.int32, 0'i32), kindImpl: skModule,
name: c.cache.getIdent(sn.name), disamb: sn.count.int32,
infoImpl: newLineInfo(module, 1, 1), state: Complete)
c.syms[symAsStr] = (result, NifIndexEntry())
return result
let (stubKind, stubName) = stubKindAndName(c.cache, sn.name)
result = PSym(itemId: id, kindImpl: stubKind, name: stubName, disamb: sn.count.int32, state: Partial)
c.syms[symAsStr] = (result, offs)

View File

@@ -17,6 +17,15 @@ when defined(nimPreviewSlimSystem):
export int128
var nifcBackendActive* = false
## Set only while the per-module NIF backend codegen stage runs
## (`nifbackend.generateCgStage`, `cmd == cmdNifC`). It gates `newSymNode`'s
## lazy-type marking so it applies ONLY in the backend — where syms are loaded
## from NIF and a cg-stage transform can build a sym node from a not-yet-typed
## stub — and never during frontend sem, where the same marking would perturb
## effect/exception inference (it diverges from a non-IC build, e.g.
## `times.toDateTimeByWeek` gaining a spurious unlisted `Exception`).
import nodekinds
export nodekinds
@@ -970,6 +979,14 @@ proc newSymNode*(sym: PSym, info: TLineInfo): PNode =
result = newNode(nkSym)
result.sym = sym
result.typField = sym.typImpl
if result.typField == nil and nifcBackendActive:
# In the per-module NIF backend cg stage a transform (chronos async
# closure-iterator lowering) builds `result = …` sym nodes from a not-yet-typed
# NIF stub; snapshotting the nil here would leave the node permanently typeless
# and the backend later reads `t.flags` off it and SIGSEGVs (injectdestructors
# hasDestructor). Mark it lazy so `typ` re-reads `sym.typ` once resolved. Gated
# on `nifcBackendActive` so frontend sem is untouched (see the flag's doc).
result.flags.incl nfLazyType
result.info = info
proc newStrNode*(kind: TNodeKind, strVal: string): PNode =

View File

@@ -336,6 +336,8 @@ proc generateCgStage(g: ModuleGraph; mainFileIdx: FileIndex) =
## module still loads everything (`loadBackendModules`) because NimMain's init
## list and the method dispatchers are whole-program; its `cg` runs essentially
## alone (every other `.c.nif` precedes it), so it does not contend for memory.
# gate `newSymNode`'s lazy-type marking to this stage only (see astdef)
nifcBackendActive = true
let mainSuffix = cachedModuleSuffix(g.config, mainFileIdx)
let targetIsMain = g.config.icBackendModule.len == 0 or
g.config.icBackendModule == mainSuffix

View File

@@ -29,7 +29,7 @@ const
nimEnableCovariance* = defined(nimEnableCovariance)
icFormatVersion* = "10"
icFormatVersion* = "12"
## 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`

View File

@@ -618,7 +618,8 @@ 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", "ttypeoffer", "ttransitiveoffer"]
"tconverterreexport", "ttypeoffer", "ttransitiveoffer",
"tmodsymref"]
proc icTest(args: string) =
temp("")

12
tests/ic/mmodsymadd.nim Normal file
View File

@@ -0,0 +1,12 @@
import mmodsymasm
# The dead `else` branch qualifies a re-exported module (`mmodsymarm`) whose
# `foo` is gated out on this host: the module sym binds at template-definition
# but the member never resolves, so a dangling module-symbol reference survives
# into the serialized template body. Before the `ModMarker` fix this failed
# under `nim ic` with `symbol has no offset` when the consumer loaded this NIF.
template satAdd*(a, b: uint64): uint64 =
when not defined(mmodSymFakeArch):
mmodsymasm.mmodsymx86.foo(a, b)
else:
mmodsymasm.mmodsymarm.foo(a, b)

5
tests/ic/mmodsymarm.nim Normal file
View File

@@ -0,0 +1,5 @@
# Gated out on every host (mmodSymFakeArch is never defined): `foo` does NOT
# exist here, so a qualified `…mmodsymarm.foo` cannot resolve to the proc and
# leaves the bare re-exported MODULE symbol dangling in the template body.
when defined(mmodSymFakeArch):
func foo*(a, b: uint64): uint64 = a + b

2
tests/ic/mmodsymasm.nim Normal file
View File

@@ -0,0 +1,2 @@
import mmodsymx86, mmodsymarm
export mmodsymx86, mmodsymarm

3
tests/ic/mmodsymx86.nim Normal file
View File

@@ -0,0 +1,3 @@
# Live on every real host: provides `foo` so the template's taken branch resolves.
when not defined(mmodSymFakeArch):
func foo*(a, b: uint64): uint64 = a + b

12
tests/ic/tmodsymref.nim Normal file
View File

@@ -0,0 +1,12 @@
discard """
output: '''7'''
"""
# Regression test: a cross-module MODULE-symbol reference left as a dangling
# qualifier in a template body (here `mmodsymasm.mmodsymarm.foo` in a dead
# `when`-branch, reached via re-export) must load under `nim ic` instead of
# raising `symbol has no offset`. Mirrors nim-intops' `inlineasm.arm64.X` in
# nimbus-eth2. See compiler/ast2nif.nim `ModMarker`.
import mmodsymadd
echo satAdd(3'u64, 4'u64)