summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--compiler/ast.nim6
-rw-r--r--compiler/destroyer.nim247
-rw-r--r--compiler/dfa.nim8
-rw-r--r--compiler/lowerings.nim11
-rw-r--r--compiler/options.nim1
-rw-r--r--compiler/sempass2.nim7
-rw-r--r--compiler/semstmts.nim13
-rw-r--r--compiler/transf.nim6
8 files changed, 178 insertions, 121 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index 97ff4b593..a6f774790 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -871,7 +871,8 @@ type
                               # mean that there is no destructor.
                               # see instantiateDestructor in semdestruct.nim
     deepCopy*: PSym           # overriden 'deepCopy' operation
-    assignment*: PSym         # overriden '=' operator
+    assignment*: PSym         # overriden '=' operation
+    sink*: PSym               # overriden '=sink' operation
     methods*: seq[(int,PSym)] # attached methods
     size*: BiggestInt         # the size of the type in bytes
                               # -1 means that the size is unkwown
@@ -1047,6 +1048,8 @@ proc newNode*(kind: TNodeKind): PNode =
 
 proc newTree*(kind: TNodeKind; children: varargs[PNode]): PNode =
   result = newNode(kind)
+  if children.len > 0:
+    result.info = children[0].info
   result.sons = @children
 
 proc newIntNode*(kind: TNodeKind, intVal: BiggestInt): PNode =
@@ -1290,6 +1293,7 @@ proc assignType*(dest, src: PType) =
   dest.align = src.align
   dest.destructor = src.destructor
   dest.deepCopy = src.deepCopy
+  dest.sink = src.sink
   dest.assignment = src.assignment
   dest.lockLevel = src.lockLevel
   # this fixes 'type TLock = TSysLock':
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
diff --git a/compiler/dfa.nim b/compiler/dfa.nim
index dd9dd4c79..ca1849a3c 100644
--- a/compiler/dfa.nim
+++ b/compiler/dfa.nim
@@ -235,14 +235,14 @@ proc genTry(c: var Con; n: PNode) =
 
 proc genRaise(c: var Con; n: PNode) =
   gen(c, n.sons[0])
-  c.code.add Instr(n: n, kind: goto, dest: high(int))
+  c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len)
 
 proc genReturn(c: var Con; n: PNode) =
   if n.sons[0].kind != nkEmpty: gen(c, n.sons[0])
-  c.code.add Instr(n: n, kind: goto, dest: high(int))
+  c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len)
 
 const
-  InterestingSyms = {skVar, skResult}
+  InterestingSyms = {skVar, skResult, skLet}
 
 proc genUse(c: var Con; n: PNode) =
   var n = n
@@ -279,7 +279,7 @@ proc genMagic(c: var Con; n: PNode; m: TMagic) =
     for i in 2..<n.len: gen(c, n[i])
   of mExit:
     genCall(c, n)
-    c.code.add Instr(n: n, kind: goto, dest: high(int))
+    c.code.add Instr(n: n, kind: goto, dest: high(int) - c.code.len)
   else:
     genCall(c, n)
 
diff --git a/compiler/lowerings.nim b/compiler/lowerings.nim
index 033472c07..f895e887c 100644
--- a/compiler/lowerings.nim
+++ b/compiler/lowerings.nim
@@ -70,6 +70,9 @@ proc newTupleAccessRaw*(tup: PNode, i: int): PNode =
   lit.intVal = i
   addSon(result, lit)
 
+proc newTryFinally*(body, final: PNode): PNode =
+  result = newTree(nkTryStmt, body, newTree(nkFinally, final))
+
 proc lowerTupleUnpackingForAsgn*(n: PNode; owner: PSym): PNode =
   let value = n.lastSon
   result = newNodeI(nkStmtList, n.info)
@@ -139,6 +142,14 @@ proc rawIndirectAccess*(a: PNode; field: PSym; info: TLineInfo): PNode =
   addSon(result, newSymNode(field))
   result.typ = field.typ
 
+proc rawDirectAccess*(obj, field: PSym): PNode =
+  # returns a.field as a node
+  assert field.kind == skField
+  result = newNodeI(nkDotExpr, field.info)
+  addSon(result, newSymNode obj)
+  addSon(result, newSymNode field)
+  result.typ = field.typ
+
 proc lookupInRecord(n: PNode, id: int): PSym =
   result = nil
   case n.kind
