mirror of
https://github.com/nim-lang/Nim.git
synced 2026-02-12 14:23:45 +00:00
The forward declarations cause `Exception` to be inferred - also, `llrecv` is an internal implementation detail and the type of the received item is controlled by generics, thus the ValueError raised there seems out of place for the generic api.
441 lines
14 KiB
Nim
441 lines
14 KiB
Nim
#
|
|
# Nim's Runtime Library
|
|
# (c) Copyright 2016 Andreas Rumpf
|
|
#
|
|
# See the file "copying.txt", included in this
|
|
# distribution, for details about the copyright.
|
|
#
|
|
|
|
{.push raises: [], gcsafe.}
|
|
|
|
# "Stack GC" for embedded devices or ultra performance requirements.
|
|
import std/private/syslocks
|
|
|
|
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, raises: [], gcsafe.}
|
|
# 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 = p +! s
|
|
|
|
template dec(p: pointer, s: int) =
|
|
p = 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, raises: [].} =
|
|
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, raises: [].} =
|
|
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, raises: [].} =
|
|
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, raises: [].} =
|
|
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, raises: [].} =
|
|
result = rawNewObj(tlRegion, typ, size)
|
|
zeroMem(result, size)
|
|
|
|
proc newSeqRC1(typ: PNimType, len: int): pointer {.compilerRtl, raises: [].} =
|
|
result = newSeq(typ, len)
|
|
|
|
proc growObj(regionUnused: var MemRegion; old: pointer, newsize: int): pointer {.raises: [].} =
|
|
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, raises: [].} =
|
|
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
|
|
|
|
{.pop.}
|