summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2019-12-05 16:59:06 +0100
committerMiran <narimiran@disroot.org>2019-12-05 16:59:06 +0100
commit3fbb3bfd3f440c059d6290c12834a38a61da98f2 (patch)
treeb4561f510b23ce4fe2a19e58e15246c5ec5293a0
parent9b0e874687177af4f758b70994b3a08f963a75cd (diff)
downloadNim-3fbb3bfd3f440c059d6290c12834a38a61da98f2.tar.gz
ARC related bugfixes and refactorings (#12781)
-rw-r--r--compiler/ast.nim20
-rw-r--r--compiler/ccgstmts.nim13
-rw-r--r--compiler/cgen.nim2
-rw-r--r--compiler/injectdestructors.nim427
-rw-r--r--compiler/semtypinst.nim4
-rw-r--r--lib/core/seqs.nim87
-rw-r--r--lib/system.nim13
-rw-r--r--lib/system/excpt.nim64
-rw-r--r--tests/destructor/texceptions.nim29
-rw-r--r--tests/destructor/tgcleak4.nim47
-rw-r--r--tests/destructor/tv2_raise.nim2
11 files changed, 361 insertions, 347 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index 9fbcab144..208e99c4a 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -1456,7 +1456,7 @@ proc isGCedMem*(t: PType): bool {.inline.} =
   result = t.kind in {tyString, tyRef, tySequence} or
            t.kind == tyProc and t.callConv == ccClosure
 
-proc propagateToOwner*(owner, elem: PType) =
+proc propagateToOwner*(owner, elem: PType; propagateHasAsgn = true) =
   const HaveTheirOwnEmpty = {tySequence, tyOpt, tySet, tyPtr, tyRef, tyProc}
   owner.flags = owner.flags + (elem.flags * {tfHasMeta, tfTriggersCompileTime})
   if tfNotNil in elem.flags:
@@ -1472,19 +1472,13 @@ proc propagateToOwner*(owner, elem: PType) =
   if elem.isMetaType:
     owner.flags.incl tfHasMeta
 
-  if tfHasAsgn in elem.flags:
+  let mask = elem.flags * {tfHasAsgn, tfHasOwned}
+  if mask != {} and propagateHasAsgn:
     let o2 = owner.skipTypes({tyGenericInst, tyAlias, tySink})
     if o2.kind in {tyTuple, tyObject, tyArray,
                    tySequence, tyOpt, tySet, tyDistinct, tyOpenArray, tyVarargs}:
-      o2.flags.incl tfHasAsgn
-      owner.flags.incl tfHasAsgn
-
-  if tfHasOwned in elem.flags:
-    let o2 = owner.skipTypes({tyGenericInst, tyAlias, tySink})
-    if o2.kind in {tyTuple, tyObject, tyArray,
-                   tySequence, tyOpt, tySet, tyDistinct, tyOpenArray, tyVarargs}:
-      o2.flags.incl tfHasOwned
-      owner.flags.incl tfHasOwned
+      o2.flags.incl mask
+      owner.flags.incl mask
 
   if owner.kind notin {tyProc, tyGenericInst, tyGenericBody,
                        tyGenericInvocation, tyPtr}:
@@ -1494,11 +1488,11 @@ proc propagateToOwner*(owner, elem: PType) =
       # ensure this doesn't bite us in sempass2.
       owner.flags.incl tfHasGCedMem
 
-proc rawAddSon*(father, son: PType) =
+proc rawAddSon*(father, son: PType; propagateHasAsgn = true) =
   when not defined(nimNoNilSeqs):
     if isNil(father.sons): father.sons = @[]
   father.sons.add(son)
-  if not son.isNil: propagateToOwner(father, son)
+  if not son.isNil: propagateToOwner(father, son, propagateHasAsgn)
 
 proc rawAddSonNoPropagationOfTypeFlags*(father, son: PType) =
   when not defined(nimNoNilSeqs):
diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim
index 59db5cb72..34fb06af8 100644
--- a/compiler/ccgstmts.nim
+++ b/compiler/ccgstmts.nim
@@ -1041,7 +1041,7 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) =
     linefmt(p, cpsStmts, "#popSafePoint();$n", [])
     genRestoreFrameAfterException(p)
   elif 1 < t.len and t[1].kind == nkExceptBranch:
-    startBlock(p, "if (#getCurrentException()) {$n")
+    startBlock(p, "if (#nimBorrowCurrentException()) {$n")
   else:
     startBlock(p)
   p.nestedTryStmts[^1].inExcept = true
@@ -1068,7 +1068,7 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) =
         else:
           genTypeInfo(p.module, t[i][j].typ, t[i][j].info)
         let memberName = if p.module.compileToCpp: "m_type" else: "Sup.m_type"
-        appcg(p.module, orExpr, "#isObj(#getCurrentException()->$1, $2)", [memberName, checkFor])
+        appcg(p.module, orExpr, "#isObj(#nimBorrowCurrentException()->$1, $2)", [memberName, checkFor])
 
       if i > 1: line(p, cpsStmts, "else ")
       startBlock(p, "if ($1) {$n", [orExpr])
@@ -1082,7 +1082,14 @@ proc genTry(p: BProc, t: PNode, d: var TLoc) =
   endBlock(p) # end of else block
   if i < t.len and t[i].kind == nkFinally:
     p.finallySafePoints.add(safePoint)
-    genSimpleBlock(p, t[i][0])
+    startBlock(p)
+    genStmts(p, t[i][0])
+    # pretend we handled the exception in a 'finally' so that we don't
+    # re-raise the unhandled one but instead keep the old one (it was
+    # not popped either):
+    if not quirkyExceptions and getCompilerProc(p.module.g.graph, "nimLeaveFinally") != nil:
+      linefmt(p, cpsStmts, "if ($1.status != 0) #nimLeaveFinally();$n", [safePoint])
+    endBlock(p)
     discard pop(p.finallySafePoints)
   if not quirkyExceptions:
     linefmt(p, cpsStmts, "if ($1.status != 0) #reraiseException();$n", [safePoint])
