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/closureleak.nim41
-rw-r--r--tests/gc/cyclecollector.nim6
-rw-r--r--tests/gc/cycleleak.nim2
-rw-r--r--tests/gc/foreign_thr.nim88
-rw-r--r--tests/gc/gcbench.nim86
-rw-r--r--tests/gc/gcemscripten.nim2
-rw-r--r--tests/gc/gcleak.nim18
-rw-r--r--tests/gc/gcleak2.nim24
-rw-r--r--tests/gc/gcleak3.nim8
-rw-r--r--tests/gc/gcleak4.nim15
-rw-r--r--tests/gc/gcleak5.nim2
-rw-r--r--tests/gc/gctest.nim72
-rw-r--r--tests/gc/gctest.nim.cfg1
-rw-r--r--tests/gc/growobjcrash.nim11
-rw-r--r--tests/gc/panicoverride.nim14
-rw-r--r--tests/gc/stackrefleak.nim3
-rw-r--r--tests/gc/tdisable_orc.nim9
-rw-r--r--tests/gc/thavlak.nim346
-rw-r--r--tests/gc/tlists.nim10
-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.nim9
23 files changed, 526 insertions, 311 deletions
diff --git a/tests/gc/closureleak.nim b/tests/gc/closureleak.nim
index 18d320bdf..e67beb513 100644
--- a/tests/gc/closureleak.nim
+++ b/tests/gc/closureleak.nim
@@ -1,30 +1,49 @@
 discard """
   outputsub: "true"
+  disabled: "32bit"
 """
 
-from strutils import join
-
 type
-  TFoo * = object
+  TFoo* = object
     id: int
-    fn: proc(){.closure.}
+    fn: proc() {.closure.}
 var foo_counter = 0
 var alive_foos = newseq[int](0)
 
-proc free*(some: ref TFoo) =
-  #echo "Tfoo #", some.id, " freed"
-  alive_foos.del alive_foos.find(some.id)
+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 =
-  new result, free
+  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:
+  discard newFoo()
 
-for i in 0 .. <10:
+for i in 0 ..< 10:
   let f = newFoo()
   f.fn = proc =
     echo f.id
diff --git a/tests/gc/cyclecollector.nim b/tests/gc/cyclecollector.nim
index 46fed6c45..2d02a7a3c 100644
--- a/tests/gc/cyclecollector.nim
+++ b/tests/gc/cyclecollector.nim
@@ -9,13 +9,15 @@ type
 proc createCycle(leaf: string): Node =
   new result
   result.a = result
-  shallowCopy result.leaf, leaf
+  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)
-  echo "done ", getOccupiedMem()
 
 main()
diff --git a/tests/gc/cycleleak.nim b/tests/gc/cycleleak.nim
index 9f5c30ebd..e355abc96 100644
--- a/tests/gc/cycleleak.nim
+++ b/tests/gc/cycleleak.nim
@@ -10,7 +10,7 @@ type
   PModule = ref Module
 
   Node = object
