summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--compiler/ast.nim6
-rw-r--r--compiler/ccgexprs.nim10
-rw-r--r--compiler/injectdestructors.nim18
-rw-r--r--compiler/liftdestructors.nim132
-rw-r--r--lib/system.nim4
-rw-r--r--lib/system/arc.nim4
-rw-r--r--testament/important_packages.nim2
-rw-r--r--tests/arc/tdup.nim7
-rw-r--r--tests/arc/topt_no_cursor.nim15
-rw-r--r--tests/arc/topt_wasmoved_destroy_pairs.nim3
-rw-r--r--tests/destructor/tatomicptrs.nim10
-rw-r--r--tests/destructor/tmatrix.nim5
-rw-r--r--tests/destructor/tprevent_assign2.nim7
-rw-r--r--tests/destructor/tprevent_assign3.nim7
-rw-r--r--tests/destructor/tv2_cast.nim14
15 files changed, 151 insertions, 93 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index b27b16fe2..2a5f18809 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -944,10 +944,10 @@ type
     attachedWasMoved,
     attachedDestructor,
     attachedAsgn,
+    attachedDup,
     attachedSink,
     attachedTrace,
-    attachedDeepCopy,
-    attachedDup
+    attachedDeepCopy
 
   TType* {.acyclic.} = object of TIdObj # \
                               # types are identical iff they have the
@@ -1518,7 +1518,7 @@ proc newProcNode*(kind: TNodeKind, info: TLineInfo, body: PNode,
 
 const
   AttachedOpToStr*: array[TTypeAttachedOp, string] = [
-    "=wasMoved", "=destroy", "=copy", "=sink", "=trace", "=deepcopy", "=dup"]
+    "=wasMoved", "=destroy", "=copy", "=dup", "=sink", "=trace", "=deepcopy"]
 
 proc `$`*(s: PSym): string =
   if s != nil:
diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim
index 6702c7537..a1c81599c 100644
--- a/compiler/ccgexprs.nim
+++ b/compiler/ccgexprs.nim
@@ -2359,11 +2359,6 @@ proc genMove(p: BProc; n: PNode; d: var TLoc) =
     genAssignment(p, d, a, {})
     resetLoc(p, a)
 
-proc genDup(p: BProc; src: TLoc; d: var TLoc; n: PNode) =
-  if d.k == locNone: getTemp(p, n.typ, d)
-  linefmt(p, cpsStmts, "#nimDupRef((void**)$1, (void*)$2);$n",
-          [addrLoc(p.config, d), rdLoc(src)])
-
 proc genDestroy(p: BProc; n: PNode) =
   if optSeqDestructors in p.config.globalOptions:
     let arg = n[1].skipAddr
@@ -2615,11 +2610,6 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
   of mAccessTypeField: genAccessTypeField(p, e, d)
   of mSlice: genSlice(p, e, d)
   of mTrace: discard "no code to generate"
-  of mDup:
-    var a: TLoc
-    let x = if e[1].kind in {nkAddr, nkHiddenAddr}: e[1][0] else: e[1]
-    initLocExpr(p, x, a)
-    genDup(p, a, d, e)
   else:
     when defined(debugMagics):
       echo p.prc.name.s, " ", p.prc.id, " ", p.prc.flags, " ", p.prc.ast[genericParamsPos].kind
diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim
index 3275c1abc..a03f85d02 100644
--- a/compiler/injectdestructors.nim
+++ b/compiler/injectdestructors.nim
@@ -188,7 +188,7 @@ template isUnpackedTuple(n: PNode): bool =
 
 proc checkForErrorPragma(c: Con; t: PType; ri: PNode; opname: string) =
   var m = "'" & opname & "' is not available for type <" & typeToString(t) & ">"
-  if (opname == "=" or opname == "=copy") and ri != nil:
+  if (opname == "=" or opname == "=copy" or opname == "=dup") and ri != nil:
     m.add "; requires a copy because it's not the last read of '"
     m.add renderTree(ri)
     m.add '\''
@@ -427,21 +427,17 @@ proc passCopyToSink(n: PNode; c: var Con; s: var Scope): PNode =
   if hasDestructor(c, n.typ):
     let typ = n.typ.skipTypes({tyGenericInst, tyAlias, tySink})
     let op = getAttachedOp(c.graph, typ, attachedDup)