diff --git a/compiler/cgen.nim b/compiler/cgen.nim
index 12acd7825..a456b7d51 100644
--- a/compiler/cgen.nim
+++ b/compiler/cgen.nim
@@ -1884,7 +1884,7 @@ proc shouldRecompile(m: BModule; code: Rope, cfile: Cfile): bool =
     if not moduleHasChanged(m.g.graph, m.module):
       result = false
     elif not equalsFile(code, cfile.cname):
-      if false:
+      when false:
         #m.config.symbolFiles == readOnlySf: #isDefined(m.config, "nimdiff"):
         if fileExists(cfile.cname):
           copyFile(cfile.cname.string, cfile.cname.string & ".backup")
diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim
index de723b776..7a8c18f13 100644
--- a/compiler/injectdestructors.nim
+++ b/compiler/injectdestructors.nim
@@ -170,6 +170,7 @@ proc genOp(c: Con; t: PType; kind: TTypeAttachedOp; dest, ri: PNode): PNode =
       op = canon.attachedOps[kind]
 
   if op == nil:
+    #echo dest.typ.id
     globalError(c.graph.config, dest.info, "internal error: '" & AttachedOpToStr[kind] &
       "' operator not found for type " & typeToString(t))
   elif op.ast[genericParamsPos].kind != nkEmpty:
@@ -250,22 +251,25 @@ proc destructiveMoveVar(n: PNode; c: var Con): PNode =
   # generate: (let tmp = v; reset(v); tmp)
   # XXX: Strictly speaking we can only move if there is a ``=sink`` defined
   # or if no ``=sink`` is defined and also no assignment.
-  result = newNodeIT(nkStmtListExpr, n.info, n.typ)
+  if not hasDestructor(n.typ):
+    result = copyTree(n)
+  else:
+    result = newNodeIT(nkStmtListExpr, n.info, n.typ)
 
-  var temp = newSym(skLet, getIdent(c.graph.cache, "blitTmp"), c.owner, n.info)
-  temp.typ = n.typ
-  var v = newNodeI(nkLetSection, n.info)
-  let tempAsNode = newSymNode(temp)
+    var temp = newSym(skLet, getIdent(c.graph.cache, "blitTmp"), c.owner, n.info)
+    temp.typ = n.typ
+    var v = newNodeI(nkLetSection, n.info)
+    let tempAsNode = newSymNode(temp)
 
-  var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3)
-  vpart[0] = tempAsNode
-  vpart[1] = c.emptyNode
-  vpart[2] = n
-  v.add(vpart)
+    var vpart = newNodeI(nkIdentDefs, tempAsNode.info, 3)
+    vpart[0] = tempAsNode
+    vpart[1] = c.emptyNode
+    vpart[2] = n
+    v.add(vpart)
 
-  result.add v
-  result.add genWasMoved(skipConv(n), c)
-  result.add tempAsNode
+    result.add v
+    result.add genWasMoved(skipConv(n), c)
+    result.add tempAsNode
 
 proc sinkParamIsLastReadCheck(c: var Con, s: PNode) =
   assert s.kind == nkSym and s.sym.kind == skParam
@@ -273,8 +277,12 @@ proc sinkParamIsLastReadCheck(c: var Con, s: PNode) =
      localError(c.graph.config, c.otherRead.info, "sink parameter `" & $s.sym.name.s &
          "` is already consumed at " & toFileLineCol(c. graph.config, s.info))
 
-proc p(n: PNode; c: var Con; consumed = false): PNode
-proc pArg(arg: PNode; c: var Con; isSink: bool): PNode
+type ProcessMode = enum
+  normal
+  consumed
+  sinkArg
+
+proc p(n: PNode; c: var Con; mode: ProcessMode = normal): PNode
 proc moveOrCopy(dest, ri: PNode; c: var Con): PNode
 
 proc isClosureEnv(n: PNode): bool = n.kind == nkSym and n.sym.name.s[0] == ':'
@@ -366,6 +374,10 @@ template handleNested(n: untyped, processCall: untyped) =
         branch.add if node.typ == nil: p(node, c) #noreturn
                    else: processCall
       result.add branch
+  of nkWhen: # This should be a "when nimvm" node.
+    result = copyTree(n)
+    template node: untyped = n[1][0]
+    result[1][0] = processCall
   else: assert(false)
 
 proc ensureDestruction(arg: PNode; c: var Con): PNode =
@@ -384,75 +396,6 @@ proc ensureDestruction(arg: PNode; c: var Con): PNode =
   else:
     result = arg
 
