diff --git a/compiler/bnode.nim b/compiler/bnode.nim new file mode 100644 index 0000000000..ea1f5b1851 --- /dev/null +++ b/compiler/bnode.nim @@ -0,0 +1,86 @@ +# +# +# 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. +## +## Building those trees is the bulk of the `lower` and `cg` stages: their cost +## tracks the size of the dependency CLOSURE a stage loads, not the module it +## compiles (measured: a 370-byte module costs 0.20s/0.16s in lower/cg, the main +## module 3.40s/3.16s, and the two are ~85% of the serial backend critical path). +## +## With `-d:newIcBackend` `BNode` is a `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` iterators all exist. This module +## deliberately does +## NOT redefine them for `PNode`: an identical second overload would make every +## call site ambiguous. It adds only what the AST lacks (`son`, `hasSons`), and +## supplies the whole vocabulary on the `Cursor` side. +## +## THE COST MODEL DIFFERS, and that is what the vocabulary is shaped around. A +## `Cursor` is a copyable position in a token buffer, so a child is reached by +## `firstSon` plus one `skip` per preceding sibling — and `skip` steps over a +## whole subtree. Reading child `i` is therefore O(size of children 0.. TNodeKind table once.".} = discard + proc len*(n: BNode): int {.error: "BNode.len: not implemented for Cursor yet (counts children; prefer sons/hasSons)".} = discard + proc safeLen*(n: BNode): int {.error: "BNode.safeLen: not implemented for Cursor yet".} = discard + proc son*(n: BNode; i: int): BNode {.error: "BNode.son: not implemented for Cursor yet (firstSon + i skips)".} = discard + proc firstSon*(n: BNode): BNode {.error: "BNode.firstSon: not implemented for Cursor yet".} = discard + proc secondSon*(n: BNode): BNode {.error: "BNode.secondSon: not implemented for Cursor yet".} = discard + proc lastSon*(n: BNode): BNode {.error: "BNode.lastSon: not implemented for Cursor yet (O(len))".} = discard + proc hasSons*(n: BNode): bool {.error: "BNode.hasSons: not implemented for Cursor yet".} = discard + proc sym*(n: BNode): PSym {.error: "BNode.sym: not implemented for Cursor yet".} = discard + proc typ*(n: BNode): PType {.error: "BNode.typ: not implemented for Cursor yet".} = discard + proc info*(n: BNode): TLineInfo {.error: "BNode.info: not implemented for Cursor yet".} = discard + iterator sons*(n: BNode): BNode {.error: "BNode.sons: not implemented for Cursor yet".} = discard + iterator sonsFrom*(n: BNode; start: int): BNode {.error: "BNode.sonsFrom: not implemented for Cursor yet".} = discard +else: + type BNode* = PNode + + # Only the two the AST does not already have. Everything else in the + # vocabulary is `ast`/`astdef`'s own `PNode` API — see the module doc. + 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 diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 4b56e49243..5b8132b8d6 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 + mangleutils, cbuilderbase, modulegraphs, bnode from expanddefaults import caseObjDefaultBranch from ast2nif import globalName, toNifFilename, icNifTypeName @@ -1344,7 +1344,7 @@ const harmless = {nkConstSection, nkTypeSection, nkEmpty, nkCommentStmt, nkTempl nkMacroDef, nkMixinStmt, nkBindStmt, nkFormalParams} + declarativeDefs -proc containsResult(n: PNode): bool = +proc containsResult(n: BNode): bool = result = false case n.kind of succ(nkEmpty)..pred(nkSym), succ(nkSym)..nkNilLit, harmless: @@ -1380,7 +1380,10 @@ proc easyResultAsgn(n: PNode): PNode = type InitResultEnum = enum Unknown, InitSkippable, InitRequired -proc allPathsAsgnResult(p: BProc; n: PNode): InitResultEnum = +proc allPathsAsgnResult(p: BProc; n: BNode): InitResultEnum = + ## Migrated to `BNode` (see bnode.nim). With `newIcBackend` off this is + ## `PNode` and nothing changes; with it on, this body is where the Cursor + ## vocabulary has to exist, and its `{.error.}` stubs name what is missing. # Exceptions coming from calls don't have not be considered here: # # proc bar(): string = raise newException(...)