diff options
Diffstat (limited to 'nimlib/system')
-rwxr-xr-x | nimlib/system/alloc.nim | 596 | ||||
-rwxr-xr-x | nimlib/system/ansi_c.nim | 105 | ||||
-rwxr-xr-x | nimlib/system/arithm.nim | 316 | ||||
-rwxr-xr-x | nimlib/system/assign.nim | 120 | ||||
-rwxr-xr-x | nimlib/system/cellsets.nim | 196 | ||||
-rwxr-xr-x | nimlib/system/cntbits.nim | 12 | ||||
-rwxr-xr-x | nimlib/system/debugger.nim | 500 | ||||
-rwxr-xr-x | nimlib/system/dyncalls.nim | 127 | ||||
-rwxr-xr-x | nimlib/system/ecmasys.nim | 531 | ||||
-rwxr-xr-x | nimlib/system/excpt.nim | 285 | ||||
-rwxr-xr-x | nimlib/system/gc.nim | 647 | ||||
-rwxr-xr-x | nimlib/system/hti.nim | 58 | ||||
-rwxr-xr-x | nimlib/system/mm.nim | 189 | ||||
-rwxr-xr-x | nimlib/system/profiler.nim | 61 | ||||
-rwxr-xr-x | nimlib/system/repr.nim | 249 | ||||
-rwxr-xr-x | nimlib/system/sets.nim | 28 | ||||
-rwxr-xr-x | nimlib/system/sysio.nim | 184 | ||||
-rwxr-xr-x | nimlib/system/sysstr.nim | 289 |
18 files changed, 4493 insertions, 0 deletions
diff --git a/nimlib/system/alloc.nim b/nimlib/system/alloc.nim new file mode 100755 index 000000000..95feff854 --- /dev/null +++ b/nimlib/system/alloc.nim @@ -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)) + diff --git a/nimlib/system/ansi_c.nim b/nimlib/system/ansi_c.nim new file mode 100755 index 000000000..e9300949b --- /dev/null +++ b/nimlib/system/ansi_c.nim @@ -0,0 +1,105 @@ +# +# +# 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, noSideEffect, importc: "strcmp".} +proc c_memcmp(a, b: CString, size: cint): cint {. + nodecl, noSideEffect, importc: "memcmp".} +proc c_memcpy(a, b: CString, size: cint) {.nodecl, importc: "memcpy".} +proc c_strlen(a: CString): int {.nodecl, noSideEffect, 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} + diff --git a/nimlib/system/arithm.nim b/nimlib/system/arithm.nim new file mode 100755 index 000000000..f097ee794 --- /dev/null +++ b/nimlib/system/arithm.nim @@ -0,0 +1,316 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2009 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, noreturn.} = + # a single proc to reduce code size to a minimum + raise newException(EOverflow, "over- or underflow") + +proc raiseDivByZero {.compilerproc, noinline, noreturn.} = + 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() diff --git a/nimlib/system/assign.nim b/nimlib/system/assign.nim new file mode 100755 index 000000000..44d2e5c64 --- /dev/null +++ b/nimlib/system/assign.nim @@ -0,0 +1,120 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2009 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 diff --git a/nimlib/system/cellsets.nim b/nimlib/system/cellsets.nim new file mode 100755 index 000000000..0ce83864c --- /dev/null +++ b/nimlib/system/cellsets.nim @@ -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 + diff --git a/nimlib/system/cntbits.nim b/nimlib/system/cntbits.nim new file mode 100755 index 000000000..281b96dd0 --- /dev/null +++ b/nimlib/system/cntbits.nim @@ -0,0 +1,12 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2006 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + + + + diff --git a/nimlib/system/debugger.nim b/nimlib/system/debugger.nim new file mode 100755 index 000000000..01d8bd8a2 --- /dev/null +++ b/nimlib/system/debugger.nim @@ -0,0 +1,500 @@ +# +# +# 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 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 open(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) + close(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) + close(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 diff --git a/nimlib/system/dyncalls.nim b/nimlib/system/dyncalls.nim new file mode 100755 index 000000000..0946ee355 --- /dev/null +++ b/nimlib/system/dyncalls.nim @@ -0,0 +1,127 @@ +# +# +# 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) + if result == nil: nimLoadLibraryError($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) + if result == nil: nimLoadLibraryError($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, name: cstring): TProcAddr = + var + nss: NSSymbol + nss = NSLookupSymbolInModule(NSModule(lib), name) + result = TProcAddr(NSAddressOfSymbol(nss)) + if result == nil: nimLoadLibraryError($name) + +else: + {.error: "no implementation for dyncalls".} diff --git a/nimlib/system/ecmasys.nim b/nimlib/system/ecmasys.nim new file mode 100755 index 000000000..c0d0a5fd6 --- /dev/null +++ b/nimlib/system/ecmasys.nim @@ -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) diff --git a/nimlib/system/excpt.nim b/nimlib/system/excpt.nim new file mode 100755 index 000000000..293491fe9 --- /dev/null +++ b/nimlib/system/excpt.nim @@ -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 diff --git a/nimlib/system/gc.nim b/nimlib/system/gc.nim new file mode 100755 index 000000000..da8f75768 --- /dev/null +++ b/nimlib/system/gc.nim @@ -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 cells on the stack, so turn them to cells: + var cell = usrToCell(p) + var c = cast[TAddress](cell) + if c >% PageSize and (c and (MemAlign-1)) == 0: + # 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() diff --git a/nimlib/system/hti.nim b/nimlib/system/hti.nim new file mode 100755 index 000000000..3343000ae --- /dev/null +++ b/nimlib/system/hti.nim @@ -0,0 +1,58 @@ +# +# +# 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 + # many enum fields are not used at runtime + TNimKind = enum + tyNone, tyBool, tyChar, + tyEmpty, tyArrayConstr, tyNil, tyExpr, tyStmt, tyTypeDesc, + tyGenericInvokation, # ``T[a, b]`` for types to invoke + tyGenericBody, # ``T[a, b, body]`` last parameter is the body + tyGenericInst, # ``T[a, b, realInstance]`` instantiated generic type + tyGenericParam, # ``a`` in the example + tyDistinct, # distinct type + tyEnum, + tyOrdinal, + tyArray, + tyObject, + tyTuple, + tySet, + tyRange, + tyPtr, tyRef, + tyVar, + tySequence, + tyProc, + tyPointer, tyOpenArray, + tyString, tyCString, tyForward, + tyInt, tyInt8, tyInt16, tyInt32, tyInt64, + tyFloat, tyFloat32, tyFloat64, tyFloat128, + tyPureObject # 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 diff --git a/nimlib/system/mm.nim b/nimlib/system/mm.nim new file mode 100755 index 000000000..76b5d83bd --- /dev/null +++ b/nimlib/system/mm.nim @@ -0,0 +1,189 @@ +# +# +# 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): + include "system/alloc" + + when false: + 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 getOccupiedMem(): int = return -1 + proc getFreeMem(): int = return -1 + proc getTotalMem(): int = return -1 + + 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 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) + + 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.} + + diff --git a/nimlib/system/profiler.nim b/nimlib/system/profiler.nim new file mode 100755 index 000000000..b87b30d4a --- /dev/null +++ b/nimlib/system/profiler.nim @@ -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 open(f, filename & $j & ".txt"): + close(f) + inc(j) + if open(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) + close(f) + +addQuitProc(writeProfile) diff --git a/nimlib/system/repr.nim b/nimlib/system/repr.nim new file mode 100755 index 000000000..e340f1d7c --- /dev/null +++ b/nimlib/system/repr.nim @@ -0,0 +1,249 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2009 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) diff --git a/nimlib/system/sets.nim b/nimlib/system/sets.nim new file mode 100755 index 000000000..f9f3eb32b --- /dev/null +++ b/nimlib/system/sets.nim @@ -0,0 +1,28 @@ +# +# +# Nimrod's Runtime Library +# (c) Copyright 2009 Andreas Rumpf +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +# set handling + +type + TNimSet = array [0..4*2048-1, int8] + +proc countBits32(n: int32): int {.compilerproc.} = + var v = n + v = v -% ((v shr 1'i32) and 0x55555555'i32) + v = (v and 0x33333333'i32) +% ((v shr 2'i32) and 0x33333333'i32) + result = ((v +% (v shr 4'i32) and 0xF0F0F0F'i32) *% 0x1010101'i32) shr 24'i32 + +proc countBits64(n: int64): int {.compilerproc.} = + result = countBits32(toU32(n and 0xffff'i64)) + + countBits32(toU32(n shr 16'i64)) + +proc cardSet(s: TNimSet, len: int): int {.compilerproc.} = + result = 0 + for i in countup(0, len-1): + inc(result, countBits32(int32(ze(s[i])))) diff --git a/nimlib/system/sysio.nim b/nimlib/system/sysio.nim new file mode 100755 index 000000000..8b6d0e285 --- /dev/null +++ b/nimlib/system/sysio.nim @@ -0,0 +1,184 @@ +# +# +# 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): cint {.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 + setLen(result, 0) # reuse the buffer! + while True: + var 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) + +#{.error: "for debugging.".} + +proc readFile(filename: string): string = + var f: TFile + try: + if open(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 + close(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 = fgetc(f) + ungetc(c, f) + return c == -1'i32 + +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 Open(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 open(f: var TFile, filehandle: TFileHandle, mode: TFileMode): bool = + f = fdopen(filehandle, FormatOpen[mode]) + result = f != nil + +proc OpenFile(f: var TFile, filename: string, + mode: TFileMode = fmRead, + bufSize: int = -1): Bool = + result = open(f, filename, mode, bufSize) + +proc openFile(f: var TFile, filehandle: TFileHandle, mode: TFileMode): bool = + result = open(f, filehandle, mode) + +# 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.} diff --git a/nimlib/system/sysstr.nim b/nimlib/system/sysstr.nim new file mode 100755 index 000000000..808941c06 --- /dev/null +++ b/nimlib/system/sysstr.nim @@ -0,0 +1,289 @@ +# +# +# 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 + +proc copyStrLast(s: NimString, start, last: int): NimString {.exportc.} = + var start = max(start, 0) + var len = min(last, s.len-1) - start + 1 + if len > 0: + result = rawNewString(len) + result.len = len + c_memcpy(result.data, addr(s.data[start]), len * sizeof(Char)) + result.data[len] = '\0' + else: + result = mnewString(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 |