-proc pArg(arg: PNode; c: var Con; isSink: bool): PNode =
-  if isSink:
-    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!
-      result = copyNode(arg)
-      let parameters = arg[0].typ
-      let L = if parameters != nil: parameters.len else: 0
-      result.add pArg(arg[0], c, isSink = false)
-      for i in 1..<arg.len:
-        result.add pArg(arg[i], c, i < L and isSinkTypeForParam(parameters[i]))
-    elif arg.containsConstSeq:
-      # const sequences are not mutable and so we need to pass a copy to the
-      # sink parameter (bug #11524). Note that the string implementation is
-      # different and can deal with 'const string sunk into var'.
-      result = passCopyToSink(arg, c)
-    elif arg.kind in nkLiterals:
-      # literals are save to share accross ASTs (for now!)
-      result = arg # literal to sink parameter: nothing to do
-    elif arg.kind in {nkBracket, nkObjConstr, nkTupleConstr, nkClosure}:
-      # object construction to sink parameter: nothing to do
-      result = copyTree(arg)
-      for i in ord(arg.kind in {nkObjConstr, nkClosure})..<arg.len:
-        if arg[i].kind == nkExprColonExpr:
-          result[i][1] = pArg(arg[i][1], c, isSink = true)
-        else:
-          result[i] = pArg(arg[i], c, isSink = true)
-      #if arg.kind == nkClosure:
-      #  result = handleClosureCall(result, c)
-      # Not required here because the nkClosure will be consumed!
-    elif arg.kind == nkSym and isSinkParam(arg.sym):
-      # Sinked params can be consumed only once. We need to reset the memory
-      # to disable the destructor which we have not elided
-      sinkParamIsLastReadCheck(c, arg)
-      result = destructiveMoveVar(arg, c)
-    elif isAnalysableFieldAccess(arg, c.owner) and isLastRead(arg, c):
-      # it is the last read, can be sinked. We need to reset the memory
-      # to disable the destructor which we have not elided
-      result = destructiveMoveVar(arg, c)
-    elif arg.kind in {nkStmtListExpr, nkBlockExpr, nkBlockStmt, nkIfExpr, nkIfStmt, nkCaseStmt}:
-      handleNested(arg): pArg(node, c, isSink)
-    elif arg.kind in {nkHiddenSubConv, nkHiddenStdConv, nkConv}:
-      result = copyTree(arg)
-      result[1] = pArg(arg[1], c, isSink = true)
-    elif arg.kind in {nkObjDownConv, nkObjUpConv}:
-      result = copyTree(arg)
-      result[0] = pArg(arg[0], c, isSink = true)
-    else:
-      # an object that is not temporary but passed to a 'sink' parameter
-      # results in a copy.
-      result = passCopyToSink(arg, c)
-  elif arg.kind == nkBracket:
-    # Treat `f([...])` like `f(...)`
-    result = copyNode(arg)
-    for son in arg:
-      result.add pArg(son, c, isSinkTypeForParam(son.typ))
-  elif arg.kind in nkCallKinds and arg.typ != nil and hasDestructor(arg.typ):
-    # produce temp creation
-    result = newNodeIT(nkStmtListExpr, arg.info, arg.typ)
-    let tmp = getTemp(c, arg.typ, arg.info)
-    let res = p(arg, c)
-    var sinkExpr = genSink(c, tmp, res)
-    sinkExpr.add res
-    result.add sinkExpr
-    result.add tmp
-    c.destroys.add genDestroy(c, tmp)
-  else:
-    result = p(arg, c)
-
 proc isCursor(n: PNode): bool =
   case n.kind
   of nkSym:
@@ -493,154 +436,172 @@ proc cycleCheck(n: PNode; c: var Con) =
       message(c.graph.config, n.info, warnCycleCreated, msg)
       break
 
-proc p(n: PNode; c: var Con; consumed = false): PNode =
-  case n.kind
-  of nkCallKinds:
-    let parameters = n[0].typ
-    let L = if parameters != nil: parameters.len else: 0
-    result = shallowCopy(n)
-    for i in 1..<n.len:
-      result[i] = pArg(n[i], c, i < L and isSinkTypeForParam(parameters[i]))
-    if n[0].kind == nkSym and n[0].sym.magic in {mNew, mNewFinalize}:
-      result[0] = copyTree(n[0])
-      if c.graph.config.selectedGC in {gcHooks, gcDestructors}:
-        let destroyOld = genDestroy(c, result[1])
-        result = newTree(nkStmtList, destroyOld, result)
-    else:
-      result[0] = pArg(n[0], c, isSink = false)
-  of nkDiscardStmt: # Small optimization
-    result = shallowCopy(n)
-    if n[0].kind != nkEmpty:
-      result[0] = pArg(n[0], c, false)
+proc p(n: PNode; c: var Con; mode: ProcessMode = normal): PNode =
+  if n.kind in {nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkIfStmt, nkIfExpr, nkCaseStmt, nkWhen}:
+    handleNested(n): p(node, c, mode)
+  elif mode == sinkArg:
+    if n.containsConstSeq:
+      # const sequences are not mutable and so we need to pass a copy to the
+      # sink parameter (bug #11524). Note that the string implementation is
+      # different and can deal with 'const string sunk into var'.
+      result = passCopyToSink(n, c)
+    elif n.kind in {nkBracket, nkObjConstr, nkTupleConstr, nkClosure} + nkCallKinds + nkLiterals:
+      result = p(n, c, consumed)
+    elif n.kind == nkSym and isSinkParam(n.sym):
+      # Sinked params can be consumed only once. We need to reset the memory
+      # to disable the destructor which we have not elided
+      sinkParamIsLastReadCheck(c, n)
+      result = destructiveMoveVar(n, c)
+    elif isAnalysableFieldAccess(n, c.owner) and isLastRead(n, c):
+      # it is the last read, can be sinkArg. We need to reset the memory
+      # to disable the destructor which we have not elided
+      result = destructiveMoveVar(n, c)
+    elif n.kind in {nkHiddenSubConv, nkHiddenStdConv, nkConv}:
+      result = copyTree(n)
+      if n.typ.skipTypes(abstractInst-{tyOwned}).kind != tyOwned and
+          n[1].typ.skipTypes(abstractInst-{tyOwned}).kind == tyOwned:
+        # allow conversions from owned to unowned via this little hack:
+        let nTyp = n[1].typ
+        n[1].typ = n.typ
+        result[1] = p(n[1], c, sinkArg)
+        result[1].typ = nTyp
+      else:
+        result[1] = p(n[1], c, sinkArg)
+    elif n.kind in {nkObjDownConv, nkObjUpConv}:
+      result = copyTree(n)
+      result[0] = p(n[0], c, sinkArg)
     else:
