diff --git a/compiler/ast.nim b/compiler/ast.nim index 1a2e91e955..7877d291cb 100644 --- a/compiler/ast.nim +++ b/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 diff --git a/compiler/ast2nif.nim b/compiler/ast2nif.nim index 832d9de3b7..b7b926353c 100644 --- a/compiler/ast2nif.nim +++ b/compiler/ast2nif.nim @@ -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 diff --git a/compiler/astalgo.nim b/compiler/astalgo.nim index ad19af0a22..22c930d6f9 100644 --- a/compiler/astalgo.nim +++ b/compiler/astalgo.nim @@ -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 diff --git a/compiler/astdef.nim b/compiler/astdef.nim index 965e6e7b98..341e98a2f1 100644 --- a/compiler/astdef.nim +++ b/compiler/astdef.nim @@ -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.. 0 + when defined(useNodeIds): const nodeIdToDebug* = -1 # 2322968 var gNodeId: int diff --git a/compiler/bnode.nim b/compiler/bnode.nim deleted file mode 100644 index 3a3c310817..0000000000 --- a/compiler/bnode.nim +++ /dev/null @@ -1,1132 +0,0 @@ -# -# -# The Nim Compiler -# (c) Copyright 2026 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -## `BNode` — the backend's node type, and the seam for running codegen off a -## `.bif` `Cursor` instead of a deserialized `PNode` tree. -## -## A stage's cost tracks the size of the dependency CLOSURE it loads, not the -## module it compiles. That is why this seam exists — but WHERE the closure's -## cost sits has since been measured, and it is not where the seam can reach. -## On a cold `--ic:on` build of a 68-module target (10.1s wall, `-d:icBNodeProf`, -## `-d:icNoParallel`), summed over all 177 backend processes: -## -## loadDepClosure 3306ms of which moduleId 1285ms -## processTopLevel 1516ms -## interface tbls 1323ms -## genProcBody 333ms -## handOffBody 60ms <- the bridge encode -## transformBody 28ms -## -## So reading a routine body off a cursor instead of a tree is DONE and free — -## `genProcBody` costs the same either way (see "WHAT IT COSTS" below) — but -## finishing the job, i.e. reading a `.t.bif` body directly and never -## materialising the `PNode`, can only win back the `handOffBody` + -## `transformBody` line: under 1% of the build. The 41% is in getting the -## closure's INTERFACE into memory, which no amount of body-reading touches. -## (Skipping the interface tables for dep-of-a-dep loads was tried as the -## obvious lever and returns ~200ms, not enough to justify a name that silently -## fails to resolve; see `SkipInterfaceTables`, which stays restricted to -## `loadTransitiveHooks`.) -## -## Anyone about to spend a week on the remaining blockers below — `TLoc.lode` -## above all — should weigh them against that budget first. -## -## With `-d:newIcBackend` `BNode` is a `distinct Cursor`; without it a plain -## `PNode`, which is what every build does today. Codegen migrates to the -## vocabulary below one area at a time and the compiler keeps building -## throughout, because on the `PNode` side the vocabulary is what `ast`/`astdef` -## already provide — `kind`, `len`, `safeLen`, `sym`, `typ`, `info`, `firstSon`, -## `secondSon`, `lastSon` and the `sons`/`isons`/`sonsFrom`/`sonsButLast`/ -## `isonsButLast` iterators all exist. This module does not redefine those for -## `PNode`; it adds only what the AST lacks (`son`, `hasSons`, `isNilNode`) and -## supplies the whole vocabulary on the `Cursor` side. -## -## THE NODE ENCODING. This is the part that a "a `Cursor` is just a tree -## position" reading gets wrong, and getting it wrong is silent. `ast2nif`'s -## writer emits every node through `withNode`, which is -## -## ( ...) -## -## so a node's own flags and its `typ` occupy the FIRST TWO raw children and the -## AST's child 0 is the THIRD. `nifcore.childCursor` — and `ast2nif`'s own -## `firstSon(n: Cursor)`, which is a RAW structural accessor used to reach the -## name inside an `(sd ...)`/`(td ...)` — return the flags slot, not child 0. -## Every accessor below therefore steps over that two-token prefix. The -## exceptions are exactly: -## -## * `(none)` and a type-less `(empty)`, written bare with NO prefix and no -## children — so "zero raw children" means "no prefix", never "prefix but no -## AST children", which is what makes the two cases distinguishable at all; -## * an `nkSym`, which is not a `TagLit` node: it is a bare `Symbol` token, or -## `(ht )` when the node's type differs from the symbol's, or -## `(nflags )` when persisted node flags need saying, or an -## `(sd ...)` definition; -## * a nil child, written as a `DotToken` — `isNilNode` is the `n == nil` of the -## `PNode` world, which has no `Cursor` counterpart. -## -## `len` follows `safeLen`'s rule and answers 0 for `nkNone..nkNilLit`: the -## payload token of an `nkIntLit`/`nkStrLit`/`nkIdent` sits where a child would, -## and is not one. -## -## `BNode` is DISTINCT from `Cursor` for that same reason. The two accessor sets -## are both spelled `firstSon` and differ by two positions, so a raw structural -## cursor reaching a call site that wants AST children must not silently -## typecheck — and the distinction also keeps `ast2nif.firstSon(n: Cursor)` from -## colliding with this module's. -## -## THE COST MODEL DIFFERS from a `PNode`'s, and that is what the vocabulary is -## shaped around. A `Cursor` is a position in a token stream, and a child is -## reached by stepping over each preceding sibling. Stepping is cheap — a -## `TagLit` token stores the width of its whole subtree, so `nifcore.skip` is a -## single pointer add regardless of how big that subtree is — but there is no -## random access and no way to walk backwards: -## -## * `firstSon` / `secondSon` / `son(n, k)` — O(k) in the NUMBER of preceding -## siblings, not in their size. Cheap for the small constant `k` that nearly -## all structural access in the cgen files uses. -## * `for x in sons(n)` / `sonsFrom(n, k)` / `sonsButLast(n, k)`, and the -## index-yielding `isons` / `isonsButLast` — one pass. ALWAYS use these for a -## loop: `for i in 0..= casePos: break`) rather than counting up -## to the bound. -## * `lastSon(n)` — O(len), because nothing points backwards. Fine once, a trap -## inside a loop; `sonsButLast` is the loop form. -## * `len(n)` — O(len) too: it counts. Do not put it in a loop condition; use -## the iterators, or `hasSons` for an emptiness test. -## -## `kind` is the one accessor whose cost is not structural. A `.bif` carries its -## OWN tag pool, so a tag id means nothing outside the file it came from and -## there can be no process-global id -> `TNodeKind` table; the answer is -## memoized per pool instead, and the memo is dropped when the pool changes. -## -## WHAT IT COSTS, AND HOW TO MEASURE IT. Cursor-driven generation is now the -## same speed as tree-driven: summed over the 2420 routines of a 68-module -## target, `genProcBody` takes 369ms off a cursor and 366ms off a `PNode`. It -## did not start there — it was 1877ms, 5.1x — and the whole difference was ONE -## accessor, so the tools are worth keeping: -## -## * `-d:icBNodeProf` counts every accessor and times the phases (`handOffBody`, -## `genProcBody`, the analyses, and `sym`/`typ`/`info`/`origin`), plus the -## COARSE phases that say where a process's time goes when none of those -## account for it: `Stage` (the whole stage body, so `Process - Stage` is -## startup: exec, runtime init, config replay, graph setup) and the per-stage -## slots `LowerOwned`/`LowerHooks`/`LowerWrite` and -## `CgGen`/`CgInit`/`CgFinish`/`CgWrite`. Each process appends a line to -## `$NIM_IC_BNODE_PROF` tagged `stage=`, so a parallel build still -## produces attributable output. -## -## Read the tag. A `nim m` process arms the profiler through ast2nif but never -## enters a backend stage, so without splitting on `stage=` those 180-odd -## frontend runs land in the "startup" column and invent an enormous phantom -## cost. Atlas at batch size 16, serially, is where that shows: 180 frontend -## processes are 9.9s, and the 28 backend ones are 6.8s of which 6.7s is -## inside the stage bodies — `.t.bif` writing 1.68s and cg's demand-driven -## generation 1.91s are the two largest single items. -## -## The frontend splits (same run, `Stage`/`WriteNif` + the loading slots): -## -## startup (exec+runtime+config) 0.17s 2% -## loading imported `.s.bif` 4.57s 46% -## writing this module's `.s.bif` 1.84s 18% -## sem + parse 3.41s 34% -## -## So two thirds of the frontend is artifact I/O, not compilation. Within the -## loading, `interfHidden` is 1.05s of it: 1.70M hidden-symbol stubs against -## 0.29M exported ones, built by every `nim m` for every module it imports. -## That table is reached ONLY through `modulegraphs.interfSelect` when -## `optImportHidden` is set, which happens in exactly one place — an -## `import x {.all.}`. Skipping it outright (measured with a probe, not a -## guess) takes `InterfTables` 1161ms -> 80ms, the frontend 9.98s -> 8.93s and -## the whole Atlas build 22.19s -> 20.47s. Doing it CORRECTLY means populating -## the table lazily on first `interfSelect(true)` rather than deciding -## up front — a macro-generated `{.all.}` import cannot be seen syntactically, -## and guessing wrong loses symbols silently. -## -## That conversion is DONE (`modulegraphs.ensureHiddenIface` + -## `ast2nif.buildHiddenInterface`), and the thing that made it hard is worth -## knowing: a module has TWO FileIndexes. `registerNifSuffix` keys -## `filenameToIndexTbl` by the NIF SUFFIX and mints a `fikNifModule` entry, -## while the graph indexes `g.ifaces` by the module's `fikSource` file. -## `DecodeContext.mods` is keyed by the former. Asking it with the latter -## misses every single time, silently, and `import x {.all.}` then reports -## "undeclared identifier" for a symbol that is right there. So the lazy -## builder takes a SUFFIX, not a FileIndex. Two further conditions are also -## load-bearing: clear the pending flag only when the build SUCCEEDS (an -## import whose `.s.bif` does not exist yet must be retried, not written off), -## and build into a LOCAL table before assigning it back (loading symbols can -## grow `g.ifaces`, which would leave a `var` alias into it dangling). -## `tests/ic/timporthidden.nim` is what says all of this still holds. -## -## THE FRONTEND'S LOADING, after the `interfHidden` fix above, is 3.55s of -## Atlas's 9.2s frontend and it is NOT concentrated anywhere: -## -## BifLoad 695ms PosIndex 519ms ModuleId 841ms InterfTables 80ms -## TopLevel 1459ms = Offers 569 + ExportBranch 312 + LogOps 137 -## + the bare cursor walk ~371 + Replay/Stmts ~11 -## -## Two candidates inside it were probed and only one paid: -## -## * hidden interface stubs — 1.05s, taken (see above). -## * the tooling-only header records (`sig`, one per signature-symbol -## occurrence, plus `expansion`/`modulesrc`). These are 80% of every header -## the loader walks: 3.36M of 4.19M nodes on Atlas, skipped immediately, -## existing only for `idetools`. Grouping or relocating them looks like an -## obvious win and IS NOT ONE. Emitting none of them at all: nodes walked -## 4.19M -> 0.85M, `.s.bif` 44.7MB -> 44.0MB, `TopLevel` 1459ms -> 1406ms, -## the frontend 9.29s -> 9.18s. Walking 3.3M records costs 53ms, because -## `skip` on a `TagLit` is a jump, not a scan — about 16ns a node. Measured -## with a probe before anything was built, which is the only reason no -## format change was made for 0.5%. -## -## What is left is the per-process re-load itself: 180 `nim m` processes each -## parsing ~20 modules' interfaces out of 44.7MB of `.s.bif`. No single item -## dominates because there is no single item — it is the same amortisation -## problem batching solved for the backend (`loadDepClosure` 10.2s -> 1.3s), and -## the frontend is where it has not been solved yet. -## -## THE C COMPILER is the largest CPU item of a cold Atlas build and the smallest -## wall lever, which is worth writing down so nobody spends a week on it. gcc is -## 12.2s of CPU against a whole-program build's 10.2s — but `callCCompiler` -## fans out across cores, so the whole `link` stage is 1.65s of the parallel -## build's 9.66s, and the EXCESS over a whole-program build is ~0.4s of wall. -## -## Where the excess is, measured on Atlas (204 IC TUs / 20.85MB against -## non-IC's 139 / 15.47MB): -## -## function definitions 3097 vs 3471 (IC emits FEWER; `merge` dedups, -## and 0 duplicated definitions) -## prototypes 11782 vs 7185 +64% -## typedefs (instances) 5870 vs 4854 -## declaration bytes 8.14MB (39%) vs 4.37MB (28%) -## body bytes 12.71MB vs 11.10MB -## -## So 3.8MB of the 5.4MB excess is per-TU DECLARATIONS — prototypes and -## typedefs each TU needs for what it references. That is intrinsic to having -## 204 translation units instead of 139, and the only real fix is fewer, larger -## TUs, which trades directly against the thing IC exists for: a sandwich edit -## currently rebuilds exactly one `.c` and one `.o`. -## -## The tempting sub-target is a dead end too: 76 of the 204 TUs contain no -## function definition at all and 53 produce object files that define NOTHING, -## but compiling all 53 costs 0.23s of user time. Skipping them is worth ~5% of -## the C compile and nothing measurable in wall. -## * `-d:icBridgeOnly` builds the buffer but generates off the tree, which -## separates the ENCODER's cost from the READER's. Encoding is free — it does -## not show in wall time at all. -## -## Three plausible suspects were measured and were all wrong. They are recorded -## so nobody re-guesses them: the tag-name string compares in `typ`/`flags` -## (worth 0.3% of the build); `Cursor`'s reference-counting lifetime hooks, -## which really do run 6M times on that target but cost 60ms of 1900ms; and the -## structural accessors as a whole — `kind`, `son` and the iterators together -## are 32ms of 1877ms. -## -## The cost was `info`: 259k calls at 5.2us each, because resolving a token's -## `FileId` copied a path out of the buffer's filename pool and hashed it, every -## single time. `ast2nif.oldLineInfo` memoizes that per pool now. That also -## halved the cold `--ic:on` build for BOTH representations, because the decoder -## was paying the same price on every node it loaded — so the accessor that -## looked like the cursor path's problem was really the whole IC pipeline's. -## -## RESOLUTION CONTEXT. `sym`, `typ` and `info` cannot be answered by the cursor -## alone: a `Symbol` token holds only a NAME, a type slot only a type's name, -## and a packed line info a `FileId` in the `.bif`'s own filename pool. All -## three need the decoder's state, and the decision recorded here is that they -## take it from AMBIENT STATE rather than from a parameter: -## -## * the `DecodeContext` is the process-wide `ast.program`, which already exists -## and is already what `ast.loadSym`/`loadType` resolve through; -## * the per-body half is a `bodynav.BodyNav` on a STACK, pushed by -## `withBodyScope` for the span of one routine body. It has to be a stack and -## not a single slot because generating one routine can pull in another -## (`genProcNoForward`) before the first is finished. -## -## Threading a `DecodeContext` parameter through the ~230 `PNode`-taking procs -## in the cgen files instead would be exactly the churn the `BNode` seam exists -## to avoid, and codegen is already single-threaded within a stage process -## (`--icBackendStage:cg --icBackendModule:X` compiles one module per process). -## -## The nav is not merely a renamed snapshot: it is a SCOPE CHAIN THE TRAVERSAL -## MAINTAINS (`openScope` / `closeScope` / `registerDefHere`), ported from -## Nimony's `typenav`, so a reader descending a body always has exactly the -## definitions it has already walked past and nothing is copied ahead of time. -## `bodynav`'s module doc has the measurement that says how much of a live -## problem the old snapshot was — the honest answer is "none yet" — and why the -## mechanism is still the right shape. `sym` goes through it; `typ` does not, -## because `ast2nif` already materialises types lazily from the module's type -## index and a frame has nothing to add. -## -## HOW THIS IS VERIFIED. Two oracles, neither of them a hand-written -## expectation: -## -## * the `isMainModule` self-test at the bottom of this file checks the -## accessors against the raw token stream of real `.bif` files. Necessary but -## NOT sufficient, and the reason is worth remembering: a vocabulary that is -## uniformly wrong — every accessor off by the same two positions — satisfies -## every accessor-against-accessor check there is. This test passed 5.2M -## assertions on exactly that wrong model. -## * `cgen.grindBNode` (opt-in, `NIM_IC_BNODE_GRIND=1` on an `--ic:on` build) -## walks the cursor and the materialised `PNode` for the SAME body in -## LOCKSTEP and requires `kind`, `len`, `info`, `flags`, the literal payloads, -## `sym` and `typ` to agree at every node of every routine body in the -## dependency closure — and, separately, requires each migrated `AnyNode` proc -## to return the same answer for the whole body. The `PNode` is the oracle, so -## it compares everything rather than what someone thought to check. That walk -## also DRIVES the nav — it opens a scope per node and offers each child to -## `registerDefHere` before descending — so what is graded is the vocabulary as -## a cursor-native pass would actually use it, not a random-access shortcut. -## -## The second oracle is what found the `typ` bug (a bare `Symbol` answered `nil` -## where `ast.typ` falls back to the symbol's type — which silently turns -## `canRaise` into "cannot raise" and drops the goto-exception check after a -## call) and the `.sons` bug named below. A clean run means nothing until you -## have broken an accessor on purpose and watched the grinder fire. -## -## The cgen files hold to one invariant, which is what makes the eventual flip -## mechanical: NO `[]` AND NO `.sons` ON A `PNode` OUTSIDE OF TREE -## CONSTRUCTION. `.sons` is not merely the un-portable spelling of the walk: it -## is the raw FIELD, so it bypasses the `len` hook that materializes a deferred -## `nfLazyBody` body, and `for x in n.sons` over a routine body that is still a -## placeholder silently visits NOTHING. `for x in sons(n)` goes through -## `safeLen` and materializes. (This is not hypothetical — it is what the -## differential grinder in `cgen.grindBNode` reported the first time it ran, and -## it had been sitting in `containsResult` itself.) Every read is -## `firstSon`/`secondSon`/`lastSon`/`son(n, k)` or one of the iterators; the -## remaining subscripts are writes that build a fresh `nkProcDef` -## (`theProc[namePos] = ...`), which a `Cursor` backend will not do at all, and -## accesses to a `PType`, a `string`, a `seq` or a `Table`, none of which are -## `BNode`s. -## -## A `PType` has its own vocabulary and its own reason for preferring it: `t[0]` -## is the return type, the base class, the index type or the generic head -## depending on the kind, and `ast.sons(t: PType)` is a `proc` returning the raw -## seq — NOT the iterator of the same name — which for a `tyProc` does not hold -## the parameters at all. Reach for `returnType` / `baseClass` / `elementType` / -## `genericHead` and the `kids` / `ikids` / `paramTypes` / `signature` -## iterators, which name the child and go through `[]`. -## -## WHAT RUNS ON A CURSOR, AND WHAT STILL DOES NOT. `expr` and the ~160 emitters -## under it read the routine body through a cursor: `cgen.genProcBody` is handed -## `BNode(bodyBuf.rootCursor)` and the whole generator follows. It had to land in -## one step — `expr` dispatches to all of them — and what made that possible -## without changing `TLoc` was `nodebridge`'s ORIGIN TRACKING: a cursor can name -## the `PNode` it was encoded from, so `TLoc.lode` stays a `PNode` holding the -## same object a tree-driven build would have stored, and the identity -## comparisons already in the backend keep meaning what they meant. -## -## `origin` is also how the generator's own REWRITES survive. It does mutate in -## places (`mAppendSeqElem`, `mNewSeq`, `genSetLengthSeq`, `genWasMoved`, -## `genArrToSeq` replace a child or a type in place; `genEnumToStr`, `mAsgn` and -## `spawn` build fresh trees), and those run on the origin. Where the mutation is -## then read back, generation continues on the origin too — the buffer does not -## see the write, so a cursor would keep reading the slot as encoded. That is the -## one hazard to remember when migrating anything else that writes. -## -## What is left is not a backlog, it is named blockers, each recorded at its own -## site: -## -## * A type's RECORD TREE is not a body. `asgnComplexity`, -## `isEmptyCaseObjectBranch`, `containsOpaqueImportcFieldAux`, -## `genRecordFieldsAux`, `fillResult` and the type-section walkers read -## `PType.n`, which stays a `PNode` by design (see the `BType` note below). -## They are not migration candidates at all. -## * RETURNS A NODE OR NIL — `ccgutils.getPragmaStmt`. `.bif` spells a missing -## child as a `DotToken` INSIDE a tree; there is no nil token to hand back as a -## return value and a `Cursor` is not nilable. The fix is to split the -## predicate out, as `stmtsContainPragma` does. The same reason keeps the -## assignment DESTINATION a `PNode` throughout the call family (`genCall` -## passes nil), along with `check`, `exvar`, `stepNode` and a try's `fin`. -## * WRITES TO THE NODE — `cgen.easyResultAsgn` sets `nfPreventCg`. Unlike the -## generator's rewrites it cannot use `origin`, because it runs BEFORE the -## handoff and the buffer is a snapshot taken after it. -## * THE ALIAS FAMILY stays on `PNode`, and NOT for the reason first recorded -## here. Field identity is exact on a bridged buffer (see `sym` below), so that -## is no longer what blocks it — but every call site passes `d.lode` as one -## operand, and that is a `PNode`, so a generic `isPartOf` would still be -## handed a `PNode` on one side and buy nothing. It moves when `TLoc.lode` -## does, not before. -## * MIXED REPRESENTATION — `potentialAlias` and `getPotentialReads` build and -## consume a `seq[PNode]` alongside the node, so both sides would have to be -## the same spelling. `genParams` materialises its arguments as origins for -## exactly this reason. -## -## There is deliberately no `BType` alongside `BNode`. Types stay `PType`s even -## under `newIcBackend` — `typ` below returns one — because the backend asks -## them semantic questions (`skipTypes`, `getSize`, `lengthOrd`, the record -## walk over `t.n`) that a raw cursor cannot answer. `ast2nif` already -## materializes them lazily from the module's type index, which is the seam -## that matters on that side. - -import ast, lineinfos, idents - -# Re-exported so `cgen`, which includes the `ccg*` files, gets the -# instrumentation along with the seam. -import icprof -export icprof - -when defined(newIcBackend): - when defined(nimPreviewSlimSystem): - import std / assertions # only this branch asserts - import "../dist/nimony/src/lib/nifcore" except pool - import ic / enum2nif - import ast2nif, icnifcore, bodynav - export ast2nif.BodyScope - export bodynav - # Imported only under the define: `cgen` is compiled during the koch - # bootstrap, where the nimony libs are unavailable (`ast2nif` is guarded the - # same way). `nifcore` — NOT `nifcursors` — is the reader half of NIF: `bif` - # loads a `.bif` into a `nifcore.TokenBuf`, and `ast2nif` decodes it with a - # `nifcore.Cursor`. `nifcursors` is the writer/builder cursor over - # `PackedToken`s and is a different type entirely. - - type BNode* = distinct Cursor - ## A cursor at an AST NODE, as distinct from a raw structural cursor: see - ## "THE NODE ENCODING" above. The conversion is deliberately not implicit. - - template raw*(n: BNode): Cursor = Cursor(n) - ## Escape hatch to the underlying structural cursor. Only this module and - ## the decoder should need it. - - proc toBNode*(c: Cursor): BNode {.inline.} = BNode(c) - ## A raw cursor known to sit on an AST node — e.g. the body cursor - ## `ast2nif.lazyBodyCursor` hands back. - - # `nifcore` also has a `kind(c: Cursor): NifKind` — the TOKEN kind (TagLit, - # SymUse, IntLit, ...). It and `kind(n: BNode): TNodeKind` below differ only - # in return type, which Nim cannot overload on, so inside this module the - # nifcore one is always spelled `nifcore.kind`. Modules that import `bnode` - # do not import `nifcore`, so they see only the `TNodeKind` one. - - const - LeafKinds* = {nkNone..nkNilLit} - ## Exactly `astdef.safeLen`'s set: these have no children, and any token - ## sitting after their flags/type prefix is a PAYLOAD (an int, a string, - ## an ident), not a child. - - type - SpecialTag = enum - ## The wrapper tags that are not AST kinds. `typ` and `hasExplicitNilType` - ## have to tell them apart, and did it by comparing the tag NAME on every - ## call — 197k string compares on a 68-module build. They are resolved - ## once per tag id instead, in the same miss path that resolves the kind. - stOther, stHiddenType, stSymDef, stSymNodeFlags, stBridgeSym - - TagInfo = object - kind: int16 ## `TNodeKind` ordinal, -1 while unresolved - special: SpecialTag - - var tagCachePool: TagPool = nil - var tagCache: seq[TagInfo] = @[] - ## `TagId -> (TNodeKind, SpecialTag)` for ONE tag pool. Not a process-global - ## table: a `.bif` carries its OWN tag pool, so ids only mean anything - ## relative to the pool the cursor came from. Codegen works through one - ## module at a time, so a single-entry memo is enough; a pool switch drops - ## it. `tagCachePool` holds a REFERENCE rather than a raw pointer: that is - ## what keeps the pool alive, so a freed pool cannot be replaced by a new - ## one at the same address and silently answer from the wrong tag table. - - proc tagInfoAt(c: Cursor): TagInfo = - ## Memoized decode of the tag at `c`. The miss path is the only place that - ## touches a tag NAME: `parse` is a compare against ~180 strings, and the - ## wrapper tags need four more, so both answers are cached together. - prof pTagKindHit - let pool {.cursor.} = c.tags - if pool != tagCachePool: - tagCachePool = pool - tagCache = @[] - let id = int(uint32(cursorTagId(c))) - if id >= tagCache.len: - let oldLen = tagCache.len - tagCache.setLen(id + 1) - for i in oldLen ..< tagCache.len: tagCache[i] = TagInfo(kind: -1'i16) - if tagCache[id].kind < 0: - prof pTagKindMiss - let name = pool.tagName(cursorTagId(c)) - let sp = if name == hiddenTypeTagName: stHiddenType - elif name == symDefTagName: stSymDef - elif name == symNodeFlagsTagName: stSymNodeFlags - elif name == bridgeSymTagName: stBridgeSym - else: stOther - let k = if sp != stOther: nkSym else: parse(TNodeKind, name) - tagCache[id] = TagInfo(kind: int16(ord(k)), special: sp) - result = tagCache[id] - - proc tagKind(c: Cursor): TNodeKind {.inline.} = - ## The `TNodeKind` a `.bif` TAG encodes — the inverse of `toNifTag`, which - ## is what wrote it (`ast2nif`: `pool.tags.getOrIncl(toNifTag(n.kind))`). - ## The wrapper tags that encode an `nkSym` are folded in; `parse` answers - ## `nkNone` for them (and for every non-AST tag, such as the module-level - ## `(unusedid ...)`). - TNodeKind(tagInfoAt(c).kind) - - proc kind*(n: BNode): TNodeKind = - ## The node kind. A bare `Symbol`/`SymbolDef` token IS an `nkSym` node — it - ## is how the common symbol use is written — and a `DotToken` is the nil - ## child, which has no kind at all and answers `nkNone`; test it with - ## `isNilNode` rather than comparing kinds. - prof pKind - result = - case nifcore.kind(n.raw) - of TagLit: tagKind(n.raw) - of Symbol, SymbolDef: nkSym - else: nkNone - - proc isNilNode*(n: BNode): bool {.inline.} = - ## The `n == nil` of the `PNode` world: `ast2nif` writes a nil child as a - ## `DotToken`, and a cursor is never itself nil. - nifcore.kind(n.raw) == DotToken - - proc hasPrefix(c: Cursor): bool {.inline.} = - ## Whether this `TagLit` carries the `withNode` flags/type prefix. `(none)` - ## and a type-less `(empty)` are written bare, and they are the only nodes - ## with zero raw children, so the test is exact. - cursorJump(c) > 0 - - proc astChildren(n: BNode): Cursor = - ## A cursor at AST child 0, or an exhausted cursor when there is none. - ## Steps over the two-token flags/type prefix; see "THE NODE ENCODING". - prof pAstChildren - result = childCursor(n.raw) - if result.hasMore: skip result # flags - if result.hasMore: skip result # type - - template walkChildren(n: BNode; c, body: untyped) = - ## Shared guard for every child accessor: only a `TagLit` node that is not a - ## leaf kind has children at all. - if nifcore.kind(n.raw) == TagLit and kind(n) notin LeafKinds: - var c = astChildren(n) - body - - proc hasSons*(n: BNode): bool = - walkChildren(n, c): - return c.hasMore - result = false - - proc son*(n: BNode; i: int): BNode = - ## Child `i`. O(i) — `skip` is a single pointer add, because a `TagLit` - ## token carries the width of its whole subtree. - prof pSon - prof(pSkip, i) - walkChildren(n, c): - for _ in 0 ..< i: - doAssert c.hasMore, "son: index out of range" - skip c - doAssert c.hasMore, "son: index out of range" - return BNode(c) - raiseAssert "son: node has no children" - - proc firstSon*(n: BNode): BNode {.inline.} = son(n, 0) - proc secondSon*(n: BNode): BNode {.inline.} = son(n, 1) - - proc len*(n: BNode): int = - ## Counts the children — O(len). Never put this in a loop condition; the - ## iterators below and `hasSons` exist so it is not needed there. Follows - ## `safeLen`: a leaf kind answers 0 even though its payload token is there. - prof pLen - result = 0 - walkChildren(n, c): - while c.hasMore: - inc result - skip c - - proc safeLen*(n: BNode): int {.inline.} = len(n) - ## Same as `len`: `len` already answers 0 for a leaf, so the `PNode` - ## distinction (`len` faults on a literal, `safeLen` does not) has nothing - ## to guard here. - - proc lastSon*(n: BNode): BNode = - ## O(len) — the token stream has no back pointer. Fine once per node, a - ## trap inside a loop; `sonsButLast` is the loop form. - prof pLastSon - walkChildren(n, c): - while c.hasMore: - result = BNode(c) - skip c - return result - raiseAssert "lastSon: node has no children" - - iterator sons*(n: BNode): BNode = - walkChildren(n, c): - while c.hasMore: - prof pIterYield - yield BNode(c) - skip c - - iterator sonsFrom*(n: BNode; start: int): BNode = - walkChildren(n, c): - for _ in 0 ..< start: - if not c.hasMore: break - skip c - while c.hasMore: - yield BNode(c) - skip c - - iterator isons*(n: BNode; start = 0): tuple[i: int, n: BNode] = - walkChildren(n, c): - var i = 0 - while i < start and c.hasMore: - skip c - inc i - while c.hasMore: - yield (i, BNode(c)) - skip c - inc i - - iterator sonsButLast*(n: BNode; count = 1): BNode = - ## One pass with `count` nodes of lookahead — the token stream cannot be - ## walked backwards, so the tail is held back instead of subtracted. - walkChildren(n, c): - var pending: seq[Cursor] = @[] - while c.hasMore: - pending.add c - skip c - if pending.len > count: - yield BNode(pending[0]) - pending.delete(0) - - iterator isonsButLast*(n: BNode; count = 1): tuple[i: int, n: BNode] = - walkChildren(n, c): - var pending: seq[Cursor] = @[] - var i = 0 - while c.hasMore: - pending.add c - skip c - if pending.len > count: - yield (i, BNode(pending[0])) - inc i - pending.delete(0) - - # ---- resolution: the three that need more than the cursor ----------------- - - var navStack {.threadvar.}: seq[BodyNav] - ## Stack, not a single slot: generating one routine can pull in another - ## before the first is finished. See "RESOLUTION CONTEXT" above. - - proc pushBodyNav*(nav: sink BodyNav) = - navStack.add nav - - proc popBodyNav*(): BodyNav {.discardable.} = - doAssert navStack.len > 0, "popBodyNav without a matching push" - result = navStack.pop() - - template withBodyScope*(scope: BodyScope; body: untyped) = - ## Runs `body` with a fresh `BodyNav` over `scope` current, so `sym`/`typ` - ## inside it resolve the body's names. Pops even if `body` raises, because a - ## codegen error is reported and compilation continues. - pushBodyNav(initBodyNav(scope)) - try: - body - finally: - popBodyNav() - - template withBridge*(tables: BridgeTables; body: untyped) = - ## Read a bridged buffer: the nav resolves `(bsym …)` / `(btyp …)` straight - ## out of `tables`. `base` stays empty on purpose — a bridged buffer names - ## nothing, so there is nothing for the decoder to resolve, and leaving a - ## fallback in place would turn a corrupt index into a confusing name - ## lookup instead of the assertion it should be. - pushBodyNav(initBridgeNav(tables)) - try: - body - finally: - popBodyNav() - - proc currentNav*(): ptr BodyNav = - ## The nav for the body being read. A `ptr` because the accessors below - ## MUTATE it (the frame cache), and because copying a nav per access would - ## defeat the point. Valid only until the next push — nothing between taking - ## it and using it pushes, and nothing may: `symAt` bottoms out in the - ## decoder, which never re-enters codegen. - doAssert navStack.len > 0, - "BNode.sym/typ outside withBodyScope: a Symbol token is a NAME and needs " & - "the owning module plus the body's scope to resolve" - result = addr navStack[^1] - - proc openScope*(kind = nsBlock) {.inline.} = openScope(currentNav()[], kind) - proc closeScope*() {.inline.} = closeScope(currentNav()[]) - proc registerDefs*(n: BNode) {.inline.} = registerDefs(currentNav()[], n.raw) - proc registerDefHere*(n: BNode) {.inline.} = - discard registerDefHere(currentNav()[], n.raw) - proc navStats*(): tuple[hits, fallbacks, registered: int] = - let nav = currentNav() - result = (nav.hits, nav.fallbacks, nav.registered) - - template withNodeScope*(kind: NavScopeKind; body: untyped) = - ## The traversal-side half of the nav: a walk brackets each scope-bearing - ## construct with this, and the definitions it passes are registered as it - ## goes. See `bodynav`. - openScope(kind) - try: - body - finally: - closeScope() - - proc sym*(n: BNode): PSym = - ## The `PSym` an `nkSym` node names, resolved through the current nav: its - ## scope chain first, the decoder second. The wrapper forms - ## (`(nflags )`, `(ht )`) are peeled by - ## `bodynav.symToken`, beside the code that derives the lookup key from them, - ## so the two cannot drift apart. - ## - ## NOT IDEMPOTENT FOR OBJECT FIELDS, and anything built on this accessor has - ## to know it. Two calls on the SAME token yield two different `skField` - ## `PSym`s with consecutive item ids: field uses deliberately bypass the - ## nav's memo and go to `loadFieldStub`, which mints per use because two - ## distinct fields can share a name AND a position across types, so one - ## shared stub would mistype one of them (see `bodynav`). For every other - ## symbol kind the answer is stable — the nav memoises it — and `cgen`'s - ## grinder asserts that for the non-field case at every node. - ## - ## The consequence is not theoretical. A proc that reads a field sym twice - ## and compares IDENTITY is correct on a `PNode` and wrong on a FILE-BACKED - ## cursor: `aliases.isPartOf` does exactly that (`a[1].sym.id != b[1].sym.id`, - ## to decide whether two accessor chains touch the same field). - ## - ## A BRIDGED buffer does not have this problem — `nodebridge` hands back the - ## object it was given, and the grinder asserts idempotence for fields there - ## while excluding them here. So field identity is no longer what keeps the - ## alias family on `PNode`; see the note in the module header for what does. - ## What codegen actually consumes for a field is the - ## name it re-navigates the reclist with (`lookupFieldAgain`) plus, for - ## tuples, the position — which is also the tolerance the grinder applies — - ## so the fix for the FILE path is either to compare fields that way or to - ## give a field token a stable identity. The latter needs the token's own - ## position as a key, - ## and `nifcore.Cursor` keeps that pointer private, so it is not something - ## this module can do alone. - prof pSym - timed tSym: - result = symAt(currentNav()[], n.raw) - - proc symTyp(n: BNode): PType = - ## The type of the symbol a sym-shaped node names, or nil. - let s = sym(n) - result = if s == nil: nil else: s.typ - - proc typ*(n: BNode): PType = - ## The node's type, INCLUDING the lazy fallback `ast.typ` performs. - ## - ## A sym node whose node type equals its symbol's is written as a bare - ## `Symbol` with no type slot at all (`writeSymNode`), and the `PNode` - ## loader marks such a node `nfLazyType` so `ast.typ` answers `n.sym.typ`. - ## Answering `nil` here instead is not a *smaller* answer, it is a DIFFERENT - ## one, and silently: `ast.canRaise` asks `fn.typ.kind == tyProc` about a - ## call's callee, so a nil type turns "this call can raise" into "it cannot" - ## and the goto-exception check after the call is dropped. The - ## `.bif`-vs-`PNode` grinder found exactly that. - prof pTyp - icProfStart(tTyp) - defer: icProfStop(tTyp) - let c = n.raw - case nifcore.kind(c) - of Symbol, SymbolDef: - result = symTyp(n) - of DotToken: - result = nil - of TagLit: - prof pTypTagLit - case tagInfoAt(c).special - of stHiddenType: - # `(ht )`: the node type is spelled out because it differed - # from the symbol's at write time. - result = typeAt(currentNav()[], childCursor(c)) - # A nil here is `(ht . )`, an EXPLICITLY nil node type, and it is - # answered as nil — the writer only emits the wrapper when the node's - # type differed from its symbol's, so nil means the node really had - # none. Do NOT fall back to `sym.typ`: a type symbol used as a value - # (`newException(KeyError, ...)`) is exactly this shape, and giving it - # the symbol's type makes sem read the typedesc as an expression of the - # type it denotes. That was tried, and it broke `--ic:on` compilation of - # anything instantiating `tables.[]`. - # - # `ast.typ` may still answer `sym.typ` here, because the loader's - # `nfLazyType` marking depends on whether the symbol happened to be - # loaded already (see `ast2nif`). That is a pre-existing load-order - # dependence in the AST, not a disagreement this side can resolve, and - # the grinder excludes this shape for that reason. - of stSymNodeFlags: - var inner = childCursor(c) - skip inner - result = typ(BNode(inner)) - of stSymDef, stBridgeSym: - # A bare `(bsym …)` is the bridge's spelling of a bare `Symbol`, so it - # answers the same thing: the symbol's own type. The bridge encoder - # always wraps a sym node in `(ht …)`, so this is the belt to that - # braces rather than a path it relies on. - result = symTyp(n) - of stOther: - if not hasPrefix(c): - result = nil - else: - var t = childCursor(c) - skip t # the flags slot - result = typeAt(currentNav()[], t) - else: - result = nil - - proc hasExplicitNilType*(n: BNode): bool = - ## Whether this is the `(ht . )` shape — a sym node the writer gave an - ## EXPLICITLY nil type — after peeling any `(nflags ...)` wrapper, which is - ## how it usually arrives. See `typ` for why nil is the faithful answer and - ## why `ast.typ` may nonetheless say otherwise. - prof pNilType - var c = n.raw - while nifcore.kind(c) == TagLit: - case tagInfoAt(c).special - of stSymNodeFlags: - var inner = childCursor(c) - skip inner # the node flags - c = inner - of stHiddenType: - return nifcore.kind(childCursor(c)) == DotToken - else: - return false - result = false - - proc rawDesc*(n: BNode): string = - ## What the token stream literally says here — the NIF token kind and, for a - ## tag, its name. Diagnostics only: the vocabulary above is the interface, - ## this is the thing it is an interface TO, and a disagreement between the - ## two spellings is almost always explained by the raw shape. - let c = n.raw - result = $nifcore.kind(c) - case nifcore.kind(c) - of TagLit: result.add "/" & c.tags.tagName(cursorTagId(c)) - of Symbol, SymbolDef: result.add "/" & symName(c) - else: discard - - # ---- leaf payloads -------------------------------------------------------- - # - # A literal is `( )`: the value is the single token - # after the prefix. These are the accessors `ccgexprs` reaches for on nearly - # every expression node, so nothing in the expression codegen can migrate - # until they exist. - - proc origin*(n: BNode): PNode = - ## The `PNode` this cursor was encoded from, when it is reading a bridged - ## buffer. This is what lets a cursor-driven generator keep filling - ## `TLoc.lode` with a `PNode`: the answer is the SAME OBJECT the encoder was - ## handed, so the identity comparisons the backend already does still hold. - ## - ## A node head on a bridged buffer always has an origin, so a miss is a bug - ## rather than a shrug — most likely a cursor that is not at a node head. - ## Reading a FILE-backed body has no origins at all and answers nil, which is - ## correct: there is no `PNode` those tokens came from. - prof pOrigin - icProfStart(tOrigin) - let b = currentNav().bridge - if b == nil: return nil - result = originAt(b, n.raw) - icProfStop(tOrigin) - doAssert result != nil or nifcore.kind(n.raw) == DotToken, - "bridged node has no origin: " & rawDesc(n) - - template origin*(n: PNode): PNode = n - ## The `PNode` spelling, so `AnyNode` code can ask for an origin without - ## caring which representation it holds. - - proc atom(n: BNode): Cursor {.inline.} = - ## The payload token of a leaf node. - result = astChildren(n) - - proc intVal*(n: BNode): BiggestInt = - let c = atom(n) - case nifcore.kind(c) - of IntLit: result = BiggestInt(nifcore.intVal(c)) - of UIntLit: result = cast[BiggestInt](nifcore.uintVal(c)) - of CharLit: result = BiggestInt(ord(charLit(c))) - else: raiseAssert "intVal on " & rawDesc(n) - - proc floatVal*(n: BNode): BiggestFloat = - let c = atom(n) - doAssert nifcore.kind(c) == FloatLit, "floatVal on " & rawDesc(n) - result = BiggestFloat(nifcore.floatVal(c)) - - proc strVal*(n: BNode): string = - let c = atom(n) - doAssert nifcore.kind(c) == StrLit, "strVal on " & rawDesc(n) - result = nifcore.strVal(c) - - proc ident*(n: BNode): PIdent = - let c = atom(n) - doAssert nifcore.kind(c) == Ident, "ident on " & rawDesc(n) - result = identFromCursor(program, c) - - proc flags*(n: BNode): TNodeFlags = - ## The node's own flags, MINUS the two the `PNode` side owns rather than the - ## file: `nfHasComment` is never written (comment text lives in a process- - ## local side channel) and `nfLazyType` is a marker the loader adds to say - ## "ask the symbol for my type" — which is what `typ` above does here - ## unconditionally, so on a cursor the flag has nothing to mark. - let c = n.raw - case nifcore.kind(c) - of TagLit: - case tagInfoAt(c).special - of stSymNodeFlags: - # `(nflags )`: the wrapper carries the flags the bare - # `Symbol` token had nowhere to put. - var inner = childCursor(c) - result = nodeFlagsFromCursor(inner) - skip inner - result = result + flags(BNode(inner)) - of stHiddenType, stSymDef, stBridgeSym: - result = {} - of stOther: - if not hasPrefix(c): - result = {} - else: - result = nodeFlagsFromCursor(childCursor(c)) - else: - result = {} - - proc lazyBodyBNode*(node: PNode; scope: var BodyScope; body: var BNode): bool = - ## The cursor for a routine body that is still a deferred `nfLazyBody` - ## placeholder, plus the scope its names resolve in. This is where a `BNode` - ## comes FROM: until codegen is driven off `.bif` cursors end to end, it is - ## the only supply, and it is what lets a migrated proc be run against the - ## `PNode` proc it replaces on the same input. Non-destructive — the - ## placeholder is still materializable afterwards. - var c: Cursor = default(Cursor) - result = lazyBodyCursor(program, node, scope, c) - if result: body = BNode(c) - - proc info*(n: BNode): TLineInfo = - ## No body scope needed: the packed line info resolves through the - ## `.bif`'s own filename pool plus the `ConfigRef`. - prof pInfo - timed tInfo: - result = lineInfoFromCursor(program, n.raw) - - proc isAtom*(n: BNode): bool {.inline.} = - ## `ast.isAtom`, which is a pure `kind` test and so needs nothing from the - ## body scope. It exists here only because `ast.isAtom` is typed `PNode`; - ## the predicate itself is the same one. - result = n.kind >= nkNone and n.kind <= nkNilLit - - # ---- predicates shared with the `PNode` spelling --------------------------- - # - # `ast.canRaise` / `ast.canRaiseConservative` only ever look at a node's - # `kind`, `sym` and `typ`, all three of which this module now answers off a - # `Cursor`. They cannot be written as `AnyNode` procs in `ast.nim` because - # `BNode` is defined HERE and this module imports `ast`; so `ast.nim` keeps - # the body in a template and these two instantiate it. There is no second - # copy of the logic — change the template and both spellings change. - - proc getInt*(n: BNode): Int128 = getIntImpl(n) - - proc skipHiddenAddr*(n: BNode): BNode {.inline.} = skipHiddenAddrImpl(n) - - proc isInfixAs*(n: BNode): bool = isInfixAsImpl(n) - - proc getStr*(n: BNode): string = getStrImpl(n) - - proc skipPragmaExpr*(n: BNode): BNode {.inline.} = - ## The `BNode` spelling of `ast.skipPragmaExpr`. - (if n.kind == nkPragmaExpr: n.firstSon else: n) - - proc canRaiseConservative*(fn: BNode): bool = canRaiseConservativeImpl(fn) - - proc canRaise*(fn: BNode): bool = canRaiseImpl(fn) - - # ---- mixed-mode plumbing -------------------------------------------------- - # - # `son` and `hasSons` are the two vocabulary members the AST does not already - # have, so during the migration they must exist for BOTH node types: the cgen - # files are full of call sites that read a `PSym.ast`, a `PType.n` or a - # freshly built tree, and those stay `PNode`s no matter how far codegen has - # moved. Without these the define does not compile at all and no proc can be - # migrated incrementally. - - template son*(n: PNode; i: int): PNode = n[i] - template hasSons*(n: PNode): bool = n.safeLen > 0 - template isNilNode*(n: PNode): bool = n == nil - - type AnyNode* = PNode | BNode - ## The migration vehicle. A proc written against the vocabulary and typed - ## `AnyNode` serves BOTH representations from one body, which means it can - ## be migrated without a flag day and — more usefully — that the two - ## instantiations can be run against each other on real input. - -else: - type BNode* = PNode - type AnyNode* = PNode - - # Only the three the AST does not already have. Everything else in the - # vocabulary is `ast`/`astdef`'s own `PNode` API — see the module doc. - template origin*(n: BNode): BNode = n - ## No bridge in this build: a node IS its own origin. - - template son*(n: BNode; i: int): BNode = - ## Named indexed access. Exists so a call site states "child i" in a form - ## that survives `BNode` becoming a `Cursor`; keep `i` small and constant. - n[i] - - template hasSons*(n: BNode): bool = - ## Emptiness test that does not compute a length — `len` counts on a - ## `Cursor`. - n.safeLen > 0 - - template isNilNode*(n: BNode): bool = - ## `n == nil`, in the form that survives the flip: a `Cursor` is never nil, - ## and a nil child is a `DotToken` in the token stream. - n == nil - -when isMainModule and defined(newIcBackend): - ## Self-test for the `Cursor` half. The backend still runs on `PNode`s, so - ## these accessors have no call sites that a normal build type-checks, let - ## alone executes. Run it against real `.bif` files: - ## - ## nim c -d:newIcBackend compiler/bnode.nim - ## ./compiler/bnode /*.bif - ## - ## Pass SEVERAL files — each `.bif` carries its own tag pool, so one file - ## alone cannot catch a `kind` cache that fails to notice the pool changed. - ## - ## What this checks that the previous version could not: the accessors agree - ## with the ENCODING, not merely with each other. Checking `sons` against - ## `len` and `firstSon` is satisfied just as well by a vocabulary that is - ## uniformly off by two — which is what it was, and this is what caught it. - ## - ## `sym`/`typ` are NOT exercised here: they resolve through `ast.program` and - ## a `BodyScope`, i.e. a live compiler, so their checks belong to the backend - ## and not to a standalone binary. What IS checked is every shape assumption - ## they rest on — that `(ht ...)`/`(nflags ...)` have exactly two raw children - ## with the symbol second, and that `(sd ...)` opens with a `SymbolDef`. - import std / [os, syncio, assertions] - from "../dist/nimony/src/lib" / bif import load, BifModule - - var nodes = 0 - var checks = 0 - var astNodes = 0 - - proc walk(c: Cursor; base: TokenBuf) = - inc nodes - let n = BNode(c) - template pos(x: Cursor): int = cursorToPosition(base, x) - - # Every RAW child, which is what the traversal follows: the AST model below - # deliberately does not see the module-level metadata tags, and a walk that - # only followed `sons` would never reach most of the file. - var rawKids: seq[Cursor] = @[] - if nifcore.kind(c) == TagLit: - var ch = childCursor(c) - while ch.hasMore: - rawKids.add ch - skip ch - - doAssert isNilNode(n) == (nifcore.kind(c) == DotToken), "isNilNode" - inc checks - - if nifcore.kind(c) in {Symbol, SymbolDef}: - doAssert kind(n) == nkSym, "a Symbol token is an nkSym node" - doAssert not hasSons(n), "a Symbol node has no children" - doAssert len(n) == 0, "a Symbol node has length 0" - inc checks, 3 - - if nifcore.kind(c) == TagLit: - let k = kind(n) - let name = c.tags.tagName(cursorTagId(c)) - - # The encoding invariants the whole vocabulary rests on. - if name == symDefTagName: - doAssert k == nkSym, "(sd ...) is an nkSym node" - doAssert rawKids.len > 0 and nifcore.kind(rawKids[0]) == SymbolDef, - "(sd ...) opens with a SymbolDef" - inc checks, 2 - elif name == symNodeFlagsTagName or name == hiddenTypeTagName: - doAssert k == nkSym, name & " wraps an nkSym" - doAssert rawKids.len == 2, name & " is exactly (payload, symnode)" - doAssert nifcore.kind(rawKids[1]) in {Symbol, SymbolDef, TagLit}, - name & "'s second child is the symbol" - inc checks, 3 - elif k != nkNone: - # A real AST node written through `withNode`: either bare (no prefix and - # no children) or prefix + children. "Exactly one raw child" is - # impossible, and that is what pins the prefix down. - doAssert rawKids.len != 1, - "AST node " & name & " has a flags/type prefix or nothing at all" - inc checks - if rawKids.len == 0: - doAssert not hasSons(n), "bare " & name & " has no children" - doAssert len(n) == 0, "bare " & name & " has length 0" - inc checks, 2 - else: - inc astNodes - let want = - if k in LeafKinds: newSeq[int]() - else: (block: - var s: seq[int] = @[] - for i in 2 ..< rawKids.len: s.add pos(rawKids[i]) - s) - - var listed: seq[int] = @[] - for ch in sons(n): listed.add pos(ch.raw) - doAssert listed == want, - "sons of " & name & " must start AFTER the flags/type prefix" - doAssert listed.len == len(n), "sons/len disagree" - doAssert (listed.len > 0) == hasSons(n), "hasSons/len disagree" - inc checks, 3 - - if listed.len > 0: - doAssert pos(firstSon(n).raw) == listed[0], "firstSon" - doAssert pos(lastSon(n).raw) == listed[^1], "lastSon" - inc checks, 2 - if listed.len > 1: - doAssert pos(secondSon(n).raw) == listed[1], "secondSon" - inc checks - for i in 0 ..< listed.len: - doAssert pos(son(n, i).raw) == listed[i], "son " & $i - inc checks, listed.len - - for start in 0 .. min(3, listed.len): - var got: seq[int] = @[] - for ch in sonsFrom(n, start): got.add pos(ch.raw) - doAssert got == listed[start .. ^1], "sonsFrom " & $start - var gotI: seq[int] = @[] - for i, ch in isons(n, start): - doAssert i == start + gotI.len, "isons index" - gotI.add pos(ch.raw) - doAssert gotI == listed[start .. ^1], "isons " & $start - inc checks, 2 - - for count in 1 .. 2: - let wantB = if listed.len > count: listed[0 ..< listed.len - count] - else: newSeq[int]() - var got: seq[int] = @[] - for ch in sonsButLast(n, count): got.add pos(ch.raw) - doAssert got == wantB, "sonsButLast " & $count - var gotI: seq[int] = @[] - for i, ch in isonsButLast(n, count): - doAssert i == gotI.len, "isonsButLast index" - gotI.add pos(ch.raw) - doAssert gotI == wantB, "isonsButLast " & $count - inc checks, 2 - - # `kind` against an uncached lookup — this is what catches a stale cache - # when the tag pool changes from one file to the next. - let direct = - if name == hiddenTypeTagName or name == symDefTagName or - name == symNodeFlagsTagName: nkSym - else: parse(TNodeKind, name) - doAssert k == direct, "kind" - inc checks - - for ch in rawKids: walk(ch, base) - - let files = commandLineParams() - if files.len == 0: - quit "usage: bnode [more.bif ...]" - for f in files: - var m = bif.load(f) - var c = beginRead(m.buf) - walk(c, m.buf) - endRead c - echo "bnode: files=", files.len, " nodes=", nodes, " astNodes=", astNodes, - " checks=", checks, " OK" diff --git a/compiler/bodynav.nim b/compiler/bodynav.nim deleted file mode 100644 index 6caefd871c..0000000000 --- a/compiler/bodynav.nim +++ /dev/null @@ -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 )` - var inner = childCursor(n) - skip inner - result = navName(inner) - elif tag == symNodeFlagsTagName: - # `(nflags )` - 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 `` 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 diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim index 8ce802d07b..53ae1fee26 100644 --- a/compiler/ccgcalls.nim +++ b/compiler/ccgcalls.nim @@ -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) diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index 56cc6668c4..c38ea3aa60 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -12,7 +12,7 @@ when defined(nimCompilerStacktraceHints): import std/stackframes -proc getNullValueAuxT(p: BProc; orig, t: PType; obj, constOrNil: AnyNode, +proc getNullValueAuxT(p: BProc; orig, t: PType; obj, constOrNil: PNode, result: var Builder; init: var StructInitializer; isConst: bool, info: TLineInfo) @@ -20,7 +20,7 @@ proc getNullValueAuxT(p: BProc; orig, t: PType; obj, constOrNil: AnyNode, proc rdSetElemLoc(conf: ConfigRef; a: TLoc, typ: PType; result: var Rope) -proc genLiteral(p: BProc, n: AnyNode, ty: PType; result: var Builder) = +proc genLiteral(p: BProc, n: PNode, ty: PType; result: var Builder) = case n.kind of nkCharLit..nkUInt64Lit: var k: TTypeKind @@ -46,10 +46,7 @@ proc genLiteral(p: BProc, n: AnyNode, ty: PType; result: var Builder) = of nkNilLit: let k = if ty == nil: tyPointer else: skipTypes(ty, abstractVarRange).kind if k == tyProc and skipTypes(ty, abstractVarRange).callConv == ccClosure: - # `dataCache` is a `PNode`-keyed structural cache, so it needs the node - # itself. `origin` supplies the very node this cursor was encoded from, so - # the key is what it always was. - let id = nodeTableTestOrSet(p.module.dataCache, origin(n), p.module.labels) + let id = nodeTableTestOrSet(p.module.dataCache, n, p.module.labels) let tmpName = p.module.tmpBase & rope(id) if id == p.module.labels: # not found in cache: @@ -92,7 +89,7 @@ proc genLiteral(p: BProc, n: AnyNode, ty: PType; result: var Builder) = else: internalError(p.config, n.info, "genLiteral(" & $n.kind & ')') -proc genLiteral(p: BProc, n: AnyNode; result: var Builder) = +proc genLiteral(p: BProc, n: PNode; result: var Builder) = genLiteral(p, n, n.typ, result) proc genRawSetData(cs: TBitSet, size: int; result: var Builder) = @@ -109,11 +106,11 @@ proc genRawSetData(cs: TBitSet, size: int; result: var Builder) = else: result.addIntLiteral(cast[BiggestInt](bitSetToWord(cs, size))) -proc genSetNode(p: BProc, n: AnyNode; result: var Builder) = +proc genSetNode(p: BProc, n: PNode; result: var Builder) = var size = int(getSize(p.config, n.typ)) - let cs = toBitSet(p.config, origin(n)) + let cs = toBitSet(p.config, n) if size > 8: - let id = nodeTableTestOrSet(p.module.dataCache, origin(n), p.module.labels) + let id = nodeTableTestOrSet(p.module.dataCache, n, p.module.labels) let tmpName = p.module.tmpBase & rope(id) if id == p.module.labels: # not found in cache: @@ -125,7 +122,7 @@ proc genSetNode(p: BProc, n: AnyNode; result: var Builder) = else: genRawSetData(cs, size, result) -proc getStorageLoc(n: AnyNode): TStorageLoc = +proc getStorageLoc(n: PNode): TStorageLoc = ## deadcode case n.kind of nkSym: @@ -151,7 +148,7 @@ proc getStorageLoc(n: AnyNode): TStorageLoc = result = getStorageLoc(n.firstSon) else: result = OnUnknown -proc canMove(p: BProc, n: AnyNode; dest: TLoc): bool = +proc canMove(p: BProc, n: PNode; dest: TLoc): bool = # for now we're conservative here: if n.kind == nkBracket: # This needs to be kept consistent with 'const' seq code @@ -187,8 +184,6 @@ proc genRefAssign(p: BProc, dest, src: TLoc) = p.s(cpsStmts).addCallStmt(fnName, cCast(ptrType(CPointer), rad), rs) proc asgnComplexity(n: PNode): int = - ## Walks a TYPE's record tree (`PType.n`), never a body, so it is not a - ## migration candidate — see the type-record-tree blocker in `bnode`. if n != nil: case n.kind of nkSym: result = 1 @@ -228,7 +223,7 @@ proc genOptAsgnTuple(p: BProc, dest, src: TLoc, flags: TAssignmentFlags) = optAsgnLoc(src, t, field), newflags) proc genOptAsgnObject(p: BProc, dest, src: TLoc, flags: TAssignmentFlags, - t: AnyNode, typ: PType) = + t: PNode, typ: PType) = if t == nil: return let newflags = if src.storage == OnStatic: @@ -544,7 +539,7 @@ proc putLocIntoDest(p: BProc, d: var TLoc, s: TLoc) = else: d = s # ``d`` is free, so fill it with ``s`` -proc putDataIntoDest(p: BProc, d: var TLoc, n: AnyNode, r: Rope) = +proc putDataIntoDest(p: BProc, d: var TLoc, n: PNode, r: Rope) = if d.k != locNone: var a: TLoc = initLoc(locData, n, OnStatic) # need to generate an assignment here @@ -555,10 +550,10 @@ proc putDataIntoDest(p: BProc, d: var TLoc, n: AnyNode, r: Rope) = # we cannot call initLoc() here as that would overwrite # the flags field! d.k = locData - d.lode = origin(n) + d.lode = n d.snippet = r -proc putIntoDest(p: BProc, d: var TLoc, n: AnyNode, r: Rope; s=OnUnknown) = +proc putIntoDest(p: BProc, d: var TLoc, n: PNode, r: Rope; s=OnUnknown) = if d.k != locNone: # need to generate an assignment here var a: TLoc = initLoc(locExpr, n, s) @@ -569,10 +564,10 @@ proc putIntoDest(p: BProc, d: var TLoc, n: AnyNode, r: Rope; s=OnUnknown) = # we cannot call initLoc() here as that would overwrite # the flags field! d.k = locExpr - d.lode = origin(n) + d.lode = n d.snippet = r -proc binaryStmt(p: BProc, e: AnyNode, d: var TLoc, op: TypedBinaryOp) = +proc binaryStmt(p: BProc, e: PNode, d: var TLoc, op: TypedBinaryOp) = if d.k != locNone: internalError(p.config, e.info, "binaryStmt") var a = initLocExpr(p, e.secondSon) var b = initLocExpr(p, son(e, 2)) @@ -580,7 +575,7 @@ proc binaryStmt(p: BProc, e: AnyNode, d: var TLoc, op: TypedBinaryOp) = let rb = rdLoc(b) p.s(cpsStmts).addInPlaceOp(op, getSimpleTypeDesc(p.module, e.secondSon.typ), ra, rb) -proc binaryStmtAddr(p: BProc, e: AnyNode, d: var TLoc, cpname: string) = +proc binaryStmtAddr(p: BProc, e: PNode, d: var TLoc, cpname: string) = if d.k != locNone: internalError(p.config, e.info, "binaryStmtAddr") var a = initLocExpr(p, e.secondSon) var b = initLocExpr(p, son(e, 2)) @@ -588,7 +583,7 @@ proc binaryStmtAddr(p: BProc, e: AnyNode, d: var TLoc, cpname: string) = let rb = rdLoc(b) p.s(cpsStmts).addCallStmt(cgsymValue(p.module, cpname), bra, rb) -template binaryExpr(p: BProc, e: AnyNode, d: var TLoc, frmt: untyped) = +template binaryExpr(p: BProc, e: PNode, d: var TLoc, frmt: untyped) = assert(e.secondSon.typ != nil) assert(son(e, 2).typ != nil) block: @@ -598,7 +593,7 @@ template binaryExpr(p: BProc, e: AnyNode, d: var TLoc, frmt: untyped) = let rb {.inject.} = rdLoc(b) putIntoDest(p, d, e, frmt) -template binaryExprChar(p: BProc, e: AnyNode, d: var TLoc, frmt: untyped) = +template binaryExprChar(p: BProc, e: PNode, d: var TLoc, frmt: untyped) = assert(e.secondSon.typ != nil) assert(son(e, 2).typ != nil) block: @@ -608,13 +603,13 @@ template binaryExprChar(p: BProc, e: AnyNode, d: var TLoc, frmt: untyped) = let rb {.inject.} = rdCharLoc(b) putIntoDest(p, d, e, frmt) -template unaryExpr(p: BProc, e: AnyNode, d: var TLoc, frmt: untyped) = +template unaryExpr(p: BProc, e: PNode, d: var TLoc, frmt: untyped) = block: var a: TLoc = initLocExpr(p, e.secondSon) let ra {.inject.} = rdLoc(a) putIntoDest(p, d, e, frmt) -template unaryExprChar(p: BProc, e: AnyNode, d: var TLoc, frmt: untyped) = +template unaryExprChar(p: BProc, e: PNode, d: var TLoc, frmt: untyped) = block: var a: TLoc = initLocExpr(p, e.secondSon) let ra {.inject.} = rdCharLoc(a) @@ -651,7 +646,7 @@ template binaryArithOverflowRaw(p: BProc, t: PType, a, b: TLoc; result -proc binaryArithOverflow(p: BProc, e: AnyNode, d: var TLoc, m: TMagic) = +proc binaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) = const prc: array[mAddI..mPred, string] = [ "nimAddInt", "nimSubInt", @@ -699,7 +694,7 @@ proc binaryArithOverflow(p: BProc, e: AnyNode, d: var TLoc, m: TMagic) = let res = cCast(typ, cOp(opr[m], typ, wrapPar(rdLoc(a)), wrapPar(rdLoc(b)))) putIntoDest(p, d, e, res) -proc unaryArithOverflow(p: BProc, e: AnyNode, d: var TLoc, m: TMagic) = +proc unaryArithOverflow(p: BProc, e: PNode, d: var TLoc, m: TMagic) = var t: PType assert(e.secondSon.typ != nil) var a: TLoc = initLocExpr(p, e.secondSon) @@ -726,7 +721,7 @@ proc unaryArithOverflow(p: BProc, e: AnyNode, d: var TLoc, m: TMagic) = else: assert(false, $m) -proc binaryArith(p: BProc, e: AnyNode, d: var TLoc, op: TMagic) = +proc binaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) = var s, k: BiggestInt = 0 assert(e.secondSon.typ != nil) @@ -851,7 +846,7 @@ proc binaryArith(p: BProc, e: AnyNode, d: var TLoc, op: TMagic) = assert(false, $op) putIntoDest(p, d, e, res) -proc genEqProc(p: BProc, e: AnyNode, d: var TLoc) = +proc genEqProc(p: BProc, e: PNode, d: var TLoc) = assert(e.secondSon.typ != nil) assert(son(e, 2).typ != nil) var a = initLocExpr(p, e.secondSon) @@ -865,7 +860,7 @@ proc genEqProc(p: BProc, e: AnyNode, d: var TLoc) = else: putIntoDest(p, d, e, cOp(Equal, ra, rb)) -proc genIsNil(p: BProc, e: AnyNode, d: var TLoc) = +proc genIsNil(p: BProc, e: PNode, d: var TLoc) = let t = skipTypes(e.secondSon.typ, abstractRange) var a: TLoc = initLocExpr(p, e.secondSon) let ra = rdLoc(a) @@ -876,7 +871,7 @@ proc genIsNil(p: BProc, e: AnyNode, d: var TLoc) = res = cOp(Equal, ra, cIntValue(0)) putIntoDest(p, d, e, res) -proc unaryArith(p: BProc, e: AnyNode, d: var TLoc, op: TMagic) = +proc unaryArith(p: BProc, e: PNode, d: var TLoc, op: TMagic) = var t: PType assert(e.secondSon.typ != nil) @@ -908,7 +903,7 @@ proc isCppRef(p: BProc; typ: PType): bool {.inline.} = skipTypes(typ, abstractInstOwned).kind in {tyVar} and tfVarIsPtr notin skipTypes(typ, abstractInstOwned).flags -proc genDeref(p: BProc, e: AnyNode, d: var TLoc) = +proc genDeref(p: BProc, e: PNode, d: var TLoc) = let mt = mapType(p.config, e.firstSon.typ, mapTypeChooser(e.firstSon) == skParam) if mt in {ctArray, ctPtrToArray} and lfEnforceDeref notin d.flags: # XXX the amount of hacks for C's arrays is incredible, maybe we should @@ -966,7 +961,7 @@ proc genDeref(p: BProc, e: AnyNode, d: var TLoc) = else: putIntoDest(p, d, e, cDeref(rdLoc(a)), a.storage) -proc cowBracket(p: BProc; n: AnyNode) = +proc cowBracket(p: BProc; n: PNode) = if n.kind == nkBracketExpr and optSeqDestructors in p.config.globalOptions and not p.config.usesSso(): let strCandidate = n.firstSon @@ -975,15 +970,15 @@ proc cowBracket(p: BProc; n: AnyNode) = let raa = byRefLoc(p, a) p.s(cpsStmts).addCallStmt(cgsymValue(p.module, "nimPrepareStrMutationV2"), raa) -proc cow(p: BProc; n: AnyNode) {.inline.} = +proc cow(p: BProc; n: PNode) {.inline.} = if n.kind == nkHiddenAddr: cowBracket(p, n.firstSon) -template ignoreConv(e: AnyNode): bool = +template ignoreConv(e: PNode): bool = let destType = e.typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink}) let srcType = e.secondSon.typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink}) sameBackendTypePickyAliases(destType, srcType) -proc genAddr(p: BProc, e: AnyNode, d: var TLoc) = +proc genAddr(p: BProc, e: PNode, d: var TLoc) = # careful 'addr(myptrToArray)' needs to get the ampersand: if e.firstSon.typ.skipTypes(abstractInstOwned).kind in {tyRef, tyPtr}: var a: TLoc = initLocExpr(p, e.firstSon) @@ -992,7 +987,7 @@ proc genAddr(p: BProc, e: AnyNode, d: var TLoc) = elif mapType(p.config, e.firstSon.typ, mapTypeChooser(e.firstSon) == skParam) == ctArray or isCppRef(p, e.typ): expr(p, e.firstSon, d) # bug #19497 - d.lode = origin(e) + d.lode = e else: let ssoStrSub = p.config.usesSso() and e.firstSon.kind == nkBracketExpr and e.firstSon.firstSon.typ.skipTypes(abstractVar).kind == tyString @@ -1010,13 +1005,13 @@ proc genAddr(p: BProc, e: AnyNode, d: var TLoc) = template inheritLocation(d: var TLoc, a: TLoc) = if d.k == locNone: d.storage = a.storage -proc genRecordFieldAux(p: BProc, e: AnyNode, d: var TLoc, a: var TLoc) = +proc genRecordFieldAux(p: BProc, e: PNode, d: var TLoc, a: var TLoc) = a = initLocExpr(p, e.firstSon) if e.secondSon.kind != nkSym: internalError(p.config, e.info, "genRecordFieldAux") d.inheritLocation(a) discard getTypeDesc(p.module, a.t) # fill the record's fields.loc -proc genTupleElem(p: BProc, e: AnyNode, d: var TLoc) = +proc genTupleElem(p: BProc, e: PNode, d: var TLoc) = var i: int = 0 var a: TLoc = initLocExpr(p, e.firstSon) @@ -1048,7 +1043,7 @@ proc lookupFieldAgain(p: BProc, ty: PType; field: PSym; r: var Rope; ty = ty.baseClass if result == nil: internalError(p.config, field.info, "genCheckedRecordField") -proc genRecordField(p: BProc, e: AnyNode, d: var TLoc) = +proc genRecordField(p: BProc, e: PNode, d: var TLoc) = var a: TLoc = default(TLoc) if p.module.compileToCpp and e.kind == nkDotExpr and e.secondSon.kind == nkSym and e.secondSon.typ.kind == tyPtr: # special case for C++: we need to pull the type of the field as member and friends require the complete type. @@ -1074,9 +1069,9 @@ proc genRecordField(p: BProc, e: AnyNode, d: var TLoc) = putIntoDest(p, d, e, r, a.storage) r.freeze -proc genInExprAux(p: BProc, e: AnyNode, a, b, d: var TLoc) +proc genInExprAux(p: BProc, e: PNode, a, b, d: var TLoc) -proc genFieldCheck(p: BProc, e: AnyNode, obj: Rope, field: PSym, ty: PType) = +proc genFieldCheck(p: BProc, e: PNode, obj: Rope, field: PSym, ty: PType) = var test, u, v: TLoc for child in sonsFrom(e, 1): var it = child @@ -1158,7 +1153,7 @@ proc genFieldCheck(p: BProc, e: AnyNode, obj: Rope, field: PSym, ty: PType) = raiseInstr(p, p.s(cpsStmts)) -proc genCheckedRecordField(p: BProc, e: AnyNode, d: var TLoc) = +proc genCheckedRecordField(p: BProc, e: PNode, d: var TLoc) = assert e.firstSon.kind == nkDotExpr if optFieldCheck in p.options: var a: TLoc = default(TLoc) @@ -1177,14 +1172,14 @@ proc genCheckedRecordField(p: BProc, e: AnyNode, d: var TLoc) = else: genRecordField(p, e.firstSon, d) -proc genUncheckedArrayElem(p: BProc, n, x, y: AnyNode, d: var TLoc) = +proc genUncheckedArrayElem(p: BProc, n, x, y: PNode, d: var TLoc) = var a = initLocExpr(p, x) var b = initLocExpr(p, y) d.inheritLocation(a) putIntoDest(p, d, n, subscript(rdLoc(a), rdCharLoc(b)), a.storage) -proc genArrayElem(p: BProc, n, x, y: AnyNode, d: var TLoc) = +proc genArrayElem(p: BProc, n, x, y: PNode, d: var TLoc) = var a = initLocExpr(p, x) var b = initLocExpr(p, y) var ty = skipTypes(a.t, abstractVarRange + abstractPtrs + tyUserTypeClasses) @@ -1223,7 +1218,7 @@ proc genArrayElem(p: BProc, n, x, y: AnyNode, d: var TLoc) = let rcb = rdCharLoc(b) putIntoDest(p, d, n, subscript(ra, cOp(Sub, NimInt, rcb, first)), a.storage) -proc genCStringElem(p: BProc, n, x, y: AnyNode, d: var TLoc) = +proc genCStringElem(p: BProc, n, x, y: PNode, d: var TLoc) = var a = initLocExpr(p, x) var b = initLocExpr(p, y) inheritLocation(d, a) @@ -1282,7 +1277,7 @@ proc genBoundsCheck(p: BProc; arr, a, b: TLoc; arrTyp: PType) = else: discard -proc genOpenArrayElem(p: BProc, n, x, y: AnyNode, d: var TLoc) = +proc genOpenArrayElem(p: BProc, n, x, y: PNode, d: var TLoc) = var a = initLocExpr(p, x) var b = initLocExpr(p, y) let ra = rdLoc(a) @@ -1308,7 +1303,7 @@ proc genOpenArrayElem(p: BProc, n, x, y: AnyNode, d: var TLoc) = inheritLocation(d, a) putIntoDest(p, d, n, subscript(arrData, rcb), a.storage) -proc genSeqElem(p: BProc, n, x, y: AnyNode, d: var TLoc) = +proc genSeqElem(p: BProc, n, x, y: PNode, d: var TLoc) = var a = initLocExpr(p, x) var b = initLocExpr(p, y) var ty = skipTypes(a.t, abstractVarRange) @@ -1349,7 +1344,7 @@ proc genSeqElem(p: BProc, n, x, y: AnyNode, d: var TLoc) = let ra = rdLoc(a) putIntoDest(p, d, n, subscript(dataField(p, ra), rcb), a.storage) -proc genBracketExpr(p: BProc; n: AnyNode; d: var TLoc) = +proc genBracketExpr(p: BProc; n: PNode; d: var TLoc) = var ty = skipTypes(n.firstSon.typ, abstractVarRange + tyUserTypeClasses) if ty.kind in {tyRef, tyPtr}: ty = skipTypes(ty.elementType, abstractVarRange) case ty.kind @@ -1362,7 +1357,7 @@ proc genBracketExpr(p: BProc; n: AnyNode; d: var TLoc) = else: internalError(p.config, n.info, "expr(nkBracketExpr, " & $ty.kind & ')') discard getTypeDesc(p.module, n.typ) -proc isSimpleExpr(n: AnyNode): bool = +proc isSimpleExpr(n: PNode): bool = # calls all the way down --> can stay expression based case n.kind of nkCallKinds, nkDotExpr, nkPar, nkTupleConstr, @@ -1378,7 +1373,7 @@ proc isSimpleExpr(n: AnyNode): bool = else: result = n.isAtom -proc genAndOr(p: BProc, e: AnyNode, d: var TLoc, m: TMagic) = +proc genAndOr(p: BProc, e: PNode, d: var TLoc, m: TMagic) = # how to generate code? # 'expr1 and expr2' becomes: # result = expr1 @@ -1433,7 +1428,7 @@ proc genAndOr(p: BProc, e: AnyNode, d: var TLoc, m: TMagic) = genAssignment(p, d, tmp, {}) # no need for deep copying dec p.splitDecls -proc genEcho(p: BProc, n: AnyNode) = +proc genEcho(p: BProc, n: PNode) = # this unusual way of implementing it ensures that e.g. ``echo("hallo", 45)`` # is threadsafe. internalAssert p.config, n.kind == nkBracket @@ -1481,8 +1476,8 @@ proc genEcho(p: BProc, n: AnyNode) = makeCString(repeat("%s", n.len) & "\L"), [args]) linefmt(p, cpsStmts, "fflush(stdout);$n", []) -proc gcUsage(conf: ConfigRef; n: AnyNode) = - if conf.selectedGC == gcNone: message(conf, n.info, warnGcMem, origin(n).renderTree) +proc gcUsage(conf: ConfigRef; n: PNode) = + if conf.selectedGC == gcNone: message(conf, n.info, warnGcMem, n.renderTree) proc strLoc(p: BProc; d: TLoc): Rope = if optSeqDestructors in p.config.globalOptions: @@ -1490,7 +1485,7 @@ proc strLoc(p: BProc; d: TLoc): Rope = else: result = rdLoc(d) -proc genStrConcat(p: BProc, e: AnyNode, d: var TLoc) = +proc genStrConcat(p: BProc, e: PNode, d: var TLoc) = # # s = "Hello " & name & ", how do you feel?" & 'z' # @@ -1540,7 +1535,7 @@ proc genStrConcat(p: BProc, e: AnyNode, d: var TLoc) = genAssignment(p, d, tmp, {}) # no need for deep copying gcUsage(p.config, e) -proc genStrAppend(p: BProc, e: AnyNode, d: var TLoc) = +proc genStrAppend(p: BProc, e: PNode, d: var TLoc) = # # s &= "Hello " & name & ", how do you feel?" & 'z' # // BUG: what if s is on the left side too? @@ -1593,7 +1588,7 @@ proc genStrAppend(p: BProc, e: AnyNode, d: var TLoc) = p.s(cpsStmts).addStmt(): p.s(cpsStmts).add(append) -proc genSeqElemAppend(p: BProc, e: AnyNode, d: var TLoc) = +proc genSeqElemAppend(p: BProc, e: PNode, d: var TLoc) = # seq &= x --> # seq = (typeof seq) incrSeq(&seq->Sup, sizeof(x)); # seq->data[seq->len-1] = x; @@ -1619,7 +1614,7 @@ proc genSeqElemAppend(p: BProc, e: AnyNode, d: var TLoc) = genAssignment(p, dest, b, {needToCopy}) gcUsage(p.config, e) -proc genSeqElemAppendV2(p: BProc, e: AnyNode, d: var TLoc) = +proc genSeqElemAppendV2(p: BProc, e: PNode, d: var TLoc) = # s.add(x) with optSeqDestructors (arc/orc), inlined for direct slot construction: # NI oldLen = s.len; # if (s.p == NIM_NIL || (s.p->cap & ~NIM_STRLIT_FLAG) < oldLen + 1) @@ -1664,7 +1659,7 @@ proc genSeqElemAppendV2(p: BProc, e: AnyNode, d: var TLoc) = dest.snippet = subscript(dataField(p, ra), tmpL.snippet) genAssignment(p, dest, b, {}) -proc genDefault(p: BProc; n: AnyNode; d: var TLoc) = +proc genDefault(p: BProc; n: PNode; d: var TLoc) = if d.k == locNone: d = getTemp(p, n.typ, needsInit=true) else: resetLoc(p, d) @@ -1741,7 +1736,7 @@ proc rawGenNew(p: BProc, a: var TLoc, sizeExpr: Rope; needsInit: bool) = # set the object type: genObjectInit(p, cpsStmts, bt, a, constructRefObj) -proc genNew(p: BProc, e: AnyNode) = +proc genNew(p: BProc, e: PNode) = var a: TLoc = initLocExpr(p, e.secondSon) # 'genNew' also handles 'unsafeNew': if e.len == 3: @@ -1792,7 +1787,7 @@ proc genNewSeqAux(p: BProc, dest: TLoc, length: Rope; lenIsZero: bool) = cgCall(p, "newSeq", typinfo, length)) genAssignment(p, dest, call, {}) -proc genNewSeq(p: BProc, e: AnyNode) = +proc genNewSeq(p: BProc, e: PNode) = var a = initLocExpr(p, e.secondSon) var b = initLocExpr(p, son(e, 2)) if optSeqDestructors in p.config.globalOptions: @@ -1813,7 +1808,7 @@ proc genNewSeq(p: BProc, e: AnyNode) = genNewSeqAux(p, a, b.rdLoc, lenIsZero) gcUsage(p.config, e) -proc genNewSeqOfCap(p: BProc; e: AnyNode; d: var TLoc) = +proc genNewSeqOfCap(p: BProc; e: PNode; d: var TLoc) = let seqtype = skipTypes(e.typ, abstractVarRange) var a: TLoc = initLocExpr(p, e.secondSon) if optSeqDestructors in p.config.globalOptions: @@ -1839,10 +1834,10 @@ proc genNewSeqOfCap(p: BProc; e: AnyNode; d: var TLoc) = putIntoDest(p, d, e, dres) gcUsage(p.config, e) -proc rawConstExpr(p: BProc, n: AnyNode; d: var TLoc) = +proc rawConstExpr(p: BProc, n: PNode; d: var TLoc) = let t = n.typ discard getTypeDesc(p.module, t) # so that any fields are initialized - let id = nodeTableTestOrSet(p.module.dataCache, origin(n), p.module.labels) + let id = nodeTableTestOrSet(p.module.dataCache, n, p.module.labels) fillLoc(d, locData, n, p.module.tmpBase & rope(id), OnStatic) if id == p.module.labels: # expression not found in the cache: @@ -1856,7 +1851,7 @@ proc rawConstExpr(p: BProc, n: AnyNode; d: var TLoc) = genBracedInit(p, n, isConst = true, t, data) p.module.s[cfsData].add(extract(data)) -proc handleConstExpr(p: BProc, n: AnyNode, d: var TLoc): bool = +proc handleConstExpr(p: BProc, n: PNode, d: var TLoc): bool = if d.k == locNone and n.len > ord(n.kind == nkObjConstr) and n.isDeepConstExpr: rawConstExpr(p, n, d) result = true @@ -1864,7 +1859,7 @@ proc handleConstExpr(p: BProc, n: AnyNode, d: var TLoc): bool = result = false -proc genFieldObjConstr[F: AnyNode; V: AnyNode](p: BProc; ty: PType; useTemp, isRef: bool; nField: F; val: V; check: PNode; d: var TLoc; r: Rope; info: TLineInfo) = +proc genFieldObjConstr(p: BProc; ty: PType; useTemp, isRef: bool; nField: PNode; val: PNode; check: PNode; d: var TLoc; r: Rope; info: TLineInfo) = var tmp2 = TLoc(snippet: r) let field = lookupFieldAgain(p, ty, nField.sym, tmp2.snippet) if field.loc.snippet == "": fillObjectFields(p.module, ty) @@ -1878,7 +1873,7 @@ proc genFieldObjConstr[F: AnyNode; V: AnyNode](p: BProc; ty: PType; useTemp, isR else: tmp2.k = d.k tmp2.storage = if isRef: OnHeap else: d.storage - tmp2.lode = origin(val) + tmp2.lode = val if nField.typ.skipTypes(abstractVar).kind in {tyOpenArray, tyVarargs}: var tmp3 = getTemp(p, val.typ) expr(p, val, tmp3) @@ -1886,7 +1881,7 @@ proc genFieldObjConstr[F: AnyNode; V: AnyNode](p: BProc; ty: PType; useTemp, isR else: expr(p, val, tmp2) -proc genObjConstr(p: BProc, e: AnyNode, d: var TLoc) = +proc genObjConstr(p: BProc, e: PNode, d: var TLoc) = # inheritance in C++ does not allow struct initialization so # we skip this step here: if not p.module.compileToCpp and optSeqDestructors notin p.config.globalOptions: @@ -1912,7 +1907,7 @@ proc genObjConstr(p: BProc, e: AnyNode, d: var TLoc) = isRef or d.k == locNone or (d.t != nil and not sameBackendType(t, d.t.skipTypes(abstractInstOwned))) or - (isPartOf(d.lode, origin(e), {pfStructural, pfBidirectional}) != arNo) + (isPartOf(d.lode, e, {pfStructural, pfBidirectional}) != arNo) var tmp: TLoc = default(TLoc) var r: Rope @@ -1942,10 +1937,9 @@ proc genObjConstr(p: BProc, e: AnyNode, d: var TLoc) = # this is an object constructor node generated by the VM and # this field is in an inactive case branch, don't generate assignment continue - # Nilable, so a `PNode` — a cursor has no standalone nil. var check: PNode = nil if it.safeLen == 3 and optFieldCheck in p.options: - check = origin(son(it, 2)) + check = son(it, 2) genFieldObjConstr(p, ty, useTemp, isRef, it.firstSon, it.secondSon, check, d, r, e.info) if useTemp: @@ -1955,17 +1949,15 @@ proc genObjConstr(p: BProc, e: AnyNode, d: var TLoc) = genAssignment(p, d, tmp, {}) proc lhsDoesAlias(a, b: PNode): bool = - ## Stays `PNode`: it is `isPartOf` underneath, which compares field symbols by - ## identity and so has not moved to the seam (see `bnode.sym`). result = false for y in sons(b): if isPartOf(a, y) != arNo: return true -proc genSeqConstr(p: BProc, n: AnyNode, d: var TLoc) = +proc genSeqConstr(p: BProc, n: PNode, d: var TLoc) = var arr: TLoc var tmp: TLoc = default(TLoc) # bug #668 - let doesAlias = lhsDoesAlias(d.lode, origin(n)) + let doesAlias = lhsDoesAlias(d.lode, n) let dest = if doesAlias: addr(tmp) else: addr(d) if doesAlias: tmp = getTemp(p, n.typ) @@ -2002,13 +1994,10 @@ proc genSeqConstr(p: BProc, n: AnyNode, d: var TLoc) = else: genAssignment(p, d, tmp, {}) -proc genArrToSeq(p: BProc, n: AnyNode, d: var TLoc) = +proc genArrToSeq(p: BProc, n: PNode, d: var TLoc) = var elem, arr: TLoc if n.secondSon.kind == nkBracket: - # Retypes the bracket in place and then generates it, so both the mutation - # and the generation run on the origin — a cursor would read the type slot - # as encoded and miss the assignment. - let bracket = origin(n.secondSon) + let bracket = n.secondSon bracket.typ = n.typ genSeqConstr(p, bracket, d) return @@ -2054,7 +2043,7 @@ proc genArrToSeq(p: BProc, n: AnyNode, d: var TLoc) = genAssignment(p, elem, arr, {needToCopy}) -proc genNewFinalize(p: BProc, e: AnyNode) = +proc genNewFinalize(p: BProc, e: PNode) = var b: TLoc refType, bt: PType @@ -2101,7 +2090,7 @@ proc genOfHelper(p: BProc; dest: PType; a: Rope; info: TLineInfo; result: var Bu ti, cache) -proc genOf(p: BProc, x: AnyNode, typ: PType, d: var TLoc) = +proc genOf(p: BProc, x: PNode, typ: PType, d: var TLoc) = var a: TLoc = initLocExpr(p, x) var dest = skipTypes(typ, typedescPtrs) var r = rdLoc(a) @@ -2129,10 +2118,10 @@ proc genOf(p: BProc, x: AnyNode, typ: PType, d: var TLoc) = putIntoDest(p, d, x, ofExpr, a.storage) -proc genOf(p: BProc, n: AnyNode, d: var TLoc) = +proc genOf(p: BProc, n: PNode, d: var TLoc) = genOf(p, n.secondSon, son(n, 2).typ, d) -proc genRepr(p: BProc, e: AnyNode, d: var TLoc) = +proc genRepr(p: BProc, e: PNode, d: var TLoc) = if optTinyRtti in p.config.globalOptions: localError(p.config, e.info, "'repr' is not available for --newruntime") var a: TLoc = initLocExpr(p, e.secondSon) @@ -2222,13 +2211,13 @@ proc rdMType(p: BProc; a: TLoc; nilCheck: var Rope; result: var Snippet; enforce if optTinyRtti in p.config.globalOptions and enforceV1: result = derefField(result, "typeInfoV1") -proc genGetTypeInfo(p: BProc, e: AnyNode, d: var TLoc) = +proc genGetTypeInfo(p: BProc, e: PNode, d: var TLoc) = cgsym(p.module, "TNimType") let t = e.secondSon.typ # ordinary static type information putIntoDest(p, d, e, genTypeInfoV1(p.module, t, e.info)) -proc genGetTypeInfoV2(p: BProc, e: AnyNode, d: var TLoc) = +proc genGetTypeInfoV2(p: BProc, e: PNode, d: var TLoc) = let t = e.secondSon.typ if isFinal(t) or e.firstSon.sym.name.s != "getDynamicTypeInfo": # ordinary static type information @@ -2241,7 +2230,7 @@ proc genGetTypeInfoV2(p: BProc, e: AnyNode, d: var TLoc) = rdMType(p, a, nilCheck, rt) putIntoDest(p, d, e, rt) -proc genAccessTypeField(p: BProc; e: AnyNode; d: var TLoc) = +proc genAccessTypeField(p: BProc; e: PNode; d: var TLoc) = var a: TLoc = initLocExpr(p, e.secondSon) var nilCheck = "" # use the dynamic type stored at offset 0: @@ -2249,7 +2238,7 @@ proc genAccessTypeField(p: BProc; e: AnyNode; d: var TLoc) = rdMType(p, a, nilCheck, rt) putIntoDest(p, d, e, rt) -template genDollarIt(p: BProc, n: AnyNode, d: var TLoc, frmt: untyped) = +template genDollarIt(p: BProc, n: PNode, d: var TLoc, frmt: untyped) = block: var a: TLoc = initLocExpr(p, n.secondSon) let it {.inject.} = rdLoc(a) @@ -2259,7 +2248,7 @@ template genDollarIt(p: BProc, n: AnyNode, d: var TLoc, frmt: untyped) = genAssignment(p, d, a, {}) gcUsage(p.config, n) -proc genArrayLen(p: BProc, e: AnyNode, d: var TLoc, op: TMagic) = +proc genArrayLen(p: BProc, e: PNode, d: var TLoc, op: TMagic) = var a = e.secondSon if a.kind == nkHiddenAddr: a = a.firstSon var typ = skipTypes(a.typ, abstractVar + tyUserTypeClasses) @@ -2323,12 +2312,10 @@ proc isTrivialTypesToSnippet(t: PType): Snippet = else: result = NimTrue -proc genSetLengthSeq(p: BProc, e: AnyNode, d: var TLoc, noinit = false) = +proc genSetLengthSeq(p: BProc, e: PNode, d: var TLoc, noinit = false) = if optSeqDestructors in p.config.globalOptions: - # In-place rewrite; continue on the origin (see `mAppendSeqElem`). - let en = origin(e) - en.secondSon = makeAddr(en.secondSon, p.module.idgen) - genCall(p, en, d) + e.secondSon = makeAddr(e.secondSon, p.module.idgen) + genCall(p, e, d) return assert(d.k == locNone) var x = e.secondSon @@ -2355,7 +2342,7 @@ proc genSetLengthSeq(p: BProc, e: AnyNode, d: var TLoc, noinit = false) = genAssignment(p, a, call, {}) gcUsage(p.config, e) -proc genSetLengthStr(p: BProc, e: AnyNode, d: var TLoc) = +proc genSetLengthStr(p: BProc, e: PNode, d: var TLoc) = if optSeqDestructors in p.config.globalOptions: binaryStmtAddr(p, e, d, "setLengthStrV2") else: @@ -2368,7 +2355,7 @@ proc genSetLengthStr(p: BProc, e: AnyNode, d: var TLoc) = genAssignment(p, a, call, {}) gcUsage(p.config, e) -proc genSwap(p: BProc, e: AnyNode, d: var TLoc) = +proc genSwap(p: BProc, e: PNode, d: var TLoc) = # swap(a, b) --> # temp = a # a = b @@ -2391,7 +2378,7 @@ proc rdSetElemLoc(conf: ConfigRef; a: TLoc, typ: PType; result: var Snippet) = if firstOrd(conf, setType) != 0: result = cOp(Sub, NimUint, result, cIntValue(firstOrd(conf, setType))) -proc fewCmps(conf: ConfigRef; s: AnyNode): bool = +proc fewCmps(conf: ConfigRef; s: PNode): bool = # this function estimates whether it is better to emit code # for constructing the set or generating a bunch of comparisons directly if s.kind != nkCurly: return false @@ -2402,13 +2389,13 @@ proc fewCmps(conf: ConfigRef; s: AnyNode): bool = else: result = s.len <= 8 # 8 seems to be a good value -template binaryExprIn(p: BProc, e: AnyNode, a, b, d: var TLoc, frmt: untyped) = +template binaryExprIn(p: BProc, e: PNode, a, b, d: var TLoc, frmt: untyped) = var elem {.inject.}: Snippet = "" rdSetElemLoc(p.config, b, a.t, elem) let ra {.inject.} = rdLoc(a) putIntoDest(p, d, e, frmt) -proc genInExprAux(p: BProc, e: AnyNode, a, b, d: var TLoc) = +proc genInExprAux(p: BProc, e: PNode, a, b, d: var TLoc) = let s = int(getSize(p.config, skipTypes(e.secondSon.typ, abstractVar))) case s of 1, 2, 4, 8: @@ -2437,7 +2424,7 @@ proc genInExprAux(p: BProc, e: AnyNode, a, b, d: var TLoc) = cUintValue(7)))), cIntValue(0))) -template binaryStmtInExcl(p: BProc, e: AnyNode, d: var TLoc, frmt: untyped) = +template binaryStmtInExcl(p: BProc, e: PNode, d: var TLoc, frmt: untyped) = assert(d.k == locNone) var a = initLocExpr(p, e.secondSon) var b = initLocExpr(p, son(e, 2)) @@ -2446,7 +2433,7 @@ template binaryStmtInExcl(p: BProc, e: AnyNode, d: var TLoc, frmt: untyped) = let ra {.inject.} = rdLoc(a) p.s(cpsStmts).add(frmt) -proc genInOp(p: BProc, e: AnyNode, d: var TLoc) = +proc genInOp(p: BProc, e: PNode, d: var TLoc) = var a, b, x, y: TLoc if (e.secondSon.kind == nkCurly) and fewCmps(p.config, e.secondSon): # a set constructor but not a constant set: @@ -2493,7 +2480,7 @@ proc genInOp(p: BProc, e: AnyNode, d: var TLoc) = b = initLocExpr(p, son(e, 2)) genInExprAux(p, e, a, b, d) -proc genSetOp(p: BProc, e: AnyNode, d: var TLoc, op: TMagic) = +proc genSetOp(p: BProc, e: PNode, d: var TLoc, op: TMagic) = var a, b: TLoc var i: TLoc var setType = skipTypes(e.secondSon.typ, abstractVar) @@ -2609,10 +2596,10 @@ proc genSetOp(p: BProc, e: AnyNode, d: var TLoc, op: TMagic) = of mInSet: genInOp(p, e, d) else: internalError(p.config, e.info, "genSetOp") -proc genOrd(p: BProc, e: AnyNode, d: var TLoc) = +proc genOrd(p: BProc, e: PNode, d: var TLoc) = unaryExprChar(p, e, d, ra) -proc genSomeCast(p: BProc, e: AnyNode, d: var TLoc) = +proc genSomeCast(p: BProc, e: PNode, d: var TLoc) = const ValueTypes = {tyTuple, tyObject, tyArray, tyOpenArray, tyVarargs, tyUncheckedArray} # we use whatever C gives us. Except if we have a value-type, we need to go @@ -2661,7 +2648,7 @@ proc genSomeCast(p: BProc, e: AnyNode, d: var TLoc) = let val = rdCharLoc(a) putIntoDest(p, d, e, cCast(destTyp, wrapPar(val)), a.storage) -proc genCast(p: BProc, e: AnyNode, d: var TLoc) = +proc genCast(p: BProc, e: PNode, d: var TLoc) = const ValueTypes = {tyFloat..tyFloat128, tyTuple, tyObject, tyArray} let destt = skipTypes(e.typ, abstractRange) @@ -2701,7 +2688,7 @@ proc genCast(p: BProc, e: AnyNode, d: var TLoc) = # C code; plus it's the right thing to do for closures: genSomeCast(p, e, d) -proc genRangeChck(p: BProc, n: AnyNode, d: var TLoc) = +proc genRangeChck(p: BProc, n: PNode, d: var TLoc) = var a: TLoc = initLocExpr(p, n.firstSon) var dest = skipTypes(n.typ, abstractVar) if optRangeCheck notin p.options or (dest.kind in {tyUInt..tyUInt64} and @@ -2757,20 +2744,20 @@ proc genRangeChck(p: BProc, n: AnyNode, d: var TLoc) = let val = rdCharLoc(a) putIntoDest(p, d, n, cCast(destType, wrapPar(val)), a.storage) -proc genConv(p: BProc, e: AnyNode, d: var TLoc) = +proc genConv(p: BProc, e: PNode, d: var TLoc) = if ignoreConv(e): expr(p, e.secondSon, d) else: genSomeCast(p, e, d) -proc convStrToCStr(p: BProc, n: AnyNode, d: var TLoc) = +proc convStrToCStr(p: BProc, n: PNode, d: var TLoc) = var a: TLoc = initLocExpr(p, n.firstSon) let arg = if p.config.usesSso(): byRefLoc(p, a) else: rdLoc(a) putIntoDest(p, d, n, cgCall(p, "nimToCStringConv", arg), a.storage) -proc convCStrToStr(p: BProc, n: AnyNode, d: var TLoc) = +proc convCStrToStr(p: BProc, n: PNode, d: var TLoc) = var a: TLoc = initLocExpr(p, n.firstSon) if p.module.compileToCpp: # fixes for const qualifier; bug #12703; bug #19588 @@ -2783,7 +2770,7 @@ proc convCStrToStr(p: BProc, n: AnyNode, d: var TLoc) = a.storage) gcUsage(p.config, n) -proc genStrEquals(p: BProc, e: AnyNode, d: var TLoc) = +proc genStrEquals(p: BProc, e: PNode, d: var TLoc) = var x: TLoc var a = e.secondSon var b = son(e, 2) @@ -2798,7 +2785,7 @@ proc genStrEquals(p: BProc, e: AnyNode, d: var TLoc) = else: binaryExpr(p, e, d, cgCall(p, "eqStrings", ra, rb)) -proc binaryFloatArith(p: BProc, e: AnyNode, d: var TLoc, m: TMagic) = +proc binaryFloatArith(p: BProc, e: PNode, d: var TLoc, m: TMagic) = if {optNaNCheck, optInfCheck} * p.options != {}: const opr: array[mAddF64..mDivF64, TypedBinaryOp] = [Add, Sub, Mul, Div] assert(e.secondSon.typ != nil) @@ -2826,7 +2813,7 @@ proc binaryFloatArith(p: BProc, e: AnyNode, d: var TLoc, m: TMagic) = else: binaryArith(p, e, d, m) -proc genWasMoved(p: BProc; n: AnyNode) = +proc genWasMoved(p: BProc; n: PNode) = var a: TLoc let n1 = n.secondSon.skipAddr if p.withinBlockLeaveActions > 0 and notYetAlive(n1): @@ -2837,7 +2824,7 @@ proc genWasMoved(p: BProc; n: AnyNode) = #linefmt(p, cpsStmts, "#nimZeroMem((void*)$1, sizeof($2));$n", # [addrLoc(p.config, a), getTypeDesc(p.module, a.t)]) -proc genMove(p: BProc; n: AnyNode; d: var TLoc) = +proc genMove(p: BProc; n: PNode; d: var TLoc) = if n.len == 4: # generated by liftdestructors: var a: TLoc = initLocExpr(p, n.secondSon.skipAddr, {lfEnforceDeref, lfPrepareForMutation}) @@ -2866,16 +2853,14 @@ proc genMove(p: BProc; n: AnyNode; d: var TLoc) = genAssignment(p, d, a, {}) resetLoc(p, a) else: - # In-place rewrite; continue on the origin (see `mAppendSeqElem`). - let nn = origin(n) - nn.secondSon = makeAddr(nn.secondSon, p.module.idgen) - genCall(p, nn, d) + n.secondSon = makeAddr(n.secondSon, p.module.idgen) + genCall(p, n, d) else: var a: TLoc = initLocExpr(p, n.secondSon.skipAddr, {lfEnforceDeref, lfPrepareForMutation}) genAssignment(p, d, a, {}) resetLoc(p, a) -proc genDestroy(p: BProc; n: AnyNode) = +proc genDestroy(p: BProc; n: PNode) = if optSeqDestructors in p.config.globalOptions: let arg = n.secondSon.skipAddr let t = arg.typ.skipTypes(abstractInst) @@ -2916,7 +2901,7 @@ proc genDestroy(p: BProc; n: AnyNode) = internalError(p.config, n.info, "destructor turned out to be not trivial") discard "ignore calls to the default destructor" -proc genSlice(p: BProc; e: AnyNode; d: var TLoc) = +proc genSlice(p: BProc; e: PNode; d: var TLoc) = let (x, y) = genOpenArraySlice(p, e, e.typ, e.typ.elementType, prepareForMutation = e.secondSon.kind == nkHiddenDeref and e.secondSon.typ.skipTypes(abstractInst).kind == tyString and @@ -2929,23 +2914,20 @@ proc genSlice(p: BProc; e: AnyNode; d: var TLoc) = localError(p.config, e.info, "invalid context for 'toOpenArray'; " & "'toOpenArray' is only valid within a call expression") -proc genEnumToStr(p: BProc, e: AnyNode, d: var TLoc) = +proc genEnumToStr(p: BProc, e: PNode, d: var TLoc) = let t = e.secondSon.typ.skipTypes(abstractInst+{tyRange}) let toStrProc = getToStringProc(p.module.g.graph, t) # XXX need to modify this logic for IC. - # The generator REWRITES here: it builds a fresh call and generates that. - # `origin` gives the `PNode` to copy, so a construction site is unaffected by - # the reader side having moved to a cursor. - var n = copyTree(origin(e)) + var n = copyTree(e) n[0] = newSymNode(toStrProc) expr(p, n, d) -proc genMagicExpr(p: BProc, e: AnyNode, d: var TLoc, op: TMagic) = +proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) = case op of mAsgn: let kind = if e.firstSon.sym.name.s == "=sink": nkSinkAsgn else: nkAsgn - let lhs = origin(e.secondSon.skipHiddenAddr) - let n = newTreeI(kind, e.info, lhs, origin(son(e, 2))) + let lhs = e.secondSon.skipHiddenAddr + let n = newTreeI(kind, e.info, lhs, son(e, 2)) n.typ = e.typ cow(p, son(e, 2)) genAsgn(p, n, fastAsgn = kind != nkAsgn) @@ -3000,15 +2982,8 @@ proc genMagicExpr(p: BProc, e: AnyNode, d: var TLoc, op: TMagic) = # gcYrc is excluded because its add() acquires a striped reader lock. genSeqElemAppendV2(p, e, d) else: - # A REWRITE inside the generator: it replaces a child in place and - # then generates the mutated call. A cursor cannot be written to, so - # this works on the origin — which is the very node the buffer was - # encoded from, so the mutation lands exactly where it always did. - # The buffer is stale for this subtree afterwards; nothing reads it - # again, because generation for this expression continues on the tree. - let en = origin(e) - en.secondSon = makeAddr(en.secondSon, p.module.idgen) - genCall(p, en, d) + e.secondSon = makeAddr(e.secondSon, p.module.idgen) + genCall(p, e, d) else: genSeqElemAppend(p, e, d) of mEqStr: genStrEquals(p, e, d) @@ -3049,11 +3024,8 @@ proc genMagicExpr(p: BProc, e: AnyNode, d: var TLoc, op: TMagic) = genNewFinalize(p, e) of mNewSeq: if optSeqDestructors in p.config.globalOptions: - # See `mAppendSeqElem` above: an in-place rewrite, so it runs on the - # origin and generation continues from the tree. - let en = origin(e) - en.secondSon = makeAddr(en.secondSon, p.module.idgen) - genCall(p, en, d) + e.secondSon = makeAddr(e.secondSon, p.module.idgen) + genCall(p, e, d) else: genNewSeq(p, e) of mNewSeqOfCap: genNewSeqOfCap(p, e, d) @@ -3064,13 +3036,11 @@ proc genMagicExpr(p: BProc, e: AnyNode, d: var TLoc, op: TMagic) = let t = e.secondSon.typ.skipTypes({tyTypeDesc}) putIntoDest(p, d, e, cCast(NimInt, cAlignof(getTypeDesc(p.module, t, dkVar)))) of mOffsetOf: - # `nil` is a possible value here, so this is a `PNode` — a cursor has no - # standalone nil — and `origin` supplies it. var dotExpr: PNode if e.secondSon.kind == nkDotExpr: - dotExpr = origin(e.secondSon) + dotExpr = e.secondSon elif e.secondSon.kind == nkCheckedFieldExpr: - dotExpr = origin(e.secondSon.firstSon) + dotExpr = e.secondSon.firstSon else: dotExpr = nil internalError(p.config, e.info, "unknown ast") @@ -3144,13 +3114,13 @@ proc genMagicExpr(p: BProc, e: AnyNode, d: var TLoc, op: TMagic) = when defined(leanCompiler): p.config.quitOrRaise "compiler built without support for the 'spawn' statement" else: - let n = spawn.wrapProcForSpawn(p.module.g.graph, p.module.idgen, p.module.module, origin(e), e.typ, nil, nil) + let n = spawn.wrapProcForSpawn(p.module.g.graph, p.module.idgen, p.module.module, e, e.typ, nil, nil) expr(p, n, d) of mParallel: when defined(leanCompiler): p.config.quitOrRaise "compiler built without support for the 'parallel' statement" else: - let n = semparallel.liftParallel(p.module.g.graph, p.module.idgen, p.module.module, origin(e)) + let n = semparallel.liftParallel(p.module.g.graph, p.module.idgen, p.module.module, e) expr(p, n, d) of mDeepCopy: if p.config.selectedGC in {gcArc, gcAtomicArc, gcOrc, gcYrc} and optEnableDeepCopy notin p.config.globalOptions: @@ -3184,7 +3154,7 @@ proc genMagicExpr(p: BProc, e: AnyNode, d: var TLoc, op: TMagic) = echo p.prc.name.s, " ", p.prc.id, " ", p.prc.flags, " ", son(p.prc.ast, genericParamsPos).kind internalError(p.config, e.info, "genMagicExpr: " & $op) -proc genSetConstr(p: BProc, e: AnyNode, d: var TLoc) = +proc genSetConstr(p: BProc, e: PNode, d: var TLoc) = # example: { a..b, c, d, e, f..g } # we have to emit an expression of the form: # nimZeroMem(tmp, sizeof(tmp)); inclRange(tmp, a, b); incl(tmp, c); @@ -3257,7 +3227,7 @@ proc genSetConstr(p: BProc, e: AnyNode, d: var TLoc) = cOp(Shl, ts, cCast(ts, cIntValue(1)), cOp(Mod, ts, aa, cOp(Mul, ts, cIntValue(size), cIntValue(8))))) -proc genTupleConstr(p: BProc, n: AnyNode, d: var TLoc) = +proc genTupleConstr(p: BProc, n: PNode, d: var TLoc) = var rec: TLoc if not handleConstExpr(p, n, d): let t = n.typ @@ -3265,7 +3235,7 @@ proc genTupleConstr(p: BProc, n: AnyNode, d: var TLoc) = var tmp: TLoc = default(TLoc) # bug #16331 - let doesAlias = lhsDoesAlias(d.lode, origin(n)) + let doesAlias = lhsDoesAlias(d.lode, n) let dest = if doesAlias: addr(tmp) else: addr(d) if doesAlias: tmp = getTemp(p, n.typ) @@ -3288,11 +3258,11 @@ proc genTupleConstr(p: BProc, n: AnyNode, d: var TLoc) = else: genAssignment(p, d, tmp, {}) -proc isConstClosure(n: AnyNode): bool {.inline.} = +proc isConstClosure(n: PNode): bool {.inline.} = result = n.firstSon.kind == nkSym and isRoutine(n.firstSon.sym) and n.secondSon.kind == nkNilLit -proc genClosure(p: BProc, n: AnyNode, d: var TLoc) = +proc genClosure(p: BProc, n: PNode, d: var TLoc) = assert n.kind in {nkPar, nkTupleConstr, nkClosure} if isConstClosure(n): @@ -3323,7 +3293,7 @@ proc genClosure(p: BProc, n: AnyNode, d: var TLoc) = p.s(cpsStmts).addFieldAssignment(dest, "ClE_0", b.rdLoc) putLocIntoDest(p, d, tmp) -proc genArrayConstr(p: BProc, n: AnyNode, d: var TLoc) = +proc genArrayConstr(p: BProc, n: PNode, d: var TLoc) = var arr: TLoc if not handleConstExpr(p, n, d): if d.k == locNone: d = getTemp(p, n.typ) @@ -3359,17 +3329,17 @@ template genStmtListExprImpl(exprOrStmt) {.dirty.} = if frameName != "": p.s(cpsStmts).add deinitFrameNoDebug(p, frameName) -proc genStmtListExpr(p: BProc, n: AnyNode, d: var TLoc) = +proc genStmtListExpr(p: BProc, n: PNode, d: var TLoc) = genStmtListExprImpl: expr(p, n.lastSon, d) -proc genStmtList(p: BProc, n: AnyNode) = +proc genStmtList(p: BProc, n: PNode) = genStmtListExprImpl: genStmts(p, n.lastSon) from parampatterns import isLValue -proc upConv(p: BProc, n: AnyNode, d: var TLoc) = +proc upConv(p: BProc, n: PNode, d: var TLoc) = var a: TLoc = initLocExpr(p, n.firstSon) let dest = skipTypes(n.typ, abstractPtrs) if optObjCheck in p.options and not isObjLackingTypeField(dest): @@ -3399,7 +3369,7 @@ proc upConv(p: BProc, n: AnyNode, d: var TLoc) = elif n.firstSon.typ.kind != tyObject: let destTyp = getTypeDesc(p.module, n.typ) let val = rdLoc(a) - if origin(n).isLValue: + if n.isLValue: # (*((destType) (&(val))))" putIntoDest(p, d, n, cDeref( @@ -3419,7 +3389,7 @@ proc upConv(p: BProc, n: AnyNode, d: var TLoc) = wrapPar(val))), a.storage) -proc downConv(p: BProc, n: AnyNode, d: var TLoc) = +proc downConv(p: BProc, n: PNode, d: var TLoc) = var arg = n.firstSon while arg.kind == nkObjDownConv: arg = arg.firstSon @@ -3427,7 +3397,7 @@ proc downConv(p: BProc, n: AnyNode, d: var TLoc) = let src = skipTypes(arg.typ, abstractPtrs) discard getTypeDesc(p.module, src) let isRef = skipTypes(arg.typ, abstractInstOwned).kind in {tyRef, tyPtr, tyVar, tyLent} - if isRef and d.k == locNone and n.typ.skipTypes(abstractInstOwned).kind in {tyRef, tyPtr} and origin(n).isLValue: + if isRef and d.k == locNone and n.typ.skipTypes(abstractInstOwned).kind in {tyRef, tyPtr} and n.isLValue: # it can happen that we end up generating '&&x->Sup' here, so we pack # the '&x->Sup' into a temporary and then those address is taken # (see bug #837). However sometimes using a temporary is not correct: @@ -3458,10 +3428,10 @@ proc downConv(p: BProc, n: AnyNode, d: var TLoc) = r = cAddr(r) putIntoDest(p, d, n, r, a.storage) -proc exprComplexConst(p: BProc, n: AnyNode, d: var TLoc) = +proc exprComplexConst(p: BProc, n: PNode, d: var TLoc) = let t = n.typ discard getTypeDesc(p.module, t) # so that any fields are initialized - let id = nodeTableTestOrSet(p.module.dataCache, origin(n), p.module.labels) + let id = nodeTableTestOrSet(p.module.dataCache, n, p.module.labels) let tmp = p.module.tmpBase & rope(id) if id == p.module.labels: @@ -3573,7 +3543,7 @@ proc genConstDefinition(q: BModule; p: BProc; sym: PSym) = q.initProc.procSec(cpsLocals).addArgument(copyCall): q.initProc.procSec(cpsLocals).addSizeof(rdLoc(sym.loc)) -proc genConstStmt(p: BProc, n: AnyNode) = +proc genConstStmt(p: BProc, n: PNode) = # This code is only used in the new DCE implementation. assert delayedCodegen(p.module) let m = p.module @@ -3583,7 +3553,7 @@ proc genConstStmt(p: BProc, n: AnyNode) = if not isSimpleConst(sym.typ) and sym.itemId.item in m.alive and genConstSetup(p, sym): genConstDefinition(m, p, sym) -proc expr(p: BProc, n: AnyNode, d: var TLoc) = +proc expr(p: BProc, n: PNode, d: var TLoc) = when defined(nimCompilerStacktraceHints): setFrameMsg p.config$n.info & " " & $n.kind p.currLineInfo = n.info @@ -3818,7 +3788,7 @@ proc expr(p: BProc, n: AnyNode, d: var TLoc) = of nkTypeSection: # we have to emit the type information for object types here to support # separate compilation: - genTypeSection(p.module, origin(n)) + genTypeSection(p.module, n) of nkCommentStmt, nkIteratorDef, nkIncludeStmt, nkImportStmt, nkImportExceptStmt, nkExportStmt, nkExportExceptStmt, nkFromStmt, nkTemplateDef, nkMacroDef, nkStaticStmt: @@ -4011,12 +3981,12 @@ proc getDefaultValue(p: BProc; typ: PType; info: TLineInfo; result: var Builder) else: globalError(p.config, info, "cannot create null element for: " & $t.kind) -proc isEmptyCaseObjectBranch(n: AnyNode): bool = +proc isEmptyCaseObjectBranch(n: PNode): bool = for it in sons(n): if it.kind == nkSym and not isEmptyType(it.sym.typ): return false return true -proc getNullValueAux(p: BProc; t: PType; obj, constOrNil: AnyNode, +proc getNullValueAux(p: BProc; t: PType; obj, constOrNil: PNode, result: var Builder; init: var StructInitializer; isConst: bool, info: TLineInfo) = case obj.kind @@ -4093,7 +4063,7 @@ proc getNullValueAux(p: BProc; t: PType; obj, constOrNil: AnyNode, else: localError(p.config, info, "cannot create null element for: " & $obj) -proc getNullValueAuxT(p: BProc; orig, t: PType; obj, constOrNil: AnyNode, +proc getNullValueAuxT(p: BProc; orig, t: PType; obj, constOrNil: PNode, result: var Builder; init: var StructInitializer; isConst: bool, info: TLineInfo) = var base = t.baseClass @@ -4117,16 +4087,16 @@ proc getNullValueAuxT(p: BProc; orig, t: PType; obj, constOrNil: AnyNode, # do not emit '{}' as that is not valid C: if oldcount == count: result = oldRes -proc genConstObjConstr(p: BProc; n: AnyNode; isConst: bool; result: var Builder) = +proc genConstObjConstr(p: BProc; n: PNode; isConst: bool; result: var Builder) = let t = n.typ.skipTypes(abstractInstOwned) # Use designated initializers when opaque importc fields present var objInit: StructInitializer let initKind = if t.kind == tyObject and containsOpaqueImportcField(t): siNamedStruct else: siOrderedStruct result.addStructInitializer(objInit, kind = initKind): if t.kind == tyObject: - getNullValueAuxT(p, t, t, t.n, origin(n), result, objInit, isConst, n.info) + getNullValueAuxT(p, t, t, t.n, n, result, objInit, isConst, n.info) -proc genConstSimpleList(p: BProc, n: AnyNode; isConst: bool; result: var Builder) = +proc genConstSimpleList(p: BProc, n: PNode; isConst: bool; result: var Builder) = var arrInit: StructInitializer result.addStructInitializer(arrInit, kind = siArray): if p.vccAndC and not n.hasSons and n.typ.kind == tyArray: @@ -4138,7 +4108,7 @@ proc genConstSimpleList(p: BProc, n: AnyNode; isConst: bool; result: var Builder result.addField(arrInit, name = ""): genBracedInit(p, val, isConst, ind.typ, result) -proc genConstTuple(p: BProc, n: AnyNode; isConst: bool; tup: PType; result: var Builder) = +proc genConstTuple(p: BProc, n: PNode; isConst: bool; tup: PType; result: var Builder) = var tupleInit: StructInitializer result.addStructInitializer(tupleInit, kind = siOrderedStruct): if p.vccAndC and not n.hasSons: @@ -4153,7 +4123,7 @@ proc genConstTuple(p: BProc, n: AnyNode; isConst: bool; tup: PType; result: var result.addField(tupleInit, name = "Field" & $i): genBracedInit(p, it, isConst, tup[i], result) -proc genConstSeq(p: BProc, n: AnyNode, t: PType; isConst: bool; result: var Builder) = +proc genConstSeq(p: BProc, n: PNode, t: PType; isConst: bool; result: var Builder) = let base = t.skipTypes(abstractInst).elementType let tmpName = getTempName(p.module) @@ -4186,7 +4156,7 @@ proc genConstSeq(p: BProc, n: AnyNode, t: PType; isConst: bool; result: var Buil result.add cCast(typ = getTypeDesc(p.module, t), value = cAddr(tmpName)) -proc genConstSeqV2(p: BProc, n: AnyNode, t: PType; isConst: bool; result: var Builder) = +proc genConstSeqV2(p: BProc, n: PNode, t: PType; isConst: bool; result: var Builder) = let base = t.skipTypes(abstractInst).elementType let payload = getTempName(p.module) @@ -4219,7 +4189,7 @@ proc genConstSeqV2(p: BProc, n: AnyNode, t: PType; isConst: bool; result: var Bu result.addField(resultInit, name = "p"): result.add cCast(typ = ptrType(getSeqPayloadType(p.module, t)), value = cAddr(payload)) -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) = case n.kind of nkHiddenStdConv, nkHiddenSubConv: genBracedInit(p, n.secondSon, isConst, n.typ, result) @@ -4236,7 +4206,7 @@ proc genBracedInit(p: BProc, n: AnyNode; isConst: bool; optionalType: PType; res ty = typ.kind case ty of tySet: - let cs = toBitSet(p.config, origin(n)) + let cs = toBitSet(p.config, n) genRawSetData(cs, int(getSize(p.config, n.typ)), result) of tySequence: if optSeqDestructors in p.config.globalOptions: @@ -4268,7 +4238,7 @@ proc genBracedInit(p: BProc, n: AnyNode; isConst: bool; optionalType: PType; res var d: TLoc = initLocExpr(p, n) result.add rdLoc(d) of tyArray, tyVarargs: - if isDefaultBroadcastArray(origin(n), p.config): + if isDefaultBroadcastArray(n, p.config): # Compact zero/null-default array (see `isDefaultBroadcastArray`): the # whole thing is the null value of every slot, so a single C `{0}` # zero-fills all `lengthOrd` elements — no need to materialise them. diff --git a/compiler/ccgliterals.nim b/compiler/ccgliterals.nim index 3bcaab7f62..ddedabf6ad 100644 --- a/compiler/ccgliterals.nim +++ b/compiler/ccgliterals.nim @@ -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) diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 3f4eff1879..3b71cd4b32 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -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) diff --git a/compiler/ccgtrav.nim b/compiler/ccgtrav.nim index d7156d1ca8..9993787b9f 100644 --- a/compiler/ccgtrav.nim +++ b/compiler/ccgtrav.nim @@ -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) = diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim index b794858688..ac2692b525 100644 --- a/compiler/ccgtypes.nim +++ b/compiler/ccgtypes.nim @@ -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 diff --git a/compiler/ccgutils.nim b/compiler/ccgutils.nim index 60beaf8495..42c3c34148 100644 --- a/compiler/ccgutils.nim +++ b/compiler/ccgutils.nim @@ -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! diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 3f6e5012b4..944a6a85c4 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -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 " & 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 " & path & " in " & - prc.name.s & ": cursor=" & cur & " ast=" & ast) - - # The result says: nothing ANYWHERE in this subtree hit the tolerated - # `(ht . )` 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 . )` 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 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 . )`'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, "", - 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 " & 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, "", - gradeable = true) - - if tolHtNil != htBefore: - internalError(m.config, prc.info, - "bridge needed the `(ht . )` 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) diff --git a/compiler/icprof.nim b/compiler/icprof.nim index ff05b3e823..3881952172 100644 --- a/compiler/icprof.nim +++ b/compiler/icprof.nim @@ -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 diff --git a/compiler/nodebridge.nim b/compiler/nodebridge.nim deleted file mode 100644 index 733e9742bd..0000000000 --- a/compiler/nodebridge.nim +++ /dev/null @@ -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 )`, an index -## into a side table holding the very `PSym` the encoder was handed, and the -## type slot is `(btyp )` 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 — `( -# …)` — 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 (ht (bsym )))`, 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 . )` 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) diff --git a/compiler/transf.nim b/compiler/transf.nim index ce9510210b..f0cf9dd33b 100644 --- a/compiler/transf.nim +++ b/compiler/transf.nim @@ -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 diff --git a/compiler/trees.nim b/compiler/trees.nim index 3291c0ef42..18bc09873a 100644 --- a/compiler/trees.nim +++ b/compiler/trees.nim @@ -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..`), 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 ======================== diff --git a/tests/ic/readme.md b/tests/ic/readme.md index 2b543b1e03..c83e5cda24 100644 --- a/tests/ic/readme.md +++ b/tests/ic/readme.md @@ -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 diff --git a/tools/icgrind/grindlib.nim b/tools/icgrind/grindlib.nim deleted file mode 100644 index c64c7f14c8..0000000000 --- a/tools/icgrind/grindlib.nim +++ /dev/null @@ -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 diff --git a/tools/icgrind/grindme.nim b/tools/icgrind/grindme.nim deleted file mode 100644 index 470aa4a852..0000000000 --- a/tools/icgrind/grindme.nim +++ /dev/null @@ -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]) diff --git a/tools/icgrind/readme.md b/tools/icgrind/readme.md deleted file mode 100644 index 1eb2f9c61c..0000000000 --- a/tools/icgrind/readme.md +++ /dev/null @@ -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(, 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.