-    if op != nil:
+    if op != nil and tfHasOwned notin typ.flags:
+      if sfError in op.flags:
+        c.checkForErrorPragma(n.typ, n, "=dup")
       let src = p(n, c, s, normal)
-      result.add newTreeI(nkFastAsgn,
-          src.info, tmp,
-          newTreeIT(nkCall, src.info, src.typ,
+      var newCall = newTreeIT(nkCall, src.info, src.typ,
             newSymNode(op),
             src)
-      )
-    elif typ.kind == tyRef:
-      let src = p(n, c, s, normal)
+      c.finishCopy(newCall, n, isFromSink = true)
       result.add newTreeI(nkFastAsgn,
           src.info, tmp,
-          newTreeIT(nkCall, src.info, src.typ,
-            newSymNode(createMagic(c.graph, c.idgen, "`=dup`", mDup)),
-            src)
+          newCall
       )
     else:
       result.add c.genWasMoved(tmp)
diff --git a/compiler/liftdestructors.nim b/compiler/liftdestructors.nim
index e8db14569..e8f25f1a3 100644
--- a/compiler/liftdestructors.nim
+++ b/compiler/liftdestructors.nim
@@ -34,6 +34,7 @@ type
 
 template destructor*(t: PType): PSym = getAttachedOp(c.g, t, attachedDestructor)
 template assignment*(t: PType): PSym = getAttachedOp(c.g, t, attachedAsgn)
+template dup*(t: PType): PSym = getAttachedOp(c.g, t, attachedDup)
 template asink*(t: PType): PSym = getAttachedOp(c.g, t, attachedSink)
 
 proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode)
@@ -82,7 +83,7 @@ proc genBuiltin(c: var TLiftCtx; magic: TMagic; name: string; i: PNode): PNode =
   result = genBuiltin(c.g, c.idgen, magic, name, i)
 
 proc defaultOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
-  if c.kind in {attachedAsgn, attachedDeepCopy, attachedSink}:
+  if c.kind in {attachedAsgn, attachedDeepCopy, attachedSink, attachedDup}:
     body.add newAsgnStmt(x, y)
   elif c.kind == attachedDestructor and c.addMemReset:
     let call = genBuiltin(c, mDefault, "default", x)
@@ -283,7 +284,7 @@ proc boolLit*(g: ModuleGraph; info: TLineInfo; value: bool): PNode =
   result.typ = getSysType(g, info, tyBool)
 
 proc getCycleParam(c: TLiftCtx): PNode =
-  assert c.kind == attachedAsgn
+  assert c.kind in {attachedAsgn, attachedDup}
   if c.fn.typ.len == 4:
     result = c.fn.typ.n.lastSon
     assert result.kind == nkSym
@@ -322,6 +323,9 @@ proc newOpCall(c: var TLiftCtx; op: PSym; x: PNode): PNode =
 proc newDeepCopyCall(c: var TLiftCtx; op: PSym; x, y: PNode): PNode =
   result = newAsgnStmt(x, newOpCall(c, op, y))
 
+proc newDupCall(c: var TLiftCtx; op: PSym; x, y: PNode): PNode =
+  result = newAsgnStmt(x, newOpCall(c, op, y))
+
 proc usesBuiltinArc(t: PType): bool =
   proc wrap(t: PType): bool {.nimcall.} = ast.isGCedMem(t)
   result = types.searchTypeFor(t, wrap)
@@ -464,7 +468,18 @@ proc considerUserDefinedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool =
       result = true
 
   of attachedDup:
-    assert false, "cannot happen"
+    var op = getAttachedOp(c.g, t, attachedDup)
+    if op != nil and sfOverriden in op.flags:
+
+      if op.ast.isGenericRoutine:
+        # patch generic destructor:
+        op = instantiateGeneric(c, op, t, t.typeInst)
+        setAttachedOp(c.g, c.idgen.module, t, attachedDup, op)
+
+      #markUsed(c.g.config, c.info, op, c.g.usageSym)
+      onUse(c.info, op)
+      body.add newDupCall(c, op, x, y)
+      result = true
 
 proc declareCounter(c: var TLiftCtx; body: PNode; first: BiggestInt): PNode =
   var temp = newSym(skTemp, getIdent(c.g.cache, lowerings.genPrefix), c.idgen, c.fn, c.info)
