diff options
Diffstat (limited to 'lib/system/gc_common.nim')
-rw-r--r-- | lib/system/gc_common.nim | 251 |
1 files changed, 140 insertions, 111 deletions
diff --git a/lib/system/gc_common.nim b/lib/system/gc_common.nim index 939776a58..eb0884560 100644 --- a/lib/system/gc_common.nim +++ b/lib/system/gc_common.nim @@ -37,50 +37,61 @@ when defined(nimTypeNames): a[j] = v if h == 1: break - proc dumpNumberOfInstances* = - # also add the allocated strings to the list of known types: + iterator dumpHeapInstances*(): tuple[name: cstring; count: int; sizes: int] = + ## Iterate over summaries of types on heaps. + ## This data may be inaccurate if allocations + ## are made by the iterator body. if strDesc.nextType == nil: strDesc.nextType = nimTypeRoot strDesc.name = "string" nimTypeRoot = addr strDesc + var it = nimTypeRoot + while it != nil: + if (it.instances > 0 or it.sizes != 0): + yield (it.name, it.instances, it.sizes) + it = it.nextType + + proc dumpNumberOfInstances* = var a: InstancesInfo var n = 0 - var it = nimTypeRoot var totalAllocated = 0 - while it != nil: - if (it.instances > 0 or it.sizes != 0) and n < a.len: - a[n] = (it.name, it.instances, it.sizes) - inc n + for it in dumpHeapInstances(): + a[n] = it + inc n inc totalAllocated, it.sizes - it = it.nextType sortInstances(a, n) for i in 0 .. n-1: - c_fprintf(stdout, "[Heap] %s: #%ld; bytes: %ld\n", a[i][0], a[i][1], a[i][2]) - c_fprintf(stdout, "[Heap] total number of bytes: %ld\n", totalAllocated) + c_fprintf(cstdout, "[Heap] %s: #%ld; bytes: %ld\n", a[i][0], a[i][1], a[i][2]) + c_fprintf(cstdout, "[Heap] total number of bytes: %ld\n", totalAllocated) + when defined(nimTypeNames): + let (allocs, deallocs) = getMemCounters() + c_fprintf(cstdout, "[Heap] allocs/deallocs: %ld/%ld\n", allocs, deallocs) when defined(nimGcRefLeak): proc oomhandler() = - c_fprintf(stdout, "[Heap] ROOTS: #%ld\n", gch.additionalRoots.len) + c_fprintf(cstdout, "[Heap] ROOTS: #%ld\n", gch.additionalRoots.len) writeLeaks() outOfMemHook = oomhandler template decTypeSize(cell, t) = - # XXX this needs to use atomics for multithreaded apps! when defined(nimTypeNames): if t.kind in {tyString, tySequence}: let cap = cast[PGenericSeq](cellToUsr(cell)).space - let size = if t.kind == tyString: cap+1+GenericSeqSize - else: addInt(mulInt(cap, t.base.size), GenericSeqSize) - dec t.sizes, size+sizeof(Cell) + let size = + if t.kind == tyString: + cap + 1 + GenericSeqSize + else: + align(GenericSeqSize, t.base.align) + cap * t.base.size + atomicDec t.sizes, size+sizeof(Cell) else: - dec t.sizes, t.base.size+sizeof(Cell) - dec t.instances + atomicDec t.sizes, t.base.size+sizeof(Cell) + atomicDec t.instances template incTypeSize(typ, size) = when defined(nimTypeNames): - inc typ.instances - inc typ.sizes, size+sizeof(Cell) + atomicInc typ.instances + atomicInc typ.sizes, size+sizeof(Cell) proc dispose*(x: ForeignCell) = when hasThreadSupport: @@ -148,69 +159,34 @@ else: iterator items(first: var GcStack): ptr GcStack = yield addr(first) proc len(stack: var GcStack): int = 1 -proc stackSize(stack: ptr GcStack): int {.noinline.} = - when nimCoroutines: - var pos = stack.pos - else: - var pos {.volatile.}: pointer - pos = addr(pos) - - if pos != nil: - when defined(stackIncreases): - result = cast[ByteAddress](pos) -% cast[ByteAddress](stack.bottom) - else: - result = cast[ByteAddress](stack.bottom) -% cast[ByteAddress](pos) - else: - result = 0 - -proc stackSize(): int {.noinline.} = - for stack in gch.stack.items(): - result = result + stack.stackSize() - -when nimCoroutines: - proc setPosition(stack: ptr GcStack, position: pointer) = - stack.pos = position - stack.maxStackSize = max(stack.maxStackSize, stack.stackSize()) - - proc setPosition(stack: var GcStack, position: pointer) = - setPosition(addr(stack), position) - - proc getActiveStack(gch: var GcHeap): ptr GcStack = - return gch.activeStack - - proc isActiveStack(stack: ptr GcStack): bool = - return gch.activeStack == stack -else: - # Stack positions do not need to be tracked if coroutines are not used. - proc setPosition(stack: ptr GcStack, position: pointer) = discard - proc setPosition(stack: var GcStack, position: pointer) = discard - # There is just one stack - main stack of the thread. It is active always. - proc getActiveStack(gch: var GcHeap): ptr GcStack = addr(gch.stack) - proc isActiveStack(stack: ptr GcStack): bool = true - -when declared(threadType): +when defined(nimdoc): proc setupForeignThreadGc*() {.gcsafe.} = ## Call this if you registered a callback that will be run from a thread not ## under your control. This has a cheap thread-local guard, so the GC for ## this thread will only be initialized once per thread, no matter how often ## it is called. ## - ## This function is available only when ``--threads:on`` and ``--tlsEmulation:off`` + ## This function is available only when `--threads:on` and `--tlsEmulation:off` + ## switches are used + discard + + proc tearDownForeignThreadGc*() {.gcsafe.} = + ## Call this to tear down the GC, previously initialized by `setupForeignThreadGc`. + ## If GC has not been previously initialized, or has already been torn down, the + ## call does nothing. + ## + ## This function is available only when `--threads:on` and `--tlsEmulation:off` ## switches are used + discard +elif declared(threadType): + proc setupForeignThreadGc*() {.gcsafe.} = if threadType == ThreadType.None: - initAllocator() var stackTop {.volatile.}: pointer nimGC_setStackBottom(addr(stackTop)) initGC() threadType = ThreadType.ForeignThread proc tearDownForeignThreadGc*() {.gcsafe.} = - ## Call this to tear down the GC, previously initialized by ``setupForeignThreadGc``. - ## If GC has not been previously initialized, or has already been torn down, the - ## call does nothing. - ## - ## This function is available only when ``--threads:on`` and ``--tlsEmulation:off`` - ## switches are used if threadType != ThreadType.ForeignThread: return when declared(deallocOsPages): deallocOsPages() @@ -227,7 +203,7 @@ else: # ----------------- stack management -------------------------------------- # inspired from Smart Eiffel -when defined(emscripten): +when defined(emscripten) or defined(wasm): const stackIncreases = true elif defined(sparc): const stackIncreases = false @@ -237,25 +213,69 @@ elif defined(hppa) or defined(hp9000) or defined(hp9000s300) or else: const stackIncreases = false +proc stackSize(stack: ptr GcStack): int {.noinline.} = + when nimCoroutines: + var pos = stack.pos + else: + var pos {.volatile, noinit.}: pointer + pos = addr(pos) + + if pos != nil: + when stackIncreases: + result = cast[int](pos) -% cast[int](stack.bottom) + else: + result = cast[int](stack.bottom) -% cast[int](pos) + else: + result = 0 + +proc stackSize(): int {.noinline.} = + result = 0 + for stack in gch.stack.items(): + result = result + stack.stackSize() + +when nimCoroutines: + proc setPosition(stack: ptr GcStack, position: pointer) = + stack.pos = position + stack.maxStackSize = max(stack.maxStackSize, stack.stackSize()) + + proc setPosition(stack: var GcStack, position: pointer) = + setPosition(addr(stack), position) + + proc getActiveStack(gch: var GcHeap): ptr GcStack = + return gch.activeStack + + proc isActiveStack(stack: ptr GcStack): bool = + return gch.activeStack == stack +else: + # Stack positions do not need to be tracked if coroutines are not used. + proc setPosition(stack: ptr GcStack, position: pointer) = discard + proc setPosition(stack: var GcStack, position: pointer) = discard + # There is just one stack - main stack of the thread. It is active always. + proc getActiveStack(gch: var GcHeap): ptr GcStack = addr(gch.stack) + proc isActiveStack(stack: ptr GcStack): bool = true + {.push stack_trace: off.} when nimCoroutines: - proc GC_addStack(bottom: pointer) {.cdecl, exportc.} = + proc GC_addStack(bottom: pointer) {.cdecl, dynlib, exportc.} = # c_fprintf(stdout, "GC_addStack: %p;\n", bottom) var stack = gch.stack.append() stack.bottom = bottom stack.setPosition(bottom) - proc GC_removeStack(bottom: pointer) {.cdecl, exportc.} = + proc GC_removeStack(bottom: pointer) {.cdecl, dynlib, exportc.} = # c_fprintf(stdout, "GC_removeStack: %p;\n", bottom) gch.stack.find(bottom).remove() - proc GC_setActiveStack(bottom: pointer) {.cdecl, exportc.} = + proc GC_setActiveStack(bottom: pointer) {.cdecl, dynlib, exportc.} = ## Sets active stack and updates current stack position. # c_fprintf(stdout, "GC_setActiveStack: %p;\n", bottom) var sp {.volatile.}: pointer gch.activeStack = gch.stack.find(bottom) gch.activeStack.setPosition(addr(sp)) + proc GC_getActiveStack() : pointer {.cdecl, exportc.} = + return gch.activeStack.bottom + when not defined(useNimRtl): proc nimGC_setStackBottom(theStackBottom: pointer) = # Initializes main stack of the thread. @@ -275,25 +295,28 @@ when not defined(useNimRtl): # the first init must be the one that defines the stack bottom: gch.stack.bottom = theStackBottom elif theStackBottom != gch.stack.bottom: - var a = cast[ByteAddress](theStackBottom) # and not PageMask - PageSize*2 - var b = cast[ByteAddress](gch.stack.bottom) + var a = cast[int](theStackBottom) # and not PageMask - PageSize*2 + var b = cast[int](gch.stack.bottom) #c_fprintf(stdout, "old: %p new: %p;\n",gch.stack.bottom,theStackBottom) when stackIncreases: gch.stack.bottom = cast[pointer](min(a, b)) else: gch.stack.bottom = cast[pointer](max(a, b)) + when nimCoroutines: + if theStackBottom != nil: gch.stack.bottom = theStackBottom + gch.stack.setPosition(theStackBottom) {.pop.} proc isOnStack(p: pointer): bool = - var stackTop {.volatile.}: pointer + var stackTop {.volatile, noinit.}: pointer stackTop = addr(stackTop) - var a = cast[ByteAddress](gch.getActiveStack().bottom) - var b = cast[ByteAddress](stackTop) + var a = cast[int](gch.getActiveStack().bottom) + var b = cast[int](stackTop) when not stackIncreases: swap(a, b) - var x = cast[ByteAddress](p) + var x = cast[int](p) result = a <=% x and x <=% b when defined(sparc): # For SPARC architecture. @@ -314,7 +337,7 @@ when defined(sparc): # For SPARC architecture. # Addresses decrease as the stack grows. while sp <= max: gcMark(gch, sp[]) - sp = cast[PPointer](cast[ByteAddress](sp) +% sizeof(pointer)) + sp = cast[PPointer](cast[int](sp) +% sizeof(pointer)) elif defined(ELATE): {.error: "stack marking code is to be written for this architecture".} @@ -323,21 +346,28 @@ elif stackIncreases: # --------------------------------------------------------------------------- # Generic code for architectures where addresses increase as the stack grows. # --------------------------------------------------------------------------- - var - jmpbufSize {.importc: "sizeof(jmp_buf)", nodecl.}: int - # a little hack to get the size of a JmpBuf in the generated C code - # in a platform independent way + when defined(emscripten) or defined(wasm): + var + jmpbufSize {.importc: "sizeof(jmp_buf)", nodecl.}: int + # a little hack to get the size of a JmpBuf in the generated C code + # in a platform independent way + + template forEachStackSlotAux(gch, gcMark: untyped) {.dirty.} = + for stack in gch.stack.items(): + var max = cast[int](gch.stack.bottom) + var sp = cast[int](addr(registers)) -% sizeof(pointer) + while sp >=% max: + gcMark(gch, cast[PPointer](sp)[]) + sp = sp -% sizeof(pointer) template forEachStackSlot(gch, gcMark: untyped) {.dirty.} = - var registers {.noinit.}: C_JmpBuf - - if c_setjmp(registers) == 0'i32: # To fill the C stack with registers. - for stack in gch.stack.items(): - var max = cast[ByteAddress](gch.stack.bottom) - var sp = cast[ByteAddress](addr(registers)) -% sizeof(pointer) - while sp >=% max: - gcMark(gch, cast[PPointer](sp)[]) - sp = sp -% sizeof(pointer) + when defined(emscripten) or defined(wasm): + var registers: cint + forEachStackSlotAux(gch, gcMark) + else: + var registers {.noinit.}: C_JmpBuf + if c_setjmp(registers) == 0'i32: # To fill the C stack with registers. + forEachStackSlotAux(gch, gcMark) else: # --------------------------------------------------------------------------- @@ -353,15 +383,14 @@ else: gch.getActiveStack().setPosition(addr(registers)) if c_setjmp(registers) == 0'i32: # To fill the C stack with registers. for stack in gch.stack.items(): - var max = cast[ByteAddress](stack.bottom) - var sp = cast[ByteAddress](addr(registers)) + var max = cast[int](stack.bottom) + var sp = cast[int](addr(registers)) when defined(amd64): if stack.isActiveStack(): # words within the jmp_buf structure may not be properly aligned. let regEnd = sp +% sizeof(registers) while sp <% regEnd: gcMark(gch, cast[PPointer](sp)[]) - gcMark(gch, cast[PPointer](sp +% sizeof(pointer) div 2)[]) sp = sp +% sizeof(pointer) # Make sure sp is word-aligned sp = sp and not (sizeof(pointer) - 1) @@ -385,7 +414,7 @@ else: # end of non-portable code # ---------------------------------------------------------------------------- -proc prepareDealloc(cell: PCell) = +proc prepareDealloc(cell: PCell) {.raises: [].} = when declared(useMarkForDebug): when useMarkForDebug: gcAssert(cell notin gch.marked, "Cell still alive!") @@ -394,7 +423,7 @@ proc prepareDealloc(cell: PCell) = # the finalizer could invoke something that # allocates memory; this could trigger a garbage # collection. Since we are already collecting we - # prevend recursive entering here by a lock. + # prevent recursive entering here by a lock. # XXX: we should set the cell's children to nil! inc(gch.recGcLock) (cast[Finalizer](t.finalizer))(cellToUsr(cell)) @@ -402,10 +431,10 @@ proc prepareDealloc(cell: PCell) = decTypeSize(cell, t) proc deallocHeap*(runFinalizers = true; allowGcAfterwards = true) = - ## Frees the thread local heap. Runs every finalizer if ``runFinalizers``` - ## is true. If ``allowGcAfterwards`` is true, a minimal amount of allocation + ## Frees the thread local heap. Runs every finalizer if `runFinalizers` + ## is true. If `allowGcAfterwards` is true, a minimal amount of allocation ## happens to ensure the GC can continue to work after the call - ## to ``deallocHeap``. + ## to `deallocHeap`. template deallocCell(x) = if isCell(x): # cast to PCell is correct here: @@ -428,26 +457,26 @@ proc deallocHeap*(runFinalizers = true; allowGcAfterwards = true) = initGC() type - GlobalMarkerProc = proc () {.nimcall, benign.} + GlobalMarkerProc = proc () {.nimcall, benign, raises: [].} var - globalMarkersLen: int - globalMarkers: array[0.. 3499, GlobalMarkerProc] - threadLocalMarkersLen: int - threadLocalMarkers: array[0.. 3499, GlobalMarkerProc] + globalMarkersLen {.exportc.}: int + globalMarkers {.exportc.}: array[0..3499, GlobalMarkerProc] + threadLocalMarkersLen {.exportc.}: int + threadLocalMarkers {.exportc.}: array[0..3499, GlobalMarkerProc] gHeapidGenerator: int -proc nimRegisterGlobalMarker(markerProc: GlobalMarkerProc) {.compilerProc.} = +proc nimRegisterGlobalMarker(markerProc: GlobalMarkerProc) {.compilerproc.} = if globalMarkersLen <= high(globalMarkers): globalMarkers[globalMarkersLen] = markerProc inc globalMarkersLen else: - echo "[GC] cannot register global variable; too many global variables" - quit 1 + cstderr.rawWrite("[GC] cannot register global variable; too many global variables") + rawQuit 1 -proc nimRegisterThreadLocalMarker(markerProc: GlobalMarkerProc) {.compilerProc.} = +proc nimRegisterThreadLocalMarker(markerProc: GlobalMarkerProc) {.compilerproc.} = if threadLocalMarkersLen <= high(threadLocalMarkers): threadLocalMarkers[threadLocalMarkersLen] = markerProc inc threadLocalMarkersLen else: - echo "[GC] cannot register thread local variable; too many thread local variables" - quit 1 + cstderr.rawWrite("[GC] cannot register thread local variable; too many thread local variables") + rawQuit 1 |