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--[-rwxr-xr-x] | tests/gc/gcbench.nim | 343 | ||||
-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--[-rwxr-xr-x] | tests/gc/gctest.nim | 109 | ||||
-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 | ||||
-rwxr-xr-x | tests/gc/talloc.nim | 637 | ||||
-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 |
26 files changed, 1459 insertions, 848 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 index d9e152326..e29ea762d 100755..100644 --- a/tests/gc/gcbench.nim +++ b/tests/gc/gcbench.nim @@ -1,163 +1,180 @@ -# 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 dont -# check for proper locking -# - -import - strutils, times - -type - PNode = ref TNode - TNode {.final.} = object - left, right: PNode - i, j: int - -proc newNode(L, r: 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 - -# 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: " & $getTotalMem() & " bytes") - echo("Free memory: " & $getFreeMem() & " bytes") - -proc TimeConstruction(depth: int) = - var - root, tempTree: PNode - t: int - iNumIters: int - - iNumIters = NumIters(depth) - - echo("Creating " & $iNumIters & " trees of depth " & $depth) - t = getStartMilsecs() - for i in 0..iNumIters-1: - new(tempTree) - Populate(depth, tempTree) - tempTree = nil - echo("\tTop down construction took " & $(getStartMilsecs() - t) & "msecs") - t = getStartMilsecs() - for i in 0..iNumIters-1: - tempTree = MakeTree(depth) - tempTree = nil - echo("\tBottom up construction took " & $(getStartMilsecs() - t) & "msecs") - -type - tMyArray = seq[float] - -proc main() = - var - root, longLivedTree, tempTree: PNode - t: int - myarray: tMyArray - - echo("Garbage Collector Test") - echo(" Stretching memory with a binary tree of depth " & $kStretchTreeDepth) - PrintDiagnostics() - t = getStartMilsecs() - - # Stretch the memory space quickly - 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") - newSeq(myarray, kArraySize) - for i in 0..kArraySize div 2 -1: - myarray[i] = 1.0 / toFloat(i) - - PrintDiagnostics() - - var d = kMinTreeDepth - while d <= kMaxTreeDepth: - 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 = getStartMilsecs() - t - PrintDiagnostics() - echo("Completed in " & $elapsed & "ms.") - -main() +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 index a2d97c944..78b78934c 100755..100644 --- a/tests/gc/gctest.nim +++ b/tests/gc/gctest.nim @@ -1,5 +1,8 @@ -# Test the garbage collector: -# This file is not in the test suite because it takes too much time. +discard """ + outputsub: "finished" +""" + +# Test the garbage collector. import strutils @@ -19,7 +22,7 @@ type data: string sons: seq[TBNode] # directly embedded! t: TTable - + TCaseKind = enum nkStr, nkWhole, nkList PCaseNode = ref TCaseNode TCaseNode {.final.} = object @@ -28,9 +31,9 @@ type of nkList: sons: seq[PCaseNode] else: unused: seq[string] - TIdObj* = object of TObject - id*: int # unique id; use this for comparisons and not the pointers - + 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 @@ -42,30 +45,23 @@ 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") else: result = newCaseNode(caseTree(lvl+1), caseTree(lvl+1)) -proc finalizeBNode(n: TBNode) = writeln(stdout, n.data) proc finalizeNode(n: PNode) = assert(n != nil) write(stdout, "finalizing: ") - if isNil(n.data): writeln(stdout, "nil!") - else: writeln(stdout, n.data) + writeLine(stdout, "not nil") var id: int = 1 @@ -79,7 +75,7 @@ proc buildTree(depth = 1): PNode = inc(id) proc returnTree(): PNode = - writeln(stdout, "creating id: " & $id) + writeLine(stdout, "creating id: " & $id) new(result, finalizeNode) result.data = $id new(result.le, finalizeNode) @@ -89,26 +85,26 @@ proc returnTree(): PNode = inc(id) # now create a cycle: - writeln(stdout, "creating id (cyclic): " & $id) + writeLine(stdout, "creating id (cyclic): " & $id) var cycle: PNode new(cycle, finalizeNode) cycle.data = $id cycle.le = cycle cycle.ri = cycle inc(id) - #writeln(stdout, "refcount: " & $refcount(cycle)) - #writeln(stdout, "refcount le: " & $refcount(cycle.le)) - #writeln(stdout, "refcount ri: " & $refcount(cycle.ri)) + #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 - writeln(stdout, "printing") - writeln(stdout, t.data) + writeLine(stdout, "printing") + writeLine(stdout, t.data) printTree(t.le) printTree(t.ri) proc unsureNew(result: var PNode) = - writeln(stdout, "creating unsure id: " & $id) + writeLine(stdout, "creating unsure id: " & $id) new(result, finalizeNode) result.data = $id new(result.le, finalizeNode) @@ -144,7 +140,7 @@ proc buildBTree(father: var TBNode) = father.t.data = @["ha", "lets", "stress", "it"] setSons(father) -proc getIdent(identifier: cstring, length: int, h: int): PIdent = +proc getIdent(identifier: cstring, length: int, h: int): PIdent = new(result) result.h = h result.s = newString(length) @@ -154,7 +150,7 @@ proc main() = discard getIdent("hall", 4, 0) discard getIdent("echo", 4, 0) discard getIdent("huch", 4, 0) - + var father: TBNode for i in 1..1_00: @@ -173,25 +169,42 @@ proc main() = var s: seq[string] = @[] for i in 1..100: add s, "hohoho" # test reallocation - writeln(stdout, s[89]) + writeLine(stdout, s[89]) write(stdout, "done!\n") var - father: TBNode - s: string -s = "" -s = "" -writeln(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() -write(stdout, "finished\n") -GC_fullCollect() -GC_fullCollect() -writeln(stdout, GC_getStatistics()) + 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/talloc.nim b/tests/gc/talloc.nim deleted file mode 100755 index 79a842415..000000000 --- a/tests/gc/talloc.nim +++ /dev/null @@ -1,637 +0,0 @@ -# -# -# Nimrod's Runtime Library -# (c) Copyright 2010 Andreas Rumpf -# -# See the file "copying.txt", included in this -# distribution, for details about the copyright. -# - -# Low level allocator for Nimrod. Has been designed to support the GC. -# TODO: -# - eliminate "used" field -# - make searching for block O(1) - -const - debugGC = false # we wish to debug the GC... - logGC = false - traceGC = false # extensive debugging - reallyDealloc = true # for debugging purposes this can be set to false - cycleGC = true # (de)activate the cycle GC - stressGC = false - reallyOsDealloc = true - coalescRight = true - coalescLeft = true - overwriteFree = false - -# Page size of the system; in most cases 4096 bytes. For exotic OS or -# CPU this needs to be changed: -const - PageShift = 12 - PageSize = 1 shl PageShift - PageMask = PageSize-1 - - MemAlign = 8 # also minimal allocatable memory block - - BitsPerPage = PageSize div MemAlign - UnitsPerPage = BitsPerPage div (sizeof(int)*8) - # how many ints do we need to describe a page: - # on 32 bit systems this is only 16 (!) - - TrunkShift = 9 - BitsPerTrunk = 1 shl TrunkShift # needs to be power of 2 and divisible by 64 - TrunkMask = BitsPerTrunk - 1 - IntsPerTrunk = BitsPerTrunk div (sizeof(int)*8) - IntShift = 5 + ord(sizeof(int) == 8) # 5 or 6, depending on int width - IntMask = 1 shl IntShift - 1 - -proc raiseOutOfMem() {.noreturn.} = - quit("out of memory") - -# ------------ platform specific chunk allocation code ----------------------- - -when defined(posix): - const - PROT_READ = 1 # page can be read - PROT_WRITE = 2 # page can be written - MAP_PRIVATE = 2 # Changes are private - - when defined(linux) or defined(aix): - const MAP_ANONYMOUS = 0x20 # don't use a file - elif defined(macosx) or defined(bsd): - const MAP_ANONYMOUS = 0x1000 - elif defined(solaris): - const MAP_ANONYMOUS = 0x100 - else: - {.error: "Port memory manager to your platform".} - - proc mmap(adr: pointer, len: int, prot, flags, fildes: cint, - off: int): pointer {.header: "<sys/mman.h>".} - - proc munmap(adr: pointer, len: int) {.header: "<sys/mman.h>".} - - proc osAllocPages(size: int): pointer {.inline.} = - result = mmap(nil, size, PROT_READ or PROT_WRITE, - MAP_PRIVATE or MAP_ANONYMOUS, -1, 0) - if result == nil or result == cast[pointer](-1): - raiseOutOfMem() - - proc osDeallocPages(p: pointer, size: int) {.inline} = - when reallyOsDealloc: munmap(p, size) - -elif defined(windows): - const - MEM_RESERVE = 0x2000 - MEM_COMMIT = 0x1000 - MEM_TOP_DOWN = 0x100000 - PAGE_READWRITE = 0x04 - - MEM_DECOMMIT = 0x4000 - MEM_RELEASE = 0x8000 - - proc VirtualAlloc(lpAddress: pointer, dwSize: int, flAllocationType, - flProtect: int32): pointer {. - header: "<windows.h>", stdcall.} - - proc VirtualFree(lpAddress: pointer, dwSize: int, - dwFreeType: int32) {.header: "<windows.h>", stdcall.} - - proc osAllocPages(size: int): pointer {.inline.} = - result = VirtualAlloc(nil, size, MEM_RESERVE or MEM_COMMIT, - PAGE_READWRITE) - if result == nil: raiseOutOfMem() - - proc osDeallocPages(p: pointer, size: int) {.inline.} = - # according to Microsoft, 0 is the only correct value here: - when reallyOsDealloc: VirtualFree(p, 0, MEM_RELEASE) - -else: - {.error: "Port memory manager to your platform".} - -# --------------------- end of non-portable code ----------------------------- - -# We manage *chunks* of memory. Each chunk is a multiple of the page size. -# Each chunk starts at an address that is divisible by the page size. Chunks -# that are bigger than ``ChunkOsReturn`` are returned back to the operating -# system immediately. - -const - ChunkOsReturn = 256 * PageSize - InitialMemoryRequest = ChunkOsReturn div 2 # < ChunkOsReturn! - SmallChunkSize = PageSize - -type - PTrunk = ptr TTrunk - TTrunk {.final.} = object - next: PTrunk # all nodes are connected with this pointer - key: int # start address at bit 0 - bits: array[0..IntsPerTrunk-1, int] # a bit vector - - TTrunkBuckets = array[0..1023, PTrunk] - TIntSet {.final.} = object - data: TTrunkBuckets - -type - TAlignType = biggestFloat - TFreeCell {.final, pure.} = object - next: ptr TFreeCell # next free cell in chunk (overlaid with refcount) - zeroField: int # 0 means cell is not used (overlaid with typ field) - # 1 means cell is manually managed pointer - - PChunk = ptr TBaseChunk - PBigChunk = ptr TBigChunk - PSmallChunk = ptr TSmallChunk - TBaseChunk {.pure.} = object - prevSize: int # size of previous chunk; for coalescing - size: int # if < PageSize it is a small chunk - used: bool # later will be optimized into prevSize... - - TSmallChunk = object of TBaseChunk - next, prev: PSmallChunk # chunks of the same size - freeList: ptr TFreeCell - free: int # how many bytes remain - acc: int # accumulator for small object allocation - data: TAlignType # start of usable memory - - TBigChunk = object of TBaseChunk # not necessarily > PageSize! - next: PBigChunk # chunks of the same (or bigger) size - prev: PBigChunk - align: int - data: TAlignType # start of usable memory - -template smallChunkOverhead(): expr = sizeof(TSmallChunk)-sizeof(TAlignType) -template bigChunkOverhead(): expr = sizeof(TBigChunk)-sizeof(TAlignType) - -proc roundup(x, v: int): int {.inline.} = - result = (x + (v-1)) and not (v-1) - assert(result >= x) - #return ((-x) and (v-1)) +% x - -assert(roundup(14, PageSize) == PageSize) -assert(roundup(15, 8) == 16) -assert(roundup(65, 8) == 72) - -# ------------- chunk table --------------------------------------------------- -# We use a PtrSet of chunk starts and a table[Page, chunksize] for chunk -# endings of big chunks. This is needed by the merging operation. The only -# remaining operation is best-fit for big chunks. Since there is a size-limit -# for big chunks (because greater than the limit means they are returned back -# to the OS), a fixed size array can be used. - -type - PLLChunk = ptr TLLChunk - TLLChunk {.pure.} = object ## *low-level* chunk - size: int # remaining size - acc: int # accumulator - - TAllocator {.final, pure.} = object - llmem: PLLChunk - currMem, maxMem, freeMem: int # memory sizes (allocated from OS) - freeSmallChunks: array[0..SmallChunkSize div MemAlign-1, PSmallChunk] - freeChunksList: PBigChunk # XXX make this a datastructure with O(1) access - chunkStarts: TIntSet - -proc incCurrMem(a: var TAllocator, bytes: int) {.inline.} = - inc(a.currMem, bytes) - -proc decCurrMem(a: var TAllocator, bytes: int) {.inline.} = - a.maxMem = max(a.maxMem, a.currMem) - dec(a.currMem, bytes) - -proc getMaxMem(a: var TAllocator): int = - # Since we update maxPagesCount only when freeing pages, - # maxPagesCount may not be up to date. Thus we use the - # maximum of these both values here: - return max(a.currMem, a.maxMem) - -var - allocator: TAllocator - -proc llAlloc(a: var TAllocator, size: int): pointer = - # *low-level* alloc for the memory managers data structures. Deallocation - # is never done. - if a.llmem == nil or size > a.llmem.size: - var request = roundup(size+sizeof(TLLChunk), PageSize) - a.llmem = cast[PLLChunk](osAllocPages(request)) - incCurrMem(a, request) - a.llmem.size = request - sizeof(TLLChunk) - a.llmem.acc = sizeof(TLLChunk) - result = cast[pointer](cast[TAddress](a.llmem) + a.llmem.acc) - dec(a.llmem.size, size) - inc(a.llmem.acc, size) - zeroMem(result, size) - -proc IntSetGet(t: TIntSet, key: int): PTrunk = - var it = t.data[key and high(t.data)] - while it != nil: - if it.key == key: return it - it = it.next - result = nil - -proc IntSetPut(t: var TIntSet, key: int): PTrunk = - result = IntSetGet(t, key) - if result == nil: - result = cast[PTrunk](llAlloc(allocator, sizeof(result^))) - result.next = t.data[key and high(t.data)] - t.data[key and high(t.data)] = result - result.key = key - -proc Contains(s: TIntSet, key: int): bool = - var t = IntSetGet(s, key shr TrunkShift) - if t != nil: - var u = key and TrunkMask - result = (t.bits[u shr IntShift] and (1 shl (u and IntMask))) != 0 - else: - result = false - -proc Incl(s: var TIntSet, key: int) = - var t = IntSetPut(s, key shr TrunkShift) - var u = key and TrunkMask - t.bits[u shr IntShift] = t.bits[u shr IntShift] or (1 shl (u and IntMask)) - -proc Excl(s: var TIntSet, key: int) = - var t = IntSetGet(s, key shr TrunkShift) - if t != nil: - var u = key and TrunkMask - t.bits[u shr IntShift] = t.bits[u shr IntShift] and not - (1 shl (u and IntMask)) - -proc ContainsOrIncl(s: var TIntSet, key: int): bool = - var t = IntSetGet(s, key shr TrunkShift) - if t != nil: - var u = key and TrunkMask - result = (t.bits[u shr IntShift] and (1 shl (u and IntMask))) != 0 - if not result: - t.bits[u shr IntShift] = t.bits[u shr IntShift] or - (1 shl (u and IntMask)) - else: - Incl(s, key) - result = false - -# ------------- chunk management ---------------------------------------------- -proc pageIndex(c: PChunk): int {.inline.} = - result = cast[TAddress](c) shr PageShift - -proc pageIndex(p: pointer): int {.inline.} = - result = cast[TAddress](p) shr PageShift - -proc pageAddr(p: pointer): PChunk {.inline.} = - result = cast[PChunk](cast[TAddress](p) and not PageMask) - assert(Contains(allocator.chunkStarts, pageIndex(result))) - -var lastSize = PageSize - -proc requestOsChunks(a: var TAllocator, size: int): PBigChunk = - incCurrMem(a, size) - inc(a.freeMem, size) - result = cast[PBigChunk](osAllocPages(size)) - assert((cast[TAddress](result) and PageMask) == 0) - #zeroMem(result, size) - result.next = nil - result.prev = nil - result.used = false - result.size = size - # update next.prevSize: - var nxt = cast[TAddress](result) +% size - assert((nxt and PageMask) == 0) - var next = cast[PChunk](nxt) - if pageIndex(next) in a.chunkStarts: - #echo("Next already allocated!") - next.prevSize = size - # set result.prevSize: - var prv = cast[TAddress](result) -% lastSize - assert((nxt and PageMask) == 0) - var prev = cast[PChunk](prv) - if pageIndex(prev) in a.chunkStarts and prev.size == lastSize: - #echo("Prev already allocated!") - result.prevSize = lastSize - else: - result.prevSize = 0 # unknown - lastSize = size # for next request - -proc freeOsChunks(a: var TAllocator, p: pointer, size: int) = - # update next.prevSize: - var c = cast[PChunk](p) - var nxt = cast[TAddress](p) +% c.size - assert((nxt and PageMask) == 0) - var next = cast[PChunk](nxt) - if pageIndex(next) in a.chunkStarts: - next.prevSize = 0 # XXX used - excl(a.chunkStarts, pageIndex(p)) - osDeallocPages(p, size) - decCurrMem(a, size) - dec(a.freeMem, size) - #c_fprintf(c_stdout, "[Alloc] back to OS: %ld\n", size) - -proc isAccessible(p: pointer): bool {.inline.} = - result = Contains(allocator.chunkStarts, pageIndex(p)) - -proc contains[T](list, x: T): bool = - var it = list - while it != nil: - if it == x: return true - it = it.next - -when false: - proc writeFreeList(a: TAllocator) = - var it = a.freeChunksList - c_fprintf(c_stdout, "freeChunksList: %p\n", it) - while it != nil: - c_fprintf(c_stdout, "it: %p, next: %p, prev: %p\n", - it, it.next, it.prev) - it = it.next - -proc ListAdd[T](head: var T, c: T) {.inline.} = - assert(c notin head) - assert c.prev == nil - assert c.next == nil - c.next = head - if head != nil: - assert head.prev == nil - head.prev = c - head = c - -proc ListRemove[T](head: var T, c: T) {.inline.} = - assert(c in head) - if c == head: - head = c.next - assert c.prev == nil - if head != nil: head.prev = nil - else: - assert c.prev != nil - c.prev.next = c.next - if c.next != nil: c.next.prev = c.prev - c.next = nil - c.prev = nil - -proc isSmallChunk(c: PChunk): bool {.inline.} = - return c.size <= SmallChunkSize-smallChunkOverhead() - #return c.size < SmallChunkSize - -proc chunkUnused(c: PChunk): bool {.inline.} = - result = not c.used - -proc updatePrevSize(a: var TAllocator, c: PBigChunk, - prevSize: int) {.inline.} = - var ri = cast[PChunk](cast[TAddress](c) +% c.size) - assert((cast[TAddress](ri) and PageMask) == 0) - if isAccessible(ri): - ri.prevSize = prevSize - -proc freeBigChunk(a: var TAllocator, c: PBigChunk) = - var c = c - assert(c.size >= PageSize) - inc(a.freeMem, c.size) - when coalescRight: - var ri = cast[PChunk](cast[TAddress](c) +% c.size) - assert((cast[TAddress](ri) and PageMask) == 0) - if isAccessible(ri) and chunkUnused(ri): - assert(not isSmallChunk(ri)) - if not isSmallChunk(ri): - ListRemove(a.freeChunksList, cast[PBigChunk](ri)) - inc(c.size, ri.size) - excl(a.chunkStarts, pageIndex(ri)) - when coalescLeft: - if c.prevSize != 0: - var le = cast[PChunk](cast[TAddress](c) -% c.prevSize) - assert((cast[TAddress](le) and PageMask) == 0) - if isAccessible(le) and chunkUnused(le): - assert(not isSmallChunk(le)) - if not isSmallChunk(le): - ListRemove(a.freeChunksList, cast[PBigChunk](le)) - inc(le.size, c.size) - excl(a.chunkStarts, pageIndex(c)) - c = cast[PBigChunk](le) - - if c.size < ChunkOsReturn: - incl(a.chunkStarts, pageIndex(c)) - updatePrevSize(a, c, c.size) - ListAdd(a.freeChunksList, c) - c.used = false - else: - freeOsChunks(a, c, c.size) - -proc splitChunk(a: var TAllocator, c: PBigChunk, size: int) = - var rest = cast[PBigChunk](cast[TAddress](c) +% size) - assert(rest notin a.freeChunksList) - # c_fprintf(c_stdout, "to add: %p\n", rest) - # writeFreeList(allocator) - # assert false - rest.size = c.size - size - rest.used = false - rest.next = nil - rest.prev = nil - rest.prevSize = size - updatePrevSize(a, c, rest.size) - c.size = size - incl(a.chunkStarts, pageIndex(rest)) - ListAdd(a.freeChunksList, rest) - -proc getBigChunk(a: var TAllocator, size: int): PBigChunk = - # use first fit for now: - assert((size and PageMask) == 0) - assert(size > 0) - result = a.freeChunksList - block search: - while result != nil: - #if not chunkUnused(result): - # c_fprintf(c_stdout, "%lld\n", int(result.used)) - assert chunkUnused(result) - if result.size == size: - ListRemove(a.freeChunksList, result) - break search - elif result.size > size: - #c_fprintf(c_stdout, "res size: %lld; size: %lld\n", result.size, size) - ListRemove(a.freeChunksList, result) - splitChunk(a, result, size) - break search - result = result.next - assert result != a.freeChunksList - if size < InitialMemoryRequest: - result = requestOsChunks(a, InitialMemoryRequest) - splitChunk(a, result, size) - else: - result = requestOsChunks(a, size) - result.prevSize = 0 # XXX why is this needed? - result.used = true - incl(a.chunkStarts, pageIndex(result)) - dec(a.freeMem, size) - -proc getSmallChunk(a: var TAllocator): PSmallChunk = - var res = getBigChunk(a, PageSize) - assert res.prev == nil - assert res.next == nil - result = cast[PSmallChunk](res) - -# ----------------------------------------------------------------------------- - -proc getCellSize(p: pointer): int {.inline.} = - var c = pageAddr(p) - result = c.size - -proc rawAlloc(a: var TAllocator, requestedSize: int): pointer = - assert(roundup(65, 8) == 72) - assert requestedSize >= sizeof(TFreeCell) - var size = roundup(requestedSize, MemAlign) - #c_fprintf(c_stdout, "alloc; size: %ld; %ld\n", requestedSize, size) - if size <= SmallChunkSize-smallChunkOverhead(): - # allocate a small block: for small chunks, we use only its next pointer - var s = size div MemAlign - var c = a.freeSmallChunks[s] - if c == nil: - c = getSmallChunk(a) - c.freeList = nil - assert c.size == PageSize - c.size = size - c.acc = size - c.free = SmallChunkSize - smallChunkOverhead() - size - c.next = nil - c.prev = nil - ListAdd(a.freeSmallChunks[s], c) - result = addr(c.data) - assert((cast[TAddress](result) and (MemAlign-1)) == 0) - else: - assert c.next != c - #if c.size != size: - # c_fprintf(c_stdout, "csize: %lld; size %lld\n", c.size, size) - assert c.size == size - if c.freeList == nil: - assert(c.acc + smallChunkOverhead() + size <= SmallChunkSize) - result = cast[pointer](cast[TAddress](addr(c.data)) +% c.acc) - inc(c.acc, size) - else: - result = c.freeList - assert(c.freeList.zeroField == 0) - c.freeList = c.freeList.next - dec(c.free, size) - assert((cast[TAddress](result) and (MemAlign-1)) == 0) - if c.free < size: - ListRemove(a.freeSmallChunks[s], c) - else: - size = roundup(requestedSize+bigChunkOverhead(), PageSize) - # allocate a large block - var c = getBigChunk(a, size) - assert c.prev == nil - assert c.next == nil - assert c.size == size - result = addr(c.data) - assert((cast[TAddress](result) and (MemAlign-1)) == 0) - assert(isAccessible(result)) - -proc rawDealloc(a: var TAllocator, p: pointer) = - var c = pageAddr(p) - if isSmallChunk(c): - # `p` is within a small chunk: - var c = cast[PSmallChunk](c) - var s = c.size - var f = cast[ptr TFreeCell](p) - #echo("setting to nil: ", $cast[TAddress](addr(f.zeroField))) - assert(f.zeroField != 0) - f.zeroField = 0 - f.next = c.freeList - c.freeList = f - when overwriteFree: - # set to 0xff to check for usage after free bugs: - c_memset(cast[pointer](cast[int](p) +% sizeof(TFreeCell)), -1'i32, - s -% sizeof(TFreeCell)) - # check if it is not in the freeSmallChunks[s] list: - if c.free < s: - assert c notin a.freeSmallChunks[s div memAlign] - # add it to the freeSmallChunks[s] array: - ListAdd(a.freeSmallChunks[s div memAlign], c) - inc(c.free, s) - else: - inc(c.free, s) - if c.free == SmallChunkSize-smallChunkOverhead(): - ListRemove(a.freeSmallChunks[s div memAlign], c) - c.size = SmallChunkSize - freeBigChunk(a, cast[PBigChunk](c)) - else: - # set to 0xff to check for usage after free bugs: - when overwriteFree: c_memset(p, -1'i32, c.size -% bigChunkOverhead()) - # free big chunk - freeBigChunk(a, cast[PBigChunk](c)) - -proc isAllocatedPtr(a: TAllocator, p: pointer): bool = - if isAccessible(p): - var c = pageAddr(p) - if not chunkUnused(c): - if isSmallChunk(c): - var c = cast[PSmallChunk](c) - var offset = (cast[TAddress](p) and PageMask) -% smallChunkOverhead() - result = (c.acc >% offset) and (offset %% c.size == 0) and - (cast[ptr TFreeCell](p).zeroField >% 1) - else: - var c = cast[PBigChunk](c) - result = p == addr(c.data) and cast[ptr TFreeCell](p).zeroField >% 1 - -# ---------------------- interface to programs ------------------------------- - -when true: - proc alloc(size: int): pointer = - result = rawAlloc(allocator, size+sizeof(TFreeCell)) - cast[ptr TFreeCell](result).zeroField = 2 # mark it as used - #assert(not isAllocatedPtr(allocator, result)) - result = cast[pointer](cast[TAddress](result) +% sizeof(TFreeCell)) - - proc alloc0(size: int): pointer = - result = talloc.alloc(size) - zeroMem(result, size) - - proc dealloc(p: pointer) = - var x = cast[pointer](cast[TAddress](p) -% sizeof(TFreeCell)) - assert(cast[ptr TFreeCell](x).zeroField == 2) - rawDealloc(allocator, x) - assert(not isAllocatedPtr(allocator, x)) - - proc isAllocatedPtr(p: pointer): bool = - var x = cast[pointer](cast[TAddress](p) -% sizeof(TFreeCell)) - result = isAllocatedPtr(allocator, x) - - proc ptrSize(p: pointer): int = - var x = cast[pointer](cast[TAddress](p) -% sizeof(TFreeCell)) - result = pageAddr(x).size - sizeof(TFreeCell) - - proc realloc(p: pointer, newsize: int): pointer = - if newsize > 0: - result = talloc.alloc(newsize) - if p != nil: - copyMem(result, p, ptrSize(p)) - talloc.dealloc(p) - elif p != nil: - talloc.dealloc(p) - - proc countFreeMem(): int = - # only used for assertions - var it = allocator.freeChunksList - while it != nil: - inc(result, it.size) - it = it.next - - proc getFreeMem(): int = - result = allocator.freeMem - #assert(result == countFreeMem()) - - proc getTotalMem(): int = return allocator.currMem - proc getOccupiedMem(): int = return talloc.getTotalMem() - talloc.getFreeMem() - -when isMainModule: - const iterations = 4000_000 - incl(allocator.chunkStarts, 11) - assert 11 in allocator.chunkStarts - excl(allocator.chunkStarts, 11) - assert 11 notin allocator.chunkStarts - var p: array [1..iterations, pointer] - for i in 4..70: - var x = i * 8 - for j in 1.. iterations: - p[j] = talloc.Alloc(x) - for j in 1..iterations: - assert isAllocatedPtr(p[j]) - echo($i, " used memory: ", $(allocator.currMem)) - for j in countdown(iterations, 1): - #echo("j: ", $j) - talloc.dealloc(p[j]) - assert(not isAllocatedPtr(allocator, p[j])) - echo($i, " after freeing: ", $(allocator.currMem)) - 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() |