@@ -526,6 +541,9 @@ proc forallElements(c: var TLiftCtx; t: PType; body, x, y: PNode) =
 
 proc fillSeqOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   case c.kind
+  of attachedDup:
+    body.add setLenSeqCall(c, t, x, y)
+    forallElements(c, t, body, x, y)
   of attachedAsgn, attachedDeepCopy:
     # we generate:
     # setLen(dest, y.len)
@@ -549,15 +567,13 @@ proc fillSeqOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
       # follow all elements:
       forallElements(c, t, body, x, y)
   of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x)
-  of attachedDup:
-    assert false, "cannot happen"
 
 proc useSeqOrStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   createTypeBoundOps(c.g, c.c, t, body.info, c.idgen)
   # recursions are tricky, so we might need to forward the generated
   # operation here:
   var t = t
-  if t.assignment == nil or t.destructor == nil:
+  if t.assignment == nil or t.destructor == nil or t.dup == nil:
     let h = sighashes.hashType(t,c.g.config, {CoType, CoConsiderOwned, CoDistinct})
     let canon = c.g.canonTypes.getOrDefault(h)
     if canon != nil: t = canon
@@ -590,11 +606,15 @@ proc useSeqOrStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
       body.add newHookCall(c, op, x, y)
   of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x)
   of attachedDup:
-    assert false, "cannot happen"
+    # XXX: replace these with assertions.
+    let op = getAttachedOp(c.g, t, c.kind)
+    if op == nil:
+      return # protect from recursion
+    body.add newDupCall(c, op, x, y)
 
 proc fillStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   case c.kind
-  of attachedAsgn, attachedDeepCopy:
+  of attachedAsgn, attachedDeepCopy, attachedDup:
     body.add callCodegenProc(c.g, "nimAsgnStrV2", c.info, genAddr(c, x), y)
   of attachedSink:
     let moveCall = genBuiltin(c, mMove, "move", x)
@@ -607,8 +627,6 @@ proc fillStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   of attachedTrace:
     discard "strings are atomic and have no inner elements that are to trace"
   of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x)
-  of attachedDup:
-    assert false, "cannot happen"
 
 proc cyclicType*(g: ModuleGraph, t: PType): bool =
   case t.kind
@@ -648,7 +666,7 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   # dynamic Acyclic refs need to use dyn decRef
 
   let tmp =
-    if isCyclic and c.kind in {attachedAsgn, attachedSink}:
+    if isCyclic and c.kind in {attachedAsgn, attachedSink, attachedDup}:
       declareTempOf(c, body, x)
     else:
       x
@@ -709,7 +727,14 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
       #echo "can follow ", elemType, " static ", isFinal(elemType)
   of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x)
   of attachedDup:
-    assert false, "cannot happen"
+    if isCyclic:
+      body.add newAsgnStmt(x, y)
+      body.add genIf(c, y, callCodegenProc(c.g,
+          "nimIncRefCyclic", c.info, y, getCycleParam(c)))
+    else:
+      body.add newAsgnStmt(x, y)
+      body.add genIf(c, y, callCodegenProc(c.g,
+          "nimIncRef", c.info, y))
 
 proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   ## Closures are really like refs except they always use a virtual destructor
@@ -719,7 +744,7 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
 
   let isCyclic = c.g.config.selectedGC == gcOrc
   let tmp =
-    if isCyclic and c.kind in {attachedAsgn, attachedSink}:
+    if isCyclic and c.kind in {attachedAsgn, attachedSink, attachedDup}:
       declareTempOf(c, body, xenv)
     else:
       xenv
@@ -753,14 +778,21 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
 
       body.add genIf(c, cond, actions)
       body.add newAsgnStmt(x, y)
+  of attachedDup:
+    let yenv = genBuiltin(c, mAccessEnv, "accessEnv", y)
+    yenv.typ = getSysType(c.g, c.info, tyPointer)
+    if isCyclic:
+      body.add newAsgnStmt(x, y)
+      body.add genIf(c, yenv, callCodegenProc(c.g, "nimIncRefCyclic", c.info, yenv, getCycleParam(c)))
+    else:
+      body.add newAsgnStmt(x, y)
+      body.add genIf(c, yenv, callCodegenProc(c.g, "nimIncRef", c.info, yenv))
   of attachedDestructor:
     body.add genIf(c, cond, actions)
   of attachedDeepCopy: assert(false, "cannot happen")
   of attachedTrace:
     body.add callCodegenProc(c.g, "nimTraceRefDyn", c.info, genAddrOf(xenv, c.idgen), y)
   of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x)
