summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--compiler/ast.nim7
-rw-r--r--compiler/ccgexprs.nim10
-rw-r--r--compiler/condsyms.nim3
-rw-r--r--compiler/injectdestructors.nim27
-rw-r--r--compiler/jsgen.nim9
-rw-r--r--compiler/liftdestructors.nim26
-rw-r--r--compiler/semstmts.nim16
-rw-r--r--compiler/vmgen.nim6
-rw-r--r--lib/system.nim6
-rw-r--r--lib/system/arc.nim4
-rw-r--r--tests/arc/tdup.nim70
-rw-r--r--tests/arc/topt_no_cursor.nim3
12 files changed, 170 insertions, 17 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index bf942f784..815cb00dc 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -687,7 +687,7 @@ type
     mIsPartOf, mAstToStr, mParallel,
     mSwap, mIsNil, mArrToSeq, mOpenArrayToSeq,
     mNewString, mNewStringOfCap, mParseBiggestFloat,
-    mMove, mWasMoved, mDestroy, mTrace,
+    mMove, mWasMoved, mDup, mDestroy, mTrace,
     mDefault, mUnown, mFinished, mIsolate, mAccessEnv, mAccessTypeField, mReset,
     mArray, mOpenArray, mRange, mSet, mSeq, mVarargs,
     mRef, mPtr, mVar, mDistinct, mVoid, mTuple,
@@ -944,7 +944,8 @@ type
     attachedAsgn,
     attachedSink,
     attachedTrace,
-    attachedDeepCopy
+    attachedDeepCopy,
+    attachedDup
 
   TType* {.acyclic.} = object of TIdObj # \
                               # types are identical iff they have the
@@ -1515,7 +1516,7 @@ proc newProcNode*(kind: TNodeKind, info: TLineInfo, body: PNode,
 
 const
   AttachedOpToStr*: array[TTypeAttachedOp, string] = [
-    "=wasMoved", "=destroy", "=copy", "=sink", "=trace", "=deepcopy"]
+    "=wasMoved", "=destroy", "=copy", "=sink", "=trace", "=deepcopy", "=dup"]
 
 proc `$`*(s: PSym): string =
   if s != nil:
diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim
index 265ccb92c..e351e95b0 100644
--- a/compiler/ccgexprs.nim
+++ b/compiler/ccgexprs.nim
@@ -2346,6 +2346,11 @@ 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
@@ -2597,6 +2602,11 @@ 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/condsyms.nim b/compiler/condsyms.nim
index fa7f56504..8e0e2f300 100644
--- a/compiler/condsyms.nim
+++ b/compiler/condsyms.nim
@@ -154,5 +154,6 @@ proc initDefines*(symbols: StringTableRef) =
   defineSymbol("nimHasGenericDefine")
   defineSymbol("nimHasDefineAliases")
   defineSymbol("nimHasWarnBareExcept")
-
+  defineSymbol("nimHasDup")
   defineSymbol("nimHasChecksums")
+
diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim
index ccb19720d..d9a2da5a0 100644
--- a/compiler/injectdestructors.nim
+++ b/compiler/injectdestructors.nim
@@ -425,11 +425,28 @@ proc passCopyToSink(n: PNode; c: var Con; s: var Scope): PNode =
   result = newNodeIT(nkStmtListExpr, n.info, n.typ)
   let tmp = c.getTemp(s, n.typ, n.info)
   if hasDestructor(c, n.typ):