-      result[0] = copyNode(n[0])
-  of nkBracket:
-    result = copyTree(n)
-    for i in 0..<n.len:
-      # everything that is passed to an array constructor is consumed,
-      # so these all act like 'sink' parameters:
-      result[i] = pArg(n[i], c, isSink = true)
-    if not consumed:
-      result = ensureDestruction(result, c)
-  of nkObjConstr:
-    result = copyTree(n)
-    for i in 1..<n.len:
-      # everything that is passed to an object constructor is consumed,
-      # so these all act like 'sink' parameters:
-      result[i][1] = pArg(n[i][1], c, isSink = true)
-    if not consumed:
-      result = ensureDestruction(result, c)
-  of nkTupleConstr, nkClosure:
-    result = copyTree(n)
-    for i in ord(n.kind == nkClosure)..<n.len:
-      # everything that is passed to an tuple constructor is consumed,
-      # so these all act like 'sink' parameters:
-      if n[i].kind == nkExprColonExpr:
-        result[i][1] = pArg(n[i][1], c, isSink = true)
+      # copy objects that are not temporary but passed to a 'sink' parameter
+      result = passCopyToSink(n, c)
+  else:
+    case n.kind
+    of nkBracket, nkObjConstr, nkTupleConstr, nkClosure:
+      result = copyTree(n)
+      for i in ord(n.kind in {nkObjConstr, nkClosure})..<n.len:
+        let m = if mode == normal: normal
+                else: sinkArg
+        if n[i].kind == nkExprColonExpr:
+          result[i][1] = p(n[i][1], c, m)
+        else:
+          result[i] = p(n[i], c, m)
+    of nkCallKinds:
+      let parameters = n[0].typ
+      let L = if parameters != nil: parameters.len else: 0
+      result = shallowCopy(n)
+      for i in 1..<n.len:
+        if i < L and isSinkTypeForParam(parameters[i]):
+          result[i] = p(n[i], c, sinkArg)
+        else:
+          result[i] = p(n[i], c, normal)
+      if n[0].kind == nkSym and n[0].sym.magic in {mNew, mNewFinalize}:
+        result[0] = copyTree(n[0])
+        if c.graph.config.selectedGC in {gcHooks, gcDestructors}:
+          let destroyOld = genDestroy(c, result[1])
+          result = newTree(nkStmtList, destroyOld, result)
       else:
-        result[i] = pArg(n[i], c, isSink = true)
-    if not consumed:
-      result = ensureDestruction(result, c)
-  of nkVarSection, nkLetSection:
-    # transform; var x = y to  var x; x op y  where op is a move or copy
-    result = newNodeI(nkStmtList, n.info)
-    for it in n:
-      var ri = it[^1]
-      if it.kind == nkVarTuple and hasDestructor(ri.typ):
-        let x = lowerTupleUnpacking(c.graph, it, c.owner)
-        result.add p(x, c)
-      elif it.kind == nkIdentDefs and hasDestructor(it[0].typ) and not isCursor(it[0]):
-        for j in 0..<it.len-2:
-          let v = it[j]
-          if v.kind == nkSym:
-            if sfCompileTime in v.sym.flags: continue
-            # move the variable declaration to the top of the frame:
-            c.addTopVar v
-            # make sure it's destroyed at the end of the proc:
-            if not isUnpackedTuple(it[0].sym):
-              c.destroys.add genDestroy(c, v)
-          if ri.kind == nkEmpty and c.inLoop > 0:
-            ri = genDefaultCall(v.typ, c, v.info)
-          if ri.kind != nkEmpty:
-            let r = moveOrCopy(v, ri, c)
-            result.add r
-      else: # keep the var but transform 'ri':
-        var v = copyNode(n)
-        var itCopy = copyNode(it)
-        for j in 0..<it.len-1:
-          itCopy.add it[j]
-        itCopy.add p(it[^1], c)
-        v.add itCopy
-        result.add v
-  of nkAsgn, nkFastAsgn:
-    if hasDestructor(n[0].typ) and n[1].kind notin {nkProcDef, nkDo, nkLambda} and
-        not isCursor(n[0]):
-      # rule (self-assignment-removal):
-      if n[1].kind == nkSym and n[0].kind == nkSym and n[0].sym == n[1].sym:
-        result = newNodeI(nkEmpty, n.info)
+        result[0] = p(n[0], c, normal)
+
+      if mode == normal:
+        result = ensureDestruction(result, c)
+    of nkDiscardStmt: # Small optimization
+      result = shallowCopy(n)
+      if n[0].kind != nkEmpty:
+        result[0] = p(n[0], c, normal)
+      else:
+        result[0] = copyNode(n[0])
+    of nkVarSection, nkLetSection:
+      # transform; var x = y to  var x; x op y  where op is a move or copy
+      result = newNodeI(nkStmtList, n.info)
+      for it in n:
+        var ri = it[^1]
+        if it.kind == nkVarTuple and hasDestructor(ri.typ):
+          let x = lowerTupleUnpacking(c.graph, it, c.owner)
+          result.add p(x, c, consumed)
+        elif it.kind == nkIdentDefs and hasDestructor(it[0].typ) and not isCursor(it[0]):
+          for j in 0..<it.len-2:
+            let v = it[j]
+            if v.kind == nkSym:
+              if sfCompileTime in v.sym.flags: continue
+              # move the variable declaration to the top of the frame:
+              c.addTopVar v
+              # make sure it's destroyed at the end of the proc:
+              if not isUnpackedTuple(it[0].sym):
+                c.destroys.add genDestroy(c, v)
+            if ri.kind == nkEmpty and c.inLoop > 0:
+              ri = genDefaultCall(v.typ, c, v.info)
+            if ri.kind != nkEmpty:
+              let r = moveOrCopy(v, ri, c)
+              result.add r
+        else: # keep the var but transform 'ri':
+          var v = copyNode(n)
+          var itCopy = copyNode(it)
+          for j in 0..<it.len-1:
+            itCopy.add it[j]
+          itCopy.add p(it[^1], c)
+          v.add itCopy
+          result.add v
+    of nkAsgn, nkFastAsgn:
+      if hasDestructor(n[0].typ) and n[1].kind notin {nkProcDef, nkDo, nkLambda} and
+          not isCursor(n[0]):
+        # rule (self-assignment-removal):
+        if n[1].kind == nkSym and n[0].kind == nkSym and n[0].sym == n[1].sym:
+          result = newNodeI(nkEmpty, n.info)
+        else:
+          if n[0].kind in {nkDotExpr, nkCheckedFieldExpr}:
+            cycleCheck(n, c)
+          result = moveOrCopy(n[0], n[1], c)
       else:
