mirror of
https://github.com/nim-lang/Nim.git
synced 2026-09-01 19:33:42 +00:00
IC: make the location primitives representation-agnostic
`initLoc`, `fillLoc`, `putIntoDest` and `putDataIntoDest` take `AnyNode` and store `origin(lode)`. That is the gate the previous commit was for: 99 of the 180 generator procs build a `TLoc` from their node, so until these four accept a cursor none of those 99 could migrate, and with them accepting one they all can — `TLoc.lode` is still a `PNode`, and on a bridged buffer it is the SAME `PNode` a tree-driven build would have stored. `bnode.origin` is the ambient accessor (through `currentNav`, like `sym` and `typ`), so a generator proc does not have to be handed the buffer to build a location. It answers nil for a file-backed body, which is correct — those tokens came from no `PNode` — and asserts on a bridged one, where a node head always has an origin and a miss means the cursor is not where the caller thinks. The origin check in the grinder now goes through that ambient path rather than calling `originOf` directly, because the direct call is not the path the generator will take and testing it would have proved the wrong thing. Verified: grind clean, origins exact by reference at every node of 1431 bodies; the default path is byte-identical to HEAD; all four build configurations compile. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XEF7FJvUkGKvG9LSGuEaNR
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) =
|
||||
|
||||
@@ -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 <body>" & 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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user