Merge pull request #9826 from cooldome/destructor_move_them_all

destructors: sink`em all
This commit is contained in:
Andreas Rumpf 2018-12-10 15:46:19 +01:00 • committed by GitHub
commit cd81f368d1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 179 additions and 71 deletions

View file

@ -116,7 +116,7 @@ Remarks: Rule 1.2 is not yet implemented because ``sink`` is currently
import import
intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees, intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees,
strutils, options, dfa, lowerings, tables, modulegraphs, strutils, options, dfa, lowerings, tables, modulegraphs, msgs,
lineinfos, parampatterns lineinfos, parampatterns
const const
@ -127,20 +127,11 @@ type
owner: PSym owner: PSym
g: ControlFlowGraph g: ControlFlowGraph
jumpTargets: IntSet jumpTargets: IntSet
tmpObj: PType
tmp: PSym
destroys, topLevelVars: PNode destroys, topLevelVars: PNode
toDropBit: Table[int, PSym]
graph: ModuleGraph graph: ModuleGraph
emptyNode: PNode emptyNode: PNode
otherRead: PNode otherRead: PNode
proc getTemp(c: var Con; typ: PType; info: TLineInfo): PNode =
# XXX why are temps fields in an object here?
let f = newSym(skField, getIdent(c.graph.cache, ":d" & $c.tmpObj.n.len), c.owner, info)
f.typ = typ
rawAddField c.tmpObj, f
result = rawDirectAccess(c.tmp, f)
proc isHarmlessVar*(s: PSym; c: Con): bool = proc isHarmlessVar*(s: PSym; c: Con): bool =
# 's' is harmless if it used only once and its # 's' is harmless if it used only once and its
@ -329,22 +320,11 @@ proc genDestroy(c: Con; t: PType; dest: PNode): PNode =
proc addTopVar(c: var Con; v: PNode) = proc addTopVar(c: var Con; v: PNode) =
c.topLevelVars.add newTree(nkIdentDefs, v, c.emptyNode, c.emptyNode) c.topLevelVars.add newTree(nkIdentDefs, v, c.emptyNode, c.emptyNode)
proc dropBit(c: var Con; s: PSym): PSym = proc getTemp(c: var Con; typ: PType; info: TLineInfo): PNode =
result = c.toDropBit.getOrDefault(s.id) let sym = newSym(skTemp, getIdent(c.graph.cache, ":tmpD"), c.owner, info)
assert result != nil sym.typ = typ
result = newSymNode(sym)
proc registerDropBit(c: var Con; s: PSym) = c.addTopVar(result)
let result = newSym(skTemp, getIdent(c.graph.cache, s.name.s & "_AliveBit"), c.owner, s.info)
result.typ = getSysType(c.graph, s.info, tyBool)
let trueVal = newIntTypeNode(nkIntLit, 1, result.typ)
c.topLevelVars.add newTree(nkIdentDefs, newSymNode result, c.emptyNode, trueVal)
c.toDropBit[s.id] = result
# generate:
# if not sinkParam_AliveBit: `=destroy`(sinkParam)
let t = s.typ.skipTypes({tyGenericInst, tyAlias, tySink})
if t.destructor != nil:
c.destroys.add newTree(nkIfStmt,
newTree(nkElifBranch, newSymNode result, genDestroy(c, t, newSymNode s)))
proc p(n: PNode; c: var Con): PNode proc p(n: PNode; c: var Con): PNode
@ -355,16 +335,6 @@ template recurse(n, dest) =
proc isSinkParam(s: PSym): bool {.inline.} = proc isSinkParam(s: PSym): bool {.inline.} =
result = s.kind == skParam and s.typ.kind == tySink result = s.kind == skParam and s.typ.kind == tySink
proc destructiveMoveSink(n: PNode; c: var Con): PNode =
# generate: (chckMove(sinkParam_AliveBit); sinkParam_AliveBit = false; sinkParam)
result = newNodeIT(nkStmtListExpr, n.info, n.typ)
let bit = newSymNode dropBit(c, n.sym)
if optMoveCheck in c.owner.options:
result.add callCodegenProc(c.graph, "chckMove", bit.info, bit)
result.add newTree(nkAsgn, bit,
newIntTypeNode(nkIntLit, 0, getSysType(c.graph, n.info, tyBool)))
result.add n
proc genMagicCall(n: PNode; c: var Con; magicname: string; m: TMagic): PNode = proc genMagicCall(n: PNode; c: var Con; magicname: string; m: TMagic): PNode =
result = newNodeI(nkCall, n.info) result = newNodeI(nkCall, n.info)
result.add(newSymNode(createMagic(c.graph, magicname, m))) result.add(newSymNode(createMagic(c.graph, magicname, m)))
@ -395,6 +365,12 @@ proc destructiveMoveVar(n: PNode; c: var Con): PNode =
result.add genWasMoved(n, c) result.add genWasMoved(n, c)
result.add tempAsNode result.add tempAsNode
proc sinkParamIsLastReadCheck(c: var Con, s: PNode) =
assert s.kind == nkSym and s.sym.kind == skParam
if not isLastRead(s, c):
localError(c.graph.config, c.otherRead.info, "sink parameter `" & $s.sym.name.s &
"` is already consumed at " & toFileLineCol(c. graph.config, s.info))
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)
@ -411,6 +387,11 @@ proc passCopyToSink(n: PNode; c: var Con): PNode =
result.add tmp result.add tmp
proc pArg(arg: PNode; c: var Con; isSink: bool): PNode = proc pArg(arg: PNode; c: var Con; isSink: bool): PNode =
template pArgIfTyped(arg_part: PNode): PNode =
# typ is nil if we are in if/case expr branch with noreturn
if arg_part.typ == nil: p(arg_part, c)
else: pArg(arg_part, c, isSink)
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
@ -421,17 +402,53 @@ proc pArg(arg: PNode; c: var Con; isSink: bool): PNode =
result.add arg[0] result.add arg[0]
for i in 1..<arg.len: for i in 1..<arg.len:
result.add pArg(arg[i], c, i < L and parameters[i].kind == tySink) result.add pArg(arg[i], c, i < L and parameters[i].kind == tySink)
elif arg.kind in {nkObjConstr, nkCharLit..nkFloat128Lit}: elif arg.kind in {nkBracket, nkObjConstr, nkTupleConstr, nkBracket, 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 isLastRead(arg, c):
# if x is a variable and it its last read we eliminate its
# destructor invokation, but don't. We need to reset its memory
# to disable its destructor which we have not elided:
result = destructiveMoveVar(arg, c)
elif arg.kind == nkSym and isSinkParam(arg.sym): elif arg.kind == nkSym and isSinkParam(arg.sym):
# mark the sink parameter as used: # Sinked params can be consumed only once. We need to reset the memory
result = destructiveMoveSink(arg, c) # to disable the destructor which we have not elided
sinkParamIsLastReadCheck(c, arg)
result = destructiveMoveVar(arg, c)
elif arg.kind == nkSym and arg.sym.kind in InterestingSyms and isLastRead(arg, c):
# it is the last read, can be sinked. We need to reset the memory
# to disable the destructor which we have not elided
result = destructiveMoveVar(arg, c)
elif arg.kind in {nkBlockExpr, nkBlockStmt}:
result = copyNode(arg)
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.
@ -440,6 +457,11 @@ proc pArg(arg: PNode; c: var Con; isSink: bool): PNode =
result = p(arg, c) result = p(arg, c)
proc moveOrCopy(dest, ri: PNode; c: var Con): PNode = proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
template moveOrCopyIfTyped(ri_part: PNode): PNode =
# typ is nil if we are in if/case expr branch with noreturn
if ri_part.typ == nil: p(ri_part, c)
else: moveOrCopy(dest, ri_part, c)
case ri.kind case ri.kind
of nkCallKinds: of nkCallKinds:
result = genSink(c, dest.typ, dest, ri) result = genSink(c, dest.typ, dest, ri)
@ -464,6 +486,45 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
for i in 0..ri.len-2: for i in 0..ri.len-2:
result.add p(ri[i], c) result.add p(ri[i], c)
result.add moveOrCopy(dest, ri[^1], 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:
# array constructor
result = genSink(c, dest.typ, dest, ri)
let ri2 = copyTree(ri)
for i in 0..<ri.len:
# everything that is passed to an array constructor is consumed,
# so these all act like 'sink' parameters:
ri2[i] = pArg(ri[i], c, isSink = true)
result.add ri2
of nkObjConstr: of nkObjConstr:
result = genSink(c, dest.typ, dest, ri) result = genSink(c, dest.typ, dest, ri)
let ri2 = copyTree(ri) let ri2 = copyTree(ri)
@ -484,14 +545,17 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
ri2[i] = pArg(ri[i], c, isSink = true) ri2[i] = pArg(ri[i], c, isSink = true)
result.add ri2 result.add ri2
of nkSym: of nkSym:
if ri.sym.kind != skParam and isLastRead(ri, c): if isSinkParam(ri.sym):
# Rule 3: `=sink`(x, z); wasMoved(z)
sinkParamIsLastReadCheck(c, ri)
var snk = genSink(c, dest.typ, dest, ri)
snk.add ri
result = newTree(nkStmtList, snk, genMagicCall(ri, c, "wasMoved", mWasMoved))
elif 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 ri
result = newTree(nkStmtList, snk, genMagicCall(ri, c, "wasMoved", mWasMoved)) result = newTree(nkStmtList, snk, genMagicCall(ri, c, "wasMoved", mWasMoved))
elif isSinkParam(ri.sym):
result = genSink(c, dest.typ, dest, ri)
result.add destructiveMoveSink(ri, c)
else: else:
result = genCopy(c, dest.typ, dest, ri) result = genCopy(c, dest.typ, dest, ri)
result.add p(ri, c) result.add p(ri, c)
@ -512,14 +576,15 @@ proc p(n: PNode; c: var Con): PNode =
if it.kind == nkVarTuple and hasDestructor(ri.typ): if it.kind == nkVarTuple and hasDestructor(ri.typ):
let x = lowerTupleUnpacking(c.graph, it, c.owner) let x = lowerTupleUnpacking(c.graph, it, c.owner)
result.add p(x, c) result.add p(x, c)
elif it.kind == nkIdentDefs and hasDestructor(it[0].typ) and not isUnpackedTuple(it[0].sym): elif it.kind == nkIdentDefs and hasDestructor(it[0].typ):
for j in 0..L-2: for j in 0..L-2:
let v = it[j] let v = it[j]
doAssert v.kind == nkSym doAssert v.kind == nkSym
# move the variable declaration to the top of the frame: # move the variable declaration to the top of the frame:
c.addTopVar v c.addTopVar v
# make sure it's destroyed at the end of the proc: # make sure it's destroyed at the end of the proc:
c.destroys.add genDestroy(c, v.typ, v) if not isUnpackedTuple(it[0].sym):
c.destroys.add genDestroy(c, v.typ, v)
if ri.kind != nkEmpty: if ri.kind != nkEmpty:
let r = moveOrCopy(v, ri, c) let r = moveOrCopy(v, ri, c)
result.add r result.add r
@ -566,12 +631,8 @@ proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode =
echo "injecting into ", n echo "injecting into ", n
var c: Con var c: Con
c.owner = owner c.owner = owner
c.tmp = newSym(skTemp, getIdent(g.cache, ":d"), owner, n.info)
c.tmpObj = createObj(g, owner, n.info)
c.tmp.typ = c.tmpObj
c.destroys = newNodeI(nkStmtList, n.info) c.destroys = newNodeI(nkStmtList, n.info)
c.topLevelVars = newNodeI(nkVarSection, n.info) c.topLevelVars = newNodeI(nkVarSection, n.info)
c.toDropBit = initTable[int, PSym]()
c.graph = g c.graph = g
c.emptyNode = newNodeI(nkEmpty, n.info) c.emptyNode = newNodeI(nkEmpty, n.info)
let cfg = constructCfg(owner, n) let cfg = constructCfg(owner, n)
@ -586,10 +647,10 @@ proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode =
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 param = params[i].sym
if param.typ.kind == tySink: registerDropBit(c, param) if param.typ.kind == tySink:
c.destroys.add genDestroy(c, param.typ.skipTypes({tyGenericInst, tyAlias, tySink}), params[i])
let body = p(n, c) let body = p(n, c)
if c.tmp.typ.n.len > 0:
c.addTopVar(newSymNode c.tmp)
result = newNodeI(nkStmtList, n.info) result = newNodeI(nkStmtList, n.info)
if c.topLevelVars.len > 0: if c.topLevelVars.len > 0:
result.add c.topLevelVars result.add c.topLevelVars

View file

@ -272,7 +272,7 @@ proc genReturn(c: var Con; n: PNode) =
c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len) c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len)
const const
InterestingSyms = {skVar, skResult, skLet} InterestingSyms = {skVar, skResult, skLet, skParam}
proc genUse(c: var Con; n: PNode) = proc genUse(c: var Con; n: PNode) =
var n = n var n = n

