mirror of
https://github.com/nim-lang/Nim.git
synced 2026-09-01 19:33:42 +00:00
`genProcBody` is handed `BNode(bodyBuf.rootCursor)` under `-d:newIcBackend`, so
`expr` and the ~160 procs under it read the routine body through a cursor rather
than a tree. This had to land as one change: `expr` dispatches to all of them, so
they move together or the dispatch converts at every node.
The evidence that it works is not that it compiles. Cursor-driven and
`PNode`-driven builds emit BYTE-IDENTICAL `.c` (50/50 on an 89k-line target,
12/12 on the grind target), the built program runs and prints the right thing,
and — the part that makes the first number mean something — sabotaging
`bnode.intVal` changes all 12 files. The generator is genuinely reading through
the cursor, not quietly falling back.
Four kinds of site could not simply take `AnyNode`, and each is marked where it
sits rather than left for the next person to rediscover:
* THE GENERATOR REWRITES. `mAppendSeqElem`, `mNewSeq`, `genSetLengthSeq`,
`genWasMoved` and `genArrToSeq` replace a child or a type IN PLACE, and
`genEnumToStr`/`mAsgn`/`spawn` build fresh trees. Those run on `origin(n)` —
the very node the buffer was encoded from — so the mutation lands exactly
where it always did. Where the mutation is then READ (`genArrToSeq` retypes a
bracket, `genArg` replaces a `var` param's type), generation continues on the
origin too, because the buffer does not see the write and a cursor would keep
reading the slot as encoded.
* NILABLE NODES stay `PNode`: a cursor has no standalone nil. That is the
assignment DESTINATION throughout the call family (`genCall` passes nil), the
`check` of an object-constructor field, `exvar`, `stepNode`, the `fin` of a
try statement.
* `PNode`-KEYED TABLES AND ANALYSES take `origin`: `dataCache`, `isPartOf`,
`lhsDoesAlias`, `potentialAlias`, the type-record walkers.
* SHARED PREDICATES in `ast.nim` cannot see `BNode`, so `skipHiddenAddr`,
`isInfixAs` and `getStr` join `canRaise`/`getInt` as templates instantiated
for both. `skipPragmaExpr` is a deliberate exception: it sits above the point
in `ast.nim` where `firstSon` for a `PNode` exists, so `bnode` carries a
one-line spelling with a pointer back.
Two Nim details worth recording. Repeated occurrences of a type class in one
signature share ONE implicit generic, so any proc whose two node parameters can
differ in representation needs explicit params — `genSingleVar`,
`genFieldObjConstr`, `callGlobalVarCppCtor`. And a `{.dirty.}` template inside a
generic resolves its identifiers at instantiation, so `genClosureCall`'s local
`rawProc` had to be bound before the template that uses it or it lost to the
module-level proc of the same name.
Verified: grind clean (1431 bodies, 260_431 nodes, 0 disagreements, origins
exact); 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
288 lines
9.3 KiB
Nim
288 lines
9.3 KiB
Nim
#
|
|
#
|
|
# The Nim Compiler
|
|
# (c) Copyright 2012 Andreas Rumpf
|
|
#
|
|
# See the file "copying.txt", included in this
|
|
# distribution, for details about the copyright.
|
|
#
|
|
|
|
# tree helper routines
|
|
|
|
import
|
|
ast, wordrecg, idents, bnode
|
|
|
|
proc cyclicTreeAux(n: PNode, visited: var seq[PNode]): bool =
|
|
result = false
|
|
if n == nil: return
|
|
for v in visited:
|
|
if v == n: return true
|
|
if not (n.kind in {nkEmpty..nkNilLit}):
|
|
visited.add(n)
|
|
for nSon in n.sons:
|
|
if cyclicTreeAux(nSon, visited): return true
|
|
discard visited.pop()
|
|
|
|
proc cyclicTree*(n: PNode): bool =
|
|
var visited: seq[PNode] = @[]
|
|
cyclicTreeAux(n, visited)
|
|
|
|
proc sameFloatIgnoreNan(a, b: BiggestFloat): bool {.inline.} =
|
|
## ignores NaN semantics, but ensures 0.0 == -0.0, see #13730
|
|
cast[uint64](a) == cast[uint64](b) or a == b
|
|
|
|
proc exprStructuralEquivalent*(a, b: PNode; strictSymEquality=false): bool =
|
|
if a == b:
|
|
result = true
|
|
elif (a != nil) and (b != nil) and (a.kind == b.kind):
|
|
case a.kind
|
|
of nkSym:
|
|
if strictSymEquality:
|
|
result = a.sym == b.sym
|
|
else:
|
|
# don't go nuts here: same symbol as string is enough:
|
|
result = a.sym.name.id == b.sym.name.id
|
|
of nkIdent: result = a.ident.id == b.ident.id
|
|
of nkCharLit..nkUInt64Lit: result = a.intVal == b.intVal
|
|
of nkFloatLit..nkFloat64Lit: result = sameFloatIgnoreNan(a.floatVal, b.floatVal)
|
|
of nkStrLit..nkTripleStrLit: result = a.strVal == b.strVal
|
|
of nkCommentStmt: result = a.comment == b.comment
|
|
of nkEmpty, nkNilLit, nkType: result = true
|
|
else:
|
|
if a.len == b.len:
|
|
for i in 0..<a.len:
|
|
if not exprStructuralEquivalent(a[i], b[i],
|
|
strictSymEquality): return
|
|
result = true
|
|
else:
|
|
result = false
|
|
else:
|
|
result = false
|
|
|
|
proc sameTree*(a, b: PNode): bool =
|
|
result = false
|
|
if a == b:
|
|
result = true
|
|
elif a != nil and b != nil and a.kind == b.kind:
|
|
if a.flags != b.flags: return
|
|
if a.info.line != b.info.line: return
|
|
if a.info.col != b.info.col:
|
|
return #if a.info.fileIndex <> b.info.fileIndex then exit;
|
|
case a.kind
|
|
of nkSym:
|
|
# don't go nuts here: same symbol as string is enough:
|
|
result = a.sym.name.id == b.sym.name.id
|
|
of nkIdent: result = a.ident.id == b.ident.id
|
|
of nkCharLit..nkUInt64Lit: result = a.intVal == b.intVal
|
|
of nkFloatLit..nkFloat64Lit: result = sameFloatIgnoreNan(a.floatVal, b.floatVal)
|
|
of nkStrLit..nkTripleStrLit: result = a.strVal == b.strVal
|
|
of nkEmpty, nkNilLit, nkType: result = true
|
|
else:
|
|
if a.len == b.len:
|
|
for i in 0..<a.len:
|
|
if not sameTree(a[i], b[i]): return
|
|
result = true
|
|
|
|
proc getMagic*(op: AnyNode): TMagic =
|
|
if op.isNilNode: return mNone
|
|
case op.kind
|
|
of nkCallKinds:
|
|
let callee = op.firstSon
|
|
case callee.kind
|
|
of nkSym: result = callee.sym.magic
|
|
else: result = mNone
|
|
else: result = mNone
|
|
|
|
proc isConstExpr*(n: AnyNode): bool =
|
|
const atomKinds = {nkCharLit..nkNilLit} # Char, Int, UInt, Str, Float and Nil literals
|
|
n.kind in atomKinds or nfAllConst in n.flags
|
|
|
|
proc isCaseObj*(n: PNode): bool =
|
|
result = false
|
|
if n.kind == nkRecCase: return true
|
|
for i in 0..<n.safeLen:
|
|
if n[i].isCaseObj: return true
|
|
|
|
proc isDeepConstExpr*(n: AnyNode; preventInheritance = false): bool =
|
|
case n.kind
|
|
of nkCharLit..nkNilLit:
|
|
result = true
|
|
of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv:
|
|
result = isDeepConstExpr(n.secondSon, preventInheritance)
|
|
of nkCurly, nkBracket, nkPar, nkTupleConstr, nkObjConstr, nkClosure, nkRange:
|
|
# `nkObjConstr` carries its TYPE as child 0 and its fields from 1.
|
|
for it in sonsFrom(n, ord(n.kind == nkObjConstr)):
|
|
if not isDeepConstExpr(it, preventInheritance): return false
|
|
if n.typ.isNil: result = true
|
|
else:
|
|
let t = n.typ.skipTypes({tyGenericInst, tyDistinct, tyAlias, tySink, tyOwned})
|
|
if t.kind in {tyRef, tyPtr} or tfUnion in t.flags: return false
|
|
if t.kind == tyObject:
|
|
if preventInheritance and t.baseClass != nil:
|
|
result = false
|
|
elif isCaseObj(t.n):
|
|
result = false
|
|
else:
|
|
result = true
|
|
else:
|
|
result = true
|
|
else: result = false
|
|
|
|
proc isRange*(n: PNode): bool {.inline.} =
|
|
if n.kind in nkCallKinds:
|
|
let callee = n[0]
|
|
if (callee.kind == nkIdent and callee.ident.id == ord(wDotDot)) or
|
|
(callee.kind == nkSym and callee.sym.name.id == ord(wDotDot)) or
|
|
(callee.kind in {nkClosedSymChoice, nkOpenSymChoice, nkOpenSym} and
|
|
callee[0].sym.name.id == ord(wDotDot)):
|
|
result = true
|
|
else:
|
|
result = false
|
|
else:
|
|
result = false
|
|
|
|
proc whichPragma*(n: AnyNode): TSpecialWord =
|
|
let key = if n.kind in nkPragmaCallKinds and n.hasSons: n.firstSon else: n
|
|
case key.kind
|
|
of nkIdent: result = whichKeyword(key.ident)
|
|
of nkSym: result = whichKeyword(key.sym.name)
|
|
of nkCast: return wCast
|
|
of nkClosedSymChoice, nkOpenSymChoice, nkOpenSym:
|
|
return whichPragma(key.firstSon)
|
|
of nkBracketExpr:
|
|
if n.kind notin nkPragmaCallKinds: return wInvalid
|
|
result = whichPragma(key.firstSon)
|
|
if result notin {wHint, wHintAsError, wWarning, wWarningAsError}:
|
|
# note bracket pragmas, see processNote
|
|
result = wInvalid
|
|
return
|
|
else: return wInvalid
|
|
if result in nonPragmaWordsLow..nonPragmaWordsHigh:
|
|
result = wInvalid
|
|
|
|
proc isNoSideEffectPragma*(n: PNode): bool =
|
|
var k = whichPragma(n)
|
|
if k == wCast:
|
|
k = whichPragma(n[1])
|
|
result = k == wNoSideEffect
|
|
|
|
proc findPragma*(n: PNode, which: TSpecialWord): PNode =
|
|
result = nil
|
|
if n.kind == nkPragma:
|
|
for son in n:
|
|
if whichPragma(son) == which:
|
|
return son
|
|
|
|
proc effectSpec*(n: PNode, effectType: TSpecialWord): PNode =
|
|
result = nil
|
|
for i in 0..<n.len:
|
|
var it = n[i]
|
|
if it.kind == nkExprColonExpr and whichPragma(it) == effectType:
|
|
result = it[1]
|
|
if result.kind notin {nkCurly, nkBracket}:
|
|
result = newNodeI(nkCurly, result.info)
|
|
result.add(it[1])
|
|
return
|
|
|
|
proc propSpec*(n: PNode, effectType: TSpecialWord): PNode =
|
|
result = nil
|
|
for i in 0..<n.len:
|
|
var it = n[i]
|
|
if it.kind == nkExprColonExpr and whichPragma(it) == effectType:
|
|
return it[1]
|
|
|
|
proc unnestStmts(n, result: PNode) =
|
|
if n.kind == nkStmtList:
|
|
for x in items(n): unnestStmts(x, result)
|
|
elif n.kind notin {nkCommentStmt, nkNilLit}:
|
|
result.add(n)
|
|
|
|
proc flattenStmts*(n: PNode): PNode =
|
|
result = newNodeI(nkStmtList, n.info)
|
|
unnestStmts(n, result)
|
|
if result.len == 1:
|
|
result = result[0]
|
|
|
|
proc extractRange*(k: TNodeKind, n: PNode, a, b: int): PNode =
|
|
result = newNodeI(k, n.info, b-a+1)
|
|
for i in 0..b-a: result[i] = n[i+a]
|
|
|
|
proc getRoot*(n: AnyNode): PSym =
|
|
## ``getRoot`` takes a *path* ``n``. A path is an lvalue expression
|
|
## like ``obj.x[i].y``. The *root* of a path is the symbol that can be
|
|
## determined as the owner; ``obj`` in the example.
|
|
case n.kind
|
|
of nkSym:
|
|
if n.sym.kind in {skVar, skResult, skTemp, skLet, skForVar, skParam}:
|
|
result = n.sym
|
|
else:
|
|
result = nil
|
|
of nkDotExpr, nkBracketExpr, nkHiddenDeref, nkDerefExpr,
|
|
nkObjUpConv, nkObjDownConv, nkCheckedFieldExpr, nkHiddenAddr, nkAddr:
|
|
result = getRoot(n.firstSon)
|
|
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
|
|
result = getRoot(n.secondSon)
|
|
of nkCallKinds:
|
|
if getMagic(n) == mSlice: result = getRoot(n.secondSon)
|
|
else: result = nil
|
|
else: result = nil
|
|
|
|
proc isCursor*(n: PNode): bool =
|
|
case n.kind
|
|
of nkSym:
|
|
sfCursor in n.sym.flags
|
|
of nkDotExpr:
|
|
isCursor(n[1])
|
|
of nkCheckedFieldExpr:
|
|
isCursor(n[0])
|
|
else:
|
|
false
|
|
|
|
proc stupidStmtListExpr*(n: PNode): bool =
|
|
for i in 0..<n.len-1:
|
|
if n[i].kind notin {nkEmpty, nkCommentStmt}: return false
|
|
result = true
|
|
|
|
proc dontInlineConstant*(orig, cnst: PNode): bool {.inline.} =
|
|
# symbols that expand to a complex constant (array, etc.) should not be
|
|
# inlined, unless it's the empty array:
|
|
result = cnst.kind in {nkCurly, nkPar, nkTupleConstr, nkBracket, nkObjConstr} and
|
|
cnst.len > ord(cnst.kind == nkObjConstr)
|
|
|
|
proc isRunnableExamples*(n: PNode): bool =
|
|
# Templates and generics don't perform symbol lookups.
|
|
result = n.kind == nkSym and n.sym.magic == mRunnableExamples or
|
|
n.kind == nkIdent and n.ident.id == ord(wRunnableExamples)
|
|
|
|
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:
|
|
of nkLiterals, nkIdent, nkFormalParams: discard
|
|
of nkSym:
|
|
if mutate: result.add n
|
|
of nkAsgn, nkFastAsgn, nkSinkAsgn:
|
|
getPotentialWrites(n[0], true, result)
|
|
getPotentialWrites(n[1], mutate, result)
|
|
of nkAddr, nkHiddenAddr:
|
|
getPotentialWrites(n[0], true, result)
|
|
of nkBracketExpr, nkDotExpr, nkCheckedFieldExpr:
|
|
getPotentialWrites(n[0], mutate, result)
|
|
of nkCallKinds:
|
|
case n.getMagic:
|
|
of mIncl, mExcl, mInc, mDec, mAppendStrCh, mAppendStrStr, mAppendSeqElem,
|
|
mAddr, mNew, mNewFinalize, mWasMoved, mDestroy:
|
|
getPotentialWrites(n[1], true, result)
|
|
for i in 2..<n.len:
|
|
getPotentialWrites(n[i], mutate, result)
|
|
of mSwap, mMove:
|
|
for i in 1..<n.len:
|
|
getPotentialWrites(n[i], true, result)
|
|
else:
|
|
for i in 1..<n.len:
|
|
getPotentialWrites(n[i], mutate, result)
|
|
else:
|
|
for s in n:
|
|
getPotentialWrites(s, mutate, result)
|