mirror of
https://github.com/nim-lang/Nim.git
synced 2026-09-02 03:43:41 +00:00
`trees.nim` can import `bnode` — nothing in `bnode`'s import closure reaches
`trees`, checked rather than assumed — so the shared helpers move to `AnyNode`
instead of being reimplemented behind the seam: `getMagic`, `whichPragma`,
`getRoot`, `isDeepConstExpr`, plus `ccgutils.stmtsContainPragma`. That unblocks
three more codegen procs, `canMove`, `notYetAlive` and `ifSwitchSplitPoint`,
which needed them and nothing else.
`stmtsContainPragma` could not simply stay `getPragmaStmt(n, w) != nil`, and
the reason is worth recording because it will recur: a proc that returns a node
OR NIL is the one shape the seam cannot serve. `.bif` spells a missing child as
a `DotToken` *inside* a tree; there is no nil token to hand back as a return
value and a `Cursor` is not nilable. So the predicate is split out — and,
because that leaves two copies of one traversal, `grindPredicates` now asserts
the two agree at every node instead of trusting them to.
Measuring the answers, not just the agreement, again earned its keep. Six of
the new checks came back with a wide spread (`getMagic` 7780 non-`mNone` over
many magics, `getRoot` 19506 non-nil syms compared by identity, `isDeepConstExpr`
7917 true, `notYetAlive` 9653 true). Two came back CONSTANT — `stmtsContainPragma`
false at all 67_721 nodes and `ifSwitchSplitPoint` zero at all 24 — because
nothing in the closure uses `{.linearScanEnd.}` or `{.computedGoto.}`. Both are
now exercised on both answers by shapes added to `tools/icgrind`. A check that
grades a constant is indistinguishable from a passing check in the output, so
this only shows up if the distribution is looked at.
Verified: grind clean over the whole `--ic:on` closure (67_857 nodes, 0
disagreements); the target's `--ic:on` output matches its `nim c` output;
215/215 byte-identical `.c` against HEAD on the default path; all four build
configurations compile.
Sabotaging `bnode.secondSon` — an accessor the lockstep walk does NOT itself
use, since it descends by index — is caught only by this layer, and is: it
fires on `getRoot`, `isDeepConstExpr`, `reifiedOpenArray` and
`skipTrivialIndirections`.
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: PNode): 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*(n: PNode): PNode {.inline.} =
|
|
result = if n.kind in {nkAddr, nkHiddenAddr}: n[0] 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)
|