View file

@ -52,6 +52,8 @@ proc matrix*(m, n: int, s: float): Matrix =
for i in 0 ..< m * n: for i in 0 ..< m * n:
result.data[i] = s result.data[i] = s
proc len(m: Matrix): int = m.n * m.m
proc `[]`*(m: Matrix, i, j: int): float {.inline.} = proc `[]`*(m: Matrix, i, j: int): float {.inline.} =
## Get a single element. ## Get a single element.
m.data[i * m.n + j] m.data[i * m.n + j]
@ -67,24 +69,26 @@ proc `[]=`*(m: var Matrix, i, j: int, s: float) =
proc `-`*(m: sink Matrix): Matrix = proc `-`*(m: sink Matrix): Matrix =
## Unary minus ## Unary minus
result = m result = m
for i in 0 ..< m.m: for i in 0 ..< result.m:
for j in 0 ..< m.n: for j in 0 ..< result.n:
result[i, j] = -m[i, j] result[i, j] = -result[i, j]
proc `+`*(a: sink Matrix; b: Matrix): Matrix = proc `+`*(a: sink Matrix; b: Matrix): Matrix =
## ``C = A + B`` ## ``C = A + B``
assert(b.m == a.m and b.n == a.n, "Matrix dimensions must agree.") doAssert(b.m == a.m and b.n == a.n, "Matrix dimensions must agree.")
doAssert(a.len == b.len) # non destructive use before sink is ok
result = a result = a
for i in 0 ..< a.m: for i in 0 ..< result.m:
for j in 0 ..< a.n: for j in 0 ..< result.n:
result[i, j] = a[i, j] + b[i, j] result[i, j] = result[i, j] + b[i, j]
proc `-`*(a: sink Matrix; b: Matrix): Matrix = proc `-`*(a: sink Matrix; b: Matrix): Matrix =
## ``C = A - B`` ## ``C = A - B``
assert(b.m == a.m and b.n == a.n, "Matrix dimensions must agree.") assert(b.m == a.m and b.n == a.n, "Matrix dimensions must agree.")
doAssert(a.len == b.len) # non destructive use before sink is ok
result = a result = a
for i in 0 ..< a.m: for i in 0 ..< result.m:
for j in 0 ..< a.n: for j in 0 ..< result.n:
result[i, j] = a[i, j] - b[i, j] result[i, j] = a[i, j] - b[i, j]
proc info = proc info =

