diff options
Diffstat (limited to 'tests/gc')
-rw-r--r-- | tests/gc/closureleak.nim | 41 | ||||
-rw-r--r-- | tests/gc/cyclecollector.nim | 6 | ||||
-rw-r--r-- | tests/gc/cycleleak.nim | 2 | ||||
-rw-r--r-- | tests/gc/foreign_thr.nim | 88 | ||||
-rw-r--r-- | tests/gc/gcbench.nim | 86 | ||||
-rw-r--r-- | tests/gc/gcemscripten.nim | 2 | ||||
-rw-r--r-- | tests/gc/gcleak.nim | 18 | ||||
-rw-r--r-- | tests/gc/gcleak2.nim | 24 | ||||
-rw-r--r-- | tests/gc/gcleak3.nim | 8 | ||||
-rw-r--r-- | tests/gc/gcleak4.nim | 15 | ||||
-rw-r--r-- | tests/gc/gcleak5.nim | 2 | ||||
-rw-r--r-- | tests/gc/gctest.nim | 72 | ||||
-rw-r--r-- | tests/gc/gctest.nim.cfg | 1 | ||||
-rw-r--r-- | tests/gc/growobjcrash.nim | 11 | ||||
-rw-r--r-- | tests/gc/panicoverride.nim | 14 | ||||
-rw-r--r-- | tests/gc/stackrefleak.nim | 3 | ||||
-rw-r--r-- | tests/gc/tdisable_orc.nim | 9 | ||||
-rw-r--r-- | tests/gc/thavlak.nim | 346 | ||||
-rw-r--r-- | tests/gc/tlists.nim | 10 | ||||
-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 | 9 |
23 files changed, 526 insertions, 311 deletions
diff --git a/tests/gc/closureleak.nim b/tests/gc/closureleak.nim index 18d320bdf..e67beb513 100644 --- a/tests/gc/closureleak.nim +++ b/tests/gc/closureleak.nim @@ -1,30 +1,49 @@ discard """ outputsub: "true" + disabled: "32bit" """ -from strutils import join - type - TFoo * = object + TFoo* = object id: int - fn: proc(){.closure.} + fn: proc() {.closure.} var foo_counter = 0 var alive_foos = newseq[int](0) -proc free*(some: ref TFoo) = - #echo "Tfoo #", some.id, " freed" - alive_foos.del alive_foos.find(some.id) +when defined(gcDestructors): + proc `=destroy`(some: TFoo) = + alive_foos.del alive_foos.find(some.id) + # TODO: fixme: investigate why `=destroy` requires `some.fn` to be `gcsafe` + # the debugging info below came from `symPrototype` in the liftdestructors + # proc (){.closure, gcsafe.}, {tfThread, tfHasAsgn, tfCheckedForDestructor, tfExplicitCallConv} + # var proc (){.closure, gcsafe.}, {tfHasGCedMem} + # it worked by accident with var T destructors because in the sempass2 + # + # let argtype = skipTypes(a.typ, abstractInst) # !!! it does't skip `tyVar` + # if argtype.kind == tyProc and notGcSafe(argtype) and not tracked.inEnforcedGcSafe: + # localError(tracked.config, n.info, $n & " is not GC safe") + {.cast(gcsafe).}: + `=destroy`(some.fn) + +else: + proc free*(some: ref TFoo) = + #echo "Tfoo #", some.id, " freed" + alive_foos.del alive_foos.find(some.id) + proc newFoo*(): ref TFoo = - new result, free + when defined(gcDestructors): + new result + else: + new result, free result.id = foo_counter alive_foos.add result.id inc foo_counter -for i in 0 .. <10: - discard newFoo() +for i in 0 ..< 10: + discard newFoo() -for i in 0 .. <10: +for i in 0 ..< 10: let f = newFoo() f.fn = proc = echo f.id diff --git a/tests/gc/cyclecollector.nim b/tests/gc/cyclecollector.nim index 46fed6c45..2d02a7a3c 100644 --- a/tests/gc/cyclecollector.nim +++ b/tests/gc/cyclecollector.nim @@ -9,13 +9,15 @@ type proc createCycle(leaf: string): Node = new result result.a = result - shallowCopy result.leaf, leaf + when defined(gcArc) or defined(gcOrc): + result.leaf = leaf + else: + shallowCopy result.leaf, leaf proc main = for i in 0 .. 100_000: var leaf = "this is the leaf. it allocates" let x = createCycle(leaf) let y = createCycle(leaf) - echo "done ", getOccupiedMem() main() diff --git a/tests/gc/cycleleak.nim b/tests/gc/cycleleak.nim index 9f5c30ebd..e355abc96 100644 --- a/tests/gc/cycleleak.nim +++ b/tests/gc/cycleleak.nim @@ -10,7 +10,7 @@ type PModule = ref Module Node = object - owner*: PModule + owner* {.cursor.}: PModule data*: array[0..200, char] # some fat to drain memory faster id: int diff --git a/tests/gc/foreign_thr.nim b/tests/gc/foreign_thr.nim new file mode 100644 index 000000000..88ab95113 --- /dev/null +++ b/tests/gc/foreign_thr.nim @@ -0,0 +1,88 @@ +discard """ + output: ''' +Hello from thread +Hello from thread +Hello from thread +Hello from thread +''' + cmd: "nim $target --hints:on --threads:on --tlsEmulation:off $options $file" +""" +# Copied from stdlib +import strutils + +const + StackGuardSize = 4096 + ThreadStackMask = 1024*256*sizeof(int)-1 + ThreadStackSize = ThreadStackMask+1 - StackGuardSize + +type ThreadFunc = proc() {.thread.} + +when defined(posix): + import posix + + proc runInForeignThread(f: ThreadFunc) = + proc wrapper(p: pointer): pointer {.noconv.} = + let thr = cast[ThreadFunc](p) + setupForeignThreadGc() + thr() + tearDownForeignThreadGc() + setupForeignThreadGc() + thr() + tearDownForeignThreadGc() + result = nil + + var attrs {.noinit.}: PthreadAttr + doAssert pthread_attr_init(addr attrs) == 0 + doAssert pthread_attr_setstacksize(addr attrs, ThreadStackSize) == 0 + var tid: Pthread + doAssert pthread_create(addr tid, addr attrs, wrapper, f) == 0 + doAssert pthread_join(tid, nil) == 0 + +elif defined(windows): + import winlean + type + WinThreadProc = proc (x: pointer): int32 {.stdcall.} + + proc createThread(lpThreadAttributes: pointer, dwStackSize: DWORD, + lpStartAddress: WinThreadProc, + lpParameter: pointer, + dwCreationFlags: DWORD, + lpThreadId: var DWORD): Handle {. + stdcall, dynlib: "kernel32", importc: "CreateThread".} + + proc wrapper(p: pointer): int32 {.stdcall.} = + let thr = cast[ThreadFunc](p) + setupForeignThreadGc() + thr() + tearDownForeignThreadGc() + setupForeignThreadGc() + thr() + tearDownForeignThreadGc() + result = 0'i32 + + proc runInForeignThread(f: ThreadFunc) = + var dummyThreadId: DWORD + var h = createThread(nil, ThreadStackSize.int32, wrapper.WinThreadProc, cast[pointer](f), 0, dummyThreadId) + doAssert h != 0.Handle + doAssert waitForSingleObject(h, -1'i32) == 0.DWORD + +else: + {.fatal: "Unknown system".} + +proc runInNativeThread(f: ThreadFunc) = + proc wrapper(f: ThreadFunc) {.thread.} = + # These operations must be NOP + setupForeignThreadGc() + tearDownForeignThreadGc() + f() + f() + var thr: Thread[ThreadFunc] + createThread(thr, wrapper, f) + joinThread(thr) + +proc f {.thread.} = + var msg = "Hello " & "from thread" + echo msg + +runInForeignThread(f) +runInNativeThread(f) diff --git a/tests/gc/gcbench.nim b/tests/gc/gcbench.nim index 782daf793..e29ea762d 100644 --- a/tests/gc/gcbench.nim +++ b/tests/gc/gcbench.nim @@ -44,7 +44,7 @@ discard """ # - No way to check on memory use # - No cyclic data structures # - No attempt to measure variation with object size -# - Results are sensitive to locking cost, but we dont +# - Results are sensitive to locking cost, but we don't # check for proper locking # @@ -53,11 +53,11 @@ import type PNode = ref TNode - TNode {.final.} = object + TNode {.final, acyclic.} = object left, right: PNode i, j: int -proc newNode(L, r: PNode): PNode = +proc newNode(L, r: sink PNode): PNode = new(result) result.left = L result.right = r @@ -65,55 +65,58 @@ proc newNode(L, r: PNode): PNode = const kStretchTreeDepth = 18 # about 16Mb kLongLivedTreeDepth = 16 # about 4Mb - kArraySize = 500000 # about 4Mb + kArraySize = 500000 # about 4Mb kMinTreeDepth = 4 kMaxTreeDepth = 16 +when not declared(withScratchRegion): + template withScratchRegion(body: untyped) = body + # Nodes used by a tree of a given size -proc TreeSize(i: int): int = return ((1 shl (i + 1)) - 1) +proc treeSize(i: int): int = return ((1 shl (i + 1)) - 1) # Number of iterations to use for a given tree depth -proc NumIters(i: int): int = - return 2 * TreeSize(kStretchTreeDepth) div TreeSize(i) +proc numIters(i: int): int = + return 2 * treeSize(kStretchTreeDepth) div treeSize(i) # Build tree top down, assigning to older objects. -proc Populate(iDepth: int, thisNode: PNode) = +proc populate(iDepth: int, thisNode: PNode) = if iDepth <= 0: return else: new(thisNode.left) new(thisNode.right) - Populate(iDepth-1, thisNode.left) - Populate(iDepth-1, thisNode.right) + populate(iDepth-1, thisNode.left) + populate(iDepth-1, thisNode.right) # Build tree bottom-up -proc MakeTree(iDepth: int): PNode = +proc makeTree(iDepth: int): PNode = if iDepth <= 0: new(result) else: - return newNode(MakeTree(iDepth-1), MakeTree(iDepth-1)) + return newNode(makeTree(iDepth-1), makeTree(iDepth-1)) -proc PrintDiagnostics() = - echo("Total memory available: " & $getTotalMem() & " bytes") - echo("Free memory: " & $getFreeMem() & " bytes") +proc printDiagnostics() = + echo("Total memory available: " & formatSize(getTotalMem()) & " bytes") + echo("Free memory: " & formatSize(getFreeMem()) & " bytes") -proc TimeConstruction(depth: int) = +proc timeConstruction(depth: int) = var root, tempTree: PNode iNumIters: int - iNumIters = NumIters(depth) + iNumIters = numIters(depth) echo("Creating " & $iNumIters & " trees of depth " & $depth) var t = epochTime() for i in 0..iNumIters-1: new(tempTree) - Populate(depth, tempTree) + populate(depth, tempTree) tempTree = nil echo("\tTop down construction took " & $(epochTime() - t) & "msecs") t = epochTime() for i in 0..iNumIters-1: - tempTree = MakeTree(depth) + tempTree = makeTree(depth) tempTree = nil echo("\tBottom up construction took " & $(epochTime() - t) & "msecs") @@ -127,42 +130,51 @@ proc main() = echo("Garbage Collector Test") echo(" Stretching memory with a binary tree of depth " & $kStretchTreeDepth) - PrintDiagnostics() + printDiagnostics() var t = epochTime() # Stretch the memory space quickly - tempTree = MakeTree(kStretchTreeDepth) - tempTree = nil + withScratchRegion: + tempTree = makeTree(kStretchTreeDepth) + tempTree = nil # Create a long lived object echo(" Creating a long-lived binary tree of depth " & $kLongLivedTreeDepth) new(longLivedTree) - Populate(kLongLivedTreeDepth, longLivedTree) + populate(kLongLivedTreeDepth, longLivedTree) # Create long-lived array, filling half of it echo(" Creating a long-lived array of " & $kArraySize & " doubles") - newSeq(myarray, kArraySize) - for i in 0..kArraySize div 2 - 1: - myarray[i] = 1.0 / toFloat(i) + withScratchRegion: + newSeq(myarray, kArraySize) + for i in 0..kArraySize div 2 - 1: + myarray[i] = 1.0 / toFloat(i) - PrintDiagnostics() + printDiagnostics() - var d = kMinTreeDepth - while d <= kMaxTreeDepth: - TimeConstruction(d) - inc(d, 2) + var d = kMinTreeDepth + while d <= kMaxTreeDepth: + withScratchRegion: + timeConstruction(d) + inc(d, 2) - if longLivedTree == nil or myarray[1000] != 1.0/1000.0: - echo("Failed") - # fake reference to LongLivedTree - # and array to keep them from being optimized away + if longLivedTree == nil or myarray[1000] != 1.0/1000.0: + echo("Failed") + # fake reference to LongLivedTree + # and array to keep them from being optimized away var elapsed = epochTime() - t - PrintDiagnostics() - echo("Completed in " & $elapsed & "ms. Success!") + printDiagnostics() + echo("Completed in " & $elapsed & "s. Success!") + when declared(getMaxMem): + echo "Max memory ", formatSize getMaxMem() when defined(GC_setMaxPause): GC_setMaxPause 2_000 +when defined(gcDestructors): + let mem = getOccupiedMem() main() +when defined(gcDestructors): + doAssert getOccupiedMem() == mem diff --git a/tests/gc/gcemscripten.nim b/tests/gc/gcemscripten.nim index bbef13d98..cc12b230f 100644 --- a/tests/gc/gcemscripten.nim +++ b/tests/gc/gcemscripten.nim @@ -15,7 +15,7 @@ when defined(allow_print): else: const print = false -proc myResult3*(i:int):X {.exportc.} = +proc myResult3*(i:int): X {.exportc.} = if print: echo "3" new(result) if print: echo "3-2" diff --git a/tests/gc/gcleak.nim b/tests/gc/gcleak.nim index 4e47db609..0bf993968 100644 --- a/tests/gc/gcleak.nim +++ b/tests/gc/gcleak.nim @@ -6,18 +6,24 @@ when defined(GC_setMaxPause): GC_setMaxPause 2_000 type - TTestObj = object of TObject + TTestObj = object of RootObj x: string -proc MakeObj(): TTestObj = +proc makeObj(): TTestObj = result.x = "Hello" -for i in 1 .. 1_000_000: - when defined(gcMarkAndSweep): +const numIter = + # see tests/gc/gcleak2.nim + when defined(boehmgc): + 1_000 + elif defined(gcMarkAndSweep): 10_000 + else: 100_000 + +for i in 1 .. numIter: + when defined(gcMarkAndSweep) or defined(boehmgc): GC_fullcollect() - var obj = MakeObj() + var obj = makeObj() if getOccupiedMem() > 300_000: quit("still a leak!") # echo GC_getstatistics() echo "no leak: ", getOccupiedMem() - diff --git a/tests/gc/gcleak2.nim b/tests/gc/gcleak2.nim index 101421683..bc943dbe7 100644 --- a/tests/gc/gcleak2.nim +++ b/tests/gc/gcleak2.nim @@ -6,23 +6,33 @@ when defined(GC_setMaxPause): GC_setMaxPause 2_000 type - TTestObj = object of TObject + TTestObj = object of RootObj x: string s: seq[int] -proc MakeObj(): TTestObj = +proc makeObj(): TTestObj = result.x = "Hello" result.s = @[1,2,3] +const numIter = + when defined(boehmgc): + # super slow because GC_fullcollect() at each iteration; especially + # on OSX 10.15 where it takes ~170s + # `getOccupiedMem` should be constant after each iteration for i >= 3 + 1_000 + elif defined(gcMarkAndSweep): + # likewise, somewhat slow, 1_000_000 would run for 8s + # and same remark as above + 100_000 + else: 1_000_000 + proc inProc() = - for i in 1 .. 1_000_000: - when defined(gcMarkAndSweep): + for i in 1 .. numIter: + when defined(gcMarkAndSweep) or defined(boehmgc): GC_fullcollect() var obj: TTestObj - obj = MakeObj() + obj = makeObj() if getOccupiedMem() > 300_000: quit("still a leak!") inProc() echo "no leak: ", getOccupiedMem() - - diff --git a/tests/gc/gcleak3.nim b/tests/gc/gcleak3.nim index 588e238e9..5e146d69f 100644 --- a/tests/gc/gcleak3.nim +++ b/tests/gc/gcleak3.nim @@ -17,14 +17,10 @@ for i in 0..1024: s.add(obj) proc limit*[t](a: var seq[t]) = - var loop = s.len() - 512 - for i in 0..loop: - #echo i - #GC_fullCollect() + while s.len > 0: if getOccupiedMem() > 3000_000: quit("still a leak!") - s.delete(i) + s.delete(0) s.limit() - echo "no leak: ", getOccupiedMem() diff --git a/tests/gc/gcleak4.nim b/tests/gc/gcleak4.nim index d93a13854..a72db67b7 100644 --- a/tests/gc/gcleak4.nim +++ b/tests/gc/gcleak4.nim @@ -2,11 +2,8 @@ discard """ outputsub: "no leak: " """ -when defined(GC_setMaxPause): - GC_setMaxPause 2_000 - type - TExpr = object {.inheritable.} ## abstract base class for an expression + TExpr {.inheritable.} = object ## abstract base class for an expression PLiteral = ref TLiteral TLiteral = object of TExpr x: int @@ -15,7 +12,7 @@ type a, b: ref TExpr op2: string -method eval(e: ref TExpr): int = +method eval(e: ref TExpr): int {.base.} = # override this base method quit "to override!" @@ -27,20 +24,18 @@ method eval(e: ref TPlusExpr): int = proc newLit(x: int): ref TLiteral = new(result) - {.watchpoint: result.} result.x = x result.op1 = $getOccupiedMem() -proc newPlus(a, b: ref TExpr): ref TPlusExpr = +proc newPlus(a, b: sink(ref TExpr)): ref TPlusExpr = new(result) - {.watchpoint: result.} result.a = a result.b = b result.op2 = $getOccupiedMem() -const Limit = when compileOption("gc", "markAndSweep"): 5*1024*1024 else: 500_000 +const Limit = when compileOption("gc", "markAndSweep") or compileOption("gc", "boehm"): 5*1024*1024 else: 500_000 -for i in 0..100_000: +for i in 0..50_000: var s: array[0..11, ref TExpr] for j in 0..high(s): s[j] = newPlus(newPlus(newLit(j), newLit(2)), newLit(4)) diff --git a/tests/gc/gcleak5.nim b/tests/gc/gcleak5.nim index 6ab50e19e..f1913831b 100644 --- a/tests/gc/gcleak5.nim +++ b/tests/gc/gcleak5.nim @@ -9,7 +9,7 @@ proc main = for ii in 0..50_000: #while true: var t = getTime() - var g = t.getGMTime() + var g = t.utc() #echo isOnStack(addr g) if i mod 100 == 0: diff --git a/tests/gc/gctest.nim b/tests/gc/gctest.nim index b3b9af608..78b78934c 100644 --- a/tests/gc/gctest.nim +++ b/tests/gc/gctest.nim @@ -31,7 +31,7 @@ type of nkList: sons: seq[PCaseNode] else: unused: seq[string] - TIdObj* = object of TObject + TIdObj* = object of RootObj id*: int # unique id; use this for comparisons and not the pointers PIdObj* = ref TIdObj @@ -45,19 +45,14 @@ var flip: int proc newCaseNode(data: string): PCaseNode = - new(result) if flip == 0: - result.kind = nkStr - result.data = data + result = PCaseNode(kind: nkStr, data: data) else: - result.kind = nkWhole - result.unused = @["", "abc", "abdc"] + result = PCaseNode(kind: nkWhole, unused: @["", "abc", "abdc"]) flip = 1 - flip proc newCaseNode(a, b: PCaseNode): PCaseNode = - new(result) - result.kind = nkList - result.sons = @[a, b] + result = PCaseNode(kind: nkList, sons: @[a, b]) proc caseTree(lvl: int = 0): PCaseNode = if lvl == 3: result = newCaseNode("data item") @@ -66,8 +61,7 @@ proc caseTree(lvl: int = 0): PCaseNode = proc finalizeNode(n: PNode) = assert(n != nil) write(stdout, "finalizing: ") - if isNil(n.data): writeLine(stdout, "nil!") - else: writeLine(stdout, "not nil") + writeLine(stdout, "not nil") var id: int = 1 @@ -179,24 +173,38 @@ proc main() = write(stdout, "done!\n") var - father: TBNode - s: string -s = "" -s = "" -writeLine(stdout, repr(caseTree())) -father.t.data = @["ha", "lets", "stress", "it"] -father.t.data = @["ha", "lets", "stress", "it"] -var t = buildTree() -write(stdout, repr(t[])) -buildBTree(father) -write(stdout, repr(father)) - -write(stdout, "starting main...\n") -main() - -GC_fullCollect() -# the M&S GC fails with this call and it's unclear why. Definitely something -# we need to fix! -GC_fullCollect() -writeLine(stdout, GC_getStatistics()) -write(stdout, "finished\n") + father {.threadvar.}: TBNode + s {.threadvar.}: string + + fatherAsGlobal: TBNode + +proc start = + s = "" + s = "" + writeLine(stdout, repr(caseTree())) + father.t.data = @["ha", "lets", "stress", "it"] + father.t.data = @["ha", "lets", "stress", "it"] + var t = buildTree() + write(stdout, repr(t[])) + buildBTree(father) + write(stdout, repr(father)) + + write(stdout, "starting main...\n") + main() + + GC_fullCollect() + # the M&S GC fails with this call and it's unclear why. Definitely something + # we need to fix! + #GC_fullCollect() + writeLine(stdout, GC_getStatistics()) + write(stdout, "finished\n") + +fatherAsGlobal.t.data = @["ha", "lets", "stress", "it"] +var tg = buildTree() +buildBTree(fatherAsGlobal) + +var thr: array[8, Thread[void]] +for i in low(thr)..high(thr): + createThread(thr[i], start) +joinThreads(thr) +start() diff --git a/tests/gc/gctest.nim.cfg b/tests/gc/gctest.nim.cfg new file mode 100644 index 000000000..aed303eef --- /dev/null +++ b/tests/gc/gctest.nim.cfg @@ -0,0 +1 @@ +--threads:on diff --git a/tests/gc/growobjcrash.nim b/tests/gc/growobjcrash.nim index a16468c7e..ff1aa7e98 100644 --- a/tests/gc/growobjcrash.nim +++ b/tests/gc/growobjcrash.nim @@ -1,8 +1,4 @@ -discard """ - output: "works" -""" - -import cgi, strtabs +import std/[cgi, strtabs] proc handleRequest(query: string): StringTableRef = iterator foo(): StringTableRef {.closure.} = @@ -14,11 +10,11 @@ proc handleRequest(query: string): StringTableRef = let x = foo result = x() -const Limit = when compileOption("gc", "markAndSweep"): 5*1024*1024 else: 700_000 +const Limit = 5*1024*1024 proc main = var counter = 0 - for i in 0 .. 100_000: + for i in 0 .. 10_000: for k, v in handleRequest("nick=Elina2&type=activate"): inc counter if counter mod 100 == 0: @@ -26,4 +22,3 @@ proc main = quit "but now a leak" main() -echo "works" diff --git a/tests/gc/panicoverride.nim b/tests/gc/panicoverride.nim new file mode 100644 index 000000000..0f28b0b72 --- /dev/null +++ b/tests/gc/panicoverride.nim @@ -0,0 +1,14 @@ + +proc printf(frmt: cstring) {.varargs, importc, header: "<stdio.h>", cdecl.} +proc exit(code: int) {.importc, header: "<stdlib.h>", cdecl.} + +{.push stack_trace: off, profiler:off.} + +proc rawoutput(s: string) = + printf("%s\n", s) + +proc panic(s: string) {.noreturn.} = + rawoutput(s) + exit(1) + +{.pop.} \ No newline at end of file diff --git a/tests/gc/stackrefleak.nim b/tests/gc/stackrefleak.nim index 302ef3599..7f3fbff43 100644 --- a/tests/gc/stackrefleak.nim +++ b/tests/gc/stackrefleak.nim @@ -12,7 +12,8 @@ type proc makePair: PCyclic = new(result) new(result.sibling) - result.sibling.sibling = result + when not defined(gcDestructors): + result.sibling.sibling = result proc loop = for i in 0..10000: diff --git a/tests/gc/tdisable_orc.nim b/tests/gc/tdisable_orc.nim new file mode 100644 index 000000000..b5f161c79 --- /dev/null +++ b/tests/gc/tdisable_orc.nim @@ -0,0 +1,9 @@ +discard """ + joinable: false +""" + +import std/asyncdispatch + +# bug #22256 +GC_disableMarkAndSweep() +waitFor sleepAsync(1000) diff --git a/tests/gc/thavlak.nim b/tests/gc/thavlak.nim index efab49e36..cfd860e25 100644 --- a/tests/gc/thavlak.nim +++ b/tests/gc/thavlak.nim @@ -1,51 +1,55 @@ discard """ output: '''Welcome to LoopTesterApp, Nim edition Constructing Simple CFG... -15000 dummy loops +5000 dummy loops Constructing CFG... Performing Loop Recognition 1 Iteration -Another 50 iterations... -.................................................. -Found 1 loops (including artificial root node) (50)''' +Another 3 iterations... +... +Found 1 loops (including artificial root node) (3)''' """ # bug #3184 -import tables -import sequtils -import sets +import tables, sets + +when not declared(withScratchRegion): + template withScratchRegion(body: untyped) = body type - BasicBlock = object - inEdges: seq[ref BasicBlock] - outEdges: seq[ref BasicBlock] + BasicBlock = ref object + inEdges: seq[BasicBlock] + outEdges: seq[BasicBlock] name: int -proc newBasicBlock(name: int): ref BasicBlock = - new(result) - result.inEdges = newSeq[ref BasicBlock]() - result.outEdges = newSeq[ref BasicBlock]() - result.name = name +proc newBasicBlock(name: int): BasicBlock = + result = BasicBlock( + inEdges: newSeq[BasicBlock](), + outEdges: newSeq[BasicBlock](), + name: name + ) -proc hash(x: ref BasicBlock): int {.inline.} = +proc hash(x: BasicBlock): int {.inline.} = result = x.name type BasicBlockEdge = object - fr: ref BasicBlock - to: ref BasicBlock + fr: BasicBlock + to: BasicBlock Cfg = object - basicBlockMap: Table[int, ref BasicBlock] + basicBlockMap: Table[int, BasicBlock] edgeList: seq[BasicBlockEdge] - startNode: ref BasicBlock + startNode: BasicBlock proc newCfg(): Cfg = - result.basicBlockMap = initTable[int, ref BasicBlock]() - result.edgeList = newSeq[BasicBlockEdge]() + result = Cfg( + basicBlockMap: initTable[int, BasicBlock](), + edgeList: newSeq[BasicBlockEdge](), + startNode: nil) -proc createNode(self: var Cfg, name: int): ref BasicBlock = +proc createNode(self: var Cfg, name: int): BasicBlock = result = self.basicBlockMap.getOrDefault(name) if result == nil: result = newBasicBlock(name) @@ -54,128 +58,94 @@ proc createNode(self: var Cfg, name: int): ref BasicBlock = if self.startNode == nil: self.startNode = result -proc addEdge(self: var Cfg, edge: BasicBlockEdge) = - self.edgeList.add(edge) - -proc getNumNodes(self: Cfg): int = - self.basicBlockMap.len - -proc newBasicBlockEdge(cfg: var Cfg, fromName: int, toName: int): BasicBlockEdge = - result.fr = cfg.createNode(fromName) - result.to = cfg.createNode(toName) +proc newBasicBlockEdge(cfg: var Cfg, fromName, toName: int) = + var result = BasicBlockEdge( + fr: cfg.createNode(fromName), + to: cfg.createNode(toName) + ) result.fr.outEdges.add(result.to) result.to.inEdges.add(result.fr) - cfg.addEdge(result) + cfg.edgeList.add(result) type - SimpleLoop = object - basicBlocks: seq[ref BasicBlock] # TODO: set here - children: seq[ref SimpleLoop] # TODO: set here - parent: ref SimpleLoop - header: ref BasicBlock - isRoot: bool - isReducible: bool - counter: int - nestingLevel: int - depthLevel: int - -proc newSimpleLoop(): ref SimpleLoop = - new(result) - result.basicBlocks = newSeq[ref BasicBlock]() - result.children = newSeq[ref SimpleLoop]() - result.parent = nil - result.header = nil - result.isRoot = false - result.isReducible = true - result.counter = 0 - result.nestingLevel = 0 - result.depthLevel = 0 - -proc addNode(self: ref SimpleLoop, bb: ref BasicBlock) = - self.basicBlocks.add bb - -proc addChildLoop(self: ref SimpleLoop, loop: ref SimpleLoop) = - self.children.add loop - -proc setParent(self: ref SimpleLoop, parent: ref SimpleLoop) = + SimpleLoop = ref object + basicBlocks: seq[BasicBlock] # TODO: set here + children: seq[SimpleLoop] # TODO: set here + parent: SimpleLoop + header: BasicBlock + isRoot, isReducible: bool + counter, nestingLevel, depthLevel: int + +proc setParent(self: SimpleLoop, parent: SimpleLoop) = self.parent = parent - self.parent.addChildLoop(self) + self.parent.children.add self -proc setHeader(self: ref SimpleLoop, bb: ref BasicBlock) = +proc setHeader(self: SimpleLoop, bb: BasicBlock) = self.basicBlocks.add(bb) self.header = bb -proc setNestingLevel(self: ref SimpleLoop, level: int) = +proc setNestingLevel(self: SimpleLoop, level: int) = self.nestingLevel = level if level == 0: self.isRoot = true -var loop_counter: int = 0 +var loopCounter: int = 0 type Lsg = object - loops: seq[ref SimpleLoop] - root: ref SimpleLoop - -proc createNewLoop(self: var Lsg): ref SimpleLoop = - result = newSimpleLoop() - loop_counter += 1 - result.counter = loop_counter - -proc addLoop(self: var Lsg, l: ref SimpleLoop) = + loops: seq[SimpleLoop] + root: SimpleLoop + +proc createNewLoop(self: var Lsg): SimpleLoop = + result = SimpleLoop( + basicBlocks: newSeq[BasicBlock](), + children: newSeq[SimpleLoop](), + isReducible: true) + loopCounter += 1 + result.counter = loopCounter + +proc addLoop(self: var Lsg, l: SimpleLoop) = self.loops.add l proc newLsg(): Lsg = - result.loops = newSeq[ref SimpleLoop]() - result.root = result.createNewLoop() + result = Lsg(loops: newSeq[SimpleLoop](), + root: result.createNewLoop()) result.root.setNestingLevel(0) result.addLoop(result.root) -proc getNumLoops(self: Lsg): int = - self.loops.len - type - UnionFindNode = object - parent: ref UnionFindNode - bb: ref BasicBlock - l: ref SimpleLoop + UnionFindNode = ref object + parent {.cursor.}: UnionFindNode + bb: BasicBlock + l: SimpleLoop dfsNumber: int -proc newUnionFindNode(): ref UnionFindNode = - new(result) - when false: - result.parent = nil - result.bb = nil - result.l = nil - result.dfsNumber = 0 - -proc initNode(self: ref UnionFindNode, bb: ref BasicBlock, dfsNumber: int) = +proc initNode(self: UnionFindNode, bb: BasicBlock, dfsNumber: int) = self.parent = self self.bb = bb self.dfsNumber = dfsNumber -proc findSet(self: ref UnionFindNode): ref UnionFindNode = - var nodeList = newSeq[ref UnionFindNode]() - result = self +proc findSet(self: UnionFindNode): UnionFindNode = + var nodeList = newSeq[UnionFindNode]() + var it {.cursor.} = self - while result != result.parent: - var parent = result.parent - if parent != parent.parent: nodeList.add result - result = parent + while it != it.parent: + var parent {.cursor.} = it.parent + if parent != parent.parent: nodeList.add it + it = parent - for iter in nodeList: iter.parent = result.parent + for iter in nodeList: iter.parent = it.parent + result = it -proc union(self: ref UnionFindNode, unionFindNode: ref UnionFindNode) = +proc union(self: UnionFindNode, unionFindNode: UnionFindNode) = self.parent = unionFindNode const - BB_TOP = 0 # uninitialized - BB_NONHEADER = 1 # a regular BB - BB_REDUCIBLE = 2 # reducible loop - BB_SELF = 3 # single BB loop - BB_IRREDUCIBLE = 4 # irreducible loop - BB_DEAD = 5 # a dead BB - BB_LAST = 6 # Sentinel + BB_NONHEADER = 1 # a regular BB + BB_REDUCIBLE = 2 # reducible loop + BB_SELF = 3 # single BB loop + BB_IRREDUCIBLE = 4 # irreducible loop + BB_DEAD = 5 # a dead BB # # Marker for uninitialized nodes. UNVISITED = -1 @@ -188,44 +158,44 @@ type cfg: Cfg lsg: Lsg -proc newHavlakLoopFinder(cfg: Cfg, lsg: Lsg): HavlakLoopFinder = - result.cfg = cfg - result.lsg = lsg +proc newHavlakLoopFinder(cfg: Cfg, lsg: sink Lsg): HavlakLoopFinder = + result = HavlakLoopFinder(cfg: cfg, lsg: lsg) -proc isAncestor(w: int, v: int, last: seq[int]): bool = +proc isAncestor(w, v: int, last: seq[int]): bool = w <= v and v <= last[w] -proc dfs(currentNode: ref BasicBlock, nodes: var seq[ref UnionFindNode], number: var Table[ref BasicBlock, int], last: var seq[int], current: int): int = +proc dfs(currentNode: BasicBlock, nodes: var seq[UnionFindNode], + number: var Table[BasicBlock, int], + last: var seq[int], current: int) = var stack = @[(currentNode, current)] while stack.len > 0: let (currentNode, current) = stack.pop() nodes[current].initNode(currentNode, current) number[currentNode] = current - result = current for target in currentNode.outEdges: if number[target] == UNVISITED: - stack.add((target, result+1)) + stack.add((target, current+1)) #result = dfs(target, nodes, number, last, result + 1) - last[number[currentNode]] = result + last[number[currentNode]] = current proc findLoops(self: var HavlakLoopFinder): int = var startNode = self.cfg.startNode if startNode == nil: return 0 - var size = self.cfg.getNumNodes + var size = self.cfg.basicBlockMap.len - var nonBackPreds = newSeq[HashSet[int]]() - var backPreds = newSeq[seq[int]]() - var number = initTable[ref BasicBlock, int]() - var header = newSeq[int](size) - var types = newSeq[int](size) - var last = newSeq[int](size) - var nodes = newSeq[ref UnionFindNode]() + var nonBackPreds = newSeq[HashSet[int]]() + var backPreds = newSeq[seq[int]]() + var number = initTable[BasicBlock, int]() + var header = newSeq[int](size) + var types = newSeq[int](size) + var last = newSeq[int](size) + var nodes = newSeq[UnionFindNode]() for i in 1..size: - nonBackPreds.add initSet[int](1) + nonBackPreds.add initHashSet[int](1) backPreds.add newSeq[int]() - nodes.add newUnionFindNode() + nodes.add(UnionFindNode()) # Step a: # - initialize all nodes as unvisited. @@ -233,7 +203,7 @@ proc findLoops(self: var HavlakLoopFinder): int = # - unreached BB's are marked as dead. # for v in self.cfg.basicBlockMap.values: number[v] = UNVISITED - var res = dfs(startNode, nodes, number, last, 0) + dfs(startNode, nodes, number, last, 0) # Step b: # - iterate over all nodes. @@ -245,7 +215,7 @@ proc findLoops(self: var HavlakLoopFinder): int = # - the list of backedges (backPreds) or # - the list of non-backedges (nonBackPreds) # - for w in 0 .. <size: + for w in 0 ..< size: header[w] = 0 types[w] = BB_NONHEADER @@ -278,7 +248,7 @@ proc findLoops(self: var HavlakLoopFinder): int = for w in countdown(size - 1, 0): # this is 'P' in Havlak's paper - var nodePool = newSeq[ref UnionFindNode]() + var nodePool = newSeq[UnionFindNode]() var nodeW = nodes[w].bb if nodeW != nil: # dead BB @@ -291,7 +261,7 @@ proc findLoops(self: var HavlakLoopFinder): int = # Copy nodePool to workList. # - var workList = newSeq[ref UnionFindNode]() + var workList = newSeq[UnionFindNode]() for x in nodePool: workList.add x if nodePool.len != 0: types[w] = BB_REDUCIBLE @@ -299,7 +269,7 @@ proc findLoops(self: var HavlakLoopFinder): int = # work the list... # while workList.len > 0: - var x = workList[0] + let x = workList[0] workList.del(0) # Step e: @@ -332,7 +302,7 @@ proc findLoops(self: var HavlakLoopFinder): int = # Collapse/Unionize nodes in a SCC to a single node # For every SCC found, create a loop descriptor and link it in. # - if (nodePool.len > 0) or (types[w] == BB_SELF): + if nodePool.len > 0 or types[w] == BB_SELF: var l = self.lsg.createNewLoop l.setHeader(nodeW) @@ -358,15 +328,15 @@ proc findLoops(self: var HavlakLoopFinder): int = node.union(nodes[w]) # Nested loops are not added, but linked together. - var node_l = node.l - if node_l != nil: - node_l.setParent(l) + var nodeL = node.l + if nodeL != nil: + nodeL.setParent(l) else: - l.addNode(node.bb) + l.basicBlocks.add node.bb self.lsg.addLoop(l) - result = self.lsg.getNumLoops + result = self.lsg.loops.len type @@ -379,27 +349,26 @@ proc newLoopTesterApp(): LoopTesterApp = result.lsg = newLsg() proc buildDiamond(self: var LoopTesterApp, start: int): int = - var bb0 = start - var x1 = newBasicBlockEdge(self.cfg, bb0, bb0 + 1) - var x2 = newBasicBlockEdge(self.cfg, bb0, bb0 + 2) - var x3 = newBasicBlockEdge(self.cfg, bb0 + 1, bb0 + 3) - var x4 = newBasicBlockEdge(self.cfg, bb0 + 2, bb0 + 3) - result = bb0 + 3 + newBasicBlockEdge(self.cfg, start, start + 1) + newBasicBlockEdge(self.cfg, start, start + 2) + newBasicBlockEdge(self.cfg, start + 1, start + 3) + newBasicBlockEdge(self.cfg, start + 2, start + 3) + result = start + 3 -proc buildConnect(self: var LoopTesterApp, start1: int, end1: int) = - var x1 = newBasicBlockEdge(self.cfg, start1, end1) +proc buildConnect(self: var LoopTesterApp, start1, end1: int) = + newBasicBlockEdge(self.cfg, start1, end1) -proc buildStraight(self: var LoopTesterApp, start: int, n: int): int = +proc buildStraight(self: var LoopTesterApp, start, n: int): int = for i in 0..n-1: self.buildConnect(start + i, start + i + 1) result = start + n proc buildBaseLoop(self: var LoopTesterApp, from1: int): int = - var header = self.buildStraight(from1, 1) - var diamond1 = self.buildDiamond(header) - var d11 = self.buildStraight(diamond1, 1) - var diamond2 = self.buildDiamond(d11) - var footer = self.buildStraight(diamond2, 1) + let header = self.buildStraight(from1, 1) + let diamond1 = self.buildDiamond(header) + let d11 = self.buildStraight(diamond1, 1) + let diamond2 = self.buildDiamond(d11) + let footer = self.buildStraight(diamond2, 1) self.buildConnect(diamond2, d11) self.buildConnect(diamond1, header) @@ -410,48 +379,63 @@ proc run(self: var LoopTesterApp) = echo "Welcome to LoopTesterApp, Nim edition" echo "Constructing Simple CFG..." - var x1 = self.cfg.createNode(0) - var x2 = self.buildBaseLoop(0) - var x3 = self.cfg.createNode(1) + discard self.cfg.createNode(0) + discard self.buildBaseLoop(0) + discard self.cfg.createNode(1) self.buildConnect(0, 2) - echo "15000 dummy loops" + echo "5000 dummy loops" - for i in 1..15000: - var h = newHavlakLoopFinder(self.cfg, newLsg()) - var res = h.findLoops + for i in 1..5000: + withScratchRegion: + var h = newHavlakLoopFinder(self.cfg, newLsg()) + discard h.findLoops echo "Constructing CFG..." var n = 2 - for parlooptrees in 1..10: - var x6 = self.cfg.createNode(n + 1) - self.buildConnect(2, n + 1) - n += 1 - for i in 1..100: - var top = n - n = self.buildStraight(n, 1) - for j in 1..25: n = self.buildBaseLoop(n) - var bottom = self.buildStraight(n, 1) - self.buildConnect n, top - n = bottom - self.buildConnect(n, 1) + when true: # not defined(gcOrc): + # currently cycle detection is so slow that we disable this part + for parlooptrees in 1..10: + discard self.cfg.createNode(n + 1) + self.buildConnect(2, n + 1) + n += 1 + for i in 1..100: + var top = n + n = self.buildStraight(n, 1) + for j in 1..25: n = self.buildBaseLoop(n) + var bottom = self.buildStraight(n, 1) + self.buildConnect n, top + n = bottom + self.buildConnect(n, 1) echo "Performing Loop Recognition\n1 Iteration" var h = newHavlakLoopFinder(self.cfg, newLsg()) var loops = h.findLoops - echo "Another 50 iterations..." + echo "Another 3 iterations..." var sum = 0 - for i in 1..50: - write stdout, "." - flushFile(stdout) - var hlf = newHavlakLoopFinder(self.cfg, newLsg()) - sum += hlf.findLoops - #echo getOccupiedMem() + for i in 1..3: + withScratchRegion: + write stdout, "." + flushFile(stdout) + var hlf = newHavlakLoopFinder(self.cfg, newLsg()) + sum += hlf.findLoops + #echo getOccupiedMem() echo "\nFound ", loops, " loops (including artificial root node) (", sum, ")" -var l = newLoopTesterApp() -l.run + when false: + echo("Total memory available: " & formatSize(getTotalMem()) & " bytes") + echo("Free memory: " & formatSize(getFreeMem()) & " bytes") + +proc main = + var l = newLoopTesterApp() + l.run + +let mem = getOccupiedMem() +main() +when defined(gcOrc): + GC_fullCollect() + doAssert getOccupiedMem() == mem diff --git a/tests/gc/tlists.nim b/tests/gc/tlists.nim index 26b32396c..959cc5f7c 100644 --- a/tests/gc/tlists.nim +++ b/tests/gc/tlists.nim @@ -10,15 +10,13 @@ import lists import strutils proc mkleak() = - # allocate 10 MB via linked lists + # allocate 1 MB via linked lists let numberOfLists = 100 for i in countUp(1, numberOfLists): var leakList = initDoublyLinkedList[string]() - let numberOfLeaks = 50000 + let numberOfLeaks = 5000 for j in countUp(1, numberOfLeaks): - let leakSize = 200 - let leaked = newString(leakSize) - leakList.append(leaked) + leakList.append(newString(200)) proc mkManyLeaks() = for i in 0..0: @@ -29,7 +27,7 @@ proc mkManyLeaks() = # lists and bring the memory usage down to a few MB's. GC_fullCollect() when false: echo getOccupiedMem() - if getOccupiedMem() > 8 * 200 * 50_000 * 2: + if getOccupiedMem() > 8 * 200 * 5000 * 2: echo GC_getStatistics() quit "leaking" echo "Success" diff --git a/tests/gc/trace_globals.nim b/tests/gc/trace_globals.nim new file mode 100644 index 000000000..f62a15692 --- /dev/null +++ b/tests/gc/trace_globals.nim @@ -0,0 +1,33 @@ +discard """ + output: ''' +10000000 +10000000 +10000000''' +""" + +# bug #17085 + +#[ +refs https://github.com/nim-lang/Nim/issues/17085#issuecomment-786466595 +with --gc:boehm, this warning sometimes gets generated: +Warning: Repeated allocation of very large block (appr. size 14880768): +May lead to memory leak and poor performance. +nim CI now runs this test with `testWithoutBoehm` to avoid running it with --gc:boehm. +]# + +proc init(): string = + for a in 0..<10000000: + result.add 'c' + +proc f() = + var a {.global.} = init() + var b {.global.} = init() + var c {.global.} = init() + + echo a.len + # `echo` intentional according to + # https://github.com/nim-lang/Nim/pull/17469/files/0c9e94cb6b9ebca9da7cb19a063fba7aa409748e#r600016573 + echo b.len + echo c.len + +f() diff --git a/tests/gc/tregionleak.nim b/tests/gc/tregionleak.nim new file mode 100644 index 000000000..277cfc987 --- /dev/null +++ b/tests/gc/tregionleak.nim @@ -0,0 +1,23 @@ +discard """ + cmd: '''nim c --gc:regions $file''' + output: ''' +finalized +finalized +''' +""" + +proc finish(o: RootRef) = + echo "finalized" + +withScratchRegion: + var test: RootRef + new(test, finish) + +var + mr: MemRegion + test: RootRef + +withRegion(mr): + new(test, finish) + +deallocAll(mr) diff --git a/tests/gc/tstandalone.nim b/tests/gc/tstandalone.nim new file mode 100644 index 000000000..41dad9ba4 --- /dev/null +++ b/tests/gc/tstandalone.nim @@ -0,0 +1,14 @@ +discard """ + matrix: "--os:standalone --gc:none" + exitcode: 1 + output: "value out of range" +""" + +type + rangeType = range[0..1] + +var + r: rangeType = 0 + i = 2 + +r = rangeType(i) diff --git a/tests/gc/weakrefs.nim b/tests/gc/weakrefs.nim index 0a6d4b873..81c048d74 100644 --- a/tests/gc/weakrefs.nim +++ b/tests/gc/weakrefs.nim @@ -19,8 +19,15 @@ var proc finalizer(x: StrongObject) = valid.excl(x.id) +when defined(gcDestructors): + proc `=destroy`(x: var TMyObject) = + valid.excl(x.id) + proc create: StrongObject = - new(result, finalizer) + when defined(gcDestructors): + new(result) + else: + new(result, finalizer) result.id = gid valid.incl(gid) inc gid |