# # # The Nim Compiler # (c) Copyright 2017 Andreas Rumpf # # See the file "copying.txt", included in this # distribution, for details about the copyright. # ## Injects destructor calls into Nim code as well as ## an optimizer that optimizes copies to moves. This is implemented as an ## AST to AST transformation so that every backend benefits from it. ## Rules for destructor injections: ## ## 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) import intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees, strutils, options, dfa, lowerings const InterestingSyms = {skVar, skResult, skLet} type Con = object owner: PSym g: ControlFlowGraph 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 # definition/usage are not split by any labels: # # let s = foo() # while true: # a[i] = s # # produces: # # def s # L1: # use s # goto L1 # # let s = foo() # if cond: # a[i] = s # else: # a[j] = s # # produces: # # def s # fork L2 # use s # goto L3 # L2: # use s # L3 # # So this analysis is for now overly conservative, but correct. var defsite = -1 var usages = 0 for i in 0.. 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 == c.owner and s.kind in InterestingSyms and hasDestructor(s.typ) proc genSink(t: PType; dest: PNode): PNode = let t = t.skipTypes({tyGenericInst, tyAlias}) 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 = let t = t.skipTypes({tyGenericInst, tyAlias}) assert t.assignment != nil result = newTree(nkCall, newSymNode(t.assignment), newTree(nkHiddenAddr, dest)) proc genDestroy(t: PType; dest: PNode): PNode = let t = t.skipTypes({tyGenericInst, tyAlias}) assert t.destructor != nil result = newTree(nkCall, newSymNode(t.destructor), newTree(nkHiddenAddr, dest)) proc addTopVar(c: var Con; v: PNode) = c.topLevelVars.add newTree(nkIdentDefs, v, emptyNode, emptyNode) proc p(n: PNode; c: var Con): PNode template recurse(n, dest) = for i in 0.. 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(body, c.destroys) else: result.add body when defined(nimDebugDestroys): if owner.name.s == "createSeq": echo "------------------------------------" echo owner.name.s, " transformed to: " echo result