Refactor injectdestructors (#12295)
One improvement over #devel is visible in the transformation of getEnv. With this approach we move to result whenever possible.
This commit is contained in:
parent
64d5e25821
commit
5f5879dc4c
7 changed files with 257 additions and 577 deletions
|
|
@ -291,10 +291,6 @@ type
|
||||||
const
|
const
|
||||||
sfNoInit* = sfMainModule # don't generate code to init the variable
|
sfNoInit* = sfMainModule # don't generate code to init the variable
|
||||||
|
|
||||||
sfCursor* = sfDispatcher
|
|
||||||
# local variable has been computed to be a "cursor".
|
|
||||||
# see cursors.nim for details about what that means.
|
|
||||||
|
|
||||||
sfAllUntyped* = sfVolatile # macro or template is immediately expanded \
|
sfAllUntyped* = sfVolatile # macro or template is immediately expanded \
|
||||||
# in a generic context
|
# in a generic context
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,72 +0,0 @@
|
||||||
#
|
|
||||||
#
|
|
||||||
# The Nim Compiler
|
|
||||||
# (c) Copyright 2019 Andreas Rumpf
|
|
||||||
#
|
|
||||||
# See the file "copying.txt", included in this
|
|
||||||
# distribution, for details about the copyright.
|
|
||||||
#
|
|
||||||
|
|
||||||
import
|
|
||||||
intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees,
|
|
||||||
strutils, options, dfa, lowerings, tables, modulegraphs, msgs,
|
|
||||||
lineinfos, parampatterns
|
|
||||||
|
|
||||||
##[
|
|
||||||
This module implements "cursor" detection. A cursor is a local variable
|
|
||||||
that is used for navigation in a datastructure, it does not "own" the
|
|
||||||
data it aliases but it might update the underlying datastructure.
|
|
||||||
|
|
||||||
Two primary examples for cursors that I have in mind and that are critical
|
|
||||||
for optimization:
|
|
||||||
|
|
||||||
1. Local string variable introduced by ``for x in a``::
|
|
||||||
|
|
||||||
var i = 0
|
|
||||||
while i < a.len:
|
|
||||||
let cursor = a[i]
|
|
||||||
use cursor
|
|
||||||
inc i
|
|
||||||
|
|
||||||
2. Local ``ref`` variable for navigation::
|
|
||||||
|
|
||||||
var cursor = listHead
|
|
||||||
while cursor != nil:
|
|
||||||
use cursor
|
|
||||||
cursor = cursor.next
|
|
||||||
|
|
||||||
Cursors are very interesting for the optimizer because they can be copyMem'ed
|
|
||||||
and don't need a destructor.
|
|
||||||
|
|
||||||
More formally, a cursor is a variable that is set on all paths to
|
|
||||||
a *location* or a proc call that produced a ``lent/var`` type. All statements
|
|
||||||
that come after these assignments MUST not mutate what the cursor aliases.
|
|
||||||
|
|
||||||
Mutations *through* the cursor are allowed if the cursor has ref semantics.
|
|
||||||
|
|
||||||
Look at this complex real world example taken from the compiler itself:
|
|
||||||
|
|
||||||
.. code-block:: Nim
|
|
||||||
|
|
||||||
proc getTypeName(m: BModule; typ: PType; sig: SigHash): Rope =
|
|
||||||
var t = typ
|
|
||||||
while true:
|
|
||||||
if t.sym != nil and {sfImportc, sfExportc} * t.sym.flags != {}:
|
|
||||||
return t.sym.loc.r
|
|
||||||
|
|
||||||
if t.kind in irrelevantForBackend:
|
|
||||||
t = t.lastSon
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
let typ = if typ.kind in {tyAlias, tySink, tyOwned}: typ.lastSon else: typ
|
|
||||||
if typ.loc.r == nil:
|
|
||||||
typ.loc.r = typ.typeName & $sig
|
|
||||||
result = typ.loc.r
|
|
||||||
if result == nil: internalError(m.config, "getTypeName: " & $typ.kind)
|
|
||||||
|
|
||||||
Here `t` is a cursor but without a control flow based analysis we are unlikely
|
|
||||||
to detect it.
|
|
||||||
|
|
||||||
]##
|
|
||||||
|
|
||||||
# Araq: I owe you an implementation. For now use the .cursor pragma. :-/
|
|
||||||
|
|
@ -11,136 +11,14 @@
|
||||||
## an optimizer that optimizes copies to moves. This is implemented as an
|
## an optimizer that optimizes copies to moves. This is implemented as an
|
||||||
## AST to AST transformation so that every backend benefits from it.
|
## AST to AST transformation so that every backend benefits from it.
|
||||||
|
|
||||||
## Rules for destructor injections:
|
## See doc/destructors.rst for a spec of the implemented rewrite rules
|
||||||
##
|
|
||||||
## foo(bar(X(), Y()))
|
|
||||||
## X and Y get destroyed after bar completes:
|
|
||||||
##
|
|
||||||
## foo( (tmpX = X(); tmpY = Y(); tmpBar = bar(tmpX, tmpY);
|
|
||||||
## destroy(tmpX); destroy(tmpY);
|
|
||||||
## tmpBar))
|
|
||||||
## destroy(tmpBar)
|
|
||||||
##
|
|
||||||
## var x = f()
|
|
||||||
## body
|
|
||||||
##
|
|
||||||
## is the same as:
|
|
||||||
##
|
|
||||||
## var x;
|
|
||||||
## try:
|
|
||||||
## move(x, f())
|
|
||||||
## finally:
|
|
||||||
## destroy(x)
|
|
||||||
##
|
|
||||||
## But this really just an optimization that tries to avoid to
|
|
||||||
## introduce too many temporaries, the 'destroy' is caused by
|
|
||||||
## the 'f()' call. No! That is not true for 'result = f()'!
|
|
||||||
##
|
|
||||||
## x = y where y is read only once
|
|
||||||
## is the same as: move(x, y)
|
|
||||||
##
|
|
||||||
## Actually the more general rule is: The *last* read of ``y``
|
|
||||||
## can become a move if ``y`` is the result of a construction.
|
|
||||||
##
|
|
||||||
## We also need to keep in mind here that the number of reads is
|
|
||||||
## control flow dependent:
|
|
||||||
## let x = foo()
|
|
||||||
## while true:
|
|
||||||
## y = x # only one read, but the 2nd iteration will fail!
|
|
||||||
## This also affects recursions! Only usages that do not cross
|
|
||||||
## a loop boundary (scope) and are not used in function calls
|
|
||||||
## are safe.
|
|
||||||
##
|
|
||||||
##
|
|
||||||
## x = f() is the same as: move(x, f())
|
|
||||||
##
|
|
||||||
## x = y
|
|
||||||
## is the same as: copy(x, y)
|
|
||||||
##
|
|
||||||
## Reassignment works under this scheme:
|
|
||||||
## var x = f()
|
|
||||||
## x = y
|
|
||||||
##
|
|
||||||
## is the same as:
|
|
||||||
##
|
|
||||||
## var x;
|
|
||||||
## try:
|
|
||||||
## move(x, f())
|
|
||||||
## copy(x, y)
|
|
||||||
## finally:
|
|
||||||
## destroy(x)
|
|
||||||
##
|
|
||||||
## result = f() must not destroy 'result'!
|
|
||||||
##
|
|
||||||
## The produced temporaries clutter up the code and might lead to
|
|
||||||
## inefficiencies. A better strategy is to collect all the temporaries
|
|
||||||
## in a single object that we put into a single try-finally that
|
|
||||||
## surrounds the proc body. This means the code stays quite efficient
|
|
||||||
## when compiled to C. In fact, we do the same for variables, so
|
|
||||||
## destructors are called when the proc returns, not at scope exit!
|
|
||||||
## This makes certains idioms easier to support. (Taking the slice
|
|
||||||
## of a temporary object.)
|
|
||||||
##
|
|
||||||
## foo(bar(X(), Y()))
|
|
||||||
## X and Y get destroyed after bar completes:
|
|
||||||
##
|
|
||||||
## var tmp: object
|
|
||||||
## foo( (move tmp.x, X(); move tmp.y, Y(); tmp.bar = bar(tmpX, tmpY);
|
|
||||||
## tmp.bar))
|
|
||||||
## destroy(tmp.bar)
|
|
||||||
## destroy(tmp.x); destroy(tmp.y)
|
|
||||||
##
|
|
||||||
|
|
||||||
#[
|
|
||||||
From https://github.com/nim-lang/Nim/wiki/Destructors
|
|
||||||
|
|
||||||
Rule Pattern Transformed into
|
|
||||||
---- ------- ----------------
|
|
||||||
1.1 var x: T; stmts var x: T; try stmts
|
|
||||||
finally: `=destroy`(x)
|
|
||||||
2 x = f() `=sink`(x, f())
|
|
||||||
3 x = lastReadOf z `=sink`(x, z); wasMoved(z)
|
|
||||||
3.2 x = path z; body ``x = bitwiseCopy(path z);``
|
|
||||||
do not emit `=destroy(x)`. Note: body
|
|
||||||
must not mutate ``z`` nor ``x``. All
|
|
||||||
assignments to ``x`` must be of the form
|
|
||||||
``path z`` but the ``z`` can differ.
|
|
||||||
Neither ``z`` nor ``x`` can have the
|
|
||||||
flag ``sfAddrTaken`` to ensure no other
|
|
||||||
aliasing is going on.
|
|
||||||
4.1 y = sinkParam `=sink`(y, sinkParam)
|
|
||||||
4.2 x = y `=`(x, y) # a copy
|
|
||||||
5.1 f_sink(g()) f_sink(g())
|
|
||||||
5.2 f_sink(y) f_sink(copy y); # copy unless we can see it's the last read
|
|
||||||
5.3 f_sink(move y) f_sink(y); wasMoved(y) # explicit moves empties 'y'
|
|
||||||
5.4 f_noSink(g()) var tmp = bitwiseCopy(g()); f(tmp); `=destroy`(tmp)
|
|
||||||
|
|
||||||
Rule 3.2 describes a "cursor" variable, a variable that is only used as a
|
|
||||||
view into some data structure. See ``compiler/cursors.nim`` for details.
|
|
||||||
|
|
||||||
Note: In order to avoid the very common combination ``reset(x); =sink(x, y)`` for
|
|
||||||
variable definitions we must turn "the first sink/assignment" operation into a
|
|
||||||
copyMem. This is harder than it looks:
|
|
||||||
|
|
||||||
while true:
|
|
||||||
try:
|
|
||||||
if cond: break # problem if we run destroy(x) here :-/
|
|
||||||
var x = f()
|
|
||||||
finally:
|
|
||||||
destroy(x)
|
|
||||||
|
|
||||||
And the C++ optimizers don't sweat to optimize it for us, so we don't have
|
|
||||||
to do it.
|
|
||||||
]#
|
|
||||||
|
|
||||||
import
|
import
|
||||||
intsets, ast, astalgo, msgs, renderer, magicsys, types, idents,
|
intsets, ast, astalgo, msgs, renderer, magicsys, types, idents,
|
||||||
strutils, options, dfa, lowerings, tables, modulegraphs, msgs,
|
strutils, options, dfa, lowerings, tables, modulegraphs, msgs,
|
||||||
lineinfos, parampatterns, sighashes
|
lineinfos, parampatterns, sighashes
|
||||||
|
|
||||||
const
|
|
||||||
InterestingSyms = {skVar, skResult, skLet, skForVar, skTemp}
|
|
||||||
|
|
||||||
type
|
type
|
||||||
Con = object
|
Con = object
|
||||||
owner: PSym
|
owner: PSym
|
||||||
|
|
@ -217,43 +95,6 @@ proc isLastRead(n: PNode; c: var Con): bool =
|
||||||
dbg:
|
dbg:
|
||||||
echo "ugh ", c.otherRead.isNil, " ", result
|
echo "ugh ", c.otherRead.isNil, " ", result
|
||||||
|
|
||||||
when false:
|
|
||||||
let s = n.sym
|
|
||||||
var pcs: seq[int] = @[instr+1]
|
|
||||||
var takenGotos: IntSet
|
|
||||||
var takenForks = initIntSet()
|
|
||||||
while pcs.len > 0:
|
|
||||||
var pc = pcs.pop
|
|
||||||
|
|
||||||
takenGotos = initIntSet()
|
|
||||||
while pc < c.g.len:
|
|
||||||
case c.g[pc].kind
|
|
||||||
of def:
|
|
||||||
if c.g[pc].sym == s:
|
|
||||||
# the path lead to a redefinition of 's' --> abandon it.
|
|
||||||
break
|
|
||||||
inc pc
|
|
||||||
of use:
|
|
||||||
if c.g[pc].sym == s:
|
|
||||||
c.otherRead = c.g[pc].n
|
|
||||||
return false
|
|
||||||
inc pc
|
|
||||||
of goto:
|
|
||||||
# we must leave endless loops eventually:
|
|
||||||
if not takenGotos.containsOrIncl(pc):
|
|
||||||
pc = pc + c.g[pc].dest
|
|
||||||
else:
|
|
||||||
inc pc
|
|
||||||
of fork:
|
|
||||||
# we follow the next instruction but push the dest onto our "work" stack:
|
|
||||||
if not takenForks.containsOrIncl(pc):
|
|
||||||
pcs.add pc + c.g[pc].dest
|
|
||||||
inc pc
|
|
||||||
of InstrKind.join:
|
|
||||||
inc pc
|
|
||||||
#echo c.graph.config $ n.info, " last read here!"
|
|
||||||
return true
|
|
||||||
|
|
||||||
proc initialized(code: ControlFlowGraph; pc: int,
|
proc initialized(code: ControlFlowGraph; pc: int,
|
||||||
init, uninit: var IntSet; comesFrom: int): int =
|
init, uninit: var IntSet; comesFrom: int): int =
|
||||||
## Computes the set of definitely initialized variables across all code paths
|
## Computes the set of definitely initialized variables across all code paths
|
||||||
|
|
@ -290,9 +131,6 @@ proc initialized(code: ControlFlowGraph; pc: int,
|
||||||
inc pc
|
inc pc
|
||||||
return pc
|
return pc
|
||||||
|
|
||||||
template interestingSym(s: PSym): bool =
|
|
||||||
s.owner == c.owner and s.kind in InterestingSyms and hasDestructor(s.typ)
|
|
||||||
|
|
||||||
template isUnpackedTuple(s: PSym): bool =
|
template isUnpackedTuple(s: PSym): bool =
|
||||||
## we move out all elements of unpacked tuples,
|
## we move out all elements of unpacked tuples,
|
||||||
## hence unpacked tuples themselves don't need to be destroyed
|
## hence unpacked tuples themselves don't need to be destroyed
|
||||||
|
|
@ -353,8 +191,8 @@ proc canBeMoved(t: PType): bool {.inline.} =
|
||||||
let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
|
let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
|
||||||
result = t.kind != tyRef and t.attachedOps[attachedSink] != nil
|
result = t.kind != tyRef and t.attachedOps[attachedSink] != nil
|
||||||
|
|
||||||
proc genSink(c: Con; t: PType; dest, ri: PNode): PNode =
|
proc genSink(c: Con; dest, ri: PNode): PNode =
|
||||||
let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
|
let t = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink})
|
||||||
let k = if t.attachedOps[attachedSink] != nil: attachedSink
|
let k = if t.attachedOps[attachedSink] != nil: attachedSink
|
||||||
else: attachedAsgn
|
else: attachedAsgn
|
||||||
if t.attachedOps[k] != nil:
|
if t.attachedOps[k] != nil:
|
||||||
|
|
@ -365,20 +203,20 @@ proc genSink(c: Con; t: PType; dest, ri: PNode): PNode =
|
||||||
# we generate a fast assignment in this case:
|
# we generate a fast assignment in this case:
|
||||||
result = newTree(nkFastAsgn, dest)
|
result = newTree(nkFastAsgn, dest)
|
||||||
|
|
||||||
proc genCopy(c: var Con; t: PType; dest, ri: PNode): PNode =
|
proc genCopyNoCheck(c: Con; dest, ri: PNode): PNode =
|
||||||
|
let t = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink})
|
||||||
|
result = genOp(c, t, attachedAsgn, dest, ri)
|
||||||
|
|
||||||
|
proc genCopy(c: var Con; dest, ri: PNode): PNode =
|
||||||
|
let t = dest.typ
|
||||||
if tfHasOwned in t.flags:
|
if tfHasOwned in t.flags:
|
||||||
# try to improve the error message here:
|
# try to improve the error message here:
|
||||||
if c.otherRead == nil: discard isLastRead(ri, c)
|
if c.otherRead == nil: discard isLastRead(ri, c)
|
||||||
checkForErrorPragma(c, t, ri, "=")
|
checkForErrorPragma(c, t, ri, "=")
|
||||||
let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
|
genCopyNoCheck(c, dest, ri)
|
||||||
result = genOp(c, t, attachedAsgn, dest, ri)
|
|
||||||
|
|
||||||
proc genCopyNoCheck(c: Con; t: PType; dest, ri: PNode): PNode =
|
proc genDestroy(c: Con; dest: PNode): PNode =
|
||||||
let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
|
let t = dest.typ.skipTypes({tyGenericInst, tyAlias, tySink})
|
||||||
result = genOp(c, t, attachedAsgn, dest, ri)
|
|
||||||
|
|
||||||
proc genDestroy(c: Con; t: PType; dest: PNode): PNode =
|
|
||||||
let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
|
|
||||||
result = genOp(c, t, attachedDestructor, dest, nil)
|
result = genOp(c, t, attachedDestructor, dest, nil)
|
||||||
|
|
||||||
proc addTopVar(c: var Con; v: PNode) =
|
proc addTopVar(c: var Con; v: PNode) =
|
||||||
|
|
@ -390,20 +228,10 @@ proc getTemp(c: var Con; typ: PType; info: TLineInfo): PNode =
|
||||||
result = newSymNode(sym)
|
result = newSymNode(sym)
|
||||||
c.addTopVar(result)
|
c.addTopVar(result)
|
||||||
|
|
||||||
proc p(n: PNode; c: var Con): PNode
|
|
||||||
|
|
||||||
template recurse(n, dest) =
|
|
||||||
for i in 0..<n.len:
|
|
||||||
dest.add p(n[i], c)
|
|
||||||
|
|
||||||
proc genMagicCall(n: PNode; c: var Con; magicname: string; m: TMagic): PNode =
|
|
||||||
result = newNodeI(nkCall, n.info)
|
|
||||||
result.add(newSymNode(createMagic(c.graph, magicname, m)))
|
|
||||||
result.add n
|
|
||||||
|
|
||||||
proc genWasMoved(n: PNode; c: var Con): PNode =
|
proc genWasMoved(n: PNode; c: var Con): PNode =
|
||||||
# The mWasMoved builtin does not take the address.
|
result = newNodeI(nkCall, n.info)
|
||||||
result = genMagicCall(n, c, "wasMoved", mWasMoved)
|
result.add(newSymNode(createMagic(c.graph, "wasMoved", mWasMoved)))
|
||||||
|
result.add n #mWasMoved does not take the address
|
||||||
|
|
||||||
proc genDefaultCall(t: PType; c: Con; info: TLineInfo): PNode =
|
proc genDefaultCall(t: PType; c: Con; info: TLineInfo): PNode =
|
||||||
result = newNodeI(nkCall, info)
|
result = newNodeI(nkCall, info)
|
||||||
|
|
@ -422,9 +250,9 @@ proc destructiveMoveVar(n: PNode; c: var Con): PNode =
|
||||||
let tempAsNode = newSymNode(temp)
|
let tempAsNode = newSymNode(temp)
|
||||||
|
|
||||||
var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3)
|
var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3)
|
||||||
vpart.sons[0] = tempAsNode
|
vpart[0] = tempAsNode
|
||||||
vpart.sons[1] = c.emptyNode
|
vpart[1] = c.emptyNode
|
||||||
vpart.sons[2] = n
|
vpart[2] = n
|
||||||
add(v, vpart)
|
add(v, vpart)
|
||||||
|
|
||||||
result.add v
|
result.add v
|
||||||
|
|
@ -437,6 +265,10 @@ proc sinkParamIsLastReadCheck(c: var Con, s: PNode) =
|
||||||
localError(c.graph.config, c.otherRead.info, "sink parameter `" & $s.sym.name.s &
|
localError(c.graph.config, c.otherRead.info, "sink parameter `" & $s.sym.name.s &
|
||||||
"` is already consumed at " & toFileLineCol(c. graph.config, s.info))
|
"` is already consumed at " & toFileLineCol(c. graph.config, s.info))
|
||||||
|
|
||||||
|
proc p(n: PNode; c: var Con): PNode
|
||||||
|
proc pArg(arg: PNode; c: var Con; isSink: bool): PNode
|
||||||
|
proc moveOrCopy(dest, ri: PNode; c: var Con): PNode
|
||||||
|
|
||||||
proc passCopyToSink(n: PNode; c: var Con): PNode =
|
proc passCopyToSink(n: PNode; c: var Con): PNode =
|
||||||
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
|
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
|
||||||
let tmp = getTemp(c, n.typ, n.info)
|
let tmp = getTemp(c, n.typ, n.info)
|
||||||
|
|
@ -444,7 +276,7 @@ proc passCopyToSink(n: PNode; c: var Con): PNode =
|
||||||
# out of loops we need to mark it as 'wasMoved'.
|
# out of loops we need to mark it as 'wasMoved'.
|
||||||
result.add genWasMoved(tmp, c)
|
result.add genWasMoved(tmp, c)
|
||||||
if hasDestructor(n.typ):
|
if hasDestructor(n.typ):
|
||||||
var m = genCopy(c, n.typ, tmp, n)
|
var m = genCopy(c, tmp, n)
|
||||||
m.add p(n, c)
|
m.add p(n, c)
|
||||||
result.add m
|
result.add m
|
||||||
if isLValue(n):
|
if isLValue(n):
|
||||||
|
|
@ -457,7 +289,7 @@ proc passCopyToSink(n: PNode; c: var Con): PNode =
|
||||||
|
|
||||||
proc isDangerousSeq(t: PType): bool {.inline.} =
|
proc isDangerousSeq(t: PType): bool {.inline.} =
|
||||||
let t = t.skipTypes(abstractInst)
|
let t = t.skipTypes(abstractInst)
|
||||||
result = t.kind == tySequence and tfHasOwned notin t.sons[0].flags
|
result = t.kind == tySequence and tfHasOwned notin t[0].flags
|
||||||
|
|
||||||
proc containsConstSeq(n: PNode): bool =
|
proc containsConstSeq(n: PNode): bool =
|
||||||
if n.kind == nkBracket and n.len > 0 and n.typ != nil and isDangerousSeq(n.typ):
|
if n.kind == nkBracket and n.len > 0 and n.typ != nil and isDangerousSeq(n.typ):
|
||||||
|
|
@ -467,19 +299,66 @@ proc containsConstSeq(n: PNode): bool =
|
||||||
of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv:
|
of nkExprEqExpr, nkExprColonExpr, nkHiddenStdConv, nkHiddenSubConv:
|
||||||
result = containsConstSeq(n[1])
|
result = containsConstSeq(n[1])
|
||||||
of nkObjConstr, nkClosure:
|
of nkObjConstr, nkClosure:
|
||||||
for i in 1 ..< n.len:
|
for i in 1..<n.len:
|
||||||
if containsConstSeq(n[i]): return true
|
if containsConstSeq(n[i]): return true
|
||||||
of nkCurly, nkBracket, nkPar, nkTupleConstr:
|
of nkCurly, nkBracket, nkPar, nkTupleConstr:
|
||||||
for i in 0 ..< n.len:
|
for son in n:
|
||||||
if containsConstSeq(n[i]): return true
|
if containsConstSeq(son): return true
|
||||||
else: discard
|
else: discard
|
||||||
|
|
||||||
proc pArg(arg: PNode; c: var Con; isSink: bool): PNode =
|
template handleNested(n: untyped, processCall: untyped) =
|
||||||
template pArgIfTyped(argPart: PNode): PNode =
|
case n.kind
|
||||||
# typ is nil if we are in if/case expr branch with noreturn
|
of nkStmtList, nkStmtListExpr:
|
||||||
if argPart.typ == nil: p(argPart, c)
|
if n.len == 0: return n
|
||||||
else: pArg(argPart, c, isSink)
|
result = copyNode(n)
|
||||||
|
for i in 0..<n.len-1:
|
||||||
|
result.add p(n[i], c)
|
||||||
|
template node: untyped = n[^1]
|
||||||
|
result.add processCall
|
||||||
|
of nkBlockStmt, nkBlockExpr:
|
||||||
|
result = copyNode(n)
|
||||||
|
result.add n[0]
|
||||||
|
template node: untyped = n[1]
|
||||||
|
result.add processCall
|
||||||
|
of nkIfStmt, nkIfExpr:
|
||||||
|
result = copyNode(n)
|
||||||
|
for son in n:
|
||||||
|
var branch = copyNode(son)
|
||||||
|
if son.kind in {nkElifBranch, nkElifExpr}:
|
||||||
|
template node: untyped = son[1]
|
||||||
|
branch.add p(son[0], c) #The condition
|
||||||
|
branch.add if node.typ == nil: p(node, c) #noreturn
|
||||||
|
else: processCall
|
||||||
|
else:
|
||||||
|
template node: untyped = son[0]
|
||||||
|
branch.add if node.typ == nil: p(node, c) #noreturn
|
||||||
|
else: processCall
|
||||||
|
result.add branch
|
||||||
|
of nkCaseStmt:
|
||||||
|
result = copyNode(n)
|
||||||
|
result.add p(n[0], c)
|
||||||
|
for i in 1..<n.len:
|
||||||
|
var branch: PNode
|
||||||
|
if n[i].kind == nkOfBranch:
|
||||||
|
branch = n[i] # of branch conditions are constants
|
||||||
|
template node: untyped = n[i][^1]
|
||||||
|
branch[^1] = if node.typ == nil: p(node, c) #noreturn
|
||||||
|
else: processCall
|
||||||
|
elif n[i].kind in {nkElifBranch, nkElifExpr}:
|
||||||
|
branch = copyNode(n[i])
|
||||||
|
branch.add p(n[i][0], c) #The condition
|
||||||
|
template node: untyped = n[i][1]
|
||||||
|
branch.add if node.typ == nil: p(node, c) #noreturn
|
||||||
|
else: processCall
|
||||||
|
else:
|
||||||
|
branch = copyNode(n[i])
|
||||||
|
template node: untyped = n[i][0]
|
||||||
|
branch.add if node.typ == nil: p(node, c) #noreturn
|
||||||
|
else: processCall
|
||||||
|
result.add branch
|
||||||
|
else: assert(false)
|
||||||
|
|
||||||
|
proc pArg(arg: PNode; c: var Con; isSink: bool): PNode =
|
||||||
if isSink:
|
if isSink:
|
||||||
if arg.kind in nkCallKinds:
|
if arg.kind in nkCallKinds:
|
||||||
# recurse but skip the call expression in order to prevent
|
# recurse but skip the call expression in order to prevent
|
||||||
|
|
@ -495,8 +374,8 @@ proc pArg(arg: PNode; c: var Con; isSink: bool): PNode =
|
||||||
# sink parameter (bug #11524). Note that the string implementation is
|
# sink parameter (bug #11524). Note that the string implementation is
|
||||||
# different and can deal with 'const string sunk into var'.
|
# different and can deal with 'const string sunk into var'.
|
||||||
result = passCopyToSink(arg, c)
|
result = passCopyToSink(arg, c)
|
||||||
elif arg.kind in {nkBracket, nkObjConstr, nkTupleConstr, nkCharLit..nkTripleStrLit}:
|
elif arg.kind in {nkBracket, nkObjConstr, nkTupleConstr} + nkLiterals:
|
||||||
discard "object construction to sink parameter: nothing to do"
|
# object construction to sink parameter: nothing to do
|
||||||
result = arg
|
result = arg
|
||||||
elif arg.kind == nkSym and isSinkParam(arg.sym):
|
elif arg.kind == nkSym and isSinkParam(arg.sym):
|
||||||
# Sinked params can be consumed only once. We need to reset the memory
|
# Sinked params can be consumed only once. We need to reset the memory
|
||||||
|
|
@ -507,202 +386,216 @@ proc pArg(arg: PNode; c: var Con; isSink: bool): PNode =
|
||||||
# it is the last read, can be sinked. We need to reset the memory
|
# it is the last read, can be sinked. We need to reset the memory
|
||||||
# to disable the destructor which we have not elided
|
# to disable the destructor which we have not elided
|
||||||
result = destructiveMoveVar(arg, c)
|
result = destructiveMoveVar(arg, c)
|
||||||
elif arg.kind in {nkBlockExpr, nkBlockStmt}:
|
elif arg.kind in {nkStmtListExpr, nkBlockExpr, nkBlockStmt, nkIfExpr, nkIfStmt, nkCaseStmt}:
|
||||||
result = copyNode(arg)
|
handleNested(arg): pArg(node, c, isSink)
|
||||||
result.add arg[0]
|
|
||||||
result.add pArg(arg[1], c, isSink)
|
|
||||||
elif arg.kind == nkStmtListExpr:
|
|
||||||
result = copyNode(arg)
|
|
||||||
for i in 0..arg.len-2:
|
|
||||||
result.add p(arg[i], c)
|
|
||||||
result.add pArg(arg[^1], c, isSink)
|
|
||||||
elif arg.kind in {nkIfExpr, nkIfStmt}:
|
|
||||||
result = copyNode(arg)
|
|
||||||
for i in 0..<arg.len:
|
|
||||||
var branch = copyNode(arg[i])
|
|
||||||
if arg[i].kind in {nkElifBranch, nkElifExpr}:
|
|
||||||
branch.add p(arg[i][0], c)
|
|
||||||
branch.add pArgIfTyped(arg[i][1])
|
|
||||||
else:
|
|
||||||
branch.add pArgIfTyped(arg[i][0])
|
|
||||||
result.add branch
|
|
||||||
elif arg.kind == nkCaseStmt:
|
|
||||||
result = copyNode(arg)
|
|
||||||
result.add p(arg[0], c)
|
|
||||||
for i in 1..<arg.len:
|
|
||||||
var branch: PNode
|
|
||||||
if arg[i].kind == nkOfBranch:
|
|
||||||
branch = arg[i] # of branch conditions are constants
|
|
||||||
branch[^1] = pArgIfTyped(arg[i][^1])
|
|
||||||
elif arg[i].kind in {nkElifBranch, nkElifExpr}:
|
|
||||||
branch = copyNode(arg[i])
|
|
||||||
branch.add p(arg[i][0], c)
|
|
||||||
branch.add pArgIfTyped(arg[i][1])
|
|
||||||
else:
|
|
||||||
branch = copyNode(arg[i])
|
|
||||||
branch.add pArgIfTyped(arg[i][0])
|
|
||||||
result.add branch
|
|
||||||
else:
|
else:
|
||||||
# an object that is not temporary but passed to a 'sink' parameter
|
# an object that is not temporary but passed to a 'sink' parameter
|
||||||
# results in a copy.
|
# results in a copy.
|
||||||
result = passCopyToSink(arg, c)
|
result = passCopyToSink(arg, c)
|
||||||
|
elif arg.kind == nkBracket:
|
||||||
|
# Treat `f([...])` like `f(...)`
|
||||||
|
result = copyNode(arg)
|
||||||
|
for son in arg:
|
||||||
|
result.add pArg(son, c, isSinkTypeForParam(son.typ))
|
||||||
|
elif arg.kind in nkCallKinds and arg.typ != nil and hasDestructor(arg.typ):
|
||||||
|
# produce temp creation
|
||||||
|
result = newNodeIT(nkStmtListExpr, arg.info, arg.typ)
|
||||||
|
let tmp = getTemp(c, arg.typ, arg.info)
|
||||||
|
let res = p(arg, c)
|
||||||
|
var sinkExpr = genSink(c, tmp, res)
|
||||||
|
sinkExpr.add res
|
||||||
|
result.add sinkExpr
|
||||||
|
result.add tmp
|
||||||
|
c.destroys.add genDestroy(c, tmp)
|
||||||
else:
|
else:
|
||||||
result = p(arg, c)
|
result = p(arg, c)
|
||||||
|
|
||||||
|
proc p(n: PNode; c: var Con): PNode =
|
||||||
|
case n.kind
|
||||||
|
of nkCallKinds:
|
||||||
|
let parameters = n[0].typ
|
||||||
|
let L = if parameters != nil: parameters.len else: 0
|
||||||
|
for i in 1..<n.len:
|
||||||
|
n[i] = pArg(n[i], c, i < L and isSinkTypeForParam(parameters[i]))
|
||||||
|
result = n
|
||||||
|
of nkDiscardStmt: #Small optimization
|
||||||
|
if n[0].kind != nkEmpty:
|
||||||
|
n[0] = pArg(n[0], c, false)
|
||||||
|
result = n
|
||||||
|
of nkBracket:
|
||||||
|
result = copyTree(n)
|
||||||
|
for i in 0..<n.len:
|
||||||
|
# everything that is passed to an array constructor is consumed,
|
||||||
|
# so these all act like 'sink' parameters:
|
||||||
|
result[i] = pArg(n[i], c, isSink = true)
|
||||||
|
of nkObjConstr:
|
||||||
|
result = copyTree(n)
|
||||||
|
for i in 1..<n.len:
|
||||||
|
# everything that is passed to an object constructor is consumed,
|
||||||
|
# so these all act like 'sink' parameters:
|
||||||
|
result[i][1] = pArg(n[i][1], c, isSink = true)
|
||||||
|
of nkTupleConstr, nkClosure:
|
||||||
|
result = copyTree(n)
|
||||||
|
for i in ord(n.kind == nkClosure)..<n.len:
|
||||||
|
# everything that is passed to an tuple constructor is consumed,
|
||||||
|
# so these all act like 'sink' parameters:
|
||||||
|
if n[i].kind == nkExprColonExpr:
|
||||||
|
result[i][1] = pArg(n[i][1], c, isSink = true)
|
||||||
|
else:
|
||||||
|
result[i] = pArg(n[i], c, isSink = true)
|
||||||
|
of nkVarSection, nkLetSection:
|
||||||
|
# transform; var x = y to var x; x op y where op is a move or copy
|
||||||
|
result = newNodeI(nkStmtList, n.info)
|
||||||
|
for it in n:
|
||||||
|
var ri = it[^1]
|
||||||
|
if it.kind == nkVarTuple and hasDestructor(ri.typ):
|
||||||
|
let x = lowerTupleUnpacking(c.graph, it, c.owner)
|
||||||
|
result.add p(x, c)
|
||||||
|
elif it.kind == nkIdentDefs and hasDestructor(it[0].typ):
|
||||||
|
for j in 0..<it.len-2:
|
||||||
|
let v = it[j]
|
||||||
|
if v.kind == nkSym:
|
||||||
|
if sfCompileTime in v.sym.flags: continue
|
||||||
|
# move the variable declaration to the top of the frame:
|
||||||
|
c.addTopVar v
|
||||||
|
# make sure it's destroyed at the end of the proc:
|
||||||
|
if not isUnpackedTuple(it[0].sym):
|
||||||
|
c.destroys.add genDestroy(c, v)
|
||||||
|
if ri.kind == nkEmpty and c.inLoop > 0:
|
||||||
|
ri = genDefaultCall(v.typ, c, v.info)
|
||||||
|
if ri.kind != nkEmpty:
|
||||||
|
let r = moveOrCopy(v, ri, c)
|
||||||
|
result.add r
|
||||||
|
else: # keep the var but transform 'ri':
|
||||||
|
var v = copyNode(n)
|
||||||
|
var itCopy = copyNode(it)
|
||||||
|
for j in 0..<it.len-1:
|
||||||
|
itCopy.add it[j]
|
||||||
|
itCopy.add p(it[^1], c)
|
||||||
|
v.add itCopy
|
||||||
|
result.add v
|
||||||
|
of nkAsgn, nkFastAsgn:
|
||||||
|
if hasDestructor(n[0].typ) and n[1].kind notin {nkProcDef, nkDo, nkLambda}:
|
||||||
|
# rule (self-assignment-removal):
|
||||||
|
if n[1].kind == nkSym and n[0].kind == nkSym and n[0].sym == n[1].sym:
|
||||||
|
result = newNodeI(nkEmpty, n.info)
|
||||||
|
else:
|
||||||
|
result = moveOrCopy(n[0], n[1], c)
|
||||||
|
else:
|
||||||
|
result = copyNode(n)
|
||||||
|
result.add n[0]
|
||||||
|
result.add p(n[1], c)
|
||||||
|
of nkRaiseStmt:
|
||||||
|
if optNimV2 in c.graph.config.globalOptions and n[0].kind != nkEmpty:
|
||||||
|
if n[0].kind in nkCallKinds:
|
||||||
|
let call = p(n[0], c)
|
||||||
|
result = copyNode(n)
|
||||||
|
result.add call
|
||||||
|
else:
|
||||||
|
let tmp = getTemp(c, n[0].typ, n.info)
|
||||||
|
var m = genCopyNoCheck(c, tmp, n[0])
|
||||||
|
m.add p(n[0], c)
|
||||||
|
result = newTree(nkStmtList, genWasMoved(tmp, c), m)
|
||||||
|
var toDisarm = n[0]
|
||||||
|
if toDisarm.kind == nkStmtListExpr: toDisarm = toDisarm.lastSon
|
||||||
|
if toDisarm.kind == nkSym and toDisarm.sym.owner == c.owner:
|
||||||
|
result.add genWasMoved(toDisarm, c)
|
||||||
|
result.add newTree(nkRaiseStmt, tmp)
|
||||||
|
else:
|
||||||
|
result = copyNode(n)
|
||||||
|
result.add p(n[0], c)
|
||||||
|
of nkNone..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef,
|
||||||
|
nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef,
|
||||||
|
nkConstSection, nkConstDef, nkIncludeStmt, nkImportStmt, nkExportStmt,
|
||||||
|
nkPragma, nkCommentStmt, nkBreakStmt:
|
||||||
|
result = n
|
||||||
|
of nkWhileStmt:
|
||||||
|
result = copyNode(n)
|
||||||
|
inc c.inLoop
|
||||||
|
result.add p(n[0], c)
|
||||||
|
result.add p(n[1], c)
|
||||||
|
dec c.inLoop
|
||||||
|
of nkWhen: # This should be a "when nimvm" node.
|
||||||
|
result = copyTree(n)
|
||||||
|
result[1][0] = p(result[1][0], c)
|
||||||
|
of nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkIfStmt, nkIfExpr, nkCaseStmt:
|
||||||
|
handleNested(n): p(node, c)
|
||||||
|
else:
|
||||||
|
result = shallowCopy(n)
|
||||||
|
for i in 0..<n.len:
|
||||||
|
result[i] = p(n[i], c)
|
||||||
|
|
||||||
proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
|
proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
|
||||||
# unfortunately, this needs to be kept consistent with the cases
|
# unfortunately, this needs to be kept consistent with the cases
|
||||||
# we handle in the 'case of' statement below:
|
# we handle in the 'case of' statement below:
|
||||||
const movableNodeKinds = (nkCallKinds + {nkSym, nkTupleConstr, nkObjConstr,
|
const movableNodeKinds = (nkCallKinds + {nkSym, nkTupleConstr, nkObjConstr,
|
||||||
nkBracket, nkBracketExpr, nkNilLit})
|
nkBracket, nkBracketExpr, nkNilLit})
|
||||||
|
|
||||||
template moveOrCopyIfTyped(riPart: PNode): PNode =
|
|
||||||
# typ is nil if we are in if/case expr branch with noreturn
|
|
||||||
if riPart.typ == nil: p(riPart, c)
|
|
||||||
else: moveOrCopy(dest, riPart, c)
|
|
||||||
|
|
||||||
case ri.kind
|
case ri.kind
|
||||||
of nkCallKinds:
|
of nkCallKinds:
|
||||||
result = genSink(c, dest.typ, dest, ri)
|
result = genSink(c, dest, ri)
|
||||||
# watch out and no not transform 'ri' twice if it's a call:
|
result.add p(ri, c)
|
||||||
let ri2 = copyNode(ri)
|
|
||||||
let parameters = ri[0].typ
|
|
||||||
let L = if parameters != nil: parameters.len else: 0
|
|
||||||
ri2.add ri[0]
|
|
||||||
for i in 1..<ri.len:
|
|
||||||
ri2.add pArg(ri[i], c, i < L and isSinkTypeForParam(parameters[i]))
|
|
||||||
#recurse(ri, ri2)
|
|
||||||
result.add ri2
|
|
||||||
of nkBracketExpr:
|
of nkBracketExpr:
|
||||||
if ri[0].kind == nkSym and isUnpackedTuple(ri[0].sym):
|
if ri[0].kind == nkSym and isUnpackedTuple(ri[0].sym):
|
||||||
# unpacking of tuple: move out the elements
|
# unpacking of tuple: move out the elements
|
||||||
result = genSink(c, dest.typ, dest, ri)
|
result = genSink(c, dest, ri)
|
||||||
result.add p(ri, c)
|
result.add p(ri, c)
|
||||||
elif isAnalysableFieldAccess(ri, c.owner) and isLastRead(ri, c):
|
elif isAnalysableFieldAccess(ri, c.owner) and isLastRead(ri, c):
|
||||||
# Rule 3: `=sink`(x, z); wasMoved(z)
|
# Rule 3: `=sink`(x, z); wasMoved(z)
|
||||||
var snk = genSink(c, dest.typ, dest, ri)
|
var snk = genSink(c, dest, ri)
|
||||||
snk.add ri
|
snk.add ri
|
||||||
result = newTree(nkStmtList, snk, genWasMoved(ri, c))
|
result = newTree(nkStmtList, snk, genWasMoved(ri, c))
|
||||||
else:
|
else:
|
||||||
result = genCopy(c, dest.typ, dest, ri)
|
result = genCopy(c, dest, ri)
|
||||||
result.add p(ri, c)
|
result.add p(ri, c)
|
||||||
of nkStmtListExpr:
|
|
||||||
result = newNodeI(nkStmtList, ri.info)
|
|
||||||
for i in 0..ri.len-2:
|
|
||||||
result.add p(ri[i], c)
|
|
||||||
result.add moveOrCopy(dest, ri[^1], c)
|
|
||||||
of nkBlockExpr, nkBlockStmt:
|
|
||||||
result = newNodeI(nkBlockStmt, ri.info)
|
|
||||||
result.add ri[0] ## add label
|
|
||||||
result.add moveOrCopy(dest, ri[1], c)
|
|
||||||
of nkIfExpr, nkIfStmt:
|
|
||||||
result = newNodeI(nkIfStmt, ri.info)
|
|
||||||
for i in 0..<ri.len:
|
|
||||||
var branch = copyNode(ri[i])
|
|
||||||
if ri[i].kind in {nkElifBranch, nkElifExpr}:
|
|
||||||
branch.add p(ri[i][0], c)
|
|
||||||
branch.add moveOrCopyIfTyped(ri[i][1])
|
|
||||||
else:
|
|
||||||
branch.add moveOrCopyIfTyped(ri[i][0])
|
|
||||||
result.add branch
|
|
||||||
of nkCaseStmt:
|
|
||||||
result = newNodeI(nkCaseStmt, ri.info)
|
|
||||||
result.add p(ri[0], c)
|
|
||||||
for i in 1..<ri.len:
|
|
||||||
var branch: PNode
|
|
||||||
if ri[i].kind == nkOfBranch:
|
|
||||||
branch = ri[i] # of branch conditions are constants
|
|
||||||
branch[^1] = moveOrCopyIfTyped(ri[i][^1])
|
|
||||||
elif ri[i].kind in {nkElifBranch, nkElifExpr}:
|
|
||||||
branch = copyNode(ri[i])
|
|
||||||
branch.add p(ri[i][0], c)
|
|
||||||
branch.add moveOrCopyIfTyped(ri[i][1])
|
|
||||||
else:
|
|
||||||
branch = copyNode(ri[i])
|
|
||||||
branch.add moveOrCopyIfTyped(ri[i][0])
|
|
||||||
result.add branch
|
|
||||||
of nkBracket:
|
of nkBracket:
|
||||||
# array constructor
|
# array constructor
|
||||||
if ri.len > 0 and isDangerousSeq(ri.typ):
|
if ri.len > 0 and isDangerousSeq(ri.typ):
|
||||||
result = genCopy(c, dest.typ, dest, ri)
|
result = genCopy(c, dest, ri)
|
||||||
else:
|
else:
|
||||||
result = genSink(c, dest.typ, dest, ri)
|
result = genSink(c, dest, ri)
|
||||||
let ri2 = copyTree(ri)
|
result.add p(ri, c)
|
||||||
for i in 0..<ri.len:
|
of nkObjConstr, nkTupleConstr, nkClosure, nkCharLit..nkNilLit:
|
||||||
# everything that is passed to an array constructor is consumed,
|
result = genSink(c, dest, ri)
|
||||||
# so these all act like 'sink' parameters:
|
result.add p(ri, c)
|
||||||
ri2[i] = pArg(ri[i], c, isSink = true)
|
|
||||||
result.add ri2
|
|
||||||
of nkObjConstr:
|
|
||||||
result = genSink(c, dest.typ, dest, ri)
|
|
||||||
let ri2 = copyTree(ri)
|
|
||||||
for i in 1..<ri.len:
|
|
||||||
# everything that is passed to an object constructor is consumed,
|
|
||||||
# so these all act like 'sink' parameters:
|
|
||||||
ri2[i].sons[1] = pArg(ri[i][1], c, isSink = true)
|
|
||||||
result.add ri2
|
|
||||||
of nkTupleConstr, nkClosure:
|
|
||||||
result = genSink(c, dest.typ, dest, ri)
|
|
||||||
let ri2 = copyTree(ri)
|
|
||||||
for i in ord(ri.kind == nkClosure)..<ri.len:
|
|
||||||
# everything that is passed to an tuple constructor is consumed,
|
|
||||||
# so these all act like 'sink' parameters:
|
|
||||||
if ri[i].kind == nkExprColonExpr:
|
|
||||||
ri2[i].sons[1] = pArg(ri[i][1], c, isSink = true)
|
|
||||||
else:
|
|
||||||
ri2[i] = pArg(ri[i], c, isSink = true)
|
|
||||||
result.add ri2
|
|
||||||
of nkNilLit:
|
|
||||||
result = genSink(c, dest.typ, dest, ri)
|
|
||||||
result.add ri
|
|
||||||
of nkSym:
|
of nkSym:
|
||||||
if isSinkParam(ri.sym):
|
if isSinkParam(ri.sym):
|
||||||
# Rule 3: `=sink`(x, z); wasMoved(z)
|
# Rule 3: `=sink`(x, z); wasMoved(z)
|
||||||
sinkParamIsLastReadCheck(c, ri)
|
sinkParamIsLastReadCheck(c, ri)
|
||||||
var snk = genSink(c, dest.typ, dest, ri)
|
var snk = genSink(c, dest, ri)
|
||||||
snk.add ri
|
snk.add ri
|
||||||
result = newTree(nkStmtList, snk, genWasMoved(ri, c))
|
result = newTree(nkStmtList, snk, genWasMoved(ri, c))
|
||||||
elif ri.sym.kind != skParam and ri.sym.owner == c.owner and
|
elif ri.sym.kind != skParam and ri.sym.owner == c.owner and
|
||||||
isLastRead(ri, c) and canBeMoved(dest.typ):
|
isLastRead(ri, c) and canBeMoved(dest.typ):
|
||||||
# Rule 3: `=sink`(x, z); wasMoved(z)
|
# Rule 3: `=sink`(x, z); wasMoved(z)
|
||||||
var snk = genSink(c, dest.typ, dest, ri)
|
var snk = genSink(c, dest, ri)
|
||||||
snk.add ri
|
snk.add ri
|
||||||
result = newTree(nkStmtList, snk, genWasMoved(ri, c))
|
result = newTree(nkStmtList, snk, genWasMoved(ri, c))
|
||||||
else:
|
else:
|
||||||
result = genCopy(c, dest.typ, dest, ri)
|
result = genCopy(c, dest, ri)
|
||||||
result.add p(ri, c)
|
result.add p(ri, c)
|
||||||
of nkHiddenSubConv, nkHiddenStdConv:
|
of nkHiddenSubConv, nkHiddenStdConv, nkConv:
|
||||||
if sameType(ri.typ, ri[1].typ):
|
|
||||||
result = moveOrCopy(dest, ri[1], c)
|
result = moveOrCopy(dest, ri[1], c)
|
||||||
elif ri[1].kind in movableNodeKinds:
|
if not sameType(ri.typ, ri[1].typ):
|
||||||
result = moveOrCopy(dest, ri[1], c)
|
let copyRi = copyTree(ri)
|
||||||
var b = newNodeIT(ri.kind, ri.info, ri.typ)
|
copyRi[1] = result[^1]
|
||||||
b.add ri[0] # add empty node
|
result[^1] = copyRi
|
||||||
let L = result.len-1
|
|
||||||
b.add result[L]
|
|
||||||
result[L] = b
|
|
||||||
else:
|
|
||||||
result = genCopy(c, dest.typ, dest, ri)
|
|
||||||
result.add p(ri, c)
|
|
||||||
of nkObjDownConv, nkObjUpConv:
|
of nkObjDownConv, nkObjUpConv:
|
||||||
if ri[0].kind in movableNodeKinds:
|
|
||||||
result = moveOrCopy(dest, ri[0], c)
|
result = moveOrCopy(dest, ri[0], c)
|
||||||
var b = newNodeIT(ri.kind, ri.info, ri.typ)
|
let copyRi = copyTree(ri)
|
||||||
let L = result.len-1
|
copyRi[0] = result[^1]
|
||||||
b.add result[L]
|
result[^1] = copyRi
|
||||||
result[L] = b
|
of nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt:
|
||||||
else:
|
handleNested(ri): moveOrCopy(dest, node, c)
|
||||||
result = genCopy(c, dest.typ, dest, ri)
|
|
||||||
result.add p(ri, c)
|
|
||||||
else:
|
else:
|
||||||
if isAnalysableFieldAccess(ri, c.owner) and isLastRead(ri, c) and
|
if isAnalysableFieldAccess(ri, c.owner) and isLastRead(ri, c) and
|
||||||
canBeMoved(dest.typ):
|
canBeMoved(dest.typ):
|
||||||
# Rule 3: `=sink`(x, z); wasMoved(z)
|
# Rule 3: `=sink`(x, z); wasMoved(z)
|
||||||
var snk = genSink(c, dest.typ, dest, ri)
|
var snk = genSink(c, dest, ri)
|
||||||
snk.add ri
|
snk.add ri
|
||||||
result = newTree(nkStmtList, snk, genWasMoved(ri, c))
|
result = newTree(nkStmtList, snk, genWasMoved(ri, c))
|
||||||
else:
|
else:
|
||||||
# XXX At least string literals can be moved?
|
result = genCopy(c, dest, ri)
|
||||||
result = genCopy(c, dest.typ, dest, ri)
|
|
||||||
result.add p(ri, c)
|
result.add p(ri, c)
|
||||||
|
|
||||||
proc computeUninit(c: var Con) =
|
proc computeUninit(c: var Con) =
|
||||||
|
|
@ -715,17 +608,14 @@ proc computeUninit(c: var Con) =
|
||||||
proc injectDefaultCalls(n: PNode, c: var Con) =
|
proc injectDefaultCalls(n: PNode, c: var Con) =
|
||||||
case n.kind
|
case n.kind
|
||||||
of nkVarSection, nkLetSection:
|
of nkVarSection, nkLetSection:
|
||||||
for i in 0..<n.len:
|
for it in n:
|
||||||
let it = n[i]
|
if it.kind == nkIdentDefs and it[^1].kind == nkEmpty:
|
||||||
let L = it.len-1
|
|
||||||
let ri = it[L]
|
|
||||||
if it.kind == nkIdentDefs and ri.kind == nkEmpty:
|
|
||||||
computeUninit(c)
|
computeUninit(c)
|
||||||
for j in 0..L-2:
|
for j in 0..<it.len-2:
|
||||||
let v = it[j]
|
let v = it[j]
|
||||||
doAssert v.kind == nkSym
|
doAssert v.kind == nkSym
|
||||||
if c.uninit.contains(v.sym.id):
|
if c.uninit.contains(v.sym.id):
|
||||||
it[L] = genDefaultCall(v.sym.typ, c, v.info)
|
it[^1] = genDefaultCall(v.sym.typ, c, v.info)
|
||||||
break
|
break
|
||||||
of nkNone..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef,
|
of nkNone..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef,
|
||||||
nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef:
|
nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef:
|
||||||
|
|
@ -734,130 +624,16 @@ proc injectDefaultCalls(n: PNode, c: var Con) =
|
||||||
for i in 0..<safeLen(n):
|
for i in 0..<safeLen(n):
|
||||||
injectDefaultCalls(n[i], c)
|
injectDefaultCalls(n[i], c)
|
||||||
|
|
||||||
proc isCursor(n: PNode): bool {.inline.} =
|
|
||||||
result = n.kind == nkSym and sfCursor in n.sym.flags
|
|
||||||
|
|
||||||
proc keepVar(n, it: PNode, c: var Con): PNode =
|
|
||||||
# keep the var but transform 'ri':
|
|
||||||
result = copyNode(n)
|
|
||||||
var itCopy = copyNode(it)
|
|
||||||
for j in 0..it.len-2:
|
|
||||||
itCopy.add it[j]
|
|
||||||
itCopy.add p(it[it.len-1], c)
|
|
||||||
result.add itCopy
|
|
||||||
|
|
||||||
proc p(n: PNode; c: var Con): PNode =
|
|
||||||
case n.kind
|
|
||||||
of nkVarSection, nkLetSection:
|
|
||||||
discard "transform; var x = y to var x; x op y where op is a move or copy"
|
|
||||||
result = newNodeI(nkStmtList, n.info)
|
|
||||||
|
|
||||||
for i in 0..<n.len:
|
|
||||||
let it = n[i]
|
|
||||||
let L = it.len
|
|
||||||
var ri = it[L-1]
|
|
||||||
if it.kind == nkVarTuple and hasDestructor(ri.typ):
|
|
||||||
let x = lowerTupleUnpacking(c.graph, it, c.owner)
|
|
||||||
result.add p(x, c)
|
|
||||||
elif it.kind == nkIdentDefs and hasDestructor(it[0].typ) and not isCursor(it[0]):
|
|
||||||
for j in 0..L-3:
|
|
||||||
let v = it[j]
|
|
||||||
if v.kind == nkSym:
|
|
||||||
if sfCompileTime in v.sym.flags: continue
|
|
||||||
# move the variable declaration to the top of the frame:
|
|
||||||
c.addTopVar v
|
|
||||||
# make sure it's destroyed at the end of the proc:
|
|
||||||
if not isUnpackedTuple(it[0].sym):
|
|
||||||
c.destroys.add genDestroy(c, v.typ, v)
|
|
||||||
if ri.kind == nkEmpty and c.inLoop > 0:
|
|
||||||
ri = genDefaultCall(v.typ, c, v.info)
|
|
||||||
if ri.kind != nkEmpty:
|
|
||||||
let r = moveOrCopy(v, ri, c)
|
|
||||||
result.add r
|
|
||||||
else:
|
|
||||||
result.add keepVar(n, it, c)
|
|
||||||
of nkCallKinds:
|
|
||||||
let parameters = n[0].typ
|
|
||||||
let L = if parameters != nil: parameters.len else: 0
|
|
||||||
for i in 1 ..< n.len:
|
|
||||||
n.sons[i] = pArg(n[i], c, i < L and isSinkTypeForParam(parameters[i]))
|
|
||||||
if n.typ != nil and hasDestructor(n.typ):
|
|
||||||
discard "produce temp creation"
|
|
||||||
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
|
|
||||||
let tmp = getTemp(c, n.typ, n.info)
|
|
||||||
var sinkExpr = genSink(c, n.typ, tmp, n)
|
|
||||||
sinkExpr.add n
|
|
||||||
result.add sinkExpr
|
|
||||||
result.add tmp
|
|
||||||
c.destroys.add genDestroy(c, n.typ, tmp)
|
|
||||||
else:
|
|
||||||
result = n
|
|
||||||
of nkAsgn, nkFastAsgn:
|
|
||||||
if hasDestructor(n[0].typ) and n[1].kind notin {nkProcDef, nkDo, nkLambda}:
|
|
||||||
# rule (self-assignment-removal):
|
|
||||||
if n[1].kind == nkSym and n[0].kind == nkSym and n[0].sym == n[1].sym:
|
|
||||||
result = newNodeI(nkEmpty, n.info)
|
|
||||||
else:
|
|
||||||
result = moveOrCopy(n[0], n[1], c)
|
|
||||||
else:
|
|
||||||
result = copyNode(n)
|
|
||||||
recurse(n, result)
|
|
||||||
of nkNone..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef,
|
|
||||||
nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef:
|
|
||||||
result = n
|
|
||||||
of nkCast, nkHiddenStdConv, nkHiddenSubConv, nkConv:
|
|
||||||
result = copyNode(n)
|
|
||||||
# Destination type
|
|
||||||
result.add n[0]
|
|
||||||
# Analyse the inner expression
|
|
||||||
result.add p(n[1], c)
|
|
||||||
of nkWhen:
|
|
||||||
# This should be a "when nimvm" node.
|
|
||||||
result = copyTree(n)
|
|
||||||
result[1][0] = p(result[1][0], c)
|
|
||||||
of nkRaiseStmt:
|
|
||||||
if optNimV2 in c.graph.config.globalOptions and n[0].kind != nkEmpty:
|
|
||||||
if n[0].kind in nkCallKinds:
|
|
||||||
let call = copyNode(n[0])
|
|
||||||
recurse(n[0], call)
|
|
||||||
result = copyNode(n)
|
|
||||||
result.add call
|
|
||||||
else:
|
|
||||||
let t = n[0].typ
|
|
||||||
let tmp = getTemp(c, t, n.info)
|
|
||||||
var m = genCopyNoCheck(c, t, tmp, n[0])
|
|
||||||
|
|
||||||
m.add p(n[0], c)
|
|
||||||
result = newTree(nkStmtList, genWasMoved(tmp, c), m)
|
|
||||||
var toDisarm = n[0]
|
|
||||||
if toDisarm.kind == nkStmtListExpr: toDisarm = toDisarm.lastSon
|
|
||||||
if toDisarm.kind == nkSym and toDisarm.sym.owner == c.owner:
|
|
||||||
result.add genWasMoved(toDisarm, c)
|
|
||||||
result.add newTree(nkRaiseStmt, tmp)
|
|
||||||
else:
|
|
||||||
result = copyNode(n)
|
|
||||||
recurse(n, result)
|
|
||||||
of nkForStmt, nkParForStmt, nkWhileStmt:
|
|
||||||
inc c.inLoop
|
|
||||||
result = copyNode(n)
|
|
||||||
recurse(n, result)
|
|
||||||
dec c.inLoop
|
|
||||||
else:
|
|
||||||
result = copyNode(n)
|
|
||||||
recurse(n, result)
|
|
||||||
|
|
||||||
proc extractDestroysForTemporaries(c: Con, destroys: PNode): PNode =
|
proc extractDestroysForTemporaries(c: Con, destroys: PNode): PNode =
|
||||||
result = newNodeI(nkStmtList, destroys.info)
|
result = newNodeI(nkStmtList, destroys.info)
|
||||||
for i in 0 ..< destroys.len:
|
for i in 0..<destroys.len:
|
||||||
if destroys[i][1][0].sym.kind == skTemp:
|
if destroys[i][1][0].sym.kind == skTemp:
|
||||||
result.add destroys[i]
|
result.add destroys[i]
|
||||||
destroys[i] = c.emptyNode
|
destroys[i] = c.emptyNode
|
||||||
|
|
||||||
proc reverseDestroys(destroys: PNode) =
|
proc reverseDestroys(destroys: seq[PNode]): seq[PNode] =
|
||||||
var reversed: seq[PNode]
|
|
||||||
for i in countdown(destroys.len - 1, 0):
|
for i in countdown(destroys.len - 1, 0):
|
||||||
reversed.add(destroys[i])
|
result.add destroys[i]
|
||||||
destroys.sons = reversed
|
|
||||||
|
|
||||||
proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode =
|
proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode =
|
||||||
if sfGeneratedOp in owner.flags or isInlineIterator(owner): return n
|
if sfGeneratedOp in owner.flags or isInlineIterator(owner): return n
|
||||||
|
|
@ -874,14 +650,15 @@ proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode =
|
||||||
if c.g[i].kind in {goto, fork}:
|
if c.g[i].kind in {goto, fork}:
|
||||||
c.jumpTargets.incl(i+c.g[i].dest)
|
c.jumpTargets.incl(i+c.g[i].dest)
|
||||||
dbg:
|
dbg:
|
||||||
echo "injecting into ", n
|
echo "\n### ", owner.name.s, ":\nCFG:"
|
||||||
echoCfg(c.g)
|
echoCfg(c.g)
|
||||||
|
echo n
|
||||||
if owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter}:
|
if owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter}:
|
||||||
let params = owner.typ.n
|
let params = owner.typ.n
|
||||||
for i in 1 ..< params.len:
|
for i in 1..<params.len:
|
||||||
let param = params[i].sym
|
let t = params[i].sym.typ
|
||||||
if isSinkTypeForParam(param.typ) and hasDestructor(param.typ.skipTypes({tySink})):
|
if isSinkTypeForParam(t) and hasDestructor(t.skipTypes({tySink})):
|
||||||
c.destroys.add genDestroy(c, param.typ.skipTypes({tyGenericInst, tyAlias, tySink}), params[i])
|
c.destroys.add genDestroy(c, params[i])
|
||||||
|
|
||||||
#if optNimV2 in c.graph.config.globalOptions:
|
#if optNimV2 in c.graph.config.globalOptions:
|
||||||
# injectDefaultCalls(n, c)
|
# injectDefaultCalls(n, c)
|
||||||
|
|
@ -890,7 +667,7 @@ proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode =
|
||||||
if c.topLevelVars.len > 0:
|
if c.topLevelVars.len > 0:
|
||||||
result.add c.topLevelVars
|
result.add c.topLevelVars
|
||||||
if c.destroys.len > 0:
|
if c.destroys.len > 0:
|
||||||
reverseDestroys(c.destroys)
|
c.destroys.sons = reverseDestroys(c.destroys.sons)
|
||||||
if owner.kind == skModule:
|
if owner.kind == skModule:
|
||||||
result.add newTryFinally(body, extractDestroysForTemporaries(c, c.destroys))
|
result.add newTryFinally(body, extractDestroysForTemporaries(c, c.destroys))
|
||||||
g.globalDestructors.add c.destroys
|
g.globalDestructors.add c.destroys
|
||||||
|
|
@ -898,8 +675,6 @@ proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode =
|
||||||
result.add newTryFinally(body, c.destroys)
|
result.add newTryFinally(body, c.destroys)
|
||||||
else:
|
else:
|
||||||
result.add body
|
result.add body
|
||||||
|
|
||||||
dbg:
|
dbg:
|
||||||
echo "------------------------------------"
|
echo ">---------transformed-to--------->"
|
||||||
echo owner.name.s, " transformed to: "
|
|
||||||
echo result
|
echo result
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ const
|
||||||
varPragmas* = declPragmas + {wVolatile, wRegister, wThreadVar,
|
varPragmas* = declPragmas + {wVolatile, wRegister, wThreadVar,
|
||||||
wMagic, wHeader, wCompilerProc, wCore, wDynlib,
|
wMagic, wHeader, wCompilerProc, wCore, wDynlib,
|
||||||
wNoInit, wCompileTime, wGlobal,
|
wNoInit, wCompileTime, wGlobal,
|
||||||
wGensym, wInject, wCodegenDecl, wGuard, wGoto, wCursor}
|
wGensym, wInject, wCodegenDecl, wGuard, wGoto}
|
||||||
constPragmas* = declPragmas + {wHeader, wMagic,
|
constPragmas* = declPragmas + {wHeader, wMagic,
|
||||||
wGensym, wInject,
|
wGensym, wInject,
|
||||||
wIntDefine, wStrDefine, wBoolDefine, wCompilerProc, wCore}
|
wIntDefine, wStrDefine, wBoolDefine, wCompilerProc, wCore}
|
||||||
|
|
@ -1103,11 +1103,6 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
|
||||||
invalidPragma(c, it)
|
invalidPragma(c, it)
|
||||||
else:
|
else:
|
||||||
sym.flags.incl sfGoto
|
sym.flags.incl sfGoto
|
||||||
of wCursor:
|
|
||||||
if sym == nil or sym.kind notin {skVar, skLet}:
|
|
||||||
invalidPragma(c, it)
|
|
||||||
else:
|
|
||||||
sym.flags.incl sfCursor
|
|
||||||
of wExportNims:
|
of wExportNims:
|
||||||
if sym == nil: invalidPragma(c, it)
|
if sym == nil: invalidPragma(c, it)
|
||||||
else: magicsys.registerNimScriptSymbol(c.graph, sym)
|
else: magicsys.registerNimScriptSymbol(c.graph, sym)
|
||||||
|
|
|
||||||
|
|
@ -663,7 +663,6 @@ proc transformFor(c: PTransf, n: PNode): PTransNode =
|
||||||
t = arg.typ
|
t = arg.typ
|
||||||
# generate a temporary and produce an assignment statement:
|
# generate a temporary and produce an assignment statement:
|
||||||
var temp = newTemp(c, t, formal.info)
|
var temp = newTemp(c, t, formal.info)
|
||||||
#temp.sym.flags.incl sfCursor
|
|
||||||
addVar(v, temp)
|
addVar(v, temp)
|
||||||
add(stmtList, newAsgnStmt(c, nkFastAsgn, temp, arg.PTransNode))
|
add(stmtList, newAsgnStmt(c, nkFastAsgn, temp, arg.PTransNode))
|
||||||
idNodeTablePut(newC.mapping, formal, temp)
|
idNodeTablePut(newC.mapping, formal, temp)
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,6 @@ type
|
||||||
wMagic, wThread, wFinal, wProfiler, wMemTracker, wObjChecks,
|
wMagic, wThread, wFinal, wProfiler, wMemTracker, wObjChecks,
|
||||||
wIntDefine, wStrDefine, wBoolDefine
|
wIntDefine, wStrDefine, wBoolDefine
|
||||||
|
|
||||||
wCursor,
|
|
||||||
|
|
||||||
wImmediate, wConstructor, wDestructor, wDelegator, wOverride,
|
wImmediate, wConstructor, wDestructor, wDelegator, wOverride,
|
||||||
wImportCpp, wImportObjC,
|
wImportCpp, wImportObjC,
|
||||||
wImportCompilerProc,
|
wImportCompilerProc,
|
||||||
|
|
@ -125,8 +123,6 @@ const
|
||||||
"magic", "thread", "final", "profiler", "memtracker", "objchecks",
|
"magic", "thread", "final", "profiler", "memtracker", "objchecks",
|
||||||
"intdefine", "strdefine", "booldefine",
|
"intdefine", "strdefine", "booldefine",
|
||||||
|
|
||||||
"cursor",
|
|
||||||
|
|
||||||
"immediate", "constructor", "destructor", "delegator", "override",
|
"immediate", "constructor", "destructor", "delegator", "override",
|
||||||
"importcpp", "importobjc",
|
"importcpp", "importobjc",
|
||||||
"importcompilerproc", "importc", "importjs", "exportc", "exportcpp", "exportnims",
|
"importcompilerproc", "importc", "importjs", "exportc", "exportcpp", "exportnims",
|
||||||
|
|
|
||||||
|
|
@ -483,7 +483,6 @@ number information are given if the program crashes or an uncaught exception
|
||||||
is raised.
|
is raised.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
DynlibOverride
|
DynlibOverride
|
||||||
==============
|
==============
|
||||||
|
|
||||||
|
|
@ -497,14 +496,6 @@ on Linux::
|
||||||
nim c --dynlibOverride:lua --passL:liblua.lib program.nim
|
nim c --dynlibOverride:lua --passL:liblua.lib program.nim
|
||||||
|
|
||||||
|
|
||||||
Cursor pragma
|
|
||||||
=============
|
|
||||||
|
|
||||||
The ``.cursor`` pragma is a temporary tool for optimization purposes
|
|
||||||
and this property will be computed by Nim's optimizer eventually. Thus it
|
|
||||||
remains undocumented.
|
|
||||||
|
|
||||||
|
|
||||||
Backend language options
|
Backend language options
|
||||||
========================
|
========================
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue