summary refs log tree commit diff stats
path: root/compiler/destroyer.nim
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/destroyer.nim')
-rw-r--r--compiler/destroyer.nim247
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