version0.7.10
This commit is contained in:
parent
ce88dc3e67
commit
4d4b3b1c04
193 changed files with 9099 additions and 17637 deletions
596
lib/system/alloc.nim
Normal file
596
lib/system/alloc.nim
Normal file
|
|
@ -0,0 +1,596 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2009 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
# Low level allocator for Nimrod. Has been designed to support the GC.
|
||||
# TODO:
|
||||
# - eliminate "used" field
|
||||
# - make searching for block O(1)
|
||||
|
||||
# ------------ platform specific chunk allocation code -----------------------
|
||||
|
||||
when defined(posix):
|
||||
const
|
||||
PROT_READ = 1 # page can be read
|
||||
PROT_WRITE = 2 # page can be written
|
||||
MAP_PRIVATE = 2 # Changes are private
|
||||
|
||||
when defined(linux) or defined(aix):
|
||||
const MAP_ANONYMOUS = 0x20 # don't use a file
|
||||
elif defined(macosx) or defined(bsd):
|
||||
const MAP_ANONYMOUS = 0x1000
|
||||
elif defined(solaris):
|
||||
const MAP_ANONYMOUS = 0x100
|
||||
else:
|
||||
{.error: "Port memory manager to your platform".}
|
||||
|
||||
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} =
|
||||
when reallyOsDealloc: munmap(p, size)
|
||||
|
||||
elif defined(windows):
|
||||
const
|
||||
MEM_RESERVE = 0x2000
|
||||
MEM_COMMIT = 0x1000
|
||||
MEM_TOP_DOWN = 0x100000
|
||||
PAGE_READWRITE = 0x04
|
||||
|
||||
MEM_DECOMMIT = 0x4000
|
||||
MEM_RELEASE = 0x8000
|
||||
|
||||
proc VirtualAlloc(lpAddress: pointer, dwSize: int, flAllocationType,
|
||||
flProtect: int32): pointer {.
|
||||
header: "<windows.h>", stdcall.}
|
||||
|
||||
proc VirtualFree(lpAddress: pointer, dwSize: int,
|
||||
dwFreeType: int32) {.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.} =
|
||||
# according to Microsoft, 0 is the only correct value here:
|
||||
when reallyOsDealloc: VirtualFree(p, 0, MEM_RELEASE)
|
||||
|
||||
else:
|
||||
{.error: "Port memory manager to your platform".}
|
||||
|
||||
# --------------------- end of non-portable code -----------------------------
|
||||
|
||||
# We manage *chunks* of memory. Each chunk is a multiple of the 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.
|
||||
|
||||
const
|
||||
ChunkOsReturn = 256 * PageSize
|
||||
InitialMemoryRequest = ChunkOsReturn div 2 # < ChunkOsReturn!
|
||||
SmallChunkSize = PageSize
|
||||
|
||||
type
|
||||
PTrunk = ptr TTrunk
|
||||
TTrunk {.final.} = object
|
||||
next: PTrunk # all nodes are connected with this pointer
|
||||
key: int # start address at bit 0
|
||||
bits: array[0..IntsPerTrunk-1, int] # a bit vector
|
||||
|
||||
TTrunkBuckets = array[0..1023, PTrunk]
|
||||
TIntSet {.final.} = object
|
||||
data: TTrunkBuckets
|
||||
|
||||
type
|
||||
TAlignType = biggestFloat
|
||||
TFreeCell {.final, pure.} = object
|
||||
next: ptr TFreeCell # next free cell in chunk (overlaid with refcount)
|
||||
zeroField: int # 0 means cell is not used (overlaid with typ field)
|
||||
# 1 means cell is manually managed pointer
|
||||
|
||||
PChunk = ptr TBaseChunk
|
||||
PBigChunk = ptr TBigChunk
|
||||
PSmallChunk = ptr TSmallChunk
|
||||
TBaseChunk {.pure.} = object
|
||||
prevSize: int # size of previous chunk; for coalescing
|
||||
size: int # if < PageSize it is a small chunk
|
||||
used: bool # later will be optimized into prevSize...
|
||||
|
||||
TSmallChunk = object of TBaseChunk
|
||||
next, prev: PSmallChunk # chunks of the same size
|
||||
freeList: ptr TFreeCell
|
||||
free: int # how many bytes remain
|
||||
acc: int # accumulator for small object allocation
|
||||
data: TAlignType # start of usable memory
|
||||
|
||||
TBigChunk = object of TBaseChunk # not necessarily > PageSize!
|
||||
next: PBigChunk # chunks of the same (or bigger) size
|
||||
prev: PBigChunk
|
||||
align: int
|
||||
data: TAlignType # start of usable memory
|
||||
|
||||
template smallChunkOverhead(): expr = sizeof(TSmallChunk)-sizeof(TAlignType)
|
||||
template bigChunkOverhead(): expr = sizeof(TBigChunk)-sizeof(TAlignType)
|
||||
|
||||
proc roundup(x, v: int): int {.inline.} =
|
||||
result = (x + (v-1)) and not (v-1)
|
||||
assert(result >= x)
|
||||
#return ((-x) and (v-1)) +% x
|
||||
|
||||
assert(roundup(14, PageSize) == PageSize)
|
||||
assert(roundup(15, 8) == 16)
|
||||
assert(roundup(65, 8) == 72)
|
||||
|
||||
# ------------- 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 # remaining size
|
||||
acc: int # accumulator
|
||||
|
||||
TAllocator {.final, pure.} = object
|
||||
llmem: PLLChunk
|
||||
currMem, maxMem, freeMem: int # memory sizes (allocated from OS)
|
||||
freeSmallChunks: array[0..SmallChunkSize div MemAlign-1, PSmallChunk]
|
||||
freeChunksList: PBigChunk # XXX make this a datastructure with O(1) access
|
||||
chunkStarts: TIntSet
|
||||
|
||||
proc incCurrMem(a: var TAllocator, bytes: int) {.inline.} =
|
||||
inc(a.currMem, bytes)
|
||||
|
||||
proc decCurrMem(a: var TAllocator, bytes: int) {.inline.} =
|
||||
a.maxMem = max(a.maxMem, a.currMem)
|
||||
dec(a.currMem, bytes)
|
||||
|
||||
proc getMaxMem(a: var TAllocator): int =
|
||||
# Since we update maxPagesCount only when freeing pages,
|
||||
# maxPagesCount may not be up to date. Thus we use the
|
||||
# maximum of these both values here:
|
||||
return max(a.currMem, a.maxMem)
|
||||
|
||||
var
|
||||
allocator: TAllocator
|
||||
|
||||
proc llAlloc(a: var TAllocator, size: int): pointer =
|
||||
# *low-level* alloc for the memory managers data structures. Deallocation
|
||||
# is never done.
|
||||
if a.llmem == nil or size > a.llmem.size:
|
||||
var request = roundup(size+sizeof(TLLChunk), PageSize)
|
||||
a.llmem = cast[PLLChunk](osAllocPages(request))
|
||||
incCurrMem(a, request)
|
||||
a.llmem.size = request - sizeof(TLLChunk)
|
||||
a.llmem.acc = sizeof(TLLChunk)
|
||||
result = cast[pointer](cast[TAddress](a.llmem) + a.llmem.acc)
|
||||
dec(a.llmem.size, size)
|
||||
inc(a.llmem.acc, size)
|
||||
zeroMem(result, size)
|
||||
|
||||
proc IntSetGet(t: TIntSet, key: int): PTrunk =
|
||||
var it = t.data[key and high(t.data)]
|
||||
while it != nil:
|
||||
if it.key == key: return it
|
||||
it = it.next
|
||||
result = nil
|
||||
|
||||
proc IntSetPut(t: var TIntSet, key: int): PTrunk =
|
||||
result = IntSetGet(t, key)
|
||||
if result == nil:
|
||||
result = cast[PTrunk](llAlloc(allocator, sizeof(result^)))
|
||||
result.next = t.data[key and high(t.data)]
|
||||
t.data[key and high(t.data)] = result
|
||||
result.key = key
|
||||
|
||||
proc Contains(s: TIntSet, key: int): bool =
|
||||
var t = IntSetGet(s, key shr TrunkShift)
|
||||
if t != nil:
|
||||
var u = key and TrunkMask
|
||||
result = (t.bits[u shr IntShift] and (1 shl (u and IntMask))) != 0
|
||||
else:
|
||||
result = false
|
||||
|
||||
proc Incl(s: var TIntSet, key: int) =
|
||||
var t = IntSetPut(s, key shr TrunkShift)
|
||||
var u = key and TrunkMask
|
||||
t.bits[u shr IntShift] = t.bits[u shr IntShift] or (1 shl (u and IntMask))
|
||||
|
||||
proc Excl(s: var TIntSet, key: int) =
|
||||
var t = IntSetGet(s, key shr TrunkShift)
|
||||
if t != nil:
|
||||
var u = key and TrunkMask
|
||||
t.bits[u shr IntShift] = t.bits[u shr IntShift] and not
|
||||
(1 shl (u and IntMask))
|
||||
|
||||
proc ContainsOrIncl(s: var TIntSet, key: int): bool =
|
||||
var t = IntSetGet(s, key shr TrunkShift)
|
||||
if t != nil:
|
||||
var u = key and TrunkMask
|
||||
result = (t.bits[u shr IntShift] and (1 shl (u and IntMask))) != 0
|
||||
if not result:
|
||||
t.bits[u shr IntShift] = t.bits[u shr IntShift] or
|
||||
(1 shl (u and IntMask))
|
||||
else:
|
||||
Incl(s, key)
|
||||
result = false
|
||||
|
||||
# ------------- chunk management ----------------------------------------------
|
||||
proc pageIndex(c: PChunk): int {.inline.} =
|
||||
result = cast[TAddress](c) shr PageShift
|
||||
|
||||
proc pageIndex(p: pointer): int {.inline.} =
|
||||
result = cast[TAddress](p) shr PageShift
|
||||
|
||||
proc pageAddr(p: pointer): PChunk {.inline.} =
|
||||
result = cast[PChunk](cast[TAddress](p) and not PageMask)
|
||||
assert(Contains(allocator.chunkStarts, pageIndex(result)))
|
||||
|
||||
var lastSize = PageSize
|
||||
|
||||
proc requestOsChunks(a: var TAllocator, size: int): PBigChunk =
|
||||
incCurrMem(a, size)
|
||||
inc(a.freeMem, size)
|
||||
result = cast[PBigChunk](osAllocPages(size))
|
||||
assert((cast[TAddress](result) and PageMask) == 0)
|
||||
#zeroMem(result, size)
|
||||
result.next = nil
|
||||
result.prev = nil
|
||||
result.used = false
|
||||
result.size = size
|
||||
# update next.prevSize:
|
||||
var nxt = cast[TAddress](result) +% size
|
||||
assert((nxt and PageMask) == 0)
|
||||
var next = cast[PChunk](nxt)
|
||||
if pageIndex(next) in a.chunkStarts:
|
||||
#echo("Next already allocated!")
|
||||
next.prevSize = size
|
||||
# set result.prevSize:
|
||||
var prv = cast[TAddress](result) -% lastSize
|
||||
assert((nxt and PageMask) == 0)
|
||||
var prev = cast[PChunk](prv)
|
||||
if pageIndex(prev) in a.chunkStarts and prev.size == lastSize:
|
||||
#echo("Prev already allocated!")
|
||||
result.prevSize = lastSize
|
||||
else:
|
||||
result.prevSize = 0 # unknown
|
||||
lastSize = size # for next request
|
||||
|
||||
proc freeOsChunks(a: var TAllocator, p: pointer, size: int) =
|
||||
# update next.prevSize:
|
||||
var c = cast[PChunk](p)
|
||||
var nxt = cast[TAddress](p) +% c.size
|
||||
assert((nxt and PageMask) == 0)
|
||||
var next = cast[PChunk](nxt)
|
||||
if pageIndex(next) in a.chunkStarts:
|
||||
next.prevSize = 0 # XXX used
|
||||
excl(a.chunkStarts, pageIndex(p))
|
||||
osDeallocPages(p, size)
|
||||
decCurrMem(a, size)
|
||||
dec(a.freeMem, size)
|
||||
#c_fprintf(c_stdout, "[Alloc] back to OS: %ld\n", size)
|
||||
|
||||
proc isAccessible(p: pointer): bool {.inline.} =
|
||||
result = Contains(allocator.chunkStarts, pageIndex(p))
|
||||
|
||||
proc contains[T](list, x: T): bool =
|
||||
var it = list
|
||||
while it != nil:
|
||||
if it == x: return true
|
||||
it = it.next
|
||||
|
||||
proc writeFreeList(a: TAllocator) =
|
||||
var it = a.freeChunksList
|
||||
c_fprintf(c_stdout, "freeChunksList: %p\n", it)
|
||||
while it != nil:
|
||||
c_fprintf(c_stdout, "it: %p, next: %p, prev: %p\n",
|
||||
it, it.next, it.prev)
|
||||
it = it.next
|
||||
|
||||
proc ListAdd[T](head: var T, c: T) {.inline.} =
|
||||
assert(c notin head)
|
||||
assert c.prev == nil
|
||||
assert c.next == nil
|
||||
c.next = head
|
||||
if head != nil:
|
||||
assert head.prev == nil
|
||||
head.prev = c
|
||||
head = c
|
||||
|
||||
proc ListRemove[T](head: var T, c: T) {.inline.} =
|
||||
assert(c in head)
|
||||
if c == head:
|
||||
head = c.next
|
||||
assert c.prev == nil
|
||||
if head != nil: head.prev = nil
|
||||
else:
|
||||
assert c.prev != nil
|
||||
c.prev.next = c.next
|
||||
if c.next != nil: c.next.prev = c.prev
|
||||
c.next = nil
|
||||
c.prev = nil
|
||||
|
||||
proc isSmallChunk(c: PChunk): bool {.inline.} =
|
||||
return c.size <= SmallChunkSize-smallChunkOverhead()
|
||||
#return c.size < SmallChunkSize
|
||||
|
||||
proc chunkUnused(c: PChunk): bool {.inline.} =
|
||||
result = not c.used
|
||||
|
||||
proc updatePrevSize(a: var TAllocator, c: PBigChunk,
|
||||
prevSize: int) {.inline.} =
|
||||
var ri = cast[PChunk](cast[TAddress](c) +% c.size)
|
||||
assert((cast[TAddress](ri) and PageMask) == 0)
|
||||
if isAccessible(ri):
|
||||
ri.prevSize = prevSize
|
||||
|
||||
proc freeBigChunk(a: var TAllocator, c: PBigChunk) =
|
||||
var c = c
|
||||
assert(c.size >= PageSize)
|
||||
inc(a.freeMem, c.size)
|
||||
when coalescRight:
|
||||
var ri = cast[PChunk](cast[TAddress](c) +% c.size)
|
||||
assert((cast[TAddress](ri) and PageMask) == 0)
|
||||
if isAccessible(ri) and chunkUnused(ri):
|
||||
assert(not isSmallChunk(ri))
|
||||
if not isSmallChunk(ri):
|
||||
ListRemove(a.freeChunksList, cast[PBigChunk](ri))
|
||||
inc(c.size, ri.size)
|
||||
excl(a.chunkStarts, pageIndex(ri))
|
||||
when coalescLeft:
|
||||
if c.prevSize != 0:
|
||||
var le = cast[PChunk](cast[TAddress](c) -% c.prevSize)
|
||||
assert((cast[TAddress](le) and PageMask) == 0)
|
||||
if isAccessible(le) and chunkUnused(le):
|
||||
assert(not isSmallChunk(le))
|
||||
if not isSmallChunk(le):
|
||||
ListRemove(a.freeChunksList, cast[PBigChunk](le))
|
||||
inc(le.size, c.size)
|
||||
excl(a.chunkStarts, pageIndex(c))
|
||||
c = cast[PBigChunk](le)
|
||||
|
||||
if c.size < ChunkOsReturn:
|
||||
incl(a.chunkStarts, pageIndex(c))
|
||||
updatePrevSize(a, c, c.size)
|
||||
ListAdd(a.freeChunksList, c)
|
||||
c.used = false
|
||||
else:
|
||||
freeOsChunks(a, c, c.size)
|
||||
|
||||
proc splitChunk(a: var TAllocator, c: PBigChunk, size: int) =
|
||||
var rest = cast[PBigChunk](cast[TAddress](c) +% size)
|
||||
if rest in a.freeChunksList:
|
||||
c_fprintf(c_stdout, "to add: %p\n", rest)
|
||||
writeFreeList(allocator)
|
||||
assert false
|
||||
rest.size = c.size - size
|
||||
rest.used = false
|
||||
rest.next = nil
|
||||
rest.prev = nil
|
||||
rest.prevSize = size
|
||||
updatePrevSize(a, c, rest.size)
|
||||
c.size = size
|
||||
incl(a.chunkStarts, pageIndex(rest))
|
||||
ListAdd(a.freeChunksList, rest)
|
||||
|
||||
proc getBigChunk(a: var TAllocator, size: int): PBigChunk =
|
||||
# use first fit for now:
|
||||
assert((size and PageMask) == 0)
|
||||
assert(size > 0)
|
||||
result = a.freeChunksList
|
||||
block search:
|
||||
while result != nil:
|
||||
#if not chunkUnused(result):
|
||||
# c_fprintf(c_stdout, "%lld\n", int(result.used))
|
||||
assert chunkUnused(result)
|
||||
if result.size == size:
|
||||
ListRemove(a.freeChunksList, result)
|
||||
break search
|
||||
elif result.size > size:
|
||||
#c_fprintf(c_stdout, "res size: %lld; size: %lld\n", result.size, size)
|
||||
ListRemove(a.freeChunksList, result)
|
||||
splitChunk(a, result, size)
|
||||
break search
|
||||
result = result.next
|
||||
assert result != a.freeChunksList
|
||||
if size < InitialMemoryRequest:
|
||||
result = requestOsChunks(a, InitialMemoryRequest)
|
||||
splitChunk(a, result, size)
|
||||
else:
|
||||
result = requestOsChunks(a, size)
|
||||
result.prevSize = 0 # XXX why is this needed?
|
||||
result.used = true
|
||||
incl(a.chunkStarts, pageIndex(result))
|
||||
dec(a.freeMem, size)
|
||||
|
||||
proc getSmallChunk(a: var TAllocator): PSmallChunk =
|
||||
var res = getBigChunk(a, PageSize)
|
||||
assert res.prev == nil
|
||||
assert res.next == nil
|
||||
result = cast[PSmallChunk](res)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
proc getCellSize(p: pointer): int {.inline.} =
|
||||
var c = pageAddr(p)
|
||||
result = c.size
|
||||
|
||||
proc rawAlloc(a: var TAllocator, requestedSize: int): pointer =
|
||||
assert(roundup(65, 8) == 72)
|
||||
assert requestedSize >= sizeof(TFreeCell)
|
||||
var size = roundup(requestedSize, MemAlign)
|
||||
#c_fprintf(c_stdout, "alloc; size: %ld; %ld\n", requestedSize, size)
|
||||
if size <= SmallChunkSize-smallChunkOverhead():
|
||||
# allocate a small block: for small chunks, we use only its next pointer
|
||||
var s = size div MemAlign
|
||||
var c = a.freeSmallChunks[s]
|
||||
if c == nil:
|
||||
c = getSmallChunk(a)
|
||||
c.freeList = nil
|
||||
assert c.size == PageSize
|
||||
c.size = size
|
||||
c.acc = size
|
||||
c.free = SmallChunkSize - smallChunkOverhead() - size
|
||||
c.next = nil
|
||||
c.prev = nil
|
||||
ListAdd(a.freeSmallChunks[s], c)
|
||||
result = addr(c.data)
|
||||
assert((cast[TAddress](result) and (MemAlign-1)) == 0)
|
||||
else:
|
||||
assert c.next != c
|
||||
#if c.size != size:
|
||||
# c_fprintf(c_stdout, "csize: %lld; size %lld\n", c.size, size)
|
||||
assert c.size == size
|
||||
if c.freeList == nil:
|
||||
assert(c.acc + smallChunkOverhead() + size <= SmallChunkSize)
|
||||
result = cast[pointer](cast[TAddress](addr(c.data)) +% c.acc)
|
||||
inc(c.acc, size)
|
||||
else:
|
||||
result = c.freeList
|
||||
assert(c.freeList.zeroField == 0)
|
||||
c.freeList = c.freeList.next
|
||||
dec(c.free, size)
|
||||
assert((cast[TAddress](result) and (MemAlign-1)) == 0)
|
||||
if c.free < size:
|
||||
ListRemove(a.freeSmallChunks[s], c)
|
||||
else:
|
||||
size = roundup(requestedSize+bigChunkOverhead(), PageSize)
|
||||
# allocate a large block
|
||||
var c = getBigChunk(a, size)
|
||||
assert c.prev == nil
|
||||
assert c.next == nil
|
||||
assert c.size == size
|
||||
result = addr(c.data)
|
||||
assert((cast[TAddress](result) and (MemAlign-1)) == 0)
|
||||
assert(isAccessible(result))
|
||||
|
||||
proc rawDealloc(a: var TAllocator, p: pointer) =
|
||||
var c = pageAddr(p)
|
||||
if isSmallChunk(c):
|
||||
# `p` is within a small chunk:
|
||||
var c = cast[PSmallChunk](c)
|
||||
var s = c.size
|
||||
var f = cast[ptr TFreeCell](p)
|
||||
#echo("setting to nil: ", $cast[TAddress](addr(f.zeroField)))
|
||||
assert(f.zeroField != 0)
|
||||
f.zeroField = 0
|
||||
f.next = c.freeList
|
||||
c.freeList = f
|
||||
when overwriteFree:
|
||||
# set to 0xff to check for usage after free bugs:
|
||||
c_memset(cast[pointer](cast[int](p) +% sizeof(TFreeCell)), -1'i32,
|
||||
s -% sizeof(TFreeCell))
|
||||
# check if it is not in the freeSmallChunks[s] list:
|
||||
if c.free < s:
|
||||
assert c notin a.freeSmallChunks[s div memAlign]
|
||||
# add it to the freeSmallChunks[s] array:
|
||||
ListAdd(a.freeSmallChunks[s div memAlign], c)
|
||||
inc(c.free, s)
|
||||
else:
|
||||
inc(c.free, s)
|
||||
if c.free == SmallChunkSize-smallChunkOverhead():
|
||||
ListRemove(a.freeSmallChunks[s div memAlign], c)
|
||||
c.size = SmallChunkSize
|
||||
freeBigChunk(a, cast[PBigChunk](c))
|
||||
else:
|
||||
# set to 0xff to check for usage after free bugs:
|
||||
when overwriteFree: c_memset(p, -1'i32, c.size -% bigChunkOverhead())
|
||||
# free big chunk
|
||||
freeBigChunk(a, cast[PBigChunk](c))
|
||||
|
||||
proc isAllocatedPtr(a: TAllocator, p: pointer): bool =
|
||||
if isAccessible(p):
|
||||
var c = pageAddr(p)
|
||||
if not chunkUnused(c):
|
||||
if isSmallChunk(c):
|
||||
var c = cast[PSmallChunk](c)
|
||||
var offset = (cast[TAddress](p) and (PageSize-1)) -%
|
||||
smallChunkOverhead()
|
||||
result = (c.acc >% offset) and (offset %% c.size == 0) and
|
||||
(cast[ptr TFreeCell](p).zeroField >% 1)
|
||||
else:
|
||||
var c = cast[PBigChunk](c)
|
||||
result = p == addr(c.data) and cast[ptr TFreeCell](p).zeroField >% 1
|
||||
|
||||
# ---------------------- interface to programs -------------------------------
|
||||
|
||||
proc alloc(size: int): pointer =
|
||||
result = rawAlloc(allocator, size+sizeof(TFreeCell))
|
||||
cast[ptr TFreeCell](result).zeroField = 1 # mark it as used
|
||||
assert(not isAllocatedPtr(allocator, result))
|
||||
result = cast[pointer](cast[TAddress](result) +% sizeof(TFreeCell))
|
||||
|
||||
proc alloc0(size: int): pointer =
|
||||
result = alloc(size)
|
||||
zeroMem(result, size)
|
||||
|
||||
proc dealloc(p: pointer) =
|
||||
var x = cast[pointer](cast[TAddress](p) -% sizeof(TFreeCell))
|
||||
assert(cast[ptr TFreeCell](x).zeroField == 1)
|
||||
rawDealloc(allocator, x)
|
||||
assert(not isAllocatedPtr(allocator, x))
|
||||
|
||||
proc ptrSize(p: pointer): int =
|
||||
var x = cast[pointer](cast[TAddress](p) -% sizeof(TFreeCell))
|
||||
result = pageAddr(x).size - sizeof(TFreeCell)
|
||||
|
||||
proc realloc(p: pointer, newsize: int): pointer =
|
||||
if newsize > 0:
|
||||
result = alloc(newsize)
|
||||
if p != nil:
|
||||
copyMem(result, p, ptrSize(p))
|
||||
dealloc(p)
|
||||
elif p != nil:
|
||||
dealloc(p)
|
||||
|
||||
proc countFreeMem(): int =
|
||||
# only used for assertions
|
||||
var it = allocator.freeChunksList
|
||||
while it != nil:
|
||||
inc(result, it.size)
|
||||
it = it.next
|
||||
|
||||
proc getFreeMem(): int =
|
||||
result = allocator.freeMem
|
||||
#assert(result == countFreeMem())
|
||||
|
||||
proc getTotalMem(): int = return allocator.currMem
|
||||
proc getOccupiedMem(): int = return getTotalMem() - getFreeMem()
|
||||
|
||||
when isMainModule:
|
||||
const iterations = 4000_000
|
||||
incl(allocator.chunkStarts, 11)
|
||||
assert 11 in allocator.chunkStarts
|
||||
excl(allocator.chunkStarts, 11)
|
||||
assert 11 notin allocator.chunkStarts
|
||||
var p: array [1..iterations, pointer]
|
||||
for i in 7..7:
|
||||
var x = i * 8
|
||||
for j in 1.. iterations:
|
||||
p[j] = alloc(allocator, x)
|
||||
for j in 1..iterations:
|
||||
assert isAllocatedPtr(allocator, p[j])
|
||||
echo($i, " used memory: ", $(allocator.currMem))
|
||||
for j in countdown(iterations, 1):
|
||||
#echo("j: ", $j)
|
||||
dealloc(allocator, p[j])
|
||||
assert(not isAllocatedPtr(allocator, p[j]))
|
||||
echo($i, " after freeing: ", $(allocator.currMem))
|
||||
|
||||
104
lib/system/ansi_c.nim
Normal file
104
lib/system/ansi_c.nim
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2009 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
# This include file contains headers of Ansi C procs
|
||||
# and definitions of Ansi C types in Nimrod syntax
|
||||
# All symbols are prefixed with 'c_' to avoid ambiguities
|
||||
|
||||
{.push hints:off}
|
||||
|
||||
proc c_strcmp(a, b: CString): cint {.nodecl, importc: "strcmp".}
|
||||
proc c_memcmp(a, b: CString, size: cint): cint {.nodecl, importc: "memcmp".}
|
||||
proc c_memcpy(a, b: CString, size: cint) {.nodecl, importc: "memcpy".}
|
||||
proc c_strlen(a: CString): int {.nodecl, importc: "strlen".}
|
||||
proc c_memset(p: pointer, value: cint, size: int) {.nodecl, importc: "memset".}
|
||||
|
||||
type
|
||||
C_TextFile {.importc: "FILE", nodecl, final.} = object # empty record for
|
||||
# data hiding
|
||||
C_BinaryFile {.importc: "FILE", nodecl, final.} = object
|
||||
C_TextFileStar = ptr CTextFile
|
||||
C_BinaryFileStar = ptr CBinaryFile
|
||||
|
||||
C_JmpBuf {.importc: "jmp_buf".} = array[0..31, int]
|
||||
|
||||
var
|
||||
c_stdin {.importc: "stdin", noDecl.}: C_TextFileStar
|
||||
c_stdout {.importc: "stdout", noDecl.}: C_TextFileStar
|
||||
c_stderr {.importc: "stderr", noDecl.}: C_TextFileStar
|
||||
|
||||
var # constants faked as variables:
|
||||
SIGINT {.importc: "SIGINT", nodecl.}: cint
|
||||
SIGSEGV {.importc: "SIGSEGV", nodecl.}: cint
|
||||
SIGABRT {.importc: "SIGABRT", nodecl.}: cint
|
||||
SIGFPE {.importc: "SIGFPE", nodecl.}: cint
|
||||
SIGILL {.importc: "SIGILL", nodecl.}: cint
|
||||
|
||||
when defined(macosx):
|
||||
var
|
||||
SIGBUS {.importc: "SIGBUS", nodecl.}: cint
|
||||
# hopefully this does not lead to new bugs
|
||||
else:
|
||||
var
|
||||
SIGBUS {.importc: "SIGSEGV", nodecl.}: cint
|
||||
# only Mac OS X has this shit
|
||||
|
||||
proc c_longjmp(jmpb: C_JmpBuf, retval: cint) {.nodecl, importc: "longjmp".}
|
||||
proc c_setjmp(jmpb: var C_JmpBuf): cint {.nodecl, importc: "setjmp".}
|
||||
|
||||
proc c_signal(sig: cint, handler: proc (a: cint) {.noconv.}) {.
|
||||
importc: "signal", header: "<signal.h>".}
|
||||
proc c_raise(sig: cint) {.importc: "raise", header: "<signal.h>".}
|
||||
|
||||
proc c_fputs(c: cstring, f: C_TextFileStar) {.importc: "fputs", noDecl.}
|
||||
proc c_fgets(c: cstring, n: int, f: C_TextFileStar): cstring {.
|
||||
importc: "fgets", noDecl.}
|
||||
proc c_fgetc(stream: C_TextFileStar): int {.importc: "fgetc", nodecl.}
|
||||
proc c_ungetc(c: int, f: C_TextFileStar) {.importc: "ungetc", nodecl.}
|
||||
proc c_putc(c: Char, stream: C_TextFileStar) {.importc: "putc", nodecl.}
|
||||
proc c_fprintf(f: C_TextFileStar, frmt: CString) {.
|
||||
importc: "fprintf", nodecl, varargs.}
|
||||
|
||||
proc c_fopen(filename, mode: cstring): C_TextFileStar {.
|
||||
importc: "fopen", nodecl.}
|
||||
proc c_fclose(f: C_TextFileStar) {.importc: "fclose", nodecl.}
|
||||
|
||||
proc c_sprintf(buf, frmt: CString) {.nodecl, importc: "sprintf", varargs.}
|
||||
# we use it only in a way that cannot lead to security issues
|
||||
|
||||
proc c_fread(buf: Pointer, size, n: int, f: C_BinaryFileStar): int {.
|
||||
importc: "fread", noDecl.}
|
||||
proc c_fseek(f: C_BinaryFileStar, offset: clong, whence: int): int {.
|
||||
importc: "fseek", noDecl.}
|
||||
|
||||
proc c_fwrite(buf: Pointer, size, n: int, f: C_BinaryFileStar): int {.
|
||||
importc: "fwrite", noDecl.}
|
||||
|
||||
proc c_exit(errorcode: cint) {.importc: "exit", nodecl.}
|
||||
proc c_ferror(stream: C_TextFileStar): bool {.importc: "ferror", nodecl.}
|
||||
proc c_fflush(stream: C_TextFileStar) {.importc: "fflush", nodecl.}
|
||||
proc c_abort() {.importc: "abort", nodecl.}
|
||||
proc c_feof(stream: C_TextFileStar): bool {.importc: "feof", nodecl.}
|
||||
|
||||
proc c_malloc(size: int): pointer {.importc: "malloc", nodecl.}
|
||||
proc c_free(p: pointer) {.importc: "free", nodecl.}
|
||||
proc c_realloc(p: pointer, newsize: int): pointer {.importc: "realloc", nodecl.}
|
||||
|
||||
var errno {.importc, header: "<errno.h>".}: cint ## error variable
|
||||
proc strerror(errnum: cint): cstring {.importc, header: "<string.h>".}
|
||||
|
||||
proc c_remove(filename: CString): cint {.importc: "remove", noDecl.}
|
||||
proc c_rename(oldname, newname: CString): cint {.importc: "rename", noDecl.}
|
||||
|
||||
proc c_system(cmd: CString): cint {.importc: "system", header: "<stdlib.h>".}
|
||||
proc c_getenv(env: CString): CString {.importc: "getenv", noDecl.}
|
||||
proc c_putenv(env: CString): cint {.importc: "putenv", noDecl.}
|
||||
|
||||
{.pop}
|
||||
|
||||
316
lib/system/arithm.nim
Normal file
316
lib/system/arithm.nim
Normal file
|
|
@ -0,0 +1,316 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2008 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
|
||||
# simple integer arithmetic with overflow checking
|
||||
|
||||
proc raiseOverflow {.compilerproc, noinline.} =
|
||||
# a single proc to reduce code size to a minimum
|
||||
raise newException(EOverflow, "over- or underflow")
|
||||
|
||||
proc raiseDivByZero {.compilerproc, noinline.} =
|
||||
raise newException(EDivByZero, "divison by zero")
|
||||
|
||||
proc addInt64(a, b: int64): int64 {.compilerProc, inline.} =
|
||||
result = a +% b
|
||||
if (result xor a) >= int64(0) or (result xor b) >= int64(0):
|
||||
return result
|
||||
raiseOverflow()
|
||||
|
||||
proc subInt64(a, b: int64): int64 {.compilerProc, inline.} =
|
||||
result = a -% b
|
||||
if (result xor a) >= int64(0) or (result xor not b) >= int64(0):
|
||||
return result
|
||||
raiseOverflow()
|
||||
|
||||
proc negInt64(a: int64): int64 {.compilerProc, inline.} =
|
||||
if a != low(int64): return -a
|
||||
raiseOverflow()
|
||||
|
||||
proc absInt64(a: int64): int64 {.compilerProc, inline.} =
|
||||
if a != low(int64):
|
||||
if a >= 0: return a
|
||||
else: return -a
|
||||
raiseOverflow()
|
||||
|
||||
proc divInt64(a, b: int64): int64 {.compilerProc, inline.} =
|
||||
if b == int64(0):
|
||||
raiseDivByZero()
|
||||
if a == low(int64) and b == int64(-1):
|
||||
raiseOverflow()
|
||||
return a div b
|
||||
|
||||
proc modInt64(a, b: int64): int64 {.compilerProc, inline.} =
|
||||
if b == int64(0):
|
||||
raiseDivByZero()
|
||||
return a mod b
|
||||
|
||||
#
|
||||
# This code has been inspired by Python's source code.
|
||||
# The native int product x*y is either exactly right or *way* off, being
|
||||
# just the last n bits of the true product, where n is the number of bits
|
||||
# in an int (the delivered product is the true product plus i*2**n for
|
||||
# some integer i).
|
||||
#
|
||||
# The native float64 product x*y is subject to three
|
||||
# rounding errors: on a sizeof(int)==8 box, each cast to double can lose
|
||||
# info, and even on a sizeof(int)==4 box, the multiplication can lose info.
|
||||
# But, unlike the native int product, it's not in *range* trouble: even
|
||||
# if sizeof(int)==32 (256-bit ints), the product easily fits in the
|
||||
# dynamic range of a float64. So the leading 50 (or so) bits of the float64
|
||||
# product are correct.
|
||||
#
|
||||
# We check these two ways against each other, and declare victory if they're
|
||||
# approximately the same. Else, because the native int product is the only
|
||||
# one that can lose catastrophic amounts of information, it's the native int
|
||||
# product that must have overflowed.
|
||||
#
|
||||
proc mulInt64(a, b: int64): int64 {.compilerproc.} =
|
||||
var
|
||||
resAsFloat, floatProd: float64
|
||||
result = a *% b
|
||||
floatProd = toBiggestFloat(a) # conversion
|
||||
floatProd = floatProd * toBiggestFloat(b)
|
||||
resAsFloat = toBiggestFloat(result)
|
||||
|
||||
# Fast path for normal case: small multiplicands, and no info
|
||||
# is lost in either method.
|
||||
if resAsFloat == floatProd: return result
|
||||
|
||||
# Somebody somewhere lost info. Close enough, or way off? Note
|
||||
# that a != 0 and b != 0 (else resAsFloat == floatProd == 0).
|
||||
# The difference either is or isn't significant compared to the
|
||||
# true value (of which floatProd is a good approximation).
|
||||
|
||||
# abs(diff)/abs(prod) <= 1/32 iff
|
||||
# 32 * abs(diff) <= abs(prod) -- 5 good bits is "close enough"
|
||||
if 32.0 * abs(resAsFloat - floatProd) <= abs(floatProd):
|
||||
return result
|
||||
raiseOverflow()
|
||||
|
||||
|
||||
proc absInt(a: int): int {.compilerProc, inline.} =
|
||||
if a != low(int):
|
||||
if a >= 0: return a
|
||||
else: return -a
|
||||
raiseOverflow()
|
||||
|
||||
const
|
||||
asmVersion = defined(I386) and (defined(vcc) or defined(wcc) or
|
||||
defined(dmc) or defined(gcc) or defined(llvm_gcc))
|
||||
# my Version of Borland C++Builder does not have
|
||||
# tasm32, which is needed for assembler blocks
|
||||
# this is why Borland is not included in the 'when'
|
||||
|
||||
when asmVersion and not defined(gcc) and not defined(llvm_gcc):
|
||||
# assembler optimized versions for compilers that
|
||||
# have an intel syntax assembler:
|
||||
proc addInt(a, b: int): int {.compilerProc, pure.} =
|
||||
# a in eax, and b in edx
|
||||
asm """
|
||||
mov eax, `a`
|
||||
add eax, `b`
|
||||
jno theEnd
|
||||
call `raiseOverflow`
|
||||
theEnd:
|
||||
"""
|
||||
|
||||
proc subInt(a, b: int): int {.compilerProc, pure.} =
|
||||
asm """
|
||||
mov eax, `a`
|
||||
sub eax, `b`
|
||||
jno theEnd
|
||||
call `raiseOverflow`
|
||||
theEnd:
|
||||
"""
|
||||
|
||||
proc negInt(a: int): int {.compilerProc, pure.} =
|
||||
asm """
|
||||
mov eax, `a`
|
||||
neg eax
|
||||
jno theEnd
|
||||
call `raiseOverflow`
|
||||
theEnd:
|
||||
"""
|
||||
|
||||
proc divInt(a, b: int): int {.compilerProc, pure.} =
|
||||
asm """
|
||||
mov eax, `a`
|
||||
mov ecx, `b`
|
||||
xor edx, edx
|
||||
idiv ecx
|
||||
jno theEnd
|
||||
call `raiseOverflow`
|
||||
theEnd:
|
||||
"""
|
||||
|
||||
proc modInt(a, b: int): int {.compilerProc, pure.} =
|
||||
asm """
|
||||
mov eax, `a`
|
||||
mov ecx, `b`
|
||||
xor edx, edx
|
||||
idiv ecx
|
||||
jno theEnd
|
||||
call `raiseOverflow`
|
||||
theEnd:
|
||||
mov eax, edx
|
||||
"""
|
||||
|
||||
proc mulInt(a, b: int): int {.compilerProc, pure.} =
|
||||
asm """
|
||||
mov eax, `a`
|
||||
mov ecx, `b`
|
||||
xor edx, edx
|
||||
imul ecx
|
||||
jno theEnd
|
||||
call `raiseOverflow`
|
||||
theEnd:
|
||||
"""
|
||||
|
||||
elif false: # asmVersion and (defined(gcc) or defined(llvm_gcc)):
|
||||
proc addInt(a, b: int): int {.compilerProc, inline.} =
|
||||
# don't use a pure proc here!
|
||||
asm """
|
||||
"addl %%ecx, %%eax\n"
|
||||
"jno 1\n"
|
||||
"call _raiseOverflow\n"
|
||||
"1: \n"
|
||||
:"=a"(`result`)
|
||||
:"a"(`a`), "c"(`b`)
|
||||
"""
|
||||
|
||||
proc subInt(a, b: int): int {.compilerProc, inline.} =
|
||||
asm """ "subl %%ecx,%%eax\n"
|
||||
"jno 1\n"
|
||||
"call _raiseOverflow\n"
|
||||
"1: \n"
|
||||
:"=a"(`result`)
|
||||
:"a"(`a`), "c"(`b`)
|
||||
"""
|
||||
|
||||
proc mulInt(a, b: int): int {.compilerProc, inline.} =
|
||||
asm """ "xorl %%edx, %%edx\n"
|
||||
"imull %%ecx\n"
|
||||
"jno 1\n"
|
||||
"call _raiseOverflow\n"
|
||||
"1: \n"
|
||||
:"=a"(`result`)
|
||||
:"a"(`a`), "c"(`b`)
|
||||
:"%edx"
|
||||
"""
|
||||
|
||||
proc negInt(a: int): int {.compilerProc, inline.} =
|
||||
asm """ "negl %%eax\n"
|
||||
"jno 1\n"
|
||||
"call _raiseOverflow\n"
|
||||
"1: \n"
|
||||
:"=a"(`result`)
|
||||
:"a"(`a`)
|
||||
"""
|
||||
|
||||
proc divInt(a, b: int): int {.compilerProc, inline.} =
|
||||
asm """ "xorl %%edx, %%edx\n"
|
||||
"idivl %%ecx\n"
|
||||
"jno 1\n"
|
||||
"call _raiseOverflow\n"
|
||||
"1: \n"
|
||||
:"=a"(`result`)
|
||||
:"a"(`a`), "c"(`b`)
|
||||
:"%edx"
|
||||
"""
|
||||
|
||||
proc modInt(a, b: int): int {.compilerProc, inline.} =
|
||||
asm """ "xorl %%edx, %%edx\n"
|
||||
"idivl %%ecx\n"
|
||||
"jno 1\n"
|
||||
"call _raiseOverflow\n"
|
||||
"1: \n"
|
||||
"movl %%edx, %%eax"
|
||||
:"=a"(`result`)
|
||||
:"a"(`a`), "c"(`b`)
|
||||
:"%edx"
|
||||
"""
|
||||
|
||||
# Platform independant versions of the above (slower!)
|
||||
when not defined(addInt):
|
||||
proc addInt(a, b: int): int {.compilerProc, inline.} =
|
||||
result = a +% b
|
||||
if (result xor a) >= 0 or (result xor b) >= 0:
|
||||
return result
|
||||
raiseOverflow()
|
||||
|
||||
when not defined(subInt):
|
||||
proc subInt(a, b: int): int {.compilerProc, inline.} =
|
||||
result = a -% b
|
||||
if (result xor a) >= 0 or (result xor not b) >= 0:
|
||||
return result
|
||||
raiseOverflow()
|
||||
|
||||
when not defined(negInt):
|
||||
proc negInt(a: int): int {.compilerProc, inline.} =
|
||||
if a != low(int): return -a
|
||||
raiseOverflow()
|
||||
|
||||
when not defined(divInt):
|
||||
proc divInt(a, b: int): int {.compilerProc, inline.} =
|
||||
if b == 0:
|
||||
raiseDivByZero()
|
||||
if a == low(int) and b == -1:
|
||||
raiseOverflow()
|
||||
return a div b
|
||||
|
||||
when not defined(modInt):
|
||||
proc modInt(a, b: int): int {.compilerProc, inline.} =
|
||||
if b == 0:
|
||||
raiseDivByZero()
|
||||
return a mod b
|
||||
|
||||
when not defined(mulInt):
|
||||
#
|
||||
# This code has been inspired by Python's source code.
|
||||
# The native int product x*y is either exactly right or *way* off, being
|
||||
# just the last n bits of the true product, where n is the number of bits
|
||||
# in an int (the delivered product is the true product plus i*2**n for
|
||||
# some integer i).
|
||||
#
|
||||
# The native float64 product x*y is subject to three
|
||||
# rounding errors: on a sizeof(int)==8 box, each cast to double can lose
|
||||
# info, and even on a sizeof(int)==4 box, the multiplication can lose info.
|
||||
# But, unlike the native int product, it's not in *range* trouble: even
|
||||
# if sizeof(int)==32 (256-bit ints), the product easily fits in the
|
||||
# dynamic range of a float64. So the leading 50 (or so) bits of the float64
|
||||
# product are correct.
|
||||
#
|
||||
# We check these two ways against each other, and declare victory if
|
||||
# they're approximately the same. Else, because the native int product is
|
||||
# the only one that can lose catastrophic amounts of information, it's the
|
||||
# native int product that must have overflowed.
|
||||
#
|
||||
proc mulInt(a, b: int): int {.compilerProc.} =
|
||||
var
|
||||
resAsFloat, floatProd: float
|
||||
|
||||
result = a *% b
|
||||
floatProd = toFloat(a) * toFloat(b)
|
||||
resAsFloat = toFloat(result)
|
||||
|
||||
# Fast path for normal case: small multiplicands, and no info
|
||||
# is lost in either method.
|
||||
if resAsFloat == floatProd: return result
|
||||
|
||||
# Somebody somewhere lost info. Close enough, or way off? Note
|
||||
# that a != 0 and b != 0 (else resAsFloat == floatProd == 0).
|
||||
# The difference either is or isn't significant compared to the
|
||||
# true value (of which floatProd is a good approximation).
|
||||
|
||||
# abs(diff)/abs(prod) <= 1/32 iff
|
||||
# 32 * abs(diff) <= abs(prod) -- 5 good bits is "close enough"
|
||||
if 32.0 * abs(resAsFloat - floatProd) <= abs(floatProd):
|
||||
return result
|
||||
raiseOverflow()
|
||||
120
lib/system/assign.nim
Normal file
120
lib/system/assign.nim
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2006 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
#when defined(debugGC):
|
||||
# {.define: logAssign.}
|
||||
proc genericAssign(dest, src: Pointer, mt: PNimType) {.compilerProc.}
|
||||
proc genericAssignAux(dest, src: Pointer, n: ptr TNimNode) =
|
||||
var
|
||||
d = cast[TAddress](dest)
|
||||
s = cast[TAddress](src)
|
||||
case n.kind
|
||||
of nkNone: assert(false)
|
||||
of nkSlot:
|
||||
genericAssign(cast[pointer](d +% n.offset), cast[pointer](s +% n.offset),
|
||||
n.typ)
|
||||
of nkList:
|
||||
for i in 0..n.len-1:
|
||||
genericAssignAux(dest, src, n.sons[i])
|
||||
of nkCase:
|
||||
copyMem(cast[pointer](d +% n.offset), cast[pointer](s +% n.offset),
|
||||
n.typ.size)
|
||||
var m = selectBranch(src, n)
|
||||
if m != nil: genericAssignAux(dest, src, m)
|
||||
|
||||
proc genericAssign(dest, src: Pointer, mt: PNimType) =
|
||||
var
|
||||
d = cast[TAddress](dest)
|
||||
s = cast[TAddress](src)
|
||||
|
||||
assert(mt != nil)
|
||||
case mt.Kind
|
||||
of tySequence:
|
||||
var s2 = cast[ppointer](src)^
|
||||
var seq = cast[PGenericSeq](s2)
|
||||
if s2 == nil: # this can happen! nil sequences are allowed
|
||||
var x = cast[ppointer](dest)
|
||||
x^ = nil
|
||||
return
|
||||
assert(dest != nil)
|
||||
unsureAsgnRef(cast[ppointer](dest),
|
||||
newObj(mt, seq.len * mt.base.size + GenericSeqSize))
|
||||
var dst = cast[taddress](cast[ppointer](dest)^)
|
||||
for i in 0..seq.len-1:
|
||||
genericAssign(
|
||||
cast[pointer](dst +% i*% mt.base.size +% GenericSeqSize),
|
||||
cast[pointer](cast[taddress](s2) +% i *% mt.base.size +%
|
||||
GenericSeqSize),
|
||||
mt.Base)
|
||||
var dstseq = cast[PGenericSeq](dst)
|
||||
dstseq.len = seq.len
|
||||
dstseq.space = seq.len
|
||||
of tyObject, tyTuple, tyPureObject:
|
||||
# we don't need to copy m_type field for tyObject, as they are equal anyway
|
||||
genericAssignAux(dest, src, mt.node)
|
||||
of tyArray, tyArrayConstr:
|
||||
for i in 0..(mt.size div mt.base.size)-1:
|
||||
genericAssign(cast[pointer](d +% i*% mt.base.size),
|
||||
cast[pointer](s +% i*% mt.base.size), mt.base)
|
||||
of tyString: # a leaf
|
||||
var s2 = cast[ppointer](s)^
|
||||
if s2 != nil: # nil strings are possible!
|
||||
unsureAsgnRef(cast[ppointer](dest), copyString(cast[NimString](s2)))
|
||||
else:
|
||||
var x = cast[ppointer](dest)
|
||||
x^ = nil
|
||||
return
|
||||
of tyRef: # BUGFIX: a long time this has been forgotten!
|
||||
unsureAsgnRef(cast[ppointer](dest), cast[ppointer](s)^)
|
||||
else:
|
||||
copyMem(dest, src, mt.size) # copy raw bits
|
||||
|
||||
proc genericSeqAssign(dest, src: Pointer, mt: PNimType) {.compilerProc.} =
|
||||
var src = src # ugly, but I like to stress the parser sometimes :-)
|
||||
genericAssign(dest, addr(src), mt)
|
||||
|
||||
proc genericAssignOpenArray(dest, src: pointer, len: int,
|
||||
mt: PNimType) {.compilerproc.} =
|
||||
var
|
||||
d = cast[TAddress](dest)
|
||||
s = cast[TAddress](src)
|
||||
for i in 0..len-1:
|
||||
genericAssign(cast[pointer](d +% i*% mt.base.size),
|
||||
cast[pointer](s +% i*% mt.base.size), mt.base)
|
||||
|
||||
proc objectInit(dest: Pointer, typ: PNimType) {.compilerProc.}
|
||||
proc objectInitAux(dest: Pointer, n: ptr TNimNode) =
|
||||
var d = cast[TAddress](dest)
|
||||
case n.kind
|
||||
of nkNone: assert(false)
|
||||
of nkSLot: objectInit(cast[pointer](d +% n.offset), n.typ)
|
||||
of nkList:
|
||||
for i in 0..n.len-1:
|
||||
objectInitAux(dest, n.sons[i])
|
||||
of nkCase:
|
||||
var m = selectBranch(dest, n)
|
||||
if m != nil: objectInitAux(dest, m)
|
||||
|
||||
proc objectInit(dest: Pointer, typ: PNimType) =
|
||||
# the generic init proc that takes care of initialization of complex
|
||||
# objects on the stack or heap
|
||||
var d = cast[TAddress](dest)
|
||||
case typ.kind
|
||||
of tyObject:
|
||||
# iterate over any structural type
|
||||
# here we have to init the type field:
|
||||
var pint = cast[ptr PNimType](dest)
|
||||
pint^ = typ
|
||||
objectInitAux(dest, typ.node)
|
||||
of tyTuple, tyPureObject:
|
||||
objectInitAux(dest, typ.node)
|
||||
of tyArray, tyArrayConstr:
|
||||
for i in 0..(typ.size div typ.base.size)-1:
|
||||
objectInit(cast[pointer](d +% i * typ.base.size), typ.base)
|
||||
else: nil # nothing to do
|
||||
196
lib/system/cellsets.nim
Normal file
196
lib/system/cellsets.nim
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2009 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
# Efficient set of pointers for the GC (and repr)
|
||||
|
||||
type
|
||||
TCell {.pure.} = object
|
||||
refcount: int # the refcount and some flags
|
||||
typ: PNimType
|
||||
when debugGC:
|
||||
filename: cstring
|
||||
line: int
|
||||
|
||||
PCell = ptr TCell
|
||||
|
||||
PPageDesc = ptr TPageDesc
|
||||
TBitIndex = range[0..UnitsPerPage-1]
|
||||
TPageDesc {.final, pure.} = object
|
||||
next: PPageDesc # all nodes are connected with this pointer
|
||||
key: TAddress # start address at bit 0
|
||||
bits: array[TBitIndex, int] # a bit vector
|
||||
|
||||
PPageDescArray = ptr array[0..1000_000, PPageDesc]
|
||||
TCellSet {.final, pure.} = object
|
||||
counter, max: int
|
||||
head: PPageDesc
|
||||
data: PPageDescArray
|
||||
|
||||
PCellArray = ptr array[0..100_000_000, PCell]
|
||||
TCellSeq {.final, pure.} = object
|
||||
len, cap: int
|
||||
d: PCellArray
|
||||
|
||||
# ------------------- cell set handling ---------------------------------------
|
||||
|
||||
proc contains(s: TCellSeq, c: PCell): bool {.inline.} =
|
||||
for i in 0 .. s.len-1:
|
||||
if s.d[i] == c: return True
|
||||
return False
|
||||
|
||||
proc add(s: var TCellSeq, c: PCell) {.inline.} =
|
||||
if s.len >= s.cap:
|
||||
s.cap = s.cap * 3 div 2
|
||||
var d = cast[PCellArray](alloc(s.cap * sizeof(PCell)))
|
||||
copyMem(d, s.d, s.len * sizeof(PCell))
|
||||
dealloc(s.d)
|
||||
s.d = d
|
||||
# XXX: realloc?
|
||||
s.d[s.len] = c
|
||||
inc(s.len)
|
||||
|
||||
proc init(s: var TCellSeq, cap: int = 1024) =
|
||||
s.len = 0
|
||||
s.cap = cap
|
||||
s.d = cast[PCellArray](alloc0(cap * sizeof(PCell)))
|
||||
|
||||
proc deinit(s: var TCellSeq) =
|
||||
dealloc(s.d)
|
||||
s.d = nil
|
||||
s.len = 0
|
||||
s.cap = 0
|
||||
|
||||
const
|
||||
InitCellSetSize = 1024 # must be a power of two!
|
||||
|
||||
proc Init(s: var TCellSet) =
|
||||
s.data = cast[PPageDescArray](alloc0(InitCellSetSize * sizeof(PPageDesc)))
|
||||
s.max = InitCellSetSize-1
|
||||
s.counter = 0
|
||||
s.head = nil
|
||||
|
||||
proc Deinit(s: var TCellSet) =
|
||||
var it = s.head
|
||||
while it != nil:
|
||||
var n = it.next
|
||||
dealloc(it)
|
||||
it = n
|
||||
s.head = nil # play it safe here
|
||||
dealloc(s.data)
|
||||
s.data = nil
|
||||
s.counter = 0
|
||||
|
||||
proc nextTry(h, maxHash: int): int {.inline.} =
|
||||
result = ((5*h) + 1) and maxHash
|
||||
# For any initial h in range(maxHash), repeating that maxHash times
|
||||
# generates each int in range(maxHash) exactly once (see any text on
|
||||
# random-number generation for proof).
|
||||
|
||||
proc CellSetGet(t: TCellSet, key: TAddress): PPageDesc =
|
||||
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 CellSetRawInsert(t: TCellSet, data: PPageDescArray, desc: PPageDesc) =
|
||||
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 CellSetEnlarge(t: var TCellSet) =
|
||||
var oldMax = t.max
|
||||
t.max = ((t.max+1)*2)-1
|
||||
var n = cast[PPageDescArray](alloc0((t.max + 1) * sizeof(PPageDesc)))
|
||||
for i in 0 .. oldmax:
|
||||
if t.data[i] != nil:
|
||||
CellSetRawInsert(t, n, t.data[i])
|
||||
dealloc(t.data)
|
||||
t.data = n
|
||||
|
||||
proc CellSetPut(t: var TCellSet, key: TAddress): PPageDesc =
|
||||
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):
|
||||
CellSetEnlarge(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[PPageDesc](alloc0(sizeof(TPageDesc)))
|
||||
result.next = t.head
|
||||
result.key = key
|
||||
t.head = result
|
||||
t.data[h] = result
|
||||
|
||||
# ---------- slightly higher level procs --------------------------------------
|
||||
|
||||
proc contains(s: TCellSet, cell: PCell): bool =
|
||||
var u = cast[TAddress](cell)
|
||||
var t = CellSetGet(s, u shr PageShift)
|
||||
if t != nil:
|
||||
u = (u %% PageSize) /% MemAlign
|
||||
result = (t.bits[u shr IntShift] and (1 shl (u and IntMask))) != 0
|
||||
else:
|
||||
result = false
|
||||
|
||||
proc incl(s: var TCellSet, cell: PCell) {.noinline.} =
|
||||
var u = cast[TAddress](cell)
|
||||
var t = CellSetPut(s, u shr PageShift)
|
||||
u = (u %% PageSize) /% MemAlign
|
||||
t.bits[u shr IntShift] = t.bits[u shr IntShift] or (1 shl (u and IntMask))
|
||||
|
||||
proc excl(s: var TCellSet, cell: PCell) =
|
||||
var u = cast[TAddress](cell)
|
||||
var t = CellSetGet(s, u shr PageShift)
|
||||
if t != nil:
|
||||
u = (u %% PageSize) /% MemAlign
|
||||
t.bits[u shr IntShift] = (t.bits[u shr IntShift] and
|
||||
not (1 shl (u and IntMask)))
|
||||
|
||||
proc containsOrIncl(s: var TCellSet, cell: PCell): bool =
|
||||
var u = cast[TAddress](cell)
|
||||
var t = CellSetGet(s, u shr PageShift)
|
||||
if t != nil:
|
||||
u = (u %% PageSize) /% MemAlign
|
||||
result = (t.bits[u shr IntShift] and (1 shl (u and IntMask))) != 0
|
||||
if not result:
|
||||
t.bits[u shr IntShift] = t.bits[u shr IntShift] or
|
||||
(1 shl (u and IntMask))
|
||||
else:
|
||||
Incl(s, cell)
|
||||
result = false
|
||||
|
||||
iterator elements(t: TCellSet): PCell {.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 shl IntShift +% j) *% MemAlign)
|
||||
inc(j)
|
||||
w = w shr 1
|
||||
inc(i)
|
||||
r = r.next
|
||||
|
||||
20
lib/system/cntbits.nim
Normal file
20
lib/system/cntbits.nim
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2006 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
|
||||
proc population16(a: int): int {.inline.} =
|
||||
var x = a
|
||||
x = ((x and 0xAAAA) shr 1) + (x and 0x5555)
|
||||
x = ((x and 0xCCCC) shr 2) + (x and 0x3333)
|
||||
x = ((x and 0xF0F0) shr 4) + (x and 0x0F0F)
|
||||
x = ((x and 0xFF00) shr 8) + (x and 0x00FF)
|
||||
return x
|
||||
|
||||
proc countBits(n: int32): int =
|
||||
result = population16(n and 0xffff'i32) + population16(n shr 16'i32)
|
||||
500
lib/system/debugger.nim
Normal file
500
lib/system/debugger.nim
Normal file
|
|
@ -0,0 +1,500 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2008 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
# This file implements the embedded debugger that can be linked
|
||||
# with the application. We should not use dynamic memory here as that
|
||||
# would interfere with the GC and trigger ON/OFF errors if the
|
||||
# user program corrupts memory. Unfortunately, for dispaying
|
||||
# variables we use the system.repr() proc which uses Nimrod
|
||||
# strings and thus allocates memory from the heap. Pity, but
|
||||
# I do not want to implement repr() twice. We also cannot deactivate
|
||||
# the GC here as that might run out of memory too quickly...
|
||||
|
||||
type
|
||||
TDbgState = enum
|
||||
dbOff, # debugger is turned off
|
||||
dbStepInto, # debugger is in tracing mode
|
||||
dbStepOver,
|
||||
dbSkipCurrent,
|
||||
dbQuiting, # debugger wants to quit
|
||||
dbBreakpoints # debugger is only interested in breakpoints
|
||||
|
||||
TDbgBreakpoint {.final.} = object
|
||||
low, high: int # range from low to high; if disabled
|
||||
# both low and high are set to their negative values
|
||||
# this makes the check faster and safes memory
|
||||
filename: string
|
||||
name: string # name of breakpoint
|
||||
|
||||
TVarSlot {.compilerproc, final.} = object # variable slots used for debugger:
|
||||
address: pointer
|
||||
typ: PNimType
|
||||
name: cstring # for globals this is "module.name"
|
||||
|
||||
PExtendedFrame = ptr TExtendedFrame
|
||||
TExtendedFrame {.final.} = object # If the debugger is enabled the compiler
|
||||
# provides an extended frame. Of course
|
||||
# only slots that are
|
||||
# needed are allocated and not 10_000,
|
||||
# except for the global data description.
|
||||
f: TFrame
|
||||
slots: array[0..10_000, TVarSlot]
|
||||
|
||||
var
|
||||
dbgInSignal: bool # wether the debugger is in the signal handler
|
||||
dbgIn: TFile # debugger input stream
|
||||
dbgUser: string = "s" # buffer for user input; first command is ``step_into``
|
||||
# needs to be global cause we store the last command
|
||||
# in it
|
||||
dbgState: TDbgState = dbStepInto # state of debugger
|
||||
dbgBP: array[0..127, TDbgBreakpoint] # breakpoints
|
||||
dbgBPlen: int = 0
|
||||
|
||||
dbgSkipToFrame: PFrame # frame to be skipped to
|
||||
|
||||
dbgGlobalData: TExtendedFrame # this reserves much space, but
|
||||
# for now it is the most practical way
|
||||
|
||||
maxDisplayRecDepth: int = 5 # do not display too much data!
|
||||
|
||||
proc findBreakpoint(name: string): int =
|
||||
# returns -1 if not found
|
||||
for i in countdown(dbgBPlen-1, 0):
|
||||
if name == dbgBP[i].name: return i
|
||||
return -1
|
||||
|
||||
proc ListBreakPoints() =
|
||||
write(stdout, "*** endb| Breakpoints:\n")
|
||||
for i in 0 .. dbgBPlen-1:
|
||||
write(stdout, dbgBP[i].name & ": " & $abs(dbgBP[i].low) & ".." &
|
||||
$abs(dbgBP[i].high) & dbgBP[i].filename)
|
||||
if dbgBP[i].low < 0:
|
||||
write(stdout, " [disabled]\n")
|
||||
else:
|
||||
write(stdout, "\n")
|
||||
write(stdout, "***\n")
|
||||
|
||||
proc openAppend(filename: string): TFile =
|
||||
if openFile(result, filename, fmAppend):
|
||||
write(result, "----------------------------------------\n")
|
||||
|
||||
proc dbgRepr(p: pointer, typ: PNimType): string =
|
||||
var
|
||||
cl: TReprClosure
|
||||
initReprClosure(cl)
|
||||
cl.recDepth = maxDisplayRecDepth
|
||||
# locks for the GC turned out to be a bad idea...
|
||||
# inc(recGcLock)
|
||||
result = ""
|
||||
reprAux(result, p, typ, cl)
|
||||
# dec(recGcLock)
|
||||
deinitReprClosure(cl)
|
||||
|
||||
proc writeVariable(stream: TFile, slot: TVarSlot) =
|
||||
write(stream, slot.name)
|
||||
write(stream, " = ")
|
||||
writeln(stream, dbgRepr(slot.address, slot.typ))
|
||||
|
||||
proc ListFrame(stream: TFile, f: PExtendedFrame) =
|
||||
write(stream, "*** endb| Frame (" & $f.f.len & " slots):\n")
|
||||
for i in 0 .. f.f.len-1:
|
||||
writeVariable(stream, f.slots[i])
|
||||
write(stream, "***\n")
|
||||
|
||||
proc ListVariables(stream: TFile, f: PExtendedFrame) =
|
||||
write(stream, "*** endb| Frame (" & $f.f.len & " slots):\n")
|
||||
for i in 0 .. f.f.len-1:
|
||||
writeln(stream, f.slots[i].name)
|
||||
write(stream, "***\n")
|
||||
|
||||
proc debugOut(msg: cstring) =
|
||||
# the *** *** markers are for easy recognition of debugger
|
||||
# output for external frontends.
|
||||
write(stdout, "*** endb| ")
|
||||
write(stdout, msg)
|
||||
write(stdout, "***\n")
|
||||
|
||||
proc dbgFatal(msg: cstring) =
|
||||
debugOut(msg)
|
||||
dbgAborting = True # the debugger wants to abort
|
||||
quit(1)
|
||||
|
||||
proc findVariable(frame: PExtendedFrame, varname: cstring): int =
|
||||
for i in 0 .. frame.f.len - 1:
|
||||
if c_strcmp(frame.slots[i].name, varname) == 0: return i
|
||||
return -1
|
||||
|
||||
proc dbgShowCurrentProc(dbgFramePointer: PFrame) =
|
||||
if dbgFramePointer != nil:
|
||||
write(stdout, "*** endb| now in proc: ")
|
||||
write(stdout, dbgFramePointer.procname)
|
||||
write(stdout, " ***\n")
|
||||
else:
|
||||
write(stdout, "*** endb| (procedure name not available) ***\n")
|
||||
|
||||
proc dbgShowExecutionPoint() =
|
||||
write(stdout, "*** endb| " & $framePtr.filename & "(" & $framePtr.line &
|
||||
") " & $framePtr.procname & " ***\n")
|
||||
|
||||
when defined(windows) or defined(dos) or defined(os2):
|
||||
{.define: FileSystemCaseInsensitive.}
|
||||
|
||||
proc fileMatches(c, bp: cstring): bool =
|
||||
# bp = breakpoint filename
|
||||
# c = current filename
|
||||
# we consider it a match if bp is a suffix of c
|
||||
# and the character for the suffix does not exist or
|
||||
# is one of: \ / :
|
||||
# depending on the OS case does not matter!
|
||||
var blen: int = c_strlen(bp)
|
||||
var clen: int = c_strlen(c)
|
||||
if blen > clen: return false
|
||||
# check for \ / :
|
||||
if clen-blen-1 >= 0 and c[clen-blen-1] notin {'\\', '/', ':'}:
|
||||
return false
|
||||
var i = 0
|
||||
while i < blen:
|
||||
var x, y: char
|
||||
x = bp[i]
|
||||
y = c[i+clen-blen]
|
||||
when defined(FileSystemCaseInsensitive):
|
||||
if x >= 'A' and x <= 'Z': x = chr(ord(x) - ord('A') + ord('a'))
|
||||
if y >= 'A' and y <= 'Z': y = chr(ord(y) - ord('A') + ord('a'))
|
||||
if x != y: return false
|
||||
inc(i)
|
||||
return true
|
||||
|
||||
proc dbgBreakpointReached(line: int): int =
|
||||
for i in 0..dbgBPlen-1:
|
||||
if line >= dbgBP[i].low and line <= dbgBP[i].high and
|
||||
fileMatches(framePtr.filename, dbgBP[i].filename): return i
|
||||
return -1
|
||||
|
||||
proc scanAndAppendWord(src: string, a: var string, start: int): int =
|
||||
result = start
|
||||
# skip whitespace:
|
||||
while src[result] in {'\t', ' '}: inc(result)
|
||||
while True:
|
||||
case src[result]
|
||||
of 'a'..'z', '0'..'9': add(a, src[result])
|
||||
of '_': nil # just skip it
|
||||
of 'A'..'Z': add(a, chr(ord(src[result]) - ord('A') + ord('a')))
|
||||
else: break
|
||||
inc(result)
|
||||
|
||||
proc scanWord(src: string, a: var string, start: int): int =
|
||||
a = ""
|
||||
result = scanAndAppendWord(src, a, start)
|
||||
|
||||
proc scanFilename(src: string, a: var string, start: int): int =
|
||||
result = start
|
||||
a = ""
|
||||
# skip whitespace:
|
||||
while src[result] in {'\t', ' '}: inc(result)
|
||||
while src[result] notin {'\t', ' ', '\0'}:
|
||||
add(a, src[result])
|
||||
inc(result)
|
||||
|
||||
proc scanNumber(src: string, a: var int, start: int): int =
|
||||
result = start
|
||||
a = 0
|
||||
while src[result] in {'\t', ' '}: inc(result)
|
||||
while true:
|
||||
case src[result]
|
||||
of '0'..'9': a = a * 10 + ord(src[result]) - ord('0')
|
||||
of '_': nil # skip underscores (nice for long line numbers)
|
||||
else: break
|
||||
inc(result)
|
||||
|
||||
proc dbgHelp() =
|
||||
debugOut("""
|
||||
list of commands (see the manual for further help):
|
||||
GENERAL
|
||||
h, help display this help message
|
||||
q, quit quit the debugger and the program
|
||||
<ENTER> repeat the previous debugger command
|
||||
EXECUTING
|
||||
s, step single step, stepping into routine calls
|
||||
n, next single step, without stepping into routine calls
|
||||
f, skipcurrent continue execution until the current routine finishes
|
||||
c, continue continue execution until the next breakpoint
|
||||
i, ignore continue execution, ignore all breakpoints
|
||||
BREAKPOINTS
|
||||
b, break <name> [fromline [toline]] [file]
|
||||
set a new breakpoint named 'name' for line and file
|
||||
if line or file are omitted the current one is used
|
||||
breakpoints display the entire breakpoint list
|
||||
disable <name> disable a breakpoint
|
||||
enable <name> enable a breakpoint
|
||||
DATA DISPLAY
|
||||
e, eval <expr> evaluate the expression <expr>
|
||||
o, out <file> <expr> evaluate <expr> and write it to <file>
|
||||
w, where display the current execution point
|
||||
stackframe [file] display current stack frame [and write it to file]
|
||||
u, up go up in the call stack
|
||||
d, down go down in the call stack
|
||||
bt, backtrace display the entire call stack
|
||||
l, locals display available local variables
|
||||
g, globals display available global variables
|
||||
maxdisplay <integer> set the display's recursion maximum
|
||||
""")
|
||||
|
||||
proc InvalidCommand() =
|
||||
debugOut("[Warning] invalid command ignored (type 'h' for help) ")
|
||||
|
||||
proc hasExt(s: string): bool =
|
||||
# returns true if s has a filename extension
|
||||
for i in countdown(len(s)-1, 0):
|
||||
if s[i] == '.': return true
|
||||
return false
|
||||
|
||||
proc setBreakPoint(s: string, start: int) =
|
||||
var dbgTemp: string
|
||||
var i = scanWord(s, dbgTemp, start)
|
||||
if i <= start:
|
||||
InvalidCommand()
|
||||
return
|
||||
if dbgBPlen >= high(dbgBP):
|
||||
debugOut("[Warning] no breakpoint could be set; out of breakpoint space ")
|
||||
return
|
||||
var x = dbgBPlen
|
||||
inc(dbgBPlen)
|
||||
dbgBP[x].name = dbgTemp
|
||||
i = scanNumber(s, dbgBP[x].low, i)
|
||||
if dbgBP[x].low == 0:
|
||||
# set to current line:
|
||||
dbgBP[x].low = framePtr.line
|
||||
i = scanNumber(s, dbgBP[x].high, i)
|
||||
if dbgBP[x].high == 0: # set to low:
|
||||
dbgBP[x].high = dbgBP[x].low
|
||||
i = scanFilename(s, dbgTemp, i)
|
||||
if not (dbgTemp.len == 0):
|
||||
if not hasExt(dbgTemp): add(dbgTemp, ".nim")
|
||||
dbgBP[x].filename = dbgTemp
|
||||
else: # use current filename
|
||||
dbgBP[x].filename = $framePtr.filename
|
||||
# skip whitespace:
|
||||
while s[i] in {' ', '\t'}: inc(i)
|
||||
if s[i] != '\0':
|
||||
dec(dbgBPLen) # remove buggy breakpoint
|
||||
InvalidCommand()
|
||||
|
||||
proc BreakpointSetEnabled(s: string, start, enabled: int) =
|
||||
var dbgTemp: string
|
||||
var i = scanWord(s, dbgTemp, start)
|
||||
if i <= start:
|
||||
InvalidCommand()
|
||||
return
|
||||
var x = findBreakpoint(dbgTemp)
|
||||
if x < 0: debugOut("[Warning] breakpoint does not exist ")
|
||||
elif enabled * dbgBP[x].low < 0: # signs are different?
|
||||
dbgBP[x].low = -dbgBP[x].low
|
||||
dbgBP[x].high = -dbgBP[x].high
|
||||
|
||||
proc dbgEvaluate(stream: TFile, s: string, start: int,
|
||||
currFrame: PExtendedFrame) =
|
||||
var dbgTemp: string
|
||||
var i = scanWord(s, dbgTemp, start)
|
||||
while s[i] in {' ', '\t'}: inc(i)
|
||||
var f = currFrame
|
||||
if s[i] == '.':
|
||||
inc(i) # skip '.'
|
||||
add(dbgTemp, '.')
|
||||
i = scanAndAppendWord(s, dbgTemp, i)
|
||||
# search for global var:
|
||||
f = addr(dbgGlobalData)
|
||||
if s[i] != '\0':
|
||||
debugOut("[Warning] could not parse expr ")
|
||||
return
|
||||
var j = findVariable(f, dbgTemp)
|
||||
if j < 0:
|
||||
debugOut("[Warning] could not find variable ")
|
||||
return
|
||||
writeVariable(stream, f.slots[j])
|
||||
|
||||
proc dbgOut(s: string, start: int, currFrame: PExtendedFrame) =
|
||||
var dbgTemp: string
|
||||
var i = scanFilename(s, dbgTemp, start)
|
||||
if dbgTemp.len == 0:
|
||||
InvalidCommand()
|
||||
return
|
||||
var stream = openAppend(dbgTemp)
|
||||
if stream == nil:
|
||||
debugOut("[Warning] could not open or create file ")
|
||||
return
|
||||
dbgEvaluate(stream, s, i, currFrame)
|
||||
closeFile(stream)
|
||||
|
||||
proc dbgStackFrame(s: string, start: int, currFrame: PExtendedFrame) =
|
||||
var dbgTemp: string
|
||||
var i = scanFilename(s, dbgTemp, start)
|
||||
if dbgTemp.len == 0:
|
||||
# just write it to stdout:
|
||||
ListFrame(stdout, currFrame)
|
||||
else:
|
||||
var stream = openAppend(dbgTemp)
|
||||
if stream == nil:
|
||||
debugOut("[Warning] could not open or create file ")
|
||||
return
|
||||
ListFrame(stream, currFrame)
|
||||
closeFile(stream)
|
||||
|
||||
proc CommandPrompt() =
|
||||
# if we return from this routine, user code executes again
|
||||
var
|
||||
again = True
|
||||
dbgFramePtr = framePtr # for going down and up the stack
|
||||
dbgDown = 0 # how often we did go down
|
||||
|
||||
while again:
|
||||
write(stdout, "*** endb| >>")
|
||||
var tmp = readLine(stdin)
|
||||
if tmp.len > 0: dbgUser = tmp
|
||||
# now look what we have to do:
|
||||
var dbgTemp: string
|
||||
var i = scanWord(dbgUser, dbgTemp, 0)
|
||||
case dbgTemp
|
||||
of "": InvalidCommand()
|
||||
of "s", "step":
|
||||
dbgState = dbStepInto
|
||||
again = false
|
||||
of "n", "next":
|
||||
dbgState = dbStepOver
|
||||
dbgSkipToFrame = framePtr
|
||||
again = false
|
||||
of "f", "skipcurrent":
|
||||
dbgState = dbSkipCurrent
|
||||
dbgSkipToFrame = framePtr.prev
|
||||
again = false
|
||||
of "c", "continue":
|
||||
dbgState = dbBreakpoints
|
||||
again = false
|
||||
of "i", "ignore":
|
||||
dbgState = dbOff
|
||||
again = false
|
||||
of "h", "help":
|
||||
dbgHelp()
|
||||
of "q", "quit":
|
||||
dbgState = dbQuiting
|
||||
dbgAborting = True
|
||||
again = false
|
||||
quit(1) # BUGFIX: quit with error code > 0
|
||||
of "e", "eval":
|
||||
dbgEvaluate(stdout, dbgUser, i, cast[PExtendedFrame](dbgFramePtr))
|
||||
of "o", "out":
|
||||
dbgOut(dbgUser, i, cast[PExtendedFrame](dbgFramePtr))
|
||||
of "stackframe":
|
||||
dbgStackFrame(dbgUser, i, cast[PExtendedFrame](dbgFramePtr))
|
||||
of "w", "where":
|
||||
dbgShowExecutionPoint()
|
||||
of "l", "locals":
|
||||
ListVariables(stdout, cast[PExtendedFrame](dbgFramePtr))
|
||||
of "g", "globals":
|
||||
ListVariables(stdout, addr(dbgGlobalData))
|
||||
of "u", "up":
|
||||
if dbgDown <= 0:
|
||||
debugOut("[Warning] cannot go up any further ")
|
||||
else:
|
||||
dbgFramePtr = framePtr
|
||||
for j in 0 .. dbgDown-2: # BUGFIX
|
||||
dbgFramePtr = dbgFramePtr.prev
|
||||
dec(dbgDown)
|
||||
dbgShowCurrentProc(dbgFramePtr)
|
||||
of "d", "down":
|
||||
if dbgFramePtr != nil:
|
||||
inc(dbgDown)
|
||||
dbgFramePtr = dbgFramePtr.prev
|
||||
dbgShowCurrentProc(dbgFramePtr)
|
||||
else:
|
||||
debugOut("[Warning] cannot go down any further ")
|
||||
of "bt", "backtrace":
|
||||
WriteStackTrace()
|
||||
of "b", "break":
|
||||
setBreakPoint(dbgUser, i)
|
||||
of "breakpoints":
|
||||
ListBreakPoints()
|
||||
of "disable":
|
||||
BreakpointSetEnabled(dbgUser, i, -1)
|
||||
of "enable":
|
||||
BreakpointSetEnabled(dbgUser, i, +1)
|
||||
of "maxdisplay":
|
||||
var parsed: int
|
||||
i = scanNumber(dbgUser, parsed, i)
|
||||
if dbgUser[i-1] in {'0'..'9'}:
|
||||
if parsed == 0: maxDisplayRecDepth = -1
|
||||
else: maxDisplayRecDepth = parsed
|
||||
else:
|
||||
InvalidCommand()
|
||||
else:
|
||||
InvalidCommand()
|
||||
|
||||
proc endbStep() =
|
||||
# we get into here if an unhandled exception has been raised
|
||||
# XXX: do not allow the user to run the program any further?
|
||||
# XXX: BUG: the frame is lost here!
|
||||
dbgShowExecutionPoint()
|
||||
CommandPrompt()
|
||||
|
||||
proc checkForBreakpoint() =
|
||||
var i = dbgBreakpointReached(framePtr.line)
|
||||
if i >= 0:
|
||||
write(stdout, "*** endb| reached ")
|
||||
write(stdout, dbgBP[i].name)
|
||||
write(stdout, " in ")
|
||||
write(stdout, framePtr.filename)
|
||||
write(stdout, "(")
|
||||
write(stdout, framePtr.line)
|
||||
write(stdout, ") ")
|
||||
write(stdout, framePtr.procname)
|
||||
write(stdout, " ***\n")
|
||||
CommandPrompt()
|
||||
|
||||
# interface to the user program:
|
||||
|
||||
proc dbgRegisterBreakpoint(line: int,
|
||||
filename, name: cstring) {.compilerproc.} =
|
||||
var x = dbgBPlen
|
||||
inc(dbgBPlen)
|
||||
dbgBP[x].name = $name
|
||||
dbgBP[x].filename = $filename
|
||||
dbgBP[x].low = line
|
||||
dbgBP[x].high = line
|
||||
|
||||
proc dbgRegisterGlobal(name: cstring, address: pointer,
|
||||
typ: PNimType) {.compilerproc.} =
|
||||
var i = dbgGlobalData.f.len
|
||||
if i >= high(dbgGlobalData.slots):
|
||||
debugOut("[Warning] cannot register global ")
|
||||
return
|
||||
dbgGlobalData.slots[i].name = name
|
||||
dbgGlobalData.slots[i].typ = typ
|
||||
dbgGlobalData.slots[i].address = address
|
||||
inc(dbgGlobalData.f.len)
|
||||
|
||||
proc endb(line: int) {.compilerproc.} =
|
||||
# This proc is called before every Nimrod code line!
|
||||
# Thus, it must have as few parameters as possible to keep the
|
||||
# code size small!
|
||||
# Check if we are at an enabled breakpoint or "in the mood"
|
||||
framePtr.line = line # this is done here for smaller code size!
|
||||
if dbgLineHook != nil: dbgLineHook()
|
||||
case dbgState
|
||||
of dbStepInto:
|
||||
# we really want the command prompt here:
|
||||
dbgShowExecutionPoint()
|
||||
CommandPrompt()
|
||||
of dbSkipCurrent, dbStepOver: # skip current routine
|
||||
if framePtr == dbgSkipToFrame:
|
||||
dbgShowExecutionPoint()
|
||||
CommandPrompt()
|
||||
else: # breakpoints are wanted though (I guess)
|
||||
checkForBreakpoint()
|
||||
of dbBreakpoints: # debugger is only interested in breakpoints
|
||||
checkForBreakpoint()
|
||||
else: nil
|
||||
124
lib/system/dyncalls.nim
Normal file
124
lib/system/dyncalls.nim
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2009 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
# This file implements the ability to call native procs from libraries.
|
||||
# It is not possible to do this in a platform independant way, unfortunately.
|
||||
# However, the interface has been designed to take platform differences into
|
||||
# account and been ported to all major platforms.
|
||||
|
||||
type
|
||||
TLibHandle = pointer # private type
|
||||
TProcAddr = pointer # libary loading and loading of procs:
|
||||
|
||||
const
|
||||
NilLibHandle: TLibHandle = nil
|
||||
|
||||
proc nimLoadLibrary(path: string): TLibHandle {.compilerproc.}
|
||||
proc nimUnloadLibrary(lib: TLibHandle) {.compilerproc.}
|
||||
proc nimGetProcAddr(lib: TLibHandle, name: cstring): TProcAddr {.compilerproc.}
|
||||
|
||||
proc nimLoadLibraryError(path: string) {.compilerproc, noinline.} =
|
||||
raise newException(EInvalidLibrary, "could not load: " & path)
|
||||
|
||||
# this code was inspired from Lua's source code:
|
||||
# Lua - An Extensible Extension Language
|
||||
# Tecgraf: Computer Graphics Technology Group, PUC-Rio, Brazil
|
||||
# http://www.lua.org
|
||||
# mailto:info@lua.org
|
||||
|
||||
when defined(posix):
|
||||
#
|
||||
# =========================================================================
|
||||
# This is an implementation based on the dlfcn interface.
|
||||
# The dlfcn interface is available in Linux, SunOS, Solaris, IRIX, FreeBSD,
|
||||
# NetBSD, AIX 4.2, HPUX 11, and probably most other Unix flavors, at least
|
||||
# as an emulation layer on top of native functions.
|
||||
# =========================================================================
|
||||
#
|
||||
|
||||
# c stuff:
|
||||
var
|
||||
RTLD_NOW {.importc: "RTLD_NOW", header: "<dlfcn.h>".}: int
|
||||
|
||||
proc dlclose(lib: TLibHandle) {.importc, header: "<dlfcn.h>".}
|
||||
proc dlopen(path: CString, mode: int): TLibHandle {.
|
||||
importc, header: "<dlfcn.h>".}
|
||||
proc dlsym(lib: TLibHandle, name: cstring): TProcAddr {.
|
||||
importc, header: "<dlfcn.h>".}
|
||||
|
||||
proc nimUnloadLibrary(lib: TLibHandle) =
|
||||
dlclose(lib)
|
||||
|
||||
proc nimLoadLibrary(path: string): TLibHandle =
|
||||
result = dlopen(path, RTLD_NOW)
|
||||
|
||||
proc nimGetProcAddr(lib: TLibHandle, name: cstring): TProcAddr =
|
||||
result = dlsym(lib, name)
|
||||
|
||||
elif defined(windows) or defined(dos):
|
||||
#
|
||||
# =======================================================================
|
||||
# Native Windows Implementation
|
||||
# =======================================================================
|
||||
#
|
||||
type
|
||||
THINSTANCE {.importc: "HINSTANCE".} = pointer
|
||||
|
||||
proc FreeLibrary(lib: THINSTANCE) {.importc, header: "<windows.h>", stdcall.}
|
||||
proc winLoadLibrary(path: cstring): THINSTANCE {.
|
||||
importc: "LoadLibraryA", header: "<windows.h>", stdcall.}
|
||||
proc GetProcAddress(lib: THINSTANCE, name: cstring): TProcAddr {.
|
||||
importc: "GetProcAddress", header: "<windows.h>", stdcall.}
|
||||
|
||||
proc nimUnloadLibrary(lib: TLibHandle) =
|
||||
FreeLibrary(cast[THINSTANCE](lib))
|
||||
|
||||
proc nimLoadLibrary(path: string): TLibHandle =
|
||||
result = cast[TLibHandle](winLoadLibrary(path))
|
||||
|
||||
proc nimGetProcAddr(lib: TLibHandle, name: cstring): TProcAddr =
|
||||
result = GetProcAddress(cast[THINSTANCE](lib), name)
|
||||
|
||||
elif defined(mac):
|
||||
#
|
||||
# =======================================================================
|
||||
# Native Mac OS X / Darwin Implementation
|
||||
# =======================================================================
|
||||
#
|
||||
{.error: "no implementation for dyncalls yet".}
|
||||
|
||||
proc nimUnloadLibrary(lib: TLibHandle) =
|
||||
NSUnLinkModule(NSModule(lib), NSUNLINKMODULE_OPTION_RESET_LAZY_REFERENCES)
|
||||
|
||||
var
|
||||
dyld_present {.importc: "_dyld_present", header: "<dyld.h>".}: int
|
||||
|
||||
proc nimLoadLibrary(path: string): TLibHandle =
|
||||
var
|
||||
img: NSObjectFileImage
|
||||
ret: NSObjectFileImageReturnCode
|
||||
modul: NSModule
|
||||
# this would be a rare case, but prevents crashing if it happens
|
||||
result = nil
|
||||
if dyld_present != 0:
|
||||
ret = NSCreateObjectFileImageFromFile(path, addr(img))
|
||||
if ret == NSObjectFileImageSuccess:
|
||||
modul = NSLinkModule(img, path, NSLINKMODULE_OPTION_PRIVATE or
|
||||
NSLINKMODULE_OPTION_RETURN_ON_ERROR)
|
||||
NSDestroyObjectFileImage(img)
|
||||
result = TLibHandle(modul)
|
||||
|
||||
proc nimGetProcAddr(lib: TLibHandle, cname: string): TProcAddr =
|
||||
var
|
||||
nss: NSSymbol
|
||||
nss = NSLookupSymbolInModule(NSModule(lib), name)
|
||||
result = TProcAddr(NSAddressOfSymbol(nss))
|
||||
|
||||
else:
|
||||
{.error: "no implementation for dyncalls".}
|
||||
531
lib/system/ecmasys.nim
Normal file
531
lib/system/ecmasys.nim
Normal file
|
|
@ -0,0 +1,531 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2008 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
## Stubs for the GC interface:
|
||||
|
||||
proc GC_disable() = nil
|
||||
proc GC_enable() = nil
|
||||
proc GC_fullCollect() = nil
|
||||
proc GC_setStrategy(strategy: TGC_Strategy) = nil
|
||||
proc GC_enableMarkAndSweep() = nil
|
||||
proc GC_disableMarkAndSweep() = nil
|
||||
proc GC_getStatistics(): string = return ""
|
||||
|
||||
proc getOccupiedMem(): int = return -1
|
||||
proc getFreeMem(): int = return -1
|
||||
proc getTotalMem(): int = return -1
|
||||
|
||||
proc alert(s: cstring) {.importc, nodecl.}
|
||||
|
||||
type
|
||||
PSafePoint = ptr TSafePoint
|
||||
TSafePoint {.compilerproc, final.} = object
|
||||
prev: PSafePoint # points to next safe point
|
||||
exc: ref E_Base
|
||||
|
||||
PCallFrame = ptr TCallFrame
|
||||
TCallFrame {.importc, nodecl, final.} = object
|
||||
prev: PCallFrame
|
||||
procname: CString
|
||||
line: int # current line number
|
||||
filename: CString
|
||||
|
||||
var
|
||||
framePtr {.importc, nodecl, volatile.}: PCallFrame
|
||||
excHandler {.importc, nodecl, volatile.}: PSafePoint = nil
|
||||
# list of exception handlers
|
||||
# a global variable for the root of all try blocks
|
||||
|
||||
{.push stacktrace: off.}
|
||||
proc nimBoolToStr(x: bool): string {.compilerproc.} =
|
||||
if x: result = "true"
|
||||
else: result = "false"
|
||||
|
||||
proc nimCharToStr(x: char): string {.compilerproc.} =
|
||||
result = newString(1)
|
||||
result[0] = x
|
||||
|
||||
proc getCurrentExceptionMsg(): string =
|
||||
if excHandler != nil: return $excHandler.exc.msg
|
||||
return ""
|
||||
|
||||
proc auxWriteStackTrace(f: PCallFrame): string =
|
||||
type
|
||||
TTempFrame = tuple[procname: CString, line: int]
|
||||
var
|
||||
it = f
|
||||
i = 0
|
||||
total = 0
|
||||
tempFrames: array [0..63, TTempFrame]
|
||||
while it != nil and i <= high(tempFrames):
|
||||
tempFrames[i].procname = it.procname
|
||||
tempFrames[i].line = it.line
|
||||
inc(i)
|
||||
inc(total)
|
||||
it = it.prev
|
||||
while it != nil:
|
||||
inc(total)
|
||||
it = it.prev
|
||||
result = ""
|
||||
# if the buffer overflowed print '...':
|
||||
if total != i:
|
||||
add(result, "(")
|
||||
add(result, $(total-i))
|
||||
add(result, " calls omitted) ...\n")
|
||||
for j in countdown(i-1, 0):
|
||||
add(result, tempFrames[j].procname)
|
||||
if tempFrames[j].line > 0:
|
||||
add(result, ", line: ")
|
||||
add(result, $tempFrames[j].line)
|
||||
add(result, "\n")
|
||||
|
||||
proc rawWriteStackTrace(): string =
|
||||
if framePtr == nil:
|
||||
result = "No stack traceback available\n"
|
||||
else:
|
||||
result = "Traceback (most recent call last)\n"& auxWriteStackTrace(framePtr)
|
||||
framePtr = nil
|
||||
|
||||
proc raiseException(e: ref E_Base, ename: cstring) {.compilerproc, pure.} =
|
||||
e.name = ename
|
||||
if excHandler != nil:
|
||||
excHandler.exc = e
|
||||
else:
|
||||
var buf = rawWriteStackTrace()
|
||||
if e.msg != nil and e.msg[0] != '\0':
|
||||
add(buf, "Error: unhandled exception: ")
|
||||
add(buf, e.msg)
|
||||
else:
|
||||
add(buf, "Error: unhandled exception")
|
||||
add(buf, " [")
|
||||
add(buf, ename)
|
||||
add(buf, "]\n")
|
||||
alert(buf)
|
||||
asm """throw `e`;"""
|
||||
|
||||
proc reraiseException() =
|
||||
if excHandler == nil:
|
||||
raise newException(ENoExceptionToReraise, "no exception to reraise")
|
||||
else:
|
||||
asm """throw excHandler.exc;"""
|
||||
|
||||
proc raiseOverflow {.exportc: "raiseOverflow", noreturn.} =
|
||||
raise newException(EOverflow, "over- or underflow")
|
||||
|
||||
proc raiseDivByZero {.exportc: "raiseDivByZero", noreturn.} =
|
||||
raise newException(EDivByZero, "divison by zero")
|
||||
|
||||
proc raiseRangeError() {.compilerproc, noreturn.} =
|
||||
raise newException(EOutOfRange, "value out of range")
|
||||
|
||||
proc raiseIndexError() {.compilerproc, noreturn.} =
|
||||
raise newException(EInvalidIndex, "index out of bounds")
|
||||
|
||||
proc raiseFieldError(f: string) {.compilerproc, noreturn.} =
|
||||
raise newException(EInvalidField, f & " is not accessible")
|
||||
|
||||
|
||||
|
||||
proc SetConstr() {.varargs, pure, compilerproc.} =
|
||||
asm """
|
||||
var result = {};
|
||||
for (var i = 0; i < arguments.length; ++i) {
|
||||
var x = arguments[i];
|
||||
if (typeof(x) == "object") {
|
||||
for (var j = x[0]; j <= x[1]; ++j) {
|
||||
result[j] = true;
|
||||
}
|
||||
} else {
|
||||
result[x] = true;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
"""
|
||||
|
||||
proc cstrToNimstr(c: cstring): string {.pure, compilerproc.} =
|
||||
asm """
|
||||
var result = [];
|
||||
for (var i = 0; i < `c`.length; ++i) {
|
||||
result[i] = `c`.charCodeAt(i);
|
||||
}
|
||||
result[result.length] = 0; // terminating zero
|
||||
return result;
|
||||
"""
|
||||
|
||||
proc toEcmaStr(s: string): cstring {.pure, compilerproc.} =
|
||||
asm """
|
||||
var len = `s`.length-1;
|
||||
var result = new Array(len);
|
||||
var fcc = String.fromCharCode;
|
||||
for (var i = 0; i < len; ++i) {
|
||||
result[i] = fcc(`s`[i]);
|
||||
}
|
||||
return result.join("");
|
||||
"""
|
||||
|
||||
proc mnewString(len: int): string {.pure, compilerproc.} =
|
||||
asm """
|
||||
var result = new Array(`len`+1);
|
||||
result[0] = 0;
|
||||
result[`len`] = 0;
|
||||
return result;
|
||||
"""
|
||||
|
||||
proc SetCard(a: int): int {.compilerproc, pure.} =
|
||||
# argument type is a fake
|
||||
asm """
|
||||
var result = 0;
|
||||
for (var elem in `a`) { ++result; }
|
||||
return result;
|
||||
"""
|
||||
|
||||
proc SetEq(a, b: int): bool {.compilerproc, pure.} =
|
||||
asm """
|
||||
for (var elem in `a`) { if (!`b`[elem]) return false; }
|
||||
for (var elem in `b`) { if (!`a`[elem]) return false; }
|
||||
return true;
|
||||
"""
|
||||
|
||||
proc SetLe(a, b: int): bool {.compilerproc, pure.} =
|
||||
asm """
|
||||
for (var elem in `a`) { if (!`b`[elem]) return false; }
|
||||
return true;
|
||||
"""
|
||||
|
||||
proc SetLt(a, b: int): bool {.compilerproc.} =
|
||||
result = SetLe(a, b) and not SetEq(a, b)
|
||||
|
||||
proc SetMul(a, b: int): int {.compilerproc, pure.} =
|
||||
asm """
|
||||
var result = {};
|
||||
for (var elem in `a`) {
|
||||
if (`b`[elem]) { result[elem] = true; }
|
||||
}
|
||||
return result;
|
||||
"""
|
||||
|
||||
proc SetPlus(a, b: int): int {.compilerproc, pure.} =
|
||||
asm """
|
||||
var result = {};
|
||||
for (var elem in `a`) { result[elem] = true; }
|
||||
for (var elem in `b`) { result[elem] = true; }
|
||||
return result;
|
||||
"""
|
||||
|
||||
proc SetMinus(a, b: int): int {.compilerproc, pure.} =
|
||||
asm """
|
||||
var result = {};
|
||||
for (var elem in `a`) {
|
||||
if (!`b`[elem]) { result[elem] = true; }
|
||||
}
|
||||
return result;
|
||||
"""
|
||||
|
||||
proc cmpStrings(a, b: string): int {.pure, compilerProc.} =
|
||||
asm """
|
||||
if (`a` == `b`) return 0;
|
||||
if (!`a`) return -1;
|
||||
if (!`b`) return 1;
|
||||
for (var i = 0; i < `a`.length-1; ++i) {
|
||||
var result = `a`[i] - `b`[i];
|
||||
if (result != 0) return result;
|
||||
}
|
||||
return 0;
|
||||
"""
|
||||
|
||||
proc cmp(x, y: string): int = return cmpStrings(x, y)
|
||||
|
||||
proc eqStrings(a, b: string): bool {.pure, compilerProc.} =
|
||||
asm """
|
||||
if (`a == `b`) return true;
|
||||
if ((!`a`) || (!`b`)) return false;
|
||||
var alen = `a`.length;
|
||||
if (alen != `b`.length) return false;
|
||||
for (var i = 0; i < alen; ++i)
|
||||
if (`a`[i] != `b`[i]) return false;
|
||||
return true;
|
||||
"""
|
||||
|
||||
type
|
||||
TDocument {.importc.} = object of TObject
|
||||
write: proc (text: cstring)
|
||||
writeln: proc (text: cstring)
|
||||
createAttribute: proc (identifier: cstring): ref TNode
|
||||
createElement: proc (identifier: cstring): ref TNode
|
||||
createTextNode: proc (identifier: cstring): ref TNode
|
||||
getElementById: proc (id: cstring): ref TNode
|
||||
getElementsByName: proc (name: cstring): seq[ref TNode]
|
||||
getElementsByTagName: proc (name: cstring): seq[ref TNode]
|
||||
|
||||
TNodeType* = enum
|
||||
ElementNode = 1,
|
||||
AttributeNode,
|
||||
TextNode,
|
||||
CDATANode,
|
||||
EntityRefNode,
|
||||
EntityNode,
|
||||
ProcessingInstructionNode,
|
||||
CommentNode,
|
||||
DocumentNode,
|
||||
DocumentTypeNode,
|
||||
DocumentFragmentNode,
|
||||
NotationNode
|
||||
TNode* {.importc.} = object of TObject
|
||||
attributes*: seq[ref TNode]
|
||||
childNodes*: seq[ref TNode]
|
||||
data*: cstring
|
||||
firstChild*: ref TNode
|
||||
lastChild*: ref TNode
|
||||
nextSibling*: ref TNode
|
||||
nodeName*: cstring
|
||||
nodeType*: TNodeType
|
||||
nodeValue*: cstring
|
||||
parentNode*: ref TNode
|
||||
previousSibling*: ref TNode
|
||||
appendChild*: proc (child: ref TNode)
|
||||
appendData*: proc (data: cstring)
|
||||
cloneNode*: proc (copyContent: bool)
|
||||
deleteData*: proc (start, len: int)
|
||||
getAttribute*: proc (attr: cstring): cstring
|
||||
getAttributeNode*: proc (attr: cstring): ref TNode
|
||||
getElementsByTagName*: proc (): seq[ref TNode]
|
||||
hasChildNodes*: proc (): bool
|
||||
insertBefore*: proc (newNode, before: ref TNode)
|
||||
insertData*: proc (position: int, data: cstring)
|
||||
removeAttribute*: proc (attr: cstring)
|
||||
removeAttributeNode*: proc (attr: ref TNode)
|
||||
removeChild*: proc (child: ref TNode)
|
||||
replaceChild*: proc (newNode, oldNode: ref TNode)
|
||||
replaceData*: proc (start, len: int, text: cstring)
|
||||
setAttribute*: proc (name, value: cstring)
|
||||
setAttributeNode*: proc (attr: ref TNode)
|
||||
|
||||
var
|
||||
document {.importc, nodecl.}: ref TDocument
|
||||
|
||||
proc ewriteln(x: cstring) =
|
||||
var node = document.getElementsByTagName("body")[0]
|
||||
if node != nil:
|
||||
node.appendChild(document.createTextNode(x))
|
||||
node.appendChild(document.createElement("br"))
|
||||
else:
|
||||
raise newException(EInvalidValue, "<body> element does not exist yet!")
|
||||
|
||||
proc echo*(x: int) = ewriteln($x)
|
||||
proc echo*(x: float) = ewriteln($x)
|
||||
proc echo*(x: bool) = ewriteln(if x: cstring("true") else: cstring("false"))
|
||||
proc echo*(x: string) = ewriteln(x)
|
||||
proc echo*(x: cstring) = ewriteln(x)
|
||||
|
||||
proc echo[Ty](x: Ty) =
|
||||
echo(x)
|
||||
|
||||
proc echo[Ty](x: openArray[Ty]) =
|
||||
for a in items(x): echo(a)
|
||||
|
||||
# Arithmetic:
|
||||
proc addInt(a, b: int): int {.pure, compilerproc.} =
|
||||
asm """
|
||||
var result = `a` + `b`;
|
||||
if (result > 2147483647 || result < -2147483648) raiseOverflow();
|
||||
return result;
|
||||
"""
|
||||
|
||||
proc subInt(a, b: int): int {.pure, compilerproc.} =
|
||||
asm """
|
||||
var result = `a` - `b`;
|
||||
if (result > 2147483647 || result < -2147483648) raiseOverflow();
|
||||
return result;
|
||||
"""
|
||||
|
||||
proc mulInt(a, b: int): int {.pure, compilerproc.} =
|
||||
asm """
|
||||
var result = `a` * `b`;
|
||||
if (result > 2147483647 || result < -2147483648) raiseOverflow();
|
||||
return result;
|
||||
"""
|
||||
|
||||
proc divInt(a, b: int): int {.pure, compilerproc.} =
|
||||
asm """
|
||||
if (`b` == 0) raiseDivByZero();
|
||||
if (`b` == -1 && `a` == 2147483647) raiseOverflow();
|
||||
return Math.floor(`a` / `b`);
|
||||
"""
|
||||
|
||||
proc modInt(a, b: int): int {.pure, compilerproc.} =
|
||||
asm """
|
||||
if (`b` == 0) raiseDivByZero();
|
||||
if (`b` == -1 && `a` == 2147483647) raiseOverflow();
|
||||
return Math.floor(`a` % `b`);
|
||||
"""
|
||||
|
||||
|
||||
|
||||
proc addInt64(a, b: int): int {.pure, compilerproc.} =
|
||||
asm """
|
||||
var result = `a` + `b`;
|
||||
if (result > 9223372036854775807
|
||||
|| result < -9223372036854775808) raiseOverflow();
|
||||
return result;
|
||||
"""
|
||||
|
||||
proc subInt64(a, b: int): int {.pure, compilerproc.} =
|
||||
asm """
|
||||
var result = `a` - `b`;
|
||||
if (result > 9223372036854775807
|
||||
|| result < -9223372036854775808) raiseOverflow();
|
||||
return result;
|
||||
"""
|
||||
|
||||
proc mulInt64(a, b: int): int {.pure, compilerproc.} =
|
||||
asm """
|
||||
var result = `a` * `b`;
|
||||
if (result > 9223372036854775807
|
||||
|| result < -9223372036854775808) raiseOverflow();
|
||||
return result;
|
||||
"""
|
||||
|
||||
proc divInt64(a, b: int): int {.pure, compilerproc.} =
|
||||
asm """
|
||||
if (`b` == 0) raiseDivByZero();
|
||||
if (`b` == -1 && `a` == 9223372036854775807) raiseOverflow();
|
||||
return Math.floor(`a` / `b`);
|
||||
"""
|
||||
|
||||
proc modInt64(a, b: int): int {.pure, compilerproc.} =
|
||||
asm """
|
||||
if (`b` == 0) raiseDivByZero();
|
||||
if (`b` == -1 && `a` == 9223372036854775807) raiseOverflow();
|
||||
return Math.floor(`a` % `b`);
|
||||
"""
|
||||
|
||||
proc nimMin(a, b: int): int {.compilerproc.} = return if a <= b: a else: b
|
||||
proc nimMax(a, b: int): int {.compilerproc.} = return if a >= b: a else: b
|
||||
|
||||
proc internalAssert(file: cstring, line: int) {.pure, compilerproc.} =
|
||||
var
|
||||
e: ref EAssertionFailed
|
||||
new(e)
|
||||
asm """`e`.message = "[Assertion failure] file: "+`file`+", line: "+`line`"""
|
||||
raise e
|
||||
|
||||
include hti
|
||||
|
||||
proc isFatPointer(ti: PNimType): bool =
|
||||
# This has to be consistent with the code generator!
|
||||
return ti.base.kind notin {tyRecord, tyRecordConstr, tyObject,
|
||||
tyArray, tyArrayConstr, tyPureObject, tyTuple,
|
||||
tyEmptySet, tyOpenArray, tySet, tyVar, tyRef, tyPtr}
|
||||
|
||||
proc NimCopy(x: pointer, ti: PNimType): pointer {.compilerproc.}
|
||||
|
||||
proc NimCopyAux(dest, src: Pointer, n: ptr TNimNode) {.exportc.} =
|
||||
case n.kind
|
||||
of nkNone: assert(false)
|
||||
of nkSlot:
|
||||
asm "`dest`[`n`.offset] = NimCopy(`src`[`n`.offset], `n`.typ);"
|
||||
of nkList:
|
||||
for i in 0..n.len-1:
|
||||
NimCopyAux(dest, src, n.sons[i])
|
||||
of nkCase:
|
||||
asm """
|
||||
`dest`[`n`.offset] = NimCopy(`src`[`n`.offset], `n`.typ);
|
||||
for (var i = 0; i < `n`.sons.length; ++i) {
|
||||
NimCopyAux(`dest`, `src`, `n`.sons[i][1]);
|
||||
}
|
||||
"""
|
||||
|
||||
proc NimCopy(x: pointer, ti: PNimType): pointer =
|
||||
case ti.kind
|
||||
of tyPtr, tyRef, tyVar, tyNil:
|
||||
if not isFatPointer(ti):
|
||||
result = x
|
||||
else:
|
||||
asm """
|
||||
`result` = [null, 0];
|
||||
`result`[0] = `x`[0];
|
||||
`result`[1] = `x`[1];
|
||||
"""
|
||||
of tyEmptySet, tySet:
|
||||
asm """
|
||||
`result` = {};
|
||||
for (var key in `x`) { `result`[key] = `x`[key]; }
|
||||
"""
|
||||
of tyPureObject, tyTuple, tyObject:
|
||||
if ti.base != nil: result = NimCopy(x, ti.base)
|
||||
elif ti.kind == tyObject:
|
||||
asm "`result` = {m_type: `ti`};"
|
||||
else:
|
||||
asm "`result` = {};"
|
||||
NimCopyAux(result, x, ti.node)
|
||||
of tySequence, tyArrayConstr, tyOpenArray, tyArray:
|
||||
asm """
|
||||
`result` = new Array(`x`.length);
|
||||
for (var i = 0; i < `x`.length; ++i) {
|
||||
`result`[i] = NimCopy(`x`[i], `ti`.base);
|
||||
}
|
||||
"""
|
||||
of tyString:
|
||||
asm "`result` = `x`.slice(0);"
|
||||
else:
|
||||
result = x
|
||||
|
||||
|
||||
proc ArrayConstr(len: int, value: pointer, typ: PNimType): pointer {.
|
||||
pure, compilerproc.} =
|
||||
# types are fake
|
||||
asm """
|
||||
var result = new Array(`len`);
|
||||
for (var i = 0; i < `len`; ++i) result[i] = NimCopy(`value`, `typ`);
|
||||
return result;
|
||||
"""
|
||||
|
||||
proc chckIndx(i, a, b: int): int {.compilerproc.} =
|
||||
if i >= a and i <= b: return i
|
||||
else: raiseIndexError()
|
||||
|
||||
proc chckRange(i, a, b: int): int {.compilerproc.} =
|
||||
if i >= a and i <= b: return i
|
||||
else: raiseRangeError()
|
||||
|
||||
proc chckObj(obj, subclass: PNimType) {.compilerproc.} =
|
||||
# checks if obj is of type subclass:
|
||||
var x = obj
|
||||
if x == subclass: return # optimized fast path
|
||||
while x != subclass:
|
||||
if x == nil:
|
||||
raise newException(EInvalidObjectConversion, "invalid object conversion")
|
||||
x = x.base
|
||||
|
||||
{.pop.}
|
||||
|
||||
#proc AddU($1, $2)
|
||||
#SubU($1, $2)
|
||||
#MulU($1, $2)
|
||||
#DivU($1, $2)
|
||||
#ModU($1, $2)
|
||||
#AddU64($1, $2)
|
||||
#SubU64($1, $2)
|
||||
#MulU64($1, $2)
|
||||
#DivU64($1, $2)
|
||||
#ModU64($1, $2)
|
||||
#LeU($1, $2)
|
||||
#LtU($1, $2)
|
||||
#LeU64($1, $2)
|
||||
#LtU64($1, $2)
|
||||
#Ze($1)
|
||||
#Ze64($1)
|
||||
#ToU8($1)
|
||||
#ToU16($1)
|
||||
#ToU32($1)
|
||||
|
||||
#NegInt($1)
|
||||
#NegInt64($1)
|
||||
#AbsInt($1)
|
||||
#AbsInt64($1)
|
||||
285
lib/system/excpt.nim
Normal file
285
lib/system/excpt.nim
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2009 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
|
||||
# Exception handling code. This is difficult because it has
|
||||
# to work if there is no more memory. Thus we have to use
|
||||
# a static string. Do not use ``sprintf``, etc. as they are
|
||||
# unsafe!
|
||||
|
||||
when not defined(windows) or not defined(guiapp):
|
||||
proc writeToStdErr(msg: CString) = write(stdout, msg)
|
||||
|
||||
else:
|
||||
proc MessageBoxA(hWnd: cint, lpText, lpCaption: cstring, uType: int): int32 {.
|
||||
header: "<windows.h>", nodecl.}
|
||||
|
||||
proc writeToStdErr(msg: CString) =
|
||||
discard MessageBoxA(0, msg, nil, 0)
|
||||
|
||||
proc raiseException(e: ref E_Base, ename: CString) {.compilerproc.}
|
||||
proc reraiseException() {.compilerproc.}
|
||||
|
||||
proc registerSignalHandler() {.compilerproc.}
|
||||
|
||||
proc chckIndx(i, a, b: int): int {.inline, compilerproc.}
|
||||
proc chckRange(i, a, b: int): int {.inline, compilerproc.}
|
||||
proc chckRangeF(x, a, b: float): float {.inline, compilerproc.}
|
||||
proc chckNil(p: pointer) {.inline, compilerproc.}
|
||||
|
||||
type
|
||||
PSafePoint = ptr TSafePoint
|
||||
TSafePoint {.compilerproc, final.} = object
|
||||
prev: PSafePoint # points to next safe point ON THE STACK
|
||||
exc: ref E_Base
|
||||
status: int
|
||||
context: C_JmpBuf
|
||||
|
||||
var
|
||||
excHandler {.compilerproc.}: PSafePoint = nil
|
||||
# list of exception handlers
|
||||
# a global variable for the root of all try blocks
|
||||
|
||||
proc reraiseException() =
|
||||
if excHandler == nil:
|
||||
raise newException(ENoExceptionToReraise, "no exception to reraise")
|
||||
else:
|
||||
c_longjmp(excHandler.context, 1)
|
||||
|
||||
type
|
||||
PFrame = ptr TFrame
|
||||
TFrame {.importc, nodecl, final.} = object
|
||||
prev: PFrame
|
||||
procname: CString
|
||||
line: int # current line number
|
||||
filename: CString
|
||||
len: int # length of slots (when not debugging always zero)
|
||||
|
||||
var
|
||||
buf: string # cannot be allocated on the stack!
|
||||
assertBuf: string # we need a different buffer for
|
||||
# assert, as it raises an exception and
|
||||
# exception handler needs the buffer too
|
||||
|
||||
framePtr {.exportc.}: PFrame
|
||||
|
||||
tempFrames: array [0..127, PFrame] # cannot be allocated on the stack!
|
||||
|
||||
stackTraceNewLine* = "\n" ## undocumented feature
|
||||
|
||||
proc auxWriteStackTrace(f: PFrame, s: var string) =
|
||||
const
|
||||
firstCalls = 32
|
||||
var
|
||||
it = f
|
||||
i = 0
|
||||
total = 0
|
||||
while it != nil and i <= high(tempFrames)-(firstCalls-1):
|
||||
# the (-1) is for a nil entry that marks where the '...' should occur
|
||||
tempFrames[i] = it
|
||||
inc(i)
|
||||
inc(total)
|
||||
it = it.prev
|
||||
var b = it
|
||||
while it != nil:
|
||||
inc(total)
|
||||
it = it.prev
|
||||
for j in 1..total-i-(firstCalls-1):
|
||||
if b != nil: b = b.prev
|
||||
if total != i:
|
||||
tempFrames[i] = nil
|
||||
inc(i)
|
||||
while b != nil and i <= high(tempFrames):
|
||||
tempFrames[i] = b
|
||||
inc(i)
|
||||
b = b.prev
|
||||
for j in countdown(i-1, 0):
|
||||
if tempFrames[j] == nil:
|
||||
add(s, "(")
|
||||
add(s, $(total-i-1))
|
||||
add(s, " calls omitted) ...")
|
||||
else:
|
||||
add(s, $tempFrames[j].procname)
|
||||
if tempFrames[j].line > 0:
|
||||
add(s, ", line: ")
|
||||
add(s, $tempFrames[j].line)
|
||||
add(s, stackTraceNewLine)
|
||||
|
||||
proc rawWriteStackTrace(s: var string) =
|
||||
if framePtr == nil:
|
||||
add(s, "No stack traceback available")
|
||||
add(s, stackTraceNewLine)
|
||||
else:
|
||||
add(s, "Traceback (most recent call last)")
|
||||
add(s, stackTraceNewLine)
|
||||
auxWriteStackTrace(framePtr, s)
|
||||
|
||||
proc quitOrDebug() {.inline.} =
|
||||
when not defined(endb):
|
||||
quit(1)
|
||||
else:
|
||||
endbStep() # call the debugger
|
||||
|
||||
proc raiseException(e: ref E_Base, ename: CString) =
|
||||
GC_disable() # a bad thing is an error in the GC while raising an exception
|
||||
e.name = ename
|
||||
if excHandler != nil:
|
||||
excHandler.exc = e
|
||||
c_longjmp(excHandler.context, 1)
|
||||
else:
|
||||
if not isNil(buf):
|
||||
setLen(buf, 0)
|
||||
rawWriteStackTrace(buf)
|
||||
if e.msg != nil and e.msg[0] != '\0':
|
||||
add(buf, "Error: unhandled exception: ")
|
||||
add(buf, $e.msg)
|
||||
else:
|
||||
add(buf, "Error: unhandled exception")
|
||||
add(buf, " [")
|
||||
add(buf, $ename)
|
||||
add(buf, "]\n")
|
||||
writeToStdErr(buf)
|
||||
else:
|
||||
writeToStdErr(ename)
|
||||
quitOrDebug()
|
||||
GC_enable()
|
||||
|
||||
var
|
||||
gAssertionFailed: ref EAssertionFailed
|
||||
|
||||
proc internalAssert(file: cstring, line: int, cond: bool) {.compilerproc.} =
|
||||
if not cond:
|
||||
#c_fprintf(c_stdout, "Assertion failure: file %s line %ld\n", file, line)
|
||||
#quit(1)
|
||||
GC_disable() # BUGFIX: `$` allocates a new string object!
|
||||
if not isNil(assertBuf):
|
||||
# BUGFIX: when debugging the GC, assertBuf may be nil
|
||||
setLen(assertBuf, 0)
|
||||
add(assertBuf, "[Assertion failure] file: ")
|
||||
add(assertBuf, file)
|
||||
add(assertBuf, " line: ")
|
||||
add(assertBuf, $line)
|
||||
add(assertBuf, "\n")
|
||||
gAssertionFailed.msg = assertBuf
|
||||
GC_enable()
|
||||
if gAssertionFailed != nil:
|
||||
raise gAssertionFailed
|
||||
else:
|
||||
c_fprintf(c_stdout, "Assertion failure: file %s line %ld\n", file, line)
|
||||
quit(1)
|
||||
|
||||
proc WriteStackTrace() =
|
||||
var s = ""
|
||||
rawWriteStackTrace(s)
|
||||
writeToStdErr(s)
|
||||
|
||||
#proc stackTraceWrapper {.noconv.} =
|
||||
# writeStackTrace()
|
||||
|
||||
#addQuitProc(stackTraceWrapper)
|
||||
|
||||
var
|
||||
dbgAborting: bool # whether the debugger wants to abort
|
||||
|
||||
proc signalHandler(sig: cint) {.exportc: "signalHandler", noconv.} =
|
||||
# print stack trace and quit
|
||||
var s = sig
|
||||
GC_disable()
|
||||
setLen(buf, 0)
|
||||
rawWriteStackTrace(buf)
|
||||
|
||||
if s == SIGINT: add(buf, "SIGINT: Interrupted by Ctrl-C.\n")
|
||||
elif s == SIGSEGV: add(buf, "SIGSEGV: Illegal storage access.\n")
|
||||
elif s == SIGABRT:
|
||||
if dbgAborting: return # the debugger wants to abort
|
||||
add(buf, "SIGABRT: Abnormal termination.\n")
|
||||
elif s == SIGFPE: add(buf, "SIGFPE: Arithmetic error.\n")
|
||||
elif s == SIGILL: add(buf, "SIGILL: Illegal operation.\n")
|
||||
elif s == SIGBUS: add(buf, "SIGBUS: Illegal storage access.\n")
|
||||
else: add(buf, "unknown signal\n")
|
||||
writeToStdErr(buf)
|
||||
dbgAborting = True # play safe here...
|
||||
GC_enable()
|
||||
quit(1) # always quit when SIGABRT
|
||||
|
||||
proc registerSignalHandler() =
|
||||
c_signal(SIGINT, signalHandler)
|
||||
c_signal(SIGSEGV, signalHandler)
|
||||
c_signal(SIGABRT, signalHandler)
|
||||
c_signal(SIGFPE, signalHandler)
|
||||
c_signal(SIGILL, signalHandler)
|
||||
c_signal(SIGBUS, signalHandler)
|
||||
|
||||
when not defined(noSignalHandler):
|
||||
registerSignalHandler() # call it in initialization section
|
||||
# for easier debugging of the GC, this memory is only allocated after the
|
||||
# signal handlers have been registered
|
||||
new(gAssertionFailed)
|
||||
buf = newString(2048)
|
||||
assertBuf = newString(2048)
|
||||
setLen(buf, 0)
|
||||
setLen(assertBuf, 0)
|
||||
|
||||
proc raiseRangeError(val: biggestInt) {.compilerproc, noreturn, noinline.} =
|
||||
raise newException(EOutOfRange, "value " & $val & " out of range")
|
||||
|
||||
proc raiseIndexError() {.compilerproc, noreturn, noinline.} =
|
||||
raise newException(EInvalidIndex, "index out of bounds")
|
||||
|
||||
proc raiseFieldError(f: string) {.compilerproc, noreturn, noinline.} =
|
||||
raise newException(EInvalidField, f & " is not accessible")
|
||||
|
||||
proc chckIndx(i, a, b: int): int =
|
||||
if i >= a and i <= b:
|
||||
return i
|
||||
else:
|
||||
raiseIndexError()
|
||||
|
||||
proc chckRange(i, a, b: int): int =
|
||||
if i >= a and i <= b:
|
||||
return i
|
||||
else:
|
||||
raiseRangeError(i)
|
||||
|
||||
proc chckRange64(i, a, b: int64): int64 {.compilerproc.} =
|
||||
if i >= a and i <= b:
|
||||
return i
|
||||
else:
|
||||
raiseRangeError(i)
|
||||
|
||||
proc chckRangeF(x, a, b: float): float =
|
||||
if x >= a and x <= b:
|
||||
return x
|
||||
else:
|
||||
raise newException(EOutOfRange, "value " & $x & " out of range")
|
||||
|
||||
proc chckNil(p: pointer) =
|
||||
if p == nil: c_raise(SIGSEGV)
|
||||
|
||||
proc chckObj(obj, subclass: PNimType) {.compilerproc.} =
|
||||
# checks if obj is of type subclass:
|
||||
var x = obj
|
||||
if x == subclass: return # optimized fast path
|
||||
while x != subclass:
|
||||
if x == nil:
|
||||
raise newException(EInvalidObjectConversion, "invalid object conversion")
|
||||
x = x.base
|
||||
|
||||
proc chckObjAsgn(a, b: PNimType) {.compilerproc, inline.} =
|
||||
if a != b:
|
||||
raise newException(EInvalidObjectAssignment, "invalid object assignment")
|
||||
|
||||
proc isObj(obj, subclass: PNimType): bool {.compilerproc.} =
|
||||
# checks if obj is of type subclass:
|
||||
var x = obj
|
||||
if x == subclass: return true # optimized fast path
|
||||
while x != subclass:
|
||||
if x == nil: return false
|
||||
x = x.base
|
||||
return true
|
||||
647
lib/system/gc.nim
Normal file
647
lib/system/gc.nim
Normal file
|
|
@ -0,0 +1,647 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2009 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
|
||||
# Garbage Collector
|
||||
#
|
||||
# The basic algorithm is *Deferrent Reference Counting* with cycle detection.
|
||||
# Special care has been taken to avoid recursion as far as possible to avoid
|
||||
# stack overflows when traversing deep datastructures. This is comparable to
|
||||
# an incremental and generational GC. It should be well-suited for soft real
|
||||
# time applications (like games).
|
||||
#
|
||||
# Future Improvements:
|
||||
# * Support for multi-threading. However, locks for the reference counting
|
||||
# might turn out to be too slow.
|
||||
|
||||
const
|
||||
CycleIncrease = 2 # is a multiplicative increase
|
||||
InitialCycleThreshold = 4*1024*1024 # X MB because cycle checking is slow
|
||||
ZctThreshold = 256 # we collect garbage if the ZCT's size
|
||||
# reaches this threshold
|
||||
# this seems to be a good value
|
||||
|
||||
const
|
||||
rcIncrement = 0b1000 # so that lowest 3 bits are not touched
|
||||
# NOTE: Most colors are currently unused
|
||||
rcBlack = 0b000 # cell is colored black; in use or free
|
||||
rcGray = 0b001 # possible member of a cycle
|
||||
rcWhite = 0b010 # member of a garbage cycle
|
||||
rcPurple = 0b011 # possible root of a cycle
|
||||
rcZct = 0b100 # in ZCT
|
||||
rcRed = 0b101 # Candidate cycle undergoing sigma-computation
|
||||
rcOrange = 0b110 # Candidate cycle awaiting epoch boundary
|
||||
rcShift = 3 # shift by rcShift to get the reference counter
|
||||
colorMask = 0b111
|
||||
type
|
||||
TWalkOp = enum
|
||||
waZctDecRef, waPush, waCycleDecRef
|
||||
|
||||
TFinalizer {.compilerproc.} = proc (self: pointer)
|
||||
# A ref type can have a finalizer that is called before the object's
|
||||
# storage is freed.
|
||||
|
||||
TGcStat {.final, pure.} = object
|
||||
stackScans: int # number of performed stack scans (for statistics)
|
||||
cycleCollections: int # number of performed full collections
|
||||
maxThreshold: int # max threshold that has been set
|
||||
maxStackSize: int # max stack size
|
||||
maxStackCells: int # max stack cells in ``decStack``
|
||||
cycleTableSize: int # max entries in cycle table
|
||||
|
||||
TGcHeap {.final, pure.} = object # this contains the zero count and
|
||||
# non-zero count table
|
||||
zct: TCellSeq # the zero count table
|
||||
decStack: TCellSeq # cells in the stack that are to decref again
|
||||
cycleRoots: TCellSet
|
||||
tempStack: TCellSeq # temporary stack for recursion elimination
|
||||
stat: TGcStat
|
||||
|
||||
var
|
||||
stackBottom: pointer
|
||||
gch: TGcHeap
|
||||
cycleThreshold: int = InitialCycleThreshold
|
||||
recGcLock: int = 0
|
||||
# we use a lock to prevend the garbage collector to be triggered in a
|
||||
# finalizer; the collector should not call itself this way! Thus every
|
||||
# object allocated by a finalizer will not trigger a garbage collection.
|
||||
# This is wasteful but safe. This is a lock against recursive garbage
|
||||
# collection, not a lock for threads!
|
||||
|
||||
proc unsureAsgnRef(dest: ppointer, src: pointer) {.compilerproc.}
|
||||
# unsureAsgnRef updates the reference counters only if dest is not on the
|
||||
# stack. It is used by the code generator if it cannot decide wether a
|
||||
# reference is in the stack or not (this can happen for var parameters).
|
||||
#proc growObj(old: pointer, newsize: int): pointer {.compilerproc.}
|
||||
proc newObj(typ: PNimType, size: int): pointer {.compilerproc.}
|
||||
proc newSeq(typ: PNimType, len: int): pointer {.compilerproc.}
|
||||
|
||||
proc addZCT(s: var TCellSeq, c: PCell) {.noinline.} =
|
||||
if (c.refcount and rcZct) == 0:
|
||||
c.refcount = c.refcount and not colorMask or rcZct
|
||||
add(s, c)
|
||||
|
||||
proc cellToUsr(cell: PCell): pointer {.inline.} =
|
||||
# convert object (=pointer to refcount) to pointer to userdata
|
||||
result = cast[pointer](cast[TAddress](cell)+%TAddress(sizeof(TCell)))
|
||||
|
||||
proc usrToCell(usr: pointer): PCell {.inline.} =
|
||||
# convert pointer to userdata to object (=pointer to refcount)
|
||||
result = cast[PCell](cast[TAddress](usr)-%TAddress(sizeof(TCell)))
|
||||
|
||||
proc canbeCycleRoot(c: PCell): bool {.inline.} =
|
||||
result = ntfAcyclic notin c.typ.flags
|
||||
|
||||
proc extGetCellType(c: pointer): PNimType {.compilerproc.} =
|
||||
# used for code generation concerning debugging
|
||||
result = usrToCell(c).typ
|
||||
|
||||
proc internRefcount(p: pointer): int {.exportc: "getRefcount".} =
|
||||
result = int(usrToCell(p).refcount) shr rcShift
|
||||
|
||||
proc GC_disable() = inc(recGcLock)
|
||||
proc GC_enable() =
|
||||
if recGcLock > 0: dec(recGcLock)
|
||||
|
||||
proc GC_setStrategy(strategy: TGC_Strategy) =
|
||||
case strategy
|
||||
of gcThroughput: nil
|
||||
of gcResponsiveness: nil
|
||||
of gcOptimizeSpace: nil
|
||||
of gcOptimizeTime: nil
|
||||
|
||||
proc GC_enableMarkAndSweep() =
|
||||
cycleThreshold = InitialCycleThreshold
|
||||
|
||||
proc GC_disableMarkAndSweep() =
|
||||
cycleThreshold = high(cycleThreshold)-1
|
||||
# set to the max value to suppress the cycle detector
|
||||
|
||||
# this that has to equals zero, otherwise we have to round up UnitsPerPage:
|
||||
when BitsPerPage mod (sizeof(int)*8) != 0:
|
||||
{.error: "(BitsPerPage mod BitsPerUnit) should be zero!".}
|
||||
|
||||
when debugGC:
|
||||
proc writeCell(msg: CString, c: PCell) =
|
||||
var kind = -1
|
||||
if c.typ != nil: kind = ord(c.typ.kind)
|
||||
when debugGC:
|
||||
c_fprintf(c_stdout, "[GC] %s: %p %d rc=%ld from %s(%ld)\n",
|
||||
msg, c, kind, c.refcount shr rcShift, c.filename, c.line)
|
||||
else:
|
||||
c_fprintf(c_stdout, "[GC] %s: %p %d rc=%ld\n",
|
||||
msg, c, kind, c.refcount shr rcShift)
|
||||
|
||||
when traceGC:
|
||||
# traceGC is a special switch to enable extensive debugging
|
||||
type
|
||||
TCellState = enum
|
||||
csAllocated, csZctFreed, csCycFreed
|
||||
var
|
||||
states: array[TCellState, TCellSet]
|
||||
|
||||
proc traceCell(c: PCell, state: TCellState) =
|
||||
case state
|
||||
of csAllocated:
|
||||
if c in states[csAllocated]:
|
||||
writeCell("attempt to alloc an already allocated cell", c)
|
||||
assert(false)
|
||||
excl(states[csCycFreed], c)
|
||||
excl(states[csZctFreed], c)
|
||||
of csZctFreed:
|
||||
if c in states[csZctFreed]:
|
||||
writeCell("attempt to free zct cell twice", c)
|
||||
assert(false)
|
||||
if c in states[csCycFreed]:
|
||||
writeCell("attempt to free with zct, but already freed with cyc", c)
|
||||
assert(false)
|
||||
if c notin states[csAllocated]:
|
||||
writeCell("attempt to free not an allocated cell", c)
|
||||
assert(false)
|
||||
excl(states[csAllocated], c)
|
||||
of csCycFreed:
|
||||
if c notin states[csAllocated]:
|
||||
writeCell("attempt to free a not allocated cell", c)
|
||||
assert(false)
|
||||
if c in states[csCycFreed]:
|
||||
writeCell("attempt to free cyc cell twice", c)
|
||||
assert(false)
|
||||
if c in states[csZctFreed]:
|
||||
writeCell("attempt to free with cyc, but already freed with zct", c)
|
||||
assert(false)
|
||||
excl(states[csAllocated], c)
|
||||
incl(states[state], c)
|
||||
|
||||
proc writeLeakage() =
|
||||
var z = 0
|
||||
var y = 0
|
||||
var e = 0
|
||||
for c in elements(states[csAllocated]):
|
||||
inc(e)
|
||||
if c in states[csZctFreed]: inc(z)
|
||||
elif c in states[csCycFreed]: inc(z)
|
||||
else: writeCell("leak", c)
|
||||
cfprintf(cstdout, "Allocations: %ld; ZCT freed: %ld; CYC freed: %ld\n",
|
||||
e, z, y)
|
||||
|
||||
template gcTrace(cell, state: expr): stmt =
|
||||
when traceGC: traceCell(cell, state)
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
# forward declarations:
|
||||
proc collectCT(gch: var TGcHeap)
|
||||
proc IsOnStack(p: pointer): bool {.noinline.}
|
||||
proc forAllChildren(cell: PCell, op: TWalkOp)
|
||||
proc doOperation(p: pointer, op: TWalkOp)
|
||||
proc forAllChildrenAux(dest: Pointer, mt: PNimType, op: TWalkOp)
|
||||
# we need the prototype here for debugging purposes
|
||||
|
||||
proc prepareDealloc(cell: PCell) =
|
||||
if cell.typ.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(recGcLock)
|
||||
(cast[TFinalizer](cell.typ.finalizer))(cellToUsr(cell))
|
||||
dec(recGcLock)
|
||||
|
||||
proc setStackBottom(theStackBottom: pointer) {.compilerproc.} =
|
||||
stackBottom = theStackBottom
|
||||
|
||||
proc PossibleRoot(gch: var TGcHeap, c: PCell) {.inline.} =
|
||||
if canbeCycleRoot(c): incl(gch.cycleRoots, c)
|
||||
|
||||
proc decRef(c: PCell) {.inline.} =
|
||||
when stressGC:
|
||||
if c.refcount <% rcIncrement:
|
||||
writeCell("broken cell", c)
|
||||
assert(c.refcount >=% rcIncrement)
|
||||
c.refcount = c.refcount -% rcIncrement
|
||||
if c.refcount <% rcIncrement:
|
||||
addZCT(gch.zct, c)
|
||||
elif canBeCycleRoot(c):
|
||||
incl(gch.cycleRoots, c)
|
||||
|
||||
proc incRef(c: PCell) {.inline.} =
|
||||
c.refcount = c.refcount +% rcIncrement
|
||||
if canBeCycleRoot(c):
|
||||
incl(gch.cycleRoots, c)
|
||||
|
||||
proc nimGCref(p: pointer) {.compilerproc, inline.} = incRef(usrToCell(p))
|
||||
proc nimGCunref(p: pointer) {.compilerproc, inline.} = decRef(usrToCell(p))
|
||||
|
||||
proc asgnRef(dest: ppointer, src: pointer) {.compilerproc, inline.} =
|
||||
# the code generator calls this proc!
|
||||
assert(not isOnStack(dest))
|
||||
# BUGFIX: first incRef then decRef!
|
||||
if src != nil: incRef(usrToCell(src))
|
||||
if dest^ != nil: decRef(usrToCell(dest^))
|
||||
dest^ = src
|
||||
|
||||
proc asgnRefNoCycle(dest: ppointer, src: pointer) {.compilerproc, inline.} =
|
||||
# the code generator calls this proc if it is known at compile time that no
|
||||
# cycle is possible.
|
||||
if src != nil:
|
||||
var c = usrToCell(src)
|
||||
c.refcount = c.refcount +% rcIncrement
|
||||
if dest^ != nil:
|
||||
var c = usrToCell(dest^)
|
||||
c.refcount = c.refcount -% rcIncrement
|
||||
if c.refcount <% rcIncrement:
|
||||
addZCT(gch.zct, c)
|
||||
dest^ = src
|
||||
|
||||
proc unsureAsgnRef(dest: ppointer, src: pointer) =
|
||||
if not IsOnStack(dest):
|
||||
if src != nil: incRef(usrToCell(src))
|
||||
if dest^ != nil: decRef(usrToCell(dest^))
|
||||
dest^ = src
|
||||
|
||||
proc initGC() =
|
||||
when traceGC:
|
||||
for i in low(TCellState)..high(TCellState): Init(states[i])
|
||||
gch.stat.stackScans = 0
|
||||
gch.stat.cycleCollections = 0
|
||||
gch.stat.maxThreshold = 0
|
||||
gch.stat.maxStackSize = 0
|
||||
gch.stat.maxStackCells = 0
|
||||
gch.stat.cycleTableSize = 0
|
||||
# init the rt
|
||||
init(gch.zct)
|
||||
init(gch.tempStack)
|
||||
Init(gch.cycleRoots)
|
||||
Init(gch.decStack)
|
||||
new(gOutOfMem) # reserve space for the EOutOfMemory exception here!
|
||||
|
||||
proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: TWalkOp) =
|
||||
var d = cast[TAddress](dest)
|
||||
case n.kind
|
||||
of nkNone: assert(false)
|
||||
of nkSlot: forAllChildrenAux(cast[pointer](d +% n.offset), n.typ, op)
|
||||
of nkList:
|
||||
for i in 0..n.len-1: forAllSlotsAux(dest, n.sons[i], op)
|
||||
of nkCase:
|
||||
var m = selectBranch(dest, n)
|
||||
if m != nil: forAllSlotsAux(dest, m, op)
|
||||
|
||||
proc forAllChildrenAux(dest: Pointer, mt: PNimType, op: TWalkOp) =
|
||||
var d = cast[TAddress](dest)
|
||||
if dest == nil: return # nothing to do
|
||||
if ntfNoRefs notin mt.flags:
|
||||
case mt.Kind
|
||||
of tyArray, tyArrayConstr, tyOpenArray:
|
||||
for i in 0..(mt.size div mt.base.size)-1:
|
||||
forAllChildrenAux(cast[pointer](d +% i *% mt.base.size), mt.base, op)
|
||||
of tyRef, tyString, tySequence: # leaf:
|
||||
doOperation(cast[ppointer](d)^, op)
|
||||
of tyObject, tyTuple, tyPureObject:
|
||||
forAllSlotsAux(dest, mt.node, op)
|
||||
else: nil
|
||||
|
||||
proc forAllChildren(cell: PCell, op: TWalkOp) =
|
||||
assert(cell != nil)
|
||||
assert(cell.typ != nil)
|
||||
case cell.typ.Kind
|
||||
of tyRef: # common case
|
||||
forAllChildrenAux(cellToUsr(cell), cell.typ.base, op)
|
||||
of tySequence:
|
||||
var d = cast[TAddress](cellToUsr(cell))
|
||||
var s = cast[PGenericSeq](d)
|
||||
if s != nil:
|
||||
for i in 0..s.len-1:
|
||||
forAllChildrenAux(cast[pointer](d +% i *% cell.typ.base.size +%
|
||||
GenericSeqSize), cell.typ.base, op)
|
||||
of tyString: nil
|
||||
else: assert(false)
|
||||
|
||||
proc checkCollection {.inline.} =
|
||||
# checks if a collection should be done
|
||||
if recGcLock == 0:
|
||||
collectCT(gch)
|
||||
|
||||
proc newObj(typ: PNimType, size: int): pointer =
|
||||
# generates a new object and sets its reference counter to 0
|
||||
assert(typ.kind in {tyRef, tyString, tySequence})
|
||||
checkCollection()
|
||||
var res = cast[PCell](rawAlloc(allocator, size + sizeof(TCell)))
|
||||
zeroMem(res, size+sizeof(TCell))
|
||||
assert((cast[TAddress](res) and (MemAlign-1)) == 0)
|
||||
# now it is buffered in the ZCT
|
||||
res.typ = typ
|
||||
when debugGC:
|
||||
if framePtr != nil and framePtr.prev != nil:
|
||||
res.filename = framePtr.prev.filename
|
||||
res.line = framePtr.prev.line
|
||||
res.refcount = rcZct # refcount is zero, but mark it to be in the ZCT
|
||||
assert(isAllocatedPtr(allocator, res))
|
||||
# its refcount is zero, so add it to the ZCT:
|
||||
block addToZCT:
|
||||
# we check the last 8 entries (cache line) for a slot
|
||||
# that could be reused
|
||||
var L = gch.zct.len
|
||||
var d = gch.zct.d
|
||||
for i in countdown(L-1, max(0, L-8)):
|
||||
var c = d[i]
|
||||
if c.refcount >=% rcIncrement:
|
||||
c.refcount = c.refcount and not colorMask
|
||||
d[i] = res
|
||||
break addToZCT
|
||||
add(gch.zct, res)
|
||||
when logGC: writeCell("new cell", res)
|
||||
gcTrace(res, csAllocated)
|
||||
result = cellToUsr(res)
|
||||
|
||||
proc newSeq(typ: PNimType, len: int): pointer =
|
||||
result = newObj(typ, addInt(mulInt(len, typ.base.size), GenericSeqSize))
|
||||
cast[PGenericSeq](result).len = len
|
||||
cast[PGenericSeq](result).space = len
|
||||
|
||||
proc growObj(old: pointer, newsize: int): pointer =
|
||||
checkCollection()
|
||||
var ol = usrToCell(old)
|
||||
assert(ol.typ != nil)
|
||||
assert(ol.typ.kind in {tyString, tySequence})
|
||||
var res = cast[PCell](rawAlloc(allocator, newsize + sizeof(TCell)))
|
||||
var elemSize = 1
|
||||
if ol.typ.kind != tyString:
|
||||
elemSize = ol.typ.base.size
|
||||
|
||||
var oldsize = cast[PGenericSeq](old).len*elemSize + GenericSeqSize
|
||||
copyMem(res, ol, oldsize + sizeof(TCell))
|
||||
zeroMem(cast[pointer](cast[TAddress](res)+% oldsize +% sizeof(TCell)),
|
||||
newsize-oldsize)
|
||||
assert((cast[TAddress](res) and (MemAlign-1)) == 0)
|
||||
assert(res.refcount shr rcShift <=% 1)
|
||||
#if res.refcount <% rcIncrement:
|
||||
# add(gch.zct, res)
|
||||
#else: # XXX: what to do here?
|
||||
# decRef(ol)
|
||||
if (ol.refcount and colorMask) == rcZct:
|
||||
var j = gch.zct.len-1
|
||||
var d = gch.zct.d
|
||||
while j >= 0:
|
||||
if d[j] == ol:
|
||||
d[j] = res
|
||||
break
|
||||
dec(j)
|
||||
if canBeCycleRoot(ol): excl(gch.cycleRoots, ol)
|
||||
when logGC:
|
||||
writeCell("growObj old cell", ol)
|
||||
writeCell("growObj new cell", res)
|
||||
gcTrace(ol, csZctFreed)
|
||||
gcTrace(res, csAllocated)
|
||||
when reallyDealloc: rawDealloc(allocator, ol)
|
||||
else:
|
||||
assert(ol.typ != nil)
|
||||
zeroMem(ol, sizeof(TCell))
|
||||
result = cellToUsr(res)
|
||||
|
||||
# ---------------- cycle collector -------------------------------------------
|
||||
|
||||
proc doOperation(p: pointer, op: TWalkOp) =
|
||||
if p == nil: return
|
||||
var c: PCell = usrToCell(p)
|
||||
assert(c != nil)
|
||||
case op # faster than function pointers because of easy prediction
|
||||
of waZctDecRef:
|
||||
assert(c.refcount >=% rcIncrement)
|
||||
c.refcount = c.refcount -% rcIncrement
|
||||
when logGC: writeCell("decref (from doOperation)", c)
|
||||
if c.refcount <% rcIncrement: addZCT(gch.zct, c)
|
||||
of waPush:
|
||||
add(gch.tempStack, c)
|
||||
of waCycleDecRef:
|
||||
assert(c.refcount >=% rcIncrement)
|
||||
c.refcount = c.refcount -% rcIncrement
|
||||
|
||||
# we now use a much simpler and non-recursive algorithm for cycle removal
|
||||
proc collectCycles(gch: var TGcHeap) =
|
||||
var tabSize = 0
|
||||
for c in elements(gch.cycleRoots):
|
||||
inc(tabSize)
|
||||
forallChildren(c, waCycleDecRef)
|
||||
gch.stat.cycleTableSize = max(gch.stat.cycleTableSize, tabSize)
|
||||
|
||||
# restore reference counts (a depth-first traversal is needed):
|
||||
var marker: TCellSet
|
||||
Init(marker)
|
||||
for c in elements(gch.cycleRoots):
|
||||
if c.refcount >=% rcIncrement:
|
||||
if not containsOrIncl(marker, c):
|
||||
gch.tempStack.len = 0
|
||||
forAllChildren(c, waPush)
|
||||
while gch.tempStack.len > 0:
|
||||
dec(gch.tempStack.len)
|
||||
var d = gch.tempStack.d[gch.tempStack.len]
|
||||
d.refcount = d.refcount +% rcIncrement
|
||||
if d in gch.cycleRoots and not containsOrIncl(marker, d):
|
||||
forAllChildren(d, waPush)
|
||||
# remove cycles:
|
||||
for c in elements(gch.cycleRoots):
|
||||
if c.refcount <% rcIncrement:
|
||||
gch.tempStack.len = 0
|
||||
forAllChildren(c, waPush)
|
||||
while gch.tempStack.len > 0:
|
||||
dec(gch.tempStack.len)
|
||||
var d = gch.tempStack.d[gch.tempStack.len]
|
||||
if d.refcount <% rcIncrement:
|
||||
if d notin gch.cycleRoots: # d is leaf of c and not part of cycle
|
||||
addZCT(gch.zct, d)
|
||||
when logGC: writeCell("add to ZCT (from cycle collector)", d)
|
||||
prepareDealloc(c)
|
||||
gcTrace(c, csCycFreed)
|
||||
when logGC: writeCell("cycle collector dealloc cell", c)
|
||||
when reallyDealloc: rawDealloc(allocator, c)
|
||||
else:
|
||||
assert(c.typ != nil)
|
||||
zeroMem(c, sizeof(TCell))
|
||||
Deinit(gch.cycleRoots)
|
||||
Init(gch.cycleRoots)
|
||||
|
||||
proc gcMark(p: pointer) {.inline.} =
|
||||
# the addresses are not as objects on the stack, so turn them to objects:
|
||||
var cell = usrToCell(p)
|
||||
var c = cast[TAddress](cell)
|
||||
if c >% PageSize:
|
||||
# fast check: does it look like a cell?
|
||||
if isAllocatedPtr(allocator, cell):
|
||||
# mark the cell:
|
||||
cell.refcount = cell.refcount +% rcIncrement
|
||||
add(gch.decStack, cell)
|
||||
|
||||
# ----------------- stack management --------------------------------------
|
||||
# inspired from Smart Eiffel
|
||||
|
||||
proc stackSize(): int {.noinline.} =
|
||||
var stackTop: array[0..1, pointer]
|
||||
result = abs(cast[int](addr(stackTop[0])) - cast[int](stackBottom))
|
||||
|
||||
when defined(sparc): # For SPARC architecture.
|
||||
proc isOnStack(p: pointer): bool =
|
||||
var stackTop: array [0..1, pointer]
|
||||
var b = cast[TAddress](stackBottom)
|
||||
var a = cast[TAddress](addr(stackTop[0]))
|
||||
var x = cast[TAddress](p)
|
||||
result = x >=% a and x <=% b
|
||||
|
||||
proc markStackAndRegisters(gch: var TGcHeap) {.noinline, cdecl.} =
|
||||
when defined(sparcv9):
|
||||
asm """"flushw \n" """
|
||||
else:
|
||||
asm """"ta 0x3 ! ST_FLUSH_WINDOWS\n" """
|
||||
|
||||
var
|
||||
max = stackBottom
|
||||
sp: PPointer
|
||||
stackTop: array[0..1, pointer]
|
||||
sp = addr(stackTop[0])
|
||||
# Addresses decrease as the stack grows.
|
||||
while sp <= max:
|
||||
gcMark(sp^)
|
||||
sp = cast[ppointer](cast[TAddress](sp) +% sizeof(pointer))
|
||||
|
||||
elif defined(ELATE):
|
||||
{.error: "stack marking code is to be written for this architecture".}
|
||||
|
||||
elif defined(hppa) or defined(hp9000) or defined(hp9000s300) or
|
||||
defined(hp9000s700) or defined(hp9000s800) or defined(hp9000s820):
|
||||
# ---------------------------------------------------------------------------
|
||||
# Generic code for architectures where addresses increase as the stack grows.
|
||||
# ---------------------------------------------------------------------------
|
||||
proc isOnStack(p: pointer): bool =
|
||||
var stackTop: array [0..1, pointer]
|
||||
var a = cast[TAddress](stackBottom)
|
||||
var b = cast[TAddress](addr(stackTop[0]))
|
||||
var x = cast[TAddress](p)
|
||||
result = x >=% a and x <=% b
|
||||
|
||||
var
|
||||
jmpbufSize {.importc: "sizeof(jmp_buf)", nodecl.}: int
|
||||
# a little hack to get the size of a TJmpBuf in the generated C code
|
||||
# in a platform independant way
|
||||
|
||||
proc markStackAndRegisters(gch: var TGcHeap) {.noinline, cdecl.} =
|
||||
var registers: C_JmpBuf
|
||||
if c_setjmp(registers) == 0'i32: # To fill the C stack with registers.
|
||||
var max = cast[TAddress](stackBottom)
|
||||
var sp = cast[TAddress](addr(registers)) +% jmpbufSize -% sizeof(pointer)
|
||||
# sp will traverse the JMP_BUF as well (jmp_buf size is added,
|
||||
# otherwise sp would be below the registers structure).
|
||||
while sp >=% max:
|
||||
gcMark(cast[ppointer](sp)^)
|
||||
sp = sp -% sizeof(pointer)
|
||||
|
||||
else:
|
||||
# ---------------------------------------------------------------------------
|
||||
# Generic code for architectures where addresses decrease as the stack grows.
|
||||
# ---------------------------------------------------------------------------
|
||||
proc isOnStack(p: pointer): bool =
|
||||
var stackTop: array [0..1, pointer]
|
||||
var b = cast[TAddress](stackBottom)
|
||||
var a = cast[TAddress](addr(stackTop[0]))
|
||||
var x = cast[TAddress](p)
|
||||
result = x >=% a and x <=% b
|
||||
|
||||
proc markStackAndRegisters(gch: var TGcHeap) {.noinline, cdecl.} =
|
||||
# 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.
|
||||
var registers: C_JmpBuf
|
||||
if c_setjmp(registers) == 0'i32: # To fill the C stack with registers.
|
||||
var max = cast[TAddress](stackBottom)
|
||||
var sp = cast[TAddress](addr(registers))
|
||||
while sp <=% max:
|
||||
gcMark(cast[ppointer](sp)^)
|
||||
sp = sp +% sizeof(pointer)
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# end of non-portable code
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
proc CollectZCT(gch: var TGcHeap) =
|
||||
# Note: Freeing may add child objects to the ZCT! So essentially we do
|
||||
# deep freeing, which is bad for incremental operation. In order to
|
||||
# avoid a deep stack, we move objects to keep the ZCT small.
|
||||
# This is performance critical!
|
||||
var L = addr(gch.zct.len)
|
||||
while L^ > 0:
|
||||
var c = gch.zct.d[0]
|
||||
# remove from ZCT:
|
||||
assert((c.refcount and colorMask) == rcZct)
|
||||
c.refcount = c.refcount and not colorMask
|
||||
gch.zct.d[0] = gch.zct.d[L^ - 1]
|
||||
dec(L^)
|
||||
if c.refcount <% rcIncrement:
|
||||
# It may have a RC > 0, if it is in the hardware stack or
|
||||
# it has not been removed yet from the ZCT. This is because
|
||||
# ``incref`` does not bother to remove the cell from the ZCT
|
||||
# as this might be too slow.
|
||||
# In any case, it should be removed from the ZCT. But not
|
||||
# freed. **KEEP THIS IN MIND WHEN MAKING THIS INCREMENTAL!**
|
||||
if canBeCycleRoot(c): excl(gch.cycleRoots, c)
|
||||
when logGC: writeCell("zct dealloc cell", c)
|
||||
gcTrace(c, csZctFreed)
|
||||
# We are about to free the object, call the finalizer BEFORE its
|
||||
# children are deleted as well, because otherwise the finalizer may
|
||||
# access invalid memory. This is done by prepareDealloc():
|
||||
prepareDealloc(c)
|
||||
forAllChildren(c, waZctDecRef)
|
||||
when reallyDealloc: rawDealloc(allocator, c)
|
||||
else:
|
||||
assert(c.typ != nil)
|
||||
zeroMem(c, sizeof(TCell))
|
||||
|
||||
proc unmarkStackAndRegisters(gch: var TGcHeap) =
|
||||
var d = gch.decStack.d
|
||||
for i in 0..gch.decStack.len-1:
|
||||
assert isAllocatedPtr(allocator, d[i])
|
||||
decRef(d[i]) # OPT: cannot create a cycle!
|
||||
gch.decStack.len = 0
|
||||
|
||||
proc collectCT(gch: var TGcHeap) =
|
||||
if gch.zct.len >= ZctThreshold or (cycleGC and
|
||||
getOccupiedMem() >= cycleThreshold) or stressGC:
|
||||
gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize())
|
||||
assert(gch.decStack.len == 0)
|
||||
markStackAndRegisters(gch)
|
||||
gch.stat.maxStackCells = max(gch.stat.maxStackCells, gch.decStack.len)
|
||||
inc(gch.stat.stackScans)
|
||||
collectZCT(gch)
|
||||
when cycleGC:
|
||||
if getOccupiedMem() >= cycleThreshold or stressGC:
|
||||
collectCycles(gch)
|
||||
collectZCT(gch)
|
||||
inc(gch.stat.cycleCollections)
|
||||
cycleThreshold = max(InitialCycleThreshold, getOccupiedMem() *
|
||||
cycleIncrease)
|
||||
gch.stat.maxThreshold = max(gch.stat.maxThreshold, cycleThreshold)
|
||||
unmarkStackAndRegisters(gch)
|
||||
|
||||
proc GC_fullCollect() =
|
||||
var oldThreshold = cycleThreshold
|
||||
cycleThreshold = 0 # forces cycle collection
|
||||
collectCT(gch)
|
||||
cycleThreshold = oldThreshold
|
||||
|
||||
proc GC_getStatistics(): string =
|
||||
GC_disable()
|
||||
result = "[GC] total memory: " & $(getTotalMem()) & "\n" &
|
||||
"[GC] occupied memory: " & $(getOccupiedMem()) & "\n" &
|
||||
"[GC] stack scans: " & $gch.stat.stackScans & "\n" &
|
||||
"[GC] stack cells: " & $gch.stat.maxStackCells & "\n" &
|
||||
"[GC] cycle collections: " & $gch.stat.cycleCollections & "\n" &
|
||||
"[GC] max threshold: " & $gch.stat.maxThreshold & "\n" &
|
||||
"[GC] zct capacity: " & $gch.zct.cap & "\n" &
|
||||
"[GC] max cycle table size: " & $gch.stat.cycleTableSize & "\n" &
|
||||
"[GC] max stack size: " & $gch.stat.maxStackSize
|
||||
when traceGC: writeLeakage()
|
||||
GC_enable()
|
||||
65
lib/system/hti.nim
Normal file
65
lib/system/hti.nim
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2009 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
type # This should be he same as ast.TTypeKind
|
||||
# some enum fields are not used at runtime
|
||||
TNimKind = enum
|
||||
tyNone, # 0
|
||||
tyBool, # 1
|
||||
tyChar, # 2
|
||||
tyEmpty, # 3
|
||||
tyArrayConstr, # 4
|
||||
tyNil, # 5
|
||||
tyGeneric, # 6
|
||||
tyGenericInst, # 7
|
||||
tyGenericParam, # 8
|
||||
tyAbstract, # 9
|
||||
tyEnum, # 10
|
||||
tyOrdinal, # 11
|
||||
tyArray, # 12
|
||||
tyObject, # 13
|
||||
tyTuple, # 14
|
||||
tySet, # 15
|
||||
tyRange, # 16
|
||||
tyPtr, # 17
|
||||
tyRef, # 18
|
||||
tyVar, # 19
|
||||
tySequence, # 20
|
||||
tyProc, # 21
|
||||
tyPointer, # 22
|
||||
tyOpenArray, # 23
|
||||
tyString, # 24
|
||||
tyCString, # 25
|
||||
tyForward, # 26
|
||||
tyInt, tyInt8, tyInt16, tyInt32, tyInt64,
|
||||
tyFloat, tyFloat32, tyFloat64, tyFloat128,
|
||||
tyPureObject # 36: signals that object has no `n_type` field
|
||||
|
||||
TNimNodeKind = enum nkNone, nkSlot, nkList, nkCase
|
||||
TNimNode {.compilerproc, final.} = object
|
||||
kind: TNimNodeKind
|
||||
offset: int
|
||||
typ: ptr TNimType
|
||||
name: Cstring
|
||||
len: int
|
||||
sons: ptr array [0..0x7fff, ptr TNimNode]
|
||||
|
||||
TNimTypeFlag = enum
|
||||
ntfNoRefs = 0, # type contains no tyRef, tySequence, tyString
|
||||
ntfAcyclic = 1 # type cannot form a cycle
|
||||
TNimType {.compilerproc, final.} = object
|
||||
size: int
|
||||
kind: TNimKind
|
||||
flags: set[TNimTypeFlag]
|
||||
base: ptr TNimType
|
||||
node: ptr TNimNode # valid for tyRecord, tyObject, tyTuple, tyEnum
|
||||
finalizer: pointer # the finalizer for the type
|
||||
PNimType = ptr TNimType
|
||||
|
||||
# node.len may be the ``first`` element of a set
|
||||
188
lib/system/mm.nim
Normal file
188
lib/system/mm.nim
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2009 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
# Nimrod high-level memory manager: It supports Boehm's GC, no GC and the
|
||||
# native Nimrod GC. The native Nimrod GC is the default.
|
||||
|
||||
#{.push checks:on, assertions:on.}
|
||||
{.push checks:off.}
|
||||
|
||||
const
|
||||
debugGC = false # we wish to debug the GC...
|
||||
logGC = false
|
||||
traceGC = false # extensive debugging
|
||||
reallyDealloc = true # for debugging purposes this can be set to false
|
||||
cycleGC = true # (de)activate the cycle GC
|
||||
stressGC = false
|
||||
reallyOsDealloc = true
|
||||
coalescRight = true
|
||||
coalescLeft = true
|
||||
overwriteFree = false
|
||||
|
||||
type
|
||||
PPointer = ptr pointer
|
||||
TByteArray = array[0..1000_0000, byte]
|
||||
PByte = ptr TByteArray
|
||||
PString = ptr string
|
||||
|
||||
# Page size of the system; in most cases 4096 bytes. For exotic OS or
|
||||
# CPU this needs to be changed:
|
||||
const
|
||||
PageShift = 12
|
||||
PageSize = 1 shl PageShift
|
||||
PageMask = PageSize-1
|
||||
|
||||
MemAlign = 8 # also minimal allocatable memory block
|
||||
|
||||
BitsPerPage = PageSize div MemAlign
|
||||
UnitsPerPage = BitsPerPage div (sizeof(int)*8)
|
||||
# how many ints do we need to describe a page:
|
||||
# on 32 bit systems this is only 16 (!)
|
||||
|
||||
TrunkShift = 9
|
||||
BitsPerTrunk = 1 shl TrunkShift # needs to be power of 2 and divisible by 64
|
||||
TrunkMask = BitsPerTrunk - 1
|
||||
IntsPerTrunk = BitsPerTrunk div (sizeof(int)*8)
|
||||
IntShift = 5 + ord(sizeof(int) == 8) # 5 or 6, depending on int width
|
||||
IntMask = 1 shl IntShift - 1
|
||||
|
||||
var
|
||||
gOutOfMem: ref EOutOfMemory
|
||||
|
||||
proc raiseOutOfMem() {.noreturn.} =
|
||||
if gOutOfMem == nil: quit("out of memory; cannot even throw an exception")
|
||||
gOutOfMem.msg = "out of memory"
|
||||
raise gOutOfMem
|
||||
|
||||
when defined(boehmgc):
|
||||
when defined(windows):
|
||||
const boehmLib = "boehmgc.dll"
|
||||
else:
|
||||
const boehmLib = "/usr/lib/libgc.so.1"
|
||||
|
||||
proc boehmGC_disable {.importc: "GC_disable", dynlib: boehmLib.}
|
||||
proc boehmGC_enable {.importc: "GC_enable", dynlib: boehmLib.}
|
||||
proc boehmGCincremental {.
|
||||
importc: "GC_enable_incremental", dynlib: boehmLib.}
|
||||
proc boehmGCfullCollect {.importc: "GC_gcollect", dynlib: boehmLib.}
|
||||
proc boehmAlloc(size: int): pointer {.
|
||||
importc: "GC_malloc", dynlib: boehmLib.}
|
||||
proc boehmAllocAtomic(size: int): pointer {.
|
||||
importc: "GC_malloc_atomic", dynlib: boehmLib.}
|
||||
proc boehmRealloc(p: pointer, size: int): pointer {.
|
||||
importc: "GC_realloc", dynlib: boehmLib.}
|
||||
proc boehmDealloc(p: pointer) {.importc: "GC_free", dynlib: boehmLib.}
|
||||
|
||||
proc alloc(size: int): pointer =
|
||||
result = boehmAlloc(size)
|
||||
if result == nil: raiseOutOfMem()
|
||||
proc alloc0(size: int): pointer =
|
||||
result = alloc(size)
|
||||
zeroMem(result, size)
|
||||
proc realloc(p: Pointer, newsize: int): pointer =
|
||||
result = boehmRealloc(p, newsize)
|
||||
if result == nil: raiseOutOfMem()
|
||||
proc dealloc(p: Pointer) =
|
||||
boehmDealloc(p)
|
||||
|
||||
proc initGC() = nil
|
||||
|
||||
#boehmGCincremental()
|
||||
|
||||
proc GC_disable() = boehmGC_disable()
|
||||
proc GC_enable() = boehmGC_enable()
|
||||
proc GC_fullCollect() = boehmGCfullCollect()
|
||||
proc GC_setStrategy(strategy: TGC_Strategy) = nil
|
||||
proc GC_enableMarkAndSweep() = nil
|
||||
proc GC_disableMarkAndSweep() = nil
|
||||
proc GC_getStatistics(): string = return ""
|
||||
|
||||
proc getOccupiedMem(): int = return -1
|
||||
proc getFreeMem(): int = return -1
|
||||
proc getTotalMem(): int = return -1
|
||||
|
||||
proc newObj(typ: PNimType, size: int): pointer {.compilerproc.} =
|
||||
result = alloc(size)
|
||||
proc newSeq(typ: PNimType, len: int): pointer {.compilerproc.} =
|
||||
result = newObj(typ, addInt(mulInt(len, typ.base.size), GenericSeqSize))
|
||||
cast[PGenericSeq](result).len = len
|
||||
cast[PGenericSeq](result).space = len
|
||||
|
||||
proc growObj(old: pointer, newsize: int): pointer =
|
||||
result = realloc(old, newsize)
|
||||
|
||||
proc setStackBottom(theStackBottom: pointer) {.compilerproc.} = nil
|
||||
proc nimGCref(p: pointer) {.compilerproc, inline.} = nil
|
||||
proc nimGCunref(p: pointer) {.compilerproc, inline.} = nil
|
||||
|
||||
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.} =
|
||||
dest^ = src
|
||||
|
||||
include "system/cellsets"
|
||||
elif defined(nogc):
|
||||
proc alloc(size: int): pointer =
|
||||
result = c_malloc(size)
|
||||
if result == nil: raiseOutOfMem()
|
||||
proc alloc0(size: int): pointer =
|
||||
result = alloc(size)
|
||||
zeroMem(result, size)
|
||||
proc realloc(p: Pointer, newsize: int): pointer =
|
||||
result = c_realloc(p, newsize)
|
||||
if result == nil: raiseOutOfMem()
|
||||
proc dealloc(p: Pointer) =
|
||||
c_free(p)
|
||||
|
||||
proc initGC() = nil
|
||||
proc GC_disable() = nil
|
||||
proc GC_enable() = nil
|
||||
proc GC_fullCollect() = nil
|
||||
proc GC_setStrategy(strategy: TGC_Strategy) = nil
|
||||
proc GC_enableMarkAndSweep() = nil
|
||||
proc GC_disableMarkAndSweep() = nil
|
||||
proc GC_getStatistics(): string = return ""
|
||||
|
||||
proc getOccupiedMem(): int = return -1
|
||||
proc getFreeMem(): int = return -1
|
||||
proc getTotalMem(): int = return -1
|
||||
|
||||
proc newObj(typ: PNimType, size: int): pointer {.compilerproc.} =
|
||||
result = alloc0(size)
|
||||
proc newSeq(typ: PNimType, len: int): pointer {.compilerproc.} =
|
||||
result = newObj(typ, addInt(mulInt(len, typ.base.size), GenericSeqSize))
|
||||
cast[PGenericSeq](result).len = len
|
||||
cast[PGenericSeq](result).space = len
|
||||
proc growObj(old: pointer, newsize: int): pointer =
|
||||
result = realloc(old, newsize)
|
||||
# XXX BUG: we need realloc0 here, but C does not support this...
|
||||
|
||||
proc setStackBottom(theStackBottom: pointer) {.compilerproc.} = nil
|
||||
proc nimGCref(p: pointer) {.compilerproc, inline.} = nil
|
||||
proc nimGCunref(p: pointer) {.compilerproc, inline.} = nil
|
||||
|
||||
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.} =
|
||||
dest^ = src
|
||||
|
||||
include "system/cellsets"
|
||||
else:
|
||||
include "system/alloc"
|
||||
include "system/cellsets"
|
||||
assert(sizeof(TCell) == sizeof(TFreeCell))
|
||||
include "system/gc"
|
||||
|
||||
{.pop.}
|
||||
|
||||
|
||||
61
lib/system/profiler.nim
Normal file
61
lib/system/profiler.nim
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2008 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
# This file implements the Nimrod profiler. The profiler needs support by the
|
||||
# code generator.
|
||||
|
||||
type
|
||||
TProfileData {.compilerproc, final.} = object
|
||||
procname: cstring
|
||||
total: float
|
||||
|
||||
var
|
||||
profileData {.compilerproc.}: array [0..64*1024-1, TProfileData]
|
||||
|
||||
proc sortProfile(a: var array[0..64*1024-1, TProfileData], N: int) =
|
||||
# we use shellsort here; fast enough 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].total <= v.total:
|
||||
a[j] = a[j-h]
|
||||
j = j-h
|
||||
if j < h: break
|
||||
a[j] = v
|
||||
if h == 1: break
|
||||
|
||||
proc writeProfile() {.noconv.} =
|
||||
const filename = "profile_results"
|
||||
var i = 0
|
||||
var f: TFile
|
||||
var j = 1
|
||||
while openFile(f, filename & $j & ".txt"):
|
||||
closeFile(f)
|
||||
inc(j)
|
||||
if openFile(f, filename & $j & ".txt", fmWrite):
|
||||
var N = 0
|
||||
# we have to compute the actual length of the array:
|
||||
while profileData[N].procname != nil: inc(N)
|
||||
sortProfile(profileData, N)
|
||||
writeln(f, "total running time of each proc" &
|
||||
" (interpret these numbers relatively)")
|
||||
while profileData[i].procname != nil:
|
||||
write(f, profileData[i].procname)
|
||||
write(f, ": ")
|
||||
writeln(f, profileData[i].total)
|
||||
inc(i)
|
||||
closeFile(f)
|
||||
|
||||
addQuitProc(writeProfile)
|
||||
249
lib/system/repr.nim
Normal file
249
lib/system/repr.nim
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2006 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
# The generic ``repr`` procedure. It is an invaluable debugging tool.
|
||||
|
||||
#proc cstrToNimStrDummy(s: cstring): string {.inline.} =
|
||||
# result = cast[string](cstrToNimStr(s))
|
||||
|
||||
proc reprInt(x: int64): string {.compilerproc.} = return $x
|
||||
proc reprFloat(x: float): string {.compilerproc.} = return $x
|
||||
|
||||
proc reprPointer(x: pointer): string {.compilerproc.} =
|
||||
var buf: array [0..59, char]
|
||||
c_sprintf(buf, "%p", x)
|
||||
return $buf
|
||||
|
||||
proc reprStrAux(result: var string, s: string) =
|
||||
if cast[pointer](s) == nil:
|
||||
add result, "nil"
|
||||
return
|
||||
add result, reprPointer(cast[pointer](s)) & "\""
|
||||
for c in items(s):
|
||||
case c
|
||||
of '"': add result, "\\\""
|
||||
of '\\': add result, "\\\\" # BUGFIX: forgotten
|
||||
of '\10': add result, "\\10\"\n\"" # " \n " # better readability
|
||||
of '\128' .. '\255', '\0'..'\9', '\11'..'\31':
|
||||
add result, "\\" & reprInt(ord(c))
|
||||
else: result.add(c)
|
||||
add result, "\""
|
||||
|
||||
proc reprStr(s: string): string {.compilerproc.} =
|
||||
result = ""
|
||||
reprStrAux(result, s)
|
||||
|
||||
proc reprBool(x: bool): string {.compilerproc.} =
|
||||
if x: result = "true"
|
||||
else: result = "false"
|
||||
|
||||
proc reprChar(x: char): string {.compilerproc.} =
|
||||
result = "\'"
|
||||
case x
|
||||
of '"': add result, "\\\""
|
||||
of '\\': add result, "\\\\"
|
||||
of '\128' .. '\255', '\0'..'\31': add result, "\\" & reprInt(ord(x))
|
||||
else: add result, x
|
||||
add result, "\'"
|
||||
|
||||
proc reprEnum(e: int, typ: PNimType): string {.compilerproc.} =
|
||||
if e <% typ.node.len: # BUGFIX
|
||||
result = $typ.node.sons[e].name
|
||||
else:
|
||||
result = $e & " (invalid data!)"
|
||||
|
||||
type
|
||||
pbyteArray = ptr array[0.. 0xffff, byte]
|
||||
|
||||
proc addSetElem(result: var string, elem: int, typ: PNimType) =
|
||||
case typ.kind
|
||||
of tyEnum: add result, reprEnum(elem, typ)
|
||||
of tyBool: add result, reprBool(bool(elem))
|
||||
of tyChar: add result, reprChar(chr(elem))
|
||||
of tyRange: addSetElem(result, elem, typ.base)
|
||||
of tyInt..tyInt64: add result, reprInt(elem)
|
||||
else: # data corrupt --> inform the user
|
||||
add result, " (invalid data!)"
|
||||
|
||||
proc reprSetAux(result: var string, p: pointer, typ: PNimType) =
|
||||
# "typ.slots.len" field is for sets the "first" field
|
||||
var elemCounter = 0 # we need this flag for adding the comma at
|
||||
# the right places
|
||||
add result, "{"
|
||||
var u: int64
|
||||
case typ.size
|
||||
of 1: u = ze64(cast[ptr int8](p)^)
|
||||
of 2: u = ze64(cast[ptr int16](p)^)
|
||||
of 4: u = ze64(cast[ptr int32](p)^)
|
||||
of 8: u = cast[ptr int64](p)^
|
||||
else:
|
||||
var a = cast[pbyteArray](p)
|
||||
for i in 0 .. typ.size*8-1:
|
||||
if (ze(a[i div 8]) and (1 shl (i mod 8))) != 0:
|
||||
if elemCounter > 0: add result, ", "
|
||||
addSetElem(result, i+typ.node.len, typ.base)
|
||||
inc(elemCounter)
|
||||
if typ.size <= 8:
|
||||
for i in 0..sizeof(int64)*8-1:
|
||||
if (u and (1 shl i)) != 0:
|
||||
if elemCounter > 0: add result, ", "
|
||||
addSetElem(result, i+typ.node.len, typ.base)
|
||||
inc(elemCounter)
|
||||
add result, "}"
|
||||
|
||||
proc reprSet(p: pointer, typ: PNimType): string {.compilerproc.} =
|
||||
result = ""
|
||||
reprSetAux(result, p, typ)
|
||||
|
||||
type
|
||||
TReprClosure {.final.} = object # we cannot use a global variable here
|
||||
# as this wouldn't be thread-safe
|
||||
marked: TCellSet
|
||||
recdepth: int # do not recurse endless
|
||||
indent: int # indentation
|
||||
|
||||
proc initReprClosure(cl: var TReprClosure) =
|
||||
Init(cl.marked)
|
||||
cl.recdepth = -1 # default is to display everything!
|
||||
cl.indent = 0
|
||||
|
||||
proc deinitReprClosure(cl: var TReprClosure) =
|
||||
Deinit(cl.marked)
|
||||
|
||||
proc reprBreak(result: var string, cl: TReprClosure) =
|
||||
add result, "\n"
|
||||
for i in 0..cl.indent-1: add result, ' '
|
||||
|
||||
proc reprAux(result: var string, p: pointer, typ: PNimType,
|
||||
cl: var TReprClosure)
|
||||
|
||||
proc reprArray(result: var string, p: pointer, typ: PNimType,
|
||||
cl: var TReprClosure) =
|
||||
add result, "["
|
||||
var bs = typ.base.size
|
||||
for i in 0..typ.size div bs - 1:
|
||||
if i > 0: add result, ", "
|
||||
reprAux(result, cast[pointer](cast[TAddress](p) + i*bs), typ.base, cl)
|
||||
add result, "]"
|
||||
|
||||
proc reprSequence(result: var string, p: pointer, typ: PNimType,
|
||||
cl: var TReprClosure) =
|
||||
if p == nil:
|
||||
add result, "nil"
|
||||
return
|
||||
result.add(reprPointer(p) & "[")
|
||||
var bs = typ.base.size
|
||||
for i in 0..cast[PGenericSeq](p).len-1:
|
||||
if i > 0: add result, ", "
|
||||
reprAux(result, cast[pointer](cast[TAddress](p) + GenericSeqSize + i*bs),
|
||||
typ.Base, cl)
|
||||
add result, "]"
|
||||
|
||||
proc reprRecordAux(result: var string, p: pointer, n: ptr TNimNode,
|
||||
cl: var TReprClosure) =
|
||||
case n.kind
|
||||
of nkNone: assert(false)
|
||||
of nkSlot:
|
||||
add result, $n.name
|
||||
add result, " = "
|
||||
reprAux(result, cast[pointer](cast[TAddress](p) + n.offset), n.typ, cl)
|
||||
of nkList:
|
||||
for i in 0..n.len-1:
|
||||
if i > 0: add result, ",\n"
|
||||
reprRecordAux(result, p, n.sons[i], cl)
|
||||
of nkCase:
|
||||
var m = selectBranch(p, n)
|
||||
reprAux(result, cast[pointer](cast[TAddress](p) + n.offset), n.typ, cl)
|
||||
if m != nil: reprRecordAux(result, p, m, cl)
|
||||
|
||||
proc reprRecord(result: var string, p: pointer, typ: PNimType,
|
||||
cl: var TReprClosure) =
|
||||
add result, "["
|
||||
reprRecordAux(result, p, typ.node, cl)
|
||||
add result, "]"
|
||||
|
||||
proc reprRef(result: var string, p: pointer, typ: PNimType,
|
||||
cl: var TReprClosure) =
|
||||
# we know that p is not nil here:
|
||||
when defined(boehmGC) or defined(nogc):
|
||||
var cell = cast[PCell](p)
|
||||
else:
|
||||
var cell = usrToCell(p)
|
||||
add result, "ref " & reprPointer(p)
|
||||
if cell notin cl.marked:
|
||||
# only the address is shown:
|
||||
incl(cl.marked, cell)
|
||||
add result, " --> "
|
||||
reprAux(result, p, typ.base, cl)
|
||||
|
||||
proc reprAux(result: var string, p: pointer, typ: PNimType,
|
||||
cl: var TReprClosure) =
|
||||
if cl.recdepth == 0:
|
||||
add result, "..."
|
||||
return
|
||||
dec(cl.recdepth)
|
||||
case typ.kind
|
||||
of tySet: reprSetAux(result, p, typ)
|
||||
of tyArray: reprArray(result, p, typ, cl)
|
||||
of tyTuple, tyPureObject: reprRecord(result, p, typ, cl)
|
||||
of tyObject:
|
||||
var t = cast[ptr PNimType](p)^
|
||||
reprRecord(result, p, t, cl)
|
||||
of tyRef, tyPtr:
|
||||
assert(p != nil)
|
||||
if cast[ppointer](p)^ == nil: add result, "nil"
|
||||
else: reprRef(result, cast[ppointer](p)^, typ, cl)
|
||||
of tySequence:
|
||||
reprSequence(result, cast[ppointer](p)^, typ, cl)
|
||||
of tyInt: add result, $(cast[ptr int](p)^)
|
||||
of tyInt8: add result, $int(cast[ptr Int8](p)^)
|
||||
of tyInt16: add result, $int(cast[ptr Int16](p)^)
|
||||
of tyInt32: add result, $int(cast[ptr Int32](p)^)
|
||||
of tyInt64: add result, $(cast[ptr Int64](p)^)
|
||||
of tyFloat: add result, $(cast[ptr float](p)^)
|
||||
of tyFloat32: add result, $(cast[ptr float32](p)^)
|
||||
of tyFloat64: add result, $(cast[ptr float64](p)^)
|
||||
of tyEnum: add result, reprEnum(cast[ptr int](p)^, typ)
|
||||
of tyBool: add result, reprBool(cast[ptr bool](p)^)
|
||||
of tyChar: add result, reprChar(cast[ptr char](p)^)
|
||||
of tyString: reprStrAux(result, cast[ptr string](p)^)
|
||||
of tyCString: reprStrAux(result, $(cast[ptr cstring](p)^))
|
||||
of tyRange: reprAux(result, p, typ.base, cl)
|
||||
of tyProc, tyPointer:
|
||||
if cast[ppointer](p)^ == nil: add result, "nil"
|
||||
else: add result, reprPointer(cast[ppointer](p)^)
|
||||
else:
|
||||
add result, "(invalid data!)"
|
||||
inc(cl.recdepth)
|
||||
|
||||
proc reprOpenArray(p: pointer, length: int, elemtyp: PNimType): string {.
|
||||
compilerproc.} =
|
||||
var
|
||||
cl: TReprClosure
|
||||
initReprClosure(cl)
|
||||
result = "["
|
||||
var bs = elemtyp.size
|
||||
for i in 0..length - 1:
|
||||
if i > 0: add result, ", "
|
||||
reprAux(result, cast[pointer](cast[TAddress](p) + i*bs), elemtyp, cl)
|
||||
add result, "]"
|
||||
deinitReprClosure(cl)
|
||||
|
||||
proc reprAny(p: pointer, typ: PNimType): string =
|
||||
var
|
||||
cl: TReprClosure
|
||||
initReprClosure(cl)
|
||||
result = ""
|
||||
if typ.kind in {tyObject, tyPureObject, tyTuple, tyArray, tySet}:
|
||||
reprAux(result, p, typ, cl)
|
||||
else:
|
||||
var p = p
|
||||
reprAux(result, addr(p), typ, cl)
|
||||
add result, "\n"
|
||||
deinitReprClosure(cl)
|
||||
88
lib/system/sets.nim
Normal file
88
lib/system/sets.nim
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2006 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
# set handling
|
||||
|
||||
type
|
||||
TMyByte = int8
|
||||
TNimSet = array [0..4*2048-1, TMyByte]
|
||||
|
||||
# implementation:
|
||||
|
||||
proc countBits(n: int32): int {.exportc: "countBits".}
|
||||
# We use a prototype here, not in "cntbits.nim", because that is included
|
||||
# in math.nim too. So when linking with math.nim it'd give a duplicated
|
||||
# symbol error which we avoid by renaming here.
|
||||
|
||||
include "system/cntbits"
|
||||
|
||||
proc unionSets(res: var TNimSet, a, b: TNimSet, len: int) {.
|
||||
compilerproc, inline.} =
|
||||
for i in countup(0, len-1): res[i] = a[i] or b[i]
|
||||
|
||||
proc diffSets(res: var TNimSet, a, b: TNimSet, len: int) {.
|
||||
compilerproc, inline.} =
|
||||
for i in countup(0, len-1): res[i] = a[i] and not b[i]
|
||||
|
||||
proc intersectSets(res: var TNimSet, a, b: TNimSet, len: int) {.
|
||||
compilerproc, inline.} =
|
||||
for i in countup(0, len-1): res[i] = a[i] and b[i]
|
||||
|
||||
proc symdiffSets(res: var TNimSet, a, b: TNimSet, len: int) {.
|
||||
compilerproc, inline.} =
|
||||
for i in countup(0, len-1): res[i] = a[i] xor b[i]
|
||||
|
||||
proc containsSets(a, b: TNimSet, len: int): bool {.compilerproc, inline.} =
|
||||
# s1 <= s2 ?
|
||||
for i in countup(0, len-1):
|
||||
if (a[i] and not b[i]) != 0'i8: return false
|
||||
return true
|
||||
|
||||
proc containsSubsets(a, b: TNimSet, len: int): bool {.compilerproc, inline.} =
|
||||
# s1 < s2 ?
|
||||
result = false # assume they are equal
|
||||
for i in countup(0, len-1):
|
||||
if (a[i]) and not b[i]) != 0'i32: return false
|
||||
if a[i] != b[i]: result = true # they are not equal
|
||||
|
||||
proc equalSets(a, b: TNimSet, len: int): bool {.compilerproc, inline.} =
|
||||
for i in countup(0, len-1):
|
||||
if a[i] != b[i]: return false
|
||||
return true
|
||||
|
||||
proc cardSet(s: TNimSet, len: int): int {.compilerproc, inline.} =
|
||||
result = 0
|
||||
for i in countup(0, len-1):
|
||||
inc(result, countBits(ze(s[i])))
|
||||
|
||||
const
|
||||
WORD_SIZE = sizeof(TMyByte)*8
|
||||
|
||||
proc inSet(s: TNimSet, elem: int): bool {.compilerproc, inline.} =
|
||||
return (s[elem /% WORD_SIZE] and (1 shl (elem %% WORD_SIZE))) != 0
|
||||
|
||||
proc inclSets(s: var TNimSet, e: int) {.compilerproc, inline.} =
|
||||
s[e /% WORD_SIZE] = s[e /% WORD_SIZE] or toU8(1 shl (e %% WORD_SIZE))
|
||||
|
||||
proc inclRange(s: var TNimSet, first, last: int) {.compilerproc.} =
|
||||
# not very fast, but it is seldom used
|
||||
for i in countup(first, last): inclSets(s, i)
|
||||
|
||||
proc smallInclRange(s: var int, first, last: int) {.compilerproc.} =
|
||||
# not very fast, but it is seldom used
|
||||
for i in countup(first, last):
|
||||
s = s or (1 shl (i %% sizeof(int)*8))
|
||||
|
||||
proc exclSets(s: var TNimSet, e: int) {.compilerproc, inline.} =
|
||||
s[e /% WORD_SIZE] = s[e /% WORD_SIZE] and
|
||||
not toU8(1 shl (e %% WORD_SIZE))
|
||||
|
||||
proc smallContainsSubsets(a, b: int): bool {.compilerProc, inline.} =
|
||||
# not used by new code generator
|
||||
return ((a and not b) != 0) and (a != b)
|
||||
178
lib/system/sysio.nim
Normal file
178
lib/system/sysio.nim
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2009 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
|
||||
## Nimrod's standard IO library. It contains high-performance
|
||||
## routines for reading and writing data to (buffered) files or
|
||||
## TTYs.
|
||||
|
||||
{.push debugger:off .} # the user does not want to trace a part
|
||||
# of the standard library!
|
||||
|
||||
|
||||
proc fputs(c: cstring, f: TFile) {.importc: "fputs", noDecl.}
|
||||
proc fgets(c: cstring, n: int, f: TFile): cstring {.importc: "fgets", noDecl.}
|
||||
proc fgetc(stream: TFile): int {.importc: "fgetc", nodecl.}
|
||||
proc ungetc(c: cint, f: TFile) {.importc: "ungetc", nodecl.}
|
||||
proc putc(c: Char, stream: TFile) {.importc: "putc", nodecl.}
|
||||
proc fprintf(f: TFile, frmt: CString) {.importc: "fprintf", nodecl, varargs.}
|
||||
proc strlen(c: cstring): int {.importc: "strlen", nodecl.}
|
||||
|
||||
proc setvbuf(stream: TFile, buf: pointer, typ, size: cint): cint {.
|
||||
importc, nodecl.}
|
||||
|
||||
proc write(f: TFile, c: cstring) = fputs(c, f)
|
||||
|
||||
var
|
||||
IOFBF {.importc: "_IOFBF", nodecl.}: cint
|
||||
IONBF {.importc: "_IONBF", nodecl.}: cint
|
||||
|
||||
proc rawReadLine(f: TFile, result: var string) =
|
||||
# of course this could be optimized a bit; but IO is slow anyway...
|
||||
# and it was difficult to get this CORRECT with Ansi C's methods
|
||||
var
|
||||
c: cint
|
||||
setLen(result, 0) # reuse the buffer!
|
||||
while True:
|
||||
c = fgetc(f)
|
||||
if c < 0'i32: break # EOF
|
||||
if c == 10'i32: break # LF
|
||||
if c == 13'i32: # CR
|
||||
c = fgetc(f) # is the next char LF?
|
||||
if c != 10'i32: ungetc(c, f) # no, put the character back
|
||||
break
|
||||
add result, chr(int(c))
|
||||
|
||||
proc readLine(f: TFile): string =
|
||||
result = ""
|
||||
rawReadLine(f, result)
|
||||
|
||||
proc write(f: TFile, s: string) = fputs(s, f)
|
||||
proc write(f: TFile, i: int) =
|
||||
when sizeof(int) == 8:
|
||||
fprintf(f, "%lld", i)
|
||||
else:
|
||||
fprintf(f, "%ld", i)
|
||||
|
||||
proc write(f: TFile, b: bool) =
|
||||
if b: write(f, "true")
|
||||
else: write(f, "false")
|
||||
proc write(f: TFile, r: float) = fprintf(f, "%g", r)
|
||||
proc write(f: TFile, c: Char) = putc(c, f)
|
||||
proc write(f: TFile, a: openArray[string]) =
|
||||
for x in items(a): write(f, x)
|
||||
|
||||
proc readFile(filename: string): string =
|
||||
var f: TFile
|
||||
try:
|
||||
if openFile(f, filename):
|
||||
var len = getFileSize(f)
|
||||
if len < high(int):
|
||||
result = newString(int(len))
|
||||
if readBuffer(f, addr(result[0]), int(len)) != len:
|
||||
result = nil
|
||||
closeFile(f)
|
||||
else:
|
||||
result = nil
|
||||
except EIO:
|
||||
result = nil
|
||||
|
||||
proc EndOfFile(f: TFile): bool =
|
||||
# do not blame me; blame the ANSI C standard this is so brain-damaged
|
||||
var
|
||||
c: int
|
||||
c = fgetc(f)
|
||||
ungetc(c, f)
|
||||
return c == -1
|
||||
|
||||
proc writeln[Ty](f: TFile, x: Ty) =
|
||||
write(f, x)
|
||||
write(f, "\n")
|
||||
|
||||
proc writeln[Ty](f: TFile, x: openArray[Ty]) =
|
||||
for i in items(x): write(f, i)
|
||||
write(f, "\n")
|
||||
|
||||
proc rawEcho(x: string) {.inline, compilerproc.} = write(stdout, x)
|
||||
proc rawEchoNL() {.inline, compilerproc.} = write(stdout, "\n")
|
||||
|
||||
# interface to the C procs:
|
||||
proc fopen(filename, mode: CString): pointer {.importc: "fopen", noDecl.}
|
||||
|
||||
const
|
||||
FormatOpen: array [TFileMode, string] = ["rb", "wb", "w+b", "r+b", "ab"]
|
||||
#"rt", "wt", "w+t", "r+t", "at"
|
||||
# we always use binary here as for Nimrod the OS line ending
|
||||
# should not be translated.
|
||||
|
||||
|
||||
proc OpenFile(f: var TFile, filename: string,
|
||||
mode: TFileMode = fmRead,
|
||||
bufSize: int = -1): Bool =
|
||||
var
|
||||
p: pointer
|
||||
p = fopen(filename, FormatOpen[mode])
|
||||
result = (p != nil)
|
||||
f = cast[TFile](p)
|
||||
if bufSize > 0:
|
||||
if setvbuf(f, nil, IOFBF, bufSize) != 0'i32:
|
||||
raise newException(EOutOfMemory, "out of memory")
|
||||
elif bufSize == 0:
|
||||
discard setvbuf(f, nil, IONBF, 0)
|
||||
|
||||
proc fdopen(filehandle: TFileHandle, mode: cstring): TFile {.
|
||||
importc: pccHack & "fdopen", header: "<stdio.h>".}
|
||||
|
||||
proc openFile(f: var TFile, filehandle: TFileHandle, mode: TFileMode): bool =
|
||||
f = fdopen(filehandle, FormatOpen[mode])
|
||||
result = f != nil
|
||||
|
||||
# C routine that is used here:
|
||||
proc fread(buf: Pointer, size, n: int, f: TFile): int {.
|
||||
importc: "fread", noDecl.}
|
||||
proc fseek(f: TFile, offset: clong, whence: int): int {.
|
||||
importc: "fseek", noDecl.}
|
||||
proc ftell(f: TFile): int {.importc: "ftell", noDecl.}
|
||||
|
||||
proc fwrite(buf: Pointer, size, n: int, f: TFile): int {.
|
||||
importc: "fwrite", noDecl.}
|
||||
|
||||
proc readBuffer(f: TFile, buffer: pointer, len: int): int =
|
||||
result = fread(buffer, 1, len, f)
|
||||
|
||||
proc ReadBytes(f: TFile, a: var openarray[byte], start, len: int): int =
|
||||
result = readBuffer(f, addr(a[start]), len)
|
||||
|
||||
proc ReadChars(f: TFile, a: var openarray[char], start, len: int): int =
|
||||
result = readBuffer(f, addr(a[start]), len)
|
||||
|
||||
proc writeBytes(f: TFile, a: openarray[byte], start, len: int): int =
|
||||
var x = cast[ptr array[0..1000_000_000, byte]](a)
|
||||
result = writeBuffer(f, addr(x[start]), len)
|
||||
proc writeChars(f: TFile, a: openarray[char], start, len: int): int =
|
||||
var x = cast[ptr array[0..1000_000_000, byte]](a)
|
||||
result = writeBuffer(f, addr(x[start]), len)
|
||||
proc writeBuffer(f: TFile, buffer: pointer, len: int): int =
|
||||
result = fwrite(buffer, 1, len, f)
|
||||
|
||||
proc setFilePos(f: TFile, pos: int64) =
|
||||
if fseek(f, clong(pos), 0) != 0:
|
||||
raise newException(EIO, "cannot set file position")
|
||||
|
||||
proc getFilePos(f: TFile): int64 =
|
||||
result = ftell(f)
|
||||
if result < 0: raise newException(EIO, "cannot retrieve file position")
|
||||
|
||||
proc getFileSize(f: TFile): int64 =
|
||||
var oldPos = getFilePos(f)
|
||||
discard fseek(f, 0, 2) # seek the end of the file
|
||||
result = getFilePos(f)
|
||||
setFilePos(f, oldPos)
|
||||
|
||||
{.pop.}
|
||||
299
lib/system/sysstr.nim
Normal file
299
lib/system/sysstr.nim
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
#
|
||||
#
|
||||
# Nimrod's Runtime Library
|
||||
# (c) Copyright 2009 Andreas Rumpf
|
||||
#
|
||||
# See the file "copying.txt", included in this
|
||||
# distribution, for details about the copyright.
|
||||
#
|
||||
|
||||
# string & sequence handling procedures needed by the code generator
|
||||
|
||||
# strings are dynamically resized, have a length field
|
||||
# and are zero-terminated, so they can be casted to C
|
||||
# strings easily
|
||||
# we don't use refcounts because that's a behaviour
|
||||
# the programmer may not want
|
||||
|
||||
# implementation:
|
||||
|
||||
proc resize(old: int): int {.inline.} =
|
||||
if old <= 0: return 4
|
||||
elif old < 65536: return old * 2
|
||||
else: return old * 3 div 2 # for large arrays * 3/2 is better
|
||||
|
||||
proc cmpStrings(a, b: NimString): int {.inline, compilerProc.} =
|
||||
if a == b: return 0
|
||||
if a == nil: return -1
|
||||
if b == nil: return 1
|
||||
return c_strcmp(a.data, b.data)
|
||||
|
||||
proc eqStrings(a, b: NimString): bool {.inline, compilerProc.} =
|
||||
if a == b: return true
|
||||
if a == nil or b == nil: return false
|
||||
return a.len == b.len and
|
||||
c_memcmp(a.data, b.data, a.len * sizeof(char)) == 0'i32
|
||||
|
||||
proc rawNewString(space: int): NimString {.compilerProc.} =
|
||||
var s = space
|
||||
if s < 8: s = 7
|
||||
result = cast[NimString](newObj(addr(strDesc), sizeof(TGenericSeq) +
|
||||
(s+1) * sizeof(char)))
|
||||
result.space = s
|
||||
|
||||
proc mnewString(len: int): NimString {.exportc.} =
|
||||
#c_fprintf(c_stdout, "[NEWSTRING] len: %ld\n", len)
|
||||
result = rawNewString(len)
|
||||
result.len = len
|
||||
|
||||
proc toNimStr(str: CString, len: int): NimString {.compilerProc.} =
|
||||
result = rawNewString(len)
|
||||
result.len = len
|
||||
c_memcpy(result.data, str, (len+1) * sizeof(Char))
|
||||
result.data[len] = '\0' # readline relies on this!
|
||||
|
||||
proc cstrToNimstr(str: CString): NimString {.compilerProc.} =
|
||||
return toNimstr(str, c_strlen(str))
|
||||
|
||||
proc copyString(src: NimString): NimString {.compilerProc.} =
|
||||
if src == nil: return nil
|
||||
result = rawNewString(src.space)
|
||||
result.len = src.len
|
||||
c_memcpy(result.data, src.data, (src.len + 1) * sizeof(Char))
|
||||
|
||||
proc hashString(s: string): int {.compilerproc.} =
|
||||
# the compiler needs exactly the same hash function!
|
||||
# this used to be used for efficient generation of string case statements
|
||||
var h = 0
|
||||
for i in 0..Len(s)-1:
|
||||
h = h +% Ord(s[i])
|
||||
h = h +% h shl 10
|
||||
h = h xor (h shr 6)
|
||||
h = h +% h shl 3
|
||||
h = h xor (h shr 11)
|
||||
h = h +% h shl 15
|
||||
result = h
|
||||
|
||||
# copy(s: string, start = 0): string
|
||||
# {.extern: "copyStr", noDecl, noSideEffect.}
|
||||
# copy(s: string, start, len: int): string
|
||||
# {.extern: "copyStrLen", noDecl, noSideEffect.}
|
||||
#
|
||||
# setLength(var s: string, newlen: int)
|
||||
# {.extern: "setLengthStr", noDecl, noSideEffect.}
|
||||
|
||||
proc copyStrLast(s: NimString, start, last: int): NimString {.exportc.} =
|
||||
var
|
||||
len: int
|
||||
if start >= s.len: return mnewString(0) # BUGFIX
|
||||
if last >= s.len:
|
||||
len = s.len - start # - 1 + 1
|
||||
else:
|
||||
len = last - start + 1
|
||||
result = rawNewString(len)
|
||||
result.len = len
|
||||
c_memcpy(result.data, addr(s.data[start]), len * sizeof(Char))
|
||||
result.data[len] = '\0'
|
||||
|
||||
proc copyStr(s: NimString, start: int): NimString {.exportc.} =
|
||||
return copyStrLast(s, start, s.len-1)
|
||||
|
||||
proc addChar(s: NimString, c: char): NimString {.compilerProc.} =
|
||||
result = s
|
||||
if result.len >= result.space:
|
||||
result.space = resize(result.space)
|
||||
result = cast[NimString](growObj(result,
|
||||
sizeof(TGenericSeq) + (result.space+1) * sizeof(char)))
|
||||
#var space = resize(result.space)
|
||||
#result = rawNewString(space)
|
||||
#copyMem(result, s, s.len * sizeof(char) + sizeof(TGenericSeq))
|
||||
#result.space = space
|
||||
result.data[result.len] = c
|
||||
result.data[result.len+1] = '\0'
|
||||
inc(result.len)
|
||||
|
||||
# These routines should be used like following:
|
||||
# <Nimrod code>
|
||||
# s &= "hallo " & name & " how do you feel?"
|
||||
#
|
||||
# <generated C code>
|
||||
# {
|
||||
# s = resizeString(s, 6 + name->len + 17);
|
||||
# appendString(s, strLit1);
|
||||
# appendString(s, strLit2);
|
||||
# appendString(s, strLit3);
|
||||
# }
|
||||
#
|
||||
# <Nimrod code>
|
||||
# s = "hallo " & name & " how do you feel?"
|
||||
#
|
||||
# <generated C code>
|
||||
# {
|
||||
# string tmp0;
|
||||
# tmp0 = rawNewString(6 + name->len + 17);
|
||||
# appendString(s, strLit1);
|
||||
# appendString(s, strLit2);
|
||||
# appendString(s, strLit3);
|
||||
# s = tmp0;
|
||||
# }
|
||||
#
|
||||
# <Nimrod code>
|
||||
# s = ""
|
||||
#
|
||||
# <generated C code>
|
||||
# s = rawNewString(0);
|
||||
|
||||
proc resizeString(dest: NimString, addlen: int): NimString {.compilerproc.} =
|
||||
if dest.len + addLen + 1 <= dest.space: # BUGFIX: this is horrible!
|
||||
result = dest
|
||||
else: # slow path:
|
||||
var sp = max(resize(dest.space), dest.len + addLen + 1)
|
||||
result = cast[NimString](growObj(dest, sizeof(TGenericSeq) +
|
||||
(sp+1) * sizeof(Char)))
|
||||
result.space = sp
|
||||
#result = rawNewString(sp)
|
||||
#copyMem(result, dest, dest.len * sizeof(char) + sizeof(TGenericSeq))
|
||||
# DO NOT UPDATE LEN YET: dest.len = newLen
|
||||
|
||||
proc appendString(dest, src: NimString) {.compilerproc, inline.} =
|
||||
c_memcpy(addr(dest.data[dest.len]), src.data, (src.len + 1) * sizeof(Char))
|
||||
inc(dest.len, src.len)
|
||||
|
||||
proc appendChar(dest: NimString, c: char) {.compilerproc, inline.} =
|
||||
dest.data[dest.len] = c
|
||||
dest.data[dest.len+1] = '\0'
|
||||
inc(dest.len)
|
||||
|
||||
proc setLengthStr(s: NimString, newLen: int): NimString {.compilerProc.} =
|
||||
var n = max(newLen, 0)
|
||||
if n <= s.space:
|
||||
result = s
|
||||
else:
|
||||
result = resizeString(s, n)
|
||||
result.len = n
|
||||
result.data[n] = '\0'
|
||||
|
||||
# ----------------- sequences ----------------------------------------------
|
||||
|
||||
proc incrSeq(seq: PGenericSeq, elemSize: int): PGenericSeq {.compilerProc.} =
|
||||
# increments the length by one:
|
||||
# this is needed for supporting ``add``;
|
||||
#
|
||||
# add(seq, x) generates:
|
||||
# seq = incrSeq(seq, sizeof(x));
|
||||
# seq[seq->len-1] = x;
|
||||
when false:
|
||||
# broken version:
|
||||
result = seq
|
||||
if result.len >= result.space:
|
||||
var s = resize(result.space)
|
||||
result = cast[PGenericSeq](newSeq(extGetCellType(seq), s))
|
||||
genericSeqAssign(result, seq, XXX)
|
||||
#copyMem(result, seq, seq.len * elemSize + GenericSeqSize)
|
||||
inc(result.len)
|
||||
else:
|
||||
result = seq
|
||||
if result.len >= result.space:
|
||||
result.space = resize(result.space)
|
||||
result = cast[PGenericSeq](growObj(result, elemSize * result.space +
|
||||
GenericSeqSize))
|
||||
# set new elements to zero:
|
||||
#var s = cast[TAddress](result)
|
||||
#zeroMem(cast[pointer](s + GenericSeqSize + (result.len * elemSize)),
|
||||
# (result.space - result.len) * elemSize)
|
||||
# for i in len .. space-1:
|
||||
# seq->data[i] = 0
|
||||
inc(result.len)
|
||||
|
||||
proc setLengthSeq(seq: PGenericSeq, elemSize, newLen: int): PGenericSeq {.
|
||||
compilerProc.} =
|
||||
when false:
|
||||
# broken version:
|
||||
result = seq
|
||||
if result.space < newLen:
|
||||
var s = max(resize(result.space), newLen)
|
||||
result = cast[PGenericSeq](newSeq(extGetCellType(seq), s))
|
||||
result.len = newLen
|
||||
else:
|
||||
result = seq
|
||||
if result.space < newLen:
|
||||
result.space = max(resize(result.space), newLen)
|
||||
result = cast[PGenericSeq](growObj(result, elemSize * result.space +
|
||||
GenericSeqSize))
|
||||
elif newLen < result.len:
|
||||
# we need to decref here, otherwise the GC leaks!
|
||||
when not defined(boehmGC) and not defined(nogc):
|
||||
for i in newLen..result.len-1:
|
||||
forAllChildrenAux(cast[pointer](cast[TAddress](result) +%
|
||||
GenericSeqSize +% (i*%elemSize)),
|
||||
extGetCellType(result).base, waZctDecRef)
|
||||
# and set the memory to nil:
|
||||
zeroMem(cast[pointer](cast[TAddress](result) +% GenericSeqSize +%
|
||||
(newLen*%elemSize)), (result.len-%newLen) *% elemSize)
|
||||
result.len = newLen
|
||||
|
||||
# --------------- other string routines ----------------------------------
|
||||
proc nimIntToStr(x: int): string {.compilerproc.} =
|
||||
result = newString(sizeof(x)*4)
|
||||
var i = 0
|
||||
var y = x
|
||||
while True:
|
||||
var d = y div 10
|
||||
result[i] = chr(abs(int(y - d*10)) + ord('0'))
|
||||
inc(i)
|
||||
y = d
|
||||
if y == 0: break
|
||||
if x < 0:
|
||||
result[i] = '-'
|
||||
inc(i)
|
||||
setLen(result, i)
|
||||
# mirror the string:
|
||||
for j in 0..i div 2 - 1:
|
||||
swap(result[j], result[i-j-1])
|
||||
|
||||
proc nimFloatToStr(x: float): string {.compilerproc.} =
|
||||
var buf: array [0..59, char]
|
||||
c_sprintf(buf, "%#g", x)
|
||||
return $buf
|
||||
|
||||
proc nimInt64ToStr(x: int64): string {.compilerproc.} =
|
||||
# we don't rely on C's runtime here as some C compiler's
|
||||
# int64 support is weak
|
||||
result = newString(sizeof(x)*4)
|
||||
var i = 0
|
||||
var y = x
|
||||
while True:
|
||||
var d = y div 10
|
||||
result[i] = chr(abs(int(y - d*10)) + ord('0'))
|
||||
inc(i)
|
||||
y = d
|
||||
if y == 0: break
|
||||
if x < 0:
|
||||
result[i] = '-'
|
||||
inc(i)
|
||||
setLen(result, i)
|
||||
# mirror the string:
|
||||
for j in 0..i div 2 - 1:
|
||||
swap(result[j], result[i-j-1])
|
||||
|
||||
proc nimBoolToStr(x: bool): string {.compilerproc.} =
|
||||
return if x: "true" else: "false"
|
||||
|
||||
proc nimCharToStr(x: char): string {.compilerproc.} =
|
||||
result = newString(1)
|
||||
result[0] = x
|
||||
|
||||
proc binaryStrSearch(x: openarray[string], y: string): int {.compilerproc.} =
|
||||
var
|
||||
a = 0
|
||||
b = len(x)
|
||||
while a < b:
|
||||
var mid = (a + b) div 2
|
||||
if x[mid] < y:
|
||||
a = mid + 1
|
||||
else:
|
||||
b = mid
|
||||
if (a < len(x)) and (x[a] == y):
|
||||
return a
|
||||
else:
|
||||
return -1
|
||||
Loading…
Add table
Add a link
Reference in a new issue