Files
Nim/lib/alloc.nim
Andreas Rumpf ddaedab835 version 0.7.2
2008-12-12 14:02:27 +01:00

395 lines
13 KiB
Nim

#
#
# Nimrod's Runtime Library
# (c) Copyright 2008 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# Low level allocator for Nimrod.
# ------------ platform specific chunk allocation code -----------------------
when defined(posix):
const # XXX: make these variables for portability?
PROT_READ = 1 # page can be read
PROT_WRITE = 2 # page can be written
PROT_EXEC = 4 # page can be executed
PROT_NONE = 0 # page can not be accessed
MAP_SHARED = 1 # Share changes
MAP_PRIVATE = 2 # Changes are private
MAP_TYPE = 0xf # Mask for type of mapping
MAP_FIXED = 0x10 # Interpret addr exactly
MAP_ANONYMOUS = 0x20 # don't use a file
MAP_GROWSDOWN = 0x100 # stack-like segment
MAP_DENYWRITE = 0x800 # ETXTBSY
MAP_EXECUTABLE = 0x1000 # mark it as an executable
MAP_LOCKED = 0x2000 # pages are locked
MAP_NORESERVE = 0x4000 # don't check for reservations
proc mmap(adr: pointer, len: int, prot, flags, fildes: cint,
off: int): pointer {.header: "<sys/mman.h>".}
proc munmap(adr: pointer, len: int) {.header: "<sys/mman.h>".}
proc osAllocPages(size: int): pointer {.inline.} =
result = mmap(nil, size, PROT_READ or PROT_WRITE,
MAP_PRIVATE or MAP_ANONYMOUS, -1, 0)
if result == nil or result == cast[pointer](-1):
raiseOutOfMem()
proc osDeallocPages(p: pointer, size: int) {.inline} =
munmap(p, len)
elif defined(windows):
const
MEM_RESERVE = 0x2000
MEM_COMMIT = 0x1000
MEM_TOP_DOWN = 0x100000
PAGE_READWRITE = 0x04
proc VirtualAlloc(lpAddress: pointer, dwSize: int, flAllocationType,
flProtect: int32): pointer {.
header: "<windows.h>", stdcall.}
proc osAllocPages(size: int): pointer {.inline.} =
result = VirtualAlloc(nil, size, MEM_RESERVE or MEM_COMMIT,
PAGE_READWRITE)
if result == nil: raiseOutOfMem()
proc osDeallocPages(p: pointer, size: int) {.inline.} =
nil
else:
{.error: "Port GC to your platform".}
# --------------------- end of non-portable code -----------------------------
# We manage *chunks* of memory. Each chunk is a multiple of the page size.
# The page size may or may not the operating system's page size. Each chunk
# starts at an address that is divisible by the page size. Chunks that are
# bigger than ``ChunkOsReturn`` are returned back to the operating system
# immediately.
# Guess the page size of the system; if it is the
# wrong value, performance may be worse (this is not
# for sure though), but GC still works; must be a power of two!
const
PageShift = if sizeof(pointer) == 4: 12 else: 13
PageSize = 1 shl PageShift # on 32 bit systems 4096
MemAlignment = sizeof(pointer)*2 # minimal memory block that can be allocated
BitsPerUnit = sizeof(int)*8
# a "unit" is a word, i.e. 4 bytes
# on a 32 bit system; I do not use the term "word" because under 32-bit
# Windows it is sometimes only 16 bits
BitsPerPage = PageSize div MemAlignment
UnitsPerPage = BitsPerPage div BitsPerUnit
# how many units do we need to describe a page:
# on 32 bit systems this is only 16 (!)
smallRequest = PageSize div 4
ChunkOsReturn = 1024 # in pages
InitialMemoryRequest = ChunkOsReturn div 2 # < ChunkOsReturn!
debugMemMan = true # we wish to debug the memory manager...
type
PChunkDesc = ptr TChunkDesc
TChunkDesc {.final, pure.} = object
key: TAddress # address at bit 0
next: PChunkDesc
bits: array[0..127, int] # a bit vector
PChunkDescArray = ptr array[0..1000_000, PChunkDesc]
TChunkSet {.final, pure.} = object
counter, max: int
head: PChunkDesc
data: PChunkDescArray
when sizeof(int) == 4:
type THalfWord = int16
else:
type THalfWord = int32
type
TFreeCell {.final, pure.} = object
zeroField: pointer # type info nil means cell is not used
next: ptr TFreeCell # next free cell in chunk
PChunk = ptr TChunk
TChunk {.final, pure.} = object
size: int # lowest two bits are used for merging:
# bit 0: chunk to the left is accessible and free
# bit 1: chunk to the right is accessible and free
len: int # for small object allocation
prev, next: PChunk # chunks of the same (or bigger) size
#len, used: THalfWord # index of next to allocate cell
freeList: ptr TFreeCell
data: float # a float for alignment purposes
proc roundup(x, v: int): int {.inline.} = return ((-x) and (v-1)) +% x
assert(roundup(14, PageSize) == PageSize)
assert(roundup(15, 8) == 16)
# ------------- chunk table ---------------------------------------------------
# We use a PtrSet of chunk starts and a table[Page, chunksize] for chunk
# endings of big chunks. This is needed by the merging operation. The only
# remaining operation is best-fit for big chunks. Since there is a size-limit
# for big chunks (because greater than the limit means they are returned back
# to the OS), a fixed size array can be used.
type
PLLChunk = ptr TLLChunk
TLLChunk {.pure.} = object ## *low-level* chunk
size: int
when sizeof(int) == 4:
align: int
TAllocator {.final, pure.} = object
llmem: PLLChunk
UsedPagesCount, FreePagesCount, maxPagesCount: int
freeSmallChunks: array[0..smallRequest div MemAlign-1, PChunk]
freeBigChunks: array[0..ChunkOsReturn-1, PChunk]
proc llAlloc(a: var TAllocator, size: int): pointer =
# *low-level* alloc for the memory managers data structures. Deallocation
# is never done.
assert(size <= PageSize-8)
if a.llmem.size + size > PageSize:
a.llmem = osGetPages(PageSize)
inc(a.gUsedPages)
a.llmem.size = 8
result = cast[pointer](cast[TAddress](a.llmem) + a.llmem.size)
inc(llmem.size, size)
zeroMem(result, size)
const
InitChunkSetSize = 1024 # must be a power of two!
proc ChunkSetInit(s: var TChunkSet) =
s.data = cast[PChunkDescArray](llAlloc(InitChunkSetSize * sizeof(PChunkDesc)))
s.max = InitChunkSetSize-1
s.counter = 0
s.head = nil
proc ChunkSetGet(t: TChunkSet, key: TAddress): PChunkDesc =
var h = cast[int](key) and t.max
while t.data[h] != nil:
if t.data[h].key == key: return t.data[h]
h = nextTry(h, t.max)
return nil
proc ChunkSetRawInsert(t: TChunkSet, data: PChunkDescArray,
desc: PChunkDesc) =
var h = cast[int](desc.key) and t.max
while data[h] != nil:
assert(data[h] != desc)
h = nextTry(h, t.max)
assert(data[h] == nil)
data[h] = desc
proc ChunkSetEnlarge(t: var TChunkSet) =
var oldMax = t.max
t.max = ((t.max+1)*2)-1
var n = cast[PChunkDescArray](llAlloc((t.max + 1) * sizeof(PChunkDescArray)))
for i in 0 .. oldmax:
if t.data[i] != nil:
ChunkSetRawInsert(t, n, t.data[i])
tlsf_free(t.data)
t.data = n
proc ChunkSetPut(t: var TChunkSet, key: TAddress): PChunkDesc =
var h = cast[int](key) and t.max
while true:
var x = t.data[h]
if x == nil: break
if x.key == key: return x
h = nextTry(h, t.max)
if ((t.max+1)*2 < t.counter*3) or ((t.max+1)-t.counter < 4):
ChunkSetEnlarge(t)
inc(t.counter)
h = cast[int](key) and t.max
while t.data[h] != nil: h = nextTry(h, t.max)
assert(t.data[h] == nil)
# the new page descriptor goes into result
result = cast[PChunkDesc](llAlloc(sizeof(TChunkDesc)))
result.next = t.head
result.key = key
t.head = result
t.data[h] = result
# ---------- slightly higher level procs --------------------------------------
proc in_Operator(s: TChunkSet, cell: PChunk): bool =
var u = cast[TAddress](cell)
var t = ChunkSetGet(s, u shr PageShift)
if t != nil:
u = (u %% PageSize) /% MemAlignment
result = (t.bits[u /% BitsPerUnit] and (1 shl (u %% BitsPerUnit))) != 0
else:
result = false
proc incl(s: var TCellSet, cell: PCell) =
var u = cast[TAddress](cell)
var t = ChunkSetPut(s, u shr PageShift)
u = (u %% PageSize) /% MemAlignment
t.bits[u /% BitsPerUnit] = t.bits[u /% BitsPerUnit] or
(1 shl (u %% BitsPerUnit))
proc excl(s: var TCellSet, cell: PCell) =
var u = cast[TAddress](cell)
var t = ChunkSetGet(s, u shr PageShift)
if t != nil:
u = (u %% PageSize) /% MemAlignment
t.bits[u /% BitsPerUnit] = (t.bits[u /% BitsPerUnit] and
not (1 shl (u %% BitsPerUnit)))
iterator elements(t: TChunkSet): PChunk {.inline.} =
# while traversing it is forbidden to add pointers to the tree!
var r = t.head
while r != nil:
var i = 0
while i <= high(r.bits):
var w = r.bits[i] # taking a copy of r.bits[i] here is correct, because
# modifying operations are not allowed during traversation
var j = 0
while w != 0: # test all remaining bits for zero
if (w and 1) != 0: # the bit is set!
yield cast[PCell]((r.key shl PageShift) or # +%
(i*%BitsPerUnit+%j) *% MemAlignment)
inc(j)
w = w shr 1
inc(i)
r = r.next
# ------------- chunk management ----------------------------------------------
proc removeChunk(a: var TAllocator, c: PChunk) {.inline.} =
if c.prev != nil: c.prev.next = c.next
if c.next != nil: c.next.prev = c.prev
if a.freeChunks[c.size div PageSize] == c:
a.freeChunks[c.size div PageSize] = c.next
proc addChunk(a: var TAllocator, c: PChunk) {.inline.} =
var s = abs(c.size) div PageSize
c.prev = nil
c.next = a.freeChunks[s]
a.freeChunks[s] = c
proc freeChunk(a: var TAllocator, c: PChunk) =
assert(c.size > 0)
if c.size < PageSize: c.size = PageSize
var le = cast[PChunk](cast[TAddress](p) and not PageMask -% PageSize)
var ri = cast[PChunk](cast[TAddress](p) and not PageMask +%
c.size +% PageSize)
if isStartOfAChunk(ri) and ri.size < 0:
removeChunk(a, ri)
inc(c.size, -ri.size)
if isEndOfAChunk(le):
le = cast[PChunk](cast[TAddress](p) and not PageMask -%
le.chunkStart+PageSize)
if le.size < 0:
removeChunk(a, le)
inc(le.size, c.size)
addChunk(a, le)
return
c.size = -c.size
addChunk(a, c)
proc splitChunk(a: var TAllocator, c: PChunk, size: int) =
var rest = cast[PChunk](cast[TAddress](p) + size)
rest.size = size - c.size # results in negative number, because rest is free
addChunk(a, rest)
# mark pages as accessible:
ChunkTablePut(a, rest, bitAccessible)
c.size = size
proc getChunkOfSize(a: var TAllocator, size: int): PChunk =
for i in size..high(a.freeChunks):
result = a.freeChunks[i]
if result != nil:
if i != size: splitChunk(a, result, size)
else: removeChunk(a, result)
result.prev = nil
result.next = nil
break
# -----------------------------------------------------------------------------
proc getChunk(p: pointer): PChunk {.inline.} =
result = cast[PChunk](cast[TAddress](p) and not PageMask)
proc getCellSize(p: pointer): int {.inline.} =
var c = getChunk(p)
result = abs(c.size)
proc alloc(a: var TAllocator, size: int): pointer =
if size <= smallRequest:
# allocate a small block
var s = size div MemAlign
var c = a.freeSmallChunks[s]
if c == nil:
c = getChunkOfSize(0)
c.freeList = nil
c.size = size
a.freeSmallChunks[s] = c
c.len = 1
c.used = 1
c.chunkStart = 0
result = addr(c.data[0])
elif c.freeList != nil:
result = c.freeList
assert(c.freeList.zeroField == nil)
c.freeList = c.freeList.next
inc(c.used)
if c.freeList == nil: removeChunk(a, c)
else:
assert(c.len*size <= high(c.data))
result = addr(c.data[c.len*size])
inc(c.len)
inc(c.used)
if c.len*size > high(c.data): removeChunk(a, c)
else:
# allocate a large block
var c = getChunkOfSize(size shr PageShift)
result = addr(c.data[0])
c.freeList = nil
c.size = size
c.len = 0
c.used = 0
c.chunkStart = 0
proc dealloc(a: var TAllocator, p: pointer) =
var c = getChunk(p)
if c.size <= smallRequest:
# free small block:
var f = cast[ptr TFreeCell](p)
f.zeroField = nil
f.next = c.freeList
c.freeList = p
dec(c.used)
if c.used == 0: freeChunk(c)
else:
# free big chunk
freeChunk(c)
proc realloc(a: var TAllocator, p: pointer, size: int): pointer =
# could be made faster, but this is unnecessary, the GC does not use it anyway
result = alloc(a, size)
copyMem(result, p, getCellSize(p))
dealloc(a, p)
proc isAllocatedPtr(a: TAllocator, p: pointer): bool =
var c = getChunk(p)
if c in a.accessibleChunks and c.size > 0:
result = cast[ptr TFreeCell](p).zeroField != nil