diff --git a/compiler/bnode.nim b/compiler/bnode.nim index eeafa8fb77..92a82fd751 100644 --- a/compiler/bnode.nim +++ b/compiler/bnode.nim @@ -592,6 +592,26 @@ when defined(newIcBackend): # 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. + let b = currentNav().bridge + if b == nil: return nil + result = originAt(b, n.raw) + 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) @@ -708,6 +728,9 @@ else: # 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. diff --git a/compiler/bodynav.nim b/compiler/bodynav.nim index a47e320712..6caefd871c 100644 --- a/compiler/bodynav.nim +++ b/compiler/bodynav.nim @@ -98,6 +98,15 @@ type ## 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 @@ -115,6 +124,12 @@ type 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. diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim index dc400dbf55..4c49b96e64 100644 --- a/compiler/ccgexprs.nim +++ b/compiler/ccgexprs.nim @@ -539,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: PNode, r: Rope) = +proc putDataIntoDest(p: BProc, d: var TLoc, n: AnyNode, r: Rope) = if d.k != locNone: var a: TLoc = initLoc(locData, n, OnStatic) # need to generate an assignment here @@ -550,10 +550,10 @@ proc putDataIntoDest(p: BProc, d: var TLoc, n: PNode, r: Rope) = # we cannot call initLoc() here as that would overwrite # the flags field! d.k = locData - d.lode = n + d.lode = origin(n) d.snippet = r -proc putIntoDest(p: BProc, d: var TLoc, n: PNode, r: Rope; s=OnUnknown) = +proc putIntoDest(p: BProc, d: var TLoc, n: AnyNode, r: Rope; s=OnUnknown) = if d.k != locNone: # need to generate an assignment here var a: TLoc = initLoc(locExpr, n, s) @@ -564,7 +564,7 @@ proc putIntoDest(p: BProc, d: var TLoc, n: PNode, r: Rope; s=OnUnknown) = # we cannot call initLoc() here as that would overwrite # the flags field! d.k = locExpr - d.lode = n + d.lode = origin(n) d.snippet = r proc binaryStmt(p: BProc, e: PNode, d: var TLoc, op: TypedBinaryOp) = diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 2686f0f35a..dbc424dc9b 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -252,23 +252,30 @@ proc emitsBodyInThisModule(m: BModule, prc: PSym): bool = else: result = prc.itemId.module == m.module.position -proc initLoc(k: TLocKind, lode: PNode, s: TStorageLoc, flags: TLocFlags = {}): TLoc = - result = TLoc(k: k, storage: s, lode: lode, +# `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), snippet: "", flags: flags) -proc fillLoc(a: var TLoc, k: TLocKind, lode: PNode, r: Rope, s: TStorageLoc) {.inline.} = +proc fillLoc(a: var TLoc, k: TLocKind, lode: AnyNode, r: Rope, s: TStorageLoc) {.inline.} = # fills the loc if it is not already initialized if a.k == locNone: a.k = k - a.lode = lode + a.lode = origin(lode) a.storage = s if a.snippet == "": a.snippet = r -proc fillLoc(a: var TLoc, k: TLocKind, lode: PNode, s: TStorageLoc) {.inline.} = +proc fillLoc(a: var TLoc, k: TLocKind, lode: AnyNode, s: TStorageLoc) {.inline.} = # fills the loc if it is not already initialized if a.k == locNone: a.k = k - a.lode = lode + a.lode = origin(lode) a.storage = s proc t(a: TLoc): PType {.inline.} = @@ -1902,7 +1909,10 @@ when defined(newIcBackend): # would not do. proc grindOrigins(enc: var BridgeBuf; c: BNode; a: PNode; path: string) = if a == nil: return - let src = originOf(enc, c.raw) + # 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 " & @@ -1914,7 +1924,8 @@ when defined(newIcBackend): for child in sons(a): grindOrigins(enc, son(c, i), child, path & "[" & $i & "]") inc i - grindOrigins(enc, BNode(rootCursor(enc)), body, "") + withBridge(enc.tables): + grindOrigins(enc, BNode(rootCursor(enc)), body, "") var rt = toPNode(enc) var enc2 = toTokenBuf(rt, m.config) diff --git a/compiler/nodebridge.nim b/compiler/nodebridge.nim index beff142c93..57f5c746a9 100644 --- a/compiler/nodebridge.nim +++ b/compiler/nodebridge.nim @@ -84,12 +84,10 @@ type conf: ConfigRef symIdx: Table[int, int] ## PSym identity -> index into `tables.syms` typeIdx: Table[int, int] ## PType identity -> index into `tables.types` - origins: Table[int, PNode] ## token position -> the `PNode` encoded there proc initBridgeBuf*(conf: ConfigRef; cap = 64): BridgeBuf = BridgeBuf(bld: newIcBuilder(cap), tables: BridgeTables(), conf: conf, - symIdx: initTable[int, int](), typeIdx: initTable[int, int](), - origins: initTable[int, PNode]()) + symIdx: initTable[int, int](), typeIdx: initTable[int, int]()) # --------------------------------------------------------------------------- # Encode @@ -177,7 +175,7 @@ proc encodeNode(b: var BridgeBuf; n: PNode) = # 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.origins[b.bld.buf.len] = n + b.tables.origins[b.bld.buf.len] = n if n.kind == nkSym and n.sym != nil: encodeSym(b, n) return @@ -213,12 +211,17 @@ proc toTokenBuf*(n: PNode; conf: ConfigRef): BridgeBuf = ## 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 = +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.origins.getOrDefault(cursorToPosition(b.bld.buf, c), nil) + 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