diff options
Diffstat (limited to 'compiler/destroyer.nim')
-rw-r--r-- | compiler/destroyer.nim | 221 |
1 files changed, 157 insertions, 64 deletions
diff --git a/compiler/destroyer.nim b/compiler/destroyer.nim index 2a82b6f3b..40af11e70 100644 --- a/compiler/destroyer.nim +++ b/compiler/destroyer.nim @@ -116,8 +116,8 @@ Remarks: Rule 1.2 is not yet implemented because ``sink`` is currently import intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees, - strutils, options, dfa, lowerings, tables, modulegraphs, - lineinfos + strutils, options, dfa, lowerings, tables, modulegraphs, msgs, + lineinfos, parampatterns const InterestingSyms = {skVar, skResult, skLet} @@ -127,20 +127,11 @@ type owner: PSym g: ControlFlowGraph jumpTargets: IntSet - tmpObj: PType - tmp: PSym destroys, topLevelVars: PNode - toDropBit: Table[int, PSym] graph: ModuleGraph emptyNode: 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 = # 's' is harmless if it used only once and its @@ -261,6 +252,11 @@ proc isLastRead(n: PNode; c: var Con): bool = template interestingSym(s: PSym): bool = s.owner == c.owner and s.kind in InterestingSyms and hasDestructor(s.typ) +template isUnpackedTuple(s: PSym): bool = + ## we move out all elements of unpacked tuples, + ## hence unpacked tuples themselves don't need to be destroyed + s.kind == skTemp and s.typ.kind == tyTuple + proc patchHead(n: PNode) = if n.kind in nkCallKinds and n[0].kind == nkSym and n.len > 1: let s = n[0].sym @@ -268,9 +264,6 @@ proc patchHead(n: PNode) = if sfFromGeneric in s.flags: excl(s.flags, sfFromGeneric) patchHead(s.getBody) - if n[1].typ.isNil: - # XXX toptree crashes without this workaround. Figure out why. - return let t = n[1].typ.skipTypes({tyVar, tyLent, tyGenericInst, tyAlias, tySink, tyInferred}) template patch(op, field) = if s.name.s == op and field != nil and field != s: @@ -296,6 +289,10 @@ proc checkForErrorPragma(c: Con; t: PType; ri: PNode; opname: string) = m.add c.graph.config $ c.otherRead.info localError(c.graph.config, ri.info, errGenerated, m) +proc makePtrType(c: Con, baseType: PType): PType = + result = newType(tyPtr, c.owner) + addSonSkipIntLit(result, baseType) + template genOp(opr, opname, ri) = let op = opr if op == nil: @@ -304,7 +301,9 @@ template genOp(opr, opname, ri) = globalError(c.graph.config, dest.info, "internal error: '" & opname & "' operator is generic") patchHead op if sfError in op.flags: checkForErrorPragma(c, t, ri, opname) - result = newTree(nkCall, newSymNode(op), newTree(nkHiddenAddr, dest)) + let addrExp = newNodeIT(nkHiddenAddr, dest.info, makePtrType(c, dest.typ)) + addrExp.add(dest) + result = newTree(nkCall, newSymNode(op), addrExp) proc genSink(c: Con; t: PType; dest, ri: PNode): PNode = let t = t.skipTypes({tyGenericInst, tyAlias, tySink}) @@ -321,22 +320,11 @@ proc genDestroy(c: Con; t: PType; dest: PNode): PNode = proc addTopVar(c: var Con; v: PNode) = c.topLevelVars.add newTree(nkIdentDefs, v, c.emptyNode, c.emptyNode) -proc dropBit(c: var Con; s: PSym): PSym = - result = c.toDropBit.getOrDefault(s.id) - assert result != nil - -proc registerDropBit(c: var Con; s: PSym) = - 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 getTemp(c: var Con; typ: PType; info: TLineInfo): PNode = + let sym = newSym(skTemp, getIdent(c.graph.cache, ":tmpD"), c.owner, info) + sym.typ = typ + result = newSymNode(sym) + c.addTopVar(result) proc p(n: PNode; c: var Con): PNode @@ -347,16 +335,6 @@ template recurse(n, dest) = proc isSinkParam(s: PSym): bool {.inline.} = 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 = result = newNodeI(nkCall, n.info) result.add(newSymNode(createMagic(c.graph, magicname, m))) @@ -387,6 +365,12 @@ proc destructiveMoveVar(n: PNode; c: var Con): PNode = result.add genWasMoved(n, c) 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 = result = newNodeIT(nkStmtListExpr, n.info, n.typ) let tmp = getTemp(c, n.typ, n.info) @@ -394,14 +378,20 @@ proc passCopyToSink(n: PNode; c: var Con): PNode = var m = genCopy(c, n.typ, tmp, n) m.add p(n, c) result.add m - message(c.graph.config, n.info, hintPerformance, - ("passing '$1' to a sink parameter introduces an implicit copy; " & - "use 'move($1)' to prevent it") % $n) + if isLValue(n): + message(c.graph.config, n.info, hintPerformance, + ("passing '$1' to a sink parameter introduces an implicit copy; " & + "use 'move($1)' to prevent it") % $n) else: result.add newTree(nkAsgn, tmp, p(n, c)) result.add tmp 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 arg.kind in nkCallKinds: # recurse but skip the call expression in order to prevent @@ -412,17 +402,53 @@ proc pArg(arg: PNode; c: var Con; isSink: bool): PNode = 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 {nkBracket, nkObjConstr, nkTupleConstr, nkBracket, nkCharLit..nkFloat128Lit}: discard "object construction to sink parameter: nothing to do" result = arg + elif arg.kind == nkSym and isSinkParam(arg.sym): + # Sinked params can be consumed only once. We need to reset the memory + # 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): - # 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: + # 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 == nkSym and isSinkParam(arg.sym): - # mark the sink parameter as used: - result = destructiveMoveSink(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: # an object that is not temporary but passed to a 'sink' parameter # results in a copy. @@ -431,6 +457,11 @@ proc pArg(arg: PNode; c: var Con; isSink: bool): PNode = result = p(arg, c) 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 of nkCallKinds: result = genSink(c, dest.typ, dest, ri) @@ -443,6 +474,57 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode = ri2.add pArg(ri[i], c, i < L and parameters[i].kind == tySink) #recurse(ri, ri2) result.add ri2 + of nkBracketExpr: + if ri[0].kind == nkSym and isUnpackedTuple(ri[0].sym): + # unpacking of tuple: move out the elements + result = genSink(c, dest.typ, dest, ri) + else: + result = genCopy(c, dest.typ, dest, ri) + 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: + # 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: result = genSink(c, dest.typ, dest, ri) let ri2 = copyTree(ri) @@ -451,15 +533,29 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode = # so these all act like 'sink' parameters: ri2[i].sons[1] = pArg(ri[i][1], c, isSink = true) result.add ri2 + of nkTupleConstr: + result = genSink(c, dest.typ, dest, ri) + let ri2 = copyTree(ri) + for i in 0..<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 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) 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)) - elif isSinkParam(ri.sym): - result = genSink(c, dest.typ, dest, ri) - result.add destructiveMoveSink(ri, c) else: result = genCopy(c, dest.typ, dest, ri) result.add p(ri, c) @@ -487,7 +583,8 @@ proc p(n: PNode; c: var Con): PNode = # move the variable declaration to the top of the frame: c.addTopVar v # 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: let r = moveOrCopy(v, ri, c) result.add r @@ -534,12 +631,8 @@ proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode = echo "injecting into ", n var c: Con 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.topLevelVars = newNodeI(nkVarSection, n.info) - c.toDropBit = initTable[int, PSym]() c.graph = g c.emptyNode = newNodeI(nkEmpty, n.info) let cfg = constructCfg(owner, n) @@ -554,10 +647,10 @@ proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode = let params = owner.typ.n for i in 1 ..< params.len: let param = params[i].sym - if param.typ.kind == tySink: registerDropBit(c, param) + if param.typ.kind == tySink and hasDestructor(param.typ): + c.destroys.add genDestroy(c, param.typ.skipTypes({tyGenericInst, tyAlias, tySink}), params[i]) + let body = p(n, c) - if c.tmp.typ.n.len > 0: - c.addTopVar(newSymNode c.tmp) result = newNodeI(nkStmtList, n.info) if c.topLevelVars.len > 0: result.add c.topLevelVars |