-  of attachedDup:
-    assert false, "cannot happen"
 
 proc weakrefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   case c.kind
@@ -773,6 +805,9 @@ proc weakrefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
     body.add genIf(c, y, callCodegenProc(c.g, "nimIncRef", c.info, y))
     body.add genIf(c, x, callCodegenProc(c.g, "nimDecWeakRef", c.info, x))
     body.add newAsgnStmt(x, y)
+  of attachedDup:
+    body.add newAsgnStmt(x, y)
+    body.add genIf(c, y, callCodegenProc(c.g, "nimIncRef", c.info, y))
   of attachedDestructor:
     # it's better to prepend the destruction of weak refs in order to
     # prevent wrong "dangling refs exist" problems:
@@ -786,8 +821,6 @@ proc weakrefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   of attachedDeepCopy: assert(false, "cannot happen")
   of attachedTrace: discard
   of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x)
-  of attachedDup:
-    assert false, "cannot happen"
 
 proc ownedRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   var actions = newNodeI(nkStmtList, c.info)
@@ -809,13 +842,13 @@ proc ownedRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   of attachedSink, attachedAsgn:
     body.add genIf(c, x, actions)
     body.add newAsgnStmt(x, y)
+  of attachedDup:
+    body.add newAsgnStmt(x, y)
   of attachedDestructor:
     body.add genIf(c, x, actions)
   of attachedDeepCopy: assert(false, "cannot happen")
   of attachedTrace: discard
   of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x)
-  of attachedDup:
-    assert false, "cannot happen"
 
 proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   if c.kind == attachedDeepCopy:
@@ -842,6 +875,11 @@ proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
       body.add genIf(c, yy, callCodegenProc(c.g, "nimIncRef", c.info, yy))
       body.add genIf(c, xx, callCodegenProc(c.g, "nimDecWeakRef", c.info, xx))
       body.add newAsgnStmt(x, y)
+    of attachedDup:
+      let yy = genBuiltin(c, mAccessEnv, "accessEnv", y)
+      yy.typ = getSysType(c.g, c.info, tyPointer)
+      body.add newAsgnStmt(x, y)
+      body.add genIf(c, yy, callCodegenProc(c.g, "nimIncRef", c.info, yy))
     of attachedDestructor:
       let des = genIf(c, xx, callCodegenProc(c.g, "nimDecWeakRef", c.info, xx))
       if body.len == 0:
@@ -851,8 +889,6 @@ proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
     of attachedDeepCopy: assert(false, "cannot happen")
     of attachedTrace: discard
     of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x)
-    of attachedDup:
-      assert false, "cannot happen"
 
 proc ownedClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   let xx = genBuiltin(c, mAccessEnv, "accessEnv", x)
@@ -864,13 +900,13 @@ proc ownedClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   of attachedSink, attachedAsgn:
     body.add genIf(c, xx, actions)
     body.add newAsgnStmt(x, y)
+  of attachedDup:
+    body.add newAsgnStmt(x, y)
   of attachedDestructor:
     body.add genIf(c, xx, actions)
   of attachedDeepCopy: assert(false, "cannot happen")
   of attachedTrace: discard
   of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x)
-  of attachedDup:
-    assert false, "cannot happen"
 
 proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   case t.kind
@@ -936,7 +972,7 @@ proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
     if not considerUserDefinedOp(c, t, body, x, y):
       if t.sym != nil and sfImportc in t.sym.flags:
         case c.kind
-        of {attachedAsgn, attachedSink}:
+        of {attachedAsgn, attachedSink, attachedDup}:
           body.add newAsgnStmt(x, y)
         of attachedWasMoved:
           body.add genBuiltin(c, mWasMoved, "`=wasMoved`", x)
