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.nim194
1 files changed, 164 insertions, 30 deletions
diff --git a/compiler/destroyer.nim b/compiler/destroyer.nim
index 0fdeceba0..31c735794 100644
--- a/compiler/destroyer.nim
+++ b/compiler/destroyer.nim
@@ -89,11 +89,35 @@
 ##       tmp.bar))
 ## destroy(tmp.bar)
 ## destroy(tmp.x); destroy(tmp.y)
+##
+
+##[
+From https://github.com/nim-lang/Nim/wiki/Destructors
+
+Rule      Pattern                 Transformed into
+----      -------                 ----------------
+1.1	      var x: T; stmts	        var x: T; try stmts
+                                  finally: `=destroy`(x)
+1.2       var x: sink T; stmts    var x: sink T; stmts; ensureEmpty(x)
+2         x = f()                 `=sink`(x, f())
+3         x = lastReadOf z        `=sink`(x, z)
+4.1       y = sinkParam           `=sink`(y, sinkParam)
+4.2       x = y                   `=`(x, y) # a copy
+5.1       f_sink(g())             f_sink(g())
+5.2       f_sink(y)               f_sink(copy y); # copy unless we can see it's the last read
+5.3       f_sink(move y)          f_sink(y); reset(y) # explicit moves empties 'y'
+5.4       f_noSink(g())           var tmp = bitwiseCopy(g()); f(tmp); `=destroy`(tmp)
 
+Remarks: Rule 1.2 is not yet implemented because ``sink`` is currently
+  not allowed as a local variable.
+
+``move`` builtin needs to be implemented.
+]##
 
 import
   intsets, ast, astalgo, msgs, renderer, magicsys, types, idents, trees,
-  strutils, options, dfa, lowerings, rodread
+  strutils, options, dfa, lowerings, rodread, tables, modulegraphs,
+  configuration
 
 const
   InterestingSyms = {skVar, skResult, skLet}
@@ -106,6 +130,15 @@ type
     tmpObj: PType
     tmp: PSym
     destroys, topLevelVars: PNode