-        if n[0].kind in {nkDotExpr, nkCheckedFieldExpr}:
-          cycleCheck(n, c)
-        result = moveOrCopy(n[0], n[1], c)
-    else:
-      result = copyNode(n)
-      result.add copyTree(n[0])
-      result.add p(n[1], c)
-  of nkRaiseStmt:
-    if optOwnedRefs in c.graph.config.globalOptions and n[0].kind != nkEmpty:
-      if n[0].kind in nkCallKinds:
-        let call = p(n[0], c)
         result = copyNode(n)
-        result.add call
+        result.add copyTree(n[0])
+        result.add p(n[1], c, consumed)
+    of nkRaiseStmt:
+      if optOwnedRefs in c.graph.config.globalOptions and n[0].kind != nkEmpty:
+        if n[0].kind in nkCallKinds:
+          let call = p(n[0], c)
+          result = copyNode(n)
+          result.add call
+        else:
+          let tmp = getTemp(c, n[0].typ, n.info)
+          var m = genCopyNoCheck(c, tmp, n[0])
+          m.add p(n[0], c)
+          result = newTree(nkStmtList, genWasMoved(tmp, c), m)
+          var toDisarm = n[0]
+          if toDisarm.kind == nkStmtListExpr: toDisarm = toDisarm.lastSon
+          if toDisarm.kind == nkSym and toDisarm.sym.owner == c.owner:
+            result.add genWasMoved(toDisarm, c)
+          result.add newTree(nkRaiseStmt, tmp)
       else:
-        let tmp = getTemp(c, n[0].typ, n.info)
-        var m = genCopyNoCheck(c, tmp, n[0])
-        m.add p(n[0], c)
-        result = newTree(nkStmtList, genWasMoved(tmp, c), m)
-        var toDisarm = n[0]
-        if toDisarm.kind == nkStmtListExpr: toDisarm = toDisarm.lastSon
-        if toDisarm.kind == nkSym and toDisarm.sym.owner == c.owner:
-          result.add genWasMoved(toDisarm, c)
-        result.add newTree(nkRaiseStmt, tmp)
-    else:
+        result = copyNode(n)
+        if n[0].kind != nkEmpty:
+          result.add p(n[0], c, sinkArg)
+        else:
+          result.add copyNode(n[0])
+    of nkWhileStmt:
       result = copyNode(n)
+      inc c.inLoop
       result.add p(n[0], c)
-  of nkNone..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef, nkMethodDef,
-      nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo, nkFuncDef,
-      nkConstSection, nkConstDef, nkIncludeStmt, nkImportStmt, nkExportStmt,
-      nkPragma, nkCommentStmt, nkBreakStmt, nkBreakState:
-    result = n
-  of nkWhileStmt:
-    result = copyNode(n)
-    inc c.inLoop
-    result.add p(n[0], c)
-    result.add p(n[1], c)
-    dec c.inLoop
-  of nkWhen: # This should be a "when nimvm" node.
-    result = copyTree(n)
-    result[1][0] = p(result[1][0], c)
-  of nkStmtList, nkStmtListExpr, nkBlockStmt, nkBlockExpr, nkIfStmt, nkIfExpr, nkCaseStmt:
-    handleNested(n): p(node, c, consumed)
-  else:
-    result = shallowCopy(n)
-    for i in 0..<n.len:
-      result[i] = p(n[i], c, consumed)
+      result.add p(n[1], c)
+      dec c.inLoop
+    of nkNone..nkNilLit, nkTypeSection, nkProcDef, nkConverterDef,
+       nkMethodDef, nkIteratorDef, nkMacroDef, nkTemplateDef, nkLambda, nkDo,
+       nkFuncDef, nkConstSection, nkConstDef, nkIncludeStmt, nkImportStmt,
+       nkExportStmt, nkPragma, nkCommentStmt, nkBreakStmt, nkBreakState:
+      result = n
+    else:
+      result = shallowCopy(n)
+      for i in 0..<n.len:
+        result[i] = p(n[i], c, mode)
 
 proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
-  # unfortunately, this needs to be kept consistent with the cases
-  # we handle in the 'case of' statement below:
-  const movableNodeKinds = (nkCallKinds + {nkSym, nkTupleConstr, nkClosure, nkObjConstr,
-                                           nkBracket, nkBracketExpr, nkNilLit})
-
   case ri.kind
   of nkCallKinds:
     result = genSink(c, dest, ri)
-    result.add p(ri, c, consumed = true)
+    result.add p(ri, c, consumed)
   of nkBracketExpr:
     if ri[0].kind == nkSym and isUnpackedTuple(ri[0].sym):
       # unpacking of tuple: move out the elements
       result = genSink(c, dest, ri)
-      result.add p(ri, c, consumed = true)
+      result.add p(ri, c, consumed)
     elif isAnalysableFieldAccess(ri, c.owner) and isLastRead(ri, c):
       # Rule 3: `=sink`(x, z); wasMoved(z)
       var snk = genSink(c, dest, ri)
@@ -648,17 +609,17 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
       result = newTree(nkStmtList, snk, genWasMoved(ri, c))
     else:
       result = genCopy(c, dest, ri)
-      result.add p(ri, c, consumed = true)
+      result.add p(ri, c, consumed)
   of nkBracket:
     # array constructor
     if ri.len > 0 and isDangerousSeq(ri.typ):
       result = genCopy(c, dest, ri)
     else:
       result = genSink(c, dest, ri)
-    result.add p(ri, c, consumed = true)
+    result.add p(ri, c, consumed)
   of nkObjConstr, nkTupleConstr, nkClosure, nkCharLit..nkNilLit:
     result = genSink(c, dest, ri)