@@ -976,8 +1012,44 @@ proc produceSymDistinctType(g: ModuleGraph; c: PContext; typ: PType;
   result = getAttachedOp(g, baseType, kind)
   setAttachedOp(g, idgen.module, typ, kind, result)
 
+proc symDupPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp;
+              info: TLineInfo; idgen: IdGenerator): PSym =
+  let procname = getIdent(g.cache, AttachedOpToStr[kind])
+  result = newSym(skProc, procname, idgen, owner, info)
+  let res = newSym(skResult, getIdent(g.cache, "result"), idgen, result, info)
+  let src = newSym(skParam, getIdent(g.cache, "src"),
+                   idgen, result, info)
+  res.typ = typ
+  src.typ = typ
+
+  result.typ = newType(tyProc, nextTypeId idgen, owner)
+  result.typ.n = newNodeI(nkFormalParams, info)
+  rawAddSon(result.typ, res.typ)
+  result.typ.n.add newNodeI(nkEffectList, info)
+
+  result.typ.addParam src
+
+  if g.config.selectedGC == gcOrc and
+    cyclicType(g, typ.skipTypes(abstractInst)):
+    let cycleParam = newSym(skParam, getIdent(g.cache, "cyclic"),
+                            idgen, result, info)
+    cycleParam.typ = getSysType(g, info, tyBool)
+    result.typ.addParam cycleParam
+
+  var n = newNodeI(nkProcDef, info, bodyPos+2)
+  for i in 0..<n.len: n[i] = newNodeI(nkEmpty, info)
+  n[namePos] = newSymNode(result)
+  n[paramsPos] = result.typ.n
+  n[bodyPos] = newNodeI(nkStmtList, info)
+  n[resultPos] = newSymNode(res)
+  result.ast = n
+  incl result.flags, sfFromGeneric
+  incl result.flags, sfGeneratedOp
+
 proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp;
               info: TLineInfo; idgen: IdGenerator): PSym =
+  if kind == attachedDup:
+    return symDupPrototype(g, typ, owner, kind, info, idgen)
 
   let procname = getIdent(g.cache, AttachedOpToStr[kind])
   result = newSym(skProc, procname, idgen, owner, info)