diff --git a/compiler/options.nim b/compiler/options.nim
index 4888cc6ba..984ef4859 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -14,6 +14,7 @@ const
   hasTinyCBackend* = defined(tinyc)
   useEffectSystem* = true
   useWriteTracking* = false
+  newDestructors* = true
   hasFFI* = defined(useFFI)
   newScopeForIf* = true
   useCaas* = not defined(noCaas)
diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim
index 17bdf0902..9ce317f80 100644
--- a/compiler/sempass2.nim
+++ b/compiler/sempass2.nim
@@ -982,9 +982,10 @@ proc trackProc*(s: PSym, body: PNode) =
     message(s.info, warnLockLevel,
       "declared lock level is $1, but real lock level is $2" %
         [$s.typ.lockLevel, $t.maxLockLevel])
-  if s.kind == skFunc:
-    when defined(dfa): dataflowAnalysis(s, body)
-    trackWrites(s, body)
+  when false:
+    if s.kind == skFunc:
+      when defined(dfa): dataflowAnalysis(s, body)
+      trackWrites(s, body)
 
 proc trackTopLevelStmt*(module: PSym; n: PNode) =
   if n.kind in {nkPragma, nkMacroDef, nkTemplateDef, nkProcDef, nkFuncDef,
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index a83de6d27..70e258bac 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -399,7 +399,7 @@ proc addToVarSection(c: PContext; result: var PNode; orig, identDefs: PNode) =
   # in order for this transformation to be correct.
   let L = identDefs.len
   let value = identDefs[L-1]
-  if value.typ != nil and tfHasAsgn in value.typ.flags:
+  if value.typ != nil and tfHasAsgn in value.typ.flags and c.p.owner.kind != skFunc:
     # the spec says we need to rewrite 'var x = T()' to 'var x: T; x = T()':
     identDefs.sons[L-1] = emptyNode
     if result.kind != nkStmtList:
@@ -1303,7 +1303,7 @@ proc semOverride(c: PContext, s: PSym, n: PNode) =
       localError(n.info, errGenerated,
                  "signature for 'deepCopy' must be proc[T: ptr|ref](x: T): T")
     incl(s.flags, sfUsed)
-  of "=":
+  of "=", "=sink":
     if s.magic == mAsgn: return
     incl(s.flags, sfUsed)
     let t = s.typ
@@ -1321,14 +1321,15 @@ proc semOverride(c: PContext, s: PSym, n: PNode) =
           objB = objB.sons[0]
         else: break
       if obj.kind in {tyObject, tyDistinct} and sameType(obj, objB):
-        if obj.assignment.isNil:
-          obj.assignment = s
+        let opr = if s.name.s == "=": addr(obj.assignment) else: addr(obj.sink)
+        if opr[].isNil:
+          opr[] = s
         else:
           localError(n.info, errGenerated,
-                     "cannot bind another '=' to: " & typeToString(obj))
+                     "cannot bind another '" & s.name.s & "' to: " & typeToString(obj))
         return
     localError(n.info, errGenerated,
-               "signature for '=' must be proc[T: object](x: var T; y: T)")
+               "signature for '" & s.name.s & "' must be proc[T: object](x: var T; y: T)")
   else:
     if sfOverriden in s.flags:
       localError(n.info, errGenerated,
diff --git a/compiler/transf.nim b/compiler/transf.nim
index f1ee49a54..f5ea8feb9 100644
--- a/compiler/transf.nim
+++ b/compiler/transf.nim
@@ -21,9 +21,7 @@
 import
   intsets, strutils, options, ast, astalgo, trees, treetab, msgs, os,
   idents, renderer, types, passes, semfold, magicsys, cgmeth, rodread,
-  lambdalifting, sempass2, lowerings, lookups
-
-# implementation
+  lambdalifting, sempass2, lowerings, lookups, destroyer
 
 type
   PTransNode* = distinct PNode
@@ -974,6 +972,8 @@ proc transformBody*(module: PSym, n: PNode, prc: PSym): PNode =
     #result = liftLambdas(prc, result)
     incl(result.flags, nfTransf)
     when useEffectSystem: trackProc(prc, result)
+    if prc.kind == skFunc:
+      result = injectDestructorCalls(prc, n)
     #if prc.name.s == "testbody":
     #  echo renderTree(result)