-    result.add c.genWasMoved(tmp)
-    var m = c.genCopy(tmp, n, {})
-    m.add p(n, c, s, normal)
-    c.finishCopy(m, n, isFromSink = true)
-    result.add m
+    let typ = n.typ.skipTypes({tyGenericInst, tyAlias, tySink})
+    let op = getAttachedOp(c.graph, typ, attachedDup)
+    if op != nil:
+      let src = p(n, c, s, normal)
+      result.add newTreeI(nkFastAsgn,
+          src.info, tmp,
+          genOp(c, op, src)
+      )
+    elif typ.kind == tyRef:
+      let src = p(n, c, s, normal)
+      result.add newTreeI(nkFastAsgn,
+          src.info, tmp,
+          newTreeIT(nkCall, src.info, src.typ,
+            newSymNode(createMagic(c.graph, c.idgen, "`=dup`", mDup)),
+            src)
+      )
+    else:
+      result.add c.genWasMoved(tmp)
+      var m = c.genCopy(tmp, n, {})
+      m.add p(n, c, s, normal)
+      c.finishCopy(m, n, isFromSink = true)
+      result.add m
     if isLValue(n) and not isCapturedVar(n) and n.typ.skipTypes(abstractInst).kind != tyRef and c.inSpawn == 0:
       message(c.graph.config, n.info, hintPerformance,
         ("passing '$1' to a sink parameter introduces an implicit copy; " &
diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim
index 619555969..45b0baec0 100644
--- a/compiler/jsgen.nim
+++ b/compiler/jsgen.nim
@@ -2174,6 +2174,13 @@ proc genMove(p: PProc; n: PNode; r: var TCompRes) =
   genReset(p, n)
   #lineF(p, "$1 = $2;$n", [dest.rdLoc, src.rdLoc])
 
+proc genDup(p: PProc; n: PNode; r: var TCompRes) =
+  var a: TCompRes
+  r.kind = resVal
+  r.res = p.getTemp()
+  gen(p, n[1], a)
+  lineF(p, "$1 = $2;$n", [r.rdLoc, a.rdLoc])
+
 proc genJSArrayConstr(p: PProc, n: PNode, r: var TCompRes) =
   var a: TCompRes
   r.res = rope("[")
@@ -2368,6 +2375,8 @@ proc genMagic(p: PProc, n: PNode, r: var TCompRes) =
     r.kind = resExpr
   of mMove:
     genMove(p, n, r)
+  of mDup:
+    genDup(p, n, r)
   else:
     genCall(p, n, r)
     #else internalError(p.config, e.info, 'genMagic: ' + magicToStr[op]);
diff --git a/compiler/liftdestructors.nim b/compiler/liftdestructors.nim
index 3fd3c5f37..3a997af82 100644
--- a/compiler/liftdestructors.nim
+++ b/compiler/liftdestructors.nim
@@ -463,6 +463,9 @@ proc considerUserDefinedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool =
       body.add genWasMovedCall(c, op, x)
       result = true
 
+  of attachedDup:
+    assert false, "cannot happen"
+
 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)
   temp.typ = getSysType(c.g, body.info, tyInt)
@@ -546,6 +549,8 @@ 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)
@@ -584,6 +589,8 @@ proc useSeqOrStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
         return # protect from recursion
       body.add newHookCall(c, op, x, y)
   of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "wasMoved", x)
+  of attachedDup:
+    assert false, "cannot happen"
 
 proc fillStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   case c.kind
@@ -600,6 +607,8 @@ 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*(t: PType): bool =
   case t.kind
@@ -699,7 +708,8 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
         body.add callCodegenProc(c.g, "nimTraceRefDyn", c.info, genAddrOf(x, c.idgen), y)
       #echo "can follow ", elemType, " static ", isFinal(elemType)
   of attachedWasMoved: body.add genBuiltin(c, mWasMoved, "wasMoved", x)
-
+  of attachedDup:
+    assert false, "cannot happen"
 
 proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   ## Closures are really like refs except they always use a virtual destructor
@@ -749,6 +759,8 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   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
@@ -774,6 +786,8 @@ 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)
@@ -800,6 +814,8 @@ proc ownedRefOp(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 closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   if c.kind == attachedDeepCopy:
@@ -835,6 +851,8 @@ 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)
@@ -851,6 +869,8 @@ proc ownedClosureOp(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 fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   case t.kind
@@ -966,7 +986,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}:
+  if kind notin {attachedDestructor, attachedWasMoved, attachedDup}:
     result.typ.addParam src
 
   if kind == attachedAsgn and g.config.selectedGC == gcOrc and
@@ -1006,7 +1026,7 @@ proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp;
 
   let dest = result.typ.n[1].sym
   let d = newDeref(newSymNode(dest))
-  let src = if kind in {attachedDestructor, attachedWasMoved}: newNodeIT(nkSym, info, getSysType(g, info, tyPointer))
+  let src = if kind in {attachedDestructor, attachedWasMoved, attachedDup}: newNodeIT(nkSym, info, getSysType(g, info, tyPointer))
             else: newSymNode(result.typ.n[2].sym)
 
   # register this operation already:
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index e6fade528..126d1aa65 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -1815,9 +1815,12 @@ proc whereToBindTypeHook(c: PContext; t: PType): PType =
 proc bindTypeHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp) =
   let t = s.typ
   var noError = false
-  let cond = if op in {attachedDestructor, attachedWasMoved}:
+  let cond = case op
+             of {attachedDestructor, attachedWasMoved}:
                t.len == 2 and t[0] == nil and t[1].kind == tyVar
-             elif op == attachedTrace:
+             of attachedDup:
+               t.len == 2 and t[0] != nil and t[1].kind == tyVar
+             of attachedTrace:
                t.len == 3 and t[0] == nil and t[1].kind == tyVar and t[2].kind == tyPointer
              else:
                t.len >= 2 and t[0] == nil
@@ -1843,9 +1846,13 @@ proc bindTypeHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp) =
         localError(c.config, n.info, errGenerated,
           "type bound operation `" & s.name.s & "` can be defined only in the same module with its type (" & obj.typeToString() & ")")
   if not noError and sfSystemModule notin s.owner.flags:
-    if op == attachedTrace:
+    case op
+    of attachedTrace:
       localError(c.config, n.info, errGenerated,
         "signature for '=trace' must be proc[T: object](x: var T; env: pointer)")
