mirror of
https://github.com/nim-lang/Nim.git
synced 2026-09-01 19:33:42 +00:00
Compare commits
2 Commits
araq-ic-fi
...
devel
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f72860d7d | ||
|
|
859b0ba270 |
171
compiler/ast.nim
171
compiler/ast.nim
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
1132
compiler/bnode.nim
1132
compiler/bnode.nim
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) =
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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]) =
|
||||
|
||||
@@ -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:
|
||||
|
||||
53
doc/ic.md
53
doc/ic.md
@@ -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
|
||||
========================
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]()
|
||||
|
||||
@@ -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
|
||||
@@ -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])
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user