-    result.add p(ri, c, consumed = true)
+    result.add p(ri, c, consumed)
   of nkSym:
     if isSinkParam(ri.sym):
       # Rule 3: `=sink`(x, z); wasMoved(z)
@@ -674,18 +635,26 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
       result = newTree(nkStmtList, snk, genWasMoved(ri, c))
     else:
       result = genCopy(c, dest, ri)
-      result.add p(ri, c, consumed = true)
+      result.add p(ri, c, consumed)
   of nkHiddenSubConv, nkHiddenStdConv, nkConv:
-    result = moveOrCopy(dest, ri[1], c)
-    if not sameType(ri.typ, ri[1].typ):
+    when false:
+      result = moveOrCopy(dest, ri[1], c)
+      if not sameType(ri.typ, ri[1].typ):
+        let copyRi = copyTree(ri)
+        copyRi[1] = result[^1]
+        result[^1] = copyRi
+    else:
+      result = genSink(c, dest, ri)
+      result.add p(ri, c, sinkArg)
+  of nkObjDownConv, nkObjUpConv:
+    when false:
+      result = moveOrCopy(dest, ri[0], c)
       let copyRi = copyTree(ri)
-      copyRi[1] = result[^1]
+      copyRi[0] = result[^1]
       result[^1] = copyRi
-  of nkObjDownConv, nkObjUpConv:
-    result = moveOrCopy(dest, ri[0], c)
-    let copyRi = copyTree(ri)
-    copyRi[0] = result[^1]
-    result[^1] = copyRi
+    else:
+      result = genSink(c, dest, ri)
+      result.add p(ri, c, sinkArg)
   of nkStmtListExpr, nkBlockExpr, nkIfExpr, nkCaseStmt:
     handleNested(ri): moveOrCopy(dest, node, c)
   else:
@@ -697,7 +666,7 @@ proc moveOrCopy(dest, ri: PNode; c: var Con): PNode =
       result = newTree(nkStmtList, snk, genWasMoved(ri, c))
     else:
       result = genCopy(c, dest, ri)
-      result.add p(ri, c, consumed = true)
+      result.add p(ri, c, consumed)
 
 proc computeUninit(c: var Con) =
   if not c.uninitComputed:
diff --git a/compiler/semtypinst.nim b/compiler/semtypinst.nim
index 786a9e4f8..6d9e9873e 100644
--- a/compiler/semtypinst.nim
+++ b/compiler/semtypinst.nim
@@ -307,6 +307,8 @@ proc instCopyType*(cl: var TReplTypeVars, t: PType): PType =
   if not (t.kind in tyMetaTypes or
          (t.kind == tyStatic and t.n == nil)):
     result.flags.excl tfInstClearedFlags
+  else:
+    result.flags.excl tfHasAsgn
   when false:
     if newDestructors:
       result.assignment = nil
@@ -380,7 +382,7 @@ proc handleGenericInvocation(cl: var TReplTypeVars, t: PType): PType =
   for i in 1..<t.len:
     # if one of the params is not concrete, we cannot do anything
     # but we already raised an error!
-    rawAddSon(result, header[i])
+    rawAddSon(result, header[i], propagateHasAsgn = false)
 
   if body.kind == tyError:
     return
diff --git a/lib/core/seqs.nim b/lib/core/seqs.nim
index 914ca438c..b7f9fb153 100644
--- a/lib/core/seqs.nim
+++ b/lib/core/seqs.nim
@@ -30,60 +30,6 @@ template payloadSize(cap): int = cap * sizeof(T) + sizeof(int) + sizeof(Allocato
 
 # XXX make code memory safe for overflows in '*'
 
-when false:
-  # this is currently not part of Nim's type bound operators and so it's
-  # built into the tracing proc generation just like before.
-  proc `=trace`[T](s: NimSeqV2[T]) =
-    for i in 0 ..< s.len: `=trace`(s.data[i])
-
-#[
-Keep in mind that C optimizers are bad at detecting the connection
-between ``s.p != nil`` and ``s.len != 0`` and that these are intermingled
-with user-level code that accesses ``s.len`` only, never ``s.p`` directly.
-This means the check for whether ``s.p`` needs to be freed should
-be ``s.len == 0`` even though that feels slightly more awkward.
-]#
-
-when not defined(nimV2):
-  proc `=destroy`[T](s: var seq[T]) =
-    var x = cast[ptr NimSeqV2[T]](addr s)
-    var p = x.p
-    if p != nil:
-      mixin `=destroy`
-      when not supportsCopyMem(T):
-        for i in 0..<x.len: `=destroy`(p.data[i])
-      if p.allocator != nil:
-        p.allocator.dealloc(p.allocator, p, payloadSize(p.cap))
-      x.p = nil
-      x.len = 0
-
-  proc `=`[T](x: var seq[T]; y: seq[T]) =
-    mixin `=destroy`
-    var a = cast[ptr NimSeqV2[T]](addr x)
-    var b = cast[ptr NimSeqV2[T]](unsafeAddr y)
-
-    if a.p == b.p: return
-    `=destroy`(x)
-    a.len = b.len
-    if b.p != nil:
-      a.p = cast[type(a.p)](alloc(payloadSize(a.len)))
-      when supportsCopyMem(T):
-        if a.len > 0:
-          copyMem(unsafeAddr a.p.data[0], unsafeAddr b.p.data[0], a.len * sizeof(T))
-      else:
-        for i in 0..<a.len:
-          a.p.data[i] = b.p.data[i]
-
-  proc `=sink`[T](x: var seq[T]; y: seq[T]) =
-    mixin `=destroy`
-    var a = cast[ptr NimSeqV2[T]](addr x)
-    var b = cast[ptr NimSeqV2[T]](unsafeAddr y)
-    if a.p != nil and a.p != b.p:
-      `=destroy`(x)
-    a.len = b.len
-    a.p = b.p
-
-
 type
   PayloadBase = object
     cap: int
