123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470 |
- #
- #
- # Nim's Runtime Library
- # (c) Copyright 2015 Rokas Kupstys
- #
- # See the file "copying.txt", included in this
- # distribution, for details about the copyright.
- #
- type
- ForeignCell* = object
- data*: pointer
- owner: ptr GcHeap
- proc protect*(x: pointer): ForeignCell =
- nimGCref(x)
- result.data = x
- result.owner = addr(gch)
- when defined(nimTypeNames):
- type InstancesInfo = array[400, (cstring, int, int)]
- proc sortInstances(a: var InstancesInfo; n: int) =
- # we use shellsort here; fast and simple
- var h = 1
- while true:
- h = 3 * h + 1
- if h > n: break
- while true:
- h = h div 3
- for i in countup(h, n - 1):
- var v = a[i]
- var j = i
- while a[j - h][2] < v[2]:
- a[j] = a[j - h]
- j = j - h
- if j < h: break
- a[j] = v
- if h == 1: break
- 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 totalAllocated = 0
- for it in dumpHeapInstances():
- a[n] = it
- inc n
- inc totalAllocated, it.sizes
- 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)
- when defined(nimTypeNames):
- let (allocs, deallocs) = getMemCounters()
- c_fprintf(stdout, "[Heap] allocs/deallocs: %ld/%ld\n", allocs, deallocs)
- when defined(nimGcRefLeak):
- proc oomhandler() =
- c_fprintf(stdout, "[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)
- else:
- dec t.sizes, t.base.size+sizeof(Cell)
- dec t.instances
- template incTypeSize(typ, size) =
- when defined(nimTypeNames):
- inc typ.instances
- inc typ.sizes, size+sizeof(Cell)
- proc dispose*(x: ForeignCell) =
- when hasThreadSupport:
- # if we own it we can free it directly:
- if x.owner == addr(gch):
- nimGCunref(x.data)
- else:
- x.owner.toDispose.add(x.data)
- else:
- nimGCunref(x.data)
- proc isNotForeign*(x: ForeignCell): bool =
- ## returns true if 'x' belongs to the calling thread.
- ## No deep copy has to be performed then.
- x.owner == addr(gch)
- when nimCoroutines:
- iterator items(first: var GcStack): ptr GcStack =
- var item = addr(first)
- while true:
- yield item
- item = item.next
- if item == addr(first):
- break
- proc append(first: var GcStack, stack: ptr GcStack) =
- ## Append stack to the ring of stacks.
- first.prev.next = stack
- stack.prev = first.prev
- first.prev = stack
- stack.next = addr(first)
- proc append(first: var GcStack): ptr GcStack =
- ## Allocate new GcStack object, append it to the ring of stacks and return it.
- result = cast[ptr GcStack](alloc0(sizeof(GcStack)))
- first.append(result)
- proc remove(first: var GcStack, stack: ptr GcStack) =
- ## Remove stack from ring of stacks.
- gcAssert(addr(first) != stack, "Main application stack can not be removed")
- if addr(first) == stack or stack == nil:
- return
- stack.prev.next = stack.next
- stack.next.prev = stack.prev
- dealloc(stack)
- proc remove(stack: ptr GcStack) =
- gch.stack.remove(stack)
- proc find(first: var GcStack, bottom: pointer): ptr GcStack =
- ## Find stack struct based on bottom pointer. If `bottom` is nil then main
- ## thread stack is is returned.
- if bottom == nil:
- return addr(gch.stack)
- for stack in first.items():
- if stack.bottom == bottom:
- return stack
- proc len(stack: var GcStack): int =
- for _ in stack.items():
- result = result + 1
- else:
- # This iterator gets optimized out in forEachStackSlot().
- 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):
- 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``
- ## switches are used
- 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()
- threadType = ThreadType.None
- when declared(gch): zeroMem(addr gch, sizeof(gch))
- else:
- template setupForeignThreadGc*() =
- {.error: "setupForeignThreadGc is available only when ``--threads:on`` and ``--tlsEmulation:off`` are used".}
- template tearDownForeignThreadGc*() =
- {.error: "tearDownForeignThreadGc is available only when ``--threads:on`` and ``--tlsEmulation:off`` are used".}
- # ----------------- stack management --------------------------------------
- # inspired from Smart Eiffel
- when defined(emscripten) or defined(wasm):
- const stackIncreases = true
- elif defined(sparc):
- const stackIncreases = false
- elif defined(hppa) or defined(hp9000) or defined(hp9000s300) or
- defined(hp9000s700) or defined(hp9000s800) or defined(hp9000s820):
- const stackIncreases = true
- else:
- const stackIncreases = false
- {.push stack_trace: off.}
- when nimCoroutines:
- proc GC_addStack(bottom: pointer) {.cdecl, 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.} =
- # c_fprintf(stdout, "GC_removeStack: %p;\n", bottom)
- gch.stack.find(bottom).remove()
- proc GC_setActiveStack(bottom: pointer) {.cdecl, 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))
- when not defined(useNimRtl):
- proc nimGC_setStackBottom(theStackBottom: pointer) =
- # Initializes main stack of the thread.
- when nimCoroutines:
- if gch.stack.next == nil:
- # Main stack was not initialized yet
- gch.stack.next = addr(gch.stack)
- gch.stack.prev = addr(gch.stack)
- gch.stack.bottom = theStackBottom
- gch.stack.maxStackSize = 0
- gch.activeStack = addr(gch.stack)
- if gch.stack.bottom == nil:
- # This branch will not be called when -d:nimCoroutines - it is fine,
- # because same thing is done just above.
- #c_fprintf(stdout, "stack bottom: %p;\n", theStackBottom)
- # 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)
- #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))
- gch.stack.setPosition(theStackBottom)
- {.pop.}
- proc isOnStack(p: pointer): bool =
- var stackTop {.volatile.}: pointer
- stackTop = addr(stackTop)
- var a = cast[ByteAddress](gch.getActiveStack().bottom)
- var b = cast[ByteAddress](stackTop)
- when not stackIncreases:
- swap(a, b)
- var x = cast[ByteAddress](p)
- result = a <=% x and x <=% b
- when defined(sparc): # For SPARC architecture.
- when nimCoroutines:
- {.error: "Nim coroutines are not supported on this platform."}
- template forEachStackSlot(gch, gcMark: untyped) {.dirty.} =
- when defined(sparcv9):
- asm """"flushw \n" """
- else:
- asm """"ta 0x3 ! ST_FLUSH_WINDOWS\n" """
- var
- max = gch.stack.bottom
- sp: PPointer
- stackTop: array[0..1, pointer]
- sp = addr(stackTop[0])
- # Addresses decrease as the stack grows.
- while sp <= max:
- gcMark(gch, sp[])
- sp = cast[PPointer](cast[ByteAddress](sp) +% sizeof(pointer))
- elif defined(ELATE):
- {.error: "stack marking code is to be written for this architecture".}
- elif stackIncreases:
- # ---------------------------------------------------------------------------
- # Generic code for architectures where addresses increase as the stack grows.
- # ---------------------------------------------------------------------------
- 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[ByteAddress](gch.stack.bottom)
- var sp = cast[ByteAddress](addr(registers)) -% sizeof(pointer)
- while sp >=% max:
- gcMark(gch, cast[PPointer](sp)[])
- sp = sp -% sizeof(pointer)
- template forEachStackSlot(gch, gcMark: untyped) {.dirty.} =
- 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:
- # ---------------------------------------------------------------------------
- # Generic code for architectures where addresses decrease as the stack grows.
- # ---------------------------------------------------------------------------
- template forEachStackSlot(gch, gcMark: untyped) {.dirty.} =
- # We use a jmp_buf buffer that is in the C stack.
- # Used to traverse the stack and registers assuming
- # that 'setjmp' will save registers in the C stack.
- type PStackSlice = ptr array[0..7, pointer]
- var registers {.noinit.}: C_JmpBuf
- # Update position of stack gc is executing in.
- 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))
- 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)
- # loop unrolled:
- while sp <% max - 8*sizeof(pointer):
- gcMark(gch, cast[PStackSlice](sp)[0])
- gcMark(gch, cast[PStackSlice](sp)[1])
- gcMark(gch, cast[PStackSlice](sp)[2])
- gcMark(gch, cast[PStackSlice](sp)[3])
- gcMark(gch, cast[PStackSlice](sp)[4])
- gcMark(gch, cast[PStackSlice](sp)[5])
- gcMark(gch, cast[PStackSlice](sp)[6])
- gcMark(gch, cast[PStackSlice](sp)[7])
- sp = sp +% sizeof(pointer)*8
- # last few entries:
- while sp <=% max:
- gcMark(gch, cast[PPointer](sp)[])
- sp = sp +% sizeof(pointer)
- # ----------------------------------------------------------------------------
- # end of non-portable code
- # ----------------------------------------------------------------------------
- proc prepareDealloc(cell: PCell) =
- when declared(useMarkForDebug):
- when useMarkForDebug:
- gcAssert(cell notin gch.marked, "Cell still alive!")
- let t = cell.typ
- if t.finalizer != nil:
- # 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.
- # XXX: we should set the cell's children to nil!
- inc(gch.recGcLock)
- (cast[Finalizer](t.finalizer))(cellToUsr(cell))
- dec(gch.recGcLock)
- 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
- ## happens to ensure the GC can continue to work after the call
- ## to ``deallocHeap``.
- template deallocCell(x) =
- if isCell(x):
- # cast to PCell is correct here:
- prepareDealloc(cast[PCell](x))
- if runFinalizers:
- when not declared(allObjectsAsProc):
- for x in allObjects(gch.region):
- deallocCell(x)
- else:
- var spaceIter: ObjectSpaceIter
- while true:
- let x = allObjectsAsProc(gch.region, addr spaceIter)
- if spaceIter.state < 0: break
- deallocCell(x)
- deallocOsPages(gch.region)
- zeroMem(addr gch.region, sizeof(gch.region))
- if allowGcAfterwards:
- initGC()
- type
- GlobalMarkerProc = proc () {.nimcall, benign.}
- var
- globalMarkersLen: int
- globalMarkers: array[0..3499, GlobalMarkerProc]
- threadLocalMarkersLen: int
- threadLocalMarkers: array[0..3499, GlobalMarkerProc]
- gHeapidGenerator: int
- 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
- 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
|