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.nim318
1 files changed, 0 insertions, 318 deletions
diff --git a/compiler/destroyer.nim b/compiler/destroyer.nim
deleted file mode 100644
index 36839bf0b..000000000
--- a/compiler/destroyer.nim
+++ /dev/null
@@ -1,318 +0,0 @@
-#
-#
-#           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, rodread
-
-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..<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 == c.owner and s.kind in InterestingSyms and hasDestructor(s.typ)
-
-proc patchHead(n: PNode) =
-  if n.kind in nkCallKinds and n[0].kind == nkSym and n.len > 1:
-    let s = n[0].sym
-    if sfFromGeneric in s.flags and s.name.s[0] == '=' and
-        s.name.s in ["=sink", "=", "=destroy"]:
-      excl(s.flags, sfFromGeneric)
-      patchHead(s.getBody)
-      let t = n[1].typ.skipTypes({tyVar, tyGenericInst, tyAlias, tyInferred})
-      template patch(op, field) =
-        if s.name.s == op and field != nil and field != s:
-          n.sons[0].sym = field
-      patch "=sink", t.sink
-      patch "=", t.assignment
-      patch "=destroy", t.destructor
-  for x in n:
-    patchHead(x)
-
-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
-  patchHead op.ast[bodyPos]
-  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
-  patchHead t.assignment.ast[bodyPos]
-  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
-  patchHead t.destructor.ast[bodyPos]
-  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..<n.len:
-    dest.add p(n[i], c)
-
-proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
-  if ri.kind in nkCallKinds:
-    result = genSink(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.add p(ri, c)
-  else:
-    result = genCopy(ri.typ, dest)
-    result.add p(ri, c)
-
-proc p(n: PNode; c: var Con): PNode =
-  case n.kind
-  of nkVarSection, nkLetSection:
-    discard "transform; var x = y to  var x; x op y  where op is a move or copy"
-    result = 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)
-        result.add p(x, c)
-      elif it.kind == nkIdentDefs and hasDestructor(it[0].typ):
-        for j in 0..L-2:
-          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)
-            result.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]
-        itCopy.add p(ri, c)
-        varSection.add itCopy
-        result.add varSection
-  of nkCallKinds:
-    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))
-    else:
-      result = copyNode(n)
-      recurse(n, result)
-  of nkAsgn, nkFastAsgn:
-    if hasDestructor(n[0].typ):
-      result = moveOrCopy(n[0], n[1], c)
-    else:
-      result = copyNode(n)
-      recurse(n, result)
-  of nkNone..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef,
-      nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef:
-    result = n
-  else:
-    result = copyNode(n)
-    recurse(n, result)
-
-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)
-  let body = p(n, 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(body, c.destroys)
-  else:
-    result.add body
-
-  when defined(nimDebugDestroys):
-    if owner.name.s == "createSeq":
-      echo "------------------------------------"
-      echo owner.name.s, " transformed to: "
-      echo result