+    toDropBit: Table[int, PSym]
+    graph: ModuleGraph
+
+proc getTemp(c: var Con; typ: PType; info: TLineInfo): PNode =
+  # XXX why are temps fields in an object here?
+  let f = newSym(skField, getIdent(":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
@@ -174,7 +207,7 @@ proc patchHead(n: PNode) =
       if n[1].typ.isNil:
         # XXX toptree crashes without this workaround. Figure out why.
         return
-      let t = n[1].typ.skipTypes({tyVar, tyGenericInst, tyAlias, tyInferred})
+      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:
           n.sons[0].sym = field
@@ -191,47 +224,118 @@ proc patchHead(s: PSym) =
 template genOp(opr, opname) =
   let op = opr
   if op == nil:
-    globalError(dest.info, "internal error: '" & opname & "' operator not found for type " & typeToString(t))
+    globalError(c.graph.config, dest.info, "internal error: '" & opname & "' operator not found for type " & typeToString(t))
   elif op.ast[genericParamsPos].kind != nkEmpty:
-    globalError(dest.info, "internal error: '" & opname & "' operator is generic")
+    globalError(c.graph.config, dest.info, "internal error: '" & opname & "' operator is generic")
   patchHead op
   result = newTree(nkCall, newSymNode(op), newTree(nkHiddenAddr, dest))
 
-proc genSink(t: PType; dest: PNode): PNode =
-  let t = t.skipTypes({tyGenericInst, tyAlias})
+proc genSink(c: Con; t: PType; dest: PNode): PNode =
+  let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
   genOp(if t.sink != nil: t.sink else: t.assignment, "=sink")
 
-proc genCopy(t: PType; dest: PNode): PNode =
-  let t = t.skipTypes({tyGenericInst, tyAlias})
+proc genCopy(c: Con; t: PType; dest: PNode): PNode =
+  let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
   genOp(t.assignment, "=")
 
-proc genDestroy(t: PType; dest: PNode): PNode =
-  let t = t.skipTypes({tyGenericInst, tyAlias})
+proc genDestroy(c: Con; t: PType; dest: PNode): PNode =
+  let t = t.skipTypes({tyGenericInst, tyAlias, tySink})
   genOp(t.destructor, "=destroy")
 
 proc addTopVar(c: var Con; v: PNode) =
   c.topLevelVars.add newTree(nkIdentDefs, v, emptyNode, 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(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, emptyNode, trueVal)
+  c.toDropBit[s.id] = result
+  # generate:
+  #  if not sinkParam_AliveBit: `=destroy`(sinkParam)
+  c.destroys.add newTree(nkIfStmt,
+    newTree(nkElifBranch, newSymNode result, genDestroy(c, s.typ, newSymNode s)))
+
 proc p(n: PNode; c: var Con): PNode
 
 template recurse(n, dest) =
   for i in 0..<n.len:
     dest.add p(n[i], c)
 
+proc isSinkParam(s: PSym): bool {.inline.} =
+  result = s.kind == skParam and s.typ.kind == tySink
+
+const constrExprs = nkCallKinds+{nkObjConstr}
+
+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)
+  result.add newTree(nkAsgn, bit,
+    newIntTypeNode(nkIntLit, 0, getSysType(c.graph, n.info, tyBool)))
+  result.add n
+
 proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
-  if ri.kind in nkCallKinds+{nkObjConstr}:
-    result = genSink(ri.typ, dest)
+  if ri.kind in constrExprs:
+    result = genSink(c, ri.typ, dest)
     # watch out and no not transform 'ri' twice if it's a call:
     let ri2 = copyNode(ri)
     recurse(ri, ri2)
     result.add ri2
   elif ri.kind == nkSym and isHarmlessVar(ri.sym, c):
-    result = genSink(ri.typ, dest)
+    result = genSink(c, ri.typ, dest)
     result.add p(ri, c)
+  elif ri.kind == nkSym and isSinkParam(ri.sym):
+    result = genSink(c, ri.typ, dest)
+    result.add destructiveMoveSink(ri, c)
   else:
-    result = genCopy(ri.typ, dest)
+    result = genCopy(c, ri.typ, dest)
     result.add p(ri, c)
 
+proc passCopyToSink(n: PNode; c: var Con): PNode =
+  result = newNodeIT(nkStmtListExpr, n.info, n.typ)
+  let tmp = getTemp(c, n.typ, n.info)
+  if hasDestructor(n.typ):
+    var m = genCopy(c, n.typ, tmp)
+    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)
+  else:
+    result.add newTree(nkAsgn, tmp, p(n, c))
+  result.add tmp
+
+proc genReset(n: PNode; c: var Con): PNode =
+  result = newNodeI(nkCall, n.info)
+  result.add(newSymNode(createMagic(c.graph, "reset", mReset)))
+  # The mReset builtin does not take the address:
+  result.add n
+
+proc destructiveMoveVar(n: PNode; c: var Con): PNode =
+  # generate: (let tmp = v; reset(v); tmp)
+  result = newNodeIT(nkStmtListExpr, n.info, n.typ)
+
+  var temp = newSym(skLet, getIdent("blitTmp"), c.owner, n.info)
+  var v = newNodeI(nkLetSection, n.info)
+  let tempAsNode = newSymNode(temp)
+
+  var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3)
+  vpart.sons[0] = tempAsNode
+  vpart.sons[1] = ast.emptyNode
+  vpart.sons[2] = n
+  add(v, vpart)
+
+  result.add v
+  result.add genReset(n, c)
+  result.add tempAsNode
+
 proc p(n: PNode; c: var Con): PNode =
   case n.kind
   of nkVarSection, nkLetSection:
@@ -243,7 +347,7 @@ proc p(n: PNode; c: var Con): PNode =
       let L = it.len-1
       let ri = it[L]
       if it.kind == nkVarTuple and hasDestructor(ri.typ):
