mirror of
https://github.com/nim-lang/Nim.git
synced 2026-09-02 03:43:41 +00:00
IC: give the bridge origin tracking, so TLoc.lode stops blocking the generator
Measured before designing: of the 180 `PNode`-taking procs in `cgen`/`ccgexprs`/ `ccgstmts`/`ccgcalls`, **99 build a `TLoc` from a node**. So the generator cannot move to the seam without an answer for `TLoc.lode`, and the obvious answers are both bad — leaving it a `PNode` means a cursor-driven proc cannot fill it, and changing its representation means editing `TLoc`, which lives in `astdef` at the bottom of the module graph, pushing the seam far below the backend and forcing a flag day. There is a third answer, and nifcore already had the piece it needs. `cursorToPosition` is documented as a stable per-token key, and `TokenBuf.len` is where the next token lands — so the encoder records `position -> PNode` as it walks, and `originOf` inverts it. A cursor-driven generator can then put the ORIGINAL node into a location: `TLoc.lode` stays a `PNode`, and the identity comparisons already in the backend (`preventNrvo`'s `dest != le`, `isPartOf(d.lode, …)`) keep meaning what they meant, because it is the same object and not an equal copy. Asserted, not assumed: the bridge grinder now walks cursor and tree together and requires `originOf(c) == a` by REFERENCE at every node — 1431 bodies, 0 failures. Recording the position one token off makes it fail on the first body, so the check is not vacuous. This unblocks the generator migration without a flag day: `expr` and its ~60 emitters can move to `AnyNode` with `TLoc` untouched. Verified: grind clean (1431 bodies, 260_431 nodes, 0 disagreements); 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:
@@ -1893,6 +1893,29 @@ when defined(newIcBackend):
|
||||
grindLockstep(m, p, prc, BNode(rootCursor(enc)), body, "<bridge>",
|
||||
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
|
||||
let src = originOf(enc, c.raw)
|
||||
if src != a:
|
||||
internalError(m.config, prc.info,
|
||||
"bridge origin is not the source node at <body>" & 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
|
||||
grindOrigins(enc, BNode(rootCursor(enc)), body, "")
|
||||
|
||||
var rt = toPNode(enc)
|
||||
var enc2 = toTokenBuf(rt, m.config)
|
||||
withBridge(enc2.tables):
|
||||
|
||||
@@ -84,10 +84,12 @@ 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]())
|
||||
symIdx: initTable[int, int](), typeIdx: initTable[int, int](),
|
||||
origins: initTable[int, PNode]())
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Encode
|
||||
@@ -162,8 +164,20 @@ proc encodeSym(b: var BridgeBuf; n: PNode) =
|
||||
|
||||
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.origins[b.bld.buf.len] = n
|
||||
if n.kind == nkSym and n.sym != nil:
|
||||
encodeSym(b, n)
|
||||
return
|
||||
@@ -200,6 +214,12 @@ proc toTokenBuf*(n: PNode; conf: ConfigRef): BridgeBuf =
|
||||
result = initBridgeBuf(conf)
|
||||
encodeNode(result, n)
|
||||
|
||||
proc originOf*(b: var BridgeBuf; c: Cursor): PNode =
|
||||
## 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)
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user