+    of attachedDup:
+      localError(c.config, n.info, errGenerated,
+        "signature for '=dup' must be proc[T: object](x: var T): T")
     else:
       localError(c.config, n.info, errGenerated,
         "signature for '" & s.name.s & "' must be proc[T: object](x: var T)")
@@ -1938,6 +1945,9 @@ proc semOverride(c: PContext, s: PSym, n: PNode) =
   of "=wasmoved":
     if s.magic != mWasMoved:
       bindTypeHook(c, s, n, attachedWasMoved)
+  of "=dup":
+    if s.magic != mDup:
+      bindTypeHook(c, s, n, attachedDup)
   else:
     if sfOverriden in s.flags:
       localError(c.config, n.info, errGenerated,
diff --git a/compiler/vmgen.nim b/compiler/vmgen.nim
index db3276aad..25ff62bcc 100644
--- a/compiler/vmgen.nim
+++ b/compiler/vmgen.nim
@@ -1401,6 +1401,12 @@ proc genMagic(c: PCtx; n: PNode; dest: var TDest; m: TMagic) =
     # c.gABx(n, opcNodeToReg, a, a)
     # c.genAsgnPatch(arg, a)
     c.freeTemp(a)
+  of mDup:
+    let arg = n[1]
+    let a = c.genx(arg)
+    if dest < 0: dest = c.getTemp(arg.typ)
+    gABC(c, arg, whichAsgnOpc(arg, requiresCopy=false), dest, a)
+    c.freeTemp(a)
   of mNodeId:
     c.genUnaryABC(n, dest, opcNodeId)
   else:
diff --git a/lib/system.nim b/lib/system.nim
index f23242315..8bb50ba5d 100644
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -347,6 +347,12 @@ proc arrPut[I: Ordinal;T,S](a: T; i: I;
 proc `=destroy`*[T](x: var T) {.inline, magic: "Destroy".} =
   ## Generic `destructor`:idx: implementation that can be overridden.
   discard
+
+when defined(nimHasDup):
+  proc `=dup`*[T](x: ref T): ref T {.inline, magic: "Dup".} =
+    ## Generic `dup` implementation that can be overridden.
+    discard
+
 proc `=sink`*[T](x: var T; y: T) {.inline, nodestroy, magic: "Asgn".} =
   ## Generic `sink`:idx: implementation that can be overridden.
   when defined(gcArc) or defined(gcOrc):
diff --git a/lib/system/arc.nim b/lib/system/arc.nim
index d8527e1e4..55c4c412a 100644
--- a/lib/system/arc.nim
+++ b/lib/system/arc.nim
@@ -185,6 +185,10 @@ 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/tests/arc/tdup.nim b/tests/arc/tdup.nim
new file mode 100644
index 000000000..3f64061fb
--- /dev/null
+++ b/tests/arc/tdup.nim
@@ -0,0 +1,70 @@
+discard """
+  cmd: "nim c --mm:arc --expandArc:foo --hints:off $file"
+  nimout: '''
+--expandArc: foo
+
+var
+  x
+  :tmpD
+  s
+  :tmpD_1
+x = Ref(id: 8)
+inc:
+  :tmpD = `=dup`(x)
+  :tmpD
+inc:
+  let blitTmp = x
+  blitTmp
+var id_1 = 777
+s = RefCustom(id_2: addr(id_1))
+inc_1 :
+  :tmpD_1 = `=dup`(s)
+  :tmpD_1
+inc_1 :
+  let blitTmp_1 = s
+  blitTmp_1
+-- end of expandArc ------------------------
+'''
+"""
+
+type
+  Ref = ref object
+    id: int
+
+  RefCustom = object
+    id: ptr int
+
+proc inc(x: sink Ref) =
+  inc x.id
+
+proc inc(x: sink RefCustom) =
+  inc x.id[]
+
+proc `=dup`(x: var RefCustom): RefCustom =
+  result.id = x.id
+
+proc foo =
+  var x = Ref(id: 8)
+  inc(x)
+  inc(x)
+  var id = 777
+  var s = RefCustom(id: addr id)
+  inc s
+  inc s
+
+foo()
+
+proc foo2 =
+  var x = Ref(id: 8)
+  inc(x)
+  doAssert x.id == 9
+  inc(x)
+  doAssert x.id == 10
+  var id = 777
+  var s = RefCustom(id: addr id)
+  inc s
+  doAssert s.id[] == 778
+  inc s
+  doAssert s.id[] == 779
+
+foo2()
diff --git a/tests/arc/topt_no_cursor.nim b/tests/arc/topt_no_cursor.nim
index 26dc25447..32652b60a 100644
--- a/tests/arc/topt_no_cursor.nim
+++ b/tests/arc/topt_no_cursor.nim
@@ -113,8 +113,7 @@ block :tmp:
       var :tmpD
       sym = shadowScope.symbols[i]
       addInterfaceDecl(c):
-        `=wasMoved`(:tmpD)
-        `=copy_1`(:tmpD, sym)
+        :tmpD = `=dup`(sym)
         :tmpD
       inc(i, 1)
 `=destroy`(shadowScope)