-        let x = lowerTupleUnpacking(it, c.owner)
+        let x = lowerTupleUnpacking(c.graph, it, c.owner)
         result.add p(x, c)
       elif it.kind == nkIdentDefs and hasDestructor(it[0].typ):
         for j in 0..L-2:
@@ -252,7 +356,7 @@ 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(v.typ, v)
+          c.destroys.add genDestroy(c, v.typ, v)
           if ri.kind != nkEmpty:
             let r = moveOrCopy(v, ri, c)
             result.add r
@@ -266,22 +370,45 @@ proc p(n: PNode; c: var Con): PNode =
         varSection.add itCopy
         result.add varSection
   of nkCallKinds:
+    let parameters = n[0].typ
+    let L = if parameters != nil: parameters.len else: 0
+    for i in 1 ..< n.len:
+      let arg = n[i]
+      if i < L and parameters[i].kind == tySink:
+        if arg.kind in nkCallKinds:
+          # recurse but skip the call expression in order to prevent
+          # destructor injections: Rule 5.1 is different from rule 5.4!
+          let a = copyNode(arg)
+          recurse(arg, a)
+          n.sons[i] = a
+        elif arg.kind in {nkObjConstr, nkCharLit..nkFloat128Lit}:
+          discard "object construction to sink parameter: nothing to do"
+        elif arg.kind == nkSym and isHarmlessVar(arg.sym, 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:
+          n.sons[i] = destructiveMoveVar(arg, c)
+        elif arg.kind == nkSym and isSinkParam(arg.sym):
+          # mark the sink parameter as used:
+          n.sons[i] = destructiveMoveSink(arg, c)
+        else:
+          # an object that is not temporary but passed to a 'sink' parameter
+          # results in a copy.
+          n.sons[i] = passCopyToSink(arg, c)
+      else:
+        n.sons[i] = p(arg, c)
+
     if n.typ != nil and hasDestructor(n.typ):
       discard "produce temp creation"
       result = newNodeIT(nkStmtListExpr, n.info, n.typ)
-      let f = newSym(skField, getIdent(":d" & $c.tmpObj.n.len), c.owner, n.info)
-      f.typ = n.typ
-      rawAddField c.tmpObj, f
-      var m = genSink(n.typ, rawDirectAccess(c.tmp, f))
-      var call = copyNode(n)
-      recurse(n, call)
-      m.add call
-      result.add m
-      result.add rawDirectAccess(c.tmp, f)
-      c.destroys.add genDestroy(n.typ, rawDirectAccess(c.tmp, f))
+      let tmp = getTemp(c, n.typ, n.info)
+      var sinkExpr = genSink(c, n.typ, tmp)
+      sinkExpr.add n
+      result.add sinkExpr
+      result.add tmp
+      c.destroys.add genDestroy(c, n.typ, tmp)
     else:
-      result = copyNode(n)
-      recurse(n, result)
+      result = n
   of nkAsgn, nkFastAsgn:
     if hasDestructor(n[0].typ):
       result = moveOrCopy(n[0], n[1], c)
@@ -295,22 +422,29 @@ proc p(n: PNode; c: var Con): PNode =
     result = copyNode(n)
     recurse(n, result)
 
-proc injectDestructorCalls*(owner: PSym; n: PNode): PNode =
+proc injectDestructorCalls*(g: ModuleGraph; owner: PSym; n: PNode): PNode =
   when defined(nimDebugDestroys):
     echo "injecting into ", n
   var c: Con
   c.owner = owner
   c.tmp = newSym(skTemp, getIdent":d", owner, n.info)
-  c.tmpObj = createObj(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
   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)
+  if owner.kind in {skProc, skFunc, skMethod, skIterator, skConverter}:
+    let params = owner.typ.n
+    for i in 1 ..< params.len:
+      let param = params[i].sym
+      if param.typ.kind == tySink: registerDropBit(c, param)
   let body = p(n, c)
   if c.tmp.typ.n.len > 0:
     c.addTopVar(newSymNode c.tmp)