diff --git a/compiler/cgen.nim b/compiler/cgen.nim index 640dee50b8..2686f0f35a 100644 --- a/compiler/cgen.nim +++ b/compiler/cgen.nim @@ -1893,6 +1893,29 @@ when defined(newIcBackend): grindLockstep(m, p, prc, BNode(rootCursor(enc)), body, "", 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 " & 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): diff --git a/compiler/nodebridge.nim b/compiler/nodebridge.nim index 6b62232f7e..beff142c93 100644 --- a/compiler/nodebridge.nim +++ b/compiler/nodebridge.nim @@ -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