@@ -181,36 +127,3 @@ proc setLen[T](s: var seq[T], newlen: Natural) =
       if xu.p == nil or xu.p.cap < newlen:
         xu.p = cast[typeof(xu.p)](prepareSeqAdd(oldLen, xu.p, newlen - oldLen, sizeof(T)))
       xu.len = newlen
-
-when false:
-  proc resize[T](s: var NimSeqV2[T]) =
-    let old = s.cap
-    if old == 0: s.cap = 8
-    else: s.cap = (s.cap * 3) shr 1
-    s.data = cast[type(s.data)](realloc(s.data, old * sizeof(T), s.cap * sizeof(T)))
-
-  proc reserveSlot[T](x: var NimSeqV2[T]): ptr T =
-    if x.len >= x.cap: resize(x)
-    result = addr(x.data[x.len])
-    inc x.len
-
-  template add*[T](x: var NimSeqV2[T]; y: T) =
-    reserveSlot(x)[] = y
-
-  template `[]`*[T](x: NimSeqV2[T]; i: Natural): T =
-    assert i < x.len
-    x.data[i]
-
-  template `[]=`*[T](x: NimSeqV2[T]; i: Natural; y: T) =
-    assert i < x.len
-    x.data[i] = y
-
-  proc `@`*[T](elems: sink openArray[T]): NimSeqV2[T] =
-    result.cap = elems.len
-    result.len = elems.len
-    result.data = cast[type(result.data)](alloc(result.cap * sizeof(T)))
-    when supportsCopyMem(T):
-      copyMem(result.data, unsafeAddr(elems[0]), result.cap * sizeof(T))
-    else:
-      for i in 0..<result.len:
-        result.data[i] = elems[i]
diff --git a/lib/system.nim b/lib/system.nim
index cb211e6a9..81d308073 100644
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -3218,6 +3218,9 @@ proc `<`*[T: tuple](x, y: T): bool =
 const
   usesDestructors = defined(gcDestructors) or defined(gcHooks)
 
+when not usesDestructors:
+  {.pragma: nodestroy.}
+
 when not defined(nimscript) and hasAlloc:
   type
     GC_Strategy* = enum  ## The strategy the GC should use for the application.
@@ -3671,8 +3674,6 @@ when not defined(JS): #and not defined(nimscript):
         prev: PSafePoint # points to next safe point ON THE STACK
         status: int
         context: C_JmpBuf
-        hasRaiseAction: bool
-        raiseAction: proc (e: ref Exception): bool {.closure.}
       SafePoint = TSafePoint
 
   when declared(initAllocator):
@@ -3776,11 +3777,15 @@ when not defined(JS): #and not defined(nimscript):
       ## Retrieves the current exception; if there is none, `nil` is returned.
       result = currException
 
+    proc nimBorrowCurrentException(): ref Exception {.compilerRtl, inl, benign, nodestroy.} =
+      # .nodestroy here so that we do not produce a write barrier as the
+      # C codegen only uses it in a borrowed way:
+      result = currException
+
     proc getCurrentExceptionMsg*(): string {.inline, benign.} =
       ## Retrieves the error message that was attached to the current
       ## exception; if there is none, `""` is returned.
-      var e = getCurrentException()
-      return if e == nil: "" else: e.msg
+      return if currException == nil: "" else: currException.msg
 
     proc setCurrentException*(exc: ref Exception) {.inline, benign.} =
       ## Sets the current exception.
diff --git a/lib/system/excpt.nim b/lib/system/excpt.nim
index ef9a6deaf..6e06b10f8 100644
--- a/lib/system/excpt.nim
+++ b/lib/system/excpt.nim
@@ -103,19 +103,20 @@ proc pushGcFrame*(s: GcFrame) {.compilerRtl, inl.} =
   gcFramePtr = s
 
 proc pushSafePoint(s: PSafePoint) {.compilerRtl, inl.} =
-  s.hasRaiseAction = false
   s.prev = excHandler
   excHandler = s
 
 proc popSafePoint {.compilerRtl, inl.} =
   excHandler = excHandler.prev
 
-proc pushCurrentException(e: ref Exception) {.compilerRtl, inl.} =
+proc pushCurrentException(e: sink(ref Exception)) {.compilerRtl, inl.} =
   e.up = currException
   currException = e
+  #showErrorMessage "A"
 
 proc popCurrentException {.compilerRtl, inl.} =
   currException = currException.up
+  #showErrorMessage "B"
 
 proc popCurrentExceptionEx(id: uint) {.compilerRtl.} =
   # in cpp backend exceptions can pop-up in the different order they were raised, example #5628
@@ -332,7 +333,53 @@ template unhandled(buf, body) =
   else:
     body
 
-proc raiseExceptionAux(e: ref Exception) =
+proc nimLeaveFinally() {.compilerRtl.} =
+  when defined(cpp) and not defined(noCppExceptions):
+    {.emit: "throw;".}
+  else:
+    template e: untyped = currException
+    if excHandler != nil:
+      c_longjmp(excHandler.context, 1)
+    else:
+      when hasSomeStackTrace:
+        var buf = newStringOfCap(2000)
+        if e.trace.len == 0: rawWriteStackTrace(buf)
+        else: add(buf, $e.trace)
+        add(buf, "Error: unhandled exception: ")
+        add(buf, e.msg)
+        add(buf, " [")
+        add(buf, $e.name)
+        add(buf, "]\n")
+        unhandled(buf):
+          showErrorMessage(buf)
+          quitOrDebug()
+        `=destroy`(buf)
+      else:
+        # ugly, but avoids heap allocations :-)
+        template xadd(buf, s, slen) =
+          if L + slen < high(buf):
+            copyMem(addr(buf[L]), cstring(s), slen)
+            inc L, slen
+        template add(buf, s) =
+          xadd(buf, s, s.len)
+        var buf: array[0..2000, char]
+        var L = 0
+        if e.trace.len != 0:
+          add(buf, $e.trace) # gc allocation
+        add(buf, "Error: unhandled exception: ")
+        add(buf, e.msg)
+        add(buf, " [")
+        xadd(buf, e.name, e.name.len)
+        add(buf, "]\n")
+        when defined(nimNoArrayToCstringConversion):
+          template tbuf(): untyped = addr buf
+        else:
+          template tbuf(): untyped = buf
+        unhandled(tbuf()):
+          showErrorMessage(tbuf())
+          quitOrDebug()
+
+proc raiseExceptionAux(e: sink(ref Exception)) {.nodestroy.} =
   if localRaiseHook != nil:
     if not localRaiseHook(e): return
   if globalRaiseHook != nil:
