#
# Nim's Runtime Library
# (c) Copyright 2016 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# "Stack GC" for embedded devices or ultra performance requirements.
when defined(memProfiler):
proc nimProfile(requestedSize: int) {.benign.}
when defined(useMalloc):
proc roundup(x, v: int): int {.inline.} =
result = (x + (v-1)) and not (v-1)
proc emalloc(size: int): pointer {.importc: "malloc", header: "<stdlib.h>".}
proc efree(mem: pointer) {.importc: "free", header: "<stdlib.h>".}
proc osAllocPages(size: int): pointer {.inline.} =
emalloc(size)
proc osTryAllocPages(size: int): pointer {.inline.} =
emalloc(size)
proc osDeallocPages(p: pointer, size: int) {.inline.} =
efree(p)
else:
include osalloc
# We manage memory as a thread local stack. Since the allocation pointer
# is detached from the control flow pointer, this model is vastly more
# useful than the traditional programming model while almost as safe.
# Individual objects can also be deleted but no coalescing is performed.
# Stacks can also be moved from one thread to another.
# We also support 'finalizers'.
type
Finalizer {.compilerproc.} = proc (self: pointer) {.nimcall, benign.}
# A ref type can have a finalizer that is called before the object's
# storage is freed.
AlignType = BiggestFloat
ObjHeader = object
typ: PNimType
nextFinal: ptr ObjHeader # next object with finalizer
Chunk = ptr BaseChunk
BaseChunk = object
next: Chunk
size: int
head, tail: ptr ObjHeader # first and last object in chunk that
# has a finalizer attached to it
const
MaxSmallObject = 128
type
FreeEntry = ptr object
next: FreeEntry
SizedFreeEntry = ptr object
next: SizedFreeEntry
size: int
StackPtr = object
bump: pointer
remaining: int
current: Chunk
MemRegion* = object
remaining: int
bump: pointer
head, tail: Chunk
nextChunkSize, totalSize: int
when false:
freeLists: array[MaxSmallObject div MemAlign, FreeEntry]
holes: SizedFreeEntry
when hasThreadSupport:
lock: SysLock
SeqHeader = object # minor hack ahead: Since we know that seqs
# and strings cannot have finalizers, we use the field
# instead for a 'region' field so that they can grow
# and shrink safely.
typ: PNimType
region: ptr MemRegion
var
tlRegion {.threadvar.}: MemRegion
# tempStrRegion {.threadvar.}: MemRegion # not yet used
template withRegion*(r: var MemRegion; body: untyped) =
let oldRegion = tlRegion
tlRegion = r
try:
body
finally:
r = tlRegion
tlRegion = oldRegion
template inc(p: pointer, s: int) =
p = cast[pointer](cast[int](p) +% s)
template dec(p: pointer, s: int) =
p = cast[pointer](cast[int](p) -% s)
template `+!`(p: pointer, s: int): pointer =
cast[pointer](cast[int](p) +% s)
template `-!`(p: pointer, s: int): pointer =
cast[pointer](cast[int](p) -% s)
const nimMinHeapPages {.intdefine.} = 4
proc allocSlowPath(r: var MemRegion; size: int) =
# we need to ensure that the underlying linked list
# stays small. Say we want to grab 16GB of RAM with some
# exponential growth function. So we allocate 16KB, then
# 32 KB, 64 KB, 128KB, 256KB, 512KB, 1MB, 2MB, 4MB,
# 8MB, 16MB, 32MB, 64MB, 128MB, 512MB, 1GB, 2GB, 4GB, 8GB,
# 16GB --> list contains only 20 elements! That's reasonable.
if (r.totalSize and 1) == 0:
r.nextChunkSize = if r.totalSize < 64 * 1024: PageSize*nimMinHeapPages
else: r.nextChunkSize*2
var s = roundup(size+sizeof(BaseChunk), PageSize)
var fresh: Chunk
if s > r.nextChunkSize:
fresh = cast[Chunk](osAllocPages(s))
else:
fresh = cast[Chunk](osTryAllocPages(r.nextChunkSize))
if fresh == nil:
fresh = cast[Chunk](osAllocPages(s))
# lowest bit in totalSize is the "don't increase nextChunkSize"
inc r.totalSize
else:
s = r.nextChunkSize
fresh.size = s
fresh.head = nil
fresh.tail = nil
fresh.next = nil
inc r.totalSize, s
let old = r.tail
if old == nil:
r.head = fresh
else:
r.tail.next = fresh
r.bump = fresh +! sizeof(BaseChunk)
r.tail = fresh
r.remaining = s - sizeof(BaseChunk)
proc allocFast(r: var MemRegion; size: int): pointer =
when false:
if size <= MaxSmallObject:
var it = r.freeLists[size div MemAlign]
if it != nil:
r.freeLists[size div MemAlign] = it.next
return pointer(it)
else:
var it = r.holes
var prev: SizedFreeEntry = nil
while it != nil:
if it.size >= size:
if prev != nil: prev.next = it.next
else: r.holes = it.next
return pointer(it)
prev = it
it = it.next
let size = roundup(size, MemAlign)
if size > r.remaining:
allocSlowPath(r, size)
sysAssert(size <= r.remaining, "size <= r.remaining")
dec(r.remaining, size)
result = r.bump
inc r.bump, size
proc runFinalizers(c: Chunk) =
var it = c.head
while it != nil:
# indivually freed objects with finalizer stay in the list, but
# their typ is nil then:
if it.typ != nil and it.typ.finalizer != nil:
(cast[Finalizer](it.typ.finalizer))(it+!sizeof(ObjHeader))
it = it.nextFinal
proc runFinalizers(c: Chunk; newbump: pointer) =
var it = c.head
var prev: ptr ObjHeader = nil
while it != nil:
let nxt = it.nextFinal
if it >= newbump:
if it.typ != nil and it.typ.finalizer != nil:
(cast[Finalizer](it.typ.finalizer))(it+!sizeof(ObjHeader))
elif prev != nil:
prev.nextFinal = nil
prev = it
it = nxt
proc dealloc(r: var MemRegion; p: pointer; size: int) =
let it = cast[ptr ObjHeader](p-!sizeof(ObjHeader))
if it.typ != nil and it.typ.finalizer != nil:
(cast[Finalizer](it.typ.finalizer))(p)
it.typ = nil
# it is beneficial to not use the free lists here:
if r.bump -! size == p:
dec r.bump, size
when false:
if size <= MaxSmallObject:
let it = cast[FreeEntry](p)
it.next = r.freeLists[size div MemAlign]
r.freeLists[size div MemAlign] = it
else:
let it = cast[SizedFreeEntry](p)
it.size = size
it.next = r.holes
r.holes = it
proc deallocAll(r: var MemRegion; head: Chunk) =
var it = head
while it != nil:
let nxt = it.next
runFinalizers(it)
dec r.totalSize, it.size
osDeallocPages(it, it.size)
it = nxt
proc deallocAll*(r: var MemRegion) =
deallocAll(r, r.head)
zeroMem(addr r, sizeof r)
proc obstackPtr*(r: MemRegion): StackPtr =
result.bump = r.bump
result.remaining = r.remaining
result.current = r.tail
template computeRemaining(r): untyped =
r.tail.size -% (cast[int](r.bump) -% cast[int](r.tail))
proc setObstackPtr*(r: var MemRegion; sp: StackPtr) =
# free everything after 'sp':
if sp.current != nil and sp.current.next != nil:
deallocAll(r, sp.current.next)
sp.current.next = nil
when false:
# better leak this memory than be sorry:
for i in 0..high(r.freeLists): r.freeLists[i] = nil
r.holes = nil
if r.tail != nil: runFinalizers(r.tail, sp.bump)
r.bump = sp.bump
r.tail = sp.current
r.remaining = sp.remaining
proc obstackPtr*(): StackPtr = tlRegion.obstackPtr()
proc setObstackPtr*(sp: StackPtr) = tlRegion.setObstackPtr(sp)
proc deallocAll*() = tlRegion.deallocAll()
proc deallocOsPages(r: var MemRegion) = r.deallocAll()
when false:
let obs = obstackPtr()
try:
body
finally:
setObstackPtr(obs)
template withScratchRegion*(body: untyped) =
let oldRegion = tlRegion
tlRegion = MemRegion()
try:
body
finally:
deallocAll()
tlRegion = oldRegion
when false:
proc joinRegion*(dest: var MemRegion; src: MemRegion) =
# merging is not hard.
if dest.head.isNil:
dest.head = src.head
else:
dest.tail.next = src.head
dest.tail = src.tail
dest.bump = src.bump
dest.remaining = src.remaining
dest.nextChunkSize = max(dest.nextChunkSize, src.nextChunkSize)
inc dest.totalSize, src.totalSize
proc isOnHeap*(r: MemRegion; p: pointer): bool =
# the tail chunk is the largest, so check it first. It's also special
# in that contains the current bump pointer:
if r.tail >= p and p < r.bump:
return true
var it = r.head
while it != r.tail:
if it >= p and p <= it+!it.size: return true
it = it.next
proc rawNewObj(r: var MemRegion, typ: PNimType, size: int): pointer =
var res = cast[ptr ObjHeader](allocFast(r, size + sizeof(ObjHeader)))
res.typ = typ
if typ.finalizer != nil:
res.nextFinal = r.head.head
r.head.head = res
result = res +! sizeof(ObjHeader)
proc rawNewSeq(r: var MemRegion, typ: PNimType, size: int): pointer =
var res = cast[ptr SeqHeader](allocFast(r, size + sizeof(SeqHeader)))
res.typ = typ
res.region = addr(r)
result = res +! sizeof(SeqHeader)
proc newObj(typ: PNimType, size: int): pointer {.compilerRtl.} =
sysAssert typ.kind notin {tySequence, tyString}, "newObj cannot be used to construct seqs"
result = rawNewObj(tlRegion, typ, size)
zeroMem(result, size)
when defined(memProfiler): nimProfile(size)
proc newObjNoInit(typ: PNimType, size: int): pointer {.compilerRtl.} =
sysAssert typ.kind notin {tySequence, tyString}, "newObj cannot be used to construct seqs"
result = rawNewObj(tlRegion, typ, size)
when defined(memProfiler): nimProfile(size)
{.push overflowChecks: on.}
proc newSeq(typ: PNimType, len: int): pointer {.compilerRtl.} =
let size = roundup(align(GenericSeqSize, typ.base.align) + len * typ.base.size, MemAlign)
result = rawNewSeq(tlRegion, typ, size)
zeroMem(result, size)
cast[PGenericSeq](result).len = len
cast[PGenericSeq](result).reserved = len
proc newStr(typ: PNimType, len: int; init: bool): pointer {.compilerRtl.} =
let size = roundup(len + GenericSeqSize, MemAlign)
result = rawNewSeq(tlRegion, typ, size)
if init: zeroMem(result, size)
cast[PGenericSeq](result).len = 0
cast[PGenericSeq](result).reserved = len
{.pop.}
proc newObjRC1(typ: PNimType, size: int): pointer {.compilerRtl.} =
result = rawNewObj(tlRegion, typ, size)
zeroMem(result, size)
proc newSeqRC1(typ: PNimType, len: int): pointer {.compilerRtl.} =
result = newSeq(typ, len)
proc growObj(regionUnused: var MemRegion; old: pointer, newsize: int): pointer =
let sh = cast[ptr SeqHeader](old -! sizeof(SeqHeader))
let typ = sh.typ
result = rawNewSeq(sh.region[], typ,
roundup(newsize, MemAlign))
let elemSize = if typ.kind == tyString: 1 else: typ.base.size
let elemAlign = if typ.kind == tyString: 1 else: typ.base.align
let oldsize = align(GenericSeqSize, elemAlign) + cast[PGenericSeq](old).len*elemSize
zeroMem(result +! oldsize, newsize-oldsize)
copyMem(result, old, oldsize)
dealloc(sh.region[], old, roundup(oldsize, MemAlign))
proc growObj(old: pointer, newsize: int): pointer {.rtl.} =
result = growObj(tlRegion, old, newsize)
proc unsureAsgnRef(dest: PPointer, src: pointer) {.compilerproc, inline.} =
dest[] = src
proc asgnRef(dest: PPointer, src: pointer) {.compilerproc, inline.} =
dest[] = src
proc asgnRefNoCycle(dest: PPointer, src: pointer) {.compilerproc, inline,
deprecated: "old compiler compat".} = asgnRef(dest, src)
proc allocImpl(size: Natural): pointer =
result = c_malloc(cast[csize_t](size))
if result == nil: raiseOutOfMem()
proc alloc0Impl(size: Natural): pointer =
result = alloc(size)
zeroMem(result, size)
proc reallocImpl(p: pointer, newsize: Natural): pointer =
result = c_realloc(p, cast[csize_t](newsize))
if result == nil: raiseOutOfMem()
proc realloc0Impl(p: pointer, oldsize, newsize: Natural): pointer =
result = c_realloc(p, cast[csize_t](newsize))
if result == nil: raiseOutOfMem()
if newsize > oldsize:
zeroMem(cast[pointer](cast[int](result) + oldsize), newsize - oldsize)
proc deallocImpl(p: pointer) = c_free(p)
proc alloc0(r: var MemRegion; size: Natural): pointer =
# ignore the region. That is correct for the channels module
# but incorrect in general. XXX
result = alloc0(size)
proc alloc(r: var MemRegion; size: Natural): pointer =
# ignore the region. That is correct for the channels module
# but incorrect in general. XXX
result = alloc(size)
proc dealloc(r: var MemRegion; p: pointer) = dealloc(p)
proc allocSharedImpl(size: Natural): pointer =
result = c_malloc(cast[csize_t](size))
if result == nil: raiseOutOfMem()
proc allocShared0Impl(size: Natural): pointer =
result = alloc(size)
zeroMem(result, size)
proc reallocSharedImpl(p: pointer, newsize: Natural): pointer =
result = c_realloc(p, cast[csize_t](newsize))
if result == nil: raiseOutOfMem()
proc reallocShared0Impl(p: pointer, oldsize, newsize: Natural): pointer =
result = c_realloc(p, cast[csize_t](newsize))
if result == nil: raiseOutOfMem()
if newsize > oldsize:
zeroMem(cast[pointer](cast[int](result) + oldsize), newsize - oldsize)
proc deallocSharedImpl(p: pointer) = c_free(p)
when hasThreadSupport:
proc getFreeSharedMem(): int = 0
proc getTotalSharedMem(): int = 0
proc getOccupiedSharedMem(): int = 0
proc GC_disable() = discard
proc GC_enable() = discard
proc GC_fullCollect() = discard
proc GC_setStrategy(strategy: GC_Strategy) = discard
proc GC_enableMarkAndSweep() = discard
proc GC_disableMarkAndSweep() = discard
proc GC_getStatistics(): string = return ""
proc getOccupiedMem(): int =
result = tlRegion.totalSize - tlRegion.remaining
proc getFreeMem(): int = tlRegion.remaining
proc getTotalMem(): int =
result = tlRegion.totalSize
proc getOccupiedMem*(r: MemRegion): int =
result = r.totalSize - r.remaining
proc getFreeMem*(r: MemRegion): int = r.remaining
proc getTotalMem*(r: MemRegion): int =
result = r.totalSize
proc nimGC_setStackBottom(theStackBottom: pointer) = discard
proc nimGCref(x: pointer) {.compilerproc.} = discard
proc nimGCunref(x: pointer) {.compilerproc.} = discard