summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorAndreas Rumpf <rumpf_a@web.de>2019-12-17 17:37:50 +0100
committerGitHub <noreply@github.com>2019-12-17 17:37:50 +0100
commit83a736a34a1ebd4bc4d769429880ccb871403ba4 (patch)
tree1a45de64686622fe9932daafb5345fdd066cab48
parent5848f0042c2d6a6dd39d9b8db747f36200c9f543 (diff)
downloadNim-83a736a34a1ebd4bc4d769429880ccb871403ba4.tar.gz
ARC: cycle detector (#12823)
* first implementation of the =trace and =dispose hooks for the cycle collector
* a cycle collector for ARC: progress
* manual: the .acyclic pragma is a thing once again
* gcbench: adaptations for --gc:arc
* enable valgrind tests for the strutils tests
* testament: better valgrind support
* ARC refactoring: growable jumpstacks
* ARC cycle detector: non-recursive algorithm
* moved and renamed core/ files back to system/
* refactoring: --gc:arc vs --gc:orc since 'orc' is even more experimental and we want to ship --gc:arc soonish
-rw-r--r--compiler/ast.nim10
-rw-r--r--compiler/ccgexprs.nim42
-rw-r--r--compiler/ccgtypes.nim126
-rw-r--r--compiler/commands.nim15
-rw-r--r--compiler/injectdestructors.nim6
-rw-r--r--compiler/lambdalifting.nim1
-rw-r--r--compiler/liftdestructors.nim158
-rw-r--r--compiler/options.nim2
-rw-r--r--compiler/pragmas.nim2
-rw-r--r--compiler/rodimpl.nim1
-rw-r--r--compiler/semexprs.nim2
-rw-r--r--compiler/sempass2.nim3
-rw-r--r--compiler/semstmts.nim80
-rw-r--r--compiler/types.nim2
-rw-r--r--doc/manual.rst28
-rw-r--r--doc/nimc.rst8
-rw-r--r--lib/system.nim8
-rw-r--r--lib/system/allocators.nim (renamed from lib/core/allocators.nim)0
-rw-r--r--lib/system/cyclicrefs_v2.nim232
-rw-r--r--lib/system/mmdisp.nim3
-rw-r--r--lib/system/refs_v2.nim (renamed from lib/core/runtime_v2.nim)65
-rw-r--r--lib/system/seqs_v2.nim (renamed from lib/core/seqs.nim)0
-rw-r--r--lib/system/strs_v2.nim (renamed from lib/core/strs.nim)0
-rw-r--r--lib/system/widestrs.nim2
-rw-r--r--testament/categories.nim4
-rw-r--r--testament/specs.nim5
-rw-r--r--testament/testament.nim2
-rw-r--r--tests/destructor/tarc2.nim7
-rw-r--r--tests/destructor/tarctypesections.nim70
-rw-r--r--tests/destructor/tasync_prototype_cyclic.nim54
-rw-r--r--tests/destructor/tbintree2.nim2
-rw-r--r--tests/destructor/tcycle1.nim53
-rw-r--r--tests/destructor/tcycle2.nim18
-rw-r--r--tests/destructor/tcycle3.nim64
-rw-r--r--tests/destructor/tgcdestructors.nim2
-rw-r--r--tests/destructor/tnewruntime_misc.nim4
-rw-r--r--tests/destructor/tnewruntime_strutils.nim5
-rw-r--r--tests/destructor/tselect.nim26
-rw-r--r--tests/destructor/tsimpleclosure.nim2
-rw-r--r--tests/destructor/tuse_ownedref_after_move.nim2
-rw-r--r--tests/destructor/tv2_raise.nim2
-rw-r--r--tests/destructor/twidgets.nim2
-rw-r--r--tests/destructor/twidgets_unown.nim2
-rw-r--r--tests/gc/gcbench.nim10
-rw-r--r--tests/gc/thavlak.nim36
45 files changed, 939 insertions, 229 deletions
diff --git a/compiler/ast.nim b/compiler/ast.nim
index 208e99c4a..a4868a9e6 100644
--- a/compiler/ast.nim
+++ b/compiler/ast.nim
@@ -478,7 +478,7 @@ type
     nfExecuteOnReload  # A top-level statement that will be executed during reloads
 
   TNodeFlags* = set[TNodeFlag]
-  TTypeFlag* = enum   # keep below 32 for efficiency reasons (now: ~38)
+  TTypeFlag* = enum   # keep below 32 for efficiency reasons (now: ~39)
     tfVarargs,        # procedure has C styled varargs
                       # tyArray type represeting a varargs list
     tfNoSideEffect,   # procedure type does not allow side effects
@@ -537,6 +537,7 @@ type
     tfContravariant   # contravariant generic param
     tfCheckedForDestructor # type was checked for having a destructor.
                            # If it has one, t.destructor is not nil.
+    tfAcyclic # object type was annotated as .acyclic
 
   TTypeFlags* = set[TTypeFlag]
 
@@ -635,7 +636,7 @@ type
     mSwap, mIsNil, mArrToSeq, mCopyStr, mCopyStrLast,
     mNewString, mNewStringOfCap, mParseBiggestFloat,
     mMove, mWasMoved, mDestroy,
-    mDefault, mUnown, mAccessEnv, mReset,
+    mDefault, mUnown, mAccessEnv, mAccessTypeInfo, mReset,
     mArray, mOpenArray, mRange, mSet, mSeq, mOpt, mVarargs,
     mRef, mPtr, mVar, mDistinct, mVoid, mTuple,
     mOrdinal,
@@ -870,6 +871,8 @@ type
     attachedDestructor,
     attachedAsgn,
     attachedSink,
+    attachedTrace,
+    attachedDispose,
     attachedDeepCopy
 
   TType* {.acyclic.} = object of TIdObj # \
@@ -1298,7 +1301,8 @@ const
   UnspecifiedLockLevel* = TLockLevel(-1'i16)
   MaxLockLevel* = 1000'i16
   UnknownLockLevel* = TLockLevel(1001'i16)
-  AttachedOpToStr*: array[TTypeAttachedOp, string] = ["=destroy", "=", "=sink", "=deepcopy"]
+  AttachedOpToStr*: array[TTypeAttachedOp, string] = [
+    "=destroy", "=", "=sink", "=trace", "=dispose", "=deepcopy"]
 
 proc `$`*(x: TLockLevel): string =
   if x.ord == UnspecifiedLockLevel.ord: result = "<unspecified>"
diff --git a/compiler/ccgexprs.nim b/compiler/ccgexprs.nim
index 9acf8c079..5fa7ac8d7 100644
--- a/compiler/ccgexprs.nim
+++ b/compiler/ccgexprs.nim
@@ -1210,7 +1210,7 @@ proc rawGenNew(p: BProc, a: TLoc, sizeExpr: Rope) =
     genAssignment(p, a, b, {})
   else:
     let ti = genTypeInfo(p.module, typ, a.lode.info)
-    if bt.destructor != nil and not trivialDestructor(bt.destructor):
+    if bt.destructor != nil and not isTrivialProc(bt.destructor):
       # the prototype of a destructor is ``=destroy(x: var T)`` and that of a
       # finalizer is: ``proc (x: ref T) {.nimcall.}``. We need to check the calling
       # convention at least:
@@ -1584,6 +1584,7 @@ proc genRepr(p: BProc, e: PNode, d: var TLoc) =
   gcUsage(p.config, e)
 
 proc genGetTypeInfo(p: BProc, e: PNode, d: var TLoc) =
+  discard cgsym(p.module, "TNimType")
   let t = e[1].typ
   putIntoDest(p, d, e, genTypeInfo(p.module, t, e.info))
 
@@ -2077,6 +2078,21 @@ proc genEnumToStr(p: BProc, e: PNode, d: var TLoc) =
   n[0] = newSymNode(toStrProc)
   expr(p, n, d)
 
+proc rdMType(p: BProc; a: TLoc; nilCheck: var Rope): Rope =
+  result = rdLoc(a)
+  var t = skipTypes(a.t, abstractInst)
+  while t.kind in {tyVar, tyLent, tyPtr, tyRef}:
+    if t.kind notin {tyVar, tyLent}: nilCheck = result
+    if t.kind notin {tyVar, tyLent} or not p.module.compileToCpp:
+      result = "(*$1)" % [result]
+    t = skipTypes(t.lastSon, abstractInst)
+  discard getTypeDesc(p.module, t)
+  if not p.module.compileToCpp:
+    while t.kind == tyObject and t[0] != nil:
+      result.add(".Sup")
+      t = skipTypes(t[0], skipPtrs)
+  result.add ".m_type"
+
 proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
   case op
   of mOr, mAnd: genAndOr(p, e, d, op)
@@ -2260,6 +2276,11 @@ proc genMagicExpr(p: BProc, e: PNode, d: var TLoc, op: TMagic) =
   of mMove: genMove(p, e, d)
   of mDestroy: genDestroy(p, e)
   of mAccessEnv: unaryExpr(p, e, d, "$1.ClE_0")
+  of mAccessTypeInfo:
+    var a: TLoc
+    var dummy: Rope
+    initLocExpr(p, e[1], a)
+    putIntoDest(p, d, e, rdMType(p, a, dummy))
   of mSlice:
     localError(p.config, e.info, "invalid context for 'toOpenArray'; " &
       "'toOpenArray' is only valid within a call expression")
@@ -2407,28 +2428,17 @@ proc upConv(p: BProc, n: PNode, d: var TLoc) =
   initLocExpr(p, n[0], a)
   let dest = skipTypes(n.typ, abstractPtrs)
   if optObjCheck in p.options and not isObjLackingTypeField(dest):
-    var r = rdLoc(a)
-    var nilCheck: Rope = nil
-    var t = skipTypes(a.t, abstractInst)
-    while t.kind in {tyVar, tyLent, tyPtr, tyRef}:
-      if t.kind notin {tyVar, tyLent}: nilCheck = r
-      if t.kind notin {tyVar, tyLent} or not p.module.compileToCpp:
-        r = "(*$1)" % [r]
-      t = skipTypes(t.lastSon, abstractInst)
-    discard getTypeDesc(p.module, t)
-    if not p.module.compileToCpp:
-      while t.kind == tyObject and t[0] != nil:
-        r.add(".Sup")
-        t = skipTypes(t[0], skipPtrs)
+    var nilCheck = Rope(nil)
+    let r = rdMType(p, a, nilCheck)
     let checkFor = if optTinyRtti in p.config.globalOptions:
                      genTypeInfo2Name(p.module, dest)
                    else:
                      genTypeInfo(p.module, dest, n.info)
     if nilCheck != nil:
-      linefmt(p, cpsStmts, "if ($1) #chckObj($2.m_type, $3);$n",
+      linefmt(p, cpsStmts, "if ($1) #chckObj($2, $3);$n",
               [nilCheck, r, checkFor])
     else:
-      linefmt(p, cpsStmts, "#chckObj($1.m_type, $2);$n",
+      linefmt(p, cpsStmts, "#chckObj($1, $2);$n",
               [r, checkFor])
   if n[0].typ.kind != tyObject:
     putIntoDest(p, d, n,
diff --git a/compiler/ccgtypes.nim b/compiler/ccgtypes.nim
index d490bd538..9c3e45697 100644
--- a/compiler/ccgtypes.nim
+++ b/compiler/ccgtypes.nim
@@ -1273,30 +1273,44 @@ proc genTypeInfo2Name(m: BModule; t: PType): Rope =
     it = it[0]
   result = makeCString(res)
 
-proc trivialDestructor(s: PSym): bool {.inline.} = s.ast[bodyPos].len == 0
+proc isTrivialProc(s: PSym): bool {.inline.} = s.ast[bodyPos].len == 0
 
-proc genObjectInfoV2(m: BModule, t, origType: PType, name: Rope; info: TLineInfo) =
-  assert t.kind == tyObject
-  if incompleteType(t):
-    localError(m.config, info, "request for RTTI generation for incomplete object: " &
-                      typeToString(t))
-
-  var d: Rope
-  if t.destructor != nil and not trivialDestructor(t.destructor):
+proc genHook(m: BModule; t: PType; info: TLineInfo; op: TTypeAttachedOp): Rope =
+  let theProc = t.attachedOps[op]
+  if theProc != nil and not isTrivialProc(theProc):
     # the prototype of a destructor is ``=destroy(x: var T)`` and that of a
     # finalizer is: ``proc (x: ref T) {.nimcall.}``. We need to check the calling
     # convention at least:
-    if t.destructor.typ == nil or t.destructor.typ.callConv != ccDefault:
+    if theProc.typ == nil or theProc.typ.callConv != ccDefault:
       localError(m.config, info,
-        "the destructor that is turned into a finalizer needs " &
-        "to have the 'nimcall' calling convention")
-    genProc(m, t.destructor)
-    d = t.destructor.loc.r
+        theProc.name.s & " needs to have the 'nimcall' calling convention")
+
+    genProc(m, theProc)
+    result = theProc.loc.r
+  else:
+    result = rope("NIM_NIL")
+
+proc genTypeInfoV2(m: BModule, t, origType: PType, name: Rope; info: TLineInfo) =
+  var typeName: Rope
+  if t.kind == tyObject:
+    if incompleteType(t):
+      localError(m.config, info, "request for RTTI generation for incomplete object: " &
+                 typeToString(t))
+    typeName = genTypeInfo2Name(m, t)
   else:
-    d = rope("NIM_NIL")
+    typeName = rope("NIM_NIL")
+
   m.s[cfsData].addf("TNimType $1;$n", [name])
-  m.s[cfsTypeInit3].addf("$1.destructor = (void*)$2; $1.size = sizeof($3); $1.name = $4;$n", [
-    name, d, getTypeDesc(m, t), genTypeInfo2Name(m, t)])
+  let destroyImpl = genHook(m, t, info, attachedDestructor)
+  let traceImpl = genHook(m, t, info, attachedTrace)
+  let disposeImpl = genHook(m, t, info, attachedDispose)
+
+  m.s[cfsTypeInit3].addf("$1.destructor = (void*)$2; $1.size = sizeof($3);$n" &
+    "$1.name = $4;$n" &
+    "$1.traceImpl = (void*)$5;$n" &
+    "$1.disposeImpl = (void*)$6;$n", [
+    name, destroyImpl, getTypeDesc(m, t), typeName,
+    traceImpl, disposeImpl])
 
 proc genTypeInfo(m: BModule, t: PType; info: TLineInfo): Rope =
   let origType = t
@@ -1333,49 +1347,51 @@ proc genTypeInfo(m: BModule, t: PType; info: TLineInfo): Rope =
     return prefixTI.rope & result & ")".rope
 
   m.g.typeInfoMarker[sig] = (str: result, owner: owner)
-  case t.kind
-  of tyEmpty, tyVoid: result = rope"0"
-  of tyPointer, tyBool, tyChar, tyCString, tyString, tyInt..tyUInt64, tyVar, tyLent:
-    genTypeInfoAuxBase(m, t, t, result, rope"0", info)
-  of tyStatic:
-    if t.n != nil: result = genTypeInfo(m, lastSon t, info)
-    else: internalError(m.config, "genTypeInfo(" & $t.kind & ')')
-  of tyUserTypeClasses:
-    internalAssert m.config, t.isResolvedUserTypeClass
-    return genTypeInfo(m, t.lastSon, info)
-  of tyProc:
-    if t.callConv != ccClosure:
+
+  if optTinyRtti in m.config.globalOptions:
+    genTypeInfoV2(m, t, origType, result, info)
+  else:
+    case t.kind
+    of tyEmpty, tyVoid: result = rope"0"
+    of tyPointer, tyBool, tyChar, tyCString, tyString, tyInt..tyUInt64, tyVar, tyLent:
       genTypeInfoAuxBase(m, t, t, result, rope"0", info)
-    else:
-      let x = fakeClosureType(m, t.owner)
-      genTupleInfo(m, x, x, result, info)
-  of tySequence:
-    genTypeInfoAux(m, t, t, result, info)
-    if optSeqDestructors notin m.config.globalOptions:
+    of tyStatic:
+      if t.n != nil: result = genTypeInfo(m, lastSon t, info)
+      else: internalError(m.config, "genTypeInfo(" & $t.kind & ')')
+    of tyUserTypeClasses:
+      internalAssert m.config, t.isResolvedUserTypeClass
+      return genTypeInfo(m, t.lastSon, info)
+    of tyProc:
+      if t.callConv != ccClosure:
+        genTypeInfoAuxBase(m, t, t, result, rope"0", info)
+      else:
+        let x = fakeClosureType(m, t.owner)
+        genTupleInfo(m, x, x, result, info)
+    of tySequence:
+      genTypeInfoAux(m, t, t, result, info)
+      if optSeqDestructors notin m.config.globalOptions:
+        if m.config.selectedGC >= gcMarkAndSweep:
+          let markerProc = genTraverseProc(m, origType, sig)
+          m.s[cfsTypeInit3].addf("$1.marker = $2;$n", [tiNameForHcr(m, result), markerProc])
+    of tyRef:
+      genTypeInfoAux(m, t, t, result, info)
       if m.config.selectedGC >= gcMarkAndSweep:
         let markerProc = genTraverseProc(m, origType, sig)
         m.s[cfsTypeInit3].addf("$1.marker = $2;$n", [tiNameForHcr(m, result), markerProc])
-  of tyRef:
-    genTypeInfoAux(m, t, t, result, info)
-    if m.config.selectedGC >= gcMarkAndSweep:
-      let markerProc = genTraverseProc(m, origType, sig)
-      m.s[cfsTypeInit3].addf("$1.marker = $2;$n", [tiNameForHcr(m, result), markerProc])
-  of tyPtr, tyRange, tyUncheckedArray: genTypeInfoAux(m, t, t, result, info)
-  of tyArray: genArrayInfo(m, t, result, info)
-  of tySet: genSetInfo(m, t, result, info)
-  of tyEnum: genEnumInfo(m, t, result, info)
-  of tyObject:
-    if optTinyRtti in m.config.globalOptions:
-      genObjectInfoV2(m, t, origType, result, info)
-    else:
+    of tyPtr, tyRange, tyUncheckedArray: genTypeInfoAux(m, t, t, result, info)
+    of tyArray: genArrayInfo(m, t, result, info)
+    of tySet: genSetInfo(m, t, result, info)
+    of tyEnum: genEnumInfo(m, t, result, info)
+    of tyObject:
       genObjectInfo(m, t, origType, result, info)
-  of tyTuple:
-    # if t.n != nil: genObjectInfo(m, t, result)
-    # else:
-    # BUGFIX: use consistently RTTI without proper field names; otherwise
-    # results are not deterministic!
-    genTupleInfo(m, t, origType, result, info)
-  else: internalError(m.config, "genTypeInfo(" & $t.kind & ')')
+    of tyTuple:
+      # if t.n != nil: genObjectInfo(m, t, result)
+      # else:
+      # BUGFIX: use consistently RTTI without proper field names; otherwise
+      # results are not deterministic!
+      genTupleInfo(m, t, origType, result, info)
+    else: internalError(m.config, "genTypeInfo(" & $t.kind & ')')
+
   if t.attachedOps[attachedDeepCopy] != nil:
     genDeepCopyProc(m, t.attachedOps[attachedDeepCopy], result)
   elif origType.attachedOps[attachedDeepCopy] != nil:
diff --git a/compiler/commands.nim b/compiler/commands.nim
index 63b350521..fa94d36e3 100644
--- a/compiler/commands.nim
+++ b/compiler/commands.nim
@@ -229,7 +229,8 @@ proc testCompileOptionArg*(conf: ConfigRef; switch, arg: string, info: TLineInfo
     of "v2": result = false
     of "markandsweep": result = conf.selectedGC == gcMarkAndSweep
     of "generational": result = false
-    of "destructors", "arc": result = conf.selectedGC == gcDestructors
+    of "destructors", "arc": result = conf.selectedGC == gcArc
+    of "orc": result = conf.selectedGC == gcOrc
     of "hooks": result = conf.selectedGC == gcHooks
     of "go": result = conf.selectedGC == gcGo
     of "none": result = conf.selectedGC == gcNone
@@ -455,8 +456,18 @@ proc processSwitch*(switch, arg: string, pass: TCmdLinePass, info: TLineInfo;
         conf.selectedGC = gcMarkAndSweep
         defineSymbol(conf.symbols, "gcmarkandsweep")
       of "destructors", "arc":
-        conf.selectedGC = gcDestructors
+        conf.selectedGC = gcArc
         defineSymbol(conf.symbols, "gcdestructors")
+        defineSymbol(conf.symbols, "gcarc")
+        incl conf.globalOptions, optSeqDestructors
+        incl conf.globalOptions, optTinyRtti
+        if pass in {passCmd2, passPP}:
+          defineSymbol(conf.symbols, "nimSeqsV2")
+          defineSymbol(conf.symbols, "nimV2")
+      of "orc":
+        conf.selectedGC = gcOrc
+        defineSymbol(conf.symbols, "gcdestructors")
+        defineSymbol(conf.symbols, "gcorc")
         incl conf.globalOptions, optSeqDestructors
         incl conf.globalOptions, optTinyRtti
         if pass in {passCmd2, passPP}:
diff --git a/compiler/injectdestructors.nim b/compiler/injectdestructors.nim
index 18fb0f5c8..0acf7fac2 100644
--- a/compiler/injectdestructors.nim
+++ b/compiler/injectdestructors.nim
@@ -218,7 +218,7 @@ proc genCopyNoCheck(c: Con; dest, ri: PNode): PNode =
 
 proc genCopy(c: var Con; dest, ri: PNode): PNode =
   let t = dest.typ
-  if tfHasOwned in t.flags:
+  if tfHasOwned in t.flags and ri.kind != nkNilLit:
     # try to improve the error message here:
     if c.otherRead == nil: discard isLastRead(ri, c)
     checkForErrorPragma(c, t, ri, "=")
@@ -409,7 +409,7 @@ proc isCursor(n: PNode): bool =
     result = false
 
 proc cycleCheck(n: PNode; c: var Con) =
-  if c.graph.config.selectedGC != gcDestructors: return
+  if c.graph.config.selectedGC != gcArc: return
   var value = n[1]
   if value.kind == nkClosure:
     value = value[1]
@@ -512,7 +512,7 @@ proc p(n: PNode; c: var Con; mode: ProcessMode): PNode =
           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}:
+        if c.graph.config.selectedGC in {gcHooks, gcArc, gcOrc}:
           let destroyOld = genDestroy(c, result[1])
           result = newTree(nkStmtList, destroyOld, result)
       else:
diff --git a/compiler/lambdalifting.nim b/compiler/lambdalifting.nim
index ea19097b8..1921a1e18 100644
--- a/compiler/lambdalifting.nim
+++ b/compiler/lambdalifting.nim
@@ -353,6 +353,7 @@ proc createUpField(c: var DetectionPass; dest, dep: PSym; info: TLineInfo) =
   # with cycles properly, so it's better to produce a weak ref (=ptr) here.
   # This seems to be generally correct but since it's a bit risky it's disabled
   # for now.
+  # XXX This is wrong for the 'hamming' test, so remove this logic again.
   let fieldType = if isDefined(c.graph.config, "nimCycleBreaker"):
                     c.getEnvTypeForOwnerUp(dep, info) #getHiddenParam(dep).typ
                   else:
diff --git a/compiler/liftdestructors.nim b/compiler/liftdestructors.nim
index a906ac181..38f9ee417 100644
--- a/compiler/liftdestructors.nim
+++ b/compiler/liftdestructors.nim
@@ -41,7 +41,8 @@ proc at(a, i: PNode, elemType: PType): PNode =
 proc fillBodyTup(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   for i in 0..<t.len:
     let lit = lowerings.newIntLit(c.g, x.info, i)
-    fillBody(c, t[i], body, x.at(lit, t[i]), y.at(lit, t[i]))
+    let b = if c.kind == attachedTrace: y else: y.at(lit, t[i])
+    fillBody(c, t[i], body, x.at(lit, t[i]), b)
 
 proc dotField(x: PNode, f: PSym): PNode =
   result = newNodeI(nkDotExpr, x.info, 2)
@@ -58,18 +59,19 @@ proc newAsgnStmt(le, ri: PNode): PNode =
   result[1] = ri
 
 proc defaultOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
-  if c.kind != attachedDestructor:
+  if c.kind in {attachedAsgn, attachedDeepCopy, attachedSink}:
     body.add newAsgnStmt(x, y)
 
 proc fillBodyObj(c: var TLiftCtx; n, body, x, y: PNode) =
   case n.kind
   of nkSym:
     let f = n.sym
+    let b = if c.kind == attachedTrace: y else: y.dotField(f)
     if sfCursor in f.flags and f.typ.skipTypes(abstractInst).kind in {tyRef, tyProc} and
-        c.g.config.selectedGC in {gcDestructors, gcHooks}:
-      defaultOp(c, f.typ, body, x.dotField(f), y.dotField(f))
+        c.g.config.selectedGC in {gcArc, gcOrc, gcHooks}:
+      defaultOp(c, f.typ, body, x.dotField(f), b)
     else:
-      fillBody(c, f.typ, body, x.dotField(f), y.dotField(f))
+      fillBody(c, f.typ, body, x.dotField(f), b)
   of nkNilLit: discard
   of nkRecCase:
     if c.kind in {attachedSink, attachedAsgn, attachedDeepCopy}:
@@ -117,13 +119,17 @@ proc genAddr(g: ModuleGraph; x: PNode): PNode =
     result = newNodeIT(nkHiddenAddr, x.info, makeVarType(x.typ.owner, x.typ))
     result.add x
 
-proc newAsgnCall(g: ModuleGraph; op: PSym; x, y: PNode): PNode =
+proc newHookCall(g: ModuleGraph; op: PSym; x, y: PNode): PNode =
   #if sfError in op.flags:
   #  localError(c.config, x.info, "usage of '$1' is a user-defined error" % op.name.s)
   result = newNodeI(nkCall, x.info)
   result.add newSymNode(op)
-  result.add genAddr(g, x)
-  result.add y
+  if op.typ.sons[1].kind == tyVar:
+    result.add genAddr(g, x)
+  else:
+    result.add x
+  if y != nil:
+    result.add y
 
 proc newOpCall(op: PSym; x: PNode): PNode =
   result = newNodeIT(nkCall, x.info, op.typ[0])
@@ -142,6 +148,10 @@ proc useNoGc(c: TLiftCtx; t: PType): bool {.inline.} =
   result = optSeqDestructors in c.g.config.globalOptions and
     ({tfHasGCedMem, tfHasOwned} * t.flags != {} or t.isGCedMem)
 
+proc requiresDestructor(c: TLiftCtx; t: PType): bool {.inline.} =
+  result = optSeqDestructors in c.g.config.globalOptions and
+    containsGarbageCollectedRef(t)
+
 proc instantiateGeneric(c: var TLiftCtx; op: PSym; t, typeInst: PType): PSym =
   if c.c != nil and typeInst != nil:
     result = c.c.instTypeBoundOp(c.c, op, typeInst, c.info, attachedAsgn, 1)
@@ -160,7 +170,7 @@ proc considerAsgnOrSink(c: var TLiftCtx; t: PType; body, x, y: PNode;
       #else:
       #  markUsed(c.g.config, c.info, op, c.g.usageSym)
       onUse(c.info, op)
-      body.add newAsgnCall(c.g, op, x, y)
+      body.add newHookCall(c.g, op, x, y)
       result = true
   elif tfHasAsgn in t.flags:
     var op: PSym
@@ -189,7 +199,7 @@ proc considerAsgnOrSink(c: var TLiftCtx; t: PType; body, x, y: PNode;
       #debug(t)
       #return false
     assert op.ast[genericParamsPos].kind == nkEmpty
-    body.add newAsgnCall(c.g, op, x, y)
+    body.add newHookCall(c.g, op, x, y)
     result = true
 
 proc addDestructorCall(c: var TLiftCtx; orig: PType; body, x: PNode) =
@@ -202,7 +212,7 @@ proc addDestructorCall(c: var TLiftCtx; orig: PType; body, x: PNode) =
       op = instantiateGeneric(c, op, t, t.typeInst)
       t.attachedOps[attachedDestructor] = op
 
-  if op == nil and useNoGc(c, t):
+  if op == nil and (useNoGc(c, t) or requiresDestructor(c, t)):
     op = produceSym(c.g, c.c, t, attachedDestructor, c.info)
     doAssert op != nil
     doAssert op == t.destructor
@@ -231,10 +241,10 @@ proc considerUserDefinedOp(c: var TLiftCtx; t: PType; body, x, y: PNode): bool =
       body.add destructorCall(c.g, op, x)
       result = true
     #result = addDestructorCall(c, t, body, x)
-  of attachedAsgn:
-    result = considerAsgnOrSink(c, t, body, x, y, t.assignment)
-  of attachedSink:
-    result = considerAsgnOrSink(c, t, body, x, y, t.asink)
+  of attachedAsgn, attachedSink, attachedTrace:
+    result = considerAsgnOrSink(c, t, body, x, y, t.attachedOps[c.kind])
+  of attachedDispose:
+    result = considerAsgnOrSink(c, t, body, x, nil, t.attachedOps[c.kind])
   of attachedDeepCopy:
     let op = t.attachedOps[attachedDeepCopy]
     if op != nil:
@@ -305,8 +315,8 @@ proc forallElements(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   let i = declareCounter(c, body, toInt64(firstOrd(c.g.config, t)))
   let whileLoop = genWhileLoop(c, i, x)
   let elemType = t.lastSon
-  fillBody(c, elemType, whileLoop[1], x.at(i, elemType),
-                                           y.at(i, elemType))
+  let b = if c.kind == attachedTrace: y else: y.at(i, elemType)
+  fillBody(c, elemType, whileLoop[1], x.at(i, elemType), b)
   addIncStmt(c, whileLoop[1], i)
   body.add whileLoop
 
@@ -330,6 +340,11 @@ proc fillSeqOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
     # destroy all elements:
     forallElements(c, t, body, x, y)
     body.add genBuiltin(c.g, mDestroy, "destroy", x)
+  of attachedTrace:
+    # follow all elements:
+    forallElements(c, t, body, x, y)
+  of attachedDispose:
+    body.add genBuiltin(c.g, mDestroy, "destroy", x)
 
 proc useSeqOrStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   createTypeBoundOps(c.g, c.c, t, body.info)
@@ -344,7 +359,7 @@ proc useSeqOrStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   case c.kind
   of attachedAsgn, attachedDeepCopy:
     doAssert t.assignment != nil
-    body.add newAsgnCall(c.g, t.assignment, x, y)
+    body.add newHookCall(c.g, t.assignment, x, y)
   of attachedSink:
     # we always inline the move for better performance:
     let moveCall = genBuiltin(c.g, mMove, "move", x)
@@ -355,10 +370,14 @@ proc useSeqOrStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
     # alternatively we could do this:
     when false:
       doAssert t.asink != nil
-      body.add newAsgnCall(c.g, t.asink, x, y)
+      body.add newHookCall(c.g, t.asink, x, y)
   of attachedDestructor:
     doAssert t.destructor != nil
     body.add destructorCall(c.g, t.destructor, x)
+  of attachedTrace:
+    body.add newHookCall(c.g, t.attachedOps[c.kind], x, y)
+  of attachedDispose:
+    body.add newHookCall(c.g, t.attachedOps[c.kind], x, nil)
 
 proc fillStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   case c.kind
@@ -370,8 +389,10 @@ proc fillStrOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
     doAssert t.destructor != nil
     moveCall.add destructorCall(c.g, t.destructor, x)
     body.add moveCall
-  of attachedDestructor:
+  of attachedDestructor, attachedDispose:
     body.add genBuiltin(c.g, mDestroy, "destroy", x)
+  of attachedTrace:
+    discard "strings are atomic and have no inner elements that are to trace"
 
 proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   var actions = newNodeI(nkStmtList, c.info)
@@ -384,7 +405,18 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
     addDestructorCall(c, elemType, newNodeI(nkStmtList, c.info), genDeref(x, nkDerefExpr))
     actions.add callCodegenProc(c.g, "nimDestroyAndDispose", c.info, x)
 
-  let cond = callCodegenProc(c.g, "nimDecRefIsLast", c.info, x)
+  let isCyclic = c.g.config.selectedGC == gcOrc and types.canFormAcycle(t)
+
+  var cond: PNode
+  if isCyclic:
+    if isFinal(elemType):
+      let typInfo = genBuiltin(c.g, mGetTypeInfo, "getTypeInfo", newNodeIT(nkType, x.info, elemType))
+      typInfo.typ = getSysType(c.g, c.info, tyPointer)
+      cond = callCodegenProc(c.g, "nimDecRefIsLastCyclicStatic", c.info, x, typInfo)
+    else:
+      cond = callCodegenProc(c.g, "nimDecRefIsLastCyclicDyn", c.info, x)
+  else:
+    cond = callCodegenProc(c.g, "nimDecRefIsLast", c.info, x)
   cond.typ = getSysType(c.g, x.info, tyBool)
 
   case c.kind
@@ -392,7 +424,8 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
     body.add genIf(c, cond, actions)
     body.add newAsgnStmt(x, y)
   of attachedAsgn:
-    body.add genIf(c, y, callCodegenProc(c.g, "nimIncRef", c.info, y))
+    body.add genIf(c, y, callCodegenProc(c.g,
+        if isCyclic: "nimIncRefCyclic" else: "nimIncRef", c.info, y))
     body.add genIf(c, cond, actions)
     body.add newAsgnStmt(x, y)
   of attachedDestructor:
@@ -401,6 +434,22 @@ proc atomicRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
       actions.add newAsgnStmt(x, newNodeIT(nkNilLit, body.info, t))
     body.add genIf(c, cond, actions)
   of attachedDeepCopy: assert(false, "cannot happen")
+  of attachedTrace:
+    if isFinal(elemType):
+      let typInfo = genBuiltin(c.g, mGetTypeInfo, "getTypeInfo", newNodeIT(nkType, x.info, elemType))
+      typInfo.typ = getSysType(c.g, c.info, tyPointer)
+      body.add callCodegenProc(c.g, "nimTraceRef", c.info, x, typInfo, y)
+    else:
+      # If the ref is polymorphic we have to account for this
+      body.add callCodegenProc(c.g, "nimTraceRefDyn", c.info, x, y)
+  of attachedDispose:
+    # this is crucial! dispose is like =destroy but we don't follow refs
+    # as that is dealt within the cycle collector.
+    when false:
+      let cond = copyTree(x)
+      cond.typ = getSysType(c.g, x.info, tyBool)
+      actions.add callCodegenProc(c.g, "nimRawDispose", c.info, x)
+      body.add genIf(c, cond, actions)
 
 proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   ## Closures are really like refs except they always use a virtual destructor
@@ -411,7 +460,10 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   var actions = newNodeI(nkStmtList, c.info)
   actions.add callCodegenProc(c.g, "nimDestroyAndDispose", c.info, xenv)
 
-  let cond = callCodegenProc(c.g, "nimDecRefIsLast", c.info, xenv)
+  let decRefProc =
+    if c.g.config.selectedGC == gcOrc: "nimDecRefIsLastCyclicDyn"
+    else: "nimDecRefIsLast"
+  let cond = callCodegenProc(c.g, decRefProc, c.info, xenv)
   cond.typ = getSysType(c.g, x.info, tyBool)
 
   case c.kind
@@ -421,7 +473,10 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   of attachedAsgn:
     let yenv = genBuiltin(c.g, mAccessEnv, "accessEnv", y)
     yenv.typ = getSysType(c.g, c.info, tyPointer)
-    body.add genIf(c, yenv, callCodegenProc(c.g, "nimIncRef", c.info, yenv))
+    let incRefProc =
+      if c.g.config.selectedGC == gcOrc: "nimIncRefCyclic"
+      else: "nimIncRef"
+    body.add genIf(c, yenv, callCodegenProc(c.g, incRefProc, c.info, yenv))
     body.add genIf(c, cond, actions)
     body.add newAsgnStmt(x, y)
   of attachedDestructor:
@@ -430,6 +485,16 @@ proc atomicClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
       actions.add newAsgnStmt(xenv, newNodeIT(nkNilLit, body.info, xenv.typ))
     body.add genIf(c, cond, actions)
   of attachedDeepCopy: assert(false, "cannot happen")
+  of attachedTrace:
+    body.add callCodegenProc(c.g, "nimTraceRefDyn", c.info, xenv, y)
+  of attachedDispose:
+    # this is crucial! dispose is like =destroy but we don't follow refs
+    # as that is dealt within the cycle collector.
+    when false:
+      let cond = copyTree(xenv)
+      cond.typ = getSysType(c.g, xenv.info, tyBool)
+      actions.add callCodegenProc(c.g, "nimRawDispose", c.info, xenv)
+      body.add genIf(c, cond, actions)
 
 proc weakrefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   case c.kind
@@ -451,6 +516,7 @@ proc weakrefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
     else:
       body.sons.insert(des, 0)
   of attachedDeepCopy: assert(false, "cannot happen")
+  of attachedTrace, attachedDispose: discard
 
 proc ownedRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   var actions = newNodeI(nkStmtList, c.info)
@@ -473,6 +539,7 @@ proc ownedRefOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   of attachedDestructor:
     body.add genIf(c, x, actions)
   of attachedDeepCopy: assert(false, "cannot happen")
+  of attachedTrace, attachedDispose: discard
 
 proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   if c.kind == attachedDeepCopy:
@@ -484,7 +551,7 @@ proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
     call[1] = y
     body.add newAsgnStmt(x, call)
   elif (optOwnedRefs in c.g.config.globalOptions and
-      optRefCheck in c.g.config.options) or c.g.config.selectedGC == gcDestructors:
+      optRefCheck in c.g.config.options) or c.g.config.selectedGC in {gcArc, gcOrc}:
     let xx = genBuiltin(c.g, mAccessEnv, "accessEnv", x)
     xx.typ = getSysType(c.g, c.info, tyPointer)
     case c.kind
@@ -506,6 +573,7 @@ proc closureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
       else:
         body.sons.insert(des, 0)
     of attachedDeepCopy: assert(false, "cannot happen")
+    of attachedTrace, attachedDispose: discard
 
 proc ownedClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   let xx = genBuiltin(c.g, mAccessEnv, "accessEnv", x)
@@ -520,6 +588,7 @@ proc ownedClosureOp(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   of attachedDestructor:
     body.add genIf(c, xx, actions)
   of attachedDeepCopy: assert(false, "cannot happen")
+  of attachedTrace, attachedDispose: discard
 
 proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
   case t.kind
@@ -528,7 +597,7 @@ proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
       tyPtr, tyOpt, tyUncheckedArray, tyVar, tyLent:
     defaultOp(c, t, body, x, y)
   of tyRef:
-    if c.g.config.selectedGC == gcDestructors:
+    if c.g.config.selectedGC in {gcArc, gcOrc}:
       atomicRefOp(c, t, body, x, y)
     elif (optOwnedRefs in c.g.config.globalOptions and
         optRefCheck in c.g.config.options):
@@ -537,7 +606,7 @@ proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
       defaultOp(c, t, body, x, y)
   of tyProc:
     if t.callConv == ccClosure:
-      if c.g.config.selectedGC == gcDestructors:
+      if c.g.config.selectedGC in {gcArc, gcOrc}:
         atomicClosureOp(c, t, body, x, y)
       else:
         closureOp(c, t, body, x, y)
@@ -569,7 +638,7 @@ proc fillBody(c: var TLiftCtx; t: PType; body, x, y: PNode) =
       # 'selectedGC' here to determine if we have the new runtime.
       discard considerUserDefinedOp(c, t, body, x, y)
     elif tfHasAsgn in t.flags:
-      if c.kind != attachedDestructor:
+      if c.kind in {attachedAsgn, attachedSink, attachedDeepCopy}:
         body.add newSeqCall(c.g, x, y)
       forallElements(c, t, body, x, y)
     else:
@@ -632,30 +701,40 @@ proc produceSym(g: ModuleGraph; c: PContext; typ: PType; kind: TTypeAttachedOp;
   a.asgnForType = typ
 
   let dest = newSym(skParam, getIdent(g.cache, "dest"), result, info)
-  let src = newSym(skParam, getIdent(g.cache, "src"), result, info)
+  let src = newSym(skParam, getIdent(g.cache, if kind == attachedTrace: "env" else: "src"), result, info)
+  var d: PNode
+  #if kind notin {attachedTrace, attachedDispose}:
   dest.typ = makeVarType(typ.owner, typ)
-  src.typ = typ
+  d = newDeref(newSymNode(dest))
+  #else:
+  #  dest.typ = typ
+  #  d = newSymNode(dest)
+
+  if kind == attachedTrace:
+    src.typ = getSysType(g, info, tyPointer)
+  else:
+    src.typ = typ
 
   result.typ = newProcType(info, typ.owner)
   result.typ.addParam dest
-  if kind != attachedDestructor:
+  if kind notin {attachedDestructor, attachedDispose}:
     result.typ.addParam src
 
   # register this operation already:
   typ.attachedOps[kind] = result
 
   var tk: TTypeKind
-  if g.config.selectedGC in {gcDestructors, gcHooks}:
+  if g.config.selectedGC in {gcArc, gcOrc, gcHooks}:
     tk = skipTypes(typ, {tyOrdinal, tyRange, tyInferred, tyGenericInst, tyStatic, tyAlias, tySink}).kind
   else:
     tk = tyNone # no special casing for strings and seqs
   case tk
   of tySequence:
-    fillSeqOp(a, typ, body, newSymNode(dest).newDeref, newSymNode(src))
+    fillSeqOp(a, typ, body, d, newSymNode(src))
   of tyString:
-    fillStrOp(a, typ, body, newSymNode(dest).newDeref, newSymNode(src))
+    fillStrOp(a, typ, body, d, newSymNode(src))
   else:
-    fillBody(a, typ, body, newSymNode(dest).newDeref, newSymNode(src))
+    fillBody(a, typ, body, d, newSymNode(src))
 
   var n = newNodeI(nkProcDef, info, bodyPos+1)
   for i in 0..<n.len: n[i] = newNodeI(nkEmpty, info)
@@ -704,7 +783,7 @@ proc isTrival(s: PSym): bool {.inline.} =
 
 
 proc isEmptyContainer(g: ModuleGraph, t: PType): bool =
-  (t.kind == tyArray and lengthOrd(g.config, t[0]) == 0) or 
+  (t.kind == tyArray and lengthOrd(g.config, t[0]) == 0) or
     (t.kind == tySequence and t[0].kind == tyError)
 
 
@@ -731,8 +810,13 @@ proc createTypeBoundOps(g: ModuleGraph; c: PContext; orig: PType; info: TLineInf
   # 4. We have a custom destructor.
   # 5. We have a (custom) generic destructor.
 
+  # we do not generate '=trace' nor '=dispose' procs if we
+  # have the cycle detection disabled, saves code size.
+  let lastAttached = if g.config.selectedGC == gcOrc: attachedDispose
+                     else: attachedSink
+
   # we generate the destructor first so that other operators can depend on it:
-  for k in attachedDestructor..attachedSink:
+  for k in attachedDestructor..lastAttached:
     if canon.attachedOps[k] == nil:
       discard produceSym(g, c, canon, k, info)
     else:
diff --git a/compiler/options.nim b/compiler/options.nim
index c880534fd..ed3ee030d 100644
--- a/compiler/options.nim
+++ b/compiler/options.nim
@@ -116,7 +116,7 @@ type
     cmdJsonScript             # compile a .json build file
   TStringSeq* = seq[string]
   TGCMode* = enum             # the selected GC
-    gcUnselected, gcNone, gcBoehm, gcRegions, gcMarkAndSweep, gcDestructors,
+    gcUnselected, gcNone, gcBoehm, gcRegions, gcMarkAndSweep, gcArc, gcOrc,
     gcHooks,
     gcRefc, gcV2, gcGo
     # gcRefc and the GCs that follow it use a write barrier,
diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim
index a04eafee9..6596cfba6 100644
--- a/compiler/pragmas.nim
+++ b/compiler/pragmas.nim
@@ -940,7 +940,7 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
       of wAcyclic:
         noVal(c, it)
         if sym.typ == nil: invalidPragma(c, it)
-        # now: ignored
+        else: incl(sym.typ.flags, tfAcyclic)
       of wShallow:
         noVal(c, it)
         if sym.typ == nil: invalidPragma(c, it)
diff --git a/compiler/rodimpl.nim b/compiler/rodimpl.nim
index 50941535e..d79dd1deb 100644
--- a/compiler/rodimpl.nim
+++ b/compiler/rodimpl.nim
@@ -17,6 +17,7 @@ import strutils, intsets, tables, ropes, db_sqlite, msgs, options,
 ## - Dependency computation should use *signature* hashes in order to
 ##   avoid recompiling dependent modules.
 ## - Patch the rest of the compiler to do lazy loading of proc bodies.
+## - serialize the AST in a smarter way (avoid storing some ASTs twice!)
 
 template db(): DbConn = g.incr.db
 
diff --git a/compiler/semexprs.nim b/compiler/semexprs.nim
index c87ab21b1..ce0f10298 100644
--- a/compiler/semexprs.nim
+++ b/compiler/semexprs.nim
@@ -1601,7 +1601,7 @@ proc borrowCheck(c: PContext, n, le, ri: PNode) =
   proc scopedLifetime(c: PContext; ri: PNode): bool {.inline.} =
     let n = getRoot(ri, followDeref = false)
     result = (ri.kind in nkCallKinds+{nkObjConstr}) or
-      (n.kind == nkSym and n.sym.owner == c.p.owner)
+      (n.kind == nkSym and n.sym.owner == c.p.owner and n.sym.kind != skResult)
 
   proc escapes(c: PContext; le: PNode): bool {.inline.} =
     # param[].foo[] = self  definitely escapes, we don't need to
diff --git a/compiler/sempass2.nim b/compiler/sempass2.nim
index 4c4899391..1a4204ec6 100644
--- a/compiler/sempass2.nim
+++ b/compiler/sempass2.nim
@@ -872,6 +872,9 @@ proc track(tracked: PEffects, n: PNode) =
         createTypeBoundOps(tracked, x[1].typ, n.info)
     setLen(tracked.guards.s, oldFacts)
     if tracked.owner.kind != skMacro:
+      # XXX n.typ can be nil in runnableExamples, we need to do something about it.
+      if n.typ != nil and n.typ.skipTypes(abstractInst).kind == tyRef:
+        createTypeBoundOps(tracked, n.typ.lastSon, n.info)
       createTypeBoundOps(tracked, n.typ, n.info)
   of nkTupleConstr:
     for i in 0..<n.len:
diff --git a/compiler/semstmts.nim b/compiler/semstmts.nim
index e8a5ae038..cf1063fe6 100644
--- a/compiler/semstmts.nim
+++ b/compiler/semstmts.nim
@@ -1594,44 +1594,52 @@ proc canonType(c: PContext, t: PType): PType =
   else:
     result = t
 
-proc semOverride(c: PContext, s: PSym, n: PNode) =
-  proc prevDestructor(c: PContext; prevOp: PSym; obj: PType; info: TLineInfo) =
-    var msg = "cannot bind another '" & prevOp.name.s & "' to: " & typeToString(obj)
-    if sfOverriden notin prevOp.flags:
-      msg.add "; previous declaration was constructed here implicitly: " & (c.config $ prevOp.info)
-    else:
-      msg.add "; previous declaration was here: " & (c.config $ prevOp.info)
-    localError(c.config, n.info, errGenerated, msg)
+proc prevDestructor(c: PContext; prevOp: PSym; obj: PType; info: TLineInfo) =
+  var msg = "cannot bind another '" & prevOp.name.s & "' to: " & typeToString(obj)
+  if sfOverriden notin prevOp.flags:
+    msg.add "; previous declaration was constructed here implicitly: " & (c.config $ prevOp.info)
+  else:
+    msg.add "; previous declaration was here: " & (c.config $ prevOp.info)
+  localError(c.config, info, errGenerated, msg)
+
+proc bindTypeHook(c: PContext; s: PSym; n: PNode; op: TTypeAttachedOp) =
+  let t = s.typ
+  var noError = false
+  let cond = if op == attachedDestructor:
+               t.len == 2 and t[0] == nil and t[1].kind == tyVar
+             else:
+               t.len >= 2 and t[0] == nil
+
+  if cond:
+    var obj = t[1].skipTypes({tyVar})
+    while true:
+      incl(obj.flags, tfHasAsgn)
+      if obj.kind in {tyGenericBody, tyGenericInst}: obj = obj.lastSon
+      elif obj.kind == tyGenericInvocation: obj = obj[0]
+      else: break
+    if obj.kind in {tyObject, tyDistinct, tySequence, tyString}:
+      obj = canonType(c, obj)
+      if obj.attachedOps[op] == s:
+        discard "forward declared destructor"
+      elif obj.attachedOps[op].isNil and tfCheckedForDestructor notin obj.flags:
+        obj.attachedOps[op] = s
+      else:
+        prevDestructor(c, obj.attachedOps[op], obj, n.info)
+      noError = true
+      if obj.owner.getModule != s.getModule:
+        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:
+    localError(c.config, n.info, errGenerated,
+      "signature for '" & s.name.s & "' must be proc[T: object](x: var T)")
+  incl(s.flags, sfUsed)
+  incl(s.flags, sfOverriden)
 
+proc semOverride(c: PContext, s: PSym, n: PNode) =
   let name = s.name.s.normalize
   case name
   of "=destroy":
-    let t = s.typ
-    var noError = false
-    if t.len == 2 and t[0] == nil and t[1].kind == tyVar:
-      var obj = t[1][0]
-      while true:
-        incl(obj.flags, tfHasAsgn)
-        if obj.kind in {tyGenericBody, tyGenericInst}: obj = obj.lastSon
-        elif obj.kind == tyGenericInvocation: obj = obj[0]
-        else: break
-      if obj.kind in {tyObject, tyDistinct, tySequence, tyString}:
-        obj = canonType(c, obj)
-        if obj.attachedOps[attachedDestructor] == s:
-          discard "forward declared destructor"
-        elif obj.destructor.isNil and tfCheckedForDestructor notin obj.flags:
-          obj.attachedOps[attachedDestructor] = s
-        else:
-          prevDestructor(c, obj.destructor, obj, n.info)
-        noError = true
-        if obj.owner.getModule != s.getModule:
-          localError(c.config, n.info, errGenerated,
-            "type bound operation `=destroy` can be defined only in the same module with its type (" & obj.typeToString() & ")")
-    if not noError and sfSystemModule notin s.owner.flags:
-      localError(c.config, n.info, errGenerated,
-        "signature for '" & s.name.s & "' must be proc[T: object](x: var T)")
-    incl(s.flags, sfUsed)
-    incl(s.flags, sfOverriden)
+    bindTypeHook(c, s, n, attachedDestructor)
   of "deepcopy", "=deepcopy":
     if s.typ.len == 2 and
         s.typ[1].skipTypes(abstractInst).kind in {tyRef, tyPtr} and
@@ -1698,6 +1706,10 @@ proc semOverride(c: PContext, s: PSym, n: PNode) =
     if sfSystemModule notin s.owner.flags:
       localError(c.config, n.info, errGenerated,
                 "signature for '" & s.name.s & "' must be proc[T: object](x: var T; y: T)")
+  of "=trace":
+    bindTypeHook(c, s, n, attachedTrace)
+  of "=dispose":
+    bindTypeHook(c, s, n, attachedDispose)
   else:
     if sfOverriden in s.flags:
       localError(c.config, n.info, errGenerated,
diff --git a/compiler/types.nim b/compiler/types.nim
index db5a7d70e..3a3133247 100644
--- a/compiler/types.nim
+++ b/compiler/types.nim
@@ -351,7 +351,9 @@ proc canFormAcycleNode(marker: var IntSet, n: PNode, startId: int): bool =
 proc canFormAcycleAux(marker: var IntSet, typ: PType, startId: int): bool =
   result = false
   if typ == nil: return
+  if tfAcyclic in typ.flags: return
   var t = skipTypes(typ, abstractInst+{tyOwned}-{tyTypeDesc})
+  if tfAcyclic in t.flags: return
   case t.kind
   of tyTuple, tyObject, tyRef, tySequence, tyArray, tyOpenArray, tyVarargs:
     if not containsOrIncl(marker, t.id):
diff --git a/doc/manual.rst b/doc/manual.rst
index aed50554b..9ce67ec45 100644
--- a/doc/manual.rst
+++ b/doc/manual.rst
@@ -6017,8 +6017,32 @@ The ``noreturn`` pragma is used to mark a proc that never returns.
 
 acyclic pragma
 --------------
-The ``acyclic`` pragma applies to type declarations. It is deprecated and
-ignored.
+The ``acyclic`` pragma can be used for object types to mark them as acyclic
+even though they seem to be cyclic. This is an **optimization** for the garbage
+collector to not consider objects of this type as part of a cycle:
+
+.. code-block:: nim
+  type
+    Node = ref NodeObj
+    NodeObj {.acyclic.} = object
+      left, right: Node
+      data: string
+
+Or if we directly use a ref object:
+
+.. code-block:: nim
+  type
+    Node {.acyclic.} = ref object
+      left, right: Node
+      data: string
+
+In the example a tree structure is declared with the ``Node`` type. Note that
+the type definition is recursive and the GC has to assume that objects of
+this type may form a cyclic graph. The ``acyclic`` pragma passes the
+information that this cannot happen to the GC. If the programmer uses the
+``acyclic`` pragma for data types that are in reality cyclic, the memory leaks
+can be the result, but memory safety is preserved.
+
 
 
 final pragma
diff --git a/doc/nimc.rst b/doc/nimc.rst
index c59653b34..cc8d0875c 100644
--- a/doc/nimc.rst
+++ b/doc/nimc.rst
@@ -237,8 +237,8 @@ This uses the configuration defined in ``config\nim.cfg`` for ``lvm_gcc``.
 If nimcache already contains compiled code from a different compiler for the same project,
 add the ``-f`` flag to force all files to be recompiled.
 
-The default compiler is defined at the top of ``config\nim.cfg``.  Changing this setting
-affects the compiler used by ``koch`` to (re)build Nim.
+The default compiler is defined at the top of ``config\nim.cfg``.
+Changing this setting affects the compiler used by ``koch`` to (re)build Nim.
 
 
 Cross compilation
@@ -557,9 +557,9 @@ For example, to generate code for an `AVR`:idx: processor use this command::
 For the ``standalone`` target one needs to provide
 a file ``panicoverride.nim``.
 See ``tests/manyloc/standalone/panicoverride.nim`` for an example
-implementation.  Additionally, users should specify the
+implementation. Additionally, users should specify the
 amount of heap space to use with the ``-d:StandaloneHeapSize=<size>``
-command line switch.  Note that the total heap size will be
+command line switch. Note that the total heap size will be
 ``<size> * sizeof(float64)``.
 
 
diff --git a/lib/system.nim b/lib/system.nim
index 81d308073..583161f85 100644
--- a/lib/system.nim
+++ b/lib/system.nim
@@ -3112,11 +3112,13 @@ when not defined(js):
         destructor: pointer
         size: int
         name: cstring
+        traceImpl: pointer
+        disposeImpl: pointer
       PNimType = ptr TNimType
 
   when defined(nimSeqsV2) and not defined(nimscript):
-    include "core/strs"
-    include "core/seqs"
+    include "system/strs_v2"
+    include "system/seqs_v2"
 
   {.pop.}
 
@@ -3139,7 +3141,7 @@ when not defined(JS) and not defined(nimscript):
   {.pop.}
 
 when defined(nimV2):
-  include core/runtime_v2
+  include system/refs_v2
 
 import system/assertions
 export assertions
diff --git a/lib/core/allocators.nim b/lib/system/allocators.nim
index 43aae0111..43aae0111 100644
--- a/lib/core/allocators.nim
+++ b/lib/system/allocators.nim
diff --git a/lib/system/cyclicrefs_v2.nim b/lib/system/cyclicrefs_v2.nim
new file mode 100644
index 000000000..4fbb4fc94
--- /dev/null
+++ b/lib/system/cyclicrefs_v2.nim
@@ -0,0 +1,232 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2019 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+# Cycle collector based on Lins' Jump Stack and other ideas,
+# see for example:
+# https://pdfs.semanticscholar.org/f2b2/0d168acf38ff86305809a55ef2c5d6ebc787.pdf
+# Further refinement in 2008 by the notion of "critical links", see
+# "Cyclic reference counting" by Rafael Dueire Lins
+# R.D. Lins / Information Processing Letters 109 (2008) 71–78
+
+const
+  colGreen = 0b000
+  colYellow = 0b001
+  colRed = 0b010
+  jumpStackFlag = 0b100  # stored in jumpstack
+  rcShift = 3      # shift by rcShift to get the reference counter
+  colorMask = 0b011
+
+type
+  TraceProc = proc (p, env: pointer) {.nimcall, benign.}
+  DisposeProc = proc (p: pointer) {.nimcall, benign.}
+
+template color(c): untyped = c.rc and colorMask
+template setColor(c, col) =
+  when col == colGreen:
+    c.rc = c.rc and not colorMask
+  else:
+    c.rc = c.rc and not colorMask or col
+
+proc nimIncRefCyclic(p: pointer) {.compilerRtl, inl.} =
+  let h = head(p)
+  inc h.rc, rcIncrement
+  h.setColor colYellow # mark as potential cycle!
+
+proc markCyclic*[T](x: ref T) {.inline.} =
+  ## Mark the underlying object as a candidate for cycle collections.
+  ## Experimental API. Do not use!
+  let h = head(cast[pointer](x))
+  h.setColor colYellow
+type
+  CellTuple = (Cell, PNimType)
+  CellArray = ptr UncheckedArray[CellTuple]
+  CellSeq = object
+    len, cap: int
+    d: CellArray
+
+  GcEnv = object
+    traceStack: CellSeq
+    jumpStack: CellSeq
+
+# ------------------- cell seq handling --------------------------------------
+
+proc add(s: var CellSeq, c: Cell; t: PNimType) {.inline.} =
+  if s.len >= s.cap:
+    s.cap = s.cap * 3 div 2
+    when defined(useMalloc):
+      var d = cast[CellArray](c_malloc(uint(s.cap * sizeof(CellTuple))))
+    else:
+      var d = cast[CellArray](alloc(s.cap * sizeof(CellTuple)))
+    copyMem(d, s.d, s.len * sizeof(CellTuple))
+    when defined(useMalloc):
+      c_free(s.d)
+    else:
+      dealloc(s.d)
+    s.d = d
+    # XXX: realloc?
+  s.d[s.len] = (c, t)
+  inc(s.len)
+
+proc init(s: var CellSeq, cap: int = 1024) =
+  s.len = 0
+  s.cap = cap
+  when defined(useMalloc):
+    s.d = cast[CellArray](c_malloc(uint(s.cap * sizeof(CellTuple))))
+  else:
+    s.d = cast[CellArray](alloc(s.cap * sizeof(CellTuple)))
+
+proc deinit(s: var CellSeq) =
+  when defined(useMalloc):
+    c_free(s.d)
+  else:
+    dealloc(s.d)
+  s.d = nil
+  s.len = 0
+  s.cap = 0
+
+proc pop(s: var CellSeq): (Cell, PNimType) =
+  result = s.d[s.len-1]
+  dec s.len
+
+# ----------------------------------------------------------------------------
+
+proc trace(s: Cell; desc: PNimType; j: var GcEnv) {.inline.} =
+  if desc.traceImpl != nil:
+    var p = s +! sizeof(RefHeader)
+    cast[TraceProc](desc.traceImpl)(p, addr(j))
+
+proc free(s: Cell; desc: PNimType) {.inline.} =
+  when traceCollector:
+    cprintf("[From ] %p rc %ld color %ld in jumpstack %ld\n", s, s.rc shr rcShift,
+            s.color, s.rc and jumpStackFlag)
+  var p = s +! sizeof(RefHeader)
+  if desc.disposeImpl != nil:
+    cast[DisposeProc](desc.disposeImpl)(p)
+  nimRawDispose(p)
+
+proc collect(s: Cell; desc: PNimType; j: var GcEnv) =
+  if s.color == colRed:
+    s.setColor colGreen
+    trace(s, desc, j)
+    while j.traceStack.len > 0:
+      let (t, desc) = j.traceStack.pop()
+      if t.color == colRed:
+        t.setColor colGreen
+        trace(t, desc, j)
+        free(t, desc)
+    free(s, desc)
+    #cprintf("[Cycle free] %p %ld\n", s, s.rc shr rcShift)
+
+proc markRed(s: Cell; desc: PNimType; j: var GcEnv) =
+  if s.color != colRed:
+    s.setColor colRed
+    trace(s, desc, j)
+    while j.traceStack.len > 0:
+      let (t, desc) = j.traceStack.pop()
+      when traceCollector:
+        cprintf("[Cycle dec] %p %ld color %ld in jumpstack %ld\n", t, t.rc shr rcShift, t.color, t.rc and jumpStackFlag)
+      dec t.rc, rcIncrement
+      if (t.rc and not rcMask) >= 0 and (t.rc and jumpStackFlag) == 0:
+        t.rc = t.rc or jumpStackFlag
+        when traceCollector:
+          cprintf("[Now in jumpstack] %p %ld color %ld in jumpstack %ld\n", t, t.rc shr rcShift, t.color, t.rc and jumpStackFlag)
+        j.jumpStack.add(t, desc)
+      if t.color != colRed:
+        t.setColor colRed
+        trace(t, desc, j)
+
+proc scanGreen(s: Cell; desc: PNimType; j: var GcEnv) =
+  s.setColor colGreen
+  trace(s, desc, j)
+  while j.traceStack.len > 0:
+    let (t, desc) = j.traceStack.pop()
+    if t.color != colGreen:
+      t.setColor colGreen
+      trace(t, desc, j)
+    inc t.rc, rcIncrement
+    when traceCollector:
+      cprintf("[Cycle inc] %p %ld color %ld\n", t, t.rc shr rcShift, t.color)
+
+proc nimTraceRef(p: pointer; desc: PNimType; env: pointer) {.compilerRtl.} =
+  if p != nil:
+    var t = head(p)
+    var j = cast[ptr GcEnv](env)
+    j.traceStack.add(t, desc)
+
+proc nimTraceRefDyn(p: pointer; env: pointer) {.compilerRtl.} =
+  if p != nil:
+    let desc = cast[ptr PNimType](p)[]
+    var t = head(p)
+    var j = cast[ptr GcEnv](env)
+    j.traceStack.add(t, desc)
+
+proc scan(s: Cell; desc: PNimType; j: var GcEnv) =
+  when traceCollector:
+    cprintf("[doScanGreen] %p %ld\n", s, s.rc shr rcShift)
+  # even after trial deletion, `s` is still alive, so undo
+  # the decrefs by calling `scanGreen`:
+  if (s.rc and not rcMask) >= 0:
+    scanGreen(s, desc, j)
+    s.setColor colYellow
+  else:
+    # first we have to repair all the nodes we have seen
+    # that are still alive; we also need to mark what they
+    # refer to as alive:
+    while j.jumpStack.len > 0:
+      let (t, desc) = j.jumpStack.pop
+      # not in jump stack anymore!
+      t.rc = t.rc and not jumpStackFlag
+      if t.color == colRed and (t.rc and not rcMask) >= 0:
+        scanGreen(t, desc, j)
+        t.setColor colYellow
+        when traceCollector:
+          cprintf("[jump stack] %p %ld\n", t, t.rc shr rcShift)
+    # we have proven that `s` and its subgraph are dead, so we can
+    # collect these nodes:
+    collect(s, desc, j)
+
+proc traceCycle(s: Cell; desc: PNimType) {.noinline.} =
+  when traceCollector:
+    cprintf("[traceCycle] %p %ld\n", s, s.rc shr rcShift)
+  var j: GcEnv
+  init j.jumpStack
+  init j.traceStack
+  markRed(s, desc, j)
+  scan(s, desc, j)
+  while j.jumpStack.len > 0:
+    let (t, desc) = j.jumpStack.pop
+    # not in jump stack anymore!
+    t.rc = t.rc and not jumpStackFlag
+  deinit j.jumpStack
+  deinit j.traceStack
+
+proc nimDecRefIsLastCyclicDyn(p: pointer): bool {.compilerRtl, inl.} =
+  if p != nil:
+    var cell = head(p)
+    if (cell.rc and not rcMask) == 0:
+      result = true
+      #cprintf("[DESTROY] %p\n", p)
+    else:
+      dec cell.rc, rcIncrement
+      if cell.color == colYellow:
+        let desc = cast[ptr PNimType](p)[]
+        traceCycle(cell, desc)
+      # According to Lins it's correct to do nothing else here.
+      #cprintf("[DeCREF] %p\n", p)
+
+proc nimDecRefIsLastCyclicStatic(p: pointer; desc: PNimType): bool {.compilerRtl, inl.} =
+  if p != nil:
+    var cell = head(p)
+    if (cell.rc and not rcMask) == 0:
+      result = true
+      #cprintf("[DESTROY] %p %s\n", p, desc.name)
+    else:
+      dec cell.rc, rcIncrement
+      if cell.color == colYellow: traceCycle(cell, desc)
+      #cprintf("[DeCREF] %p %s %ld\n", p, desc.name, cell.rc)
diff --git a/lib/system/mmdisp.nim b/lib/system/mmdisp.nim
index 330c551c5..60f8f7db6 100644
--- a/lib/system/mmdisp.nim
+++ b/lib/system/mmdisp.nim
@@ -499,7 +499,8 @@ else:
   when not defined(gcRegions):
     include "system/alloc"
 
-    include "system/cellsets"
+    when not usesDestructors:
+      include "system/cellsets"
     when not leakDetector and not useCellIds:
       sysAssert(sizeof(Cell) == sizeof(FreeCell), "sizeof FreeCell")
   when compileOption("gc", "v2"):
diff --git a/lib/core/runtime_v2.nim b/lib/system/refs_v2.nim
index d566a4c69..3033880c3 100644
--- a/lib/core/runtime_v2.nim
+++ b/lib/system/refs_v2.nim
@@ -1,3 +1,12 @@
+#
+#
+#            Nim's Runtime Library
+#        (c) Copyright 2019 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
 #[
 In this new runtime we simplify the object layouts a bit: The runtime type
 information is only accessed for the objects that have it and it's always
@@ -25,11 +34,16 @@ hash of ``package & "." & module & "." & name`` to save space.
 
 ]#
 
+const
+  rcIncrement = 0b1000 # so that lowest 3 bits are not touched
+  rcMask = 0b111
+
 type
   RefHeader = object
     rc: int # the object header is now a single RC field.
             # we could remove it in non-debug builds for the 'owned ref'
             # design but this seems unwise.
+  Cell = ptr RefHeader
 
 template `+!`(p: pointer, s: int): pointer =
   cast[pointer](cast[int](p) +% s)
@@ -37,8 +51,11 @@ template `+!`(p: pointer, s: int): pointer =
 template `-!`(p: pointer, s: int): pointer =
   cast[pointer](cast[int](p) -% s)
 
-template head(p: pointer): ptr RefHeader =
-  cast[ptr RefHeader](cast[int](p) -% sizeof(RefHeader))
+template head(p: pointer): Cell =
+  cast[Cell](cast[int](p) -% sizeof(RefHeader))
+
+const
+  traceCollector = defined(traceArc)
 
 var allocs*: int
 
@@ -56,28 +73,22 @@ proc nimNewObj(size: int): pointer {.compilerRtl.} =
     atomicInc allocs
   else:
     inc allocs
+  when traceCollector:
+    cprintf("[Allocated] %p\n", result -! sizeof(RefHeader))
 
 proc nimDecWeakRef(p: pointer) {.compilerRtl, inl.} =
-  when hasThreadSupport:
-    atomicDec head(p).rc
-  else:
-    dec head(p).rc
+  dec head(p).rc, rcIncrement
 
 proc nimIncRef(p: pointer) {.compilerRtl, inl.} =
-  when hasThreadSupport:
-    atomicInc head(p).rc
-  else:
-    inc head(p).rc
-    #cprintf("[INCREF] %p\n", p)
+  inc head(p).rc, rcIncrement
+  #cprintf("[INCREF] %p\n", p)
 
 proc nimRawDispose(p: pointer) {.compilerRtl.} =
   when not defined(nimscript):
+    when traceCollector:
+      cprintf("[Freed] %p\n", p -! sizeof(RefHeader))
     when defined(nimOwnedEnabled):
-      when hasThreadSupport:
-        let hasDanglingRefs = atomicLoadN(addr head(p).rc, ATOMIC_RELAXED) != 0
-      else:
-        let hasDanglingRefs = head(p).rc != 0
-      if hasDanglingRefs:
+      if head(p).rc >= rcIncrement:
         cstderr.rawWrite "[FATAL] dangling references exist\n"
         quit 1
     when defined(useMalloc):
@@ -108,21 +119,19 @@ proc nimDestroyAndDispose(p: pointer) {.compilerRtl.} =
       cstderr.rawWrite "has destructor!\n"
   nimRawDispose(p)
 
+when defined(gcOrc):
+  include cyclicrefs_v2
+
 proc nimDecRefIsLast(p: pointer): bool {.compilerRtl, inl.} =
   if p != nil:
-    when hasThreadSupport:
-      if atomicLoadN(addr head(p).rc, ATOMIC_RELAXED) == 0:
-        result = true
-      else:
-        discard atomicDec(head(p).rc)
+    var cell = head(p)
+    if (cell.rc and not rcMask) == 0:
+      result = true
+      #cprintf("[DESTROY] %p\n", p)
     else:
-      if head(p).rc == 0:
-        result = true
-        #cprintf("[DESTROY] %p\n", p)
-      else:
-        dec head(p).rc
-        # According to Lins it's correct to do nothing else here.
-        #cprintf("[DeCREF] %p\n", p)
+      dec cell.rc, rcIncrement
+      # According to Lins it's correct to do nothing else here.
+      #cprintf("[DeCREF] %p\n", p)
 
 proc GC_unref*[T](x: ref T) =
   ## New runtime only supports this operation for 'ref T'.
diff --git a/lib/core/seqs.nim b/lib/system/seqs_v2.nim
index b7f9fb153..b7f9fb153 100644
--- a/lib/core/seqs.nim
+++ b/lib/system/seqs_v2.nim
diff --git a/lib/core/strs.nim b/lib/system/strs_v2.nim
index 3b7a46ff1..3b7a46ff1 100644
--- a/lib/core/strs.nim
+++ b/lib/system/strs_v2.nim
diff --git a/lib/system/widestrs.nim b/lib/system/widestrs.nim
index cf5e728d7..f3a6f9d77 100644
--- a/lib/system/widestrs.nim
+++ b/lib/system/widestrs.nim
@@ -18,7 +18,7 @@ type
 
 when defined(nimv2):
 
-  import core / allocators
+  import system / allocators
 
   type
     WideCString* = ptr UncheckedArray[Utf16Char]
diff --git a/testament/categories.nim b/testament/categories.nim
index 55e30f460..98ddd7e10 100644
--- a/testament/categories.nim
+++ b/testament/categories.nim
@@ -179,9 +179,9 @@ proc gcTests(r: var TResults, cat: Category, options: string) =
                   " -d:release -d:useRealtimeGC", cat)
     when filename != "gctest":
       testSpec r, makeTest("tests/gc" / filename, options &
-                    " --gc:arc", cat)
+                    " --gc:orc", cat)
       testSpec r, makeTest("tests/gc" / filename, options &
-                    " --gc:arc -d:release", cat)
+                    " --gc:orc -d:release", cat)
 
   template testWithoutBoehm(filename: untyped) =
     testWithoutMs filename
diff --git a/testament/specs.nim b/testament/specs.nim
index 61820c328..f4d06e093 100644
--- a/testament/specs.nim
+++ b/testament/specs.nim
@@ -170,7 +170,8 @@ proc parseSpec*(filename: string): TSpec =
       of "tcolumn":
         discard parseInt(e.value, result.tcolumn)
       of "output":
-        result.outputCheck = ocEqual
+        if result.outputCheck != ocSubstr:
+          result.outputCheck = ocEqual
         result.output = strip(e.value)
       of "input":
         result.input = e.value
@@ -200,6 +201,8 @@ proc parseSpec*(filename: string): TSpec =
         when defined(linux) and sizeof(int) == 8:
           result.useValgrind = parseCfgBool(e.value)
           result.unjoinable = true
+          if result.useValgrind:
+            result.outputCheck = ocSubstr
         else:
           # Windows lacks valgrind. Silly OS.
           # Valgrind only supports OSX <= 17.x
diff --git a/testament/testament.nim b/testament/testament.nim
index c7aa854b8..976218f43 100644
--- a/testament/testament.nim
+++ b/testament/testament.nim
@@ -449,7 +449,7 @@ proc testSpecHelper(r: var TResults, test: TTest, expected: TSpec, target: TTarg
             else:
               exeCmd = exeFile
             if expected.useValgrind:
-              args = exeCmd & args
+              args = @["--error-exitcode=1"] & exeCmd & args
               exeCmd = "valgrind"
           var (_, buf, exitCode) = execCmdEx2(exeCmd, args, input = expected.input)
           # Treat all failure codes from nodejs as 1. Older versions of nodejs used
diff --git a/tests/destructor/tarc2.nim b/tests/destructor/tarc2.nim
index 56dbfe929..bd6343b2f 100644
--- a/tests/destructor/tarc2.nim
+++ b/tests/destructor/tarc2.nim
@@ -1,6 +1,6 @@
 discard """
-  output: '''leak: true'''
-  cmd: '''nim c --gc:arc $file'''
+  output: '''leak: false'''
+  cmd: '''nim c --gc:orc $file'''
 """
 
 type
@@ -19,11 +19,8 @@ proc addX(x: T; child: T) =
 proc main(rootName: string) =
   var root = create()
   root.data = rootName
-  # this implies we do the refcounting wrong. We should leak memory here
-  # and not create a destruction cycle:
   root.addX root
 
 let mem = getOccupiedMem()
 main("yeah")
-# since we created a retain cycle, we MUST leak memory here:
 echo "leak: ", getOccupiedMem() - mem > 0
diff --git a/tests/destructor/tarctypesections.nim b/tests/destructor/tarctypesections.nim
new file mode 100644
index 000000000..da81f1884
--- /dev/null
+++ b/tests/destructor/tarctypesections.nim
@@ -0,0 +1,70 @@
+discard """
+  output: "MEM 0"
+  cmd: "nim c --gc:arc $file"
+"""
+
+type
+  RefNode = ref object
+    le, ri: RefNode
+    name: char
+
+proc edge0(a, b: RefNode) =
+  if a.le == nil: a.le = b
+  else: a.ri = b
+
+proc createNode0(name: char): RefNode =
+  new result
+  result.name = name
+
+proc main0 =
+  let r = createNode0('R')
+  let c = createNode0('C')
+  c.edge0 r
+
+
+type
+  NodeDesc = object
+    le, ri: Node
+    name: char
+  Node = ref NodeDesc
+
+proc edge(a, b: Node) =
+  if a.le == nil: a.le = b
+  else: a.ri = b
+
+proc createNode(name: char): Node =
+  new result
+  result.name = name
+
+proc main =
+  let r = createNode('R')
+  let c = createNode('C')
+  c.edge r
+
+
+type
+  NodeB = ref NodeBo
+  NodeBo = object
+    le, ri: NodeB
+    name: char
+
+proc edge(a, b: NodeB) =
+  if a.le == nil: a.le = b
+  else: a.ri = b
+
+proc createNodeB(name: char): NodeB =
+  new result
+  result.name = name
+
+
+proc mainB =
+  let r = createNodeB('R')
+  let c = createNodeB('C')
+  c.edge r
+
+
+let memB = getOccupiedMem()
+main0()
+main()
+mainB()
+echo "MEM ", getOccupiedMem() - memB
diff --git a/tests/destructor/tasync_prototype_cyclic.nim b/tests/destructor/tasync_prototype_cyclic.nim
new file mode 100644
index 000000000..136e0b676
--- /dev/null
+++ b/tests/destructor/tasync_prototype_cyclic.nim
@@ -0,0 +1,54 @@
+discard """
+  output: '''asdas
+processClient end
+false
+MEMORY 0
+'''
+  cmd: '''nim c --gc:orc $file'''
+"""
+
+type
+  PAsyncHttpServer = ref object
+    value: string
+  PFutureBase = ref object
+    callback: proc () {.closure.}
+    value: string
+    failed: bool
+
+proc accept(server: PAsyncHttpServer): PFutureBase =
+  new(result)
+  result.callback = proc () =
+    discard
+  server.value = "hahaha"
+
+proc processClient(): PFutureBase =
+  new(result)
+
+proc serve(server: PAsyncHttpServer): PFutureBase =
+  iterator serveIter(): PFutureBase {.closure.} =
+    echo server.value
+    while true:
+      var acceptAddrFut = server.accept()
+      yield acceptAddrFut
+      var fut = acceptAddrFut.value
+
+      var f = processClient()
+      when true:
+        f.callback =
+          proc () =
+            echo("processClient end")
+            echo(f.failed)
+      yield f
+  var x = serveIter
+  for i in 0 .. 1:
+    result = x()
+    if result.callback != nil:
+      result.callback()
+
+let mem = getOccupiedMem()
+
+proc main =
+  discard serve(PAsyncHttpServer(value: "asdas"))
+
+main()
+echo "MEMORY ", getOccupiedMem() - mem
diff --git a/tests/destructor/tbintree2.nim b/tests/destructor/tbintree2.nim
index 15a3c41ec..5f88ffff5 100644
--- a/tests/destructor/tbintree2.nim
+++ b/tests/destructor/tbintree2.nim
@@ -4,7 +4,7 @@ discard """
 3 3  alloc/dealloc pairs: 0'''
 """
 
-import core / allocators
+import system / allocators
 import system / ansi_c
 
 import random
diff --git a/tests/destructor/tcycle1.nim b/tests/destructor/tcycle1.nim
new file mode 100644
index 000000000..c30977433
--- /dev/null
+++ b/tests/destructor/tcycle1.nim
@@ -0,0 +1,53 @@
+discard """
+  output: "MEM 0"
+  cmd: "nim c --gc:orc $file"
+"""
+
+type
+  Node = ref object of RootObj
+    le, ri: Node
+    name: char
+
+proc edge(a, b: Node) =
+  if a.le == nil: a.le = b
+  else: a.ri = b
+
+proc createNode(name: char): Node =
+  new result
+  result.name = name
+
+#[
+
++---------+      +------+
+|         |      |      |
+|  A      +----->+      <------+-------------+
++--+------+      |      |      |             |
+   |             |      |      |     C       |
+   |             |  R   |      |             |
++--v------+      |      |      +-------------+
+|         |      |      |        ^
+|   B     <------+      |        |
+|         |      |      +--------+
++---------+      |      |
+                 +------+
+
+]#
+
+proc main =
+  let a = createNode('A')
+  let b = createNode('B')
+  let r = createNode('R')
+  let c = createNode('C')
+
+  a.edge b
+  a.edge r
+
+  r.edge b
+  r.edge c
+
+  c.edge r
+
+
+let mem = getOccupiedMem()
+main()
+echo "MEM ", getOccupiedMem() - mem
diff --git a/tests/destructor/tcycle2.nim b/tests/destructor/tcycle2.nim
new file mode 100644
index 000000000..c4b297559
--- /dev/null
+++ b/tests/destructor/tcycle2.nim
@@ -0,0 +1,18 @@
+discard """
+  output: "MEM 0"
+  cmd: "nim c --gc:orc $file"
+"""
+
+type
+  Node = ref object
+    kids: seq[Node]
+    data: string
+
+proc main(x: int) =
+  var n = Node(kids: @[], data: "3" & $x)
+  let m = n
+  n.kids.add m
+
+let mem = getOccupiedMem()
+main(90)
+echo "MEM ", getOccupiedMem() - mem
diff --git a/tests/destructor/tcycle3.nim b/tests/destructor/tcycle3.nim
new file mode 100644
index 000000000..a938ded01
--- /dev/null
+++ b/tests/destructor/tcycle3.nim
@@ -0,0 +1,64 @@
+discard """
+  output: '''BEGIN
+END
+END 2
+0'''
+  cmd: '''nim c --gc:orc $file'''
+"""
+
+# extracted from thavlak.nim
+
+type
+  BasicBlock = ref object
+    inEdges: seq[BasicBlock]
+    outEdges: seq[BasicBlock]
+    name: int
+
+proc newBasicBlock(name: int): BasicBlock =
+  result = BasicBlock(
+    inEdges: newSeq[BasicBlock](),
+    outEdges: newSeq[BasicBlock](),
+    name: name
+  )
+
+type
+  Cfg = object
+    basicBlockMap: seq[BasicBlock]
+    startNode: BasicBlock
+
+proc newCfg(): Cfg =
+  result = Cfg(
+    basicBlockMap: newSeq[BasicBlock](),
+    startNode: nil)
+
+proc createNode(cfg: var Cfg, name: int): BasicBlock =
+  if name < cfg.basicBlockMap.len:
+    result = cfg.basicBlockMap[name]
+  else:
+    result = newBasicBlock(name)
+    cfg.basicBlockMap.setLen name+1
+    cfg.basicBlockMap[name] = result
+
+proc newBasicBlockEdge(cfg: var Cfg, fromName, toName: int) =
+  echo "BEGIN"
+  let fr = cfg.createNode(fromName)
+  let to = cfg.createNode(toName)
+
+  fr.outEdges.add(to)
+  to.inEdges.add(fr)
+
+proc run(cfg: var Cfg) =
+  cfg.startNode = cfg.createNode(0) # RC = 2
+  newBasicBlockEdge(cfg, 0, 1) #
+  echo "END"
+
+  discard cfg.createNode(1)
+
+proc main =
+  var c = newCfg()
+  c.run
+  echo "END 2"
+
+let mem = getOccupiedMem()
+main()
+echo getOccupiedMem() - mem
diff --git a/tests/destructor/tgcdestructors.nim b/tests/destructor/tgcdestructors.nim
index 7eb865915..4a66b4f77 100644
--- a/tests/destructor/tgcdestructors.nim
+++ b/tests/destructor/tgcdestructors.nim
@@ -13,7 +13,7 @@ true
 41 41'''
 """
 
-import allocators
+import system / allocators
 include system / ansi_c
 
 proc main =
diff --git a/tests/destructor/tnewruntime_misc.nim b/tests/destructor/tnewruntime_misc.nim
index 025383565..612d1a116 100644
--- a/tests/destructor/tnewruntime_misc.nim
+++ b/tests/destructor/tnewruntime_misc.nim
@@ -11,7 +11,7 @@ test
 '''
 """
 
-import core / allocators
+import system / allocators
 import system / ansi_c
 
 import tables
@@ -132,5 +132,5 @@ proc xx(xml: string): MyObject =
   result.x  = xml
   defer: echo stream
 
- 
+
 discard xx("test")
diff --git a/tests/destructor/tnewruntime_strutils.nim b/tests/destructor/tnewruntime_strutils.nim
index 54ce4acea..3e4ee27be 100644
--- a/tests/destructor/tnewruntime_strutils.nim
+++ b/tests/destructor/tnewruntime_strutils.nim
@@ -1,11 +1,12 @@
 discard """
-  cmd: '''nim c --newruntime $file'''
+  valgrind: true
+  cmd: '''nim c --newruntime -d:useMalloc $file'''
   output: '''422 422'''
 """
 
 import strutils, os, std / wordwrap
 
-import core / allocators
+import system / allocators
 import system / ansi_c
 
 # bug #11004
diff --git a/tests/destructor/tselect.nim b/tests/destructor/tselect.nim
new file mode 100644
index 000000000..9262b47d4
--- /dev/null
+++ b/tests/destructor/tselect.nim
@@ -0,0 +1,26 @@
+discard """
+   output: '''abcsuffix
+xyzsuffix'''
+  cmd: '''nim c --gc:arc $file'''
+"""
+
+proc select(cond: bool; a, b: sink string): string =
+  if cond:
+    result = a # moves a into result
+  else:
+    result = b # moves b into result
+
+proc test(param: string; cond: bool) =
+  var x = "abc" & param
+  var y = "xyz" & param
+
+  # possible self-assignment:
+  x = select(cond, x, y)
+
+  echo x
+  # 'select' must communicate what parameter has been
+  # consumed. We cannot simply generate:
+  # (select(...); wasMoved(x); wasMoved(y))
+
+test("suffix", true)
+test("suffix", false)
diff --git a/tests/destructor/tsimpleclosure.nim b/tests/destructor/tsimpleclosure.nim
index 583cc7d88..088f4a95c 100644
--- a/tests/destructor/tsimpleclosure.nim
+++ b/tests/destructor/tsimpleclosure.nim
@@ -8,7 +8,7 @@ hello
 2 2  alloc/dealloc pairs: 0'''
 """
 
-import core / allocators
+import system / allocators
 import system / ansi_c
 
 proc main(): owned(proc()) =
diff --git a/tests/destructor/tuse_ownedref_after_move.nim b/tests/destructor/tuse_ownedref_after_move.nim
index 2108fa01c..31f580db3 100644
--- a/tests/destructor/tuse_ownedref_after_move.nim
+++ b/tests/destructor/tuse_ownedref_after_move.nim
@@ -4,7 +4,7 @@ discard """
   line: 49
 """
 
-import core / allocators
+import system / allocators
 import system / ansi_c
 
 type
diff --git a/tests/destructor/tv2_raise.nim b/tests/destructor/tv2_raise.nim
index dcc3b1b38..409cdead8 100644
--- a/tests/destructor/tv2_raise.nim
+++ b/tests/destructor/tv2_raise.nim
@@ -6,7 +6,7 @@ discard """
 
 import strutils, math
 import system / ansi_c
-import core / allocators
+import system / allocators
 
 proc mainA =
   try:
diff --git a/tests/destructor/twidgets.nim b/tests/destructor/twidgets.nim
index 1d75a803d..9537748e3 100644
--- a/tests/destructor/twidgets.nim
+++ b/tests/destructor/twidgets.nim
@@ -5,7 +5,7 @@ clicked!
 1 1  alloc/dealloc pairs: 0'''
 """
 
-import core / allocators
+import system / allocators
 import system / ansi_c
 
 type
diff --git a/tests/destructor/twidgets_unown.nim b/tests/destructor/twidgets_unown.nim
index 39d3c46df..d594ad54c 100644
--- a/tests/destructor/twidgets_unown.nim
+++ b/tests/destructor/twidgets_unown.nim
@@ -5,7 +5,7 @@ clicked!
 6 6  alloc/dealloc pairs: 0'''
 """
 
-import core / allocators
+import system / allocators
 import system / ansi_c
 
 type
diff --git a/tests/gc/gcbench.nim b/tests/gc/gcbench.nim
index 9de558234..7a8addff3 100644
--- a/tests/gc/gcbench.nim
+++ b/tests/gc/gcbench.nim
@@ -53,11 +53,11 @@ import
 
 type
   PNode = ref TNode
-  TNode {.final.} = object
+  TNode {.final, acyclic.} = object
     left, right: PNode
     i, j: int
 
-proc newNode(L, r: PNode): PNode =
+proc newNode(L, r: sink PNode): PNode =
   new(result)
   result.left = L
   result.right = r
@@ -166,9 +166,13 @@ proc main() =
 
   var elapsed = epochTime() - t
   printDiagnostics()
-  echo("Completed in " & $elapsed & "ms. Success!")
+  echo("Completed in " & $elapsed & "s. Success!")
 
 when defined(GC_setMaxPause):
   GC_setMaxPause 2_000
 
+when defined(gcDestructors):
+  let mem = getOccupiedMem()
 main()
+when defined(gcDestructors):
+  doAssert getOccupiedMem() == mem
diff --git a/tests/gc/thavlak.nim b/tests/gc/thavlak.nim
index f90e09c5a..b4cdacf7c 100644
--- a/tests/gc/thavlak.nim
+++ b/tests/gc/thavlak.nim
@@ -394,18 +394,20 @@ proc run(self: var LoopTesterApp) =
   echo "Constructing CFG..."
   var n = 2
 
-  for parlooptrees in 1..10:
-    discard self.cfg.createNode(n + 1)
-    self.buildConnect(2, n + 1)
-    n += 1
-    for i in 1..100:
-      var top = n
-      n = self.buildStraight(n, 1)
-      for j in 1..25: n = self.buildBaseLoop(n)
-      var bottom = self.buildStraight(n, 1)
-      self.buildConnect n, top
-      n = bottom
-    self.buildConnect(n, 1)
+  when not defined(gcOrc):
+    # currently cycle detection is so slow that we disable this part
+    for parlooptrees in 1..10:
+      discard self.cfg.createNode(n + 1)
+      self.buildConnect(2, n + 1)
+      n += 1
+      for i in 1..100:
+        var top = n
+        n = self.buildStraight(n, 1)
+        for j in 1..25: n = self.buildBaseLoop(n)
+        var bottom = self.buildStraight(n, 1)
+        self.buildConnect n, top
+        n = bottom
+      self.buildConnect(n, 1)
 
   echo "Performing Loop Recognition\n1 Iteration"
 
@@ -428,5 +430,11 @@ proc run(self: var LoopTesterApp) =
     echo("Total memory available: " & formatSize(getTotalMem()) & " bytes")
     echo("Free memory: " & formatSize(getFreeMem()) & " bytes")
 
-var l = newLoopTesterApp()
-l.run
+proc main =
+  var l = newLoopTesterApp()
+  l.run
+
+let mem = getOccupiedMem()
+main()
+when defined(gcOrc):
+  doAssert getOccupiedMem() == mem