@@ -351,9 +398,8 @@ proc raiseExceptionAux(e: ref Exception) =
     pushCurrentException(e)
   else:
     if excHandler != nil:
-      if not excHandler.hasRaiseAction or excHandler.raiseAction(e):
-        pushCurrentException(e)
-        c_longjmp(excHandler.context, 1)
+      pushCurrentException(e)
+      c_longjmp(excHandler.context, 1)
     else:
       when hasSomeStackTrace:
         var buf = newStringOfCap(2000)
@@ -367,6 +413,7 @@ proc raiseExceptionAux(e: ref Exception) =
         unhandled(buf):
           showErrorMessage(buf)
           quitOrDebug()
+        `=destroy`(buf)
       else:
         # ugly, but avoids heap allocations :-)
         template xadd(buf, s, slen) =
@@ -392,7 +439,8 @@ proc raiseExceptionAux(e: ref Exception) =
           showErrorMessage(tbuf())
           quitOrDebug()
 
-proc raiseExceptionEx(e: ref Exception, ename, procname, filename: cstring, line: int) {.compilerRtl.} =
+proc raiseExceptionEx(e: sink(ref Exception), ename, procname, filename: cstring,
+                      line: int) {.compilerRtl, nodestroy.} =
   if e.name.isNil: e.name = ename
   when hasSomeStackTrace:
     if e.trace.len == 0:
@@ -406,7 +454,7 @@ proc raiseExceptionEx(e: ref Exception, ename, procname, filename: cstring, line
       e.trace.add StackTraceEntry(procname: procname, filename: filename, line: line)
   raiseExceptionAux(e)
 
-proc raiseException(e: ref Exception, ename: cstring) {.compilerRtl.} =
+proc raiseException(e: sink(ref Exception), ename: cstring) {.compilerRtl.} =
   raiseExceptionEx(e, ename, nil, nil, 0)
 
 proc reraiseException() {.compilerRtl.} =
diff --git a/tests/destructor/texceptions.nim b/tests/destructor/texceptions.nim
new file mode 100644
index 000000000..335ca23be
--- /dev/null
+++ b/tests/destructor/texceptions.nim
@@ -0,0 +1,29 @@
+discard """
+  cmd: '''nim c --gc:arc $file'''
+  output: '''0'''
+"""
+
+proc other =
+  raise newException(ValueError, "stuff happening")
+
+proc indirectViaProcCall =
+  var correct = 0
+  for i in 1 .. 20:
+    try:
+      other()
+    except:
+      let x = getCurrentException()
+      correct += ord(x of ValueError)
+  doAssert correct == 20
+
+proc direct =
+  for i in 1 .. 20:
+    try:
+      raise newException(ValueError, "stuff happening")
+    except ValueError:
+      discard
+
+let mem = getOccupiedMem()
+indirectViaProcCall()
+direct()
+echo getOccupiedMem() - mem
diff --git a/tests/destructor/tgcleak4.nim b/tests/destructor/tgcleak4.nim
new file mode 100644
index 000000000..4299c8841
--- /dev/null
+++ b/tests/destructor/tgcleak4.nim
@@ -0,0 +1,47 @@
+discard """
+  outputsub: "no leak: "
+  cmd: "nim c --gc:arc $file"
+"""
+# bug #12758
+type
+  TExpr {.inheritable.} = object ## abstract base class for an expression
+  PLiteral = ref TLiteral
+  TLiteral = object of TExpr
+    x: int
+    op1: string
+  TPlusExpr = object of TExpr
+    a, b: ref TExpr
+    op2: string
+
+method eval(e: ref TExpr): int {.base.} =
+  # override this base method
+  quit "to override!"
+
+method eval(e: ref TLiteral): int = return e.x
+
+method eval(e: ref TPlusExpr): int =
+  # watch out: relies on dynamic binding
+  return eval(e.a) + eval(e.b)
+
+proc newLit(x: int): ref TLiteral =
+  new(result)
+  result.x = x
+  result.op1 = $getOccupiedMem()
+
+proc newPlus(a, b: ref TExpr): ref TPlusExpr =
+  new(result)
+  result.a = a
+  result.b = b
+  result.op2 = $getOccupiedMem()
+
+const Limit = when compileOption("gc", "markAndSweep") or compileOption("gc", "boehm"): 5*1024*1024 else: 500_000
+
+for i in 0..100_000:
+  var s: array[0..11, ref TExpr]
+  for j in 0..high(s):
+    s[j] = newPlus(newPlus(newLit(j), newLit(2)), newLit(4))
+    if eval(s[j]) != j+6:
+      quit "error: wrong result"
+  if getOccupiedMem() > Limit: quit("still a leak!")
+
+echo "no leak: ", getOccupiedMem()
diff --git a/tests/destructor/tv2_raise.nim b/tests/destructor/tv2_raise.nim
index 5795c08a0..dcc3b1b38 100644
--- a/tests/destructor/tv2_raise.nim
+++ b/tests/destructor/tv2_raise.nim
@@ -1,7 +1,7 @@
 discard """
   cmd: '''nim c --newruntime $file'''
   output: '''OK 3
-5 1'''
+5 2'''
 """
 
 import strutils, math