@@ -992,7 +1064,7 @@ proc symPrototype(g: ModuleGraph; typ: PType; owner: PSym; kind: TTypeAttachedOp
 
   result.typ = newProcType(info, nextTypeId(idgen), owner)
   result.typ.addParam dest
-  if kind notin {attachedDestructor, attachedWasMoved, attachedDup}:
+  if kind notin {attachedDestructor, attachedWasMoved}:
     result.typ.addParam src
 
   if kind == attachedAsgn and g.config.selectedGC == gcOrc and
@@ -1033,9 +1105,11 @@ proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp;
   var a = TLiftCtx(info: info, g: g, kind: kind, c: c, asgnForType: typ, idgen: idgen,
                    fn: result)
 
-  let dest = result.typ.n[1].sym
-  let d = newDeref(newSymNode(dest))
-  let src = if kind in {attachedDestructor, attachedWasMoved, attachedDup}: newNodeIT(nkSym, info, getSysType(g, info, tyPointer))
+  let dest = if kind == attachedDup: result.ast[resultPos].sym else: result.typ.n[1].sym
+  let d = if kind == attachedDup: newSymNode(dest) else: newDeref(newSymNode(dest))
+  let src = case kind
+            of {attachedDestructor, attachedWasMoved}: newNodeIT(nkSym, info, getSysType(g, info, tyPointer))
+            of attachedDup: newSymNode(result.typ.n[1].sym)
             else: newSymNode(result.typ.n[2].sym)
 
   # register this operation already:
@@ -1059,7 +1133,7 @@ proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp;
       fillStrOp(a, typ, result.ast[bodyPos], d, src)
     else:
       fillBody(a, typ, result.ast[bodyPos], d, src)
-      if tk == tyObject and a.kind in {attachedAsgn, attachedSink, attachedDeepCopy} and not lacksMTypeField(typ):
+      if tk == tyObject and a.kind in {attachedAsgn, attachedSink, attachedDeepCopy, attachedDup} and not lacksMTypeField(typ):
         # bug #19205: Do not forget to also copy the hidden type field:
         genTypeFieldCopy(a, typ, result.ast[bodyPos], d, src)
 
diff --git a/lib/system.nim b/lib/system.nim
index f8523b094..b0d900076 100644
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -137,7 +137,7 @@ proc new*[T](a: var ref T, finalizer: proc (x: ref T) {.nimcall.}) {.
   ## **Note**: The `finalizer` refers to the type `T`, not to the object!
   ## This means that for each object of type `T` the finalizer will be called!
 
-proc `=wasMoved`[T](obj: var T) {.magic: "WasMoved", noSideEffect.} =
+proc `=wasMoved`*[T](obj: var T) {.magic: "WasMoved", noSideEffect.} =
   ## Generic `wasMoved`:idx: implementation that can be overridden.
 
 proc wasMoved*[T](obj: var T) {.inline, noSideEffect.} =
@@ -354,7 +354,7 @@ proc `=destroy`*[T](x: var T) {.inline, magic: "Destroy".} =
   discard
 
 when defined(nimHasDup):
-  proc `=dup`*[T](x: ref T): ref T {.inline, magic: "Dup".} =
+  proc `=dup`*[T](x: T): T {.inline, magic: "Dup".} =
     ## Generic `dup`:idx: implementation that can be overridden.
     discard
 
diff --git a/lib/system/arc.nim b/lib/system/arc.nim
index cc93eb9fc..2186806d6 100644
--- a/lib/system/arc.nim
+++ b/lib/system/arc.nim
@@ -200,10 +200,6 @@ proc nimDecRefIsLast(p: pointer): bool {.compilerRtl, inl.} =
       when traceCollector:
         cprintf("[DECREF] %p\n", cell)
 
-proc nimDupRef(dest: ptr pointer, src: pointer) {.compilerRtl, inl.} =
-  dest[] = src
-  if src != nil: nimIncRef src
-
 proc GC_unref*[T](x: ref T) =
   ## New runtime only supports this operation for 'ref T'.
   var y {.cursor.} = x
diff --git a/testament/important_packages.nim b/testament/important_packages.nim
index e2d1024b1..1ab6f3f04 100644
--- a/testament/important_packages.nim
+++ b/testament/important_packages.nim
@@ -87,7 +87,7 @@ pkg "kdtree", "nimble test -d:nimLegacyRandomInitRand", "https://github.com/jbli
 pkg "loopfusion"
 pkg "lockfreequeues"
 pkg "macroutils"
-pkg "manu"
+pkg "manu", url = "https://github.com/nim-lang/manu"
 pkg "markdown"
 pkg "measuremancer", "nimble testDeps; nimble -y test"
 pkg "memo"
diff --git a/tests/arc/tdup.nim b/tests/arc/tdup.nim
index b77f5c6eb..61f262a68 100644
--- a/tests/arc/tdup.nim
+++ b/tests/arc/tdup.nim
@@ -18,7 +18,7 @@ inc:
 var id_1 = 777
 s = RefCustom(id_2: addr(id_1))
 inc_1 :
-  :tmpD_1 = `=dup`(s)
+  :tmpD_1 = `=dup_1`(s)
   :tmpD_1
 inc_1 :
   let blitTmp_1 = s
@@ -34,14 +34,15 @@ type
   RefCustom = object
     id: ptr int
 
+proc `=dup`(x: RefCustom): RefCustom =
+  result.id = x.id
+
 proc inc(x: sink Ref) =
   inc x.id
 
 proc inc(x: sink RefCustom) =
   inc x.id[]
 
-proc `=dup`(x: RefCustom): RefCustom =
-  result.id = x.id
 
 proc foo =
   var x = Ref(id: 8)
diff --git a/tests/arc/topt_no_cursor.nim b/tests/arc/topt_no_cursor.nim
index 32652b60a..39e5c8a9b 100644
--- a/tests/arc/topt_no_cursor.nim
+++ b/tests/arc/topt_no_cursor.nim
@@ -61,11 +61,9 @@ var
 try:
   it_cursor = x
   a = (
-    `=wasMoved`(:tmpD)
-    `=copy`(:tmpD, it_cursor.key)
+    :tmpD = `=dup`(it_cursor.key)
     :tmpD,
-    `=wasMoved`(:tmpD_1)
-    `=copy`(:tmpD_1, it_cursor.val)
+    :tmpD_1 = `=dup`(it_cursor.val)
     :tmpD_1)
   echo [
     :tmpD_2 = `$$`(a)
@@ -125,19 +123,16 @@ this.isValid = fileExists(this.value)
 if dirExists(this.value):
   var :tmpD
   par = (dir:
-    `=wasMoved`(:tmpD)
-    `=copy`(:tmpD, this.value)
+    :tmpD = `=dup`(this.value)
     :tmpD, front: "") else:
   var
     :tmpD_1
     :tmpD_2
     :tmpD_3
   par = (dir_1: parentDir(this.value), front_1:
-    `=wasMoved`(:tmpD_1)
-    `=copy`(:tmpD_1,
+    :tmpD_1 = `=dup`(
       :tmpD_3 = splitDrive do:
-        `=wasMoved`(:tmpD_2)
-        `=copy`(:tmpD_2, this.value)
+        :tmpD_2 = `=dup`(this.value)
         :tmpD_2
       :tmpD_3.path)
     :tmpD_1)
diff --git a/tests/arc/topt_wasmoved_destroy_pairs.nim b/tests/arc/topt_wasmoved_destroy_pairs.nim
index 6577d6787..c96340a30 100644
--- a/tests/arc/topt_wasmoved_destroy_pairs.nim
+++ b/tests/arc/topt_wasmoved_destroy_pairs.nim
@@ -39,8 +39,7 @@ try:
         if i_cursor == 2:
           return
         add(a):
-          `=wasMoved`(:tmpD)
-          `=copy`(:tmpD, x)
+          :tmpD = `=dup`(x)
           :tmpD
         inc i_1, 1
   if cond:
diff --git a/tests/destructor/tatomicptrs.nim b/tests/destructor/tatomicptrs.nim
index 88f84d67c..7bd5482b2 100644
--- a/tests/destructor/tatomicptrs.nim
+++ b/tests/destructor/tatomicptrs.nim
@@ -39,7 +39,7 @@ proc `=destroy`*[T](dest: var SharedPtr[T]) =
     echo "deallocating"
     dest.x = nil
 
-proc `=`*[T](dest: var SharedPtr[T]; src: SharedPtr[T]) =
+proc `=copy`*[T](dest: var SharedPtr[T]; src: SharedPtr[T]) =
   var s = src.x
   if s != nil: incRef(s)
   #atomicSwap(dest, s)
@@ -50,6 +50,9 @@ proc `=`*[T](dest: var SharedPtr[T]; src: SharedPtr[T]) =
     deallocShared(s)
     echo "deallocating"
 
+proc `=dup`*[T](src: SharedPtr[T]): SharedPtr[T] =
+  `=copy`(result, src)
+
 proc `=sink`*[T](dest: var SharedPtr[T]; src: SharedPtr[T]) =
   ## XXX make this an atomic store:
   if dest.x != src.x:
@@ -120,7 +123,7 @@ proc `=destroy`*[T](m: var MySeq[T]) {.inline.} =
     deallocShared(m.data)
     m.data = nil
 
-proc `=`*[T](m: var MySeq[T], m2: MySeq[T]) =
+proc `=copy`*[T](m: var MySeq[T], m2: MySeq[T]) =
   if m.data == m2.data: return
   if m.data != nil:
     `=destroy`(m)
@@ -131,6 +134,9 @@ proc `=`*[T](m: var MySeq[T], m2: MySeq[T]) =
     m.data = cast[ptr UncheckedArray[T]](allocShared(bytes))
     copyMem(m.data, m2.data, bytes)
 
+proc `=dup`*[T](m: MySeq[T]): MySeq[T] =
+  `=copy`[T](result, m)
+
 proc `=sink`*[T](m: var MySeq[T], m2: MySeq[T]) {.inline.} =
   if m.data != m2.data:
     if m.data != nil:
diff --git a/tests/destructor/tmatrix.nim b/tests/destructor/tmatrix.nim
index 98ca95c94..2fd5af789 100644
--- a/tests/destructor/tmatrix.nim
+++ b/tests/destructor/tmatrix.nim
@@ -31,7 +31,7 @@ proc `=sink`*(a: var Matrix; b: Matrix) =
   a.m = b.m
   a.n = b.n
 
-proc `=`*(a: var Matrix; b: Matrix) =
+proc `=copy`*(a: var Matrix; b: Matrix) =
   if a.data != nil and a.data != b.data:
     dealloc(a.data)
     deallocCount.inc
@@ -43,6 +43,9 @@ proc `=`*(a: var Matrix; b: Matrix) =
     allocCount.inc
     copyMem(a.data, b.data, b.m * b.n * sizeof(float))
 
+proc `=dup`*(a: Matrix): Matrix =
+  `=copy`(result, a)
+
 proc matrix*(m, n: int, s: float): Matrix =
   ## Construct an m-by-n constant matrix.
   result.m = m
diff --git a/tests/destructor/tprevent_assign2.nim b/tests/destructor/tprevent_assign2.nim
index fd1a711db..7d788fa5c 100644
--- a/tests/destructor/tprevent_assign2.nim
+++ b/tests/destructor/tprevent_assign2.nim
@@ -1,7 +1,7 @@
 discard """
-  errormsg: "'=copy' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'"
+  errormsg: "'=dup' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'"
   file: "tprevent_assign2.nim"
-  line: 48
+  line: 49
 """
 
 type
@@ -9,7 +9,8 @@ type
     x: int
 
 proc `=destroy`(f: var Foo) = f.x = 0
-proc `=`(a: var Foo; b: Foo) {.error.} # = a.x = b.x
+proc `=copy`(a: var Foo; b: Foo) {.error.} # = a.x = b.x
+proc `=dup`(a: Foo): Foo {.error.}
 proc `=sink`(a: var Foo; b: Foo) = a.x = b.x
 
 proc createTree(x: int): Foo =
diff --git a/tests/destructor/tprevent_assign3.nim b/tests/destructor/tprevent_assign3.nim
index b9bd40fa9..aa834a66c 100644
--- a/tests/destructor/tprevent_assign3.nim
+++ b/tests/destructor/tprevent_assign3.nim
@@ -1,7 +1,7 @@
 discard """
-  errormsg: "'=copy' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'"
+  errormsg: "'=dup' is not available for type <Foo>; requires a copy because it's not the last read of 'otherTree'"
   file: "tprevent_assign3.nim"
-  line: 46
+  line: 47
 """
 
 type
@@ -9,7 +9,8 @@ type
     x: int
 
 proc `=destroy`(f: var Foo) = f.x = 0
-proc `=`(a: var Foo; b: Foo) {.error.} # = a.x = b.x
+proc `=copy`(a: var Foo; b: Foo) {.error.} # = a.x = b.x
+proc `=dup`(a: Foo): Foo {.error.}
 proc `=sink`(a: var Foo; b: Foo) = a.x = b.x
 
 proc createTree(x: int): Foo =
diff --git a/tests/destructor/tv2_cast.nim b/tests/destructor/tv2_cast.nim
index 6fa419996..4ff2dc9a0 100644
--- a/tests/destructor/tv2_cast.nim
+++ b/tests/destructor/tv2_cast.nim
@@ -3,7 +3,7 @@ discard """
 @[116, 101, 115, 116]
 @[1953719668, 875770417]
 destroying O1'''
-  cmd: '''nim c --gc:arc --expandArc:main --expandArc:main1 --expandArc:main2 --expandArc:main3 --hints:off --assertions:off $file'''
+  cmd: '''nim c --mm:arc --expandArc:main --expandArc:main1 --expandArc:main2 --expandArc:main3 --hints:off --assertions:off $file'''
   nimout: '''
 --expandArc: main
 
@@ -13,8 +13,7 @@ var
   :tmpD_1
   :tmpD_2
 data =
-  `=wasMoved`(:tmpD)
-  `=copy`(:tmpD, cast[string](
+  :tmpD = `=dup`(cast[string](
     :tmpD_2 = encode(cast[seq[byte]](
       :tmpD_1 = newString(100)
       :tmpD_1))
@@ -33,8 +32,7 @@ var
   :tmpD_1
 s = newString(100)
 data =
-  `=wasMoved`(:tmpD)
-  `=copy`(:tmpD, cast[string](
+  :tmpD = `=dup`(cast[string](
     :tmpD_1 = encode(toOpenArrayByte(s, 0, len(s) - 1))
     :tmpD_1))
   :tmpD
@@ -51,8 +49,7 @@ var
   :tmpD_1
 s = newSeq(100)
 data =
-  `=wasMoved`(:tmpD)
-  `=copy`(:tmpD, cast[string](
+  :tmpD = `=dup`(cast[string](
     :tmpD_1 = encode(s)
     :tmpD_1))
   :tmpD
@@ -68,8 +65,7 @@ var
   :tmpD_1
   :tmpD_2
 data =
-  `=wasMoved`(:tmpD)
-  `=copy`(:tmpD, cast[string](
+  :tmpD = `=dup`(cast[string](
     :tmpD_2 = encode do:
       :tmpD_1 = newSeq(100)
       :tmpD_1