-    owner*: PModule
+    owner* {.cursor.}: PModule
     data*: array[0..200, char] # some fat to drain memory faster
     id: int
 
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
index 782daf793..e29ea762d 100644
--- a/tests/gc/gcbench.nim
+++ b/tests/gc/gcbench.nim
@@ -44,7 +44,7 @@ discard """
 # - 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 dont
+# - Results are sensitive to locking cost, but we don't
 #   check for proper locking
 #
 
@@ -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
@@ -65,55 +65,58 @@ proc newNode(L, r: PNode): PNode =
 const
   kStretchTreeDepth = 18 # about 16Mb
   kLongLivedTreeDepth = 16  # about 4Mb
-  kArraySize  = 500000  # 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)
+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)
+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) =
+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)
+    populate(iDepth-1, thisNode.left)
+    populate(iDepth-1, thisNode.right)
 
 # Build tree bottom-up
-proc MakeTree(iDepth: int): PNode =
+proc makeTree(iDepth: int): PNode =
   if iDepth <= 0:
     new(result)
   else:
-    return newNode(MakeTree(iDepth-1), MakeTree(iDepth-1))
+    return newNode(makeTree(iDepth-1), makeTree(iDepth-1))
 
-proc PrintDiagnostics() =
-  echo("Total memory available: " & $getTotalMem() & " bytes")
-  echo("Free memory: " & $getFreeMem() & " bytes")
+proc printDiagnostics() =
+  echo("Total memory available: " & formatSize(getTotalMem()) & " bytes")
+  echo("Free memory: " & formatSize(getFreeMem()) & " bytes")
 
-proc TimeConstruction(depth: int) =
+proc timeConstruction(depth: int) =
   var
     root, tempTree: PNode
     iNumIters: int
 
-  iNumIters = NumIters(depth)
+  iNumIters = numIters(depth)
 
   echo("Creating " & $iNumIters & " trees of depth " & $depth)
   var t = epochTime()
   for i in 0..iNumIters-1:
     new(tempTree)
-    Populate(depth, 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 = makeTree(depth)
     tempTree = nil
   echo("\tBottom up construction took " & $(epochTime() - t) & "msecs")
 
@@ -127,42 +130,51 @@ proc main() =
 
   echo("Garbage Collector Test")
   echo(" Stretching memory with a binary tree of depth " & $kStretchTreeDepth)
-  PrintDiagnostics()
+  printDiagnostics()
   var t = epochTime()
 
   # Stretch the memory space quickly
-  tempTree = MakeTree(kStretchTreeDepth)
-  tempTree = nil
+  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)
+  populate(kLongLivedTreeDepth, longLivedTree)
 
   # Create long-lived array, filling half of it
   echo(" Creating a long-lived array of " & $kArraySize & " doubles")
-  newSeq(myarray, kArraySize)
-  for i in 0..kArraySize div 2 - 1:
-    myarray[i] = 1.0 / toFloat(i)
+  withScratchRegion:
+    newSeq(myarray, kArraySize)
+    for i in 0..kArraySize div 2 - 1:
+      myarray[i] = 1.0 / toFloat(i)
 
-  PrintDiagnostics()
+    printDiagnostics()
 
-  var d = kMinTreeDepth
-  while d <= kMaxTreeDepth:
-    TimeConstruction(d)
-    inc(d, 2)
+    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
+    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 & "ms. Success!")
+  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
index bbef13d98..cc12b230f 100644
--- a/tests/gc/gcemscripten.nim
+++ b/tests/gc/gcemscripten.nim
@@ -15,7 +15,7 @@ when defined(allow_print):
 else:
   const print = false
 
-proc myResult3*(i:int):X {.exportc.} =
+proc myResult3*(i:int): X {.exportc.} =
   if print: echo "3"
   new(result)
   if print: echo "3-2"
diff --git a/tests/gc/gcleak.nim b/tests/gc/gcleak.nim
index 4e47db609..0bf993968 100644
--- a/tests/gc/gcleak.nim
+++ b/tests/gc/gcleak.nim
@@ -6,18 +6,24 @@ when defined(GC_setMaxPause):
   GC_setMaxPause 2_000
 
 type
-  TTestObj = object of TObject
+  TTestObj = object of RootObj
     x: string
 
-proc MakeObj(): TTestObj =
+proc makeObj(): TTestObj =
   result.x = "Hello"
 
-for i in 1 .. 1_000_000:
-  when defined(gcMarkAndSweep):
+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()
+  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
index 101421683..bc943dbe7 100644
--- a/tests/gc/gcleak2.nim
+++ b/tests/gc/gcleak2.nim
@@ -6,23 +6,33 @@ when defined(GC_setMaxPause):
   GC_setMaxPause 2_000
 
 type
-  TTestObj = object of TObject
+  TTestObj = object of RootObj
     x: string
     s: seq[int]
 
-proc MakeObj(): TTestObj =
+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 .. 1_000_000:
-    when defined(gcMarkAndSweep):
+  for i in 1 .. numIter:
+    when defined(gcMarkAndSweep) or defined(boehmgc):
       GC_fullcollect()
     var obj: TTestObj
-    obj = MakeObj()
+    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
index 588e238e9..5e146d69f 100644
--- a/tests/gc/gcleak3.nim
+++ b/tests/gc/gcleak3.nim
@@ -17,14 +17,10 @@ for i in 0..1024:
   s.add(obj)
 
 proc limit*[t](a: var seq[t]) =
-  var loop = s.len() - 512
-  for i in 0..loop:
-    #echo i
-    #GC_fullCollect()
+  while s.len > 0:
     if getOccupiedMem() > 3000_000: quit("still a leak!")
-    s.delete(i)
+    s.delete(0)
 
 s.limit()
-
 echo "no leak: ", getOccupiedMem()
 
diff --git a/tests/gc/gcleak4.nim b/tests/gc/gcleak4.nim
index d93a13854..a72db67b7 100644
--- a/tests/gc/gcleak4.nim
+++ b/tests/gc/gcleak4.nim
@@ -2,11 +2,8 @@ discard """
   outputsub: "no leak: "
 """
 
-when defined(GC_setMaxPause):
-  GC_setMaxPause 2_000
-
 type
-  TExpr = object {.inheritable.} ## abstract base class for an expression
+  TExpr {.inheritable.} = object ## abstract base class for an expression
   PLiteral = ref TLiteral
   TLiteral = object of TExpr
     x: int
@@ -15,7 +12,7 @@ type
     a, b: ref TExpr
     op2: string
 
-method eval(e: ref TExpr): int =
+method eval(e: ref TExpr): int {.base.} =
   # override this base method
   quit "to override!"
 
@@ -27,20 +24,18 @@ method eval(e: ref TPlusExpr): int =
 
 proc newLit(x: int): ref TLiteral =
   new(result)
-  {.watchpoint: result.}
   result.x = x
   result.op1 = $getOccupiedMem()
 
-proc newPlus(a, b: ref TExpr): ref TPlusExpr =
+proc newPlus(a, b: sink(ref TExpr)): ref TPlusExpr =
   new(result)
-  {.watchpoint: result.}
   result.a = a
   result.b = b
   result.op2 = $getOccupiedMem()
 
-const Limit = when compileOption("gc", "markAndSweep"): 5*1024*1024 else: 500_000
+const Limit = when compileOption("gc", "markAndSweep") or compileOption("gc", "boehm"): 5*1024*1024 else: 500_000
 
-for i in 0..100_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))
diff --git a/tests/gc/gcleak5.nim b/tests/gc/gcleak5.nim
index 6ab50e19e..f1913831b 100644
--- a/tests/gc/gcleak5.nim
+++ b/tests/gc/gcleak5.nim
@@ -9,7 +9,7 @@ proc main =
   for ii in 0..50_000:
     #while true:
     var t = getTime()
-    var g = t.getGMTime()
+    var g = t.utc()
     #echo isOnStack(addr g)
 
     if i mod 100 == 0:
diff --git a/tests/gc/gctest.nim b/tests/gc/gctest.nim
index b3b9af608..78b78934c 100644
--- a/tests/gc/gctest.nim
+++ b/tests/gc/gctest.nim
@@ -31,7 +31,7 @@ type
     of nkList: sons: seq[PCaseNode]
     else: unused: seq[string]
 
-  TIdObj* = object of TObject
+  TIdObj* = object of RootObj
     id*: int  # unique id; use this for comparisons and not the pointers
 
   PIdObj* = ref TIdObj
@@ -45,19 +45,14 @@ var
   flip: int
 
 proc newCaseNode(data: string): PCaseNode =
-  new(result)
   if flip == 0:
-    result.kind = nkStr
-    result.data = data
+    result = PCaseNode(kind: nkStr, data: data)
   else:
-    result.kind = nkWhole
-    result.unused = @["", "abc", "abdc"]
+    result = PCaseNode(kind: nkWhole, unused: @["", "abc", "abdc"])
   flip = 1 - flip
 
 proc newCaseNode(a, b: PCaseNode): PCaseNode =
-  new(result)
-  result.kind = nkList
-  result.sons = @[a, b]
+  result = PCaseNode(kind: nkList, sons: @[a, b])
 
 proc caseTree(lvl: int = 0): PCaseNode =
   if lvl == 3: result = newCaseNode("data item")
@@ -66,8 +61,7 @@ proc caseTree(lvl: int = 0): PCaseNode =
 proc finalizeNode(n: PNode) =
   assert(n != nil)
   write(stdout, "finalizing: ")
-  if isNil(n.data): writeLine(stdout, "nil!")
-  else: writeLine(stdout, "not nil")
+  writeLine(stdout, "not nil")
 
 var
   id: int = 1
@@ -179,24 +173,38 @@ proc main() =
   write(stdout, "done!\n")
 
 var
-    father: TBNode
-    s: string
-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")
+  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
index a16468c7e..ff1aa7e98 100644
--- a/tests/gc/growobjcrash.nim
+++ b/tests/gc/growobjcrash.nim
@@ -1,8 +1,4 @@
-discard """
-  output: "works"
-"""
-
-import cgi, strtabs
+import std/[cgi, strtabs]
 
 proc handleRequest(query: string): StringTableRef =
   iterator foo(): StringTableRef {.closure.} =
@@ -14,11 +10,11 @@ proc handleRequest(query: string): StringTableRef =
   let x = foo
   result = x()
 
-const Limit = when compileOption("gc", "markAndSweep"): 5*1024*1024 else: 700_000
+const Limit = 5*1024*1024
 
 proc main =
   var counter = 0
-  for i in 0 .. 100_000:
+  for i in 0 .. 10_000:
     for k, v in handleRequest("nick=Elina2&type=activate"):
       inc counter
       if counter mod 100 == 0:
@@ -26,4 +22,3 @@ proc main =
           quit "but now a leak"
 
 main()
-echo "works"
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/stackrefleak.nim b/tests/gc/stackrefleak.nim
index 302ef3599..7f3fbff43 100644
--- a/tests/gc/stackrefleak.nim
+++ b/tests/gc/stackrefleak.nim
@@ -12,7 +12,8 @@ type
 proc makePair: PCyclic =
   new(result)
   new(result.sibling)
-  result.sibling.sibling = result
+  when not defined(gcDestructors):
+    result.sibling.sibling = result
 
 proc loop =
   for i in 0..10000:
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
index efab49e36..cfd860e25 100644
--- a/tests/gc/thavlak.nim
+++ b/tests/gc/thavlak.nim
@@ -1,51 +1,55 @@
 discard """
   output: '''Welcome to LoopTesterApp, Nim edition
 Constructing Simple CFG...
-15000 dummy loops
+5000 dummy loops
 Constructing CFG...
 Performing Loop Recognition
 1 Iteration
-Another 50 iterations...
-..................................................
-Found 1 loops (including artificial root node) (50)'''
+Another 3 iterations...
+...
+Found 1 loops (including artificial root node) (3)'''
 """
 
 # bug #3184
 
-import tables
-import sequtils
-import sets
+import tables, sets
+
+when not declared(withScratchRegion):
+  template withScratchRegion(body: untyped) = body
 
 type
-  BasicBlock = object
-    inEdges: seq[ref BasicBlock]
-    outEdges: seq[ref BasicBlock]
+  BasicBlock = ref object
+    inEdges: seq[BasicBlock]
+    outEdges: seq[BasicBlock]
     name: int
 
-proc newBasicBlock(name: int): ref BasicBlock =
-  new(result)
-  result.inEdges = newSeq[ref BasicBlock]()
-  result.outEdges = newSeq[ref BasicBlock]()
-  result.name = name
+proc newBasicBlock(name: int): BasicBlock =
+  result = BasicBlock(
+    inEdges: newSeq[BasicBlock](),
+    outEdges: newSeq[BasicBlock](),
+    name: name
+  )
 
-proc hash(x: ref BasicBlock): int {.inline.} =
+proc hash(x: BasicBlock): int {.inline.} =
   result = x.name
 
 type
   BasicBlockEdge = object
-    fr: ref BasicBlock
-    to: ref BasicBlock
+    fr: BasicBlock
+    to: BasicBlock
 
   Cfg = object
-    basicBlockMap: Table[int, ref BasicBlock]
+    basicBlockMap: Table[int, BasicBlock]
     edgeList: seq[BasicBlockEdge]
-    startNode: ref BasicBlock
+    startNode: BasicBlock
 
 proc newCfg(): Cfg =
-  result.basicBlockMap = initTable[int, ref BasicBlock]()
-  result.edgeList = newSeq[BasicBlockEdge]()
+  result = Cfg(
+    basicBlockMap: initTable[int, BasicBlock](),
+    edgeList: newSeq[BasicBlockEdge](),
+    startNode: nil)
 
-proc createNode(self: var Cfg, name: int): ref BasicBlock =
+proc createNode(self: var Cfg, name: int): BasicBlock =
   result = self.basicBlockMap.getOrDefault(name)
   if result == nil:
     result = newBasicBlock(name)
@@ -54,128 +58,94 @@ proc createNode(self: var Cfg, name: int): ref BasicBlock =
   if self.startNode == nil:
     self.startNode = result
 
-proc addEdge(self: var Cfg, edge: BasicBlockEdge) =
-  self.edgeList.add(edge)
-
-proc getNumNodes(self: Cfg): int =
-  self.basicBlockMap.len
-
-proc newBasicBlockEdge(cfg: var Cfg, fromName: int, toName: int): BasicBlockEdge =
-  result.fr = cfg.createNode(fromName)
-  result.to = cfg.createNode(toName)
+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.addEdge(result)
+  cfg.edgeList.add(result)
 
 type
-  SimpleLoop = object
-    basicBlocks: seq[ref BasicBlock] # TODO: set here
-    children: seq[ref SimpleLoop] # TODO: set here
-    parent: ref SimpleLoop
-    header: ref BasicBlock
-    isRoot: bool
-    isReducible: bool
-    counter: int
-    nestingLevel: int
-    depthLevel: int
-
-proc newSimpleLoop(): ref SimpleLoop =
-  new(result)
-  result.basicBlocks = newSeq[ref BasicBlock]()
-  result.children = newSeq[ref SimpleLoop]()
-  result.parent = nil
-  result.header = nil
-  result.isRoot = false
-  result.isReducible = true
-  result.counter = 0
-  result.nestingLevel = 0
-  result.depthLevel = 0
-
-proc addNode(self: ref SimpleLoop, bb: ref BasicBlock) =
-  self.basicBlocks.add bb
-
-proc addChildLoop(self: ref SimpleLoop, loop: ref SimpleLoop) =
-  self.children.add loop
-
-proc setParent(self: ref SimpleLoop, parent: ref SimpleLoop) =
+  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.addChildLoop(self)
+  self.parent.children.add self
 
-proc setHeader(self: ref SimpleLoop, bb: ref BasicBlock) =
+proc setHeader(self: SimpleLoop, bb: BasicBlock) =
   self.basicBlocks.add(bb)
   self.header = bb
 
-proc setNestingLevel(self: ref SimpleLoop, level: int) =
+proc setNestingLevel(self: SimpleLoop, level: int) =
   self.nestingLevel = level
   if level == 0: self.isRoot = true
 
-var loop_counter: int = 0
+var loopCounter: int = 0
 
 type
   Lsg = object
-    loops: seq[ref SimpleLoop]
-    root: ref SimpleLoop
-
-proc createNewLoop(self: var Lsg): ref SimpleLoop =
-  result = newSimpleLoop()
-  loop_counter += 1
-  result.counter = loop_counter
-
-proc addLoop(self: var Lsg, l: ref SimpleLoop) =
+    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.loops = newSeq[ref SimpleLoop]()
-  result.root = result.createNewLoop()
+  result = Lsg(loops: newSeq[SimpleLoop](),
+    root: result.createNewLoop())
   result.root.setNestingLevel(0)
   result.addLoop(result.root)
 
-proc getNumLoops(self: Lsg): int =
-  self.loops.len
-
 type
-  UnionFindNode = object
-    parent: ref UnionFindNode
-    bb: ref BasicBlock
-    l: ref SimpleLoop
+  UnionFindNode = ref object
+    parent {.cursor.}: UnionFindNode
+    bb: BasicBlock
+    l: SimpleLoop
     dfsNumber: int
 
-proc newUnionFindNode(): ref UnionFindNode =
-  new(result)
-  when false:
-    result.parent = nil
-    result.bb = nil
-    result.l = nil
-    result.dfsNumber = 0
-
-proc initNode(self: ref UnionFindNode, bb: ref BasicBlock, dfsNumber: int) =
+proc initNode(self: UnionFindNode, bb: BasicBlock, dfsNumber: int) =
   self.parent = self
   self.bb = bb
   self.dfsNumber = dfsNumber
 
-proc findSet(self: ref UnionFindNode): ref UnionFindNode =
-  var nodeList = newSeq[ref UnionFindNode]()
-  result = self
+proc findSet(self: UnionFindNode): UnionFindNode =
+  var nodeList = newSeq[UnionFindNode]()
+  var it {.cursor.} = self
 
-  while result != result.parent:
-    var parent = result.parent
-    if parent != parent.parent: nodeList.add result
-    result = parent
+  while it != it.parent:
+    var parent {.cursor.} = it.parent
+    if parent != parent.parent: nodeList.add it
+    it = parent
 
-  for iter in nodeList: iter.parent = result.parent
+  for iter in nodeList: iter.parent = it.parent
+  result = it
 
-proc union(self: ref UnionFindNode, unionFindNode: ref UnionFindNode) =
+proc union(self: UnionFindNode, unionFindNode: UnionFindNode) =
   self.parent = unionFindNode
 
 
 const
-  BB_TOP          = 0 # uninitialized
-  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
-  BB_LAST         = 6 # Sentinel
+  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
@@ -188,44 +158,44 @@ type
     cfg: Cfg
     lsg: Lsg
 
-proc newHavlakLoopFinder(cfg: Cfg, lsg: Lsg): HavlakLoopFinder =
-  result.cfg = cfg
-  result.lsg = lsg
+proc newHavlakLoopFinder(cfg: Cfg, lsg: sink Lsg): HavlakLoopFinder =
+  result = HavlakLoopFinder(cfg: cfg, lsg: lsg)
 
-proc isAncestor(w: int, v: int, last: seq[int]): bool =
+proc isAncestor(w, v: int, last: seq[int]): bool =
   w <= v and v <= last[w]
 
-proc dfs(currentNode: ref BasicBlock, nodes: var seq[ref UnionFindNode], number: var Table[ref BasicBlock, int], last: var seq[int], current: int): int =
+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
 
-    result = current
     for target in currentNode.outEdges:
       if number[target] == UNVISITED:
-        stack.add((target, result+1))
+        stack.add((target, current+1))
         #result = dfs(target, nodes, number, last, result + 1)
-  last[number[currentNode]] = result
+  last[number[currentNode]] = current
 
 proc findLoops(self: var HavlakLoopFinder): int =
   var startNode = self.cfg.startNode
   if startNode == nil: return 0
-  var size = self.cfg.getNumNodes
+  var size = self.cfg.basicBlockMap.len
 
-  var nonBackPreds    = newSeq[HashSet[int]]()
-  var backPreds       = newSeq[seq[int]]()
-  var number          = initTable[ref BasicBlock, int]()
-  var header          = newSeq[int](size)
-  var types           = newSeq[int](size)
-  var last            = newSeq[int](size)
-  var nodes           = newSeq[ref UnionFindNode]()
+  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 initSet[int](1)
+    nonBackPreds.add initHashSet[int](1)
     backPreds.add newSeq[int]()
-    nodes.add newUnionFindNode()
+    nodes.add(UnionFindNode())
 
   # Step a:
   #   - initialize all nodes as unvisited.
@@ -233,7 +203,7 @@ proc findLoops(self: var HavlakLoopFinder): int =
   #   - unreached BB's are marked as dead.
   #
   for v in self.cfg.basicBlockMap.values: number[v] = UNVISITED
-  var res = dfs(startNode, nodes, number, last, 0)
+  dfs(startNode, nodes, number, last, 0)
 
   # Step b:
   #   - iterate over all nodes.
@@ -245,7 +215,7 @@ proc findLoops(self: var HavlakLoopFinder): int =
   #     - the list of backedges (backPreds) or
   #     - the list of non-backedges (nonBackPreds)
   #
-  for w in 0 .. <size:
+  for w in 0 ..< size:
     header[w] = 0
     types[w]  = BB_NONHEADER
 
@@ -278,7 +248,7 @@ proc findLoops(self: var HavlakLoopFinder): int =
 
   for w in countdown(size - 1, 0):
     # this is 'P' in Havlak's paper
-    var nodePool = newSeq[ref UnionFindNode]()
+    var nodePool = newSeq[UnionFindNode]()
 
     var nodeW = nodes[w].bb
     if nodeW != nil: # dead BB
@@ -291,7 +261,7 @@ proc findLoops(self: var HavlakLoopFinder): int =
 
       # Copy nodePool to workList.
       #
-      var workList = newSeq[ref UnionFindNode]()
+      var workList = newSeq[UnionFindNode]()
       for x in nodePool: workList.add x
 
       if nodePool.len != 0: types[w] = BB_REDUCIBLE
@@ -299,7 +269,7 @@ proc findLoops(self: var HavlakLoopFinder): int =
       # work the list...
       #
       while workList.len > 0:
-        var x = workList[0]
+        let x = workList[0]
         workList.del(0)
 
         # Step e:
@@ -332,7 +302,7 @@ proc findLoops(self: var HavlakLoopFinder): int =
       # 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):
+      if nodePool.len > 0 or types[w] == BB_SELF:
         var l = self.lsg.createNewLoop
 
         l.setHeader(nodeW)
@@ -358,15 +328,15 @@ proc findLoops(self: var HavlakLoopFinder): int =
           node.union(nodes[w])
 
           # Nested loops are not added, but linked together.
-          var node_l = node.l
-          if node_l != nil:
-            node_l.setParent(l)
+          var nodeL = node.l
+          if nodeL != nil:
+            nodeL.setParent(l)
           else:
-            l.addNode(node.bb)
+            l.basicBlocks.add node.bb
 
         self.lsg.addLoop(l)
 
-  result = self.lsg.getNumLoops
+  result = self.lsg.loops.len
 
 
 type
@@ -379,27 +349,26 @@ proc newLoopTesterApp(): LoopTesterApp =
   result.lsg = newLsg()
 
 proc buildDiamond(self: var LoopTesterApp, start: int): int =
-  var bb0 = start
-  var x1 = newBasicBlockEdge(self.cfg, bb0, bb0 + 1)
-  var x2 = newBasicBlockEdge(self.cfg, bb0, bb0 + 2)
-  var x3 = newBasicBlockEdge(self.cfg, bb0 + 1, bb0 + 3)
-  var x4 = newBasicBlockEdge(self.cfg, bb0 + 2, bb0 + 3)
-  result = bb0 + 3
+  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: int, end1: int) =
-  var x1 = newBasicBlockEdge(self.cfg, start1, end1)
+proc buildConnect(self: var LoopTesterApp, start1, end1: int) =
+  newBasicBlockEdge(self.cfg, start1, end1)
 
-proc buildStraight(self: var LoopTesterApp, start: int, n: int): int =
+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 =
-  var header   = self.buildStraight(from1, 1)
-  var diamond1 = self.buildDiamond(header)
-  var d11      = self.buildStraight(diamond1, 1)
-  var diamond2 = self.buildDiamond(d11)
-  var footer   = self.buildStraight(diamond2, 1)
+  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)
@@ -410,48 +379,63 @@ proc run(self: var LoopTesterApp) =
   echo "Welcome to LoopTesterApp, Nim edition"
   echo "Constructing Simple CFG..."
 
-  var x1 = self.cfg.createNode(0)
-  var x2 = self.buildBaseLoop(0)
-  var x3 = self.cfg.createNode(1)
+  discard self.cfg.createNode(0)
+  discard self.buildBaseLoop(0)
+  discard self.cfg.createNode(1)
   self.buildConnect(0, 2)
 
-  echo "15000 dummy loops"
+  echo "5000 dummy loops"
 
-  for i in 1..15000:
-    var h = newHavlakLoopFinder(self.cfg, newLsg())
-    var res = h.findLoops
+  for i in 1..5000:
+    withScratchRegion:
+      var h = newHavlakLoopFinder(self.cfg, newLsg())
+      discard h.findLoops
 
   echo "Constructing CFG..."
   var n = 2
 
-  for parlooptrees in 1..10:
-    var x6 = 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 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 50 iterations..."
+  echo "Another 3 iterations..."
 
   var sum = 0
-  for i in 1..50:
-    write stdout, "."
-    flushFile(stdout)
-    var hlf = newHavlakLoopFinder(self.cfg, newLsg())
-    sum += hlf.findLoops
-    #echo getOccupiedMem()
+  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, ")"
 
-var l = newLoopTesterApp()
-l.run
+  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
index 26b32396c..959cc5f7c 100644
--- a/tests/gc/tlists.nim
+++ b/tests/gc/tlists.nim
@@ -10,15 +10,13 @@ import lists
 import strutils
 
 proc mkleak() =
-    # allocate 10 MB via linked lists
+    # allocate 1 MB via linked lists
     let numberOfLists = 100
     for i in countUp(1, numberOfLists):
         var leakList = initDoublyLinkedList[string]()
-        let numberOfLeaks = 50000
+        let numberOfLeaks = 5000
         for j in countUp(1, numberOfLeaks):
-            let leakSize = 200
-            let leaked = newString(leakSize)
-            leakList.append(leaked)
+            leakList.append(newString(200))
 
 proc mkManyLeaks() =
     for i in 0..0:
@@ -29,7 +27,7 @@ proc mkManyLeaks() =
         # lists and bring the memory usage down to a few MB's.
         GC_fullCollect()
         when false: echo getOccupiedMem()
-        if getOccupiedMem() > 8 * 200 * 50_000 * 2:
+        if getOccupiedMem() > 8 * 200 * 5000 * 2:
           echo GC_getStatistics()
           quit "leaking"
     echo "Success"
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
index 0a6d4b873..81c048d74 100644
--- a/tests/gc/weakrefs.nim
+++ b/tests/gc/weakrefs.nim
@@ -19,8 +19,15 @@ var
 proc finalizer(x: StrongObject) =
   valid.excl(x.id)
 
+when defined(gcDestructors):
+  proc `=destroy`(x: var TMyObject) =
+    valid.excl(x.id)
+
 proc create: StrongObject =
-  new(result, finalizer)
+  when defined(gcDestructors):
+    new(result)
+  else:
+    new(result, finalizer)
   result.id = gid
   valid.incl(gid)
   inc gid