From c1a815fa0744e8f9fbe6a447887164c200416352 Mon Sep 17 00:00:00 2001 From: Araq Date: Sun, 30 Aug 2026 11:06:52 +0200 Subject: [PATCH] IC: take the last leaf readers, and map where the leaf migration ends MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `skipAddr`, `skipAddrDeref` and `isInactiveDestructorCall` move to the seam — all three pure, none of them touching a field symbol. The node-returning ones are graded by a `checkNodeResult` template rather than a hand-written pair, so adding the next one is a line. The more useful half of this commit is the map. Counting rather than guessing: of the 191 `PNode`-taking procs across `cgen` and the `ccg*` files, 160 EMIT — they take a `Builder`/`TLoc` out-param or write into `p` directly. Those do not move one at a time. They are one mutual recursion rooted at `expr`/`genStmts`, so the generator moves as a unit or not at all, and doing that needs write-side capability the seam does not have. What is left over is not a backlog, it is six named blockers, and `bnode`'s module doc now says which proc each one holds up and why: * a type's RECORD TREE is not a body (`PType.n` stays `PNode` by design, so `asgnComplexity` and friends were never candidates); * RETURNS A NODE OR NIL (`getPragmaStmt` — no nil token exists to return); * WRITES TO THE NODE (`easyResultAsgn` sets `nfPreventCg`; the seam is read-only); * NEEDS RENDERING (`preventNrvo` interpolates `$le` into a warning); * NEEDS STABLE FIELD IDENTITY (`lhsDoesAlias`, `potentialAlias`, through `isPartOf` — see the `sym` note, this one is blocked on a property rather than on effort); * MIXED REPRESENTATION (`potentialAlias`, `getPotentialReads` carry a `seq[PNode]` beside the node). Stating it this way because the alternative is someone re-deriving each blocker by trying the migration and watching it fail, which is how three of the six were found. Verified: grind clean (67_857 nodes, 0 disagreements); 215/215 byte-identical `.c` against HEAD on the default path; all four build configurations compile. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01XEF7FJvUkGKvG9LSGuEaNR --- compiler/bnode.nim | 29 +++++++++++++++++++++++++++++ compiler/ccgcalls.nim | 6 +++--- compiler/cgen.nim | 25 +++++++++++++++++-------- compiler/trees.nim | 4 ++-- 4 files changed, 51 insertions(+), 13 deletions(-) diff --git a/compiler/bnode.nim b/compiler/bnode.nim index c2842a4458..1847f5cdb5 100644 --- a/compiler/bnode.nim +++ b/compiler/bnode.nim @@ -161,6 +161,35 @@ ## `genericHead` and the `kids` / `ikids` / `paramTypes` / `signature` ## iterators, which name the child and go through `[]`. ## +## WHERE THE LEAF MIGRATION ENDS. Of the 191 `PNode`-taking procs across +## `cgen` and the `ccg*` files, 160 EMIT — they take a `Builder`/`TLoc` +## out-param or write into `p` directly. Those do not move one at a time: they +## are one mutual recursion rooted at `expr`/`genStmts`, so the whole generator +## has to move together, and it needs write-side capability this seam does not +## have. What is left over splits into named blockers rather than a backlog, and +## each is 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. +## * WRITES TO THE NODE — `cgen.easyResultAsgn` does `incl n.flags, nfPreventCg`. +## The seam is read-only and a `Cursor` points into a shared token buffer. +## * NEEDS RENDERING — `ccgcalls.preventNrvo` interpolates `$le` into +## `warnObservableStores`. Reconstructing source text is a different job from +## reading a node, and only a diagnostic wants it. +## * NEEDS STABLE FIELD IDENTITY — `lhsDoesAlias` and `potentialAlias` through +## `aliases.isPartOf`, which compares field `sym.id`. See the note on `sym` +## below: it is not idempotent for fields, so this one is not blocked on +## effort, it is blocked on a property the seam does not currently have. +## * MIXED REPRESENTATION — `potentialAlias` and `getPotentialReads` build and +## consume a `seq[PNode]` alongside the node, so both sides would have to be +## the same spelling. +## ## 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 diff --git a/compiler/ccgcalls.nim b/compiler/ccgcalls.nim index 882cb03e8e..a96de827d2 100644 --- a/compiler/ccgcalls.nim +++ b/compiler/ccgcalls.nim @@ -696,7 +696,7 @@ y.v() --> y.v() is correct """ -proc skipAddrDeref(node: PNode): PNode = +proc skipAddrDeref[T: AnyNode](node: T): T = var n = node var isAddr = false case n.kind @@ -915,7 +915,7 @@ proc notYetAlive(n: AnyNode): bool {.inline.} = let r = getRoot(n) result = r != nil and r.loc.lode == nil -proc isInactiveDestructorCall(p: BProc, e: PNode): bool = +proc isInactiveDestructorCall(p: BProc, e: AnyNode): bool = #[ Consider this example. var :tmpD_3281815 @@ -932,7 +932,7 @@ proc isInactiveDestructorCall(p: BProc, e: PNode): bool = We want to return early but the 'finally' section is traversed before the 'let args = ...' statement. We exploit this to generate better code for 'return'. ]# - result = e.len == 2 and e.firstSon.kind == nkSym and + 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, ri: PNode, d: var TLoc) = diff --git a/compiler/cgen.nim b/compiler/cgen.nim index b707a774e2..cd07d4dc34 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -1622,6 +1622,7 @@ when defined(newIcBackend): 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) @@ -1655,14 +1656,22 @@ when defined(newIcBackend): # 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. - block: - let cs = skipTrivialIndirections(c) - let a2 = skipTrivialIndirections(a) - if cs.kind != a2.kind: - bail("skipTrivialIndirections kind", $cs.kind, $a2.kind) - if cs.info != a2.info: - bail("skipTrivialIndirections info", - $(m.config, cs.info), $(m.config, a2.info)) + 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: diff --git a/compiler/trees.nim b/compiler/trees.nim index 3d2378df8e..7d8c87ec35 100644 --- a/compiler/trees.nim +++ b/compiler/trees.nim @@ -254,8 +254,8 @@ proc isRunnableExamples*(n: PNode): bool = result = n.kind == nkSym and n.sym.magic == mRunnableExamples or n.kind == nkIdent and n.ident.id == ord(wRunnableExamples) -proc skipAddr*(n: PNode): PNode {.inline.} = - result = if n.kind in {nkAddr, nkHiddenAddr}: n[0] else: n +proc skipAddr*[T: AnyNode](n: T): T {.inline.} = + result = if n.kind in {nkAddr, nkHiddenAddr}: n.firstSon else: n proc getPotentialWrites*(n: PNode; mutate: bool; result: var seq[PNode]) = case n.kind: