diff options
Diffstat (limited to 'tests/gc')
-rw-r--r-- | tests/gc/bintrees.nim | 54 | ||||
-rw-r--r-- | tests/gc/closureleak.nim | 52 | ||||
-rw-r--r-- | tests/gc/cyclecollector.nim | 23 | ||||
-rw-r--r-- | tests/gc/cycleleak.nim | 56 | ||||
-rw-r--r-- | tests/gc/foreign_thr.nim | 88 | ||||
-rw-r--r-- | tests/gc/gcbench.nim | 180 | ||||
-rw-r--r-- | tests/gc/gcemscripten.nim | 59 | ||||
-rw-r--r-- | tests/gc/gcleak.nim | 29 | ||||
-rw-r--r-- | tests/gc/gcleak2.nim | 38 | ||||
-rw-r--r-- | tests/gc/gcleak3.nim | 26 | ||||
-rw-r--r-- | tests/gc/gcleak4.nim | 46 | ||||
-rw-r--r-- | tests/gc/gcleak5.nim | 25 | ||||
-rw-r--r-- | tests/gc/gctest.nim | 210 | ||||
-rw-r--r-- | tests/gc/gctest.nim.cfg | 1 | ||||
-rw-r--r-- | tests/gc/growobjcrash.nim | 24 | ||||
-rw-r--r-- | tests/gc/panicoverride.nim | 14 | ||||
-rw-r--r-- | tests/gc/refarrayleak.nim | 39 | ||||
-rw-r--r-- | tests/gc/stackrefleak.nim | 32 | ||||
-rw-r--r-- | tests/gc/tdisable_orc.nim | 9 | ||||
-rw-r--r-- | tests/gc/thavlak.nim | 441 | ||||
-rw-r--r-- | tests/gc/tlists.nim | 35 | ||||
-rw-r--r-- | tests/gc/trace_globals.nim | 33 | ||||
-rw-r--r-- | tests/gc/tregionleak.nim | 23 | ||||
-rw-r--r-- | tests/gc/tstandalone.nim | 14 | ||||
-rw-r--r-- | tests/gc/weakrefs.nim | 57 |
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() |