summary refs log tree commit diff stats
path: root/tests/gc
diff options
context:
space:
mode:
Diffstat (limited to 'tests/gc')
-rw-r--r--tests/gc/bintrees.nim54
-rw-r--r--tests/gc/closureleak.nim52
-rw-r--r--tests/gc/cyclecollector.nim23
-rw-r--r--tests/gc/cycleleak.nim56
-rw-r--r--tests/gc/foreign_thr.nim88
-rw-r--r--tests/gc/gcbench.nim180
-rw-r--r--tests/gc/gcemscripten.nim59
-rw-r--r--tests/gc/gcleak.nim29
-rw-r--r--tests/gc/gcleak2.nim38
-rw-r--r--tests/gc/gcleak3.nim26
-rw-r--r--tests/gc/gcleak4.nim46
-rw-r--r--tests/gc/gcleak5.nim25
-rw-r--r--tests/gc/gctest.nim210
-rw-r--r--tests/gc/gctest.nim.cfg1
-rw-r--r--tests/gc/growobjcrash.nim24
-rw-r--r--tests/gc/panicoverride.nim14
-rw-r--r--tests/gc/refarrayleak.nim39
-rw-r--r--tests/gc/stackrefleak.nim32
-rw-r--r--tests/gc/tdisable_orc.nim9
-rw-r--r--tests/gc/thavlak.nim441
-rw-r--r--tests/gc/tlists.nim35
-rw-r--r--tests/gc/trace_globals.nim33
-rw-r--r--tests/gc/tregionleak.nim23
-rw-r--r--tests/gc/tstandalone.nim14
-rw-r--r--tests/gc/weakrefs.nim57
25 files changed, 1608 insertions, 0 deletions
diff --git a/tests/gc/bintrees.nim b/tests/gc/bintrees.nim
new file mode 100644
index 000000000..5b65bb437
--- /dev/null
+++ b/tests/gc/bintrees.nim
@@ -0,0 +1,54 @@
+# -*- nim -*-
+
+import os, strutils
+
+type
+  PNode = ref TNode
+  TNode {.final, acyclic.} = object
+    left, right: PNode
+    item: int
+
+proc checkTree(node: PNode): int =
+  result = node.item
+  if node.left != nil:
+    inc result, checkTree(node.left) - checkTree(node.right)
+
+proc makeTreeAux(item, depth: int): PNode =
+  new(result)
+  result.item = item
+  if depth > 0:
+    result.left = makeTreeAux(2 * item - 1, depth - 1)
+    result.right = makeTreeAux(2 * item,    depth - 1)
+
+proc makeTree(item, depth: int): PNode =
+  #GC_disable()
+  result = makeTreeAux(item, depth)
+  #GC_enable()
+
+proc main =
+  var n = parseInt(paramStr(1))
+  const minDepth = 4
+  var maxDepth = if minDepth+2 > n: minDepth+2 else: n
+
+  var stretchDepth = maxDepth + 1
+
+  echo("stretch tree of depth ", stretchDepth, "\t check: ", checkTree(
+      makeTree(0, stretchDepth)))
+
+  var longLivedTree = makeTree(0, maxDepth)
+
+  var iterations = 1 shl maxDepth
+  for depth in countup (minDepth, stretchDepth-1, 2):
+    var check = 0
+    for i in 1..iterations:
+      check += checkTree(makeTree(i, depth)) + checkTree(makeTree(-i, depth))
+
+    echo(iterations*2, "\t trees of depth ", depth, "\t check: ", check)
+    iterations = iterations div 4
+
+  echo("long lived tree of depth ", maxDepth, "\t check: ",
+      longLivedTree.checkTree)
+  echo GC_getstatistics()
+
+main()
+
diff --git a/tests/gc/closureleak.nim b/tests/gc/closureleak.nim
new file mode 100644
index 000000000..e67beb513
--- /dev/null
+++ b/tests/gc/closureleak.nim
@@ -0,0 +1,52 @@
+discard """
+  outputsub: "true"
+  disabled: "32bit"
+"""
+
+type
+  TFoo* = object
+    id: int
+    fn: proc() {.closure.}
+var foo_counter = 0
+var alive_foos = newseq[int](0)
+
+when defined(gcDestructors):
+  proc `=destroy`(some: TFoo) =
+    alive_foos.del alive_foos.find(some.id)
+    # TODO: fixme: investigate why `=destroy` requires `some.fn` to be `gcsafe`
+    # the debugging info below came from `symPrototype` in the liftdestructors
+    # proc (){.closure, gcsafe.}, {tfThread, tfHasAsgn, tfCheckedForDestructor, tfExplicitCallConv}
+    # var proc (){.closure, gcsafe.}, {tfHasGCedMem}
+    # it worked by accident with var T destructors because in the sempass2
+    #
+    # let argtype = skipTypes(a.typ, abstractInst) # !!! it does't skip `tyVar`
+    # if argtype.kind == tyProc and notGcSafe(argtype) and not tracked.inEnforcedGcSafe:
+    #   localError(tracked.config, n.info, $n & " is not GC safe")
+    {.cast(gcsafe).}:
+      `=destroy`(some.fn)
+
+else:
+  proc free*(some: ref TFoo) =
+    #echo "Tfoo #", some.id, " freed"
+    alive_foos.del alive_foos.find(some.id)
+
+proc newFoo*(): ref TFoo =
+  when defined(gcDestructors):
+    new result
+  else:
+    new result, free
+
+  result.id = foo_counter
+  alive_foos.add result.id
+  inc foo_counter
+
+for i in 0 ..< 10:
+  discard newFoo()
+
+for i in 0 ..< 10:
+  let f = newFoo()
+  f.fn = proc =
+    echo f.id
+
+GC_fullcollect()
+echo alive_foos.len <= 3
diff --git a/tests/gc/cyclecollector.nim b/tests/gc/cyclecollector.nim
new file mode 100644
index 000000000..2d02a7a3c
--- /dev/null
+++ b/tests/gc/cyclecollector.nim
@@ -0,0 +1,23 @@
+
+# Program to detect bug #1796 reliably
+
+type
+  Node = ref object
+    a, b: Node
+    leaf: string
+
+proc createCycle(leaf: string): Node =
+  new result
+  result.a = result
+  when defined(gcArc) or defined(gcOrc):
+    result.leaf = leaf
+  else:
+    shallowCopy result.leaf, leaf
+
+proc main =
+  for i in 0 .. 100_000:
+    var leaf = "this is the leaf. it allocates"
+    let x = createCycle(leaf)
+    let y = createCycle(leaf)
+
+main()
diff --git a/tests/gc/cycleleak.nim b/tests/gc/cycleleak.nim
new file mode 100644
index 000000000..e355abc96
--- /dev/null
+++ b/tests/gc/cycleleak.nim
@@ -0,0 +1,56 @@
+discard """
+  outputsub: "no leak: "
+"""
+
+type
+  Module = object
+    nodes*: seq[PNode]
+    id: int
+
+  PModule = ref Module
+
+  Node = object
+    owner* {.cursor.}: PModule
+    data*: array[0..200, char] # some fat to drain memory faster
+    id: int
+
+  PNode = ref Node
+
+var
+  gid: int
+
+when false:
+  proc finalizeNode(x: PNode) =
+    echo "node id: ", x.id
+  proc finalizeModule(x: PModule) =
+    echo "module id: ", x.id
+
+proc newNode(owner: PModule): PNode =
+  new(result)
+  result.owner = owner
+  inc gid
+  result.id = gid
+
+proc compileModule: PModule =
+  new(result)
+  result.nodes = @[]
+  for i in 0..100:
+    result.nodes.add newNode(result)
+  inc gid
+  result.id = gid
+
+var gModuleCache: PModule
+
+proc loop =
+  for i in 0..1000:
+    gModuleCache = compileModule()
+    gModuleCache = nil
+    GC_fullCollect()
+
+    if getOccupiedMem() > 9_000_000:
+      echo "still a leak! ", getOccupiedMem()
+      quit(1)
+  echo "no leak: ", getOccupiedMem()
+
+loop()
+
diff --git a/tests/gc/foreign_thr.nim b/tests/gc/foreign_thr.nim
new file mode 100644
index 000000000..88ab95113
--- /dev/null
+++ b/tests/gc/foreign_thr.nim
@@ -0,0 +1,88 @@
+discard """
+  output: '''
+Hello from thread
+Hello from thread
+Hello from thread
+Hello from thread
+'''
+  cmd: "nim $target --hints:on --threads:on --tlsEmulation:off $options $file"
+"""
+# Copied from stdlib
+import strutils
+
+const
+  StackGuardSize = 4096
+  ThreadStackMask = 1024*256*sizeof(int)-1
+  ThreadStackSize = ThreadStackMask+1 - StackGuardSize
+
+type ThreadFunc = proc() {.thread.}
+
+when defined(posix):
+  import posix
+
+  proc runInForeignThread(f: ThreadFunc) =
+    proc wrapper(p: pointer): pointer {.noconv.} =
+      let thr = cast[ThreadFunc](p)
+      setupForeignThreadGc()
+      thr()
+      tearDownForeignThreadGc()
+      setupForeignThreadGc()
+      thr()
+      tearDownForeignThreadGc()
+      result = nil
+
+    var attrs {.noinit.}: PthreadAttr
+    doAssert pthread_attr_init(addr attrs) == 0
+    doAssert pthread_attr_setstacksize(addr attrs, ThreadStackSize) == 0
+    var tid: Pthread
+    doAssert pthread_create(addr tid, addr attrs, wrapper, f) == 0
+    doAssert pthread_join(tid, nil) == 0
+
+elif defined(windows):
+  import winlean
+  type
+    WinThreadProc = proc (x: pointer): int32 {.stdcall.}
+
+  proc createThread(lpThreadAttributes: pointer, dwStackSize: DWORD,
+                     lpStartAddress: WinThreadProc,
+                     lpParameter: pointer,
+                     dwCreationFlags: DWORD,
+                     lpThreadId: var DWORD): Handle {.
+    stdcall, dynlib: "kernel32", importc: "CreateThread".}
+
+  proc wrapper(p: pointer): int32 {.stdcall.} =
+    let thr = cast[ThreadFunc](p)
+    setupForeignThreadGc()
+    thr()
+    tearDownForeignThreadGc()
+    setupForeignThreadGc()
+    thr()
+    tearDownForeignThreadGc()
+    result = 0'i32
+
+  proc runInForeignThread(f: ThreadFunc) =
+    var dummyThreadId: DWORD
+    var h = createThread(nil, ThreadStackSize.int32, wrapper.WinThreadProc, cast[pointer](f), 0, dummyThreadId)
+    doAssert h != 0.Handle
+    doAssert waitForSingleObject(h, -1'i32) == 0.DWORD
+
+else:
+  {.fatal: "Unknown system".}
+
+proc runInNativeThread(f: ThreadFunc) =
+  proc wrapper(f: ThreadFunc) {.thread.} =
+    # These operations must be NOP
+    setupForeignThreadGc()
+    tearDownForeignThreadGc()
+    f()
+    f()
+  var thr: Thread[ThreadFunc]
+  createThread(thr, wrapper, f)
+  joinThread(thr)
+
+proc f {.thread.} =
+  var msg = "Hello " & "from thread"
+  echo msg
+
+runInForeignThread(f)
+runInNativeThread(f)
diff --git a/tests/gc/gcbench.nim b/tests/gc/gcbench.nim
new file mode 100644
index 000000000..e29ea762d
--- /dev/null
+++ b/tests/gc/gcbench.nim
@@ -0,0 +1,180 @@
+discard """
+  outputsub: "Success!"
+"""
+
+# This is adapted from a benchmark written by John Ellis and Pete Kovac
+# of Post Communications.
+# It was modified by Hans Boehm of Silicon Graphics.
+#
+# This is no substitute for real applications. No actual application
+# is likely to behave in exactly this way. However, this benchmark was
+# designed to be more representative of real applications than other
+# Java GC benchmarks of which we are aware.
+# It attempts to model those properties of allocation requests that
+# are important to current GC techniques.
+# It is designed to be used either to obtain a single overall performance
+# number, or to give a more detailed estimate of how collector
+# performance varies with object lifetimes. It prints the time
+# required to allocate and collect balanced binary trees of various
+# sizes. Smaller trees result in shorter object lifetimes. Each cycle
+# allocates roughly the same amount of memory.
+# Two data structures are kept around during the entire process, so
+# that the measured performance is representative of applications
+# that maintain some live in-memory data. One of these is a tree
+# containing many pointers. The other is a large array containing
+# double precision floating point numbers. Both should be of comparable
+# size.
+#
+# The results are only really meaningful together with a specification
+# of how much memory was used. It is possible to trade memory for
+# better time performance. This benchmark should be run in a 32 MB
+# heap, though we don't currently know how to enforce that uniformly.
+#
+# Unlike the original Ellis and Kovac benchmark, we do not attempt
+# measure pause times. This facility should eventually be added back
+# in. There are several reasons for omitting it for now.  The original
+# implementation depended on assumptions about the thread scheduler
+# that don't hold uniformly. The results really measure both the
+# scheduler and GC. Pause time measurements tend to not fit well with
+# current benchmark suites. As far as we know, none of the current
+# commercial Java implementations seriously attempt to minimize GC pause
+# times.
+#
+# Known deficiencies:
+# - No way to check on memory use
+# - No cyclic data structures
+# - No attempt to measure variation with object size
+# - Results are sensitive to locking cost, but we don't
+#   check for proper locking
+#
+
+import
+  strutils, times
+
+type
+  PNode = ref TNode
+  TNode {.final, acyclic.} = object
+    left, right: PNode
+    i, j: int
+
+proc newNode(L, r: sink PNode): PNode =
+  new(result)
+  result.left = L
+  result.right = r
+
+const
+  kStretchTreeDepth = 18 # about 16Mb
+  kLongLivedTreeDepth = 16  # about 4Mb
+  kArraySize = 500000  # about 4Mb
+  kMinTreeDepth = 4
+  kMaxTreeDepth = 16
+
+when not declared(withScratchRegion):
+  template withScratchRegion(body: untyped) = body
+
+# Nodes used by a tree of a given size
+proc treeSize(i: int): int = return ((1 shl (i + 1)) - 1)
+
+# Number of iterations to use for a given tree depth
+proc numIters(i: int): int =
+  return 2 * treeSize(kStretchTreeDepth) div treeSize(i)
+
+# Build tree top down, assigning to older objects.
+proc populate(iDepth: int, thisNode: PNode) =
+  if iDepth <= 0:
+    return
+  else:
+    new(thisNode.left)
+    new(thisNode.right)
+    populate(iDepth-1, thisNode.left)
+    populate(iDepth-1, thisNode.right)
+
+# Build tree bottom-up
+proc makeTree(iDepth: int): PNode =
+  if iDepth <= 0:
+    new(result)
+  else:
+    return newNode(makeTree(iDepth-1), makeTree(iDepth-1))
+
+proc printDiagnostics() =
+  echo("Total memory available: " & formatSize(getTotalMem()) & " bytes")
+  echo("Free memory: " & formatSize(getFreeMem()) & " bytes")
+
+proc timeConstruction(depth: int) =
+  var
+    root, tempTree: PNode
+    iNumIters: int
+
+  iNumIters = numIters(depth)
+
+  echo("Creating " & $iNumIters & " trees of depth " & $depth)
+  var t = epochTime()
+  for i in 0..iNumIters-1:
+    new(tempTree)
+    populate(depth, tempTree)
+    tempTree = nil
+  echo("\tTop down construction took " & $(epochTime() - t) & "msecs")
+  t = epochTime()
+  for i in 0..iNumIters-1:
+    tempTree = makeTree(depth)
+    tempTree = nil
+  echo("\tBottom up construction took " & $(epochTime() - t) & "msecs")
+
+type
+  tMyArray = seq[float]
+
+proc main() =
+  var
+    root, longLivedTree, tempTree: PNode
+    myarray: tMyArray
+
+  echo("Garbage Collector Test")
+  echo(" Stretching memory with a binary tree of depth " & $kStretchTreeDepth)
+  printDiagnostics()
+  var t = epochTime()
+
+  # Stretch the memory space quickly
+  withScratchRegion:
+    tempTree = makeTree(kStretchTreeDepth)
+    tempTree = nil
+
+  # Create a long lived object
+  echo(" Creating a long-lived binary tree of depth " &
+        $kLongLivedTreeDepth)
+  new(longLivedTree)
+  populate(kLongLivedTreeDepth, longLivedTree)
+
+  # Create long-lived array, filling half of it
+  echo(" Creating a long-lived array of " & $kArraySize & " doubles")
+  withScratchRegion:
+    newSeq(myarray, kArraySize)
+    for i in 0..kArraySize div 2 - 1:
+      myarray[i] = 1.0 / toFloat(i)
+
+    printDiagnostics()
+
+    var d = kMinTreeDepth
+    while d <= kMaxTreeDepth:
+      withScratchRegion:
+        timeConstruction(d)
+      inc(d, 2)
+
+    if longLivedTree == nil or myarray[1000] != 1.0/1000.0:
+      echo("Failed")
+      # fake reference to LongLivedTree
+      # and array to keep them from being optimized away
+
+  var elapsed = epochTime() - t
+  printDiagnostics()
+  echo("Completed in " & $elapsed & "s. Success!")
+  when declared(getMaxMem):
+    echo "Max memory ", formatSize getMaxMem()
+
+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/gcemscripten.nim b/tests/gc/gcemscripten.nim
new file mode 100644
index 000000000..cc12b230f
--- /dev/null
+++ b/tests/gc/gcemscripten.nim
@@ -0,0 +1,59 @@
+discard """
+  outputsub: "77\n77"
+"""
+
+## Check how GC/Alloc works in Emscripten
+import strutils
+
+type
+  X = ref XObj
+  XObj = object
+    name: string
+    value: int
+when defined(allow_print):
+  const print = true
+else:
+  const print = false
+
+proc myResult3*(i:int): X {.exportc.} =
+  if print: echo "3"
+  new(result)
+  if print: echo "3-2"
+  result.value = i
+
+proc myResult5*(i:int, x:X):X {.exportc.} =
+  if print: echo "5"
+  system.GC_fullCollect()
+  new(result)
+  if print: echo "5-2"
+  result.value = i
+  x.value = i+1
+  if result.value == x.value:
+    echo "This should not happen. Just allocated variable points to parameter"
+
+proc myResult2*(val: string, i: int): X {.exportc.} =
+  if print: echo "2-1"
+  result = myResult3(i)
+  if print: echo "2-2"
+  system.GC_fullCollect()
+  if print: echo "2-3"
+  var t = new(X)
+  if print: echo "2-4"
+  result.name = val
+  if t.name == "qwe":
+    echo "This should not happen. Variable is GC collected and new one on same place are allocated."
+  if print: echo "2-5"
+
+proc myResult4*(val: string, i: int): X {.exportc.} =
+  if print: echo "4-1"
+  result = myResult5(i, X())
+  if print: echo "4-2"
+
+var x = myResult2("qwe", 77)
+echo intToStr(x.value)
+
+var x2 = myResult4("qwe", 77)
+echo intToStr(x2.value)
+
+
+
diff --git a/tests/gc/gcleak.nim b/tests/gc/gcleak.nim
new file mode 100644
index 000000000..0bf993968
--- /dev/null
+++ b/tests/gc/gcleak.nim
@@ -0,0 +1,29 @@
+discard """
+  outputsub: "no leak: "
+"""
+
+when defined(GC_setMaxPause):
+  GC_setMaxPause 2_000
+
+type
+  TTestObj = object of RootObj
+    x: string
+
+proc makeObj(): TTestObj =
+  result.x = "Hello"
+
+const numIter =
+  # see tests/gc/gcleak2.nim
+  when defined(boehmgc):
+    1_000
+  elif defined(gcMarkAndSweep): 10_000
+  else: 100_000
+
+for i in 1 .. numIter:
+  when defined(gcMarkAndSweep) or defined(boehmgc):
+    GC_fullcollect()
+  var obj = makeObj()
+  if getOccupiedMem() > 300_000: quit("still a leak!")
+#  echo GC_getstatistics()
+
+echo "no leak: ", getOccupiedMem()
diff --git a/tests/gc/gcleak2.nim b/tests/gc/gcleak2.nim
new file mode 100644
index 000000000..bc943dbe7
--- /dev/null
+++ b/tests/gc/gcleak2.nim
@@ -0,0 +1,38 @@
+discard """
+  outputsub: "no leak: "
+"""
+
+when defined(GC_setMaxPause):
+  GC_setMaxPause 2_000
+
+type
+  TTestObj = object of RootObj
+    x: string
+    s: seq[int]
+
+proc makeObj(): TTestObj =
+  result.x = "Hello"
+  result.s = @[1,2,3]
+
+const numIter =
+  when defined(boehmgc):
+    # super slow because GC_fullcollect() at each iteration; especially
+    # on OSX 10.15 where it takes ~170s
+    # `getOccupiedMem` should be constant after each iteration for i >= 3
+    1_000
+  elif defined(gcMarkAndSweep):
+    # likewise, somewhat slow, 1_000_000 would run for 8s
+    # and same remark as above
+    100_000
+  else: 1_000_000
+
+proc inProc() =
+  for i in 1 .. numIter:
+    when defined(gcMarkAndSweep) or defined(boehmgc):
+      GC_fullcollect()
+    var obj: TTestObj
+    obj = makeObj()
+    if getOccupiedMem() > 300_000: quit("still a leak!")
+
+inProc()
+echo "no leak: ", getOccupiedMem()
diff --git a/tests/gc/gcleak3.nim b/tests/gc/gcleak3.nim
new file mode 100644
index 000000000..5e146d69f
--- /dev/null
+++ b/tests/gc/gcleak3.nim
@@ -0,0 +1,26 @@
+discard """
+  outputsub: "no leak: "
+"""
+
+when defined(GC_setMaxPause):
+  GC_setMaxPause 2_000
+
+type
+  TSomething = object
+    s: string
+    s1: string
+var s: seq[TSomething] = @[]
+for i in 0..1024:
+  var obj: TSomething
+  obj.s = "blah"
+  obj.s1 = "asd"
+  s.add(obj)
+
+proc limit*[t](a: var seq[t]) =
+  while s.len > 0:
+    if getOccupiedMem() > 3000_000: quit("still a leak!")
+    s.delete(0)
+
+s.limit()
+echo "no leak: ", getOccupiedMem()
+
diff --git a/tests/gc/gcleak4.nim b/tests/gc/gcleak4.nim
new file mode 100644
index 000000000..a72db67b7
--- /dev/null
+++ b/tests/gc/gcleak4.nim
@@ -0,0 +1,46 @@
+discard """
+  outputsub: "no leak: "
+"""
+
+type
+  TExpr {.inheritable.} = object ## abstract base class for an expression
+  PLiteral = ref TLiteral
+  TLiteral = object of TExpr
+    x: int
+    op1: string
+  TPlusExpr = object of TExpr
+    a, b: ref TExpr
+    op2: string
+
+method eval(e: ref TExpr): int {.base.} =
+  # override this base method
+  quit "to override!"
+
+method eval(e: ref TLiteral): int = return e.x
+
+method eval(e: ref TPlusExpr): int =
+  # watch out: relies on dynamic binding
+  return eval(e.a) + eval(e.b)
+
+proc newLit(x: int): ref TLiteral =
+  new(result)
+  result.x = x
+  result.op1 = $getOccupiedMem()
+
+proc newPlus(a, b: sink(ref TExpr)): ref TPlusExpr =
+  new(result)
+  result.a = a
+  result.b = b
+  result.op2 = $getOccupiedMem()
+
+const Limit = when compileOption("gc", "markAndSweep") or compileOption("gc", "boehm"): 5*1024*1024 else: 500_000
+
+for i in 0..50_000:
+  var s: array[0..11, ref TExpr]
+  for j in 0..high(s):
+    s[j] = newPlus(newPlus(newLit(j), newLit(2)), newLit(4))
+    if eval(s[j]) != j+6:
+      quit "error: wrong result"
+  if getOccupiedMem() > Limit: quit("still a leak!")
+
+echo "no leak: ", getOccupiedMem()
diff --git a/tests/gc/gcleak5.nim b/tests/gc/gcleak5.nim
new file mode 100644
index 000000000..f1913831b
--- /dev/null
+++ b/tests/gc/gcleak5.nim
@@ -0,0 +1,25 @@
+discard """
+  output: "success"
+"""
+
+import os, times
+
+proc main =
+  var i = 0
+  for ii in 0..50_000:
+    #while true:
+    var t = getTime()
+    var g = t.utc()
+    #echo isOnStack(addr g)
+
+    if i mod 100 == 0:
+      let om = getOccupiedMem()
+      #echo "memory: ", om
+      if om > 100_000: quit "leak"
+
+    inc(i)
+    sleep(1)
+
+  echo "success"
+
+main()
diff --git a/tests/gc/gctest.nim b/tests/gc/gctest.nim
new file mode 100644
index 000000000..78b78934c
--- /dev/null
+++ b/tests/gc/gctest.nim
@@ -0,0 +1,210 @@
+discard """
+  outputsub: "finished"
+"""
+
+# Test the garbage collector.
+
+import
+  strutils
+
+type
+  PNode = ref TNode
+  TNode {.final.} = object
+    le, ri: PNode
+    data: string
+
+  TTable {.final.} = object
+    counter, max: int
+    data: seq[string]
+
+  TBNode {.final.} = object
+    other: PNode  # a completely different tree
+    data: string
+    sons: seq[TBNode] # directly embedded!
+    t: TTable
+
+  TCaseKind = enum nkStr, nkWhole, nkList
+  PCaseNode = ref TCaseNode
+  TCaseNode {.final.} = object
+    case kind: TCaseKind
+    of nkStr: data: string
+    of nkList: sons: seq[PCaseNode]
+    else: unused: seq[string]
+
+  TIdObj* = object of RootObj
+    id*: int  # unique id; use this for comparisons and not the pointers
+
+  PIdObj* = ref TIdObj
+  PIdent* = ref TIdent
+  TIdent*{.acyclic.} = object of TIdObj
+    s*: string
+    next*: PIdent             # for hash-table chaining
+    h*: int                   # hash value of s
+
+var
+  flip: int
+
+proc newCaseNode(data: string): PCaseNode =
+  if flip == 0:
+    result = PCaseNode(kind: nkStr, data: data)
+  else:
+    result = PCaseNode(kind: nkWhole, unused: @["", "abc", "abdc"])
+  flip = 1 - flip
+
+proc newCaseNode(a, b: PCaseNode): PCaseNode =
+  result = PCaseNode(kind: nkList, sons: @[a, b])
+
+proc caseTree(lvl: int = 0): PCaseNode =
+  if lvl == 3: result = newCaseNode("data item")
+  else: result = newCaseNode(caseTree(lvl+1), caseTree(lvl+1))
+
+proc finalizeNode(n: PNode) =
+  assert(n != nil)
+  write(stdout, "finalizing: ")
+  writeLine(stdout, "not nil")
+
+var
+  id: int = 1
+
+proc buildTree(depth = 1): PNode =
+  if depth == 7: return nil
+  new(result, finalizeNode)
+  result.le = buildTree(depth+1)
+  result.ri = buildTree(depth+1)
+  result.data = $id
+  inc(id)
+
+proc returnTree(): PNode =
+  writeLine(stdout, "creating id: " & $id)
+  new(result, finalizeNode)
+  result.data = $id
+  new(result.le, finalizeNode)
+  result.le.data = $id & ".1"
+  new(result.ri, finalizeNode)
+  result.ri.data = $id & ".2"
+  inc(id)
+
+  # now create a cycle:
+  writeLine(stdout, "creating id (cyclic): " & $id)
+  var cycle: PNode
+  new(cycle, finalizeNode)
+  cycle.data = $id
+  cycle.le = cycle
+  cycle.ri = cycle
+  inc(id)
+  #writeLine(stdout, "refcount: " & $refcount(cycle))
+  #writeLine(stdout, "refcount le: " & $refcount(cycle.le))
+  #writeLine(stdout, "refcount ri: " & $refcount(cycle.ri))
+
+proc printTree(t: PNode) =
+  if t == nil: return
+  writeLine(stdout, "printing")
+  writeLine(stdout, t.data)
+  printTree(t.le)
+  printTree(t.ri)
+
+proc unsureNew(result: var PNode) =
+  writeLine(stdout, "creating unsure id: " & $id)
+  new(result, finalizeNode)
+  result.data = $id
+  new(result.le, finalizeNode)
+  result.le.data = $id & ".a"
+  new(result.ri, finalizeNode)
+  result.ri.data = $id & ".b"
+  inc(id)
+
+proc setSons(n: var TBNode) =
+  n.sons = @[] # free memory of the sons
+  n.t.data = @[]
+  var
+    m: seq[string]
+  m = @[]
+  setLen(m, len(n.t.data) * 2)
+  for i in 0..high(m):
+    m[i] = "..."
+  n.t.data = m
+
+proc buildBTree(father: var TBNode) =
+  father.data = "father"
+  father.other = nil
+  father.sons = @[]
+  for i in 1..10:
+    write(stdout, "next iteration!\n")
+    var n: TBNode
+    n.other = returnTree()
+    n.data = "B node: " & $i
+    if i mod 2 == 0: n.sons = @[] # nil and [] need to be handled correctly!
+    add father.sons, n
+    father.t.counter = 0
+    father.t.max = 3
+    father.t.data = @["ha", "lets", "stress", "it"]
+  setSons(father)
+
+proc getIdent(identifier: cstring, length: int, h: int): PIdent =
+  new(result)
+  result.h = h
+  result.s = newString(length)
+
+proc main() =
+  discard getIdent("addr", 4, 0)
+  discard getIdent("hall", 4, 0)
+  discard getIdent("echo", 4, 0)
+  discard getIdent("huch", 4, 0)
+
+  var
+    father: TBNode
+  for i in 1..1_00:
+    buildBTree(father)
+
+  for i in 1..1_00:
+    var t = returnTree()
+    var t2: PNode
+    unsureNew(t2)
+  write(stdout, "now building bigger trees: ")
+  var t2: PNode
+  for i in 1..100:
+    t2 = buildTree()
+  printTree(t2)
+  write(stdout, "now test sequences of strings:")
+  var s: seq[string] = @[]
+  for i in 1..100:
+    add s, "hohoho" # test reallocation
+  writeLine(stdout, s[89])
+  write(stdout, "done!\n")
+
+var
+  father {.threadvar.}: TBNode
+  s {.threadvar.}: string
+
+  fatherAsGlobal: TBNode
+
+proc start =
+  s = ""
+  s = ""
+  writeLine(stdout, repr(caseTree()))
+  father.t.data = @["ha", "lets", "stress", "it"]
+  father.t.data = @["ha", "lets", "stress", "it"]
+  var t = buildTree()
+  write(stdout, repr(t[]))
+  buildBTree(father)
+  write(stdout, repr(father))
+
+  write(stdout, "starting main...\n")
+  main()
+
+  GC_fullCollect()
+  # the M&S GC fails with this call and it's unclear why. Definitely something
+  # we need to fix!
+  #GC_fullCollect()
+  writeLine(stdout, GC_getStatistics())
+  write(stdout, "finished\n")
+
+fatherAsGlobal.t.data = @["ha", "lets", "stress", "it"]
+var tg = buildTree()
+buildBTree(fatherAsGlobal)
+
+var thr: array[8, Thread[void]]
+for i in low(thr)..high(thr):
+  createThread(thr[i], start)
+joinThreads(thr)
+start()
diff --git a/tests/gc/gctest.nim.cfg b/tests/gc/gctest.nim.cfg
new file mode 100644
index 000000000..aed303eef
--- /dev/null
+++ b/tests/gc/gctest.nim.cfg
@@ -0,0 +1 @@
+--threads:on
diff --git a/tests/gc/growobjcrash.nim b/tests/gc/growobjcrash.nim
new file mode 100644
index 000000000..ff1aa7e98
--- /dev/null
+++ b/tests/gc/growobjcrash.nim
@@ -0,0 +1,24 @@
+import std/[cgi, strtabs]
+
+proc handleRequest(query: string): StringTableRef =
+  iterator foo(): StringTableRef {.closure.} =
+    var params = {:}.newStringTable()
+    for key, val in cgi.decodeData(query):
+      params[key] = val
+    yield params
+
+  let x = foo
+  result = x()
+
+const Limit = 5*1024*1024
+
+proc main =
+  var counter = 0
+  for i in 0 .. 10_000:
+    for k, v in handleRequest("nick=Elina2&type=activate"):
+      inc counter
+      if counter mod 100 == 0:
+        if getOccupiedMem() > Limit:
+          quit "but now a leak"
+
+main()
diff --git a/tests/gc/panicoverride.nim b/tests/gc/panicoverride.nim
new file mode 100644
index 000000000..0f28b0b72
--- /dev/null
+++ b/tests/gc/panicoverride.nim
@@ -0,0 +1,14 @@
+
+proc printf(frmt: cstring) {.varargs, importc, header: "<stdio.h>", cdecl.}
+proc exit(code: int) {.importc, header: "<stdlib.h>", cdecl.}
+
+{.push stack_trace: off, profiler:off.}
+
+proc rawoutput(s: string) =
+  printf("%s\n", s)
+
+proc panic(s: string) {.noreturn.} =
+  rawoutput(s)
+  exit(1)
+
+{.pop.}
\ No newline at end of file
diff --git a/tests/gc/refarrayleak.nim b/tests/gc/refarrayleak.nim
new file mode 100644
index 000000000..57b489721
--- /dev/null
+++ b/tests/gc/refarrayleak.nim
@@ -0,0 +1,39 @@
+discard """
+  outputsub: "no leak: "
+"""
+
+type
+  TNode = object
+    data: array[0..300, char]
+
+  PNode = ref TNode
+
+  TNodeArray = array[0..10, PNode]
+
+  TArrayHolder = object
+    sons: TNodeArray
+
+proc nullify(a: var TNodeArray) =
+  for i in 0..high(a):
+    a[i] = nil
+
+proc newArrayHolder: ref TArrayHolder =
+  new result
+
+  for i in 0..high(result.sons):
+    new result.sons[i]
+
+  nullify result.sons
+
+proc loop =
+  for i in 0..10000:
+    discard newArrayHolder()
+
+  if getOccupiedMem() > 300_000:
+    echo "still a leak! ", getOccupiedMem()
+    quit 1
+  else:
+    echo "no leak: ", getOccupiedMem()
+
+loop()
+
diff --git a/tests/gc/stackrefleak.nim b/tests/gc/stackrefleak.nim
new file mode 100644
index 000000000..7f3fbff43
--- /dev/null
+++ b/tests/gc/stackrefleak.nim
@@ -0,0 +1,32 @@
+discard """
+  outputsub: "no leak: "
+"""
+
+type
+  Cyclic = object
+    sibling: PCyclic
+    data: array[0..200, char]
+
+  PCyclic = ref Cyclic
+
+proc makePair: PCyclic =
+  new(result)
+  new(result.sibling)
+  when not defined(gcDestructors):
+    result.sibling.sibling = result
+
+proc loop =
+  for i in 0..10000:
+    var x = makePair()
+    GC_fullCollect()
+    x = nil
+    GC_fullCollect()
+
+  if getOccupiedMem() > 300_000:
+    echo "still a leak! ", getOccupiedMem()
+    quit(1)
+  else:
+    echo "no leak: ", getOccupiedMem()
+
+loop()
+
diff --git a/tests/gc/tdisable_orc.nim b/tests/gc/tdisable_orc.nim
new file mode 100644
index 000000000..b5f161c79
--- /dev/null
+++ b/tests/gc/tdisable_orc.nim
@@ -0,0 +1,9 @@
+discard """
+  joinable: false
+"""
+
+import std/asyncdispatch
+
+# bug #22256
+GC_disableMarkAndSweep()
+waitFor sleepAsync(1000)
diff --git a/tests/gc/thavlak.nim b/tests/gc/thavlak.nim
new file mode 100644
index 000000000..cfd860e25
--- /dev/null
+++ b/tests/gc/thavlak.nim
@@ -0,0 +1,441 @@
+discard """
+  output: '''Welcome to LoopTesterApp, Nim edition
+Constructing Simple CFG...
+5000 dummy loops
+Constructing CFG...
+Performing Loop Recognition
+1 Iteration
+Another 3 iterations...
+...
+Found 1 loops (including artificial root node) (3)'''
+"""
+
+# bug #3184
+
+import tables, sets
+
+when not declared(withScratchRegion):
+  template withScratchRegion(body: untyped) = body
+
+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
+  )
+
+proc hash(x: BasicBlock): int {.inline.} =
+  result = x.name
+
+type
+  BasicBlockEdge = object
+    fr: BasicBlock
+    to: BasicBlock
+
+  Cfg = object
+    basicBlockMap: Table[int, BasicBlock]
+    edgeList: seq[BasicBlockEdge]
+    startNode: BasicBlock
+
+proc newCfg(): Cfg =
+  result = Cfg(
+    basicBlockMap: initTable[int, BasicBlock](),
+    edgeList: newSeq[BasicBlockEdge](),
+    startNode: nil)
+
+proc createNode(self: var Cfg, name: int): BasicBlock =
+  result = self.basicBlockMap.getOrDefault(name)
+  if result == nil:
+    result = newBasicBlock(name)
+    self.basicBlockMap.add name, result
+
+  if self.startNode == nil:
+    self.startNode = result
+
+proc newBasicBlockEdge(cfg: var Cfg, fromName, toName: int) =
+  var result = BasicBlockEdge(
+    fr: cfg.createNode(fromName),
+    to: cfg.createNode(toName)
+  )
+  result.fr.outEdges.add(result.to)
+  result.to.inEdges.add(result.fr)
+  cfg.edgeList.add(result)
+
+type
+  SimpleLoop = ref object
+    basicBlocks: seq[BasicBlock] # TODO: set here
+    children: seq[SimpleLoop] # TODO: set here
+    parent: SimpleLoop
+    header: BasicBlock
+    isRoot, isReducible: bool
+    counter, nestingLevel, depthLevel: int
+
+proc setParent(self: SimpleLoop, parent: SimpleLoop) =
+  self.parent = parent
+  self.parent.children.add self
+
+proc setHeader(self: SimpleLoop, bb: BasicBlock) =
+  self.basicBlocks.add(bb)
+  self.header = bb
+
+proc setNestingLevel(self: SimpleLoop, level: int) =
+  self.nestingLevel = level
+  if level == 0: self.isRoot = true
+
+var loopCounter: int = 0
+
+type
+  Lsg = object
+    loops: seq[SimpleLoop]
+    root: SimpleLoop
+
+proc createNewLoop(self: var Lsg): SimpleLoop =
+  result = SimpleLoop(
+    basicBlocks: newSeq[BasicBlock](),
+    children: newSeq[SimpleLoop](),
+    isReducible: true)
+  loopCounter += 1
+  result.counter = loopCounter
+
+proc addLoop(self: var Lsg, l: SimpleLoop) =
+  self.loops.add l
+
+proc newLsg(): Lsg =
+  result = Lsg(loops: newSeq[SimpleLoop](),
+    root: result.createNewLoop())
+  result.root.setNestingLevel(0)
+  result.addLoop(result.root)
+
+type
+  UnionFindNode = ref object
+    parent {.cursor.}: UnionFindNode
+    bb: BasicBlock
+    l: SimpleLoop
+    dfsNumber: int
+
+proc initNode(self: UnionFindNode, bb: BasicBlock, dfsNumber: int) =
+  self.parent = self
+  self.bb = bb
+  self.dfsNumber = dfsNumber
+
+proc findSet(self: UnionFindNode): UnionFindNode =
+  var nodeList = newSeq[UnionFindNode]()
+  var it {.cursor.} = self
+
+  while it != it.parent:
+    var parent {.cursor.} = it.parent
+    if parent != parent.parent: nodeList.add it
+    it = parent
+
+  for iter in nodeList: iter.parent = it.parent
+  result = it
+
+proc union(self: UnionFindNode, unionFindNode: UnionFindNode) =
+  self.parent = unionFindNode
+
+
+const
+  BB_NONHEADER = 1 # a regular BB
+  BB_REDUCIBLE = 2 # reducible loop
+  BB_SELF = 3 # single BB loop
+  BB_IRREDUCIBLE = 4 # irreducible loop
+  BB_DEAD = 5 # a dead BB
+
+  # # Marker for uninitialized nodes.
+  UNVISITED = -1
+
+  # # Safeguard against pathologic algorithm behavior.
+  MAXNONBACKPREDS = (32 * 1024)
+
+type
+  HavlakLoopFinder = object
+    cfg: Cfg
+    lsg: Lsg
+
+proc newHavlakLoopFinder(cfg: Cfg, lsg: sink Lsg): HavlakLoopFinder =
+  result = HavlakLoopFinder(cfg: cfg, lsg: lsg)
+
+proc isAncestor(w, v: int, last: seq[int]): bool =
+  w <= v and v <= last[w]
+
+proc dfs(currentNode: BasicBlock, nodes: var seq[UnionFindNode],
+         number: var Table[BasicBlock, int],
+         last: var seq[int], current: int) =
+  var stack = @[(currentNode, current)]
+  while stack.len > 0:
+    let (currentNode, current) = stack.pop()
+    nodes[current].initNode(currentNode, current)
+    number[currentNode] = current
+
+    for target in currentNode.outEdges:
+      if number[target] == UNVISITED:
+        stack.add((target, current+1))
+        #result = dfs(target, nodes, number, last, result + 1)
+  last[number[currentNode]] = current
+
+proc findLoops(self: var HavlakLoopFinder): int =
+  var startNode = self.cfg.startNode
+  if startNode == nil: return 0
+  var size = self.cfg.basicBlockMap.len
+
+  var nonBackPreds = newSeq[HashSet[int]]()
+  var backPreds = newSeq[seq[int]]()
+  var number = initTable[BasicBlock, int]()
+  var header = newSeq[int](size)
+  var types = newSeq[int](size)
+  var last = newSeq[int](size)
+  var nodes = newSeq[UnionFindNode]()
+
+  for i in 1..size:
+    nonBackPreds.add initHashSet[int](1)
+    backPreds.add newSeq[int]()
+    nodes.add(UnionFindNode())
+
+  # Step a:
+  #   - initialize all nodes as unvisited.
+  #   - depth-first traversal and numbering.
+  #   - unreached BB's are marked as dead.
+  #
+  for v in self.cfg.basicBlockMap.values: number[v] = UNVISITED
+  dfs(startNode, nodes, number, last, 0)
+
+  # Step b:
+  #   - iterate over all nodes.
+  #
+  #   A backedge comes from a descendant in the DFS tree, and non-backedges
+  #   from non-descendants (following Tarjan).
+  #
+  #   - check incoming edges 'v' and add them to either
+  #     - the list of backedges (backPreds) or
+  #     - the list of non-backedges (nonBackPreds)
+  #
+  for w in 0 ..< size:
+    header[w] = 0
+    types[w]  = BB_NONHEADER
+
+    var nodeW = nodes[w].bb
+    if nodeW != nil:
+      for nodeV in nodeW.inEdges:
+        var v = number[nodeV]
+        if v != UNVISITED:
+          if isAncestor(w, v, last):
+            backPreds[w].add v
+          else:
+            nonBackPreds[w].incl v
+    else:
+      types[w] = BB_DEAD
+
+  # Start node is root of all other loops.
+  header[0] = 0
+
+  # Step c:
+  #
+  # The outer loop, unchanged from Tarjan. It does nothing except
+  # for those nodes which are the destinations of backedges.
+  # For a header node w, we chase backward from the sources of the
+  # backedges adding nodes to the set P, representing the body of
+  # the loop headed by w.
+  #
+  # By running through the nodes in reverse of the DFST preorder,
+  # we ensure that inner loop headers will be processed before the
+  # headers for surrounding loops.
+
+  for w in countdown(size - 1, 0):
+    # this is 'P' in Havlak's paper
+    var nodePool = newSeq[UnionFindNode]()
+
+    var nodeW = nodes[w].bb
+    if nodeW != nil: # dead BB
+      # Step d:
+      for v in backPreds[w]:
+        if v != w:
+          nodePool.add nodes[v].findSet
+        else:
+          types[w] = BB_SELF
+
+      # Copy nodePool to workList.
+      #
+      var workList = newSeq[UnionFindNode]()
+      for x in nodePool: workList.add x
+
+      if nodePool.len != 0: types[w] = BB_REDUCIBLE
+
+      # work the list...
+      #
+      while workList.len > 0:
+        let x = workList[0]
+        workList.del(0)
+
+        # Step e:
+        #
+        # Step e represents the main difference from Tarjan's method.
+        # Chasing upwards from the sources of a node w's backedges. If
+        # there is a node y' that is not a descendant of w, w is marked
+        # the header of an irreducible loop, there is another entry
+        # into this loop that avoids w.
+        #
+
+        # The algorithm has degenerated. Break and
+        # return in this case.
+        #
+        var nonBackSize = nonBackPreds[x.dfsNumber].len
+        if nonBackSize > MAXNONBACKPREDS: return 0
+
+        for iter in nonBackPreds[x.dfsNumber]:
+          var y = nodes[iter]
+          var ydash = y.findSet
+
+          if not isAncestor(w, ydash.dfsNumber, last):
+            types[w] = BB_IRREDUCIBLE
+            nonBackPreds[w].incl ydash.dfsNumber
+          else:
+            if ydash.dfsNumber != w and not nodePool.contains(ydash):
+              workList.add ydash
+              nodePool.add ydash
+
+      # Collapse/Unionize nodes in a SCC to a single node
+      # For every SCC found, create a loop descriptor and link it in.
+      #
+      if nodePool.len > 0 or types[w] == BB_SELF:
+        var l = self.lsg.createNewLoop
+
+        l.setHeader(nodeW)
+        l.isReducible = types[w] != BB_IRREDUCIBLE
+
+        # At this point, one can set attributes to the loop, such as:
+        #
+        # the bottom node:
+        #    iter  = backPreds(w).begin();
+        #    loop bottom is: nodes(iter).node;
+        #
+        # the number of backedges:
+        #    backPreds(w).size()
+        #
+        # whether this loop is reducible:
+        #    types(w) != BB_IRREDUCIBLE
+        #
+        nodes[w].l = l
+
+        for node in nodePool:
+          # Add nodes to loop descriptor.
+          header[node.dfsNumber] = w
+          node.union(nodes[w])
+
+          # Nested loops are not added, but linked together.
+          var nodeL = node.l
+          if nodeL != nil:
+            nodeL.setParent(l)
+          else:
+            l.basicBlocks.add node.bb
+
+        self.lsg.addLoop(l)
+
+  result = self.lsg.loops.len
+
+
+type
+  LoopTesterApp = object
+    cfg: Cfg
+    lsg: Lsg
+
+proc newLoopTesterApp(): LoopTesterApp =
+  result.cfg = newCfg()
+  result.lsg = newLsg()
+
+proc buildDiamond(self: var LoopTesterApp, start: int): int =
+  newBasicBlockEdge(self.cfg, start, start + 1)
+  newBasicBlockEdge(self.cfg, start, start + 2)
+  newBasicBlockEdge(self.cfg, start + 1, start + 3)
+  newBasicBlockEdge(self.cfg, start + 2, start + 3)
+  result = start + 3
+
+proc buildConnect(self: var LoopTesterApp, start1, end1: int) =
+  newBasicBlockEdge(self.cfg, start1, end1)
+
+proc buildStraight(self: var LoopTesterApp, start, n: int): int =
+  for i in 0..n-1:
+    self.buildConnect(start + i, start + i + 1)
+  result = start + n
+
+proc buildBaseLoop(self: var LoopTesterApp, from1: int): int =
+  let header = self.buildStraight(from1, 1)
+  let diamond1 = self.buildDiamond(header)
+  let d11 = self.buildStraight(diamond1, 1)
+  let diamond2 = self.buildDiamond(d11)
+  let footer = self.buildStraight(diamond2, 1)
+
+  self.buildConnect(diamond2, d11)
+  self.buildConnect(diamond1, header)
+  self.buildConnect(footer, from1)
+  result = self.buildStraight(footer, 1)
+
+proc run(self: var LoopTesterApp) =
+  echo "Welcome to LoopTesterApp, Nim edition"
+  echo "Constructing Simple CFG..."
+
+  discard self.cfg.createNode(0)
+  discard self.buildBaseLoop(0)
+  discard self.cfg.createNode(1)
+  self.buildConnect(0, 2)
+
+  echo "5000 dummy loops"
+
+  for i in 1..5000:
+    withScratchRegion:
+      var h = newHavlakLoopFinder(self.cfg, newLsg())
+      discard h.findLoops
+
+  echo "Constructing CFG..."
+  var n = 2
+
+  when true: # 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"
+
+  var h = newHavlakLoopFinder(self.cfg, newLsg())
+  var loops = h.findLoops
+
+  echo "Another 3 iterations..."
+
+  var sum = 0
+  for i in 1..3:
+    withScratchRegion:
+      write stdout, "."
+      flushFile(stdout)
+      var hlf = newHavlakLoopFinder(self.cfg, newLsg())
+      sum += hlf.findLoops
+      #echo getOccupiedMem()
+  echo "\nFound ", loops, " loops (including artificial root node) (", sum, ")"
+
+  when false:
+    echo("Total memory available: " & formatSize(getTotalMem()) & " bytes")
+    echo("Free memory: " & formatSize(getFreeMem()) & " bytes")
+
+proc main =
+  var l = newLoopTesterApp()
+  l.run
+
+let mem = getOccupiedMem()
+main()
+when defined(gcOrc):
+  GC_fullCollect()
+  doAssert getOccupiedMem() == mem
diff --git a/tests/gc/tlists.nim b/tests/gc/tlists.nim
new file mode 100644
index 000000000..959cc5f7c
--- /dev/null
+++ b/tests/gc/tlists.nim
@@ -0,0 +1,35 @@
+discard """
+    output: '''Success'''
+"""
+
+# bug #3793
+
+import os
+import math
+import lists
+import strutils
+
+proc mkleak() =
+    # allocate 1 MB via linked lists
+    let numberOfLists = 100
+    for i in countUp(1, numberOfLists):
+        var leakList = initDoublyLinkedList[string]()
+        let numberOfLeaks = 5000
+        for j in countUp(1, numberOfLeaks):
+            leakList.append(newString(200))
+
+proc mkManyLeaks() =
+    for i in 0..0:
+        when false: echo getOccupiedMem()
+        mkleak()
+        when false: echo getOccupiedMem()
+        # Force a full collection. This should free all of the
+        # lists and bring the memory usage down to a few MB's.
+        GC_fullCollect()
+        when false: echo getOccupiedMem()
+        if getOccupiedMem() > 8 * 200 * 5000 * 2:
+          echo GC_getStatistics()
+          quit "leaking"
+    echo "Success"
+
+mkManyLeaks()
diff --git a/tests/gc/trace_globals.nim b/tests/gc/trace_globals.nim
new file mode 100644
index 000000000..f62a15692
--- /dev/null
+++ b/tests/gc/trace_globals.nim
@@ -0,0 +1,33 @@
+discard """
+  output: '''
+10000000
+10000000
+10000000'''
+"""
+
+# bug #17085
+
+#[
+refs https://github.com/nim-lang/Nim/issues/17085#issuecomment-786466595
+with --gc:boehm, this warning sometimes gets generated:
+Warning: Repeated allocation of very large block (appr. size 14880768):
+May lead to memory leak and poor performance.
+nim CI now runs this test with `testWithoutBoehm` to avoid running it with --gc:boehm.
+]#
+
+proc init(): string =
+  for a in 0..<10000000:
+    result.add 'c'
+
+proc f() =
+  var a {.global.} = init()
+  var b {.global.} = init()
+  var c {.global.} = init()
+
+  echo a.len
+    # `echo` intentional according to
+    # https://github.com/nim-lang/Nim/pull/17469/files/0c9e94cb6b9ebca9da7cb19a063fba7aa409748e#r600016573
+  echo b.len
+  echo c.len
+
+f()
diff --git a/tests/gc/tregionleak.nim b/tests/gc/tregionleak.nim
new file mode 100644
index 000000000..277cfc987
--- /dev/null
+++ b/tests/gc/tregionleak.nim
@@ -0,0 +1,23 @@
+discard """
+  cmd: '''nim c --gc:regions $file'''
+  output: '''
+finalized
+finalized
+'''
+"""
+
+proc finish(o: RootRef) =
+  echo "finalized"
+
+withScratchRegion:
+  var test: RootRef
+  new(test, finish)
+
+var
+  mr: MemRegion
+  test: RootRef
+
+withRegion(mr):
+  new(test, finish)
+
+deallocAll(mr)
diff --git a/tests/gc/tstandalone.nim b/tests/gc/tstandalone.nim
new file mode 100644
index 000000000..41dad9ba4
--- /dev/null
+++ b/tests/gc/tstandalone.nim
@@ -0,0 +1,14 @@
+discard """
+  matrix: "--os:standalone --gc:none"
+  exitcode: 1
+  output: "value out of range"
+"""
+
+type
+  rangeType = range[0..1]
+
+var
+  r: rangeType = 0
+  i = 2
+
+r = rangeType(i)
diff --git a/tests/gc/weakrefs.nim b/tests/gc/weakrefs.nim
new file mode 100644
index 000000000..81c048d74
--- /dev/null
+++ b/tests/gc/weakrefs.nim
@@ -0,0 +1,57 @@
+discard """
+  output: "true"
+"""
+
+import intsets
+
+type
+  TMyObject = object
+    id: int
+  StrongObject = ref TMyObject
+  WeakObject = object
+    id: int
+    data: ptr TMyObject
+
+var
+  gid: int # for id generation
+  valid = initIntSet()
+
+proc finalizer(x: StrongObject) =
+  valid.excl(x.id)
+
+when defined(gcDestructors):
+  proc `=destroy`(x: var TMyObject) =
+    valid.excl(x.id)
+
+proc create: StrongObject =
+  when defined(gcDestructors):
+    new(result)
+  else:
+    new(result, finalizer)
+  result.id = gid
+  valid.incl(gid)
+  inc gid
+
+proc register(s: StrongObject): WeakObject =
+  result.data = cast[ptr TMyObject](s)
+  result.id = s.id
+
+proc access(w: WeakObject): StrongObject =
+  ## returns nil if the object doesn't exist anymore
+  if valid.contains(w.id):
+    result = cast[StrongObject](w.data)
+
+proc main =
+  var s: seq[WeakObject]
+  newSeq(s, 10_000)
+  for i in 0 .. s.high:
+    s[i] = register(create())
+  # test that we have at least 80% unreachable weak objects by now:
+  when defined(gcMarkAndSweep):
+    GC_fullcollect()
+  var unreachable = 0
+  for i in 0 .. s.high:
+    if access(s[i]) == nil: inc unreachable
+  echo unreachable > 8_000
+
+main()