destructors: optimize more assignments into moves; also fixes #9294
This commit is contained in:
parent
2fecf4f36a
commit
fcca59e415
5 changed files with 143 additions and 9 deletions
|
|
@ -133,6 +133,7 @@ type
|
||||||
toDropBit: Table[int, PSym]
|
toDropBit: Table[int, PSym]
|
||||||
graph: ModuleGraph
|
graph: ModuleGraph
|
||||||
emptyNode: PNode
|
emptyNode: PNode
|
||||||
|
otherRead: PNode
|
||||||
|
|
||||||
proc getTemp(c: var Con; typ: PType; info: TLineInfo): PNode =
|
proc getTemp(c: var Con; typ: PType; info: TLineInfo): PNode =
|
||||||
# XXX why are temps fields in an object here?
|
# XXX why are temps fields in an object here?
|
||||||
|
|
@ -181,7 +182,7 @@ proc isHarmlessVar*(s: PSym; c: Con): bool =
|
||||||
if c.g[i].sym == s:
|
if c.g[i].sym == s:
|
||||||
if defsite < 0: defsite = i
|
if defsite < 0: defsite = i
|
||||||
else: return false
|
else: return false
|
||||||
of use, useWithinCall:
|
of use:
|
||||||
if c.g[i].sym == s:
|
if c.g[i].sym == s:
|
||||||
if defsite < 0: return false
|
if defsite < 0: return false
|
||||||
for j in defsite .. i:
|
for j in defsite .. i:
|
||||||
|
|
@ -196,6 +197,66 @@ proc isHarmlessVar*(s: PSym; c: Con): bool =
|
||||||
discard "we do not perform an abstract interpretation yet"
|
discard "we do not perform an abstract interpretation yet"
|
||||||
result = usages <= 1
|
result = usages <= 1
|
||||||
|
|
||||||
|
proc isLastRead(n: PNode; c: var Con): bool =
|
||||||
|
# first we need to search for the instruction that belongs to 'n':
|
||||||
|
doAssert n.kind == nkSym
|
||||||
|
c.otherRead = nil
|
||||||
|
var instr = -1
|
||||||
|
for i in 0..<c.g.len:
|
||||||
|
if c.g[i].n == n:
|
||||||
|
if instr < 0: instr = i
|
||||||
|
else:
|
||||||
|
# eh, we found two positions that belong to 'n'?
|
||||||
|
# better return 'false' then:
|
||||||
|
return false
|
||||||
|
if instr < 0: return false
|
||||||
|
# we go through all paths beginning from 'instr+1' and need to
|
||||||
|
# ensure that we don't find another 'use X' instruction.
|
||||||
|
if instr+1 >= c.g.len: return true
|
||||||
|
let s = n.sym
|
||||||
|
var pcs: seq[int] = @[instr+1]
|
||||||
|
var takenGotos: IntSet
|
||||||
|
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.
|
||||||
|
when false:
|
||||||
|
# Too complex thinking ahead: In reality it is enough to find
|
||||||
|
# the 'def x' here on the current path to make the 'use x' valid.
|
||||||
|
# but for this the definition needs to dominate the usage:
|
||||||
|
var dominates = true
|
||||||
|
for j in pc+1 .. instr:
|
||||||
|
# not within the same basic block?
|
||||||
|
if c.g[j].kind in {goto, fork} and (j + c.g[j].dest) in (pc+1 .. instr):
|
||||||
|
#if j in c.jumpTargets:
|
||||||
|
dominates = false
|
||||||
|
if dominates: break
|
||||||
|
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 takenGotos.containsOrIncl(pc):
|
||||||
|
pcs.add pc + c.g[pc].dest
|
||||||
|
inc pc
|
||||||
|
#echo c.graph.config $ n.info, " last read here!"
|
||||||
|
return true
|
||||||
|
|
||||||
template interestingSym(s: PSym): bool =
|
template interestingSym(s: PSym): bool =
|
||||||
s.owner == c.owner and s.kind in InterestingSyms and hasDestructor(s.typ)
|
s.owner == c.owner and s.kind in InterestingSyms and hasDestructor(s.typ)
|
||||||
|
|
||||||
|
|
@ -229,6 +290,9 @@ proc checkForErrorPragma(c: Con; t: PType; ri: PNode; opname: string) =
|
||||||
m.add "; requires a copy because it's not the last read of '"
|
m.add "; requires a copy because it's not the last read of '"
|
||||||
m.add renderTree(ri)
|
m.add renderTree(ri)
|
||||||
m.add '\''
|
m.add '\''
|
||||||
|
if c.otherRead != nil:
|
||||||
|
m.add "; another read is done here: "
|
||||||
|
m.add c.graph.config $ c.otherRead.info
|
||||||
localError(c.graph.config, ri.info, errGenerated, m)
|
localError(c.graph.config, ri.info, errGenerated, m)
|
||||||
|
|
||||||
template genOp(opr, opname, ri) =
|
template genOp(opr, opname, ri) =
|
||||||
|
|
@ -303,6 +367,8 @@ proc genWasMoved(n: PNode; c: var Con): PNode =
|
||||||
|
|
||||||
proc destructiveMoveVar(n: PNode; c: var Con): PNode =
|
proc destructiveMoveVar(n: PNode; c: var Con): PNode =
|
||||||
# generate: (let tmp = v; reset(v); tmp)
|
# generate: (let tmp = v; reset(v); tmp)
|
||||||
|
# XXX: Strictly speaking we can only move if there is a ``=sink`` defined
|
||||||
|
# or if no ``=sink`` is defined and also no assignment.
|
||||||
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
|
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
|
||||||
|
|
||||||
var temp = newSym(skLet, getIdent(c.graph.cache, "blitTmp"), c.owner, n.info)
|
var temp = newSym(skLet, getIdent(c.graph.cache, "blitTmp"), c.owner, n.info)
|
||||||
|
|
@ -339,13 +405,16 @@ proc pArg(arg: PNode; c: var Con; isSink: bool): PNode =
|
||||||
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
|
||||||
# destructor injections: Rule 5.1 is different from rule 5.4!
|
# destructor injections: Rule 5.1 is different from rule 5.4!
|
||||||
let a = copyNode(arg)
|
result = copyNode(arg)
|
||||||
recurse(arg, a)
|
let parameters = arg[0].typ
|
||||||
result = a
|
let L = if parameters != nil: parameters.len else: 0
|
||||||
|
result.add arg[0]
|
||||||
|
for i in 1..<arg.len:
|
||||||
|
result.add pArg(arg[i], c, i < L and parameters[i].kind == tySink)
|
||||||
elif arg.kind in {nkObjConstr, nkCharLit..nkFloat128Lit}:
|
elif arg.kind in {nkObjConstr, nkCharLit..nkFloat128Lit}:
|
||||||
discard "object construction to sink parameter: nothing to do"
|
discard "object construction to sink parameter: nothing to do"
|
||||||
result = arg
|
result = arg
|
||||||
elif arg.kind == nkSym and arg.sym.kind in InterestingSyms and isHarmlessVar(arg.sym, c):
|
elif arg.kind == nkSym and arg.sym.kind in InterestingSyms and isLastRead(arg, c):
|
||||||
# if x is a variable and it its last read we eliminate its
|
# if x is a variable and it its last read we eliminate its
|
||||||
# destructor invokation, but don't. We need to reset its memory
|
# destructor invokation, but don't. We need to reset its memory
|
||||||
# to disable its destructor which we have not elided:
|
# to disable its destructor which we have not elided:
|
||||||
|
|
@ -382,7 +451,7 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
|
||||||
ri2[i].sons[1] = pArg(ri[i][1], c, isSink = true)
|
ri2[i].sons[1] = pArg(ri[i][1], c, isSink = true)
|
||||||
result.add ri2
|
result.add ri2
|
||||||
of nkSym:
|
of nkSym:
|
||||||
if ri.sym.kind != skParam and isHarmlessVar(ri.sym, c):
|
if ri.sym.kind != skParam 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.typ, dest, ri)
|
||||||
snk.add p(ri, c)
|
snk.add p(ri, c)
|
||||||
|
|
@ -478,6 +547,8 @@ proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode =
|
||||||
for i in 0..<c.g.len:
|
for i in 0..<c.g.len:
|
||||||
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)
|
||||||
|
#if owner.name.s == "test0p1":
|
||||||
|
# echoCfg(c.g)
|
||||||
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:
|
||||||
|
|
@ -495,7 +566,7 @@ proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode =
|
||||||
result.add body
|
result.add body
|
||||||
|
|
||||||
when defined(nimDebugDestroys):
|
when defined(nimDebugDestroys):
|
||||||
if owner.name.s == "test1": # or true:
|
if true:
|
||||||
echo "------------------------------------"
|
echo "------------------------------------"
|
||||||
echo owner.name.s, " transformed to: "
|
echo owner.name.s, " transformed to: "
|
||||||
echo result
|
echo result
|
||||||
|
|
|
||||||
|
|
@ -19,9 +19,12 @@ proc `=`(lhs: var T, rhs: T) =
|
||||||
proc `=destroy`(v: var T) =
|
proc `=destroy`(v: var T) =
|
||||||
echo "destroy"
|
echo "destroy"
|
||||||
|
|
||||||
|
proc use(x: T) = discard
|
||||||
|
|
||||||
proc usedToBeBlock =
|
proc usedToBeBlock =
|
||||||
var v1 : T
|
var v1 : T
|
||||||
var v2 : T = v1
|
var v2 : T = v1
|
||||||
|
use v1
|
||||||
|
|
||||||
usedToBeBlock()
|
usedToBeBlock()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
discard """
|
discard """
|
||||||
output: '''after 3 3
|
output: '''after 2 2
|
||||||
after 3 3
|
after 2 2
|
||||||
|
after 2 2
|
||||||
after 2 2'''
|
after 2 2'''
|
||||||
"""
|
"""
|
||||||
# bug #9263
|
# bug #9263
|
||||||
|
|
@ -107,6 +108,11 @@ proc test3 =
|
||||||
# a = a - b
|
# a = a - b
|
||||||
b = -b + a
|
b = -b + a
|
||||||
|
|
||||||
|
proc test4 =
|
||||||
|
# bug #9294
|
||||||
|
var a = matrix(5, 5, 1.0)
|
||||||
|
a = -a + a
|
||||||
|
|
||||||
test1()
|
test1()
|
||||||
info()
|
info()
|
||||||
|
|
||||||
|
|
@ -115,3 +121,6 @@ info()
|
||||||
|
|
||||||
test3()
|
test3()
|
||||||
info()
|
info()
|
||||||
|
|
||||||
|
test4()
|
||||||
|
info()
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,8 @@ proc write(t: opt[Tree]) =
|
||||||
write stdout, it.data, "\n"
|
write stdout, it.data, "\n"
|
||||||
write(it.ri)
|
write(it.ri)
|
||||||
|
|
||||||
|
proc use(t: opt[Tree]) = discard
|
||||||
|
|
||||||
proc main =
|
proc main =
|
||||||
var t: opt[Tree]
|
var t: opt[Tree]
|
||||||
insert t, 60.0
|
insert t, 60.0
|
||||||
|
|
@ -99,6 +101,7 @@ proc main =
|
||||||
write t
|
write t
|
||||||
let copy = t
|
let copy = t
|
||||||
write copy
|
write copy
|
||||||
|
use t
|
||||||
|
|
||||||
main()
|
main()
|
||||||
echo allocCount, " ", deallocCount
|
echo allocCount, " ", deallocCount
|
||||||
|
|
|
||||||
48
tests/destructor/tprevent_assign2.nim
Normal file
48
tests/destructor/tprevent_assign2.nim
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
discard """
|
||||||
|
errormsg: "'=' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'"
|
||||||
|
line: 44
|
||||||
|
"""
|
||||||
|
|
||||||
|
type
|
||||||
|
Foo = object
|
||||||
|
x: int
|
||||||
|
|
||||||
|
proc `=destroy`(f: var Foo) = f.x = 0
|
||||||
|
proc `=`(a: var Foo; b: Foo) {.error.} # = a.x = b.x
|
||||||
|
proc `=sink`(a: var Foo; b: Foo) = a.x = b.x
|
||||||
|
|
||||||
|
proc createTree(x: int): Foo =
|
||||||
|
Foo(x: x)
|
||||||
|
|
||||||
|
proc take2(a, b: sink Foo) =
|
||||||
|
echo a.x, " ", b.x
|
||||||
|
|
||||||
|
proc allowThis() =
|
||||||
|
var otherTree: Foo
|
||||||
|
for i in 0..3:
|
||||||
|
while true:
|
||||||
|
#if i == 0:
|
||||||
|
otherTree = createTree(44)
|
||||||
|
case i
|
||||||
|
of 0:
|
||||||
|
echo otherTree
|
||||||
|
take2(createTree(34), otherTree)
|
||||||
|
of 1:
|
||||||
|
take2(createTree(34), otherTree)
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
|
||||||
|
proc preventThis() =
|
||||||
|
var otherTree: Foo
|
||||||
|
for i in 0..3:
|
||||||
|
while true:
|
||||||
|
if i == 0:
|
||||||
|
otherTree = createTree(44)
|
||||||
|
case i
|
||||||
|
of 0:
|
||||||
|
echo otherTree
|
||||||
|
take2(createTree(34), otherTree)
|
||||||
|
of 1:
|
||||||
|
take2(createTree(34), otherTree)
|
||||||
|
else:
|
||||||
|
discard
|
||||||
Loading…
Add table
Add a link
Reference in a new issue