View file

@ -109,7 +109,19 @@ proc myfunc(x, y: int): (MySeqNonCopyable, MySeqNonCopyable) =
proc myfunc2(x, y: int): tuple[a: MySeqNonCopyable, b:int, c:MySeqNonCopyable] = proc myfunc2(x, y: int): tuple[a: MySeqNonCopyable, b:int, c:MySeqNonCopyable] =
var cc = newMySeq(y, 5.0) var cc = newMySeq(y, 5.0)
(a: newMySeq(x, 1.0), b:0, c: cc) (a: case x:
of 1:
let (z1, z2) = myfunc(x,y)
z2
elif x > 5: raise newException(ValueError, "new error")
else: newMySeq(x, 1.0),
b: 0,
c: block:
var tmp = if y > 0: move(cc) else: newMySeq(1, 3.0)
tmp[0] = 5
tmp
)
let (seq1, seq2) = myfunc(2, 3) let (seq1, seq2) = myfunc(2, 3)
doAssert seq1.len == 2 doAssert seq1.len == 2
@ -123,3 +135,34 @@ doAssert seq3[0] == 1.0
var seq4, seq5: MySeqNonCopyable var seq4, seq5: MySeqNonCopyable
(seq4, i, seq5) = myfunc2(2, 3) (seq4, i, seq5) = myfunc2(2, 3)
seq4 = block:
var tmp = newMySeq(4, 1.0)
tmp[0] = 3.0
tmp
doAssert seq4[0] == 3.0
import macros
seq4 =
if i > 0: newMySeq(2, 5.0)
elif i < -100: raise newException(ValueError, "Parse Error")
else: newMySeq(2, 3.0)
seq4 =
case (char) i:
of 'A', {'W'..'Z'}: newMySeq(2, 5.0)
of 'B': quit(-1)
else:
let (x1, x2, x3) = myfunc2(2, 3)
x3
#------------------------------------------------------------
#-- Move into array constructor
#------------------------------------------------------------
var ii = 1
let arr2 = [newMySeq(2, 5.0), if i > 1: newMySeq(3, 1.0) else: newMySeq(0, 0.0)]
var seqOfSeq2 = @[newMySeq(2, 5.0), newMySeq(3, 1.0)]