diff options
Diffstat (limited to 'compiler/destroyer.nim')
-rw-r--r-- | compiler/destroyer.nim | 247 |
1 files changed, 143 insertions, 104 deletions
diff --git a/compiler/destroyer.nim b/compiler/destroyer.nim index e7ff00bb9..db26c5366 100644 --- a/compiler/destroyer.nim +++ b/compiler/destroyer.nim @@ -76,7 +76,10 @@ ## 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. +## 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: @@ -94,103 +97,17 @@ import template hasDestructor(t: PType): bool = tfHasAsgn in t.flags -when false: - type - VarInfo = object - hasInitValue: bool - addrTaken: bool - assigned: int # we don't care about the 'var' vs 'let' - # distinction; it's an optimization pass - read: int - scope: int # the scope the variable is declared in - - Con = object - t: Table[int, VarInfo] - owner: PSym - scope: int - - const - InterestingSyms = {skVar, skResult} - - proc collectData(c: var Con; n: PNode) - - proc collectDef(c: var Con; n: PNode; hasInitValue: bool) = - if n.kind == nkSym: - c.t[n.sym.id] = VarInfo(hasInitValue: hasInitValue, - addrTaken: false, assigned: 0, read: 0, - scope: scope) - - proc collectVarSection(c: var Con; n: PNode) = - for a in n: - if a.kind == nkCommentStmt: continue - if a.kind == nkVarTuple: - collectData(c, a.lastSon) - for i in 0 .. a.len-3: collectDef(c, a[i], a.lastSon != nil) - else: - collectData(c, a.lastSon) - if a.lastSon.kind != nkEmpty: - collectDef(c, a.sons[0], a.lastSon != nil) - - proc collectData(c: var Con; n: PNode) = - case n.kind - of nkAsgn, nkFastAsgn: - if n[0].kind == nkSym and (let s = n[0].sym; s.owner == c.owner and - s.kind in InterestingSyms): - inc c.t[s.id].assigned - collectData(c, n[1]) - of nkSym: - if (let s = n[0].sym; s.owner == c.owner and - s.kind in InterestingSyms): - inc c.t[s.id].read - of nkAddr, nkHiddenAddr: - var n = n[0] - while n.kind == nkBracketExpr: n = n[0] - if (let s = n[0].sym; s.owner == c.owner and - s.kind in InterestingSyms): - c.t[s.id].addrTaken = true - - of nkCallKinds: - if n.sons[0].kind == nkSym: - let s = n.sons[0].sym - if s.magic != mNone: - genMagic(c, n, s.magic) - else: - genCall(c, n) - else: - genCall(c, n) - of nkCharLit..nkNilLit, nkIdent: discard - of nkDotExpr, nkCheckedFieldExpr, nkBracketExpr, - nkDerefExpr, nkHiddenDeref: - collectData(c, n[0]) - of nkIfStmt, nkIfExpr: genIf(c, n) - of nkWhenStmt: - # This is "when nimvm" node. Chose the first branch. - collectData(c, n.sons[0].sons[1]) - of nkCaseStmt: genCase(c, n) - of nkWhileStmt: genWhile(c, n) - of nkBlockExpr, nkBlockStmt: genBlock(c, n) - of nkReturnStmt: genReturn(c, n) - of nkRaiseStmt: genRaise(c, n) - of nkBreakStmt: genBreak(c, n) - of nkTryStmt: genTry(c, n) - of nkStmtList, nkStmtListExpr, nkChckRangeF, nkChckRange64, nkChckRange, - nkBracket, nkCurly, nkPar, nkClosure, nkObjConstr: - for x in n: collectData(c, x) - of nkPragmaBlock: collectData(c, n.lastSon) - of nkDiscardStmt: collectData(c, n.sons[0]) - of nkHiddenStdConv, nkHiddenSubConv, nkConv, nkExprColonExpr, nkExprEqExpr, - nkCast: - collectData(c, n.sons[1]) - of nkObjDownConv, nkStringToCString, nkCStringToString: - collectData(c, n.sons[0]) - of nkVarSection, nkLetSection: collectVarSection(c, n) - else: discard +const + InterestingSyms = {skVar, skResult, skLet} type Con = object owner: PSym g: ControlFlowGraph - tmps: PType + jumpTargets: IntSet + tmpObj: PType + tmp: PSym + destroys, topLevelVars: PNode proc isHarmlessVar*(s: PSym; c: Con): bool = # 's' is harmless if it used only once and its @@ -224,29 +141,151 @@ proc isHarmlessVar*(s: PSym; c: Con): bool = # L3 # # So this analysis is for now overly conservative, but correct. - discard + var defsite = -1 + var usages = 0 + for i in 0..<c.g.len: + case c.g[i].kind + of def: + if c.g[i].sym == s: + if defsite < 0: defsite = i + else: return false + of use: + if c.g[i].sym == s: + if defsite < 0: return false + for j in defsite .. i: + # not within the same basic block? + if j in c.jumpTargets: return false + # if we want to die after the first 'use': + if usages > 1: return false + inc usages + of useWithinCall: + if c.g[i].sym == s: return false + of goto, fork: + discard "we do not perform an abstract interpretation yet" template interestingSym(s: PSym): bool = - s.owner == owner and s.kind in InterestingSyms and hasDestructor(s.typ) + s.owner == c.owner and s.kind in InterestingSyms and hasDestructor(s.typ) + +proc genSink(t: PType; dest: PNode): PNode = + let op = if t.sink != nil: t.sink else: t.assignment + assert op != nil + result = newTree(nkCall, newSymNode(op), newTree(nkHiddenAddr, dest)) + +proc genCopy(t: PType; dest: PNode): PNode = + assert t.assignment != nil + result = newTree(nkCall, newSymNode(t.assignment), newTree(nkHiddenAddr, dest)) + +proc genDestroy(t: PType; dest: PNode): PNode = + assert t.destructor != nil + result = newTree(nkCall, newSymNode(t.destructor), newTree(nkHiddenAddr, dest)) + +proc moveOrCopy(dest, ri: PNode; c: var Con): PNode = + if ri.kind in nkCallKinds: + result = genSink(ri.typ, dest) + elif ri.kind == nkSym and isHarmlessVar(ri.sym, c): + result = genSink(ri.typ, dest) + else: + result = genCopy(ri.typ, dest) + +proc addTopVar(c: var Con; v: PNode) = + c.topLevelVars.add newTree(nkIdentDefs, v, emptyNode) proc p(n, parent: PNode; c: var Con) = + template recurse(n, dest) = + let x = dest + for i in 0..<n.safeLen: + p(n[i], x, c) + parent.add x + case n.kind of nkVarSection, nkLetSection: discard "transform; var x = y to var x; x op y where op is a move or copy" + var stmtList = newNodeI(nkStmtList, n.info) + + for i in 0..<n.len: + let it = n[i] + let L = it.len-1 + let ri = it[L] + if it.kind == nkVarTuple and hasDestructor(ri.typ): + let x = lowerTupleUnpacking(it, c.owner) + p(x, stmtList, c) + elif it.kind == nkIdentDefs and hasDestructor(it[0].typ): + it.sons[L] = emptyNode + for j in 0..L-1: + let v = it[j] + doAssert v.kind == nkSym + # 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(v.typ, v) + if ri.kind != nkEmpty: + let r = moveOrCopy(v, ri, c) + recurse(ri, r) + stmtList.add r + else: + # keep it, but transform 'ri': + var varSection = copyNode(n) + var itCopy = copyNode(it) + for j in 0..L-1: + itCopy.add it[j] + p(ri, itCopy, c) + varSection.add itCopy + stmtList.add varSection + parent.add stmtList of nkCallKinds: if n.typ != nil and hasDestructor(n.typ): discard "produce temp creation" + let stmtList = newNodeIT(nkStmtListExpr, n.info, n.typ) + let f = newSym(skField, getIdent(":d" & $c.tmpObj.n.len), c.owner, n.info) + rawAddField c.tmpObj, f + var m = genSink(n.typ, rawDirectAccess(c.tmp, f)) + recurse(n, m) + stmtList.add m + stmtList.add rawDirectAccess(c.tmp, f) + parent.add stmtList + c.destroys.add genDestroy(n.typ, rawDirectAccess(c.tmp, f)) + else: + recurse(n, copyNode(n)) of nkAsgn, nkFastAsgn: if n[0].kind == nkSym and interestingSym(n[0].sym): discard "use move or assignment" + let ri = n[1] + let r = moveOrCopy(n[0], ri, c) + # fortunately this skips the nkCall which we do not want to transform + # to a temp here! + recurse(ri, r) + parent.add r + else: + recurse(n, copyNode(n)) + of nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef, nkIteratorDef, + nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef: + parent.add n else: - for i in 0..<n.len: - p(n[i], n, c) - -proc injectDestructorCalls*(owner: PSym; n: PNode; - disableExceptions = false): PNode = - when false: - var c = Con(t: initTable[int, VarInfo](), owner: owner) - collectData(c, n) - var allTemps = createObj(owner, n.info) + recurse(n, copyNode(n)) + +proc injectDestructorCalls*(owner: PSym; n: PNode): PNode = + var c: Con + c.owner = owner + c.tmp = newSym(skTemp, getIdent":d", owner, n.info) + c.tmpObj = createObj(owner, n.info) + c.tmp.typ = c.tmpObj + c.destroys = newNodeI(nkStmtList, n.info) + c.topLevelVars = newNodeI(nkVarSection, n.info) let cfg = constructCfg(owner, n) + shallowCopy(c.g, cfg) + c.jumpTargets = initIntSet() + for i in 0..<c.g.len: + if c.g[i].kind in {goto, fork}: + c.jumpTargets.incl(i+c.g[i].dest) + var stmtList = newNodeI(nkStmtList, n.info) + for i in 0..<n.len: + p(n[i], stmtList, 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 + if c.destroys.len > 0: + result.add newTryFinally(stmtList, c.destroys) + else: + result.add stmtList |