Compare commits

..

2 Commits

Author SHA1 Message Date
Andreas Rumpf
8f72860d7d ic fixes3 (#26157) 2026-09-01 16:47:03 +02:00
ringabout
859b0ba270 fixes #26152; JS regression: dockhack.js is invalid (#26156)
fixes #26152

PR #26086 introduced {base, off, len} view wrappers for var openArray
arguments to preserve write-through semantics. This caused imported JS
pattern calls such as #.sort(#) to emit invalid object-literal syntax
instead of invoking the underlying array method.

Skip the view wrapper when generating arguments for imported pattern
calls, while retaining it for regular Nim procedures.
2026-09-01 10:26:00 +02:00
26 changed files with 462 additions and 3237 deletions

View File

@@ -714,10 +714,6 @@ proc extractPragma*(s: PSym): PNode =
proc skipPragmaExpr*(n: PNode): PNode =
## if pragma expr, give the node the pragmas are applied to,
## otherwise give node itself
##
## `bnode` carries the `BNode` spelling. It is a separate one-liner rather
## than a shared template because this sits above the point in this module
## where `firstSon` for a `PNode` exists.
if n.kind == nkPragmaExpr:
result = n[0]
else:
@@ -1486,28 +1482,18 @@ proc hasSubnodeWith*(n: PNode, kind: TNodeKind): bool =
return true
result = false
template getIntImpl*(aArg: typed): Int128 =
## The body of `getInt`, in a form `bnode.nim` can instantiate for a `BNode`
## too — same reason as `canRaiseImpl`: `BNode` is defined there and that
## module imports this one, so the shared logic has to live in a template
## rather than an `AnyNode` proc. There is no second copy.
block:
let a = aArg
var res: Int128
case a.kind
of nkCharLit, nkUIntLit..nkUInt64Lit:
res = toInt128(cast[uint64](a.intVal))
of nkInt8Lit..nkInt64Lit:
res = toInt128(a.intVal)
of nkIntLit:
# XXX: enable this assert
# assert a.typ.kind notin {tyChar, tyUint..tyUInt64}
res = toInt128(a.intVal)
else:
raiseRecoverableError("cannot extract number from invalid AST node")
res
proc getInt*(a: PNode): Int128 = getIntImpl(a)
proc getInt*(a: PNode): Int128 =
case a.kind
of nkCharLit, nkUIntLit..nkUInt64Lit:
result = toInt128(cast[uint64](a.intVal))
of nkInt8Lit..nkInt64Lit:
result = toInt128(a.intVal)
of nkIntLit:
# XXX: enable this assert
# assert a.typ.kind notin {tyChar, tyUint..tyUInt64}
result = toInt128(a.intVal)
else:
raiseRecoverableError("cannot extract number from invalid AST node")
proc getInt64*(a: PNode): int64 {.deprecated: "use getInt".} =
case a.kind
@@ -1527,21 +1513,14 @@ proc getFloat*(a: PNode): BiggestFloat =
#internalError(a.info, "getFloat")
#result = 0.0
template getStrImpl*(aArg: typed): string =
## Body shared with `bnode`'s `BNode` spelling — see `canRaiseImpl`.
block:
let gs = aArg
var res = ""
case gs.kind
of nkStrLit..nkTripleStrLit: res = gs.strVal
of nkNilLit:
# let's hope this fixes more problems than it creates:
res = ""
else:
raiseRecoverableError("cannot extract string from invalid AST node")
res
proc getStr*(a: PNode): string = getStrImpl(a)
proc getStr*(a: PNode): string =
case a.kind
of nkStrLit..nkTripleStrLit: result = a.strVal
of nkNilLit:
# let's hope this fixes more problems than it creates:
result = ""
else:
raiseRecoverableError("cannot extract string from invalid AST node")
#doAssert false, "getStr"
#internalError(a.info, "getStr")
#result = ""
@@ -1684,14 +1663,8 @@ proc isImportedException*(t: PType; conf: ConfigRef): bool =
let base = t.skipTypes({tyAlias, tyPtr, tyDistinct, tyGenericInst})
result = base.sym != nil and {sfCompileToCpp, sfImportc} * base.sym.flags != {}
template isInfixAsImpl*(nArg: typed): bool =
## Body shared with `bnode`'s `BNode` spelling — see `canRaiseImpl`.
block:
let ia = nArg
ia.kind == nkInfix and ia.firstSon.kind == nkIdent and
ia.firstSon.ident.id == ord(wAs)
proc isInfixAs*(n: PNode): bool = isInfixAsImpl(n)
proc isInfixAs*(n: PNode): bool =
return n.kind == nkInfix and n.firstSon.kind == nkIdent and n.firstSon.ident.id == ord(wAs)
proc skipColon*(n: PNode): PNode =
result = n
@@ -1768,27 +1741,14 @@ proc addParam*(procType: PType; param: PSym) =
procType.n.add newSymNode(param)
rawAddSon(procType, param.typ)
const magicsThatCanRaise* = {
const magicsThatCanRaise = {
mNone, mSlurp, mStaticExec, mParseExprToAst, mParseStmtToAst, mEcho}
# `canRaise` and `canRaiseConservative` are asked by the C backend, which is
# migrating to reading routine bodies straight off a `.bif` `Cursor` rather than
# off a materialised `PNode` tree (see `compiler/bnode.nim`). Both predicates
# only ever look at a node's `kind`, `sym` and `typ`, so ONE body serves either
# spelling -- but `BNode` is defined in `bnode.nim`, which imports this module,
# so the `BNode` overloads cannot live here. The bodies therefore live in
# templates and `bnode.nim` instantiates them for its own node type: one source
# of truth, no import cycle, and no second copy to keep in sync.
#
# The effect list is reached through `effectsOf` / `raisesNothing` rather than
# by subscripting `fn.typ.n`, so the templates below contain no knowledge of the
# layout and the `BNode` instantiation inherits none. `fn.typ` stays a `PType`
# in both spellings -- there is deliberately no `BType` (see `bnode.nim`) -- so
# what "works on a `.bif`" means for these two is that the type the decoder
# materialises must carry the same effect list the from-source one did. That is
# a claim about the WRITER, not about the vocabulary, and it is checked
# separately: `-d:icCanRaiseLog` logs every answer, and the same program built
# with and without `--ic:on` must produce the same verdicts.
# `canRaise` reaches the effect list through `effectsOf` / `raisesNothing`
# rather than by subscripting `fn.typ.n`, so the layout is written down in one
# place. Under `--ic:on` that list came back from a `.bif`, and whether it came
# back intact is checked separately: `-d:icCanRaiseLog` logs every verdict, and
# the same program built with and without `--ic:on` must produce the same ones.
when defined(icCanRaiseLog):
var canRaiseBranch* = 0
@@ -1803,11 +1763,9 @@ when defined(icCanRaiseLog):
template markCanRaiseBranch*(n: int) =
when defined(icCanRaiseLog): canRaiseBranch = n
template canRaiseConservativeImpl*(fnArg: typed): bool =
block:
let fn = fnArg
markCanRaiseBranch 4
not (fn.kind == nkSym and fn.sym.magic notin magicsThatCanRaise)
proc canRaiseConservative*(fn: PNode): bool =
markCanRaiseBranch 4
result = not (fn.kind == nkSym and fn.sym.magic notin magicsThatCanRaise)
proc effectsOf*(t: PType): PNode {.inline.} =
## The `nkEffectList` a proc type carries as child 0 of its formal-params
@@ -1838,40 +1796,32 @@ proc raisesNothing*(effects: PNode): bool =
effects[exceptionEffects] != nil and
effects[exceptionEffects].safeLen == 0
template canRaiseImpl*(fnArg: typed): bool =
block:
let fn = fnArg
var res: bool
if fn.kind == nkSym and (fn.sym.magic notin magicsThatCanRaise or
{sfImportc, sfInfixCall} * fn.sym.flags == {sfImportc} or
sfGeneratedOp in fn.sym.flags):
markCanRaiseBranch 1
res = false
elif fn.kind == nkSym and fn.sym.magic == mEcho:
markCanRaiseBranch 2
res = true
elif fn.typ != nil and fn.typ.kind == tyProc and fn.typ.n != nil:
markCanRaiseBranch 3
let effects = effectsOf(fn.typ)
if effects.kind == nkSym:
# The historical shape: slot 0 used to be an `nkType` before the effects
# moved in (see `newProcType`). Nothing to read, so nothing licenses a
# raise.
res = false
else:
# A proc-typed value with no explicit raises slot still has
# unspecified effects, which sempass2 treats conservatively.
# Codegen needs to do the same in order to keep goto-exception
# checks after indirect/closure calls.
res = not raisesNothing(effects)
proc canRaise*(fn: PNode): bool =
if fn.kind == nkSym and (fn.sym.magic notin magicsThatCanRaise or
{sfImportc, sfInfixCall} * fn.sym.flags == {sfImportc} or
sfGeneratedOp in fn.sym.flags):
markCanRaiseBranch 1
result = false
elif fn.kind == nkSym and fn.sym.magic == mEcho:
markCanRaiseBranch 2
result = true
elif fn.typ != nil and fn.typ.kind == tyProc and fn.typ.n != nil:
markCanRaiseBranch 3
let effects = effectsOf(fn.typ)
if effects.kind == nkSym:
# The historical shape: slot 0 used to be an `nkType` before the effects
# moved in (see `newProcType`). Nothing to read, so nothing licenses a
# raise.
result = false
else:
markCanRaiseBranch 0
res = false
res
proc canRaiseConservative*(fn: PNode): bool = canRaiseConservativeImpl(fn)
proc canRaise*(fn: PNode): bool = canRaiseImpl(fn)
# A proc-typed value with no explicit raises slot still has
# unspecified effects, which sempass2 treats conservatively.
# Codegen needs to do the same in order to keep goto-exception
# checks after indirect/closure calls.
result = not raisesNothing(effects)
else:
markCanRaiseBranch 0
result = false
proc toHumanStrImpl[T](kind: T, num: static int): string =
result = $kind
@@ -1886,13 +1836,8 @@ proc toHumanStr*(kind: TTypeKind): string =
## strips leading `tk`
result = toHumanStrImpl(kind, 2)
template skipHiddenAddrImpl*(nArg: typed): untyped =
## Body shared with `bnode`'s `BNode` spelling — see `canRaiseImpl`.
block:
let sha = nArg
(if sha.kind == nkHiddenAddr: sha.firstSon else: sha)
proc skipHiddenAddr*(n: PNode): PNode {.inline.} = skipHiddenAddrImpl(n)
proc skipHiddenAddr*(n: PNode): PNode {.inline.} =
(if n.kind == nkHiddenAddr: n.firstSon else: n)
proc isNewStyleConcept*(n: PNode): bool {.inline.} =
assert n.kind == nkTypeClassTy

View File

@@ -282,19 +282,6 @@ type
# `modulegraphs.reexportedLocalSyms`
when defined(icLocalSymStats):
# TEMPORARY instrumentation: how is `localSyms` actually populated? The
# snapshot-vs-shared-table question only matters if body-local NIF names exist
# at all, and `isLocalSym` below returns a hardwired `false`.
import std / exitprocs
var lsLocalHit, lsFieldStub, lsMiss, lsSdReg, lsExtractReg: int
addExitProc proc () =
if lsLocalHit + lsFieldStub + lsMiss + lsSdReg + lsExtractReg > 0:
stderr.writeLine "LOCALSYM localHit=" & $lsLocalHit &
" fieldStub=" & $lsFieldStub & " miss=" & $lsMiss &
" sdReg=" & $lsSdReg & " extractReg=" & $lsExtractReg
proc isLocalSym(sym: PSym): bool {.inline.} =
## Every symbol is emitted as a *global* (module-suffixed) name so that its
## `sdef` gets an index entry and is resolvable by index lookup even when
@@ -2929,7 +2916,6 @@ proc extractLocalSymsFromTree(c: var DecodeContext; n: var Cursor; thisModule: s
let sym = PSym(itemId: id, kindImpl: skStub, name: stubName,
disamb: sn.count.int32, state: Complete)
localSyms[symName] = sym
when defined(icLocalSymStats): inc lsExtractReg
# `loadSymFromCursor` enters the `(sd` and consumes the whole block,
# leaving n positioned after the closing `)`.
loadSymFromCursor(c, sym, n, thisModule, localSyms)
@@ -2995,15 +2981,12 @@ proc loadSymStub(c: var DecodeContext; symAsStr: string; thisModule: string;
if sn.module.len == 0:
result = localSyms.getOrDefault(symAsStr)
if result != nil:
when defined(icLocalSymStats): inc lsLocalHit
return result
elif isFieldMarked(sn.name):
when defined(icLocalSymStats): inc lsFieldStub
# A cross-context object-field reference reaching a non-dotExpr slot (e.g. a
# `{.guard.}` field, an owner): stub it like any other field use.
return c.loadFieldStub(symAsStr, thisModule, localSyms)
else:
when defined(icLocalSymStats): inc lsMiss
raiseAssert "local symbol '" & symAsStr & "' not found in localSyms."
# Global symbol - look up in index for lazy loading
result = c.syms.getOrDefault(symAsStr)[0]
@@ -3378,8 +3361,7 @@ proc loadNode(c: var DecodeContext; n: var Cursor; thisModule: string;
# is NOT fixed by pinning the flag either way: setting it breaks sem as
# above, and clearing it would strip the fallback from the stub
# population that `nifcBackendActive` exists to serve. Left alone
# deliberately; `bnode.typ` answers the faithful `nil` and the grinder
# excludes this one shape with the reason recorded there.
# deliberately.
elif tagIs(n, symDefTagName):
let info = c.infos.oldLineInfo(n.info, cursorPool(n))
let name = n.firstSon
@@ -3405,7 +3387,6 @@ proc loadNode(c: var DecodeContext; n: var Cursor; thisModule: string;
sym = PSym(itemId: id, kindImpl: skStub, name: stubName,
disamb: sn.count.int32, state: Complete)
localSyms[symName] = sym # register for later references
when defined(icLocalSymStats): inc lsSdReg
# Now fully load the symbol from the sdef
loadSymFromCursor(c, sym, n, thisModule, localSyms)
sym.state = c.loadedState # mark as fully loaded
@@ -3584,80 +3565,6 @@ proc materializeLazyBody*(c: var DecodeContext; node: PNode) =
node.typField = real.typField
node.flags = real.flags
# ---------------------------------------------------------------------------
# Cursor-native backend seam (see `bnode.nim`)
#
# The three things a `.bif` `Cursor` cannot answer on its own — what symbol a
# `Symbol` token names, what type a node's type slot denotes, and what
# `TLineInfo` its packed line info maps to — all need the decoder's state. They
# are exposed here rather than reimplemented in `bnode` so that the Cursor
# backend and the `PNode` loader resolve names through exactly the same code.
# ---------------------------------------------------------------------------
type
BodyScope* = object
## Resolution scope for reading ONE routine body straight off a cursor.
## `thisModule` is the owning module's NIF suffix (a `Symbol` token with no
## module suffix is body-local and appears in no index) and `localSyms` is
## the enclosing sym def's local symbols, so a param/local reference
## resolves to the SAME `PSym` the signature already created.
thisModule*: string
localSyms*: Table[string, PSym]
proc lazyBodyCursor*(c: var DecodeContext; node: PNode; scope: var BodyScope;
body: var Cursor): bool =
## Non-destructive lookup of a deferred routine body: the cursor at its
## `(stmtlist ...)` plus the scope its symbol references resolve in. Unlike
## `materializeLazyBody` this does NOT consume the pending entry, so the
## `PNode` path still works afterwards and the two representations of the same
## body can be walked side by side and compared — which is how a proc migrated
## to `BNode` is checked against the one it replaces.
let key = cast[int](node)
if not c.pendingBodies.hasKey(key): return false
let pb = c.pendingBodies[key]
body = pb.cursor
scope = BodyScope(thisModule: pb.thisModule, localSyms: pb.localSyms)
result = true
proc symFromCursor*(c: var DecodeContext; n: Cursor; scope: var BodyScope): PSym =
## The `PSym` a `Symbol` / `SymbolDef` / `(sd ...)` token names. Non-consuming
## (`loadSymStub` advances a `var Cursor`; this one works on a copy).
##
## The bare `SymbolDef` case goes through the by-name overload: the cursor
## overload of `loadSymStub` deliberately rejects it, because inside the
## loader a def token is always reached through its `(sd ...)` wrapper and a
## bare one means a malformed stream. A reader that starts at an arbitrary
## token has no such guarantee, and the def NAMES the same symbol the use
## does.
var cur = n
if cur.kind == SymbolDef:
result = loadSymStub(c, symName(cur), scope.thisModule, scope.localSyms)
else:
result = loadSymStub(c, cur, scope.thisModule, scope.localSyms)
proc typeFromCursor*(c: var DecodeContext; n: Cursor; scope: var BodyScope): PType =
## The `PType` a node's type slot denotes — a `Symbol`, an inline `(td ...)`,
## or a `DotToken` for "no type of its own". Non-consuming.
var cur = n
result = loadTypeStub(c, cur, scope.localSyms)
proc nodeFlagsFromCursor*(n: Cursor): TNodeFlags =
## The node-flags slot: an `Ident` naming the set, or a `DotToken` for empty.
## Non-consuming.
var cur = n
result = loadAtom(TNodeFlags, cur)
proc identFromCursor*(c: var DecodeContext; n: Cursor): PIdent =
## The `PIdent` an `Ident` token names, interned in the SAME cache the loader
## uses — `nkIdent` nodes compare by identity in places.
result = c.cache.getIdent(strVal(n))
proc lineInfoFromCursor*(c: var DecodeContext; n: Cursor): TLineInfo =
## The `TLineInfo` for a token's packed line info. The `FileId` inside belongs
## to the `.bif`'s OWN filename pool, so the mapping needs both the pool and
## the `ConfigRef` the `LineInfoWriter` holds.
result = c.infos.oldLineInfo(n.info, cursorPool(n))
forceLazyBodyHook = proc (n: PNode) {.nimcall, raises: [], tags: [], gcsafe.} =
# `len` (the sole caller path) MUST stay effect-free, so this hook is typed
# `raises: []`. The underlying `loadNode` chain infers `raises: [KeyError]`
@@ -4141,8 +4048,7 @@ var topTagPool: TagPool = nil
var topTagCache: seq[int8] = @[]
## `TagId -> TopTag`, -1 unresolved, for ONE tag pool. `topTagPool` holds the
## pool by REFERENCE so it stays alive and a freed pool cannot be replaced at
## the same address — the same argument `indexFromBif`'s and `bnode`'s memos
## rest on.
## the same address — the same argument `indexFromBif`'s memo rests on.
proc topTagAt(cur: Cursor): TopTag =
let pool {.cursor.} = cur.tags

View File

@@ -13,7 +13,7 @@
import
ast, astyaml, options, lineinfos, idents, rodutils,
msgs, bnode
msgs
import std/[hashes, intsets]
import std/strutils except addf
@@ -100,7 +100,7 @@ proc skipConvCastAndClosure*(n: PNode): PNode =
result = result[1]
else: break
proc sameValue*[T: AnyNode](a, b: T): bool =
proc sameValue*(a, b: PNode): bool =
result = false
case a.kind
of nkCharLit..nkUInt64Lit:
@@ -740,7 +740,7 @@ proc listSymbolNames*(symbols: openArray[PSym]): string =
result.add ", "
result.add sym.name.s
proc isDiscriminantField*(n: AnyNode): bool =
proc isDiscriminantField*(n: PNode): bool =
if n.kind == nkCheckedFieldExpr: sfDiscriminant in n.firstSon.secondSon.sym.flags
elif n.kind == nkDotExpr: sfDiscriminant in n.secondSon.sym.flags
else: false

View File

@@ -960,7 +960,7 @@ iterator sons*(n: PNode): PNode =
## as it does not rely on random indexed access, and over `for x in n.sons`,
## which reads the raw FIELD and so skips the `len` hook that materialises a
## deferred `nfLazyBody` body — over such a body that loop silently visits
## nothing. See `compiler/bnode.nim` for the backend vocabulary this feeds.
## nothing.
for i in 0..<n.safeLen: yield n[i]
iterator isons*(n: PNode; start = 0): tuple[i: int, n: PNode] =
@@ -997,6 +997,15 @@ iterator isonsButLast*(n: PNode; count = 1): tuple[i: int, n: PNode] =
## position, a parallel index into the tuple's `PType`, and so on.
for i in 0 ..< n.safeLen - count: yield (i, n[i])
template son*(n: PNode; i: int): PNode =
## Named indexed access to child `i`, for the small constant positions that
## `firstSon`/`secondSon`/`lastSon` do not cover.
n[i]
template hasSons*(n: PNode): bool =
## Emptiness test; goes through `safeLen` so a deferred body is materialised.
n.safeLen > 0
when defined(useNodeIds):
const nodeIdToDebug* = -1 # 2322968
var gNodeId: int

File diff suppressed because it is too large Load Diff

View File

@@ -1,335 +0,0 @@
#
#
# The Nim Compiler
# (c) Copyright 2026 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## `BodyNav` — a scope-chained navigator over a `.bif` routine body.
##
## Ported from Nimony's `nimony/typenav.nim` (`TypeCache` / `TypeScope`). The
## idea being stolen is not the type algebra — we do not need it, `typ` returns
## a fully materialized `PType` — but the SHAPE of the resolution context:
##
## * a chain of scope frames, each a small table, linked to its parent;
## * `openScope` / `closeScope` / `registerLocal`, called BY THE TRAVERSAL as it
## descends and as it walks past each definition;
## * a lookup that consults the chain and, on a miss, falls through to the
## module index (`typenav`'s `tryLoadSym`; here the decoder's own
## `symFromCursor`).
##
## The consequence is the point: the scope is a PRODUCT OF THE WALK. Nothing is
## snapshotted, so nothing can be stale, and a reader that starts at the top of
## a body and descends always has exactly the definitions it has already passed.
##
## WHAT THIS REPLACES. `ast2nif.PendingBody` stashes `localSyms` — a COPY of the
## enclosing sym def's local symbols, taken when the body was deferred — and
## `bnode`'s `BodyScope` then copies it again. `materializeLazyBody` loads the
## body with its own `var pb`, so every definition the load creates lands in a
## table that is discarded on return. A cursor-side reader holding the earlier
## copy therefore cannot see them, and would mint its own `PSym` for the same
## name: two objects, one symbol.
##
## HOW BIG THAT PROBLEM ACTUALLY IS, measured rather than assumed. Build with
## `-d:icLocalSymStats` and every process reports its `localSyms` traffic on
## exit. Over a full `--ic:on` build of the standard-library closure (104
## backend processes):
##
## localHit=0 fieldStub=2 miss=0 sdReg=5902 extractReg=45
##
## Definitions register constantly and NOT ONE use ever resolves through the
## table. The reason is `ast2nif.isLocalSym`, which returns a hardwired `false`:
## every symbol is emitted with a module suffix and resolves through the
## decoder's global `syms` memo, so both spellings get the same `PSym` whatever
## either one has cached. The 5902 registrations are object FIELDS, whose uses
## deliberately go to `loadFieldStub` instead.
##
## So the stale snapshot is a LATENT hazard, not a live bug, and this module is
## not a bug fix — it is the mechanism that keeps it latent once `isLocalSym`
## stops being `false`, or once a body-local name appears for any other reason.
## Said plainly so nobody has to re-derive it: today the nav changes no answers,
## and the grinder in `cgen` proves that by requiring the navigated symbol to be
## the same object the `PNode` loader produced, at every node of every body.
##
## It is not decorative either, and that also has a number. Over the same build,
## the grinder's traversal reports `navHits=42236 navFallbacks=12658
## navRegistered=311`: the chain answers 77% of lookups, and 311 definitions are
## registered by the walk rather than read from a table someone filled in
## earlier. Sabotaging the key (truncating it to three characters, so
## `c_fwrite` and `c_fflush` collide) makes the grinder fail on the first body
## it reaches — so a clean run means the resolution is right, not that the
## lookup never happened.
##
## FIELDS ARE NOT REGISTERED, and that is deliberate. `loadFieldStub` mints a
## fresh stub per use because two distinct fields can share a name (and a
## position) across types — `a.x` and `b.x` in one body are two different
## symbols. Caching a field by its bare name would hand the second use the first
## one's stub, and its type. The nav skips field names entirely and leaves that
## path exactly as it was.
import std / tables
import ast, ast2nif
when defined(nimPreviewSlimSystem):
import std / assertions
import "../dist/nimony/src/lib/nifcore" except pool
type
NavScopeKind* = enum
nsBlock, ## an ordinary nested scope
nsRoutine ## a routine boundary — see `crossedRoutines`
NavScope {.acyclic.} = ref object
locals: Table[string, PSym]
parent: NavScope
kind: NavScopeKind
BridgeTables* = ref object
## The side tables of an IN-PROCESS bridged buffer (`nodebridge.nim`).
## A `.bif` names its symbols because the reader is a different process; a
## buffer built and read inside ONE process does not have to, and paying the
## name round trip anyway would be worse than pointless — it is what makes
## the file path unable to give a field a stable identity (`loadFieldStub`
## mints per use). Here a symbol reference is an index and resolution hands
## back the very same object, so `symAt` is exact and idempotent for every
## symbol kind, fields included.
syms*: seq[PSym]
types*: seq[PType]
origins*: Table[int, PNode]
## Token position -> the `PNode` encoded there, so a cursor can name the
## node it came from. Lives here rather than in `BridgeBuf` because the
## lookup has to be reachable from wherever a location is built, which is
## everywhere in the generator — the same reason `syms` is here.
buf*: ptr TokenBuf
## The buffer `origins` is keyed against; `cursorToPosition` needs it.
## Borrowed, not owned: it points into the `BridgeBuf` that a scoped
## `withBridge` is currently reading, and never outlives it.
BodyNav* = object
## The resolution context for ONE routine body. `base` is what the decoder
## itself needs (the owning module plus a table `loadSymStub` can write
## into); the frame chain on top of it is this module's contribution.
##
## `bridge` is non-nil only while reading a bridged buffer. It is consulted
## FIRST and, when it answers, it answers exactly — there is no fallback,
## because a `(bsym …)` index that the tables cannot resolve is a corrupt
## buffer, not a cache miss.
base*: BodyScope
bridge*: BridgeTables
current: NavScope
hits*: int ## resolved from the chain
fallbacks*: int ## resolved through the decoder
registered*: int ## definitions the walk registered
proc originAt*(t: BridgeTables; c: Cursor): PNode =
## The source node a cursor was encoded from, or nil when there is none (a
## `DotToken`, or a cursor that is not at a node head).
if t == nil or t.buf == nil: return nil
result = t.origins.getOrDefault(cursorToPosition(t.buf[], c), nil)
proc initBodyNav*(base: sink BodyScope): BodyNav =
## A nav over a body, seeded with whatever resolution context the decoder
## handed out. The root frame is a routine frame: a body IS one.
result = BodyNav(base: base,
current: NavScope(locals: initTable[string, PSym](),
parent: nil, kind: nsRoutine))
proc initBridgeNav*(tables: BridgeTables): BodyNav =
## A nav over an in-process bridged buffer. `base` stays empty — a bridged
## buffer names nothing, so there is nothing for the decoder to resolve — but
## the ROOT FRAME still has to exist: a walk brackets its descent with
## `openScope`/`closeScope`, and a nav without a root frame makes the first
## `closeScope` pop past the bottom.
result = BodyNav(bridge: tables,
current: NavScope(locals: initTable[string, PSym](),
parent: nil, kind: nsRoutine))
proc openScope*(nav: var BodyNav; kind = nsBlock) {.inline.} =
nav.current = NavScope(locals: initTable[string, PSym](),
parent: nav.current, kind: kind)
proc closeScope*(nav: var BodyNav) {.inline.} =
doAssert nav.current.parent != nil, "closeScope past the root frame"
nav.current = nav.current.parent
template withScope*(nav: var BodyNav; kind: NavScopeKind; body: untyped) =
openScope(nav, kind)
try:
body
finally:
closeScope(nav)
proc registerLocal*(nav: var BodyNav; name: string; s: PSym) {.inline.} =
## Record a definition the walk has just passed, in the innermost frame.
nav.current.locals[name] = s
inc nav.registered
proc lookupLocal*(nav: BodyNav; name: string): PSym =
## The chain only. `nil` when nothing in scope carries this name.
var it {.cursor.} = nav.current
while it != nil:
let s = it.locals.getOrDefault(name)
if s != nil: return s
it = it.parent
result = nil
proc crossedRoutines*(nav: BodyNav; name: string): int =
## How many routine frames separate the use from the definition — 0 when the
## definition is in the current routine. `typenav` computes the same thing as
## `LocalInfo.crossedProc`, and it is what tells a closure pass that a name is
## captured rather than local. Nothing consumes it here yet; it is the reason
## the frames carry a kind at all, and dropping the kind would make it
## unrecoverable later.
var it {.cursor.} = nav.current
var crossed = 0
while it != nil:
if it.locals.getOrDefault(name) != nil: return crossed
if it.kind == nsRoutine: inc crossed
it = it.parent
result = -1
# ---------------------------------------------------------------------------
# Names
#
# A symbol reaches the reader in four shapes and they all NAME the same thing;
# `navName` is the one place that knows which token holds the name, so the
# lookup key is derived identically no matter which wrapper the writer chose.
proc navName*(n: Cursor): string =
## The NIF name a token denotes, or `""` when the token names no symbol.
case nifcore.kind(n)
of Symbol, SymbolDef:
result = symName(n)
of TagLit:
let tag = n.tags.tagName(cursorTagId(n))
if tag == symDefTagName:
let name = childCursor(n)
result = if nifcore.kind(name) in {Symbol, SymbolDef}: symName(name) else: ""
elif tag == hiddenTypeTagName:
# `(ht <type> <sym>)`
var inner = childCursor(n)
skip inner
result = navName(inner)
elif tag == symNodeFlagsTagName:
# `(nflags <flags> <symnode>)`
var inner = childCursor(n)
skip inner
result = navName(inner)
else:
result = ""
else:
result = ""
proc symToken*(n: Cursor): Cursor =
## The token that actually NAMES the symbol, with the wrappers stripped.
## `loadSymStub` accepts a `Symbol`, a `SymbolDef` or an `(sd ...)` and
## rejects everything else, so the `(ht ...)` / `(nflags ...)` forms have to be
## peeled here rather than at each call site — the same peeling `navName` does
## for the key, kept beside it so the two cannot drift apart.
result = n
while nifcore.kind(result) == TagLit:
let tag = result.tags.tagName(cursorTagId(result))
if tag == hiddenTypeTagName or tag == symNodeFlagsTagName:
var inner = childCursor(result)
skip inner # the explicit type / the node flags
result = inner
else:
break
proc cacheFrame(nav: var BodyNav): NavScope =
## Where a decoder-resolved name is remembered: the nearest ROUTINE frame.
## Not the innermost frame — a `.bif` name is unique within its module (see
## `isLocalSym`), so its meaning cannot change between frames, and caching it
## deeper would only throw it away sooner. Not the root either, so that a
## nested routine's names die with the nested routine.
result = nav.current
while result.kind != nsRoutine and result.parent != nil:
result = result.parent
proc bridgeIndex(n: Cursor; tag: string): int =
## The `<intlit>` payload of a `(bsym …)` / `(btyp …)` token, or -1 when `n`
## is not that shape.
result = -1
if nifcore.kind(n) == TagLit and n.tags.tagName(cursorTagId(n)) == tag:
let payload = childCursor(n)
if nifcore.kind(payload) == IntLit:
result = int(nifcore.intVal(payload))
proc symAt*(nav: var BodyNav; n: Cursor): PSym =
## The symbol a token names: the bridge first (exact), then the chain, then
## the decoder.
if nav.bridge != nil:
let idx = bridgeIndex(symToken(n), bridgeSymTagName)
if idx >= 0:
doAssert idx < nav.bridge.syms.len,
"bridged sym index out of range: " & $idx
inc nav.hits
return nav.bridge.syms[idx]
let name = navName(n)
if name.len > 0:
let cached = lookupLocal(nav, name)
if cached != nil:
inc nav.hits
return cached
inc nav.fallbacks
result = symFromCursor(program, symToken(n), nav.base)
if result != nil and name.len > 0 and not isFieldNifName(name):
cacheFrame(nav).locals[name] = result
proc typeAt*(nav: var BodyNav; n: Cursor): PType =
## Types are not navigated: `ast2nif` already materializes them lazily from
## the module's type index, keyed by name, so there is no per-body state to
## keep and nothing a frame could cache that the decoder does not already.
if nav.bridge != nil:
if nifcore.kind(n) == DotToken: return nil
let idx = bridgeIndex(n, bridgeTypeTagName)
if idx >= 0:
doAssert idx < nav.bridge.types.len,
"bridged type index out of range: " & $idx
return nav.bridge.types[idx]
result = typeFromCursor(program, n, nav.base)
# ---------------------------------------------------------------------------
# Registration during a walk
proc registerDefHere*(nav: var BodyNav; n: Cursor): bool {.discardable.} =
## Register `n` if `n` ITSELF is a definition; do not descend. This is the
## incremental half: a walk calls it on each child before recursing into it,
## so a use can only resolve from the chain to a definition the walk has
## already passed. A use that precedes its definition simply misses and falls
## through to the decoder, which is the behaviour there was before — the nav
## degrades to the old path rather than answering wrongly.
result = false
if nifcore.kind(n) == TagLit and
n.tags.tagName(cursorTagId(n)) == symDefTagName:
let name = navName(n)
if name.len > 0 and not isFieldNifName(name):
let s = symFromCursor(program, n, nav.base)
if s != nil:
registerLocal(nav, name, s)
result = true
proc registerDefs*(nav: var BodyNav; n: Cursor) =
## Register every definition in the SUBTREE at `n` — `typenav.registerLocals`
## with the recursion left in, because a Nim body puts `nkIdentDefs` under an
## `nkVarSection` under the statement list rather than declaring at one level.
##
## Call it on entering a scope to get the eager behaviour (every definition
## known before any use is resolved, which is what a RANDOM-ACCESS reader
## needs), or per statement to get the incremental one (only definitions
## already walked past are visible, which is what a real pass wants and what
## makes use-before-def detectable rather than silently working).
if nifcore.kind(n) == TagLit and
n.tags.tagName(cursorTagId(n)) == symDefTagName:
let name = navName(n)
if name.len > 0 and not isFieldNifName(name):
let s = symFromCursor(program, n, nav.base)
if s != nil: registerLocal(nav, name, s) # `(sd ...)` needs no peeling
return
var c = childCursor(n)
while c.hasMore:
registerDefs(nav, c)
skip c

View File

@@ -9,7 +9,7 @@
#
# included from cgen.nim
proc canRaiseDisp(p: BProc; n: AnyNode): bool =
proc canRaiseDisp(p: BProc; n: PNode): bool =
# we assume things like sysFatal cannot raise themselves
# 5 = "decided here, neither predicate ran". Without resetting, the marker
# keeps whatever the PREVIOUS call left in it and the early return below
@@ -33,20 +33,13 @@ proc canRaiseDisp(p: BProc; n: AnyNode): bool =
result = canRaiseConservative(n)
when defined(icCanRaiseLog):
# `canRaise` reads the raises spec off `fn.typ.n`, and under `--ic:on` that
# node came back from a `.bif`. Whether it came back INTACT is not something
# the `BNode`/`PNode` grinder can answer — both spellings ask the same
# `PType` and so agree however wrong it is. The only oracle is the same
# program built without IC. Log the verdict per callee; the two builds must
# produce the same one.
# node came back from a `.bif`. The only oracle for whether it came back
# INTACT is the same program built without IC. Log the verdict per callee;
# the two builds must produce the same one.
if n.kind == nkSym:
logCanRaise(n.sym, result)
proc preventNrvo(p: BProc; dest, le: PNode; ri: AnyNode): bool =
## `dest` and `le` stay `PNode`s: they are DESTINATIONS, which the whole call
## family keeps as `PNode`s so they can be nil and so they can be handed to
## the alias analysis, and it is also what keeps the `warnObservableStores`
## message able to RENDER `le` — rendering being a capability the cursor seam
## does not have at all. `ri`, the call being generated, is a cursor.
proc preventNrvo(p: BProc; dest, le, ri: PNode): bool =
proc locationEscapes(p: BProc; le: PNode; inTryStmt: bool): bool =
result = false
var n = le
@@ -74,9 +67,7 @@ proc preventNrvo(p: BProc; dest, le: PNode; ri: AnyNode): bool =
result = false
if le != nil:
for r in sonsFrom(ri, 1):
# `isPartOf` compares field symbols by identity and so has not moved to
# the seam; `origin` hands it the same nodes it always compared.
if isPartOf(le, origin(r), {pfStructural}) != arNo: return true
if isPartOf(le, r, {pfStructural}) != arNo: return true
# we use the weaker 'canRaise' here in order to prevent too many
# annoying warnings, see #14514
if canRaise(ri.firstSon) and
@@ -85,9 +76,9 @@ proc preventNrvo(p: BProc; dest, le: PNode; ri: AnyNode): bool =
# bug #19613 prevent dangerous aliasing too:
if dest != nil and dest != le:
for r in sonsFrom(ri, 1):
if isPartOf(dest, origin(r), {pfStructural}) != arNo: return true
if isPartOf(dest, r, {pfStructural}) != arNo: return true
proc hasNoInit(call: AnyNode): bool {.inline.} =
proc hasNoInit(call: PNode): bool {.inline.} =
result = call.firstSon.kind == nkSym and sfNoInit in call.firstSon.sym.flags
proc isHarmlessStore(p: BProc; canRaise: bool; d: TLoc): bool =
@@ -119,11 +110,7 @@ proc cleanupTemp(p: BProc; returnType: PType, tmp: TLoc): bool =
else:
result = false
# `le` — the assignment DESTINATION — stays a `PNode` throughout this family.
# It is nilable (`genCall` passes nil, and a cursor has no standalone nil), and
# it is what `preventNrvo` and `isPartOf` are handed, both of which are still
# `PNode`-typed. `ri`, the expression being generated, is the part that moves.
proc fixupCall(p: BProc, le: PNode, ri: AnyNode, d: var TLoc,
proc fixupCall(p: BProc, le: PNode, ri: PNode, d: var TLoc,
result: var Builder, call: var CallBuilder) =
let canRaise = p.config.exc == excGoto and canRaiseDisp(p, ri.firstSon)
genLineDir(p, ri)
@@ -207,7 +194,7 @@ proc fixupCall(p: BProc, le: PNode, ri: AnyNode, d: var TLoc,
proc genBoundsCheck(p: BProc; arr, a, b: TLoc; arrTyp: PType)
proc reifiedOpenArray(n: AnyNode): bool {.inline.} =
proc reifiedOpenArray(n: PNode): bool {.inline.} =
var x = n
while true:
case x.kind
@@ -222,7 +209,7 @@ proc reifiedOpenArray(n: AnyNode): bool {.inline.} =
else:
result = true
proc genOpenArraySlice(p: BProc; q: AnyNode; formalType, destType: PType; prepareForMutation = false): (Rope, Rope) =
proc genOpenArraySlice(p: BProc; q: PNode; formalType, destType: PType; prepareForMutation = false): (Rope, Rope) =
var a = initLocExpr(p, q.secondSon)
var b = initLocExpr(p, son(q, 2))
var c = initLocExpr(p, son(q, 3))
@@ -285,7 +272,7 @@ proc genOpenArraySlice(p: BProc; q: AnyNode; formalType, destType: PType; prepar
result = ("", "")
internalError(p.config, "openArrayLoc: " & typeToString(a.t))
proc openArrayLoc(p: BProc, formalType: PType, n: AnyNode; result: var Builder) =
proc openArrayLoc(p: BProc, formalType: PType, n: PNode; result: var Builder) =
var q = skipConv(n)
var skipped = false
while q.kind == nkStmtListExpr and q.hasSons:
@@ -395,13 +382,13 @@ proc expressionsNeedsTmp(p: BProc, a: TLoc): TLoc =
result = getTemp(p, a.lode.typ, needsInit=false)
genAssignment(p, result, a, {})
proc genArgStringToCString(p: BProc, n: AnyNode; result: var Builder; needsTmp: bool) {.inline.} =
proc genArgStringToCString(p: BProc, n: PNode; result: var Builder; needsTmp: bool) {.inline.} =
var a = initLocExpr(p, n.firstSon)
let tmp = withTmpIfNeeded(p, a, needsTmp)
let ra = if p.config.usesSso(): byRefLoc(p, tmp) else: tmp.rdLoc
result.addCall(cgsymValue(p.module, "nimToCStringConv"), ra)
proc genArg(p: BProc, n: AnyNode, param: PSym; call: AnyNode; result: var Builder; needsTmp = false) =
proc genArg(p: BProc, n: PNode, param: PSym; call: PNode; result: var Builder; needsTmp = false) =
var a: TLoc
if n.kind == nkStringToCString:
genArgStringToCString(p, n, result, needsTmp)
@@ -421,16 +408,10 @@ proc genArg(p: BProc, n: AnyNode, param: PSym; call: AnyNode; result: var Builde
# will be a reference in C++ and we cannot create a temporary reference
# variable. Thus, we create a temporary pointer variable instead.
let needsIndirect = mapType(p.config, n.firstSon.typ, mapTypeChooser(n.firstSon) == skParam) != ctArray
# A REWRITE, and one that has to be followed. The node's type is replaced in
# place, and a cursor would keep reading the type slot as it was ENCODED —
# the buffer does not see the mutation. So from here this site works on the
# origin, which is the node being mutated and therefore the one that has the
# new type.
let nn = origin(n)
if needsIndirect:
nn.typ = copyType(nn.typ, p.module.idgen, nn.typ.owner)
nn.typ.incl tfVarIsPtr
a = initLocExprSingleUse(p, nn)
n.typ = copyType(n.typ, p.module.idgen, n.typ.owner)
n.typ.incl tfVarIsPtr
a = initLocExprSingleUse(p, n)
a = withTmpIfNeeded(p, a, needsTmp)
if needsIndirect: a.flags.incl lfIndirect
# if the proc is 'importc'ed but not 'importcpp'ed then 'var T' still
@@ -452,7 +433,7 @@ proc genArg(p: BProc, n: AnyNode, param: PSym; call: AnyNode; result: var Builde
addRdLoc(withTmpIfNeeded(p, a, needsTmp), result)
#assert result != nil
proc genArgNoParam(p: BProc, n: AnyNode; result: var Builder; needsTmp = false) =
proc genArgNoParam(p: BProc, n: PNode; result: var Builder; needsTmp = false) =
var a: TLoc
if n.kind == nkStringToCString:
genArgStringToCString(p, n, result, needsTmp)
@@ -462,16 +443,13 @@ proc genArgNoParam(p: BProc, n: AnyNode; result: var Builder; needsTmp = false)
import aliasanalysis
proc potentialAlias(n: AnyNode, potentialWrites: seq[PNode]): bool =
proc potentialAlias(n: PNode, potentialWrites: seq[PNode]): bool =
result = false
for p in potentialWrites:
if p.aliases(n) != no or n.aliases(p) != no:
return true
proc skipTrivialIndirections[T: AnyNode](n: T): T =
## Explicitly generic rather than `(n: AnyNode): AnyNode`: two occurrences of
## a type class in one signature are two INDEPENDENT parameters, so that
## spelling would let the result type drift from the argument's.
proc skipTrivialIndirections(n: PNode): PNode =
result = n
while true:
case result.kind
@@ -481,7 +459,7 @@ proc skipTrivialIndirections[T: AnyNode](n: T): T =
result = result.secondSon
else: break
proc getPotentialReads(n: AnyNode; result: var seq[PNode]) =
proc getPotentialReads(n: PNode; result: var seq[PNode]) =
case n.kind:
of nkLiterals, nkIdent, nkFormalParams: discard
of nkSym: result.add n
@@ -489,22 +467,12 @@ proc getPotentialReads(n: AnyNode; result: var seq[PNode]) =
for s in sons(n):
getPotentialReads(s, result)
proc genParams(p: BProc, ri: AnyNode, typ: PType; result: var Builder, argBuilder: var CallBuilder) =
proc genParams(p: BProc, ri: PNode, typ: PType; result: var Builder, argBuilder: var CallBuilder) =
# We must generate temporaries in cases like #14396
# to keep the strict Left-To-Right evaluation
# The arguments are walked BACKWARDS below, which a `Cursor` cannot do and
# which costs a re-walk per step even on a `PNode`. Materialize them in one
# forward pass and index that; `needTmp` already allocates per call, so this
# is the same order of work.
#
# The arguments are materialized as `PNode`s, not cursors, because the alias
# analysis below (`potentialAlias`, `getPotentialReads`) carries a
# `seq[PNode]` beside the node and has not moved to the seam — see the
# mixed-representation blocker in `bnode`'s module doc. `origin` gives the
# same objects the tree-driven build used, so this is the argument list it
# always was; when that analysis moves, this becomes `seq[AnyNode]`.
# The arguments are walked BACKWARDS below; collect them once and index that.
var args: seq[PNode] = @[]
for it in sonsFrom(ri, 1): args.add origin(it)
for it in sonsFrom(ri, 1): args.add it
var needTmp = newSeq[bool](args.len)
var potentialWrites: seq[PNode] = @[]
for i in countdown(args.high, 0):
@@ -546,7 +514,7 @@ proc addActualSuffixForHCR(res: var Rope, module: PSym, sym: PSym) =
(sym.typ.callConv == ccInline or sym.owner.id == module.id):
res = res & "_actual".rope
proc genPrefixCall(p: BProc, le: PNode, ri: AnyNode, d: var TLoc) =
proc genPrefixCall(p: BProc, le: PNode, ri: PNode, d: var TLoc) =
# this is a hotspot in the compiler
var op = initLocExpr(p, ri.firstSon)
# getUniqueType() is too expensive here:
@@ -562,7 +530,7 @@ proc genPrefixCall(p: BProc, le: PNode, ri: AnyNode, d: var TLoc) =
genParams(p, ri, typ, res, call)
fixupCall(p, le, ri, d, res, call)
proc genClosureCall(p: BProc, le: PNode, ri: AnyNode, d: var TLoc) =
proc genClosureCall(p: BProc, le: PNode, ri: PNode, d: var TLoc) =
template callProc(rp, params, pTyp: Snippet): Snippet =
let e = dotField(rp, "ClE_0")
@@ -662,7 +630,7 @@ proc genClosureCall(p: BProc, le: PNode, ri: AnyNode, d: var TLoc) =
genCallPattern()
if canRaise: raiseExit(p)
proc genOtherArg(p: BProc; ri: AnyNode; i: int; typ: PType; result: var Builder;
proc genOtherArg(p: BProc; ri: PNode; i: int; typ: PType; result: var Builder;
argBuilder: var CallBuilder) =
if i < typ.n.len:
# 'var T' is 'T&' in C++. This means we ignore the request of
@@ -721,7 +689,7 @@ y.v() --> y.v() is correct
"""
proc skipAddrDeref[T: AnyNode](node: T): T =
proc skipAddrDeref(node: PNode): PNode =
var n = node
var isAddr = false
case n.kind
@@ -739,7 +707,7 @@ proc skipAddrDeref[T: AnyNode](node: T): T =
else:
result = node
proc genThisArg(p: BProc; ri: AnyNode; i: int; typ: PType; result: var Builder) =
proc genThisArg(p: BProc; ri: PNode; i: int; typ: PType; result: var Builder) =
# for better or worse c2nim translates the 'this' argument to a 'var T'.
# However manual wrappers may also use 'ptr T'. In any case we support both
# for convenience.
@@ -774,7 +742,7 @@ proc genThisArg(p: BProc; ri: AnyNode; i: int; typ: PType; result: var Builder)
genArgNoParam(p, ri, result) #, son(typ.n, i).sym)
result.add(".")
proc genPatternCall(p: BProc; ri: AnyNode; pat: string; typ: PType; result: var Builder) =
proc genPatternCall(p: BProc; ri: PNode; pat: string; typ: PType; result: var Builder) =
var i = 0
var j = 1
while i < pat.len:
@@ -828,7 +796,7 @@ proc genPatternCall(p: BProc; ri: AnyNode; pat: string; typ: PType; result: var
if i - 1 >= start:
result.add(substr(pat, start, i - 1))
proc genInfixCall(p: BProc, le: PNode, ri: AnyNode, d: var TLoc) =
proc genInfixCall(p: BProc, le: PNode, ri: PNode, d: var TLoc) =
var op = initLocExpr(p, ri.firstSon)
# getUniqueType() is too expensive here:
var typ = skipTypes(ri.firstSon.typ, abstractInst)
@@ -869,7 +837,7 @@ proc genInfixCall(p: BProc, le: PNode, ri: AnyNode, d: var TLoc) =
genOtherArg(p, ri, i, typ, res, call)
fixupCall(p, le, ri, d, res, call)
proc genNamedParamCall(p: BProc, ri: AnyNode, d: var TLoc) =
proc genNamedParamCall(p: BProc, ri: PNode, d: var TLoc) =
# generates a crappy ObjC call
var op = initLocExpr(p, ri.firstSon)
var pl = newBuilder("[")
@@ -936,11 +904,11 @@ proc genNamedParamCall(p: BProc, ri: AnyNode, d: var TLoc) =
p.s(cpsStmts).addStmt():
p.s(cpsStmts).add(extract(pl))
proc notYetAlive(n: AnyNode): bool {.inline.} =
proc notYetAlive(n: PNode): bool {.inline.} =
let r = getRoot(n)
result = r != nil and r.loc.lode == nil
proc isInactiveDestructorCall(p: BProc, e: AnyNode): bool =
proc isInactiveDestructorCall(p: BProc, e: PNode): bool =
#[ Consider this example.
var :tmpD_3281815
@@ -960,7 +928,7 @@ proc isInactiveDestructorCall(p: BProc, e: AnyNode): bool =
result = e.safeLen == 2 and e.firstSon.kind == nkSym and
e.firstSon.sym.name.s == "=destroy" and notYetAlive(e.secondSon.skipAddr)
proc genAsgnCall(p: BProc, le: PNode, ri: AnyNode, d: var TLoc) =
proc genAsgnCall(p: BProc, le: PNode, ri: PNode, d: var TLoc) =
if p.withinBlockLeaveActions > 0 and isInactiveDestructorCall(p, ri):
return
when defined(icDbgHash):
@@ -982,4 +950,4 @@ proc genAsgnCall(p: BProc, le: PNode, ri: AnyNode, d: var TLoc) =
else:
genPrefixCall(p, le, ri, d)
proc genCall(p: BProc, e: AnyNode, d: var TLoc) = genAsgnCall(p, nil, e, d)
proc genCall(p: BProc, e: PNode, d: var TLoc) = genAsgnCall(p, nil, e, d)

File diff suppressed because it is too large Load Diff

View File

@@ -53,11 +53,11 @@ proc genStringLiteralDataOnlyV1(m: BModule, s: string; result: var Rope) =
res.add(makeCString(s))
m.s[cfsStrData].add(extract(res))
proc genStringLiteralV1(m: BModule; n: AnyNode; result: var Builder) =
proc genStringLiteralV1(m: BModule; n: PNode; result: var Builder) =
if s.isNil:
result.add(cCast(ptrType(cgsymValue(m, "NimStringDesc")), NimNil))
else:
let id = nodeTableTestOrSet(m.dataCache, origin(n), m.labels)
let id = nodeTableTestOrSet(m.dataCache, n, m.labels)
var name: string = ""
if id == m.labels:
# string literal not found in the cache:
@@ -85,8 +85,8 @@ proc genStringLiteralDataOnlyV2(m: BModule, s: string; result: Rope; isConst: bo
res.add(makeCString(s))
m.s[cfsStrData].add(extract(res))
proc genStringLiteralV2(m: BModule; n: AnyNode; isConst: bool; result: var Builder) =
let id = nodeTableTestOrSet(m.dataCache, origin(n), m.labels)
proc genStringLiteralV2(m: BModule; n: PNode; isConst: bool; result: var Builder) =
let id = nodeTableTestOrSet(m.dataCache, n, m.labels)
var litName: string
if id == m.labels:
cgsym(m, "NimStrPayload")
@@ -111,8 +111,8 @@ proc genStringLiteralV2(m: BModule; n: AnyNode; isConst: bool; result: var Build
res.add(cCast(ptrType("NimStrPayload"), cAddr(litName)))
m.s[cfsStrData].add(extract(res))
proc genStringLiteralV2Const(m: BModule; n: AnyNode; isConst: bool; result: var Builder) =
let id = nodeTableTestOrSet(m.dataCache, origin(n), m.labels)
proc genStringLiteralV2Const(m: BModule; n: PNode; isConst: bool; result: var Builder) =
let id = nodeTableTestOrSet(m.dataCache, n, m.labels)
var pureLit: Rope
if id == m.labels:
pureLit = getTempName(m)
@@ -164,7 +164,7 @@ proc ssoMoreLit(m: BModule; s: string): string =
val = val or (ch shl (uint(ptrSize - 1 - i) * 8))
result = cCast(ptrType("LongString"), "(uintptr_t)" & $val)
proc genStringLiteralV3Const(m: BModule; n: AnyNode; isConst: bool; result: var Builder) =
proc genStringLiteralV3Const(m: BModule; n: PNode; isConst: bool; result: var Builder) =
# Inline SmallString struct initializer for use inside const aggregate types.
# Layout: {bytes: NimUint, more: ptr LongString}
# bytes = slen (low byte) | char[0]<<8 | char[1]<<16 | ... | char[6]<<56
@@ -220,7 +220,7 @@ proc genStringLiteralV3Const(m: BModule; n: AnyNode; isConst: bool; result: var
# ------ Version 3: SmallString (SSO) strings --------------------------------
proc genStringLiteralV3(m: BModule; n: AnyNode; isConst: bool; result: var Builder) =
proc genStringLiteralV3(m: BModule; n: PNode; isConst: bool; result: var Builder) =
# SmallString literal. Always generate a fresh SmallString variable (like v2
# always generates a fresh outer NimStringV2). For long strings, cache the
# LongString payload to avoid duplicates within a module.
@@ -259,7 +259,7 @@ proc genStringLiteralV3(m: BModule; n: AnyNode; isConst: bool; result: var Build
else:
# Long: cache the LongString block to emit it only once per module per string.
# Always generate a fresh SmallString pointing at the (possibly cached) block.
let id = nodeTableTestOrSet(m.dataCache, origin(n), m.labels)
let id = nodeTableTestOrSet(m.dataCache, n, m.labels)
var dataName: string
if id == m.labels:
dataName = getTempName(m)
@@ -301,7 +301,7 @@ proc genStringLiteralV3(m: BModule; n: AnyNode; isConst: bool; result: var Build
proc genNilStringLiteral(m: BModule; info: TLineInfo; result: var Builder) =
result.add(cCast(ptrType(cgsymValue(m, "NimStringDesc")), NimNil))
proc genStringLiteral(m: BModule; n: AnyNode; result: var Builder) =
proc genStringLiteral(m: BModule; n: PNode; result: var Builder) =
case detectStrVersion(m)
of 0, 1: genStringLiteralV1(m, n, result)
of 2: genStringLiteralV2(m, n, isConst = true, result)

View File

@@ -31,10 +31,10 @@ proc registerTraverseProc(p: BProc, v: PSym) =
p.module.preInitProc.procSec(cpsInit).addCallStmt(fnName, traverseProc)
p.module.preInitProc.procSec(cpsInit).add("\n")
proc isAssignedImmediately(conf: ConfigRef; n: AnyNode): bool {.inline.} =
proc isAssignedImmediately(conf: ConfigRef; n: PNode): bool {.inline.} =
if n.kind == nkEmpty:
result = false
elif n.kind in nkCallKinds and not n.firstSon.isNilNode and n.firstSon.typ != nil and n.firstSon.typ.skipTypes(abstractInst).kind == tyProc:
elif n.kind in nkCallKinds and n.firstSon != nil and n.firstSon.typ != nil and n.firstSon.typ.skipTypes(abstractInst).kind == tyProc:
if n.firstSon.kind == nkSym and sfConstructor in n.firstSon.sym.flags:
result = true
elif isInvalidReturnType(conf, n.firstSon.typ, true):
@@ -94,13 +94,13 @@ template endBlockWith(p: BProc, body: typed) =
body
endBlockOutside(p, label)
proc genVarTuple(p: BProc, n: AnyNode) =
proc genVarTuple(p: BProc, n: PNode) =
if n.kind != nkVarTuple: internalError(p.config, n.info, "genVarTuple")
# if we have a something that's been captured, use the lowering instead:
for it in sonsButLast(n, 2):
if it.kind != nkSym:
genStmts(p, lowerTupleUnpacking(p.module.g.graph, origin(n), p.module.idgen, p.prc))
genStmts(p, lowerTupleUnpacking(p.module.g.graph, n, p.module.idgen, p.prc))
return
# check only the first son
@@ -172,7 +172,7 @@ proc genVarTuple(p: BProc, n: AnyNode) =
cCast(ptrType(CPointer), cAddr(curr.loc.snippet))))
proc loadInto(p: BProc, le: PNode, ri: AnyNode, a: var TLoc) {.inline.} =
proc loadInto(p: BProc, le: PNode, ri: PNode, a: var TLoc) {.inline.} =
## `le` is the DESTINATION and stays a `PNode` — it only ever reaches
## `genAsgnCall`, which keeps it a `PNode` for the alias analysis.
if ri.kind in nkCallKinds and (ri.firstSon.kind != nkSym or
@@ -201,13 +201,13 @@ proc endSimpleBlock(p: BProc, scope: var ScopeBuilder) {.inline.} =
endBlockWith(p):
finishScope(p.s(cpsStmts), scope)
proc genSimpleBlock(p: BProc, stmts: AnyNode) {.inline.} =
proc genSimpleBlock(p: BProc, stmts: PNode) {.inline.} =
var scope: ScopeBuilder
startSimpleBlock(p, scope)
genStmts(p, stmts)
endSimpleBlock(p, scope)
proc exprBlock(p: BProc, n: AnyNode, d: var TLoc) =
proc exprBlock(p: BProc, n: PNode, d: var TLoc) =
var scope: ScopeBuilder
startSimpleBlock(p, scope)
expr(p, n, d)
@@ -218,7 +218,7 @@ template preserveBreakIdx(body: untyped): untyped =
body
p.breakIdx = oldBreakIdx
proc genState(p: BProc, n: AnyNode) =
proc genState(p: BProc, n: PNode) =
internalAssert p.config, n.len == 1
let n0 = n.firstSon
if n0.kind == nkIntLit:
@@ -263,7 +263,7 @@ proc blockLeaveActions(p: BProc, howManyTrys, howManyExcepts: int, isReturnStmt
for i in countdown(howManyExcepts-1, 0):
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "popCurrentException"))
proc genGotoState(p: BProc, n: AnyNode) =
proc genGotoState(p: BProc, n: PNode) =
# we resist the temptation to translate it into duff's device as it later
# will be translated into computed gotos anyway for GCC at least:
# switch (x.state) {
@@ -287,7 +287,7 @@ proc genGotoState(p: BProc, n: AnyNode) =
p.s(cpsStmts).addSingleSwitchCase(cIntValue(i)):
p.s(cpsStmts).addGoto(prefix & $i)
proc genBreakState(p: BProc, n: AnyNode, d: var TLoc) =
proc genBreakState(p: BProc, n: PNode, d: var TLoc) =
var a: TLoc
d = initLoc(locExpr, n, OnUnknown)
@@ -309,23 +309,23 @@ proc genBreakState(p: BProc, n: AnyNode, d: var TLoc) =
cIntValue(1)),
cIntValue(0))
proc genGotoVar(p: BProc; value: AnyNode) =
proc genGotoVar(p: BProc; value: PNode) =
if value.kind notin {nkCharLit..nkUInt64Lit}:
localError(p.config, value.info, "'goto' target must be a literal value")
else:
p.s(cpsStmts).addGoto("NIMSTATE_" & $value.intVal)
proc genBracedInit(p: BProc, n: AnyNode; isConst: bool; optionalType: PType; result: var Builder)
proc genBracedInit(p: BProc, n: PNode; isConst: bool; optionalType: PType; result: var Builder)
proc potentialValueInit(p: BProc; v: PSym; value: AnyNode; result: var Builder) =
proc potentialValueInit(p: BProc; v: PSym; value: PNode; result: var Builder) =
if lfDynamicLib in v.loc.flags or sfThread in v.flags or p.hcrOn:
discard "nothing to do"
elif sfGlobal in v.flags and not value.isNilNode and isDeepConstExpr(value, p.module.compileToCpp) and
elif sfGlobal in v.flags and value != nil and isDeepConstExpr(value, p.module.compileToCpp) and
p.withinLoop == 0 and not containsGarbageCollectedRef(v.typ):
#echo "New code produced for ", v.name.s, " ", p.config $ value.info
genBracedInit(p, value, isConst = false, v.typ, result)
proc genCppParamsForCtor(p: BProc; call: AnyNode; didGenTemp: var bool): Snippet =
proc genCppParamsForCtor(p: BProc; call: PNode; didGenTemp: var bool): Snippet =
var res = newBuilder("")
var argBuilder = default(CallBuilder) # not init, only building params
let typ = skipTypes(call.firstSon.typ, abstractInst)
@@ -350,11 +350,7 @@ proc genCppParamsForCtor(p: BProc; call: AnyNode; didGenTemp: var bool): Snippet
genOtherArg(p, call, i, typ, res, argBuilder)
result = extract(res)
proc genSingleVar[V: AnyNode; W: AnyNode](p: BProc, v: PSym; vn: V; value: W) =
## `vn` and `value` are SEPARATE type parameters, not one shared: the
## definition site is a body node while the value can come from the symbol's
## own AST (`astdef`), so the two are not necessarily the same
## representation.
proc genSingleVar(p: BProc, v: PSym; vn: PNode; value: PNode) =
if sfGoto in v.flags:
# translate 'var state {.goto.} = X' into 'goto LX':
genGotoVar(p, value)
@@ -468,13 +464,13 @@ proc genSingleVar[V: AnyNode; W: AnyNode](p: BProc, v: PSym; vn: V; value: W) =
genLineDir(targetProc, vn)
if not isCppCtorCall:
backendEnsureMutable v
loadInto(targetProc, origin(vn), value, v.locImpl)
loadInto(targetProc, vn, value, v.locImpl)
if forHcr:
endBlockWith(targetProc):
finishBranch(p.s(cpsStmts), hcrInit)
finishIfStmt(p.s(cpsStmts), hcrInit)
proc genSingleVar(p: BProc, a: AnyNode) =
proc genSingleVar(p: BProc, a: PNode) =
let v = a.firstSon.sym
if sfCompileTime in v.flags:
# fix issue #12640
@@ -485,16 +481,16 @@ proc genSingleVar(p: BProc, a: AnyNode) =
return
genSingleVar(p, v, a.firstSon, son(a, 2))
proc genClosureVar(p: BProc, a: AnyNode) =
proc genClosureVar(p: BProc, a: PNode) =
var immediateAsgn = son(a, 2).kind != nkEmpty
var v: TLoc = initLocExpr(p, a.firstSon)
genLineDir(p, a)
if immediateAsgn:
loadInto(p, origin(a.firstSon), son(a, 2), v)
loadInto(p, a.firstSon, son(a, 2), v)
elif sfNoInit notin a.firstSon.secondSon.sym.flags:
constructLoc(p, v)
proc genVarStmt(p: BProc, n: AnyNode) =
proc genVarStmt(p: BProc, n: PNode) =
for it in sons(n):
case it.kind
of nkCommentStmt: discard
@@ -509,7 +505,7 @@ proc genVarStmt(p: BProc, n: AnyNode) =
else:
genVarTuple(p, it)
proc genIf(p: BProc, n: AnyNode, d: var TLoc) =
proc genIf(p: BProc, n: PNode, d: var TLoc) =
#
# { if (!expr1) goto L1;
# thenPart }
@@ -558,7 +554,7 @@ proc genIf(p: BProc, n: AnyNode, d: var TLoc) =
else: internalError(p.config, n.info, "genIf()")
if n.len > 1: fixLabel(p, lend)
proc genReturnStmt(p: BProc, t: AnyNode) =
proc genReturnStmt(p: BProc, t: PNode) =
if nfPreventCg in t.flags: return
p.flags.incl beforeRetNeeded
genLineDir(p, t)
@@ -578,7 +574,7 @@ proc genReturnStmt(p: BProc, t: AnyNode) =
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "popCurrentException"))
p.s(cpsStmts).addGoto("BeforeRet_")
proc genGotoForCase(p: BProc; caseStmt: AnyNode) =
proc genGotoForCase(p: BProc; caseStmt: PNode) =
for child in sonsFrom(caseStmt, 1):
var scope: ScopeBuilder
startSimpleBlock(p, scope)
@@ -601,14 +597,14 @@ iterator fieldValuePairs(n: PNode): tuple[memberSym, valueSym: PNode] =
for memberSym in sonsButLast(identDefs, 2):
yield((memberSym: memberSym, valueSym: valueSym))
proc genComputedGoto(p: BProc; n: AnyNode) =
proc genComputedGoto(p: BProc; n: PNode) =
# first pass: Generate array of computed labels:
# flatten the loop body because otherwise let and var sections
# wrapped inside stmt lists by inject destructors won't be recognised
# REBUILDS the statement list, so from here this proc works on
# a fresh `PNode` tree — there is nothing in the buffer corresponding to it.
let n = origin(n).flattenStmts()
let n = n.flattenStmts()
var casePos = -1
var arraySize: int = 0
for i, it in isons(n):
@@ -696,7 +692,7 @@ proc genComputedGoto(p: BProc; n: AnyNode) =
genStmts(p, it)
proc genWhileStmt(p: BProc, t: AnyNode) =
proc genWhileStmt(p: BProc, t: PNode) =
# we don't generate labels here as for example GCC would produce
# significantly worse code
var
@@ -735,7 +731,7 @@ proc genWhileStmt(p: BProc, t: AnyNode) =
dec(p.withinLoop)
proc genBlock(p: BProc, n: AnyNode, d: var TLoc) =
proc genBlock(p: BProc, n: PNode, d: var TLoc) =
if not isEmptyType(n.typ):
# bug #4505: allocate the temp in the outer scope
# so that it can escape the generated {}:
@@ -756,7 +752,7 @@ proc genBlock(p: BProc, n: AnyNode, d: var TLoc) =
expr(p, n.secondSon, d)
endSimpleBlock(p, scope)
proc genParForStmt(p: BProc, t: AnyNode) =
proc genParForStmt(p: BProc, t: PNode) =
assert(t.len == 3)
inc(p.withinLoop)
genLineDir(p, t)
@@ -779,7 +775,7 @@ proc genParForStmt(p: BProc, t: AnyNode) =
else:
p.s(cpsStmts).addCPragma(son(call, 3).getStr)
else: # `||`(a, b, step, annotation)
stepNode = origin(son(call, 3))
stepNode = son(call, 3)
p.s(cpsStmts).addCPragma("omp " & son(call, 4).getStr)
p.breakIdx = startBlockWith(p):
@@ -795,7 +791,7 @@ proc genParForStmt(p: BProc, t: AnyNode) =
dec(p.withinLoop)
proc genBreakStmt(p: BProc, t: AnyNode) =
proc genBreakStmt(p: BProc, t: PNode) =
var idx = p.breakIdx
if t.firstSon.kind != nkEmpty:
# named break?
@@ -876,7 +872,7 @@ proc raiseInstr(p: BProc; result: var Builder) =
result.addGoto("LA" & $p.nestedTryStmts[L-1].label & "_")
# + ord(p.nestedTryStmts[L-1].inExcept)])
proc genRaiseStmt(p: BProc, t: AnyNode) =
proc genRaiseStmt(p: BProc, t: PNode) =
if t.firstSon.kind != nkEmpty:
var a: TLoc = initLocExprSingleUse(p, t.firstSon)
finallyActions(p)
@@ -913,7 +909,7 @@ proc genRaiseStmt(p: BProc, t: AnyNode) =
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "reraiseException"))
raiseInstr(p, p.s(cpsStmts))
template genCaseGenericBranch(p: BProc, b: AnyNode, e: TLoc, labl: TLabel,
template genCaseGenericBranch(p: BProc, b: PNode, e: TLoc, labl: TLabel,
rangeFormat, eqFormat: untyped) =
var x, y: TLoc
for it in sonsButLast(b):
@@ -931,7 +927,7 @@ template genCaseGenericBranch(p: BProc, b: AnyNode, e: TLoc, labl: TLabel,
let rb {.inject.} = rdCharLoc(x)
eqFormat
proc genCaseSecondPass(p: BProc, t: AnyNode, d: var TLoc,
proc genCaseSecondPass(p: BProc, t: PNode, d: var TLoc,
labId, until: int): TLabel =
var lend = getLabel(p)
for i, branch in isons(t, 1):
@@ -946,7 +942,7 @@ proc genCaseSecondPass(p: BProc, t: AnyNode, d: var TLoc,
exprBlock(p, branch.firstSon, d)
result = lend
template genIfForCaseUntil(p: BProc, t: AnyNode, d: var TLoc,
template genIfForCaseUntil(p: BProc, t: PNode, d: var TLoc,
until: int, a: TLoc,
rangeFormat, eqFormat: untyped): TLabel =
# generate a C-if statement for a Nim case statement
@@ -970,13 +966,13 @@ template genIfForCaseUntil(p: BProc, t: AnyNode, d: var TLoc,
res = genCaseSecondPass(p, t, d, labId, until)
res
template genCaseGeneric(p: BProc, t: AnyNode, d: var TLoc,
template genCaseGeneric(p: BProc, t: PNode, d: var TLoc,
rangeFormat, eqFormat: untyped) =
var a: TLoc = initLocExpr(p, t.firstSon)
var lend = genIfForCaseUntil(p, t, d, t.safeLen-1, a, rangeFormat, eqFormat)
fixLabel(p, lend)
proc genCaseStringBranch(p: BProc, b: AnyNode, e: TLoc, labl: TLabel,
proc genCaseStringBranch(p: BProc, b: PNode, e: TLoc, labl: TLabel,
stringKind: TTypeKind,
branches: var openArray[Builder]) =
var x: TLoc
@@ -998,7 +994,7 @@ proc genCaseStringBranch(p: BProc, b: AnyNode, e: TLoc, labl: TLabel,
do:
branches[j].addGoto(labl)
proc genStringCase(p: BProc, t: AnyNode, stringKind: TTypeKind, d: var TLoc) =
proc genStringCase(p: BProc, t: PNode, stringKind: TTypeKind, d: var TLoc) =
# count how many constant strings there are in the case:
var strings = 0
for it in sonsFrom(t, 1):
@@ -1047,7 +1043,7 @@ proc genStringCase(p: BProc, t: AnyNode, stringKind: TTypeKind, d: var TLoc) =
cCall(eqFn, ra, rb)):
p.s(cpsStmts).addGoto(rlabel)
proc branchHasTooBigRange(b: AnyNode): bool =
proc branchHasTooBigRange(b: PNode): bool =
result = false
for it in sons(b):
# last son is block
@@ -1055,7 +1051,7 @@ proc branchHasTooBigRange(b: AnyNode): bool =
it.secondSon.intVal - it.firstSon.intVal > RangeExpandLimit:
return true
proc ifSwitchSplitPoint(p: BProc, n: AnyNode): int =
proc ifSwitchSplitPoint(p: BProc, n: PNode): int =
result = 0
for i, branch in isons(n, 1):
var stmtBlock = lastSon(branch)
@@ -1065,7 +1061,7 @@ proc ifSwitchSplitPoint(p: BProc, n: AnyNode): int =
if branch.kind == nkOfBranch and branchHasTooBigRange(branch):
result = i
proc genCaseRange(p: BProc, branch: AnyNode, info: var SwitchCaseBuilder) =
proc genCaseRange(p: BProc, branch: PNode, info: var SwitchCaseBuilder) =
for it in sonsButLast(branch):
if it.kind == nkRange:
if hasSwitchRange in CC[p.config.cCompiler].props:
@@ -1075,9 +1071,7 @@ proc genCaseRange(p: BProc, branch: AnyNode, info: var SwitchCaseBuilder) =
genLiteral(p, it.secondSon, litB)
p.s(cpsStmts).addCaseRange(info, extract(litA), extract(litB))
else:
# A working COPY is mutated in the loop below, so it is a `PNode`
# built from the origin — there is nothing to mutate on a cursor.
var v = copyNode(origin(it.firstSon))
var v = copyNode(it.firstSon)
while v.intVal <= it.secondSon.intVal:
var litA = newBuilder("")
genLiteral(p, v, litA)
@@ -1088,7 +1082,7 @@ proc genCaseRange(p: BProc, branch: AnyNode, info: var SwitchCaseBuilder) =
genLiteral(p, it, litA)
p.s(cpsStmts).addCase(info, extract(litA))
proc genOrdinalCase(p: BProc, n: AnyNode, d: var TLoc) =
proc genOrdinalCase(p: BProc, n: PNode, d: var TLoc) =
# analyse 'case' statement:
var splitPoint = ifSwitchSplitPoint(p, n)
@@ -1134,7 +1128,7 @@ proc genOrdinalCase(p: BProc, n: AnyNode, d: var TLoc) =
p.s(cpsStmts).addCallStmt("__assume", cIntValue(0))
if lend != "": fixLabel(p, lend)
proc genCase(p: BProc, t: AnyNode, d: var TLoc) =
proc genCase(p: BProc, t: PNode, d: var TLoc) =
genLineDir(p, t)
if not isEmptyType(t.typ) and d.k == locNone:
d = getTemp(p, t.typ)
@@ -1170,7 +1164,7 @@ proc genRestoreFrameAfterException(p: BProc) =
p.procSec(cpsInit).addCall(cgsymValue(p.module, "getFrame"))
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "setFrame"), "_nimCurFrame")
proc genTryCpp(p: BProc, t: AnyNode, d: var TLoc) =
proc genTryCpp(p: BProc, t: PNode, d: var TLoc) =
#[ code to generate:
std::exception_ptr error;
@@ -1206,7 +1200,7 @@ proc genTryCpp(p: BProc, t: AnyNode, d: var TLoc) =
#init on locals, fixes #23306
lineCg(p, cpsLocals, "std::exception_ptr T$1_;$n", [etmp])
let fin = if t.lastSon.kind == nkFinally: origin(t.lastSon) else: nil
let fin = if t.lastSon.kind == nkFinally: t.lastSon else: nil
p.nestedTryStmts.add((fin, false, t.kind == nkHiddenTryStmt, 0.Natural))
if t.kind == nkHiddenTryStmt:
@@ -1261,7 +1255,7 @@ proc genTryCpp(p: BProc, t: AnyNode, d: var TLoc) =
var typeNode = label
if label.isInfixAs():
typeNode = label.secondSon
exvar = origin(son(label, 2)) # ex1 in `except ExceptType as ex1:`
exvar = son(label, 2) # ex1 in `except ExceptType as ex1:`
assert(typeNode.kind == nkType)
if isImportedException(typeNode.typ, p.config):
hasImportedCppExceptions = true
@@ -1302,7 +1296,7 @@ proc genTryCpp(p: BProc, t: AnyNode, d: var TLoc) =
linefmt(p, cpsStmts, "}$n", [])
# Second pass: handle C++ based exceptions:
template genExceptBranchBody(body: AnyNode) {.dirty.} =
template genExceptBranchBody(body: PNode) {.dirty.} =
genRestoreFrameAfterException(p)
#linefmt(p, cpsStmts, "T$1_ = std::current_exception();$n", [etmp])
expr(p, body, d)
@@ -1330,7 +1324,7 @@ proc genTryCpp(p: BProc, t: AnyNode, d: var TLoc) =
if label.isInfixAs():
typeNode = label.secondSon
if isImportedException(typeNode.typ, p.config):
let exvar = origin(son(label, 2)) # ex1 in `except ExceptType as ex1:`
let exvar = son(label, 2) # ex1 in `except ExceptType as ex1:`
fillLocalName(p, exvar.sym)
backendEnsureMutable exvar.sym
fillLoc(exvar.sym.locImpl, locTemp, exvar, OnStack)
@@ -1364,7 +1358,7 @@ proc genTryCpp(p: BProc, t: AnyNode, d: var TLoc) =
linefmt(p, cpsStmts, "if (T$1_) std::rethrow_exception(T$1_);$n", [etmp])
endSimpleBlock(p, scope)
proc bodyCanRaise(p: BProc; n: AnyNode): bool =
proc bodyCanRaise(p: BProc; n: PNode): bool =
case n.kind
of nkCallKinds:
result = canRaiseDisp(p, n.firstSon)
@@ -1382,8 +1376,8 @@ proc bodyCanRaise(p: BProc; n: AnyNode): bool =
for it in sons(n):
if bodyCanRaise(p, it): return true
proc genTryGoto(p: BProc; t: AnyNode; d: var TLoc) =
let fin = if t.lastSon.kind == nkFinally: origin(t.lastSon) else: nil
proc genTryGoto(p: BProc; t: PNode; d: var TLoc) =
let fin = if t.lastSon.kind == nkFinally: t.lastSon else: nil
inc p.labels
let lab = p.labels
let hasExcept = t.secondSon.kind == nkExceptBranch
@@ -1516,7 +1510,7 @@ proc genTryGoto(p: BProc; t: AnyNode; d: var TLoc) =
raiseExit(p)
if hasExcept: inc p.withinTryWithExcept
proc genTrySetjmp(p: BProc, t: AnyNode, d: var TLoc) =
proc genTrySetjmp(p: BProc, t: PNode, d: var TLoc) =
# code to generate:
#
# XXX: There should be a standard dispatch algorithm
@@ -1595,7 +1589,7 @@ proc genTrySetjmp(p: BProc, t: AnyNode, d: var TLoc) =
nonQuirkyIf = initIfStmt(p.s(cpsStmts))
initElifBranch(p.s(cpsStmts), nonQuirkyIf, removeSinglePar(
cOp(Equal, dotField(safePoint, "status"), cIntValue(0))))
let fin = if t.lastSon.kind == nkFinally: origin(t.lastSon) else: nil
let fin = if t.lastSon.kind == nkFinally: t.lastSon else: nil
p.nestedTryStmts.add((fin, quirkyExceptions, t.kind == nkHiddenTryStmt, 0.Natural))
expr(p, t.firstSon, d)
var quirkyIf = default(IfBuilder)
@@ -1718,7 +1712,7 @@ proc genTrySetjmp(p: BProc, t: AnyNode, d: var TLoc) =
cIntValue(0))):
p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "reraiseException"))
proc genAsmOrEmitStmt(p: BProc, t: AnyNode, isAsmStmt=false; result: var Rope) =
proc genAsmOrEmitStmt(p: BProc, t: PNode, isAsmStmt=false; result: var Rope) =
var res = ""
let offset =
if isAsmStmt: 1 # first son is pragmas
@@ -1764,7 +1758,7 @@ proc genAsmOrEmitStmt(p: BProc, t: AnyNode, isAsmStmt=false; result: var Rope) =
res.add("\L")
result.add res.rope
proc genAsmStmt(p: BProc, t: AnyNode) =
proc genAsmStmt(p: BProc, t: PNode) =
assert(t.kind == nkAsmStmt)
genLineDir(p, t)
var s = newRopeAppender()
@@ -1794,7 +1788,7 @@ proc genAsmStmt(p: BProc, t: AnyNode) =
addIndent p, p.s(cpsStmts)
p.s(cpsStmts).add runtimeFormat(CC[p.config.cCompiler].asmStmtFrmt, [s])
proc determineSection(n: AnyNode): TCFileSection =
proc determineSection(n: PNode): TCFileSection =
result = cfsProcHeaders
if n.len >= 1 and n.firstSon.kind in {nkStrLit..nkTripleStrLit}:
let sec = n.firstSon.strVal
@@ -1802,7 +1796,7 @@ proc determineSection(n: AnyNode): TCFileSection =
elif sec.startsWith("/*VARSECTION*/"): result = cfsVars
elif sec.startsWith("/*INCLUDESECTION*/"): result = cfsHeaders
proc genEmit(p: BProc, t: AnyNode) =
proc genEmit(p: BProc, t: PNode) =
var s = newRopeAppender()
genAsmOrEmitStmt(p, t.secondSon, false, s)
if p.prc == nil:
@@ -1814,12 +1808,12 @@ proc genEmit(p: BProc, t: AnyNode) =
genLineDir(p, t)
line(p, cpsStmts, s)
proc genPragma(p: BProc, n: AnyNode) =
proc genPragma(p: BProc, n: PNode) =
for i, it in isons(n):
case whichPragma(it)
of wEmit: genEmit(p, it)
of wPush:
processPushBackendOption(p.config, p.optionsStack, p.options, origin(n), i+1)
processPushBackendOption(p.config, p.optionsStack, p.options, n, i+1)
of wPop:
processPopBackendOption(p.config, p.optionsStack, p.options)
else: discard
@@ -1845,7 +1839,7 @@ proc genDiscriminantCheck(p: BProc, a, tmp: TLoc, objtype: PType,
if p.config.exc == excGoto:
raiseExit(p)
proc asgnFieldDiscriminant(p: BProc, e: AnyNode) =
proc asgnFieldDiscriminant(p: BProc, e: PNode) =
var dotExpr = e.firstSon
if dotExpr.kind == nkCheckedFieldExpr: dotExpr = dotExpr.firstSon
var a = initLocExpr(p, e.firstSon)
@@ -1857,7 +1851,7 @@ proc asgnFieldDiscriminant(p: BProc, e: AnyNode) =
message(p.config, e.info, warnCaseTransition)
genAssignment(p, a, tmp, {})
proc genAsgn(p: BProc, e: AnyNode, fastAsgn: bool) =
proc genAsgn(p: BProc, e: PNode, fastAsgn: bool) =
if e.firstSon.kind == nkSym and sfGoto in e.firstSon.sym.flags:
genLineDir(p, e)
genGotoVar(p, e.secondSon)
@@ -1886,9 +1880,9 @@ proc genAsgn(p: BProc, e: AnyNode, fastAsgn: bool) =
if fastAsgn: incl(a.flags, lfNoDeepCopy)
assert(a.t != nil)
genLineDir(p, ri)
loadInto(p, origin(le), ri, a)
loadInto(p, le, ri, a)
proc genStmts(p: BProc, t: AnyNode) =
proc genStmts(p: BProc, t: PNode) =
var a: TLoc = default(TLoc)
let isPush = p.config.hasHint(hintExtendedContext)

View File

@@ -18,7 +18,7 @@ type
proc genTraverseProc(c: TTraversalClosure, accessor: Rope, typ: PType)
proc genCaseRange(p: BProc, branch: AnyNode, info: var SwitchCaseBuilder)
proc genCaseRange(p: BProc, branch: PNode, info: var SwitchCaseBuilder)
proc getTemp(p: BProc, t: PType, needsInit=false): TLoc
proc visit(p: BProc, data, visitor: Snippet) =

View File

@@ -747,7 +747,7 @@ proc hasCppCtor(m: BModule; typ: PType): bool =
if sfConstructor in prc.flags:
return true
proc genCppParamsForCtor(p: BProc; call: AnyNode; didGenTemp: var bool): string
proc genCppParamsForCtor(p: BProc; call: PNode; didGenTemp: var bool): string
proc genCppInitializer(m: BModule, prc: BProc; typ: PType; didGenTemp: var bool): string =
#To avoid creating a BProc per test when called inside a struct nil BProc is allowed

View File

@@ -11,7 +11,7 @@
import
ast, types, msgs, wordrecg,
platform, trees, options, cgendata, mangleutils, renderer, modulegraphs, bnode
platform, trees, options, cgendata, mangleutils, renderer, modulegraphs
import std/[hashes, strutils, formatfloat]
@@ -32,28 +32,8 @@ proc getPragmaStmt*(n: PNode, w: TSpecialWord): PNode =
else:
result = nil
proc stmtsContainPragma*(n: AnyNode, w: TSpecialWord): bool =
## Deliberately NOT `getPragmaStmt(n, w) != nil`, and the reason is the one
## shape the `AnyNode` seam cannot serve: a proc that returns a node OR nil.
## `.bif` spells a missing child as a `DotToken` *inside* a tree, so there is
## no nil token to hand back as a return value, and a `Cursor` is not nilable.
## Predicates split out from such a proc are the way across.
##
## The duplicated traversal is the cost, and it is checked rather than
## trusted: `grindPredicates` asserts this answers exactly
## `getPragmaStmt(n, w) != nil` at every node, so the two cannot drift apart
## silently.
case n.kind
of nkStmtList:
result = false
for it in sons(n):
if stmtsContainPragma(it, w): return true
of nkPragma:
result = false
for it in sons(n):
if whichPragma(it) == w: return true
else:
result = false
proc stmtsContainPragma*(n: PNode, w: TSpecialWord): bool =
result = getPragmaStmt(n, w) != nil
proc hashString*(conf: ConfigRef; s: string): BiggestInt =
# has to be the same algorithm as strmantle.hashString!

View File

@@ -16,7 +16,7 @@ import
rodutils, renderer, cgendata, aliases,
lowerings, lineinfos, pathutils, transf,
injectdestructors, astmsgs, modulepaths, pushpoppragmas,
mangleutils, cbuilderbase, modulegraphs, bnode
mangleutils, cbuilderbase, modulegraphs, icprof
from expanddefaults import caseObjDefaultBranch
from ast2nif import globalName, toNifFilename, icNifTypeName
@@ -282,30 +282,23 @@ proc emitsBodyInThisModule(m: BModule, prc: PSym): bool =
else:
result = prc.itemId.module == m.module.position
# `TLoc.lode` stays a `PNode` even when the generator is driven off a cursor,
# and `origin` is why: on a bridged buffer it answers the very node the encoder
# was handed, so a location built from a cursor holds the same object a location
# built from the tree would have held. That is what keeps the identity
# comparisons the backend already does (`preventNrvo`'s `dest != le`,
# `isPartOf(d.lode, …)`) meaning what they meant. Taking `AnyNode` here is what
# unblocks the 99 generator procs that build a location from their node.
proc initLoc(k: TLocKind, lode: AnyNode, s: TStorageLoc, flags: TLocFlags = {}): TLoc =
result = TLoc(k: k, storage: s, lode: origin(lode),
proc initLoc(k: TLocKind, lode: PNode, s: TStorageLoc, flags: TLocFlags = {}): TLoc =
result = TLoc(k: k, storage: s, lode: lode,
snippet: "", flags: flags)
proc fillLoc(a: var TLoc, k: TLocKind, lode: AnyNode, r: Rope, s: TStorageLoc) {.inline.} =
proc fillLoc(a: var TLoc, k: TLocKind, lode: PNode, r: Rope, s: TStorageLoc) {.inline.} =
# fills the loc if it is not already initialized
if a.k == locNone:
a.k = k
a.lode = origin(lode)
a.lode = lode
a.storage = s
if a.snippet == "": a.snippet = r
proc fillLoc(a: var TLoc, k: TLocKind, lode: AnyNode, s: TStorageLoc) {.inline.} =
proc fillLoc(a: var TLoc, k: TLocKind, lode: PNode, s: TStorageLoc) {.inline.} =
# fills the loc if it is not already initialized
if a.k == locNone:
a.k = k
a.lode = origin(lode)
a.lode = lode
a.storage = s
proc t(a: TLoc): PType {.inline.} =
@@ -542,7 +535,7 @@ proc genCLineDir(r: var Builder, p: BProc, info: TLineInfo; conf: ConfigRef) =
if freshLineInfo(p, info):
genCLineDir(r, info.fileIndex, info.safeLineNm, p, info, lastFileIndex)
proc genLineDir(p: BProc; t: AnyNode) =
proc genLineDir(p: BProc; t: PNode) =
if p == p.module.preInitProc: return
let line = t.info.safeLineNm
@@ -625,7 +618,7 @@ include ccgtypes
# ------------------------------ Manager of temporaries ------------------
template mapTypeChooser(n: AnyNode): TSymKind =
template mapTypeChooser(n: PNode): TSymKind =
(if n.kind == nkSym: n.sym.kind else: skVar)
template mapTypeChooser(a: TLoc): TSymKind = mapTypeChooser(a.lode)
@@ -662,8 +655,8 @@ type
needAssignCall
TAssignmentFlags = set[TAssignmentFlag]
proc genObjConstr(p: BProc; e: AnyNode, d: var TLoc)
proc rawConstExpr(p: BProc; n: AnyNode; d: var TLoc)
proc genObjConstr(p: BProc; e: PNode, d: var TLoc)
proc rawConstExpr(p: BProc; n: PNode; d: var TLoc)
proc genAssignment(p: BProc, dest, src: TLoc, flags: TAssignmentFlags)
type
@@ -882,7 +875,7 @@ proc getIntTemp(p: BProc): TLoc =
flags: {})
p.s(cpsLocals).addVar(kind = Local, name = result.snippet, typ = NimInt)
proc localVarDecl(res: var Builder, p: BProc; n: AnyNode,
proc localVarDecl(res: var Builder, p: BProc; n: PNode,
initializer: Snippet = "",
initializerKind: VarInitializerKind = Assignment) =
let s = n.sym
@@ -910,7 +903,7 @@ proc localVarDecl(res: var Builder, p: BProc; n: AnyNode,
initializer = initializer,
initializerKind = initializerKind)
proc assignLocalVar(p: BProc; n: AnyNode) =
proc assignLocalVar(p: BProc; n: PNode) =
#assert(s.loc.k == locNone) # not yet assigned
# this need not be fulfilled for inline procs; they are regenerated
# for each module that uses them!
@@ -934,7 +927,7 @@ proc treatGlobalDifferentlyForHCR(m: BModule, s: PSym): bool =
# and s.owner.kind == skModule # owner isn't always a module (global pragma on local var)
# and s.loc.k == locGlobalVar # loc isn't always initialized when this proc is used
proc genGlobalVarDecl(res: var Builder, p: BProc; n: AnyNode; td: Snippet;
proc genGlobalVarDecl(res: var Builder, p: BProc; n: PNode; td: Snippet;
initializer: Snippet = "",
initializerKind: VarInitializerKind = Assignment,
allowConst = true) =
@@ -975,7 +968,7 @@ proc genGlobalVarDecl(res: var Builder, p: BProc; n: AnyNode; td: Snippet;
initializer = initializer,
initializerKind = initializerKind)
proc assignGlobalVar(p: BProc; n: AnyNode; value: Rope) =
proc assignGlobalVar(p: BProc; n: PNode; value: Rope) =
let s = n.sym
if s.loc.k == locNone:
fillBackendName(p.module, s)
@@ -1039,7 +1032,7 @@ proc assignGlobalVar(p: BProc; n: AnyNode; value: Rope) =
backendEnsureMutable s
resetLoc(p, s.locImpl)
proc callGlobalVarCppCtor[V: AnyNode; W: AnyNode](p: BProc; v: PSym; vn: V; value: W; didGenTemp: var bool) =
proc callGlobalVarCppCtor(p: BProc; v: PSym; vn: PNode; value: PNode; didGenTemp: var bool) =
let s = vn.sym
fillBackendName(p.module, s)
backendEnsureMutable s
@@ -1058,7 +1051,7 @@ proc assignParam(p: BProc, s: PSym, retType: PType) =
assert(s.loc.snippet != "")
scopeMangledParam(p, s)
proc fillProcLoc(m: BModule; n: AnyNode) =
proc fillProcLoc(m: BModule; n: PNode) =
let sym = n.sym
if sym.loc.k == locNone:
fillBackendName(m, sym)
@@ -1072,22 +1065,22 @@ proc getLabel(p: BProc): TLabel =
proc fixLabel(p: BProc, labl: TLabel) =
p.s(cpsStmts).addLabel(labl)
proc genVarPrototype(m: BModule, n: AnyNode)
proc genVarPrototype(m: BModule, n: PNode)
proc requestConstImpl(p: BProc, sym: PSym)
proc genStmts(p: BProc, t: AnyNode)
proc expr(p: BProc, n: AnyNode, d: var TLoc)
proc genStmts(p: BProc, t: PNode)
proc expr(p: BProc, n: PNode, d: var TLoc)
proc putLocIntoDest(p: BProc, d: var TLoc, s: TLoc)
proc genLiteral(p: BProc; n: AnyNode; result: var Builder)
proc genOtherArg(p: BProc; ri: AnyNode; i: int; typ: PType; result: var Builder; argBuilder: var CallBuilder)
proc genLiteral(p: BProc; n: PNode; result: var Builder)
proc genOtherArg(p: BProc; ri: PNode; i: int; typ: PType; result: var Builder; argBuilder: var CallBuilder)
proc raiseExit(p: BProc)
proc raiseExitCleanup(p: BProc, destroy: string)
proc initLocExpr(p: BProc; e: AnyNode, flags: TLocFlags = {}): TLoc =
proc initLocExpr(p: BProc; e: PNode, flags: TLocFlags = {}): TLoc =
result = initLoc(locNone, e, OnUnknown, flags)
expr(p, e, result)
proc initLocExprSingleUse(p: BProc; e: AnyNode): TLoc =
proc initLocExprSingleUse(p: BProc; e: PNode): TLoc =
result = initLoc(locNone, e, OnUnknown)
if e.kind in nkCallKinds and (e.firstSon.kind != nkSym or e.firstSon.sym.magic == mNone):
# We cannot check for tfNoSideEffect here because of mutable parameters.
@@ -1407,7 +1400,7 @@ const harmless = {nkConstSection, nkTypeSection, nkEmpty, nkCommentStmt, nkTempl
nkMacroDef, nkMixinStmt, nkBindStmt, nkFormalParams} +
declarativeDefs
proc containsResult(n: AnyNode): bool =
proc containsResult(n: PNode): bool =
result = false
case n.kind
of succ(nkEmpty)..pred(nkSym), succ(nkSym)..nkNilLit, harmless:
@@ -1443,7 +1436,7 @@ proc easyResultAsgn(n: PNode): PNode =
type
InitResultEnum = enum Unknown, InitSkippable, InitRequired
proc allPathsAsgnResult(p: BProc; n: AnyNode): InitResultEnum =
proc allPathsAsgnResult(p: BProc; n: PNode): InitResultEnum =
# Exceptions coming from calls don't have not be considered here:
#
# proc bar(): string = raise newException(...)
@@ -1570,494 +1563,6 @@ proc allPathsAsgnResult(p: BProc; n: AnyNode): InitResultEnum =
for it in sons(n):
allPathsInBranch(it)
when defined(newIcBackend):
import std / [exitprocs, syncio]
import nodebridge
var bnodeGrind = -1
# Whether the scope chain is load-bearing or decorative is a question with a
# number for an answer, so it gets counted rather than asserted. Reported per
# process on exit; a run in which `navHits` is 0 means every lookup fell
# through to the decoder and the chain is doing nothing.
var navHits, navFallbacks, navRegistered: int
# Same reasoning for the predicate grinder: "0 disagreements" is only worth
# something next to how many nodes were actually graded and how many were
# excused, so all three are counted and reported together.
var gradeGraded, gradeSkipDecl, gradeSkipTyp: int
# The two differences `grindLockstep` EXCUSES on the file path. Counted so the
# bridge can assert it needed neither: a bridged buffer hands back the very
# objects it was given, so any tolerance firing there is a bug in the bridge,
# not a property of the format.
var tolHtNil, tolFieldSym: int
var bridgeGraded: int
const nkIntLits = {nkCharLit..nkUInt64Lit}
const notGradeable = {nkTypeSection, nkConstSection, nkProcDef, nkConverterDef,
nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef,
nkLambda, nkDo, nkFuncDef}
## Subtrees the predicates are not graded inside, because production never
## evaluates an expression there either — `bodyCanRaise` declares the same
## boundary and returns `false` for the whole set without looking in. The
## nodes inside carry unresolved types (a template's parameters, a generic's
## `tyGenericParam`), and asking `getSize` about one is not a disagreement
## between the two spellings, it is a question with no answer in either.
proc ordinalRanges(a: PNode): bool =
## Whether every `nkRange` directly under `a` has integer endpoints. The
## gate for `branchHasTooBigRange`, which reads `intVal` off them: a `case`
## over strings or floats has `nkOfBranch`es whose ranges hold no integer,
## and production only ever reaches that proc from the ordinal path. Computed
## from the AST side ALONE so the two spellings are gated identically — a
## gate that consulted the cursor could hide the very disagreement it is
## supposed to expose.
result = true
for it in sons(a):
if it.kind == nkRange and
(it.firstSon.kind notin nkIntLits or it.secondSon.kind notin nkIntLits):
return false
proc grindPredicates(m: BModule; p: BProc; prc: PSym; c: BNode; a: PNode;
path: string) =
## Every migrated pure predicate, run on BOTH spellings of the SAME node.
##
## The point of doing it HERE rather than once per body is coverage. A proc
## graded at the root of a body is graded on the shapes that body happens to
## start with; graded at every node it meets every shape the closure
## contains, which over a standard-library build is tens of thousands of
## nodes and effectively all of them. These predicates are pure and cheap,
## so the whole set can be run at every node for the price of the walk that
## is already happening.
##
## Only calls that are TOTAL on the node are made, and the predicates split
## in two on that question.
##
## The structural ones — `isSimpleExpr`, `bodyCanRaise`, the indirection
## walkers — read `kind`, children and (defensively) `sym`, and answer for
## any node in a body. They are graded everywhere.
##
## The type-consuming ones — `isAssignedImmediately`, `fewCmps` — hand
## `n.typ` to `getSize` / `mapType`, which are total only over types the C
## backend can lay out. Production reaches them from exactly one shape each
## (the value of a var definition; the set operand of an `in`), and away
## from that shape they meet types codegen never maps — a `tyGenericParam`,
## a `tyAnything` — and abort. That is not a disagreement between the two
## spellings, it is a question with no answer in either, so these are graded
## FROM THE PARENT at the position production calls them from. Widening a
## guard until the run goes green would be the wrong move; restricting the
## call to where it is defined is not the same thing.
template bail(what: string; cur, ast: string) =
internalError(m.config, prc.info,
"BNode/PNode disagree on " & what & " at <body>" & path & " in " &
prc.name.s & ": cursor=" & cur & " ast=" & ast)
template checkAt(what: string; cn: BNode; an: PNode; call: untyped) =
## `call` is written ONCE and instantiated twice — once with `n` bound to
## the cursor, once to the AST. Writing it twice is what would let the two
## sides drift into asking different questions.
block:
let cv = block:
let n {.inject.} = cn
call
let av = block:
let n {.inject.} = an
call
if cv != av: bail(what, $cv, $av)
template check(what: string; call: untyped) = checkAt(what, c, a, call)
# Total on any well-formed node.
check "isSimpleExpr", isSimpleExpr(n)
check "reifiedOpenArray", reifiedOpenArray(n)
check "bodyCanRaise", bodyCanRaise(p, n)
check "getMagic", getMagic(n)
check "whichPragma", whichPragma(n)
check "getRoot", getRoot(n)
check "isDeepConstExpr", isDeepConstExpr(n)
check "stmtsContainPragma", stmtsContainPragma(n, wLinearScanEnd)
check "notYetAlive", notYetAlive(n)
check "isInactiveDestructorCall", isInactiveDestructorCall(p, n)
check "getInt", (if n.kind in nkIntLits: $getInt(n) else: "")
check "sameValue self", sameValue(n, n)
# `sym` IS NOT A FUNCTION OF ITS ARGUMENT for object fields, so this asserts
# the property the rest of the seam quietly assumes everywhere else. Two
# calls on the SAME token mint two `skField` stubs with consecutive item
# ids (`loadFieldStub`, by design: two distinct fields can share a name and
# a position across types, so one shared stub would mistype one of them).
# Anything that reads a field sym twice and compares identity is therefore
# wrong on a cursor and right on an AST — which is exactly how the attempt
# to migrate `aliases.isPartOf` failed, and it failed LOUDLY only because
# this grinder existed. Left as a live check so the day it starts holding
# is visible.
# On the FILE path fields are excluded: `loadFieldStub` mints per use, so
# two reads of one token give two stubs. On a BRIDGED buffer they are NOT
# excluded, because the bridge hands back the object it was given — that is
# the property that makes field-comparing code (`aliases.isPartOf`) correct
# on a bridge and wrong on a file, and it is asserted here rather than
# merely claimed in `nodebridge`'s doc.
let bridged = currentNav().bridge != nil
if a.kind == nkSym and a.sym != nil and (bridged or a.sym.kind != skField):
if c.sym != c.sym:
bail("sym is not idempotent", "two different PSyms", "one PSym")
# `stmtsContainPragma` had to be re-derived rather than defined as
# `getPragmaStmt(...) != nil`, because a `Cursor` has no nil to return (see
# the note at its definition). That leaves two copies of one traversal, so
# the equivalence is asserted here instead of assumed — on the AST side,
# where `getPragmaStmt` exists.
for w in [wLinearScanEnd, wComputedGoto]:
if stmtsContainPragma(a, w) != (getPragmaStmt(a, w) != nil):
bail("stmtsContainPragma vs getPragmaStmt for " & $w,
$stmtsContainPragma(a, w), $(getPragmaStmt(a, w) != nil))
# `skipTrivialIndirections` returns a NODE, and the two spellings return
# values of different types that cannot be compared directly. Kind plus
# line info pins which node was landed on: the proc only ever walks DOWN a
# spine, so two different stopping points on the same input differ in one or
# the other unless the tree has two identical nodes at one position, which
# would make the choice immaterial anyway.
template checkNodeResult(what: string; call: untyped) =
block:
let cs = block:
let n {.inject.} = c
call
let a2 = block:
let n {.inject.} = a
call
if cs.kind != a2.kind:
bail(what & " kind", $cs.kind, $a2.kind)
if cs.info != a2.info:
bail(what & " info", $(m.config, cs.info), $(m.config, a2.info))
checkNodeResult "skipTrivialIndirections", skipTrivialIndirections(n)
checkNodeResult "skipAddr", skipAddr(n)
checkNodeResult "skipAddrDeref", skipAddrDeref(n)
# Shape-guarded, matching the contexts production calls them from.
if a.kind in nkCallKinds and a.safeLen > 0:
check "hasNoInit", hasNoInit(n)
if a.kind in {nkClosure, nkPar, nkTupleConstr} and a.safeLen == 2:
check "isConstClosure", isConstClosure(n)
if a.kind == nkOfBranch and ordinalRanges(a):
check "branchHasTooBigRange", branchHasTooBigRange(n)
if a.kind == nkCaseStmt and a.safeLen > 1 and
(block:
# `ifSwitchSplitPoint` reaches `branchHasTooBigRange`, so the same
# ordinal gate has to hold for every branch it will look at.
var ok = true
for br in sonsFrom(a, 1):
if br.kind == nkOfBranch and not ordinalRanges(br): ok = false
ok):
check "ifSwitchSplitPoint", ifSwitchSplitPoint(p, n)
# Graded from the parent — see the note above on why these two cannot be
# asked at an arbitrary node. `genVarTuple` asks about the tuple's last
# child; `genSingleVar` about the value of an `nkIdentDefs` that defines a
# symbol; `genInOp` about the set operand of an `in`.
if a.kind == nkVarTuple and a.safeLen > 0:
checkAt "isAssignedImmediately", c.lastSon, a.lastSon,
isAssignedImmediately(m.config, n)
elif a.kind == nkIdentDefs and a.safeLen == 3 and a.firstSon.kind == nkSym:
checkAt "isAssignedImmediately", son(c, 2), son(a, 2),
isAssignedImmediately(m.config, n)
if a.kind in nkCallKinds and a.safeLen > 1 and a.secondSon.kind == nkCurly and
a.secondSon.typ != nil:
checkAt "fewCmps", c.secondSon, a.secondSon, fewCmps(m.config, n)
proc grindLockstep(m: BModule; p: BProc; prc: PSym; c: BNode; a: PNode;
path: string; gradeable: bool): bool {.discardable.} =
## Walk the `.bif` cursor and the materialised `PNode` for the SAME body in
## lockstep and require every vocabulary member to answer identically at
## every node. This grades the VOCABULARY rather than any one migrated proc,
## which is the difference that matters: a proc-level oracle only sees an
## accessor that the proc happens to reach on that body, so a wrong accessor
## stays invisible until some later proc migrates and quietly miscompiles.
## `typ` was exactly that — it answered `nil` for every bare `Symbol`, which
## no `containsResult` body could notice.
##
## Must run AFTER the proc-level comparisons: reading `a.kind`/`a.len` fires
## the lazy-body hook and materialises the body, which is fine here (the
## cursor is unaffected) but would spoil their cursor-answer-first ordering.
template bail(what, cur, ast: string) =
internalError(m.config, prc.info,
"BNode/PNode disagree on " & what & " at <body>" & path & " in " &
prc.name.s & ": cursor=" & cur & " ast=" & ast)
# The result says: nothing ANYWHERE in this subtree hit the tolerated
# `(ht . <sym>)` type difference. Only a subtree that clean is handed to
# `grindPredicates` — see the descent below for why.
result = true
if a == nil:
if not c.isNilNode: bail("nil-ness", "not-nil", "nil")
return
if c.isNilNode: bail("nil-ness", "nil", "not-nil")
if c.kind != a.kind: bail("kind", $c.kind, $a.kind)
let here = path & "." & $a.kind
if c.safeLen != a.safeLen: bail("len", $c.safeLen, $a.safeLen)
if c.info != a.info:
bail("info", $(m.config, c.info), $(m.config, a.info))
# Symbols first: a wrong symbol shows up as a wrong TYPE two lines below,
# and "cursor=nil ast=tyProc" is a much worse bug report than "these are
# different symbols".
if a.kind == nkSym:
let cs = c.sym
let asym = a.sym
template describe(x: PSym): string =
(if x == nil: "nil"
else: x.name.s & "/" & $x.kind & "/" & $x.itemId & "/" & $x.state)
if cs == nil or asym == nil:
if cs != asym: bail("sym nil-ness", describe(cs), describe(asym))
elif cs != asym:
# A cross-context object-field reference is stubbed FRESH at every use
# (`loadFieldStub`: two distinct fields can share a local name and
# position across types, so ONE shared stub would mistype one of them).
# Pointer identity is therefore not part of the contract for fields —
# what codegen consumes is the name it re-navigates the reclist with
# (`lookupFieldAgain`) and, for tuples, the position.
if cs.kind == skField and asym.kind == skField:
inc tolFieldSym
if cs.name.s != asym.name.s or cs.position != asym.position:
bail("field sym", describe(cs) & "@" & $cs.position,
describe(asym) & "@" & $asym.position)
else:
bail("sym identity", describe(cs), describe(asym))
# `nfHasComment` is never serialised and `nfLazyType` is a `PNode`-side
# marker (see `bnode.flags`); everything else must round-trip exactly.
const ownedByTheAst = {nfHasComment, nfLazyType}
if c.flags - ownedByTheAst != a.flags - ownedByTheAst:
bail("flags", $(c.flags - ownedByTheAst), $(a.flags - ownedByTheAst))
case a.kind
of nkCharLit..nkUInt64Lit:
if c.intVal != a.intVal: bail("intVal", $c.intVal, $a.intVal)
of nkFloatLit..nkFloat128Lit:
# Compare the BITS: two NaNs are never `==`, and a float that survives the
# round trip must be the same float, not merely an equal one.
if cast[uint64](c.floatVal) != cast[uint64](a.floatVal):
bail("floatVal bits", $cast[uint64](c.floatVal),
$cast[uint64](a.floatVal))
of nkStrLit..nkTripleStrLit:
if c.strVal != a.strVal: bail("strVal", c.strVal, a.strVal)
of nkIdent:
if c.ident != a.ident: bail("ident", c.ident.s, a.ident.s)
else: discard
let ct = c.typ
let at = a.typ
# `(ht . <sym>)` is the one shape where the two spellings may legitimately
# differ: the cursor answers the faithful `nil`, while `ast.typ` answers
# `sym.typ` for whichever nodes the loader happened to mark `nfLazyType`
# (see `bnode.typ`). Excluded rather than papered over — and narrowly: only
# when the cursor says nil AND the AST is saying exactly the symbol's type.
let htNilTyp = ct == nil and at != nil and a.kind == nkSym and
a.typField == nil and a.sym != nil and at == a.sym.typ and
c.hasExplicitNilType
if htNilTyp:
inc tolHtNil
result = false
elif (ct == nil) != (at == nil):
bail("typ nil-ness",
(if ct == nil: "nil" else: $ct.kind) & " raw=" & c.rawDesc,
(if at == nil: "nil" else: $at.kind) & " kind=" & $a.kind &
" typField=" & (if a.typField == nil: "nil" else: $a.typField.kind) &
" lazy=" & $(nfLazyType in a.flags) &
(if a.kind != nkSym: "" else:
" sym=" & a.sym.name.s & "/" & $a.sym.kind & "/" & $a.sym.state &
" symTypImpl=" & (if a.sym.typImpl == nil: "nil" else: $a.sym.typImpl.kind)))
elif ct != nil and ct != at:
# Fields carry their own stub type, so a tolerated field-sym difference
# brings a tolerated type difference with it; compare by kind there.
if a.kind == nkSym and a.sym.kind == skField:
if ct.kind != at.kind:
bail("field typ", $ct.kind, $at.kind)
else:
bail("typ identity", $ct.kind & "/" & $ct.itemId, $at.kind & "/" & $at.itemId)
# `safeLen` already matched, so indexed access stays in range on both sides.
# `son` rescans from the first child each time, which is quadratic — fine for
# a debug-only oracle over routine bodies, and it keeps the walk honest by
# exercising the same accessor migrated code will use.
#
# The descent is bracketed by a nav scope and each child is offered to
# `registerDefHere` BEFORE it is entered, so this walk maintains the scope
# chain exactly the way a cursor-native pass would have to (see `bodynav`).
# That is the part being graded here: not just that the accessors agree, but
# that they still agree when the resolution context is built by the
# traversal instead of handed to it.
let gradeHere = gradeable and a.kind notin notGradeable
if a.safeLen > 0:
withNodeScope(nsBlock):
var i = 0
for child in sons(a):
let cc = son(c, i)
registerDefHere(cc)
if not grindLockstep(m, p, prc, cc, child, here & "[" & $i & "]",
gradeHere):
result = false
inc i
# AFTER the descent, and only on a subtree with no tolerated type difference
# anywhere in it. The predicates RECURSE, so one excused node poisons every
# ancestor's answer too: grading `bodyCanRaise` at a call whose callee is an
# `(ht . <sym>)` sym would re-report that one known difference as a fresh
# finding at every enclosing node. Excused, not ignored — the exclusions are
# counted, so a run that grades nothing cannot pass for a run that grades
# everything.
if not gradeHere:
inc gradeSkipDecl
elif not result:
inc gradeSkipTyp
else:
inc gradeGraded
grindPredicates(m, p, prc, c, a, here)
proc grindBridge(m: BModule; p: BProc; prc: PSym; body: PNode) =
## Grade the `PNode` -> `TokenBuf` bridge against its own input.
##
## This is a strictly harder test than the file path gets, and deliberately.
## `grindBNode` compares a cursor loaded from a `.bif` against a `PNode`
## loaded from the same `.bif` — two decodings of one file, which is why it
## has to excuse two differences (a field symbol is stubbed per use, and
## `(ht . <sym>)`'s nil is load-order dependent). The bridge is handed a live
## tree and hands the same objects back, so it must need NEITHER excuse, and
## the counters are checked to make sure the run did not quietly take one.
##
## Then the same buffer is decoded and RE-ENCODED, and the second buffer is
## graded against the ORIGINAL tree. That is what covers `toPNode`: anything
## the decoder drops is missing from the re-encoding and shows up as a
## disagreement with the original, so both directions are checked by the one
## oracle rather than by a hand-written comparator that could agree with the
## bug.
if bnodeGrind == 0 or body == nil: return
let htBefore = tolHtNil
let fieldBefore = tolFieldSym
var enc = toTokenBuf(body, m.config)
withBridge(enc.tables):
grindLockstep(m, p, prc, BNode(rootCursor(enc)), body, "<bridge>",
gradeable = true)
# ORIGIN IDENTITY, at every node. The generator migration rests on this and
# on nothing else: if a cursor can name the very `PNode` it was encoded
# from, `TLoc.lode` stays a `PNode` and the identity comparisons already in
# the backend keep working, so the 99 of 180 generator procs that build a
# location from a node do not force `TLoc` to change representation.
# Asserted rather than assumed, with `==` on the reference: an equal copy
# would not do.
proc grindOrigins(enc: var BridgeBuf; c: BNode; a: PNode; path: string) =
if a == nil: return
# Through the AMBIENT accessor (`bnode.origin`, via `currentNav`), which
# is the one a migrated generator proc will call from inside `initLoc` —
# not the direct `originOf`, which would test a path nothing uses.
let src = origin(c)
if src != a:
internalError(m.config, prc.info,
"bridge origin is not the source node at <body>" & path & " in " &
prc.name.s & ": got " &
(if src == nil: "nil" else: $src.kind & "@" & $cast[int](src)) &
" want " & $a.kind & "@" & $cast[int](a))
if a.safeLen > 0:
var i = 0
for child in sons(a):
grindOrigins(enc, son(c, i), child, path & "[" & $i & "]")
inc i
withBridge(enc.tables):
grindOrigins(enc, BNode(rootCursor(enc)), body, "")
var rt = toPNode(enc)
var enc2 = toTokenBuf(rt, m.config)
withBridge(enc2.tables):
grindLockstep(m, p, prc, BNode(rootCursor(enc2)), body, "<bridge-rt>",
gradeable = true)
if tolHtNil != htBefore:
internalError(m.config, prc.info,
"bridge needed the `(ht . <sym>)` tolerance in " & prc.name.s &
" — it encodes the node's own type explicitly, so it cannot legitimately")
if tolFieldSym != fieldBefore:
internalError(m.config, prc.info,
"bridge needed the field-symbol tolerance in " & prc.name.s &
" — it hands back the same PSym, so identity must already match")
inc bridgeGraded
proc grindBNode(m: BModule; p: BProc; prc: PSym) =
## Differential grinding for the migrating vocabulary, opt-in via
## `NIM_IC_BNODE_GRIND`: run every proc that has moved to `AnyNode` over
## BOTH representations of the SAME body and require the same answer. This
## is the only thing that executes the `Cursor` accessors — codegen itself
## is still driven off `PNode`s — and it is deliberately the same technique
## that found the IC bugs earlier on this branch: an oracle beats a
## hand-written expectation, because it compares everything, not what
## someone thought to check.
##
## Order matters. The `PNode` walk calls `len`, which fires the lazy-body
## hook and MATERIALIZES the deferred body; the cursor answer is therefore
## taken first. `lazyBodyBNode` itself does not consume the pending entry.
##
## `allPathsAsgnResult` is graded here too, and it is the more valuable of
## the two: it reaches `typ` (via `skipTypes` on a case selector) and
## `canRaiseDisp` (via `sym`), so a disagreement exercises the resolution
## path — `symFromCursor` / `typeFromCursor` against the body's `localSyms`
## — and not just the child walk.
##
## `grindLockstep` runs last and grades the vocabulary itself rather than
## these two procs; it is the check that actually covers accessors no
## migrated proc happens to call yet, and it carries `grindPredicates` —
## every OTHER migrated proc, run at every node of the body.
##
## WHAT THIS CANNOT SEE. Only a body that arrived as a deferred `nfLazyBody`
## placeholder can be graded, and `ast2nif` defers only bodies whose root is
## an `nkStmtList`. A one-line `proc f(x: int): int = case x ...` has an
## `nkAsgn` body, is loaded eagerly, and never reaches this proc — 652 of
## 1434 bodies on the reference target (`tools/icgrind`). Nor is the main
## module graded at all: its routines are built in-process. Both are stated
## because they are invisible from the outside — a shape added to a grind
## target can produce exactly zero coverage and no diagnostic.
if bnodeGrind < 0:
bnodeGrind = ord(existsEnv("NIM_IC_BNODE_GRIND"))
if bnodeGrind == 1:
addExitProc proc () =
stderr.writeLine "BNODEGRIND navHits=" & $navHits &
" navFallbacks=" & $navFallbacks & " navRegistered=" & $navRegistered &
" graded=" & $gradeGraded & " skipDecl=" & $gradeSkipDecl &
" skipTyp=" & $gradeSkipTyp & " bridged=" & $bridgeGraded
if bnodeGrind == 0: return
let ast = prc.ast
if ast == nil or ast.safeLen <= bodyPos: return
let body = son(ast, bodyPos)
if body == nil: return
var scope = default(BodyScope)
var viaCursor = default(BNode)
if not lazyBodyBNode(body, scope, viaCursor): return
var curResult = false
var curPaths = Unknown
withBodyScope(scope):
curResult = containsResult(viaCursor)
curPaths = allPathsAsgnResult(p, viaCursor)
let astResult = containsResult(body)
if curResult != astResult:
internalError(m.config, prc.info,
"BNode/PNode disagree on containsResult for " & prc.name.s &
": cursor=" & $curResult & " ast=" & $astResult)
let astPaths = allPathsAsgnResult(p, body)
if curPaths != astPaths:
internalError(m.config, prc.info,
"BNode/PNode disagree on allPathsAsgnResult for " & prc.name.s &
": cursor=" & $curPaths & " ast=" & $astPaths)
withBodyScope(scope):
grindLockstep(m, p, prc, viaCursor, body, "", gradeable = true)
let (hits, fallbacks, registered) = navStats()
navHits += hits
navFallbacks += fallbacks
navRegistered += registered
proc getProcTypeCast(m: BModule, prc: PSym): Rope =
result = getTypeDesc(m, prc.loc.t)
if prc.typ.callConv == ccClosure:
@@ -2068,7 +1573,7 @@ proc getProcTypeCast(m: BModule, prc: PSym): Rope =
let params = extract(desc)
result = procPtrTypeUnnamed(rettype = rettype, params = params)
proc genProcBody(p: BProc; procBody: AnyNode) =
proc genProcBody(p: BProc; procBody: PNode) =
genStmts(p, procBody) # modifies p.locals, p.init, etc.
if {nimErrorFlagAccessed, nimErrorFlagDeclared, nimErrorFlagDisabled} * p.flags == {nimErrorFlagAccessed}:
p.flags.incl nimErrorFlagDeclared
@@ -2129,44 +1634,12 @@ proc genProcLvl3*(m: BModule, prc: PSym) =
# CT-evaluated or earlier-referenced routine), NOT a `.t.bif` load — gating on
# it there would WRONGLY skip destructor injection and miscompile (orc
# decref-on-freed). The `.t.bif`-loaded-body concept exists only under cmdNifC.
when defined(newIcBackend):
grindBNode(m, p, prc)
let wasLoaded = m.config.cmd == cmdNifC and prc.transformedBody != nil
icProfStart(tTransform)
var procBody = transformBody(m.g.graph, m.idgen, prc, {})
if sfInjectDestructors in prc.flags and not wasLoaded:
procBody = injectDestructorCalls(m.g.graph, m.idgen, prc, procBody)
icProfStop(tTransform)
# THE HANDOFF (`transf.handOffBody`). Rewriting is done for this body —
# transformed, and destructor-injected when this process did the injecting —
# so from here the reading side works off a cursor.
#
# Under `-d:newIcBackend` only, because that is what the switch means: the
# generator still needs a `PNode` (`expr` dispatches to ~60 emitters that have
# to move together or not at all), so building a buffer in a default build
# would cost every routine a tree walk and buy nothing. The ANALYSES below are
# already `AnyNode`, and they are the part that moves now.
when defined(newIcBackend):
icProfStart(tHandOff)
var bodyBuf = handOffBody(procBody, m.config)
icProfStop(tHandOff)
grindBridge(m, p, prc, procBody)
template readBody(res, call: untyped) =
## Run a migrated `AnyNode` analysis over the body the READING side sees:
## a cursor over the handed-off buffer when there is one, the `PNode`
## otherwise. Both spellings type-check, and the generated C must not depend
## on which one ran — which is what the byte-identical `.c` check verifies
## end to end, a stronger statement than the node-level grinder can make.
icProfStart(tAnalyses)
when defined(newIcBackend) and not defined(icBridgeOnly):
withBridge(bodyBuf.tables):
let n {.inject.} = BNode(bodyBuf.rootCursor)
res = call
else:
let n {.inject.} = procBody
res = call
icProfStop(tAnalyses)
let tmpInfo = prc.info
discard freshLineInfo(p, prc.info)
@@ -2186,8 +1659,7 @@ proc genProcLvl3*(m: BModule, prc: PSym) =
# declare the result symbol:
assignLocalVar(p, resNode)
assert(res.loc.snippet != "")
var paths = Unknown
readBody(paths, allPathsAsgnResult(p, n))
let paths = allPathsAsgnResult(p, procBody)
if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc} and
paths == InitSkippable:
# In an ideal world the codegen could rely on injectdestructors doing its job properly
@@ -2240,21 +1712,8 @@ proc genProcLvl3*(m: BModule, prc: PSym) =
continue
assignParam(p, param, prc.typ.returnType)
closureSetup(p, prc)
# THE FLIP: under `-d:newIcBackend` the generator is driven off the cursor
# into the handed-off buffer, not the tree. Both spellings must produce the
# same C, which is what the cursor-vs-`PNode` `.c` comparison checks.
#
# `-d:icBridgeOnly` is a MEASUREMENT switch, not a mode: it still builds the
# buffer but generates off the tree, which is the only way to separate what
# the encoder costs from what reading costs. Keep it working — it is what
# showed encoding to be free, and so that the reader was the thing to profile.
prof pGenBodyCalls
icProfStart(tGenBody)
when defined(newIcBackend) and not defined(icBridgeOnly):
withBridge(bodyBuf.tables):
genProcBody(p, BNode(bodyBuf.rootCursor))
else:
genProcBody(p, procBody)
genProcBody(p, procBody)
icProfStop(tGenBody)
# IC: spurious write, seems fine for now:
@@ -2554,7 +2013,7 @@ proc requestProcDef*(m: BModule, prc: PSym) =
## code had referenced it.
genProc(m, prc)
proc genVarPrototype(m: BModule, n: AnyNode) =
proc genVarPrototype(m: BModule, n: PNode) =
#assert(sfGlobal in sym.flags)
let sym = n.sym
useHeader(m, sym)
@@ -3460,7 +2919,7 @@ when false:
readMergeInfo(getCFile(m), m)
result = m
proc addHcrInitGuards(p: BProc; n: AnyNode, inInitGuard: var bool, init: var IfBuilder) =
proc addHcrInitGuards(p: BProc; n: PNode, inInitGuard: var bool, init: var IfBuilder) =
if n.kind == nkStmtList:
for child in sons(n):
addHcrInitGuards(p, child, inInitGuard, init)
@@ -3499,7 +2958,7 @@ proc handleProcGlobals(m: BModule) =
handleProcGlobals(m)
m.preInitProc.s(cpsStmts).add stmts.extract()
proc genTopLevelStmt*(m: BModule; n: AnyNode) =
proc genTopLevelStmt*(m: BModule; n: PNode) =
## Also called from `ic/cbackend.nim`.
if pipelineutils.skipCodegen(m.config, n): return
m.initProc.options = initProcOptions(m)
@@ -3594,7 +3053,7 @@ proc writeModule(m: BModule) =
code = stripCnifMarks(code)
registerModuleCode(m, cf, code)
proc generateLibraryDestroyGlobals(graph: ModuleGraph; m: BModule; body: AnyNode; isDynlib: bool): PSym =
proc generateLibraryDestroyGlobals(graph: ModuleGraph; m: BModule; body: PNode; isDynlib: bool): PSym =
let prefixedName = m.config.nimMainPrefix & "NimDestroyGlobals"
let procname = getIdent(graph.cache, prefixedName)
result = newSym(skProc, procname, m.idgen, m.module.owner, m.module.info)
@@ -3648,7 +3107,7 @@ proc genIcModuleDestroyGlobals*(graph: ModuleGraph; m: BModule): string =
dtor.ast = theProc
genProcLvl3(m, dtor)
proc finalCodegenActions*(graph: ModuleGraph; m: BModule; n: AnyNode) =
proc finalCodegenActions*(graph: ModuleGraph; m: BModule; n: PNode) =
## Also called from IC.
if sfMainModule in m.module.flags:
# phase ordering problem here: We need to announce this
@@ -3671,7 +3130,7 @@ proc finalCodegenActions*(graph: ModuleGraph; m: BModule; n: AnyNode) =
# if the module is cached, we don't regenerate the main proc
# nor the dispatchers? But if the dispatchers changed?
# XXX emit the dispatchers into its own .c file?
if not n.isNilNode:
if n != nil:
m.initProc.options = initProcOptions(m)
genProcBody(m.initProc, n)

View File

@@ -11,8 +11,8 @@
## Off, every template below is `discard` and nothing is linked in.
##
## It lives in its own module with NO compiler imports so that any stage can
## use it without creating a cycle — `bnode` needs it for the accessors,
## `nifbackend` for the stage phases, `cgen` for what happens per routine.
## use it without creating a cycle — `ast2nif` for the loader, `nifbackend` for
## the stage phases, `cgen` for what happens per routine.
##
## Each backend process appends ONE line to `$NIM_IC_BNODE_PROF` at exit (or to
## stderr when that is unset), because a `--ic:on` build fans out a process per
@@ -20,8 +20,8 @@
## when the numbers need to be attributable to a particular module.
##
## Counts are for volume, timings for cost, and the two answer different
## questions: the accessors turned out to be 700k calls worth 8ms, while `info`
## was 259k calls worth 1.36s. Neither number alone would have found that.
## questions: a call count alone once pointed at the wrong accessor (700k calls
## worth 8ms) while the real cost was 259k `info` resolutions worth 1.36s.
when defined(icBNodeProf):
import std / [envvars, exitprocs, syncio, monotimes]
@@ -29,15 +29,12 @@ when defined(icBNodeProf):
type
ProfSlot* = enum
pKind, pTagKindHit, pTagKindMiss, pAstChildren, pSkip, pSon, pLen,
pLastSon, pIterYield, pSym, pTyp, pTypTagLit, pOrigin, pNilType,
pGenBodyCalls, pInfo, pIfaceExported, pIfaceHidden, pIfaceModules,
pTyp, pIfaceExported, pIfaceHidden, pIfaceModules,
pTopNodes, pExportSyms, pPeekKind, pPeekFallback, pPeekLoaded,
pTopToolingSkip
TimeSlot* = enum
tLoadClosure, tModuleId, tBifLoad, tPosIndex, tTopLevel, tInterfTables,
tTransform, tHandOff, tGenBody, tAnalyses,
tSym, tTyp, tInfo, tOrigin, tExportBranch, tResolveSym, tEnumFields,
tTransform, tGenBody, tExportBranch, tResolveSym, tEnumFields,
# Coarse phases, added to find where a backend process spends the time
# that none of the slots above account for. `tStage` is the whole stage
# body, so `Process - tStage` is everything before it: exec, the Nim
@@ -97,8 +94,8 @@ when defined(icBNodeProf):
template timed*(s: TimeSlot; body: untyped) =
## Leaf timing. NOT re-entrant, and the phase slots are not disjoint —
## `tTransform` contains body materialization, `tTyp` reaches `tSym`. Read
## them as nested, not additive.
## `tTransform` contains body materialization. Read them as nested, not
## additive.
##
## Arms the dump like `prof`/`icProfStart` do. It did not, and so a process
## whose ONLY instrumentation is a `timed` never reported at all: the

View File

@@ -1787,9 +1787,11 @@ proc genVarOpenArrayArg(p: PProc, n: PNode, r: var TCompRes) =
r.res = "{base: $1, off: 0, len: ($1).length}" % [v.rdLoc]
r.kind = resExpr
proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes; emitted: ptr int = nil) =
proc genArg(p: PProc, n: PNode, param: PSym, r: var TCompRes;
emitted: ptr int = nil; skipVarOpenArray = false) =
var a: TCompRes = default(TCompRes)
if param.typ != nil and param.typ.kind == tyVar and param.typ[0].kind == tyOpenArray:
if (not skipVarOpenArray) and param.typ != nil and param.typ.kind == tyVar and
param.typ[0].kind == tyOpenArray:
# `var openArray` params are passed as a `{base, off, len}` slice view.
genVarOpenArrayArg(p, n, a)
r.res.add(a.rdLoc)
@@ -1847,7 +1849,8 @@ proc genArgs(p: PProc, n: PNode, r: var TCompRes; start=1) =
r.kind = resExpr
proc genOtherArg(p: PProc; n: PNode; i: int; typ: PType;
generated: var int; r: var TCompRes) =
generated: var int; r: var TCompRes;
skipVarOpenArray = false) =
if i >= n.len:
globalError(p.config, n.info, "wrong importcpp pattern; expected parameter at position " & $i &
" but got only: " & $(n.len-1))
@@ -1860,11 +1863,12 @@ proc genOtherArg(p: PProc; n: PNode; i: int; typ: PType;
if paramType.isNil:
genArgNoParam(p, it, r)
else:
genArg(p, it, paramType.sym, r)
genArg(p, it, paramType.sym, r, skipVarOpenArray = skipVarOpenArray)
inc generated
proc genPatternCall(p: PProc; n: PNode; pat: string; typ: PType;
r: var TCompRes) =
let skipVarOpenArray = sfImportc in n[0].sym.flags
var i = 0
var j = 1
r.kind = resExpr
@@ -1874,11 +1878,11 @@ proc genPatternCall(p: PProc; n: PNode; pat: string; typ: PType;
var generated = 0
for k in j..<n.len:
if generated > 0: r.res.add(", ")
genOtherArg(p, n, k, typ, generated, r)
genOtherArg(p, n, k, typ, generated, r, skipVarOpenArray)
inc i
of '#':
var generated = 0
genOtherArg(p, n, j, typ, generated, r)
genOtherArg(p, n, j, typ, generated, r, skipVarOpenArray)
inc j
inc i
of '\31':

View File

@@ -1,340 +0,0 @@
#
#
# The Nim Compiler
# (c) Copyright 2026 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## `PNode` <-> `TokenBuf`, in one process.
##
## WHY THIS EXISTS. The backend splits in two along a line that is not the one
## the migration to `BNode` was drawn along. Passes that REWRITE — transf,
## destructor injection, closure lifting, the tree the code generator builds as
## it goes — construct new nodes, and a `Cursor` is a read cursor into a shared
## token buffer, so they cannot be expressed against it and there is no reason
## to try. Passes that READ want the cursor. The bridge is the seam between
## them: a rewriting pass keeps producing a `PNode`, and anything that only
## reads gets a `TokenBuf`, from which a `Cursor` — and so a `BNode` — is a
## pointer.
##
## HOW IT DIFFERS FROM THE `.bif` FORMAT, and why that is the point. A `.bif`
## is read by a DIFFERENT PROCESS, so every symbol and type has to be written as
## a NAME the reader can look up again. A bridged buffer is read by the process
## that built it, so it does not: a symbol reference is `(bsym <idx>)`, an index
## into a side table holding the very `PSym` the encoder was handed, and the
## type slot is `(btyp <idx>)` the same way.
##
## Three consequences, and the middle one is the reason to prefer this over
## routing rewrites back through the file format:
##
## * It is LOSSLESS. No name mangling, no module index, no stubs, so nothing can
## be lost or renamed on the way through. `toPNode(toTokenBuf(n))` is `n`
## again, and `cgen`'s grinder checks the stronger property — that the cursor
## answers identically to the ORIGINAL `PNode` at every node, with no
## tolerated differences at all, unlike the file path which needs two.
## * `sym` IS IDEMPOTENT HERE, FIELDS INCLUDED. On the file path it is not, and
## cannot be: a cross-context field reference has no index entry, so
## `loadFieldStub` mints a fresh `skField` stub per use because two distinct
## fields can share a name and a position across types. That is what blocks
## `aliases.isPartOf` from moving to the seam (see `bnode.sym`). A bridged
## buffer hands back the same object every time, so code that compares field
## identity is correct on it.
## * The ENCODER is cheap, and that part is measured: no string formatting, no
## pool lookups for names, no index seeks, just a tree walk and two `seq.add`s.
## Building a buffer for every routine and NOT reading it costs 6.79s against a
## 6.75s baseline on a 50-module target — inside the noise.
##
## READING is not free, and that is where the cost of the whole seam sits.
## Driving the generator off cursors takes the same target from 6.75s to 8.85s,
## **+31%**, stable across interleaved runs. Since a compile is mostly frontend,
## codegen itself is slowed by considerably more than 31%. The suspects are the
## per-access costs a `PNode` does not have: `son(n, i)` is O(i) because it skips
## from the first child, `kind` checks the tag pool and indexes a memo on every
## call, `sym`/`typ` go through the nav, and `origin` is a hash lookup on every
## location built. None of that is inherent — `son` could cache, `origin` could
## key on something cheaper — but none of it has been optimised, and the number
## is here so nobody has to rediscover it before deciding whether to.
##
## WHAT IT IS NOT. The buffer is transient and process-local: `(bsym …)` means
## nothing without the tables beside it, so a bridged buffer must never be
## written to a file. The `.bif` writer in `ast2nif` is still the only thing
## that serializes, and it is a different job — it has to name things precisely
## because the reader cannot see this process's heap.
##
## USE:
##
## var b = toTokenBuf(n, conf)
## withBridge(b.tables):
## let root = BNode(b.rootCursor) # read it like any other `BNode`
## ...
## let back = toPNode(b) # a fresh `PNode` tree, if a rewrite needs one
##
## `withBridge` and `BNode` live in `bnode.nim` and exist only under
## `-d:newIcBackend`; this module is below that seam and does not depend on it,
## so the encoder and the round trip are usable either way.
import std / tables
import ast, astdef, idents, options, msgs, lineinfos
import icnifcore, ast2nif
import ic / enum2nif
import "../dist/nimony/src/lib/nifcore" except pool
import bodynav
when defined(nimPreviewSlimSystem):
import std / assertions
type
BridgeBuf* = object
## An encoded tree plus everything needed to read it back. Not copyable —
## it owns a `TokenBuf`.
bld*: IcBuilder
tables*: BridgeTables
conf: ConfigRef
symIdx: Table[int, int] ## PSym identity -> index into `tables.syms`
typeIdx: Table[int, int] ## PType identity -> index into `tables.types`
proc initBridgeBuf*(conf: ConfigRef; cap = 64): BridgeBuf =
BridgeBuf(bld: newIcBuilder(cap), tables: BridgeTables(), conf: conf,
symIdx: initTable[int, int](), typeIdx: initTable[int, int]())
# ---------------------------------------------------------------------------
# Encode
#
# The shape mirrors the `.bif` node encoding exactly — `(<kind> <flags> <type>
# <child|payload>…)` — so `bnode` reads a bridged buffer with the accessors it
# already has. Only the two leaves that would have been NAMES differ.
proc symIndex(b: var BridgeBuf; s: PSym): int =
## Symbols are deduplicated by identity, so the same `PSym` referenced twenty
## times costs one table slot and twenty equal indices — which is also what
## makes `sym` idempotent on the way back.
let key = cast[int](s)
result = b.symIdx.getOrDefault(key, -1)
if result < 0:
result = b.tables.syms.len
b.tables.syms.add s
b.symIdx[key] = result
proc typeIndex(b: var BridgeBuf; t: PType): int =
let key = cast[int](t)
result = b.typeIdx.getOrDefault(key, -1)
if result < 0:
result = b.tables.types.len
b.tables.types.add t
b.typeIdx[key] = result
proc emitInfo(b: var BridgeBuf; info: TLineInfo) =
## Line info goes through the SAME filename pool the `.bif` writer uses
## (`icPool.filenames`, keyed by full path), so `bnode.info` — which resolves
## through the decoder's `oldLineInfo` — needs no bridge-specific path.
if info == unknownLineInfo: return
b.bld.lineInfo(msgs.toFullPath(b.conf, info.fileIndex),
info.line.int32, info.col.int32)
proc emitFlags(b: var BridgeBuf; flags: TNodeFlags) =
var asIdent = ""
genFlags(flags, asIdent)
if asIdent.len > 0: b.bld.addIdent asIdent
else: b.bld.addDotToken()
proc emitTypeSlot(b: var BridgeBuf; t: PType) =
if t == nil:
b.bld.addDotToken()
else:
b.bld.openTag bridgeTypeTagName
b.bld.addIntLit typeIndex(b, t).int64
b.bld.closeTag()
proc encodeNode(b: var BridgeBuf; n: PNode)
proc encodeSym(b: var BridgeBuf; n: PNode) =
## `(nflags <flags> (ht <type> (bsym <idx>)))`, always the full chain.
##
## The wrappers are unconditional on purpose. The `.bif` writer emits them
## only when the node differs from its symbol, which is what creates the
## `(ht . <sym>)` shape whose nil is load-bearing and whose meaning depends on
## whether the symbol was loaded yet — a real ambiguity that cost a reverted
## commit on this branch. A bridge has no reason to inherit it: spelling the
## node's own type and flags out every time costs four tokens and makes the
## answer exact by construction.
b.bld.openTag symNodeFlagsTagName
b.emitInfo(n.info)
b.emitFlags(n.flags)
b.bld.openTag hiddenTypeTagName
b.emitTypeSlot(n.typ) # the LAZY-AWARE accessor: what `ast.typ` says
b.bld.openTag bridgeSymTagName
b.bld.addIntLit symIndex(b, n.sym).int64
b.bld.closeTag() # bsym
b.bld.closeTag() # ht
b.bld.closeTag() # nflags
proc encodeNode(b: var BridgeBuf; n: PNode) =
if n == nil:
# A nil child is a `DotToken` and has no origin: there is no node to
# remember, and `originOf` answering nil for it is the right answer.
b.bld.addDotToken()
return
# ORIGIN TRACKING. `len` is where this node's head token is about to land, and
# `cursorToPosition` is its inverse — nifcore documents that index as a stable
# key for exactly this. Recording it is what keeps `TLoc.lode` a `PNode`: a
# cursor-driven generator can still put the ORIGINAL node in a location, so
# the identity comparisons that already exist (`preventNrvo`'s `dest != le`,
# `isPartOf(d.lode, …)`) keep meaning what they meant. Without this the
# generator could not migrate without `TLoc` itself changing representation —
# and `TLoc` lives in `astdef`, at the bottom of the module graph, so that
# would push the seam far below the backend.
b.tables.origins[b.bld.buf.len] = n
if n.kind == nkSym and n.sym != nil:
encodeSym(b, n)
return
b.bld.openTag toNifTag(n.kind)
b.emitInfo(n.info)
b.emitFlags(n.flags)
b.emitTypeSlot(n.typ)
case n.kind
of nkCharLit:
b.bld.addCharLit char(n.intVal)
of nkIntLit..nkInt64Lit:
b.bld.addIntLit n.intVal
of nkUIntLit..nkUInt64Lit:
b.bld.addUIntLit cast[uint64](n.intVal)
of nkFloatLit..nkFloat128Lit:
b.bld.addFloatLit n.floatVal
of nkStrLit..nkTripleStrLit:
b.bld.addStrLit n.strVal
of nkIdent:
b.bld.addIdent n.ident.s
of nkSym:
# `n.sym == nil`, which `encodeSym` cannot express. It is a broken node
# either way; encode it as a childless `nkSym` so the walk stays total.
discard
of nkNone, nkEmpty, nkNilLit, nkType, nkCommentStmt:
discard
else:
for child in sons(n): encodeNode(b, child)
b.bld.closeTag()
proc toTokenBuf*(n: PNode; conf: ConfigRef): BridgeBuf =
## Encode a whole tree. `n` is not modified and not retained: the buffer holds
## tokens, and the tables hold the `PSym`/`PType` objects the tree pointed at.
result = initBridgeBuf(conf)
encodeNode(result, n)
# The tables carry a BORROWED pointer to the buffer so `originAt` can key
# against it. Set once, here, after encoding is finished and the buffer will
# not be reallocated out from under it.
result.tables.buf = addr result.bld.buf
proc originOf*(b: var BridgeBuf; c: Cursor): PNode {.inline.} =
## The `PNode` that was encoded at `c`, or nil when `c` is a `DotToken` (a nil
## child) or does not point at a node head. Identity-preserving: this is the
## very object the encoder was handed, not a copy, which is the whole point.
b.tables.buf = addr b.bld.buf
originAt(b.tables, c)
proc rootCursor*(b: var BridgeBuf): Cursor {.inline.} =
## A read cursor at the encoded root. `beginRead` asserts every tag was
## closed, so a mis-nested encode is caught here rather than as nonsense
## further along.
beginRead(b.bld.buf)
# ---------------------------------------------------------------------------
# Decode
#
# The other direction, for a rewriting pass that has a cursor and needs a tree
# it can mutate. Deliberately NOT written against `bnode`: this module is below
# it (`bnode` reads through a nav, which is exactly the state a decoder should
# not need), and the shape is the encoder's, right here, so the two stay
# legible as a pair.
proc decodeNode(b: BridgeBuf; c: var Cursor): PNode
proc decodeTypeSlot(b: BridgeBuf; c: var Cursor): PType =
if nifcore.kind(c) == DotToken:
result = nil
skip c
else:
doAssert nifcore.kind(c) == TagLit and
c.tags.tagName(cursorTagId(c)) == bridgeTypeTagName,
"bridge: type slot expected"
let payload = childCursor(c)
doAssert nifcore.kind(payload) == IntLit, "bridge: (btyp) payload expected"
let idx = int(nifcore.intVal(payload))
doAssert idx < b.tables.types.len, "bridge: type index out of range"
result = b.tables.types[idx]
skip c
proc decodeFlags(c: var Cursor): TNodeFlags =
result = nodeFlagsFromCursor(c)
skip c
proc decodeSym(b: BridgeBuf; c: var Cursor): PNode =
## Unwinds exactly what `encodeSym` wrote.
var outer = childCursor(c) # inside (nflags
let flags = decodeFlags(outer)
doAssert nifcore.kind(outer) == TagLit and
outer.tags.tagName(cursorTagId(outer)) == hiddenTypeTagName,
"bridge: (ht) expected inside (nflags)"
var ht = childCursor(outer) # inside (ht
let typ = decodeTypeSlot(b, ht)
doAssert nifcore.kind(ht) == TagLit and
ht.tags.tagName(cursorTagId(ht)) == bridgeSymTagName,
"bridge: (bsym) expected inside (ht)"
let payload = childCursor(ht)
doAssert nifcore.kind(payload) == IntLit, "bridge: (bsym) payload expected"
let idx = int(nifcore.intVal(payload))
doAssert idx < b.tables.syms.len, "bridge: sym index out of range"
result = newSymNode(b.tables.syms[idx], lineInfoFromCursor(program, c))
result.typField = typ
result.flags = flags
skip c
proc decodeNode(b: BridgeBuf; c: var Cursor): PNode =
case nifcore.kind(c)
of DotToken:
result = nil
skip c
of TagLit:
let tag = c.tags.tagName(cursorTagId(c))
if tag == symNodeFlagsTagName:
return decodeSym(b, c)
let kind = parse(TNodeKind, tag)
let info = lineInfoFromCursor(program, c)
var inner = childCursor(c)
let flags = decodeFlags(inner)
let typ = decodeTypeSlot(b, inner)
result = newNodeI(kind, info)
result.flags = flags
result.typField = typ
case kind
of nkCharLit..nkUInt64Lit:
result.intVal =
case nifcore.kind(inner)
of CharLit: BiggestInt(ord(charLit(inner)))
of UIntLit: cast[BiggestInt](nifcore.uintVal(inner))
else: BiggestInt(nifcore.intVal(inner))
of nkFloatLit..nkFloat128Lit:
result.floatVal = nifcore.floatVal(inner)
of nkStrLit..nkTripleStrLit:
result.strVal = strVal(inner)
of nkIdent:
result.ident = identFromCursor(program, inner)
else:
while inner.hasMore:
result.sons.add decodeNode(b, inner)
skip c
else:
raiseAssert "bridge: unexpected token " & $nifcore.kind(c)
proc toPNode*(b: var BridgeBuf): PNode =
## The tree the buffer encodes, as fresh `PNode`s sharing the ORIGINAL
## `PSym`s and `PType`s. Round-tripping is therefore identity-preserving for
## symbols and types and structure-preserving for everything else, which is
## what a rewriting pass needs: it can rebuild a subtree without the symbols
## underneath it changing identity.
var c = rootCursor(b)
result = decodeNode(b, c)

View File

@@ -38,15 +38,6 @@ proc transformBody*(g: ModuleGraph; idgen: IdGenerator; prc: PSym; flags: Transf
import closureiters, lambdalifting
when not defined(nimKochBootstrap):
# The `PNode` -> `TokenBuf` bridge, and through it `bodynav`, which resolves
# names against `ast.program`. `program` does not EXIST under
# `-d:nimKochBootstrap` — that define disables the whole IC subsystem (see
# `ast.nim` and `koch.bootic`) — so the bridge has to be out of that build
# too, not merely unused by it. `handOffBody` below is guarded for the same
# reason; its only caller is `cgen`, under `-d:newIcBackend`.
import nodebridge
type
PTransCon = ref object # part of TContext; stackable
mapping: TIdTable[PNode] # mapping from symbols to nodes
@@ -1445,25 +1436,6 @@ proc transformBody*(g: ModuleGraph; idgen: IdGenerator; prc: PSym; flags: Transf
#if prc.name.s == "main":
# echo "transformed into ", renderTree(result, {renderIds})
when not defined(nimKochBootstrap):
proc handOffBody*(body: PNode; conf: ConfigRef): BridgeBuf =
## THE HANDOFF from the rewriting stage to the reading stage: the transformed
## body, as a `TokenBuf` a reader can cursor over (`nodebridge`).
##
## It lives here because the invariant it carries is this module's: a bridged
## buffer is a SNAPSHOT, so it must be taken after the LAST rewrite the body
## will receive. Anything that mutates a node afterwards — `cgen.easyResultAsgn`
## setting `nfPreventCg` is the one that does — leaves the buffer describing a
## tree that no longer exists.
##
## The call site is in `cgen` rather than at the end of `transformBody` for
## exactly that reason: destructor injection runs *after* `transformBody`
## returns and is another rewrite, so transforming is not the last step and a
## buffer taken here would be stale before it was read. `transformBody` returns
## a `PNode` on purpose; this is the point where a caller that has finished
## rewriting says so.
result = toTokenBuf(body, conf)
proc transformStmt*(g: ModuleGraph; idgen: IdGenerator; module: PSym, n: PNode; flags: TransformFlags = {}): PNode =
if nfTransf in n.flags:
result = n

View File

@@ -10,7 +10,7 @@
# tree helper routines
import
ast, wordrecg, idents, bnode
ast, wordrecg, idents
proc cyclicTreeAux(n: PNode, visited: var seq[PNode]): bool =
result = false
@@ -83,8 +83,8 @@ proc sameTree*(a, b: PNode): bool =
if not sameTree(a[i], b[i]): return
result = true
proc getMagic*(op: AnyNode): TMagic =
if op.isNilNode: return mNone
proc getMagic*(op: PNode): TMagic =
if op == nil: return mNone
case op.kind
of nkCallKinds:
let callee = op.firstSon
@@ -93,7 +93,7 @@ proc getMagic*(op: AnyNode): TMagic =
else: result = mNone
else: result = mNone
proc isConstExpr*(n: AnyNode): bool =
proc isConstExpr*(n: PNode): bool =
const atomKinds = {nkCharLit..nkNilLit} # Char, Int, UInt, Str, Float and Nil literals
n.kind in atomKinds or nfAllConst in n.flags
@@ -103,7 +103,7 @@ proc isCaseObj*(n: PNode): bool =
for i in 0..<n.safeLen:
if n[i].isCaseObj: return true
proc isDeepConstExpr*(n: AnyNode; preventInheritance = false): bool =
proc isDeepConstExpr*(n: PNode; preventInheritance = false): bool =
case n.kind
of nkCharLit..nkNilLit:
result = true
@@ -141,7 +141,7 @@ proc isRange*(n: PNode): bool {.inline.} =
else:
result = false
proc whichPragma*(n: AnyNode): TSpecialWord =
proc whichPragma*(n: PNode): TSpecialWord =
let key = if n.kind in nkPragmaCallKinds and n.hasSons: n.firstSon else: n
case key.kind
of nkIdent: result = whichKeyword(key.ident)
@@ -207,7 +207,7 @@ proc extractRange*(k: TNodeKind, n: PNode, a, b: int): PNode =
result = newNodeI(k, n.info, b-a+1)
for i in 0..b-a: result[i] = n[i+a]
proc getRoot*(n: AnyNode): PSym =
proc getRoot*(n: PNode): PSym =
## ``getRoot`` takes a *path* ``n``. A path is an lvalue expression
## like ``obj.x[i].y``. The *root* of a path is the symbol that can be
## determined as the owner; ``obj`` in the example.
@@ -254,7 +254,7 @@ proc isRunnableExamples*(n: PNode): bool =
result = n.kind == nkSym and n.sym.magic == mRunnableExamples or
n.kind == nkIdent and n.ident.id == ord(wRunnableExamples)
proc skipAddr*[T: AnyNode](n: T): T {.inline.} =
proc skipAddr*(n: PNode): PNode {.inline.} =
result = if n.kind in {nkAddr, nkHiddenAddr}: n.firstSon else: n
proc getPotentialWrites*(n: PNode; mutate: bool; result: var seq[PNode]) =

View File

@@ -11,7 +11,7 @@
import
ast, astalgo, trees, msgs, platform, renderer, options,
lineinfos, int128, modulegraphs, astmsgs, bnode
lineinfos, int128, modulegraphs, astmsgs
import std/[intsets, strutils]
@@ -102,7 +102,7 @@ proc isPureObject*(typ: PType): bool =
proc isUnsigned*(t: PType): bool =
t.skipTypes(abstractInst).kind in {tyChar, tyUInt..tyUInt64}
proc getOrdValueAux*(n: AnyNode, err: var bool): Int128 =
proc getOrdValueAux*(n: PNode, err: var bool): Int128 =
var k = n.kind
if n.typ != nil and n.typ.skipTypes(abstractInst).kind in {tyChar, tyUInt..tyUInt64}:
k = nkUIntLit
@@ -124,12 +124,12 @@ proc getOrdValueAux*(n: AnyNode, err: var bool): Int128 =
err = true
int128.Zero
proc getOrdValue*(n: AnyNode): Int128 =
proc getOrdValue*(n: PNode): Int128 =
var err: bool = false
result = getOrdValueAux(n, err)
#assert err == false
proc getOrdValue*(n: AnyNode, onError: Int128): Int128 =
proc getOrdValue*(n: PNode, onError: Int128): Int128 =
var err = false
result = getOrdValueAux(n, err)
if err:
@@ -1392,7 +1392,7 @@ proc classify*(t: PType): OrdinalType =
result = IntLike
else: result = NoneLike
proc skipConv*[T: AnyNode](n: T): T =
proc skipConv*(n: PNode): PNode =
result = n
case n.kind
of nkObjUpConv, nkObjDownConv, nkChckRange, nkChckRangeF, nkChckRange64:

View File

@@ -380,6 +380,59 @@ widely-imported module is not, and the cost is almost entirely frontend re-sem.
(see the comment at `generateEmitStage`): partial `emit` leaves inconsistent
ownership across the `.c` set. This path was tried and reverted; do not retry.
Where a cold build's time is (measured)
---------------------------------------
Numbers from `-d:icBNodeProf` (`compiler/icprof.nim`; each process appends a
line to `$NIM_IC_BNODE_PROF` tagged `stage=<name>`), on Atlas, 204 modules,
cold, 2026-08-31. They are recorded here because two obvious optimisations
were tried against them and did not pay.
Per stage, summed process wall, parallel build of 9.66s elapsed:
| stage | procs | wall |
| ----- | ----- | ---- |
| frontend (`nim m`) | 181 | 10.60s |
| lower | 14 | 4.49s |
| cg | 14 | 4.50s |
| merge | 1 | 0.20s |
| emit | 14 | 0.42s |
| link (the whole C compile + link) | 1 | 1.65s |
A `nim m` process splits as: startup 2%, loading imported `.s.bif` 46%,
writing its own `.s.bif` 18%, sem + parse 34% — two thirds of the frontend is
artifact I/O. The loading is not concentrated anywhere (`BifLoad` 695ms,
`PosIndex` 519ms, `ModuleId` 841ms, `TopLevel` 1459ms = offers 569 + export
branch 312 + log ops 137 + the bare cursor walk ~371); it is 180 processes each
re-parsing ~20 modules' interfaces out of 44.7MB of `.s.bif`, i.e. the
amortisation problem that batching solved for the backend
(`loadDepClosure` 10.2s -> 1.3s) and the frontend has not solved.
- **Hidden interface stubs** were 1.05s of that loading (1.70M stubs against
0.29M exported ones) and are now built on demand
(`modulegraphs.ensureHiddenIface`). A module has TWO FileIndexes — the NIF
suffix's `fikNifModule` entry keys `DecodeContext.mods`, the source file's
keys `g.ifaces` — so the lazy builder takes a suffix.
- **The tooling-only header records** (`sig`, `expansion`, `modulesrc`) are
80% of every module header the loader walks (3.36M of 4.19M nodes) and
skipping them entirely was measured at 53ms: `skip` on a `TagLit` is a
jump, ~16ns a node. Not worth a format change.
- **The C compiler** is the largest CPU item (12.2s against a whole-program
build's 10.2s) and the smallest wall lever: it fans out across cores, and the
excess over a whole-program build is ~0.4s of wall. 3.8MB of the 5.4MB of
extra C is per-TU prototypes and typedefs, intrinsic to 204 translation units
instead of 139; 53 of the 204 object files define nothing and compiling all
of them costs 0.23s of user time. Fewer, larger TUs is the only real fix and
trades directly against what IC exists for.
- **Reading routine bodies off a `.bif` cursor instead of a `PNode`** was
built and measured (branch `araq-ic-fixes2`, removed again in
`araq-ic-fixes3`): it reached parity with the tree, not a win, and could
only ever have saved `transformBody` + the body hand-off — under 1% of the
build. The lasting result of that work is the loader's `oldLineInfo`
memoization, which halved a cold `--ic:on` build, and the cgen files'
iterator/named-accessor vocabulary (`sons`/`sonsFrom`/`sonsButLast`,
`firstSon`/`secondSon`/`son`, `baseClass`/`returnType`/`elementType`).
Code, logic & debugging
========================

View File

@@ -32,8 +32,8 @@ Cap the fan-out to fit the machine — precedence documented at `deps.nim`'s
-d:icNoParallel # serial, and non-interleaved child output
Serial output matters for a second reason: the parallel backend processes share
one stderr, so any per-process diagnostic printing (`NIM_IC_BNODE_GRIND`,
`-d:icCanRaiseLog`) interleaves and produces torn lines. Either use
one stderr, so any per-process diagnostic printing (`-d:icCanRaiseLog`)
interleaves and produces torn lines. Either use
`-d:icNoParallel` or parse defensively and count what you dropped.
## Running a single test

View File

@@ -49,6 +49,13 @@ proc bar(s: var seq[int], a: int) =
s.bar(5)
doAssert(s == @[123, 1])
# Imported JavaScript patterns must receive the underlying array, not the
# `{base, off, len}` view used for regular `var openArray` parameters.
proc jsSort[T](x: var openArray[T], cmp: proc(a, b: T): int) {.importcpp: "#.sort(#)", nodecl.}
var sorted = @[2, 1]
sorted.jsSort(proc(a, b: int): int = a - b)
doAssert(sorted == @[1, 2])
import tables
block: # Test get addr of byvar return value
var t = initTable[string, int]()

View File

@@ -1,136 +0,0 @@
# Shapes the predicate grinder needs, in an IMPORTED module with STATEMENT-LIST
# bodies.
#
# Two constraints, both structural, both learned by measuring rather than
# guessing:
#
# 1. The main module's routines are built in-process and never arrive as a
# deferred body, so nothing written in `grindme.nim` is graded at all.
#
# 2. `ast2nif` defers only bodies whose root is an `nkStmtList` (see the comment
# at the placeholder site: 82.5% of bodies, with one-line `nkAsgn` bodies the
# bulk of the rest). A `proc f(x: int): int = case x ...` has an `nkAsgn`
# body and is loaded eagerly, so it is invisible to the grinder. Every proc
# here therefore opens with a statement.
import std/strutils
proc risky*(x: int): int =
if x < 0: raise newException(ValueError, "neg")
result = x * 2
proc classifyChar*(c: char): string =
## `branchHasTooBigRange`, false side: char ranges are all under the limit.
var r = ""
case c
of 'a'..'z': r = "lower"
of 'A'..'Z': r = "upper"
of '0'..'9', '_': r = "wordish"
else: r = "other"
result = r
proc bigRange*(x: int): int =
## `branchHasTooBigRange`, TRUE side: 100000 > RangeExpandLimit (256).
var r = 0
case x
of 0..100000: r = 1
of 100001..200000: r = 2
else: r = 3
result = r
proc smallRange*(x: int): int =
var r = 0
case x
of 0..10: r = 1
of 11..20: r = 2
else: r = 3
result = r
proc inSets*(c: char): bool =
## `fewCmps` true side: a narrow set of an int-based element type.
discard
result = c in {'a', 'e', 'i', 'o', 'u'} and c notin {'x'..'z'}
proc bigSet*(c: char): bool =
## `fewCmps` false side: wide enough that emitting the set wins.
discard
result = c in {'a'..'z', 'A'..'Z', '0'..'9', '_', '-', '.', '+', '/', '=', '%'}
proc sumOpen*(xs: openArray[int]): int =
## `reifiedOpenArray`: an openarray PARAM is the one shape answering false.
result = 0
for x in xs: result += x
proc viaOpen*(xs: seq[int]): int =
result = 0
result += sumOpen(xs)
result += sumOpen([1, 2, 3])
result += sumOpen(xs.toOpenArray(0, 0))
proc adder*(n: int): proc (x: int): int =
## A real closure — `isConstClosure` false side.
discard
result = proc (x: int): int = x + n
proc constClosure*(): proc (x: int): int =
## `isConstClosure` TRUE side: a top-level routine as a closure value pairs
## the sym with a nil environment.
discard
result = risky
proc tuples*(): (int, string) =
discard
result = (risky(2), classifyChar('q'))
proc noInitVar*(): int =
## `hasNoInit`: a call to a `.noinit.` routine.
var t {.noinit.}: array[4, int]
t[0] = 1
result = t[0]
proc guardedLib*(x: int): string =
## `bodyCanRaise` through both a raising call and its arguments.
try:
result = $risky(x) & $risky(x + 1)
except ValueError:
result = "err"
finally:
discard
proc scanEnd*(x: int): int =
## `stmtsContainPragma(wLinearScanEnd)` and, through it, a NON-ZERO
## `ifSwitchSplitPoint`. Without this both answer the same thing at every node
## in the closure — the stdlib uses neither pragma — and the grinder grades
## two constants.
var r = 0
case x
of 0:
r = 1
of 1:
{.linearScanEnd.}
r = 2
of 2: r = 3
else: r = 4
result = r
type Op* = enum opAdd, opAdd2, opSub, opEnd
proc computedGotoLoop*(inp: openArray[Op]): int =
## `stmtsContainPragma(wComputedGoto)`, the other word the equivalence check
## against `getPragmaStmt` looks for. The operand is an ENUM because
## `computedGoto` requires an exhaustive case and rejects an `else`, and it
## jumps straight from the end of one branch to the next dispatch — the
## `while` condition is NOT re-evaluated, so termination has to come from an
## explicit op.
var r = 0
var i = 0
while true:
{.computedGoto.}
let op = inp[i]
case op
of opAdd: r += 1
of opAdd2: r += 2
of opSub: r -= 1
of opEnd: break
inc i
result = r

View File

@@ -1,47 +0,0 @@
import std/[strutils, tables, algorithm]
import grindlib
type Kind = enum kA, kB, kC
type Item = object
name: string
k: Kind
vals: seq[int]
proc classify(i: Item): string =
case i.k
of kA:
if i.vals.len > 2: result = "many"
else: result = "few"
of kB:
for v in i.vals:
if v < 0: return "neg"
result = "pos"
of kC:
result = i.name.toUpperAscii
proc total(i: Item): int =
for v in i.vals: result += v
iterator pairsish(t: Table[string, int]): (string, int) =
for k, v in t: yield (k, v)
proc build(): Table[string, int] =
result = initTable[string, int]()
var items = @[Item(name: "a", k: kA, vals: @[1, 2, 3]),
Item(name: "b", k: kB, vals: @[-1]),
Item(name: "c", k: kC, vals: @[])]
items.sort(proc (x, y: Item): int = cmp(x.name, y.name))
for it in items:
result[classify(it)] = total(it)
when isMainModule:
var t = build()
var keys: seq[string] = @[]
for k, v in pairsish(t): keys.add k & "=" & $v
keys.sort()
echo keys.join(",")
echo guardedLib(5), " ", guardedLib(-5)
echo classifyChar('Q'), bigRange(150000), smallRange(5), inSets('e'), bigSet('q')
echo viaOpen(@[1, 2, 3]), adder(4)(5), constClosure()(3), noInitVar()
echo tuples()
echo scanEnd(1), " ", computedGotoLoop([opAdd, opAdd2, opSub, opEnd])

View File

@@ -1,49 +0,0 @@
# `NIM_IC_BNODE_GRIND` target
Input for the differential oracle in `compiler/cgen.nim` (`grindBNode`), which
runs every codegen proc that has moved to `AnyNode` over BOTH the `.bif` cursor
and the materialised `PNode` for the same body and requires the same answer.
nim c -d:newIcBackend -o:bin/nim_grind compiler/nim.nim
NIM_IC_BNODE_GRIND=1 bin/nim_grind c --ic:on --nimcache:/tmp/ncgrind \
tools/icgrind/grindme.nim
A disagreement is an `internalError` naming the proc, the path within the body
and both answers. Each backend process reports its coverage on exit:
BNODEGRIND navHits=… navFallbacks=… navRegistered=… graded=… skipDecl=… skipTyp=…
`graded` is what the number "0 disagreements" is worth. The two skip counts are
printed beside it on purpose, so a run that grades nothing cannot be mistaken
for a run that grades everything.
## What this target is for
The oracle grades whatever the dependency closure contains, so most of its
coverage comes from the standard library for free. This target exists for the
shapes the stdlib closure does NOT produce often enough to exercise both
answers of a predicate — a `case` branch wider than `RangeExpandLimit`, a set
literal narrow enough for `fewCmps` to prefer comparisons, an `openArray`
parameter (the one shape `reifiedOpenArray` answers `false` for).
## Two things that silently produce no coverage
Both were found by counting, after adding shapes here that turned out never to
be graded at all:
1. **The main module's routines are never graded.** They are built in-process
and never arrive as a deferred body. Anything worth grading has to live in
`grindlib.nim`, not in `grindme.nim`.
2. **Only `nkStmtList` bodies are deferred**, so only those can be graded — see
the placeholder site in `ast2nif.loadRoutine`. A one-line
`proc f(x: int): int = case x ...` has an `nkAsgn` body, is loaded eagerly,
and is invisible to the oracle. Every routine here opens with a statement for
that reason. Measured on this target: 782 of 1434 bodies reach the grinder.
## Known coverage gap
`isConstClosure` is graded but only ever on its `false` side: a const closure
(`nkClosure(<routine sym>, nil)`) does not appear in any graded body of this
closure — the whole run contains exactly one `nkClosure` node, the real closure
in `adder`. Adding a shape here that produces one would be worth doing.