Files
Nim/compiler/transf.nim
ringabout 08d51e5c88 fixes #7179; Floats are not range checked (#25050)
fixes #7179


```nim
var f = 751.0
echo f.int8
```

In this case, `int8(float)` yields different numbers for different
optimization levels, since float to int conversions are undefined
behaviors. In this PR, it mitigates this problem by conversions to same
size integers before converting to the final type: i.e.
`int8(int64(float))`, which has UB problems but is better than before
2025-07-18 21:30:50 +02:00

1341 lines
47 KiB
Nim

#
#
# The Nim Compiler
# (c) Copyright 2015 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# This module implements the transformator. It transforms the syntax tree
# to ease the work of the code generators. Does some transformations:
#
# * inlines iterators
# * inlines constants
# * performs constant folding
# * converts "continue" to "break"; disambiguates "break"
# * introduces method dispatchers
# * performs lambda lifting for closure support
# * transforms 'defer' into a 'try finally' statement
import std / tables
import
options, ast, astalgo, trees, msgs,
idents, renderer, types, semfold, magicsys, cgmeth,
lowerings, liftlocals,
modulegraphs, lineinfos
when defined(nimPreviewSlimSystem):
import std/assertions
type
TransformFlag* = enum
useCache, keepOpenArrayConversions, force
TransformFlags* = set[TransformFlag]
proc transformBody*(g: ModuleGraph; idgen: IdGenerator; prc: PSym; flags: TransformFlags): PNode
import closureiters, lambdalifting
type
PTransCon = ref object # part of TContext; stackable
mapping: TIdTable[PNode] # mapping from symbols to nodes
owner: PSym # current owner
forStmt: PNode # current for stmt
forLoopBody: PNode # transformed for loop body
yieldStmts: int # we count the number of yield statements,
# because we need to introduce new variables
# if we encounter the 2nd yield statement
next: PTransCon # for stacking
PTransf = ref object
module: PSym
transCon: PTransCon # top of a TransCon stack
inlining: int # > 0 if we are in inlining context (copy vars)
contSyms, breakSyms: seq[PSym] # to transform 'continue' and 'break'
deferDetected, tooEarly: bool
isIntroducingNewLocalVars: bool # true if we are in `introducingNewLocalVars` (don't transform yields)
flags: TransformFlags
graph: ModuleGraph
idgen: IdGenerator
proc newTransNode(a: PNode): PNode {.inline.} =
result = shallowCopy(a)
proc newTransNode(kind: TNodeKind, info: TLineInfo,
sons: int): PNode {.inline.} =
var x = newNodeI(kind, info)
newSeq(x.sons, sons)
result = x
proc newTransNode(kind: TNodeKind, n: PNode,
sons: int): PNode {.inline.} =
var x = newNodeIT(kind, n.info, n.typ)
newSeq(x.sons, sons)
# x.flags = n.flags
result = x
proc newTransCon(owner: PSym): PTransCon =
assert owner != nil
result = PTransCon(mapping: initIdTable[PNode](), owner: owner)
proc pushTransCon(c: PTransf, t: PTransCon) =
t.next = c.transCon
c.transCon = t
proc popTransCon(c: PTransf) =
if (c.transCon == nil): internalError(c.graph.config, "popTransCon")
c.transCon = c.transCon.next
proc getCurrOwner(c: PTransf): PSym =
if c.transCon != nil: result = c.transCon.owner
else: result = c.module
proc newTemp(c: PTransf, typ: PType, info: TLineInfo): PNode =
let r = newSym(skTemp, getIdent(c.graph.cache, genPrefix), c.idgen, getCurrOwner(c), info)
r.typ = typ #skipTypes(typ, {tyGenericInst, tyAlias, tySink})
incl(r.flags, sfFromGeneric)
let owner = getCurrOwner(c)
result = newSymNode(r)
proc transform(c: PTransf, n: PNode, noConstFold = false): PNode
proc transformSons(c: PTransf, n: PNode, noConstFold = false): PNode =
result = newTransNode(n)
for i in 0..<n.len:
result[i] = transform(c, n[i], noConstFold)
proc transformSonsAfterType(c: PTransf, n: PNode, noConstFold = false): PNode =
result = newTransNode(n)
assert n.len != 0
result[0] = copyTree(n[0])
for i in 1..<n.len:
result[i] = transform(c, n[i], noConstFold)
proc newAsgnStmt(c: PTransf, kind: TNodeKind, le: PNode, ri: PNode; isFirstWrite: bool): PNode =
result = newTransNode(kind, ri.info, 2)
result[0] = le
if isFirstWrite:
le.flags.incl nfFirstWrite
result[1] = ri
proc transformSymAux(c: PTransf, n: PNode): PNode =
let s = n.sym
if s.typ != nil and s.typ.callConv == ccClosure:
if s.kind in routineKinds:
discard transformBody(c.graph, c.idgen, s, {useCache}+c.flags)
if s.kind == skIterator:
if c.tooEarly: return n
else: return liftIterSym(c.graph, n, c.idgen, getCurrOwner(c))
elif s.kind in {skProc, skFunc, skConverter, skMethod} and not c.tooEarly:
# top level .closure procs are still somewhat supported for 'Nake':
return makeClosure(c.graph, c.idgen, s, nil, n.info)
#elif n.sym.kind in {skVar, skLet} and n.sym.typ.callConv == ccClosure:
# echo n.info, " come heer for ", c.tooEarly
# if not c.tooEarly:
var b: PNode
var tc = c.transCon
if sfBorrow in s.flags and s.kind in routineKinds:
# simply exchange the symbol:
var s = s
while true:
# Skips over all borrowed procs getting the last proc symbol without an implementation
let body = getBody(c.graph, s)
if body.kind == nkSym and sfBorrow in body.sym.flags and getBody(c.graph, body.sym).kind == nkSym:
s = body.sym
else:
break
b = getBody(c.graph, s)
if b.kind != nkSym: internalError(c.graph.config, n.info, "wrong AST for borrowed symbol")
b = newSymNode(b.sym, n.info)
elif c.inlining > 0:
# see bug #13596: we use ref-based equality in the DFA for destruction
# injections so we need to ensure unique nodes after iterator inlining
# which can lead to duplicated for loop bodies! Consider:
#[
while remaining > 0:
if ending == nil:
yield ms
break
...
yield ms
]#
b = newSymNode(n.sym, n.info)
else:
b = n
while tc != nil:
result = getOrDefault(tc.mapping, b.sym.itemId)
if result != nil:
# this slightly convoluted way ensures the line info stays correct:
if result.kind == nkSym:
result = copyNode(result)
result.info = n.info
return
tc = tc.next
result = b
proc transformSym(c: PTransf, n: PNode): PNode =
result = transformSymAux(c, n)
proc freshVar(c: PTransf; v: PSym): PNode =
let owner = getCurrOwner(c)
var newVar = copySym(v, c.idgen)
incl(newVar.flags, sfFromGeneric)
setOwner(newVar, owner)
result = newSymNode(newVar)
proc transformVarSection(c: PTransf, v: PNode): PNode =
result = newTransNode(v)
for i in 0..<v.len:
var it = v[i]
if it.kind == nkCommentStmt:
result[i] = it
elif it.kind == nkIdentDefs:
var vn = it[0]
if vn.kind == nkPragmaExpr: vn = vn[0]
if vn.kind == nkSym:
internalAssert(c.graph.config, it.len == 3)
let x = freshVar(c, vn.sym)
c.transCon.mapping[vn.sym.itemId] = x
var defs = newTransNode(nkIdentDefs, it.info, 3)
if importantComments(c.graph.config):
# keep documentation information:
defs.comment = it.comment
defs[0] = x
defs[1] = it[1]
defs[2] = transform(c, it[2])
if x.kind == nkSym: x.sym.ast = defs[2]
result[i] = defs
else:
# has been transformed into 'param.x' for closure iterators, so just
# transform it:
result[i] = transform(c, it)
else:
if it.kind != nkVarTuple:
internalError(c.graph.config, it.info, "transformVarSection: not nkVarTuple")
var defs = newTransNode(it.kind, it.info, it.len)
for j in 0..<it.len-2:
if it[j].kind == nkSym:
let x = freshVar(c, it[j].sym)
c.transCon.mapping[it[j].sym.itemId] = x
defs[j] = x
else:
defs[j] = transform(c, it[j])
assert(it[^2].kind == nkEmpty)
defs[^2] = newNodeI(nkEmpty, it.info)
defs[^1] = transform(c, it[^1])
result[i] = defs
proc transformConstSection(c: PTransf, v: PNode): PNode =
result = v
when false:
result = newTransNode(v)
for i in 0..<v.len:
var it = v[i]
if it.kind == nkCommentStmt:
result[i] = it
else:
if it.kind != nkConstDef: internalError(c.graph.config, it.info, "transformConstSection")
if it[0].kind != nkSym:
debug it[0]
internalError(c.graph.config, it.info, "transformConstSection")
result[i] = it
proc hasContinue(n: PNode): bool =
case n.kind
of nkEmpty..nkNilLit, nkForStmt, nkParForStmt, nkWhileStmt: result = false
of nkContinueStmt: result = true
else:
result = false
for i in 0..<n.len:
if hasContinue(n[i]): return true
proc newLabel(c: PTransf, n: PNode): PSym =
result = newSym(skLabel, getIdent(c.graph.cache, genPrefix), c.idgen, getCurrOwner(c), n.info)
proc transformBlock(c: PTransf, n: PNode): PNode =
var labl: PSym
if c.inlining > 0:
labl = newLabel(c, n[0])
c.transCon.mapping[n[0].sym.itemId] = newSymNode(labl)
else:
labl =
if n[0].kind != nkEmpty:
n[0].sym # already named block? -> Push symbol on the stack
else:
newLabel(c, n)
c.breakSyms.add(labl)
result = transformSons(c, n)
discard c.breakSyms.pop
result[0] = newSymNode(labl)
proc transformLoopBody(c: PTransf, n: PNode): PNode =
# What if it contains "continue" and "break"? "break" needs
# an explicit label too, but not the same!
# We fix this here by making every 'break' belong to its enclosing loop
# and changing all breaks that belong to a 'block' by annotating it with
# a label (if it hasn't one already).
if hasContinue(n):
let labl = newLabel(c, n)
c.contSyms.add(labl)
result = newTransNode(nkBlockStmt, n.info, 2)
result[0] = newSymNode(labl)
result[1] = transform(c, n)
discard c.contSyms.pop()
else:
result = transform(c, n)
proc transformWhile(c: PTransf; n: PNode): PNode =
if c.inlining > 0:
result = transformSons(c, n)
else:
let labl = newLabel(c, n)
c.breakSyms.add(labl)
result = newTransNode(nkBlockStmt, n.info, 2)
result[0] = newSymNode(labl)
var body = newTransNode(n)
for i in 0..<n.len-1:
body[i] = transform(c, n[i])
body[^1] = transformLoopBody(c, n[^1])
result[1] = body
discard c.breakSyms.pop
proc transformBreak(c: PTransf, n: PNode): PNode =
result = transformSons(c, n)
if n[0].kind == nkEmpty and c.breakSyms.len > 0:
let labl = c.breakSyms[c.breakSyms.high]
result[0] = newSymNode(labl)
proc introduceNewLocalVars(c: PTransf, n: PNode): PNode =
case n.kind
of nkSym:
result = transformSym(c, n)
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit:
# nothing to be done for leaves:
result = n
of nkVarSection, nkLetSection:
result = transformVarSection(c, n)
of nkClosure:
# it can happen that for-loop-inlining produced a fresh
# set of variables, including some computed environment
# (bug #2604). We need to patch this environment here too:
let a = n[1]
if a.kind == nkSym:
n[1] = transformSymAux(c, a)
return n
of nkProcDef: # todo optimize nosideeffects?
result = newTransNode(n)
let x = newSymNode(copySym(n[namePos].sym, c.idgen))
c.transCon.mapping[n[namePos].sym.itemId] = x
result[namePos] = x # we have to copy proc definitions for iters
for i in 1..<n.len:
result[i] = introduceNewLocalVars(c, n[i])
result[namePos].sym.ast = result
else:
result = newTransNode(n)
for i in 0..<n.len:
result[i] = introduceNewLocalVars(c, n[i])
proc transformAsgn(c: PTransf, n: PNode): PNode =
let rhs = n[1]
if rhs.kind != nkTupleConstr:
return transformSons(c, n)
# Unpack the tuple assignment into N temporary variables and then pack them
# into a tuple: this allows us to get the correct results even when the rhs
# depends on the value of the lhs
let letSection = newTransNode(nkLetSection, n.info, rhs.len)
let newTupleConstr = newTransNode(nkTupleConstr, n.info, rhs.len)
for i, field in rhs:
let val = if field.kind == nkExprColonExpr: field[1] else: field
let def = newTransNode(nkIdentDefs, field.info, 3)
def[0] = newTemp(c, val.typ, field.info)
def[1] = newNodeI(nkEmpty, field.info)
def[2] = transform(c, val)
letSection[i] = def
# NOTE: We assume the constructor fields are in the correct order for the
# given tuple type
newTupleConstr[i] = def[0]
newTupleConstr.typ() = rhs.typ
let asgnNode = newTransNode(nkAsgn, n.info, 2)
asgnNode[0] = transform(c, n[0])
asgnNode[1] = newTupleConstr
result = newTransNode(nkStmtList, n.info, 2)
result[0] = letSection
result[1] = asgnNode
template assignTupleUnpacking(c: PTransf, e: PNode) =
for i in 0..<c.transCon.forStmt.len - 2:
if c.transCon.forStmt[i].kind == nkVarTuple:
for j in 0..<c.transCon.forStmt[i].len-1:
let lhs = c.transCon.forStmt[i][j]
let rhs = transform(c, newTupleAccess(c.graph, newTupleAccess(c.graph, e, i), j))
result.add(asgnTo(lhs, rhs))
else:
let lhs = c.transCon.forStmt[i]
let rhs = transform(c, newTupleAccess(c.graph, e, i))
result.add(asgnTo(lhs, rhs))
proc transformYield(c: PTransf, n: PNode): PNode =
proc asgnTo(lhs: PNode, rhs: PNode): PNode =
# Choose the right assignment instruction according to the given ``lhs``
# node since it may not be a nkSym (a stack-allocated skForVar) but a
# nkDotExpr (a heap-allocated slot into the envP block)
case lhs.kind
of nkSym:
internalAssert c.graph.config, lhs.sym.kind == skForVar
result = newAsgnStmt(c, nkFastAsgn, lhs, rhs, false)
of nkDotExpr:
result = newAsgnStmt(c, nkAsgn, lhs, rhs, false)
else:
result = nil
internalAssert c.graph.config, false
result = newTransNode(nkStmtList, n.info, 0)
var e = n[0]
# c.transCon.forStmt.len == 3 means that there is one for loop variable
# and thus no tuple unpacking:
if e.typ.isNil: return result # can happen in nimsuggest for unknown reasons
if c.transCon.forStmt.len != 3:
e = skipConv(e)
if e.kind == nkTupleConstr:
for i in 0..<e.len:
var v = e[i]
if v.kind == nkExprColonExpr: v = v[1]
if c.transCon.forStmt[i].kind == nkVarTuple:
for j in 0..<c.transCon.forStmt[i].len-1:
let lhs = c.transCon.forStmt[i][j]
let rhs = transform(c, newTupleAccess(c.graph, v, j))
result.add(asgnTo(lhs, rhs))
else:
let lhs = c.transCon.forStmt[i]
let rhs = transform(c, v)
result.add(asgnTo(lhs, rhs))
elif e.kind notin {nkAddr, nkHiddenAddr} and e.kind != nkSym:
# no need to generate temp for address operation + nodes without sideeffects
# TODO do not use temp for nodes which cannot have side-effects
var tmp = newTemp(c, e.typ, e.info)
let v = newNodeI(nkVarSection, e.info)
v.addVar(tmp, e)
result.add transform(c, v)
assignTupleUnpacking(c, tmp)
else:
assignTupleUnpacking(c, e)
else:
if c.transCon.forStmt[0].kind == nkVarTuple:
var notLiteralTuple = false # we don't generate temp for tuples with const value: (1, 2, 3)
let ev = e.skipConv
if ev.kind == nkTupleConstr:
for i in ev:
if not isConstExpr(i):
notLiteralTuple = true
break
else:
notLiteralTuple = true
if e.kind notin {nkAddr, nkHiddenAddr} and notLiteralTuple and e.kind != nkSym:
# no need to generate temp for address operation + nodes without sideeffects
# TODO do not use temp for nodes which cannot have side-effects
var tmp = newTemp(c, e.typ, e.info)
let v = newNodeI(nkVarSection, e.info)
v.addVar(tmp, e)
result.add transform(c, v)
for i in 0..<c.transCon.forStmt[0].len-1:
let lhs = c.transCon.forStmt[0][i]
let rhs = transform(c, newTupleAccess(c.graph, tmp, i))
result.add(asgnTo(lhs, rhs))
else:
for i in 0..<c.transCon.forStmt[0].len-1:
let lhs = c.transCon.forStmt[0][i]
let rhs = transform(c, newTupleAccess(c.graph, e, i))
result.add(asgnTo(lhs, rhs))
else:
let lhs = c.transCon.forStmt[0]
let rhs = transform(c, e)
result.add(asgnTo(lhs, rhs))
# bug #23536; note that the info of forLoopBody should't change
for idx in 0 ..< result.len:
var changeNode = result[idx]
changeNode.info = c.transCon.forStmt.info
for i, child in changeNode:
child.info = changeNode.info
inc(c.transCon.yieldStmts)
if c.transCon.yieldStmts <= 1:
# common case
result.add(c.transCon.forLoopBody)
else:
# we need to introduce new local variables:
c.isIntroducingNewLocalVars = true # don't transform yields when introducing new local vars
result.add(introduceNewLocalVars(c, c.transCon.forLoopBody))
c.isIntroducingNewLocalVars = false
proc transformAddrDeref(c: PTransf, n: PNode, kinds: TNodeKinds, isAddr = false): PNode =
result = transformSons(c, n, noConstFold = isAddr)
# inlining of 'var openarray' iterators; bug #19977
if n.typ.kind != tyOpenArray and (c.graph.config.backend == backendCpp or sfCompileToCpp in c.module.flags): return
var n = result
case n[0].kind
of nkObjUpConv, nkObjDownConv, nkChckRange, nkChckRangeF, nkChckRange64:
var m = n[0][0]
if m.kind in kinds:
# addr ( nkConv ( deref ( x ) ) ) --> nkConv(x)
n[0][0] = m[0]
result = n[0]
if n.typ.skipTypes(abstractVar).kind != tyOpenArray:
result.typ() = n.typ
elif n.typ.skipTypes(abstractInst).kind in {tyVar}:
result.typ() = toVar(result.typ, n.typ.skipTypes(abstractInst).kind, c.idgen)
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
var m = n[0][1]
if m.kind in kinds:
# addr ( nkConv ( deref ( x ) ) ) --> nkConv(x)
n[0][1] = m[0]
result = n[0]
if n.typ.skipTypes(abstractVar).kind != tyOpenArray:
result.typ() = n.typ
elif n.typ.skipTypes(abstractInst).kind in {tyVar}:
result.typ() = toVar(result.typ, n.typ.skipTypes(abstractInst).kind, c.idgen)
else:
if n[0].kind in kinds and
not (n[0][0].kind == nkSym and n[0][0].sym.kind == skForVar and
n[0][0].typ.skipTypes(abstractVar).kind == tyTuple
) and not (n[0][0].kind == nkSym and n[0][0].sym.kind == skParam and
n.typ.kind == tyVar and
n.typ.skipTypes(abstractVar).kind == tyOpenArray and
n[0][0].typ.skipTypes(abstractVar).kind == tyString) and
not (isAddr and n.typ.kind == tyVar and n[0][0].typ.kind == tyRef and
n[0][0].kind == nkObjConstr)
: # elimination is harmful to `for tuple unpack` because of newTupleAccess
# it is also harmful to openArrayLoc (var openArray) for strings
# addr ( deref ( x )) --> x
result = n[0][0]
if n.typ.skipTypes(abstractVar).kind != tyOpenArray:
result.typ() = n.typ
proc generateThunk(c: PTransf; prc: PNode, dest: PType): PNode =
## Converts 'prc' into '(thunk, nil)' so that it's compatible with
## a closure.
# we cannot generate a proper thunk here for GC-safety reasons
# (see internal documentation):
if jsNoLambdaLifting in c.graph.config.legacyFeatures and c.graph.config.backend == backendJs: return prc
result = newNodeIT(nkClosure, prc.info, dest)
var conv = newNodeIT(nkHiddenSubConv, prc.info, dest)
conv.add(newNodeI(nkEmpty, prc.info))
conv.add(prc)
if prc.kind == nkClosure:
internalError(c.graph.config, prc.info, "closure to closure created")
result.add(conv)
result.add(newNodeIT(nkNilLit, prc.info, getSysType(c.graph, prc.info, tyNil)))
proc transformConv(c: PTransf, n: PNode): PNode =
# numeric types need range checks:
var dest = skipTypes(n.typ, abstractVarRange)
var source = skipTypes(n[1].typ, abstractVarRange)
case dest.kind
of tyInt..tyInt64, tyEnum, tyChar, tyUInt8..tyUInt32:
# we don't include uint and uint64 here as these are no ordinal types ;-)
if not isOrdinalType(source):
# float -> int conversions. ugh.
# generate a range check:
if dest.kind in tyInt..tyInt64:
if dest.kind == tyInt64 or source.kind == tyInt64:
result = newTransNode(nkChckRange64, n, 3)
else:
result = newTransNode(nkChckRange, n, 3)
dest = skipTypes(n.typ, abstractVar)
if dest.size < source.size:
let intType =
if source.size == 4:
getSysType(c.graph, n.info, tyInt32)
else:
getSysType(c.graph, n.info, tyInt64)
result[0] =
newTreeIT(n.kind, n.info, n.typ, n[0],
newTreeIT(nkConv, n.info, intType,
newNodeIT(nkType, n.info, intType), transform(c, n[1]))
)
else:
result[0] = transformSons(c, n)
result[1] = newIntTypeNode(firstOrd(c.graph.config, dest), dest)
result[2] = newIntTypeNode(lastOrd(c.graph.config, dest), dest)
else:
result = transformSons(c, n)
elif firstOrd(c.graph.config, n.typ) <= firstOrd(c.graph.config, n[1].typ) and
lastOrd(c.graph.config, n[1].typ) <= lastOrd(c.graph.config, n.typ):
# BUGFIX: simply leave n as it is; we need a nkConv node,
# but no range check:
result = transformSons(c, n)
else:
# generate a range check:
if dest.kind == tyInt64 or source.kind == tyInt64:
result = newTransNode(nkChckRange64, n, 3)
else:
result = newTransNode(nkChckRange, n, 3)
dest = skipTypes(n.typ, abstractVar)
result[0] = transform(c, n[1])
result[1] = newIntTypeNode(firstOrd(c.graph.config, dest), dest)
result[2] = newIntTypeNode(lastOrd(c.graph.config, dest), dest)
of tyFloat..tyFloat128:
# XXX int64 -> float conversion?
if skipTypes(n.typ, abstractVar).kind == tyRange:
result = newTransNode(nkChckRangeF, n, 3)
dest = skipTypes(n.typ, abstractVar)
result[0] = transform(c, n[1])
result[1] = copyTree(dest.n[0])
result[2] = copyTree(dest.n[1])
else:
result = transformSons(c, n)
of tyOpenArray, tyVarargs:
if keepOpenArrayConversions in c.flags:
result = transformSons(c, n)
else:
result = transform(c, n[1])
#result = transformSons(c, n)
result.typ() = takeType(n.typ, n[1].typ, c.graph, c.idgen)
#echo n.info, " came here and produced ", typeToString(result.typ),
# " from ", typeToString(n.typ), " and ", typeToString(n[1].typ)
of tyCstring:
if source.kind == tyString:
result = newTransNode(nkStringToCString, n, 1)
result[0] = transform(c, n[1])
else:
result = transformSons(c, n)
of tyString:
if source.kind == tyCstring:
result = newTransNode(nkCStringToString, n, 1)
result[0] = transform(c, n[1])
else:
result = transformSons(c, n)
of tyRef, tyPtr:
dest = skipTypes(dest, abstractPtrs)
source = skipTypes(source, abstractPtrs)
if source.kind == tyObject:
var diff = inheritanceDiff(dest, source)
if diff < 0:
result = newTransNode(nkObjUpConv, n, 1)
result[0] = transform(c, n[1])
elif diff > 0 and diff != high(int):
result = newTransNode(nkObjDownConv, n, 1)
result[0] = transform(c, n[1])
else:
result = transform(c, n[1])
result.typ() = n.typ
else:
result = transformSons(c, n)
of tyObject:
var diff = inheritanceDiff(dest, source)
if diff < 0:
result = newTransNode(nkObjUpConv, n, 1)
result[0] = transform(c, n[1])
elif diff > 0 and diff != high(int):
result = newTransNode(nkObjDownConv, n, 1)
result[0] = transform(c, n[1])
else:
result = transform(c, n[1])
result.typ() = n.typ
of tyGenericParam, tyOrdinal:
result = transform(c, n[1])
# happens sometimes for generated assignments, etc.
of tyProc:
result = transformSons(c, n)
if dest.callConv == ccClosure and source.callConv == ccNimCall:
result = generateThunk(c, result[1], dest)
else:
result = transformSons(c, n)
type
TPutArgInto = enum
paDirectMapping, paFastAsgn, paFastAsgnTakeTypeFromArg
paVarAsgn, paComplexOpenarray, paViaIndirection
proc putArgInto(arg: PNode, formal: PType): TPutArgInto =
# This analyses how to treat the mapping "formal <-> arg" in an
# inline context.
if formal.kind == tyTypeDesc: return paDirectMapping
if skipTypes(formal, abstractInst).kind in {tyOpenArray, tyVarargs}:
case arg.kind
of nkStmtListExpr:
return paComplexOpenarray
of nkCall:
if skipTypes(arg.typ, abstractInst).kind in {tyOpenArray, tyVarargs}:
# XXX incorrect, causes #13417 when `arg` has side effects.
return paDirectMapping
else:
return paComplexOpenarray
of nkBracket:
return paFastAsgnTakeTypeFromArg
else:
# XXX incorrect, causes #13417 when `arg` has side effects.
return paDirectMapping
case arg.kind
of nkEmpty..nkNilLit:
result = paDirectMapping
of nkDotExpr, nkDerefExpr, nkHiddenDeref:
result = putArgInto(arg[0], formal)
of nkAddr, nkHiddenAddr:
result = putArgInto(arg[0], formal)
if result == paViaIndirection: result = paFastAsgn
of nkCurly, nkBracket:
for i in 0..<arg.len:
if putArgInto(arg[i], formal) != paDirectMapping:
return paFastAsgn
result = paDirectMapping
of nkPar, nkTupleConstr, nkObjConstr:
for i in 0..<arg.len:
let a = if arg[i].kind == nkExprColonExpr: arg[i][1]
else: arg[0]
if putArgInto(a, formal) != paDirectMapping:
return paFastAsgn
result = paDirectMapping
of nkBracketExpr:
if skipTypes(formal, abstractInst).kind in {tyVar, tyLent}: result = paVarAsgn
else: result = paViaIndirection
else:
if skipTypes(formal, abstractInst).kind in {tyVar, tyLent}: result = paVarAsgn
else: result = paFastAsgn
proc findWrongOwners(c: PTransf, n: PNode) =
if n.kind == nkVarSection:
let x = n[0][0]
if x.kind == nkSym and x.sym.owner != getCurrOwner(c):
internalError(c.graph.config, x.info, "bah " & x.sym.name.s & " " &
x.sym.owner.name.s & " " & getCurrOwner(c).name.s)
else:
for i in 0..<n.safeLen: findWrongOwners(c, n[i])
proc isSimpleIteratorVar(c: PTransf; iter: PSym; call: PNode; owner: PSym): bool =
proc rec(n: PNode; owner: PSym; dangerousYields: var int) =
case n.kind
of nkEmpty..nkNilLit: discard
of nkYieldStmt:
if n[0].kind == nkSym and n[0].sym.owner == owner:
discard "good: yield a single variable that we own"
else:
inc dangerousYields
else:
for c in n: rec(c, owner, dangerousYields)
proc recSym(n: PNode; owner: PSym; sameOwner: var bool) =
case n.kind
of {nkEmpty..nkNilLit} - {nkSym}: discard
of nkSym:
if n.sym.owner != owner:
sameOwner = false
else:
for c in n: recSym(c, owner, sameOwner)
var dangerousYields = 0
rec(getBody(c.graph, iter), iter, dangerousYields)
result = dangerousYields == 0
# the parameters should be owned by the owner
# bug #22237
for i in 1..<call.len:
recSym(call[i], owner, result)
template destructor(t: PType): PSym = getAttachedOp(c.graph, t, attachedDestructor)
proc transformFor(c: PTransf, n: PNode): PNode =
# generate access statements for the parameters (unless they are constant)
# put mapping from formal parameters to actual parameters
if n.kind != nkForStmt: internalError(c.graph.config, n.info, "transformFor")
var call = n[^2]
let labl = newLabel(c, n)
result = newTransNode(nkBlockStmt, n.info, 2)
result[0] = newSymNode(labl)
if call.typ.isNil:
# see bug #3051
result[1] = newNode(nkEmpty)
return result
c.breakSyms.add(labl)
if call.kind notin nkCallKinds or call[0].kind != nkSym or
call[0].typ.skipTypes(abstractInst).callConv == ccClosure:
result[1] = n
result[1][^1] = transformLoopBody(c, n[^1])
result[1][^2] = transform(c, n[^2])
result[1] = lambdalifting.liftForLoop(c.graph, result[1], c.idgen, getCurrOwner(c))
discard c.breakSyms.pop
return result
#echo "transforming: ", renderTree(n)
var stmtList = newTransNode(nkStmtList, n.info, 0)
result[1] = stmtList
var loopBody = transformLoopBody(c, n[^1])
discard c.breakSyms.pop
let iter = call[0].sym
var v = newNodeI(nkVarSection, n.info)
for i in 0..<n.len - 2:
if n[i].kind == nkVarTuple:
for j in 0..<n[i].len-1:
addVar(v, copyTree(n[i][j])) # declare new vars
else:
if n[i].kind == nkSym and isSimpleIteratorVar(c, iter, call, n[i].sym.owner):
incl n[i].sym.flags, sfCursor
addVar(v, copyTree(n[i])) # declare new vars
stmtList.add(v)
# Bugfix: inlined locals belong to the invoking routine, not to the invoked
# iterator!
var newC = newTransCon(getCurrOwner(c))
newC.forStmt = n
newC.forLoopBody = loopBody
# this can fail for 'nimsuggest' and 'check':
if iter.kind != skIterator: return result
# generate access statements for the parameters (unless they are constant)
pushTransCon(c, newC)
for i in 1..<call.len:
var arg = transform(c, call[i])
let ff = skipTypes(iter.typ, abstractInst)
# can happen for 'nim check':
if i >= ff.n.len: return result
var formal = ff.n[i].sym
let pa = putArgInto(arg, formal.typ)
case pa
of paDirectMapping:
newC.mapping[formal.itemId] = arg
of paFastAsgn, paFastAsgnTakeTypeFromArg:
var t = formal.typ
if pa == paFastAsgnTakeTypeFromArg:
t = arg.typ
elif formal.ast != nil and formal.ast.typ.destructor != nil and t.destructor == nil:
t = formal.ast.typ # better use the type that actually has a destructor.
elif t.destructor == nil and arg.typ.destructor != nil:
t = arg.typ
# generate a temporary and produce an assignment statement:
var temp = newTemp(c, t, formal.info)
#incl(temp.sym.flags, sfCursor)
addVar(v, temp)
stmtList.add(newAsgnStmt(c, nkFastAsgn, temp, arg, true))
newC.mapping[formal.itemId] = temp
of paVarAsgn:
assert(skipTypes(formal.typ, abstractInst).kind in {tyVar, tyLent})
newC.mapping[formal.itemId] = arg
# XXX BUG still not correct if the arg has a side effect!
of paViaIndirection:
let t = formal.typ
let vt = makeVarType(t.owner, t, c.idgen)
vt.flags.incl tfVarIsPtr
var temp = newTemp(c, vt, formal.info)
addVar(v, temp)
var addrExp = newNodeIT(nkHiddenAddr, formal.info, makeVarType(t.owner, t, c.idgen, tyPtr))
addrExp.add(arg)
stmtList.add(newAsgnStmt(c, nkFastAsgn, temp, addrExp, true))
newC.mapping[formal.itemId] = newDeref(temp)
of paComplexOpenarray:
# XXX arrays will deep copy here (pretty bad).
var temp = newTemp(c, arg.typ, formal.info)
addVar(v, temp)
stmtList.add(newAsgnStmt(c, nkFastAsgn, temp, arg, true))
newC.mapping[formal.itemId] = temp
let body = transformBody(c.graph, c.idgen, iter, {useCache}+c.flags)
pushInfoContext(c.graph.config, n.info)
inc(c.inlining)
stmtList.add(transform(c, body))
#findWrongOwners(c, stmtList.PNode)
dec(c.inlining)
popInfoContext(c.graph.config)
popTransCon(c)
# echo "transformed: ", stmtList.renderTree
proc transformCase(c: PTransf, n: PNode): PNode =
# removes `elif` branches of a case stmt
# adds ``else: nil`` if needed for the code generator
result = newTransNode(nkCaseStmt, n, 0)
var ifs: PNode = nil
for it in n:
var e = transform(c, it)
case it.kind
of nkElifBranch:
if ifs == nil:
# Generate the right node depending on whether `n` is used as a stmt or
# as an expr
let kind = if n.typ != nil: nkIfExpr else: nkIfStmt
ifs = newTransNode(kind, it.info, 0)
ifs.typ() = n.typ
ifs.add(e)
of nkElse:
if ifs == nil: result.add(e)
else: ifs.add(e)
else:
result.add(e)
if ifs != nil:
var elseBranch = newTransNode(nkElse, n.info, 1)
elseBranch[0] = ifs
result.add(elseBranch)
elif result.lastSon.kind != nkElse and not (
skipTypes(n[0].typ, abstractVarRange).kind in
{tyInt..tyInt64, tyChar, tyEnum, tyUInt..tyUInt64}):
# fix a stupid code gen bug by normalizing:
var elseBranch = newTransNode(nkElse, n.info, 1)
elseBranch[0] = newTransNode(nkNilLit, n.info, 0)
result.add(elseBranch)
proc transformArrayAccess(c: PTransf, n: PNode): PNode =
# XXX this is really bad; transf should use a proper AST visitor
if n[0].kind == nkSym and n[0].sym.kind == skType:
result = n
else:
result = transformSons(c, n)
if n.len >= 2 and result[1].kind in {nkChckRange, nkChckRange64} and
n[1].kind in {nkHiddenStdConv, nkHiddenSubConv}:
# implicit conversion, was transformed into range check
# remove in favor of index check if conversion to array index type
# has to be done here because the array index type needs to be relaxed
# i.e. a uint32 index can implicitly convert to range[0..3] but not int
let arr = skipTypes(n[0].typ, abstractVarRange)
if arr.kind == tyArray and
firstOrd(c.graph.config, arr) == getOrdValue(result[1][1]) and
lastOrd(c.graph.config, arr) == getOrdValue(result[1][2]):
result[1] = result[1].skipConv
proc getMergeOp(n: PNode): PSym =
case n.kind
of nkCall, nkHiddenCallConv, nkCommand, nkInfix, nkPrefix, nkPostfix,
nkCallStrLit:
if n[0].kind == nkSym and n[0].sym.magic == mConStrStr:
result = n[0].sym
else:
result = nil
else: result = nil
proc flattenTreeAux(d, a: PNode, op: PSym) =
## Optimizes away the `&` calls in the children nodes and
## lifts the leaf nodes to the same level as `op2`.
let op2 = getMergeOp(a)
if op2 != nil and
(op2.id == op.id or op.magic != mNone and op2.magic == op.magic):
for i in 1..<a.len: flattenTreeAux(d, a[i], op)
else:
d.add copyTree(a)
proc flattenTree(root: PNode): PNode =
let op = getMergeOp(root)
if op != nil:
result = copyNode(root)
result.add copyTree(root[0])
flattenTreeAux(result, root, op)
else:
result = root
proc transformCall(c: PTransf, n: PNode): PNode =
var n = flattenTree(n)
let op = getMergeOp(n)
let magic = getMagic(n)
if op != nil and op.magic != mNone and n.len >= 3:
result = newTransNode(nkCall, n, 0)
result.add(transform(c, n[0]))
var j = 1
while j < n.len:
var a = transform(c, n[j])
inc(j)
if isConstExpr(a):
while (j < n.len):
let b = transform(c, n[j])
if not isConstExpr(b): break
a = evalOp(op.magic, n, a, b, nil, c.idgen, c.graph)
inc(j)
result.add(a)
if result.len == 2: result = result[1]
elif magic in {mNBindSym, mTypeOf, mRunnableExamples}:
# for bindSym(myconst) we MUST NOT perform constant folding:
result = n
elif magic == mProcCall:
# but do not change to its dispatcher:
result = transformSons(c, n[1])
elif magic == mStrToStr:
result = transform(c, n[1])
else:
let s = transformSons(c, n)
# bugfix: check after 'transformSons' if it's still a method call:
# use the dispatcher for the call:
if s[0].kind == nkSym and s[0].sym.kind == skMethod:
when false:
let t = lastSon(s[0].sym.ast)
if t.kind != nkSym or sfDispatcher notin t.sym.flags:
methodDef(s[0].sym, false)
result = methodCall(s, c.graph.config)
else:
result = s
proc transformBareExcept(c: PTransf, n: PNode): PNode =
result = newTransNode(nkExceptBranch, n, 1)
if isEmptyType(n[0].typ):
result[0] = newNodeI(nkStmtList, n[0].info)
else:
result[0] = newNodeIT(nkStmtListExpr, n[0].info, n[0].typ)
# Generating `raiseDefect()`
let raiseDefectCall = callCodegenProc(c.graph, "raiseDefect", n[0].info)
result[0].add raiseDefectCall
if n[0].kind in {nkStmtList, nkStmtListExpr}:
# flattens stmtList
for son in n[0]:
result[0].add son
else:
result[0].add n[0]
result[0] = transform(c, result[0])
proc transformExceptBranch(c: PTransf, n: PNode): PNode =
if n[0].isInfixAs() and not isImportedException(n[0][1].typ, c.graph.config):
let excTypeNode = n[0][1]
let actions = newTransNode(nkStmtListExpr, n[1], 2)
# Generating `let exc = (excType)(getCurrentException())`
# -> getCurrentException()
let excCall = callCodegenProc(c.graph, "getCurrentException")
# -> (excType)
let convNode = newTransNode(nkHiddenSubConv, n[1].info, 2)
convNode[0] = newNodeI(nkEmpty, n.info)
convNode[1] = excCall
convNode.typ() = excTypeNode.typ.toRef(c.idgen)
# -> let exc = ...
let identDefs = newTransNode(nkIdentDefs, n[1].info, 3)
identDefs[0] = n[0][2]
identDefs[1] = newNodeI(nkEmpty, n.info)
identDefs[2] = convNode
let letSection = newTransNode(nkLetSection, n[1].info, 1)
letSection[0] = identDefs
# Place the let statement and body of the 'except' branch into new stmtList.
actions[0] = letSection
actions[1] = transform(c, n[1])
# Overwrite 'except' branch body with our stmtList.
result = newTransNode(nkExceptBranch, n[1].info, 2)
# Replace the `Exception as foobar` with just `Exception`.
result[0] = transform(c, n[0][1])
result[1] = actions
elif n.len == 1 and
noPanicOnExcept notin c.graph.config.legacyFeatures:
result = transformBareExcept(c, n)
else:
result = transformSons(c, n)
proc commonOptimizations*(g: ModuleGraph; idgen: IdGenerator; c: PSym, n: PNode): PNode =
## Merges adjacent constant expressions of the children of the `&` call into
## a single constant expression. It also inlines constant expressions which are not
## complex.
result = n
for i in 0..<n.safeLen:
result[i] = commonOptimizations(g, idgen, c, n[i])
var op = getMergeOp(n)
if (op != nil) and (op.magic != mNone) and (n.len >= 3):
result = newNodeIT(nkCall, n.info, n.typ)
result.add(n[0])
var args = newNode(nkArgList)
flattenTreeAux(args, n, op)
var j = 0
while j < args.len:
var a = args[j]
inc(j)
if isConstExpr(a):
while j < args.len:
let b = args[j]
if not isConstExpr(b): break
a = evalOp(op.magic, result, a, b, nil, idgen, g)
inc(j)
result.add(a)
if result.len == 2: result = result[1]
else:
var cnst = getConstExpr(c, n, idgen, g)
# we inline constants if they are not complex constants:
if cnst != nil and not dontInlineConstant(n, cnst):
result = cnst
else:
result = n
proc transformDerefBlock(c: PTransf, n: PNode): PNode =
# We transform (block: x)[] to (block: x[])
let e0 = n[0]
result = shallowCopy(e0)
result.typ() = n.typ
for i in 0 ..< e0.len - 1:
result[i] = e0[i]
result[e0.len-1] = newTreeIT(nkHiddenDeref, n.info, n.typ, e0[e0.len-1])
proc transform(c: PTransf, n: PNode, noConstFold = false): PNode =
when false:
var oldDeferAnchor: PNode
if n.kind in {nkElifBranch, nkOfBranch, nkExceptBranch, nkElifExpr,
nkElseExpr, nkElse, nkForStmt, nkWhileStmt, nkFinally,
nkBlockStmt, nkBlockExpr}:
oldDeferAnchor = c.deferAnchor
c.deferAnchor = n
case n.kind
of nkSym:
result = transformSym(c, n)
of nkEmpty..pred(nkSym), succ(nkSym)..nkNilLit, nkComesFrom:
# nothing to be done for leaves:
result = n
of nkBracketExpr: result = transformArrayAccess(c, n)
of procDefs:
var s = n[namePos].sym
if n.typ != nil and s.typ.callConv == ccClosure:
result = transformSym(c, n[namePos])
# use the same node as before if still a symbol:
if result.kind == nkSym: result = n
else:
result = n
of nkMacroDef:
# XXX no proper closure support yet:
when false:
if n[genericParamsPos].kind == nkEmpty:
var s = n[namePos].sym
n[bodyPos] = transform(c, s.getBody)
if n.kind == nkMethodDef: methodDef(s, false)
result = n
of nkForStmt:
result = transformFor(c, n)
of nkParForStmt:
result = transformSons(c, n)
of nkCaseStmt:
result = transformCase(c, n)
of nkWhileStmt: result = transformWhile(c, n)
of nkBlockStmt, nkBlockExpr:
result = transformBlock(c, n)
of nkDefer:
c.deferDetected = true
result = transformSons(c, n)
when false:
let deferPart = newNodeI(nkFinally, n.info)
deferPart.add n[0]
let tryStmt = newNodeI(nkTryStmt, n.info)
if c.deferAnchor.isNil:
tryStmt.add c.root
c.root = tryStmt
result = tryStmt
else:
# modify the corresponding *action*, don't rely on nkStmtList:
tryStmt.add c.deferAnchor[^1]
c.deferAnchor[^1] = tryStmt
result = newTransNode(nkCommentStmt, n.info, 0)
tryStmt.add deferPart
# disable the original 'defer' statement:
n.kind = nkEmpty
of nkContinueStmt:
result = newNodeI(nkBreakStmt, n.info)
var labl = c.contSyms[c.contSyms.high]
result.add(newSymNode(labl))
of nkBreakStmt: result = transformBreak(c, n)
of nkCallKinds:
result = transformCall(c, n)
of nkAddr, nkHiddenAddr:
result = transformAddrDeref(c, n, {nkDerefExpr, nkHiddenDeref}, isAddr = true)
of nkDerefExpr:
result = transformAddrDeref(c, n, {nkAddr, nkHiddenAddr})
of nkHiddenDeref:
if n[0].kind in {nkBlockExpr, nkBlockStmt}:
# bug #20107 bug #21540. Watch out to not deref the pointer too late.
let e = transformDerefBlock(c, n)
result = transformBlock(c, e)
else:
result = transformAddrDeref(c, n, {nkAddr, nkHiddenAddr})
of nkHiddenStdConv, nkHiddenSubConv, nkConv:
result = transformConv(c, n)
of nkObjConstr, nkCast:
# don't try to transform type node
result = transformSonsAfterType(c, n)
of nkDiscardStmt:
result = n
if n[0].kind != nkEmpty:
result = transformSons(c, n)
if isConstExpr(result[0]):
# ensure that e.g. discard "some comment" gets optimized away
# completely:
result = newNode(nkCommentStmt)
of nkCommentStmt, nkTemplateDef, nkImportStmt, nkStaticStmt,
nkExportStmt, nkExportExceptStmt:
return n
of nkConstSection:
# do not replace ``const c = 3`` with ``const 3 = 3``
return transformConstSection(c, n)
of nkTypeSection, nkTypeOfExpr, nkMixinStmt, nkBindStmt:
# no need to transform type sections:
return n
of nkVarSection, nkLetSection:
if c.inlining > 0:
# we need to copy the variables for multiple yield statements:
result = transformVarSection(c, n)
else:
result = transformSons(c, n)
of nkYieldStmt:
if c.inlining > 0 and not c.isIntroducingNewLocalVars:
result = transformYield(c, n)
else:
result = transformSons(c, n)
of nkAsgn:
result = transformAsgn(c, n)
of nkIdentDefs, nkConstDef:
result = newTransNode(n)
result[0] = transform(c, skipPragmaExpr(n[0]))
# Skip the second son since it only contains an unsemanticized copy of the
# variable type used by docgen
let last = n.len-1
for i in 1..<last: result[i] = n[i]
result[last] = transform(c, n[last])
# XXX comment handling really sucks:
if importantComments(c.graph.config):
result.comment = n.comment
of nkClosure:
# it can happen that for-loop-inlining produced a fresh
# set of variables, including some computed environment
# (bug #2604). We need to patch this environment here too:
let a = n[1]
if a.kind == nkSym:
result = copyTree(n)
result[1] = transformSymAux(c, a)
else:
result = n
of nkExceptBranch:
result = transformExceptBranch(c, n)
of nkCheckedFieldExpr:
result = transformSons(c, n)
if result[0].kind != nkDotExpr:
# simplfied beyond a dot expression --> simplify further.
result = result[0]
else:
result = transformSons(c, n)
when false:
if oldDeferAnchor != nil: c.deferAnchor = oldDeferAnchor
# Constants can be inlined here, but only if they cannot result in a cast
# in the back-end (e.g. var p: pointer = someProc)
let exprIsPointerCast = n.kind in {nkCast, nkConv, nkHiddenStdConv} and
n.typ != nil and
n.typ.kind == tyPointer
if not exprIsPointerCast and not noConstFold:
var cnst = getConstExpr(c.module, result, c.idgen, c.graph)
# we inline constants if they are not complex constants:
if cnst != nil and not dontInlineConstant(n, cnst):
result = cnst # do not miss an optimization
proc processTransf(c: PTransf, n: PNode, owner: PSym): PNode =
# Note: For interactive mode we cannot call 'passes.skipCodegen' and skip
# this step! We have to rely that the semantic pass transforms too errornous
# nodes into an empty node.
if nfTransf in n.flags: return n
pushTransCon(c, newTransCon(owner))
result = transform(c, n)
popTransCon(c)
incl(result.flags, nfTransf)
proc openTransf(g: ModuleGraph; module: PSym, filename: string; idgen: IdGenerator; flags: TransformFlags): PTransf =
result = PTransf(module: module, graph: g, idgen: idgen, flags: flags)
proc flattenStmts(n: PNode) =
var goOn = true
while goOn:
goOn = false
var i = 0
while i < n.len:
let it = n[i]
if it.kind in {nkStmtList, nkStmtListExpr}:
n.sons[i..i] = it.sons[0..<it.len]
goOn = true
inc i
proc liftDeferAux(n: PNode) =
if n.kind in {nkStmtList, nkStmtListExpr}:
flattenStmts(n)
var goOn = true
while goOn:
goOn = false
let last = n.len-1
for i in 0..last:
if n[i].kind == nkDefer:
let deferPart = newNodeI(nkFinally, n[i].info)
deferPart.add n[i][0]
var tryStmt = newNodeIT(nkTryStmt, n[i].info, n.typ)
var body = newNodeIT(n.kind, n[i].info, n.typ)
if i < last:
body.sons = n.sons[(i+1)..last]
tryStmt.add body
tryStmt.add deferPart
n[i] = tryStmt
n.sons.setLen(i+1)
n.typ() = tryStmt.typ
goOn = true
break
for i in 0..n.safeLen-1:
liftDeferAux(n[i])
template liftDefer(c, root) =
if c.deferDetected:
liftDeferAux(root)
proc transformBody*(g: ModuleGraph; idgen: IdGenerator; prc: PSym; flags: TransformFlags): PNode =
assert prc.kind in routineKinds
if prc.transformedBody != nil:
result = prc.transformedBody
elif nfTransf in getBody(g, prc).flags or prc.kind in {skTemplate}:
result = getBody(g, prc)
else:
prc.transformedBody = newNode(nkEmpty) # protects from recursion
var c = openTransf(g, prc.getModule, "", idgen, flags)
result = liftLambdas(g, prc, getBody(g, prc), c.tooEarly, c.idgen, flags)
result = processTransf(c, result, prc)
liftDefer(c, result)
result = liftLocalsIfRequested(prc, result, g.cache, g.config, c.idgen)
if prc.isIterator:
result = g.transformClosureIterator(c.idgen, prc, result)
incl(result.flags, nfTransf)
if useCache in flags or prc.typ.callConv == ccInline:
# genProc for inline procs will be called multiple times from different modules,
# it is important to transform exactly once to get sym ids and locations right
prc.transformedBody = result
else:
prc.transformedBody = nil
# XXX Rodfile support for transformedBody!
#if prc.name.s == "main":
# echo "transformed into ", renderTree(result, {renderIds})
proc transformStmt*(g: ModuleGraph; idgen: IdGenerator; module: PSym, n: PNode; flags: TransformFlags = {}): PNode =
if nfTransf in n.flags:
result = n
else:
var c = openTransf(g, module, "", idgen, flags)
result = processTransf(c, n, module)
liftDefer(c, result)
#result = liftLambdasForTopLevel(module, result)
incl(result.flags, nfTransf)
proc transformExpr*(g: ModuleGraph; idgen: IdGenerator; module: PSym, n: PNode; flags: TransformFlags = {}): PNode =
if nfTransf in n.flags:
result = n
else:
var c = openTransf(g, module, "", idgen, flags)
result = processTransf(c, n, module)
liftDefer(c, result)
# expressions are not to be injected with destructor calls as that
# the list of top level statements needs to be collected before.
incl(result.flags, nfTransf)