version 0.7.8

This commit is contained in:
Andreas Rumpf 2009-05-08 16:36:06 +02:00
commit db4f617afc
92 changed files with 3088 additions and 3477 deletions

View file

@ -7,29 +7,27 @@
# distribution, for details about the copyright.
#
# Low level allocator for Nimrod.
# Low level allocator for Nimrod. Has been designed to support the GC.
# TODO:
# - eliminate "used" field
# - make searching for block O(1)
proc raiseOutOfMem {.noinline.} =
assert false
quit(1)
# ------------ platform specific chunk allocation code -----------------------
when defined(posix):
const # XXX: make these variables for portability?
const
PROT_READ = 1 # page can be read
PROT_WRITE = 2 # page can be written
MAP_PRIVATE = 2 # Changes are private
when defined(linux):
when defined(linux) or defined(aix):
const MAP_ANONYMOUS = 0x20 # don't use a file
elif defined(macosx):
elif defined(macosx) or defined(bsd):
const MAP_ANONYMOUS = 0x1000
elif defined(solaris):
const MAP_ANONYMOUS = 0x100
else:
const MAP_ANONYMOUS = 0 # other operating systems may not know about this
{.error: "Port memory manager to your platform".}
proc mmap(adr: pointer, len: int, prot, flags, fildes: cint,
off: int): pointer {.header: "<sys/mman.h>".}
@ -43,7 +41,7 @@ when defined(posix):
raiseOutOfMem()
proc osDeallocPages(p: pointer, size: int) {.inline} =
munmap(p, size)
when reallyOsDealloc: munmap(p, size)
elif defined(windows):
const
@ -69,7 +67,7 @@ elif defined(windows):
proc osDeallocPages(p: pointer, size: int) {.inline.} =
# according to Microsoft, 0 is the only correct value here:
VirtualFree(p, 0, MEM_RELEASE)
when reallyOsDealloc: VirtualFree(p, 0, MEM_RELEASE)
else:
{.error: "Port memory manager to your platform".}
@ -77,48 +75,14 @@ else:
# --------------------- end of non-portable code -----------------------------
# We manage *chunks* of memory. Each chunk is a multiple of the page size.
# The page size may or may not the operating system's page size. Each chunk
# starts at an address that is divisible by the page size. Chunks that are
# bigger than ``ChunkOsReturn`` are returned back to the operating system
# immediately.
# Guess the page size of the system; if it is the
# wrong value, performance may be worse (this is not
# for sure though), but GC still works; must be a power of two!
when defined(linux) or defined(windows) or defined(macosx):
const
PageShift = 12
PageSize = 1 shl PageShift # on 32 bit systems 4096
else:
{.error: "unkown 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
PageMask = PageSize-1
SmallChunkSize = PageSize # * 4
MemAlign = 8 # minimal memory block that can be allocated
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 (!)
ChunkOsReturn = 64 * PageSize
ChunkOsReturn = 256 * PageSize
InitialMemoryRequest = ChunkOsReturn div 2 # < ChunkOsReturn!
# Compile time options:
coalescRight = true
coalescLeft = true
const
TrunkShift = 9
BitsPerTrunk = 1 shl TrunkShift # needs to be a 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
SmallChunkSize = PageSize
type
PTrunk = ptr TTrunk
@ -132,10 +96,11 @@ type
data: TTrunkBuckets
type
TAlignType = float
TAlignType = biggestFloat
TFreeCell {.final, pure.} = object
next: ptr TFreeCell # next free cell in chunk (overlaid with refcount)
zeroField: pointer # nil means cell is not used (overlaid with typ field)
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
@ -155,15 +120,20 @@ type
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.} = return ((-x) and (v-1)) +% x
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
@ -180,7 +150,7 @@ type
TAllocator {.final, pure.} = object
llmem: PLLChunk
currMem, maxMem: int # currently and maximum used memory size (allocated from OS)
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
@ -277,8 +247,10 @@ 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
@ -312,11 +284,28 @@ proc freeOsChunks(a: var TAllocator, p: pointer, size: int) =
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
@ -326,6 +315,7 @@ proc ListAdd[T](head: var T, c: T) {.inline.} =
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
@ -344,13 +334,22 @@ proc isSmallChunk(c: PChunk): bool {.inline.} =
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)
@ -360,6 +359,7 @@ proc freeBigChunk(a: var TAllocator, c: PBigChunk) =
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)
@ -367,6 +367,8 @@ proc freeBigChunk(a: var TAllocator, c: PBigChunk) =
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:
@ -374,11 +376,16 @@ proc freeBigChunk(a: var TAllocator, c: PBigChunk) =
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 # XXX
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)
@ -386,26 +393,32 @@ proc splitChunk(a: var TAllocator, c: PBigChunk, size: int) =
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:
splitChunk(a, result, 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
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)
@ -419,8 +432,11 @@ proc getCellSize(p: pointer): int {.inline.} =
var c = pageAddr(p)
result = c.size
proc alloc(a: var TAllocator, requestedSize: int): pointer =
var size = roundup(max(requestedSize, sizeof(TFreeCell)), MemAlign)
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
@ -436,8 +452,11 @@ proc alloc(a: var TAllocator, requestedSize: int): pointer =
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)
@ -445,9 +464,10 @@ proc alloc(a: var TAllocator, requestedSize: int): pointer =
inc(c.acc, size)
else:
result = c.freeList
assert(c.freeList.zeroField == nil)
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:
@ -458,16 +478,10 @@ proc alloc(a: var TAllocator, requestedSize: int): pointer =
assert c.next == nil
assert c.size == size
result = addr(c.data)
cast[ptr TFreeCell](result).zeroField = cast[ptr TFreeCell](1) # make it != nil
#echo("setting to one: ", $cast[TAddress](addr(cast[ptr TFreeCell](result).zeroField)))
assert((cast[TAddress](result) and (MemAlign-1)) == 0)
assert(isAccessible(result))
proc contains(list, x: PSmallChunk): bool =
var it = list
while it != nil:
if it == x: return true
it = it.next
proc dealloc(a: var TAllocator, p: pointer) =
proc rawDealloc(a: var TAllocator, p: pointer) =
var c = pageAddr(p)
if isSmallChunk(c):
# `p` is within a small chunk:
@ -475,8 +489,8 @@ proc dealloc(a: var TAllocator, p: pointer) =
var s = c.size
var f = cast[ptr TFreeCell](p)
#echo("setting to nil: ", $cast[TAddress](addr(f.zeroField)))
assert(f.zeroField != nil)
f.zeroField = nil
assert(f.zeroField != 0)
f.zeroField = 0
f.next = c.freeList
c.freeList = f
# check if it is not in the freeSmallChunks[s] list:
@ -495,23 +509,64 @@ proc dealloc(a: var TAllocator, p: pointer) =
# free big chunk
freeBigChunk(a, cast[PBigChunk](c))
proc realloc(a: var TAllocator, p: pointer, size: int): pointer =
# could be made faster, but this is unnecessary, the GC does not use it anyway
result = alloc(a, size)
copyMem(result, p, getCellSize(p))
dealloc(a, p)
proc isAllocatedPtr(a: TAllocator, p: pointer): bool =
if isAccessible(p):
var c = pageAddr(p)
if not chunkUnused(c):
if isSmallChunk(c):
result = (cast[TAddress](p) -% cast[TAddress](c) -%
smallChunkOverhead()) %% c.size == 0 and
cast[ptr TFreeCell](p).zeroField != nil
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)
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

View file

@ -1,7 +1,7 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2006 Andreas Rumpf
# (c) Copyright 2009 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@ -82,3 +82,7 @@ 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.}

View file

@ -43,12 +43,8 @@ proc genericAssign(dest, src: Pointer, mt: PNimType) =
x^ = nil
return
assert(dest != nil)
when defined(boehmGC):
unsureAsgnRef(cast[ppointer](dest),
newObj(seq.len * mt.base.size + GenericSeqSize))
else:
unsureAsgnRef(cast[ppointer](dest),
newObj(mt, seq.len * mt.base.size + GenericSeqSize))
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(

View file

@ -103,71 +103,77 @@ proc cgiError*(msg: string) {.noreturn.} =
e.msg = msg
raise e
proc getEncodedData(allowedMethods: set[TRequestMethod]): string =
case getenv("REQUEST_METHOD")
of "POST":
if methodPost notin allowedMethods:
cgiError("'REQUEST_METHOD' 'POST' is not supported")
var L = parseInt(getenv("CONTENT_LENGTH"))
result = newString(L)
if readBuffer(stdin, addr(result[0]), L) != L:
cgiError("cannot read from stdin")
of "GET":
if methodGet notin allowedMethods:
cgiError("'REQUEST_METHOD' 'GET' is not supported")
result = getenv("QUERY_STRING")
else:
if methodNone notin allowedMethods:
cgiError("'REQUEST_METHOD' must be 'POST' or 'GET'")
iterator decodeData*(allowedMethods: set[TRequestMethod] =
{methodNone, methodPost, methodGet}): tuple[key, value: string] =
## Reads and decodes CGI data and yields the (name, value) pairs the
## data consists of. If the client does not use a method listed in the
## `allowedMethods` set, an `ECgi` exception is raised.
var enc = getEncodedData(allowedMethods)
if not isNil(enc):
# decode everything in one pass:
var i = 0
var name = ""
var value = ""
while enc[i] != '\0':
setLen(name, 0) # reuse memory
while true:
case enc[i]
of '\0': break
of '%':
var x = 0
handleHexChar(enc[i+1], x)
handleHexChar(enc[i+2], x)
inc(i, 2)
add(name, chr(x))
of '+': add(name, ' ')
of '=', '&': break
else: add(name, enc[i])
inc(i)
if enc[i] != '=': cgiError("'=' expected")
inc(i) # skip '='
setLen(value, 0) # reuse memory
while true:
case enc[i]
of '%':
var x = 0
handleHexChar(enc[i+1], x)
handleHexChar(enc[i+2], x)
inc(i, 2)
add(value, chr(x))
of '+': add(value, ' ')
of '&', '\0': break
else: add(value, enc[i])
inc(i)
yield (name, value)
if enc[i] == '&': inc(i)
elif enc[i] == '\0': break
else: cgiError("'&' expected")
proc readData*(allowedMethods: set[TRequestMethod] =
{methodNone, methodPost, methodGet}): PStringTable =
## Read CGI data. If the client does not use a method listed in the
## `allowedMethods` set, an `ECgi` exception is raised.
result = newStringTable()
var enc: string # encoded data
case getenv("REQUEST_METHOD")
of "POST":
if methodPost notin allowedMethods:
cgiError("'REQUEST_METHOD' 'POST' is not supported")
# read from stdin:
var L = parseInt(getenv("CONTENT_LENGTH"))
enc = newString(L)
if readBuffer(stdin, addr(enc[0]), L) != L:
cgiError("cannot read from stdin")
of "GET":
if methodGet notin allowedMethods:
cgiError("'REQUEST_METHOD' 'GET' is not supported")
# read from the QUERY_STRING environment variable:
enc = getenv("QUERY_STRING")
else:
if methodNone in allowedMethods:
return result
else:
cgiError("'REQUEST_METHOD' must be 'POST' or 'GET'")
# decode everything in one pass:
var i = 0
var name = ""
var value = ""
while true:
setLen(name, 0) # reuse memory
while true:
case enc[i]
of '\0': return
of '%':
var x = 0
handleHexChar(enc[i+1], x)
handleHexChar(enc[i+2], x)
inc(i, 2)
add(name, chr(x))
of '+': add(name, ' ')
of '=', '&': break
else: add(name, enc[i])
inc(i)
if enc[i] != '=': cgiError("'=' expected")
inc(i) # skip '='
setLen(value, 0) # reuse memory
while true:
case enc[i]
of '%':
var x = 0
handleHexChar(enc[i+1], x)
handleHexChar(enc[i+2], x)
inc(i, 2)
add(value, chr(x))
of '+': add(value, ' ')
of '&', '\0': break
else: add(value, enc[i])
inc(i)
for name, value in decodeData(allowedMethods):
result[name] = value
if enc[i] == '&': inc(i)
elif enc[i] == '\0': break
else: cgiError("'&' expected")
proc validateData*(data: PStringTable, validKeys: openarray[string]) =
## validates data; raises `ECgi` if this fails. This checks that each variable
## name of the CGI `data` occurs in the `validKeys` array.
@ -323,8 +329,13 @@ proc setTestData*(keysvalues: openarray[string]) =
proc writeContentType*() =
## call this before starting to send your HTML data to `stdout`. This
## is just a shorthand for:
## implements this part of the CGI protocol:
##
## .. code-block:: Nimrod
## write(stdout, "Content-type: text/html\n\n")
##
## It also modifies the debug stack traces so that they contain
## ``<br />`` and are easily readable in a browser.
write(stdout, "Content-type: text/html\n\n")
system.stackTraceNewLine = "<br />\n"

View file

@ -70,6 +70,8 @@ var
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
@ -101,19 +103,21 @@ proc auxWriteStackTrace(f: PFrame, s: var string) =
if tempFrames[j] == nil:
add(s, "(")
add(s, $(total-i-1))
add(s, " calls omitted) ...\n")
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, "\n")
add(s, stackTraceNewLine)
proc rawWriteStackTrace(s: var string) =
if framePtr == nil:
add(s, "No stack traceback available\n")
add(s, "No stack traceback available")
add(s, stackTraceNewLine)
else:
add(s, "Traceback (most recent call last)\n")
add(s, "Traceback (most recent call last)")
add(s, stackTraceNewLine)
auxWriteStackTrace(framePtr, s)
proc quitOrDebug() {.inline.} =
@ -151,6 +155,8 @@ var
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
@ -162,7 +168,11 @@ proc internalAssert(file: cstring, line: int, cond: bool) {.compilerproc.} =
add(assertBuf, "\n")
gAssertionFailed.msg = assertBuf
GC_enable()
raise gAssertionFailed # newException(EAssertionFailed, assertBuf)
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 = ""

View file

@ -9,23 +9,17 @@
# Garbage Collector
# Current Features:
# * incremental
# * non-recursive
# * generational
#
# 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.
# ---------------------------------------------------------------------------
proc getOccupiedMem(): int = return tlsfUsed()
proc getFreeMem(): int = return tlsfMax() - tlsfUsed()
proc getTotalMem(): int = return tlsfMax()
# ---------------------------------------------------------------------------
const
CycleIncrease = 2 # is a multiplicative increase
InitialCycleThreshold = 4*1024*1024 # X MB because cycle checking is slow
@ -36,14 +30,14 @@ const
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
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
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
rcShift = 3 # shift by rcShift to get the reference counter
colorMask = 0b111
type
TWalkOp = enum
@ -52,21 +46,22 @@ type
TFinalizer {.compilerproc.} = proc (self: pointer)
# A ref type can have a finalizer that is called before the object's
# storage is freed.
TGcHeap {.final, pure.} = object # this contains the zero count and
# non-zero count table
mask: TAddress # mask for fast pointer detection
zct: TCellSeq # the zero count table
stackCells: TCellSet # cells and addresses that look like a cell but
# aren't of the hardware stack
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
maxStackPages: int # max number of pages in stack
cycleTableSize: int # max entries in cycle table
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
@ -82,13 +77,13 @@ var
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 out/var parameters).
# 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 colorMask) != rcZct:
if (c.refcount and rcZct) == 0:
c.refcount = c.refcount and not colorMask or rcZct
add(s, c)
@ -131,7 +126,7 @@ proc GC_disableMarkAndSweep() =
# 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 BitsPerUnit != 0:
when BitsPerPage mod (sizeof(int)*8) != 0:
{.error: "(BitsPerPage mod BitsPerUnit) should be zero!".}
when debugGC:
@ -203,8 +198,7 @@ template gcTrace(cell, state: expr): stmt =
# -----------------------------------------------------------------------------
# forward declarations:
proc updateZCT()
proc collectCT(gch: var TGcHeap, zctUpdated: bool)
proc collectCT(gch: var TGcHeap)
proc IsOnStack(p: pointer): bool {.noinline.}
proc forAllChildren(cell: PCell, op: TWalkOp)
proc doOperation(p: pointer, op: TWalkOp)
@ -232,7 +226,7 @@ proc decRef(c: PCell) {.inline.} =
when stressGC:
if c.refcount <% rcIncrement:
writeCell("broken cell", c)
assert(c.refcount >% rcIncrement)
assert(c.refcount >=% rcIncrement)
c.refcount = c.refcount -% rcIncrement
if c.refcount <% rcIncrement:
addZCT(gch.zct, c)
@ -242,7 +236,6 @@ proc decRef(c: PCell) {.inline.} =
proc incRef(c: PCell) {.inline.} =
c.refcount = c.refcount +% rcIncrement
if canBeCycleRoot(c):
# OPT: the code generator should special case this
incl(gch.cycleRoots, c)
proc nimGCref(p: pointer) {.compilerproc, inline.} = incRef(usrToCell(p))
@ -277,19 +270,18 @@ proc unsureAsgnRef(dest: ppointer, src: pointer) =
proc initGC() =
when traceGC:
for i in low(TCellState)..high(TCellState): CellSetInit(states[i])
gch.stackScans = 0
gch.cycleCollections = 0
gch.maxThreshold = 0
gch.maxStackSize = 0
gch.maxStackPages = 0
gch.cycleTableSize = 0
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)
CellSetInit(gch.cycleRoots)
CellSetInit(gch.stackCells)
gch.mask = 0
Init(gch.cycleRoots)
Init(gch.decStack)
new(gOutOfMem) # reserve space for the EOutOfMemory exception here!
proc forAllSlotsAux(dest: pointer, n: ptr TNimNode, op: TWalkOp) =
@ -333,54 +325,63 @@ proc forAllChildren(cell: PCell, op: TWalkOp) =
of tyString: nil
else: assert(false)
proc checkCollection(zctUpdated: bool) {.inline.} =
proc checkCollection {.inline.} =
# checks if a collection should be done
if recGcLock == 0:
collectCT(gch, zctUpdated)
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})
var zctUpdated = false
if gch.zct.len >= ZctThreshold:
updateZCT()
zctUpdated = true
# check if we have to collect:
checkCollection(zctUpdated)
var res = cast[PCell](gcAlloc(size + sizeof(TCell)))
when stressGC: assert((cast[TAddress](res) and (MemAlignment-1)) == 0)
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
add(gch.zct, res) # its refcount is zero, so add it to the ZCT
gch.mask = gch.mask or cast[TAddress](res)
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 =
# XXX: overflow checks!
result = newObj(typ, len * typ.base.size + GenericSeqSize)
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(false)
checkCollection()
var ol = usrToCell(old)
assert(ol.typ != nil)
assert(ol.typ.kind in {tyString, tySequence})
var res = cast[PCell](gcAlloc(newsize + sizeof(TCell)))
var res = cast[PCell](rawAlloc(allocator, newsize + sizeof(TCell)))
var elemSize = 1
if ol.typ.kind != tyString:
elemSize = ol.typ.base.size
copyMem(res, ol, cast[PGenericSeq](old).len*elemSize +
GenericSeqSize + sizeof(TCell))
assert((cast[TAddress](res) and (MemAlignment-1)) == 0)
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)
@ -395,13 +396,12 @@ proc growObj(old: pointer, newsize: int): pointer =
break
dec(j)
if canBeCycleRoot(ol): excl(gch.cycleRoots, ol)
gch.mask = gch.mask or cast[TAddress](res)
when logGC:
writeCell("growObj old cell", ol)
writeCell("growObj new cell", res)
gcTrace(ol, csZctFreed)
gcTrace(res, csAllocated)
when reallyDealloc: tlsf_free(ol)
when reallyDealloc: rawDealloc(allocator, ol)
else:
assert(ol.typ != nil)
zeroMem(ol, sizeof(TCell))
@ -409,13 +409,6 @@ proc growObj(old: pointer, newsize: int): pointer =
# ---------------- cycle collector -------------------------------------------
# When collecting cycles, we have to consider the following:
# * there may still be references in the stack
# * some cells may still be in the ZCT, because they are referenced from
# the stack (!), so their refcounts are zero
# the ZCT is a subset of stackCells here, so we only need to care
# for stackcells
proc doOperation(p: pointer, op: TWalkOp) =
if p == nil: return
var c: PCell = usrToCell(p)
@ -438,36 +431,25 @@ proc collectCycles(gch: var TGcHeap) =
for c in elements(gch.cycleRoots):
inc(tabSize)
forallChildren(c, waCycleDecRef)
gch.cycleTableSize = max(gch.cycleTableSize, tabSize)
gch.stat.cycleTableSize = max(gch.stat.cycleTableSize, tabSize)
# restore reference counts (a depth-first traversal is needed):
var marker, newRoots: TCellSet
CellSetInit(marker)
CellSetInit(newRoots)
var marker: TCellSet
Init(marker)
for c in elements(gch.cycleRoots):
var needsRestore = false
if c in gch.stackCells:
needsRestore = true
incl(newRoots, c)
# we need to scan this later again; maybe stack changes
# NOTE: adding to ZCT here does NOT work
elif c.refcount >=% rcIncrement:
needsRestore = true
if needsRestore:
if c notin marker:
incl(marker, c)
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 notin marker and d in gch.cycleRoots:
incl(marker, d)
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 and c notin gch.stackCells:
if c.refcount <% rcIncrement:
gch.tempStack.len = 0
forAllChildren(c, waPush)
while gch.tempStack.len > 0:
@ -480,21 +462,23 @@ proc collectCycles(gch: var TGcHeap) =
prepareDealloc(c)
gcTrace(c, csCycFreed)
when logGC: writeCell("cycle collector dealloc cell", c)
when reallyDealloc: tlsf_free(c)
when reallyDealloc: rawDealloc(allocator, c)
else:
assert(c.typ != nil)
zeroMem(c, sizeof(TCell))
CellSetDeinit(gch.cycleRoots)
gch.cycleRoots = newRoots
Deinit(gch.cycleRoots)
Init(gch.cycleRoots)
proc gcMark(p: pointer) {.noinline.} =
proc gcMark(p: pointer) {.inline.} =
# the addresses are not as objects on the stack, so turn them to objects:
var cell = usrToCell(p)
var c = cast[TAddress](cell)
if ((c and gch.mask) == c) and c >% 1024:
if c >% PageSize:
# fast check: does it look like a cell?
when logGC: cfprintf(cstdout, "in stackcells %p\n", cell)
incl(gch.stackCells, cell) # yes: mark it
if isAllocatedPtr(allocator, cell):
# mark the cell:
cell.refcount = cell.refcount +% rcIncrement
add(gch.decStack, cell)
# ----------------- stack management --------------------------------------
# inspired from Smart Eiffel
@ -560,53 +544,6 @@ elif defined(hppa) or defined(hp9000) or defined(hp9000s300) or
gcMark(sp^)
sp = cast[ppointer](cast[TAddress](sp) -% sizeof(pointer))
elif defined(I386) and asmVersion:
# addresses decrease as the stack grows:
proc isOnStack(p: pointer): bool =
var
stackTop: array [0..1, pointer]
result = p >= addr(stackTop[0]) and p <= stackBottom
proc markStackAndRegisters(gch: var TGcHeap) {.noinline, cdecl.} =
# This code should be safe even for aggressive optimizers. The try
# statement safes all registers into the safepoint, which we
# scan additionally to the stack.
type
TPtrArray = array[0..0xffffff, pointer]
try:
var pa = cast[ptr TPtrArray](excHandler)
for i in 0 .. sizeof(TSafePoint) - 1:
gcMark(pa[i])
finally:
# iterate over the stack:
var max = cast[TAddress](stackBottom)
var stackTop{.volatile.}: array [0..15, pointer]
var sp {.volatile.} = cast[TAddress](addr(stackTop[0]))
while sp <= max:
gcMark(cast[ppointer](sp)^)
sp = sp +% sizeof(pointer)
when false:
var counter = 0
#mov ebx, OFFSET `stackBottom`
#mov ebx, [ebx]
asm """
pusha
mov edi, esp
call `getStackBottom`
mov ebx, eax
L1:
cmp edi, ebx
ja L2
mov eax, [edi]
call `gcMark`
add edi, 4
inc [`counter`]
jmp L1
L2:
popa
"""
cfprintf(cstdout, "stack %ld\n", counter)
else:
# ---------------------------------------------------------------------------
# Generic code for architectures where addresses decrease as the stack grows.
@ -617,82 +554,47 @@ else:
result = p >= addr(stackTop[0]) and p <= stackBottom
var
gRegisters: C_JmpBuf
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.} =
when false:
# new version: several C compilers are too smart here
var
max = cast[TAddress](stackBottom)
stackTop: array [0..15, pointer]
if c_setjmp(gregisters) == 0'i32: # To fill the C stack with registers.
# iterate over the registers:
var sp = cast[TAddress](addr(gregisters))
while sp < cast[TAddress](addr(gregisters))+%jmpbufSize:
gcMark(cast[ppointer](sp)^)
sp = sp +% sizeof(pointer)
# iterate over the stack:
sp = cast[TAddress](addr(stackTop[0]))
while sp <= max:
gcMark(cast[ppointer](sp)^)
sp = sp +% sizeof(pointer)
else:
c_longjmp(gregisters, 42)
# this can never happen, but should trick any compiler that is
# not as smart as a human
else:
var
max = stackBottom
registers: C_JmpBuf # The jmp_buf buffer is in the C stack.
sp: PPointer # Used to traverse the stack and registers assuming
# that 'setjmp' will save registers in the C stack.
if c_setjmp(registers) == 0'i32: # To fill the C stack with registers.
sp = cast[ppointer](addr(registers))
while sp <= max:
gcMark(sp^)
sp = cast[ppointer](cast[TAddress](sp) +% sizeof(pointer))
var
max = stackBottom
registers: C_JmpBuf # The jmp_buf buffer is in the C stack.
sp: PPointer # Used to traverse the stack and registers assuming
# that 'setjmp' will save registers in the C stack.
if c_setjmp(registers) == 0'i32: # To fill the C stack with registers.
sp = cast[ppointer](addr(registers))
while sp <= max:
gcMark(sp^)
sp = cast[ppointer](cast[TAddress](sp) +% sizeof(pointer))
# ----------------------------------------------------------------------------
# end of non-portable code
# ----------------------------------------------------------------------------
proc updateZCT() =
# We have to make an additional pass over the ZCT unfortunately, because
# the ZCT may be out of date, which means it contains cells with a
# refcount > 0. The reason is that ``incRef`` does not bother to remove
# the cell from the ZCT as this might be too slow.
var j = 0
var L = gch.zct.len # because globals make it hard for the optimizer
var d = gch.zct.d
while j < L:
var c = d[j]
if c.refcount >=% rcIncrement:
when logGC: writeCell("remove from ZCT", c)
# remove from ZCT:
dec(L)
d[j] = d[L]
c.refcount = c.refcount and not colorMask
# we have a new cell at position j, so don't increment j
else:
inc(j)
gch.zct.len = L
proc CollectZCT(gch: var TGcHeap) =
var i = 0
while i < gch.zct.len:
var c = gch.zct.d[i]
assert(c.refcount <% rcIncrement)
# 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)
if canBeCycleRoot(c): excl(gch.cycleRoots, c)
if c notin gch.stackCells:
# remove from ZCT:
c.refcount = c.refcount and not colorMask
gch.zct.d[i] = gch.zct.d[gch.zct.len-1]
# we have a new cell at position i, so don't increment i
dec(gch.zct.len)
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
@ -700,51 +602,53 @@ proc CollectZCT(gch: var TGcHeap) =
# access invalid memory. This is done by prepareDealloc():
prepareDealloc(c)
forAllChildren(c, waZctDecRef)
when reallyDealloc: tlsf_free(c)
when reallyDealloc: rawDealloc(allocator, c)
else:
assert(c.typ != nil)
zeroMem(c, sizeof(TCell))
else:
inc(i)
when stressGC:
for j in 0..gch.zct.len-1: assert(gch.zct.d[j] in gch.stackCells)
proc collectCT(gch: var TGcHeap, zctUpdated: bool) =
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:
if not zctUpdated: updateZCT()
gch.maxStackSize = max(gch.maxStackSize, stackSize())
CellSetInit(gch.stackCells)
getOccupiedMem() >= cycleThreshold) or stressGC:
gch.stat.maxStackSize = max(gch.stat.maxStackSize, stackSize())
assert(gch.decStack.len == 0)
markStackAndRegisters(gch)
gch.maxStackPages = max(gch.maxStackPages, gch.stackCells.counter)
inc(gch.stackScans)
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.cycleCollections)
inc(gch.stat.cycleCollections)
cycleThreshold = max(InitialCycleThreshold, getOccupiedMem() *
cycleIncrease)
gch.maxThreshold = max(gch.maxThreshold, cycleThreshold)
CellSetDeinit(gch.stackCells)
gch.stat.maxThreshold = max(gch.stat.maxThreshold, cycleThreshold)
unmarkStackAndRegisters(gch)
proc GC_fullCollect() =
var oldThreshold = cycleThreshold
cycleThreshold = 0 # forces cycle collection
collectCT(gch, false)
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.stackScans & "\n" &
"[GC] stack pages: " & $gch.maxStackPages & "\n" &
"[GC] cycle collections: " & $gch.cycleCollections & "\n" &
"[GC] max threshold: " & $gch.maxThreshold & "\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.cycleTableSize & "\n" &
"[GC] max stack size: " & $gch.maxStackSize
"[GC] max cycle table size: " & $gch.stat.cycleTableSize & "\n" &
"[GC] max stack size: " & $gch.stat.maxStackSize
when traceGC: writeLeakage()
GC_enable()

View file

@ -1,7 +1,7 @@
#
#
# The Nimrod Compiler
# (c) Copyright 2008 Andreas Rumpf
# (c) Copyright 2009 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.

View file

@ -1,15 +1,17 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2008 Andreas Rumpf
# (c) Copyright 2009 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module contains the interface to the compiler's abstract syntax tree.
## Abstract syntax trees should be modified in macros.
## This module contains the interface to the compiler's abstract syntax
## tree (`AST`:idx:). Macros operate on this tree.
## .. include:: ../doc/astspec.txt
#[[[cog
#def toEnum(name, elems):
@ -235,3 +237,13 @@ proc newCall*(theProc: string,
result.add(newIdentNode(theProc))
result.add(args)
proc nestList*(theProc: TNimrodIdent,
x: PNimrodNode): PNimrodNode {.compileTime.} =
## nests the list `x` into a tree of call expressions:
## ``[a, b, c]`` is transformed into ``theProc(a, theProc(c, d))``
var L = x.len
result = newCall(theProc, x[L-2], x[L-1])
var a = result
for i in countdown(L-3, 0):
a = newCall(theProc, x[i], copyNimTree(a))

View file

@ -208,5 +208,40 @@ else:
var y = exp(2.0*x)
return (y-1.0)/(y+1.0)
type
TRunningStat* = object ## an accumulator for statistical data
n*: int ## number of pushed data
sum*, min*, max*, mean*: float ## self-explaining
oldM, oldS, newS: float
proc push*(s: var TRunningStat, x: float) =
## pushes a value `x` for processing
inc(s.n)
# See Knuth TAOCP vol 2, 3rd edition, page 232
if s.n == 1:
s.oldM = x
s.mean = x
s.oldS = 0.0
else:
s.mean = s.oldM + (x - s.oldM)/toFloat(s.n)
s.newS = s.oldS + (x - s.oldM)*(x - s.mean)
# set up for next iteration:
s.oldM = s.mean
s.oldS = s.newS
s.sum = s.sum + x
if s.min > x: s.min = x
if s.max < x: s.max = x
proc variance*(s: TRunningStat): float =
## computes the current variance of `s`
if s.n > 1: result = s.newS / (toFloat(s.n - 1))
proc standardDeviation*(s: TRunningStat): float =
## computes the current standard deviation of `s`
result = sqrt(variance(s))
{.pop.}
{.pop.}

View file

@ -1,698 +0,0 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2008 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
# Memory manager. Based on:
# Two Levels Segregate Fit memory allocator (TLSF)
# Version 2.4.2
#
# Written by Miguel Masmano Tello <mimastel@doctor.upv.es>
#
# Thanks to Ismael Ripoll for his suggestions and reviews
#
# Copyright (C) 2008, 2007, 2006, 2005, 2004
#
# This code is released using a dual license strategy: GPL/LGPL
# You can choose the licence that better fits your requirements.
#
# Released under the terms of the GNU General Public License Version 2.0
# Released under the terms of the GNU Lesser General Public License Version 2.1
# Some IMPORTANT TLSF parameters
const
blockAlign = sizeof(pointer) * 2
maxFli = 30
maxLog2Sli = 5
maxSli = 1 shl maxLog2Sli
fliOffset = 6 # tlsf structure just will manage blocks bigger than 128 bytes
smallBlock = 128
realFli = MaxFli - fliOffset
type
TFreePtr {.final.} = object
prev, next: PBhdr
Pbhdr = ptr Tbhdr
Tbhdr {.final.} = object
prevHdr: Pbhdr # this is just valid if the first bit of size is set
size: int # the size is stored in bytes
# bit 0 indicates whether the block is used and
# bit 1 allows to know whether the previous block is free
freePtr: TFreePtr # at this offset bhdr.buffer starts (was a union in the
# C version)
TAreaInfo {.final.} = object # This structure is embedded at the beginning
# of each area, giving us enough information
# to cope with a set of areas
theEnd: Pbhdr
next: PAreaInfo
PAreaInfo = ptr TAreaInfo
TLSF {.final.} = object
tlsf_signature: int32 # the TLSF's structure signature
usedSize, maxSize: int
areaHead: PAreaInfo # A linked list holding all the existing areas
flBitmap: int32 # the first-level bitmap
# This array should have a size of REAL_FLI bits
slBitmap: array[0..realFli, int32] # the second-level bitmap
matrix: array [0..realFli, array[0..maxSli, PBhdr]]
const
minBlockSize = sizeof(TFreePtr)
bhdrOverhead = sizeof(Tbhdr) - minBlockSize
tlsfSignature = 0x2A59FA59
ptrMask = sizeof(pointer) - 1
blockSize = 0xFFFFFFFF - ptrMask
memAlign = blockAlign - 1
blockState = 0x1
prevState = 0x2
freeBlock = 0x1 # bit 0 of the block size
usedBlock = 0x0
prevFree = 0x2 # bit 1 of the block size
prevUsed = 0x0
defaultAreaSize = 64*1024 # 1024*10
pageSize = if defined(cpu32): 4096 else: 4096*2
proc getNextBlock(adr: pointer, r: int): PBhdr {.inline.} =
return cast[PBhdr](cast[TAddress](adr) +% r)
proc roundupSize(r: int): int = return (r +% memAlign) and not memAlign
proc rounddownSize(r: int): int = return r and not memAlign
proc roundup(x, v: int): int = return (((not x)+%1) and (v-%1)) +% x
proc addSize(s: PTLSF, b: Pbhdr) =
inc(s.usedSize, (b.size and blockSize) + bhdrOverhead)
s.maxSize = max(s.maxSize, s.usedSize)
proc removeSize(s: PTLSF, b: Pbhdr) =
dec(s.usedSize, (b.size and blockSize) + bhdrOverhead)
# ------------ platform specific code -----------------------------------------
when defined(posix):
const # XXX: make these variables for portability?
PROT_READ = 1 # page can be read
PROT_WRITE = 2 # page can be written
PROT_EXEC = 4 # page can be executed
PROT_NONE = 0 # page can not be accessed
MAP_SHARED = 1 # Share changes
MAP_PRIVATE = 2 # Changes are private
MAP_TYPE = 0xf # Mask for type of mapping
MAP_FIXED = 0x10 # Interpret addr exactly
MAP_ANONYMOUS = 0x20 # don't use a file
MAP_GROWSDOWN = 0x100 # stack-like segment
MAP_DENYWRITE = 0x800 # ETXTBSY
MAP_EXECUTABLE = 0x1000 # mark it as an executable
MAP_LOCKED = 0x2000 # pages are locked
MAP_NORESERVE = 0x4000 # don't check for reservations
proc mmap(adr: pointer, len: int, prot, flags, fildes: cint,
off: int): pointer {.header: "<sys/mman.h>".}
proc getNewArea(size: var int): pointer {.inline.} =
size = roundup(size, PageSize)
result = mmap(0, size, PROT_READ or PROT_WRITE,
MAP_PRIVATE or MAP_ANONYMOUS, -1, 0)
if result == nil or result == cast[pointer](-1):
raiseOutOfMem()
elif defined(windows):
const
MEM_RESERVE = 0x2000
MEM_COMMIT = 0x1000
MEM_TOP_DOWN = 0x100000
PAGE_READWRITE = 0x04
proc VirtualAlloc(lpAddress: pointer, dwSize: int, flAllocationType,
flProtect: int32): pointer {.
header: "<windows.h>", stdcall.}
proc getNewArea(size: var int): pointer {.inline.} =
size = roundup(size, PageSize)
result = VirtualAlloc(nil, size, MEM_RESERVE or MEM_COMMIT or MEM_TOP_DOWN,
PAGE_READWRITE)
if result == nil: raiseOutOfMem()
else:
{.warning: "Generic code for allocating pages is used".}
# generic implementation relying on malloc:
proc malloc(size: int): pointer {.nodecl, importc.}
proc getNewArea(size: var int): pointer {.inline.} =
size = roundup(size, PageSize)
result = malloc(size)
if result == nil: raiseOutOfMem()
# ---------------------------------------------------------------------------
# small fixed size allocator:
# Design: We manage pages. A page is of constant size, but not necessarily
# the OS's page size. Pages are managed in a hash table taking advantage of
# the fact that the OS is likely to give us pages with contingous numbers.
# A page contains either small fixed size objects of the same size or
# variable length objects. An object's size is always aligned at 16 byte
# boundary. Huge objects are dealt with the TLSF algorithm.
# The design supports iterating over any object in a fast way.
# A bitset contains any page that starts an allocated page. The page may be
# empty however. This bitset can be used to quickly determine if a given
# page belongs to the GC heap. The design of the memory allocator makes it
# simple to return unused pages back to the OS.
# Small bocks
# -----------
#
# If we use a list in the free object's space. Allocation and deallocation are
# O(1). Since any object is of the same size, iteration is quite efficient too.
# However, pointer detection is easy too: Just check if the type-field is nil.
# Deallocation sets it to nil.
# Algorithm:
# i = 0
# f = b.f # first free address
# while i < max:
# if a[i] == f: # not a valid block
# f = f.next # next free address
# else:
# a[i] is a valid object of size s
# inc(i)
# The zero count table is an array. Since we know that the RC is zero, we can
# use the bits for an index into this array. Thus multiple ZCT tables are not
# difficult to support and insertion and removal is O(1). We use negative
# indexes for this. This makes it even fast enough (and necessary!) to do a ZCT
# removal if the RC is incremented.
#
# Huge blocks
# -----------
#
# Huge blocks are always rounded up to a multiple of the page size. These are
# called *strides*. We also need to keep an index structure
# of (stridesize, pagelist).
#
const
MemAlign = 8
PageShift = if sizeof(int) == 4: 12 else: 13
PageSize = 1 shl PageShift
type
TFreeList {.final.} = object
next, prev: ptr TFreeList
TPageDesc {.final.} = object # the start of a stride always starts with this!
size: int # lowest bit is set, if it is a huge block
free: ptr TFreeList # only used if it manages multiple cells
snext, sprev: ptr TPageDesc # next and prev pagedescs with the same size
TCellArray {.final.} = object
i: int # length
d: ptr array [0..1000_000, TCell]
TPageManager = table[page, ptr TPageDesc]
TGcHeap {.final.} = object
# structure that manages the garbage collected heap
zct: TCellArray
stackCells: TCellArray
smallBlocks: array [PageSize div MemAlign, ptr TPageDesc]
freeLists: array [PageSize div MemAlign, ptr TFreeList]
pages: TPageManager
usedPages: TPageList
freePages: TPageList
# small blocks:
proc allocSmall(var h: TGcHeap, size: int): pointer =
var s = align(size)
var f = h.freeLists[s]
if f != nil:
f.prev = f.next # remove from list
f.next.prev = f.prev
return f
var p = h.smallBlocks[s]
if p == nil or p.free == nil:
p = newSmallBlock(s, p)
h.smallBlocks[s] = p
proc decRef(cell: PCell) {.inline.} =
assert(cell in ct.AT)
assert(cell.refcount > 0) # this should be the case!
assert(seqCheck(cell))
dec(cell.refcount)
if cell.refcount == 0:
# add to zero count table:
zct.d[zct.i] = cell
cell.recfcount = -zct.i
inc(zct.i)
proc incRef(cell: PCell) {.inline.} =
assert(seqCheck(cell))
if cell.refcount < 0:
# remove from zero count table:
zct.d[-cell.refcount] = zct.d[zct.i-1]
dec(zct.i)
cell.refcount = 1
else:
inc(cell.refcount)
proc asgnRef(dest: ppointer, src: pointer) =
# 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
# ----------------------------------------------------------------------------
# helpers
const
table: array[0..255, int8] = [
-1, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 4, 4, 4, 4, 4, 4,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
6, 6, 6, 6, 6, 6, 6, 6,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
7, 7, 7, 7, 7, 7, 7, 7
]
proc ls_bit(i: int32): int {.inline.} =
var
a: int = 0
x: int = i and -i
if x <=% 0xffff:
if x <=% ff: a = 0
else: a = 8
elif x <=% 0xffffff: a = 16
else: a = 24
return table[x shr a] + a
proc ms_bit(i: int): int {.inline.} =
var
a = if i <=% 0xffff: (if i <=% 0xff: 0 else: 8) elif
i <=% 0xffffff: 16 else: 24
return table[i shr a] + a
proc set_bit[IX](nr: int, adr: var array[IX, int32]) {.inline.} =
adr[nr shr 5] = adr[nr shr 5] or (1 shl (nr and 0x1f))
proc clear_bit[IX](nr: int, adr: var array[IX, int32]) {.inline.} =
adr[nr shr 5] = adr[nr shr 5] and not (1 shl (nr and 0x1f))
proc mappingSearch(r, fl, sl: var int) {.inline.} =
if r < smallBlock:
fl = 0
sl = r div (smallBlock div maxSli)
else:
var t = (1 shl (ms_bit(r) - maxLog2Sli)) - 1
r = r + t
fl = ms_bit(r)
sl = (r shl (fl - maxLog2Sli)) - maxSli
fl = fl - fliOffset
r = r and not t
proc mappingInsert(r: int, fl, sl: var int) {.inline.} =
if r < smallBlock:
fl = 0
sl = r div (smallBlock div maxSli)
else:
fl = ms_bit(r)
sl = (r shr (fl - maxLog2Sli)) - maxSli
fl = fl - fliOffset
proc findSuitableBlock(t: var TLSF, fl, sl: var int): Pbhdr =
var tmp = t.slBitmap[fl] and ((not 0) shl sl)
if tmp != 0:
sl = ls_bit(tmp)
result = t.matrix[fl][sl]
else:
fl = ls_bit(t.flBitmap and (not 0 shl (fl + 1)))
if fl > 0: # likely
sl = ls_bit(t.slBitmap[fl])
result = t.matrix[fl][sl]
proc extractBlockHdr(b: Pbhdr, t: var TLSF, fl, sl: int) {.inline.} =
t.matrix[fl][sl] = b.freePtr.next
if t.matrix[fl][sl] != nil:
t.matrix[fl][sl].freePtr.prev = nil
else:
clear_bit(sl, t.slBitmap[fl])
if t.slBitmap[fl] == 0:
clear_bit(fl, t.flBitmap)
b.freePtr.prev = nil
b.freePtr.next = nil
proc extractBlock(b: Pbhdr, t: var TLSF, fl, sl: int) {.inline.} =
if b.freePtr.next != nil:
b.freePtr.next.freePtr.prev = b.freePtr.prev
if b.freePtr.prev != nil:
b.freePtr.prev.freePtr.next = b.freePtr.next
if t.matrix[fl][sl] == b:
t.matrix[fl][sl] = b.freePtr.next
if t.matrix[fl][sl] == nil:
clear_bit(sl, t.slBitmap[fl])
if t.slBitmap[fl] == 0:
clear_bit(fl, t.flBitmap)
b.freePtr.prev = nil
b.freePtr.next = nil
proc insertBlock(b: Pbhdr, t: var TLSF, fl, sl: int) {.inline.} =
b.freePtr.prev = nil
b.freePtr.next = t.matrix[fl][sl]
if t.matrix[fl][sl] != nil:
t.matrix[fl][sl].freePtr.prev = b
t.matrix[fl][sl] = b
set_bit(sl, t.slBitmap[fl])
set_bit(fl, t.flBitmap)
proc getBuffer(b: Pbhdr): pointer {.inline.} =
result = cast[pointer](addr(b.freePtr))
proc processArea(area: pointer, size: int): Pbhdr =
var
b, lb, ib: Pbhdr
ai: PAreaInfo
ib = cast[Pbhdr](area)
if sizeof(TAreaInfo) < minBlockSize:
ib.size = minBlockSize or usedBlock or prevUsed
else
ib.size = roundupSize(sizeof(TAreaInfo)) or usedBlock or prevUsed
b = getNextBlock(getBuffer(ib), ib.size and blockSize)
b.size = rounddownSize(size - 3 * bhdrOverhead - (ib.size and blockSize)) or
usedBlock or prevUsed
b.freePtr.prev = nil
b.freePtr.next = nil
lb = getNextBlock(getBuffer(b), b.size and blockSize)
lb.prevHdr = b
lb.size = 0 or usedBlock or prevFree
ai = cast[PAreaInfo](getBuffer(ib))
ai.next = nil
ai.theEnd = lb
return ib
# ----------------------------------------------------------------------------
# Begin of the allocator code
proc initMemoryPool(memPoolSize: int, memPool: pointer): int =
var
t: PLSF
b, ib: Pbhdr
if memPool == nil or memPoolSize < sizeof(TLSF) + bhdrOverhead * 8:
writeToStdErr("initMemoryPool(): memory_pool invalid\n")
return -1
if (cast[TAddress](memPool) and ptrMask) != 0:
writeToStdErr("initMemoryPool(): memPool must be aligned to a word\n")
return -1
t = cast[PLSF](memPool)
# Check if already initialised
if t.signature == tlsfSignature:
b = getNextBlock(memPool, roundupSize(sizeof(TLSF)))
return b.size and blockSize
zeroMem(memPool, sizeof(TLSF))
t.signature = tlsfSignature
ib = processArea(getNextBlock(memPool, roundupSize(sizeof(TLSF))),
rounddownSize(memPoolSize - sizeof(TLSF)))
b = getNextBlock(getBuffer(ib), ib.size and blockSize)
freeEx(getBuffer(b), t)
t.areaHead = cast[PAreaInfo](getBuffer(ib))
t.used_size = memPoolSize - (b.size and blockSize)
t.max_size = t.used_size
return b.size and blockSize
proc addNewArea(area: pointer, areaSize: int, t: var TLSF): int =
var
p, ptrPrev, ai: PAreaInfo
ib0, b0, lb0, ib1, b1, lb1, nextB: Pbhdr
zeroMem(area, areaSize)
p = t.areaHead
ptrPrev = 0
ib0 = processArea(area, areaSize)
b0 = getNextBlock(getBuffer(ib0), ib0.size and blockSize)
lb0 = getNextBlock(getBuffer(b0), b0.size and blockSize)
# Before inserting the new area, we have to merge this area with the
# already existing ones
while p != nil:
ib1 = cast[Pbhdr](cast[TAddress](p) -% bhdrOverhead)
b1 = getNextBlock(getBuffer(ib1), ib1.size and blockSize)
lb1 = p.theEnd
# Merging the new area with the next physically contigous one
if cast[TAddress](ib1) == cast[TAddress](lb0) +% bhdrOverhead:
if t.areaHead == p:
t.areaHead = p.next
p = p.next
else:
ptrPrev.next = p.next
p = p.next
b0.size = rounddownSize((b0.size and blockSize) +
(ib1.size and blockSize) + 2 * bhdrOverhead) or
usedBlock or prevUsed
b1.prevHdr = b0
lb0 = lb1
continue
# Merging the new area with the previous physically contigous one
if getBuffer(lb1) == pointer(ib0):
if t.areaHead == p:
t.areaHead = p.next
p = p.next
else:
ptrPrev.next = p.next
p = p.next
lb1->size = rounddownSize((b0.size and blockSize) +
(ib0.size and blockSize) + 2 * bhdrOverhead) or
usedBlock or (lb1.size and prevState)
nextB = getNextBlock(getBuffer(lb1), lb1.size and blockSize)
nextB.prevHdr = lb1
b0 = lb1
ib0 = ib1
continue
ptrPrev = p
p = p.next
# Inserting the area in the list of linked areas
ai = cast[PAreaInfo](getBuffer(ib0))
ai.next = t.areaHead
ai.theEnd = lb0
t.areaHead = ai
freeEx(getBuffer(b0), memPool)
return (b0.size and blockSize)
proc mallocEx(asize: int, t: var TLSF): pointer =
var
b, b2, nextB: Pbhdr
fl, sl, tmpSize, size: int
size = if asize < minBlockSize: minBlockSize else: roundupSize(asize)
# Rounding up the requested size and calculating fl and sl
mappingSearch(size, fl, sl)
# Searching a free block, recall that this function changes the values
# of fl and sl, so they are not longer valid when the function fails
b = findSuitableBlock(tlsf, fl, sl)
if b == nil:
# Growing the pool size when needed
# size plus enough room for the required headers:
var areaSize = max(size + bhdrOverhead * 8, defaultAreaSize)
var area = getNewArea(areaSize)
addNewArea(area, areaSize, t)
# Rounding up the requested size and calculating fl and sl
mappingSearch(size, fl, sl)
# Searching a free block
b = findSuitableBlock(t, fl, sl)
if b == nil:
raiseOutOfMem()
extractBlockHdr(b, t, fl, sl)
#-- found:
nextB = getNextBlock(getBuffer(b), b.size and blockSize)
# Should the block be split?
tmpSize = (b.size and blockSize) - size
if tmpSize >= sizeof(Tbhdr):
dec(tmpSize, bhdrOverhead)
b2 = getNextBlock(getBuffer(b), size)
b2.size = tmpSize or freeBlock or prevUsed
nextB.prevHdr = b2
mappingInsert(tmpSize, fl, sl)
insertBlock(b2, t, fl, sl)
b.size = size or (b.size and prevState)
else:
nextB.size = nextB.size and not prevFree
b.size = b.size and not freeBlock # Now it's used
addSize(t, b)
return getBuffer(b)
proc freeEx(p: pointer, t: var TLSF) =
var
fl = 0
sl = 0
b, tmpB: Pbhdr
assert(p != nil)
b = cast[Pbhdr](cast[TAddress](p) -% bhdrOverhead)
b.size = b.size or freeBlock
removeSize(t, b)
b.freePtr.prev = nil
b.freePtr.next = nil
tmpB = getNextBlock(getBuffer(b), b.size and blockSize)
if (tmpB.size and freeBlock) != 0:
mappingInsert(tmpB.size and blockSize, fl, sl)
extractBlock(tmpB, t, fl, sl)
inc(b.size, (tmpB.size and blockSize) + bhdrOverhead)
if (b.size and prevFree) != 0:
tmpB = b.prevHdr
mappingInsert(tmpB.size and blockSize, fl, sl)
extractBlock(tmpB, t, fl, sl)
inc(tmpB.size, (b.size and blockSize) + bhdrOverhead)
b = tmpB
mappingInsert(b.size and blockSize, fl, sl)
insertBlock(b, t, fl, sl)
tmpB = getNextBlock(getBuffer(b), b.size and blockSize)
tmpB.size = tmpB.size or prevFree
tmpB.prevHdr = b
proc reallocEx(p: pointer, newSize: int, t: var TLSF): pointer =
var
cpsize, fl, sl, tmpSize: int
b, tmpB, nextB: Pbhdr
assert(p != nil)
assert(newSize > 0)
b = cast[Pbhdr](cast[TAddress](p) -% bhdrOverhead)
nextB = getNextBlock(getBuffer(b), b.size and blockSize)
newSize = if newSize < minBlockSize: minBlockSize else: roundupSize(newSize)
tmpSize = b.size and blockSize
if newSize <= tmpSize:
removeSize(t, b)
if (nextB.size and freeBlock) != 0:
mappingInsert(nextB.size and blockSize, fl, sl)
extractBlock(nextB, t, fl, sl)
inc(tmpSize, (nextB.size and blockSize) + bhdrOverhead)
nextB = getNextBlock(getBuffer(nextB), nextB.size and blockSize)
# We always reenter this free block because tmpSize will
# be greater then sizeof(Tbhdr)
dec(tmpSize, newSize)
if tmpSize >= sizeof(Tbhdr):
dec(tmpSize, bhdrOverhead)
tmpB = getNextBlock(getBuffer(b), newSize)
tmpB.size = tmpSize or freeBlock or prevUsed
nextB.prevHdr = tmpB
nextB.size = nextB.size or prevFree
mappingInsert(tmpSize, fl, sl)
insertBlock(tmpB, t, fl, sl)
b.size = newSize or (b.size and prevState)
addSize(t, b)
return getBuffer(b)
if (nextB.size and freeBlock) != 0:
if newSize <= tmpSize + (nextB.size and blockSize):
removeSize(t, b)
mappingInsert(nextB.size and blockSize, fl, sl)
extractBlock(nextB, t, fl, sl)
inc(b.size, (nextB.size and blockSize) + bhdrOverhead)
nextB = getNextBlock(getBuffer(b), b.size and blockSize)
nextB.prevHdr = b
nextB.size = nextB.size and not prevFree
tmpSize = (b.size and blockSize) - newSize
if tmpSize >= sizeof(Tbhdr):
dec(tmpSize, bhdrOverhead)
tmpB = getNextBlock(getBuffer(b), newSize)
tmpB.size = tmpSize or freeBlock or prevUsed
nextB.prevHdr = tmpB
nextB.size = nextB.size or prevFree
mappingInsert(tmpSize, fl, sl)
insertBlock(tmpB, t, fl, sl)
b.size = newSize or (b.size and prevState)
addSize(t, b)
return getBuffer(b)
var ptrAux = mallocEx(newSize, t)
cpsize = if (b.size and blockSize) > newSize: newSize else:
(b.size and blockSize)
copyMem(ptrAux, p, cpsize)
freeEx(p, memPool)
return ptrAux
proc ansiCrealloc(p: pointer, newSize: int, t: var TLSF): pointer =
if p == nil:
if newSize > 0:
result = mallocEx(newSize, t)
else:
result = nil
elif newSize <= 0:
freeEx(p, t)
result = nil
else:
result = reallocEx(p, newSize, t)
proc InitTLSF(t: var TLSF) =
var areaSize = sizeof(TLSF) + BHDR_OVERHEAD * 8 # Just a safety constant
areaSize = max(areaSize, DEFAULT_areaSize)
var area = getNewArea(areaSize)
initMemoryPool(areaSize, area)
var
t: PLSF
b, ib: Pbhdr
t = cast[PLSF](memPool)
zeroMem(area, areaSize)
t.signature = tlsfSignature
var ib = processArea(getNextBlock(memPool, roundupSize(sizeof(TLSF))),
rounddownSize(memPoolSize - sizeof(TLSF)))
var b = getNextBlock(getBuffer(ib), ib.size and blockSize)
freeEx(getBuffer(b), t)
t.areaHead = cast[PAreaInfo](getBuffer(ib))
t.used_size = memPoolSize - (b.size and blockSize)
t.max_size = t.used_size
# XXX

187
lib/mm.nim Normal file
View file

@ -0,0 +1,187 @@
#
#
# 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
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 cellsets
elif defined(nogc):
proc alloc(size: int): pointer =
result = c_malloc(size)
if result == nil: raiseOutOfMem()
proc alloc0(size: int): pointer =
result = alloc(size)
zeroMem(result, size)
proc realloc(p: Pointer, newsize: int): pointer =
result = c_realloc(p, newsize)
if result == nil: raiseOutOfMem()
proc dealloc(p: Pointer) =
c_free(p)
proc initGC() = nil
proc GC_disable() = nil
proc GC_enable() = nil
proc GC_fullCollect() = nil
proc GC_setStrategy(strategy: TGC_Strategy) = nil
proc GC_enableMarkAndSweep() = nil
proc GC_disableMarkAndSweep() = nil
proc GC_getStatistics(): string = return ""
proc getOccupiedMem(): int = return -1
proc getFreeMem(): int = return -1
proc getTotalMem(): int = return -1
proc newObj(typ: PNimType, size: int): pointer {.compilerproc.} =
result = alloc0(size)
proc newSeq(typ: PNimType, len: int): pointer {.compilerproc.} =
result = newObj(typ, addInt(mulInt(len, typ.base.size), GenericSeqSize))
cast[PGenericSeq](result).len = len
cast[PGenericSeq](result).space = len
proc growObj(old: pointer, newsize: int): pointer =
result = realloc(old, newsize)
# XXX BUG: we need realloc0 here, but C does not support this...
proc setStackBottom(theStackBottom: pointer) {.compilerproc.} = nil
proc nimGCref(p: pointer) {.compilerproc, inline.} = nil
proc nimGCunref(p: pointer) {.compilerproc, inline.} = nil
proc unsureAsgnRef(dest: ppointer, src: pointer) {.compilerproc, inline.} =
dest^ = src
proc asgnRef(dest: ppointer, src: pointer) {.compilerproc, inline.} =
dest^ = src
proc asgnRefNoCycle(dest: ppointer, src: pointer) {.compilerproc, inline.} =
dest^ = src
include cellsets
else:
include alloc
include cellsets
assert(sizeof(TCell) == sizeof(TFreeCell))
include gc
{.pop.}

View file

@ -546,7 +546,7 @@ proc getApplicationFilename(): string =
# Solaris:
# /proc/<pid>/object/a.out (filename only)
# /proc/<pid>/path/a.out (complete pathname)
# *BSD (and maybe Darwing too):
# *BSD (and maybe Darwin too):
# /proc/<pid>/file
when defined(windows):
result = newString(256)

View file

@ -1,20 +1,21 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2008 Andreas Rumpf
# (c) Copyright 2009 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements an advanced facility for executing OS processes.
## On Windows this module is currently broken. Please help!
## This module implements an advanced facility for executing OS processes
## and process communication.
## **On Windows this module does not work properly. Please help!**
import
os, strtabs, streams
when defined(windows):
import windows
import winlean
type
TProcess = object of TObject
@ -41,6 +42,10 @@ proc executeProcess*(command: string,
## A convience procedure that executes ``command`` with ``startProcess``
## and returns its output as a string.
proc executeCommand*(command: string): int
## Executes ``command`` and returns its error code. Standard input, output,
## error streams are inherited from the calling process.
proc startProcess*(command: string,
workingDir: string = "",
args: openarray[string] = [],
@ -133,14 +138,14 @@ when defined(Windows):
proc hsReadData(s: PFileHandleStream, buffer: pointer, bufLen: int): int =
var br: int32
var a = windows.ReadFile(s.handle, buffer, bufLen, br, nil)
var a = winlean.ReadFile(s.handle, buffer, bufLen, br, nil)
if a == 0: OSError()
result = br
#atEnd = bytesRead < bufLen
proc hsWriteData(s: PFileHandleStream, buffer: pointer, bufLen: int) =
var bytesWritten: int32
var a = windows.writeFile(s.handle, buffer, bufLen, bytesWritten, nil)
var a = winlean.writeFile(s.handle, buffer, bufLen, bytesWritten, nil)
if a == 0: OSError()
proc newFileHandleStream(handle: THandle): PFileHandleStream =
@ -180,12 +185,12 @@ when defined(Windows):
# O_WRONLY {.importc: "_O_WRONLY", header: "<fcntl.h>".}: int
# O_RDONLY {.importc: "_O_RDONLY", header: "<fcntl.h>".}: int
proc CreatePipeHandles(Inhandle, OutHandle: ptr THandle) =
proc CreatePipeHandles(Inhandle, OutHandle: var THandle) =
var piInheritablePipe: TSecurityAttributes
piInheritablePipe.nlength = SizeOF(TSecurityAttributes)
piInheritablePipe.lpSecurityDescriptor = Nil
piInheritablePipe.lpSecurityDescriptor = nil
piInheritablePipe.Binherithandle = 1
if CreatePipe(Inhandle, Outhandle, addr(piInheritablePipe), 0) == 0'i32:
if CreatePipe(Inhandle, Outhandle, piInheritablePipe, 0) == 0'i32:
OSError()
proc startProcess*(command: string,
@ -201,15 +206,15 @@ when defined(Windows):
hi, ho, he: THandle
SI.cb = SizeOf(SI)
SI.dwFlags = STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES
CreatePipeHandles(addr(SI.hStdInput), addr(HI))
CreatePipeHandles(addr(HO), addr(Si.hStdOutput))
CreatePipeHandles(SI.hStdInput, HI)
CreatePipeHandles(HO, Si.hStdOutput)
#SI.hStdInput = GetStdHandle(STD_INPUT_HANDLE())
#SI.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE())
if poStdErrToStdOut in options:
SI.hStdError = SI.hStdOutput
HE = HO
else:
CreatePipeHandles(addr(HE), addr(Si.hStdError))
CreatePipeHandles(HE, Si.hStdError)
#SI.hStdError = GetStdHandle(STD_ERROR_HANDLE())
#result.inputHandle = open_osfhandle(HI, O_WRONLY)
#if result.inputHandle == -1'i32: OSError()
@ -224,16 +229,12 @@ when defined(Windows):
var wd: cstring = nil
if len(workingDir) > 0: wd = workingDir
if env == nil:
success = Windows.CreateProcess(nil,
cmdl, nil, nil, 0,
NORMAL_PRIORITY_CLASS, nil, wd,
addr(SI), addr(ProcInfo))
success = winlean.CreateProcess(nil,
cmdl, nil, nil, 0, NORMAL_PRIORITY_CLASS, nil, wd, SI, ProcInfo)
else:
var e = buildEnv(env)
success = Windows.CreateProcess(nil,
cmdl, nil, nil, 0,
NORMAL_PRIORITY_CLASS, e, wd,
addr(SI), addr(ProcInfo))
success = winlean.CreateProcess(nil,
cmdl, nil, nil, 0, NORMAL_PRIORITY_CLASS, e, wd, SI, ProcInfo)
dealloc(e)
dealloc(cmdl)
if success == 0:
@ -249,7 +250,7 @@ when defined(Windows):
discard ResumeThread(p.FThreadHandle)
proc running(p: PProcess): bool =
var x = waitForSingleObject(p.FThreadHandle, 50)
var x = waitForSingleObject(p.FProcessHandle, 50)
return x == WAIT_TIMEOUT
proc terminate(p: PProcess) =
@ -257,11 +258,11 @@ when defined(Windows):
discard TerminateProcess(p.FProcessHandle, 0)
proc waitForExit(p: PProcess): int =
discard WaitForSingleObject(p.FThreadHandle, Infinite)
var res: dword
discard CloseHandle(p.FThreadHandle)
discard WaitForSingleObject(p.FProcessHandle, Infinite)
var res: int32
discard GetExitCodeProcess(p.FProcessHandle, res)
result = res
discard CloseHandle(p.FThreadHandle)
discard CloseHandle(p.FProcessHandle)
proc inputStream(p: PProcess): PStream =
@ -273,6 +274,29 @@ when defined(Windows):
proc errorStream(p: PProcess): PStream =
result = newFileHandleStream(p.errorHandle)
proc executeCommand(command: string): int =
var
SI: TStartupInfo
ProcInfo: TProcessInformation
process: THandle
L: int32
SI.cb = SizeOf(SI)
SI.hStdError = GetStdHandle(STD_ERROR_HANDLE)
SI.hStdInput = GetStdHandle(STD_INPUT_HANDLE)
SI.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE)
if winlean.CreateProcess(nil, command, nil, nil, 0,
NORMAL_PRIORITY_CLASS, nil, nil, SI, ProcInfo) == 0:
OSError()
else:
Process = ProcInfo.hProcess
discard CloseHandle(ProcInfo.hThread)
if WaitForSingleObject(Process, INFINITE) != -1:
discard GetExitCodeProcess(Process, L)
result = int(L)
else:
result = -1
discard CloseHandle(Process)
else:
import posix
@ -321,15 +345,15 @@ else:
if pid == 0:
## child process:
discard close(p_stdin[writeIdx])
discard dup2(p_stdin[readIdx], readIdx)
if dup2(p_stdin[readIdx], readIdx) < 0: OSError()
discard close(p_stdout[readIdx])
discard dup2(p_stdout[writeIdx], writeIdx)
if dup2(p_stdout[writeIdx], writeIdx) < 0: OSError()
if poStdErrToStdOut in options:
discard dup2(p_stdout[writeIdx], 2)
if dup2(p_stdout[writeIdx], 2) < 0: OSError()
else:
if pipe(p_stderr) != 0'i32: OSError("failed to create a pipe")
discard close(p_stderr[readIdx])
discard dup2(p_stderr[writeIdx], 2)
if dup2(p_stderr[writeIdx], 2) < 0: OSError()
if workingDir.len > 0:
os.setCurrentDir(workingDir)
@ -347,8 +371,7 @@ else:
else:
discard execve("/bin/sh", a, ToCStringArray(env))
# too risky to raise an exception here:
echo("execve call failed: " & $strerror(errno))
quit(1)
quit("execve call failed: " & $strerror(errno))
# Parent process. Copy process information.
result.id = pid
@ -376,6 +399,7 @@ else:
if running(p): discard kill(p.id, SIGKILL)
proc waitForExit(p: PProcess): int =
result = 1
if waitPid(p.id, p.exitCode, 0) == int(p.id):
result = p.exitCode
@ -393,3 +417,11 @@ else:
var f: TFile
if not openFile(f, p.errorHandle, fmRead): OSError()
result = newFileStream(f)
proc csystem(cmd: cstring): cint {.nodecl, importc: "system".}
proc executeCommand(command: string): int =
result = csystem(command)
when isMainModule:
echo executeCommand("gcc -v")

176
lib/parsecsv.nim Normal file
View file

@ -0,0 +1,176 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2009 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements a simple high performance `CSV`:idx:
## (`comma separated value`:idx:) parser.
##
## Example: How to use the parser
## ==============================
##
## .. code-block:: nimrod
## import os, parsecsv, streams
## var s = newFileStream(ParamStr(1), fmRead)
## if s == nil: quit("cannot open the file" & ParamStr(1))
## var x: TCsvParser
## open(x, s, ParamStr(1))
## while readRow(x):
## Echo "new row: "
## for val in items(x.row):
## Echo "##", val, "##"
## close(x)
##
import
lexbase, streams
type
TCsvRow* = seq[string] ## a row in a CSV file
TCsvParser* = object of TBaseLexer ## the parser object.
row*: TCsvRow ## the current row
filename: string
sep, quote, esc: char
skipWhite: bool
currRow: int
EInvalidCsv* = object of EIO ## exception that is raised if
## a parsing error occurs
proc raiseEInvalidCsv(filename: string, line, col: int,
msg: string) {.noreturn.} =
var e: ref EInvalidCsv
new(e)
e.msg = filename & "(" & $line & ", " & $col & ") Error: " & msg
raise e
proc error(my: TCsvParser, pos: int, msg: string) =
raiseEInvalidCsv(my.filename, my.LineNumber, getColNumber(my, pos), msg)
proc open*(my: var TCsvParser, input: PStream, filename: string,
separator = ',', quote = '"', escape = '\0',
skipInitialSpace = false) =
## initializes the parser with an input stream. `Filename` is only used
## for nice error messages. The parser's behaviour can be controlled by
## the diverse optional parameters:
## - `separator`: character used to separate fields
## - `quote`: Used to quote fields containing special characters like
## `separator`, `quote` or new-line characters. '\0' disables the parsing
## of quotes.
## - `escape`: removes any special meaning from the following character;
## '\0' disables escaping; if escaping is disabled and `quote` is not '\0',
## two `quote` characters are parsed one literal `quote` character.
## - `skipInitialSpace`: If true, whitespace immediately following the
## `separator` is ignored.
lexbase.open(my, input)
my.filename = filename
my.sep = separator
my.quote = quote
my.esc = escape
my.skipWhite = skipInitialSpace
my.row = @[]
my.currRow = 0
proc parseField(my: var TCsvParser, a: var string) =
var pos = my.bufpos
var buf = my.buf
if my.skipWhite:
while buf[pos] in {' ', '\t'}: inc(pos)
setLen(a, 0) # reuse memory
if buf[pos] == my.quote and my.quote != '\0':
inc(pos)
while true:
var c = buf[pos]
if c == '\0':
my.bufpos = pos # can continue after exception?
error(my, pos, my.quote & " expected")
break
elif c == my.quote:
if my.esc == '\0' and buf[pos+1] == my.quote:
add(a, my.quote)
inc(pos, 2)
else:
inc(pos)
break
elif c == my.esc:
add(a, buf[pos+1])
inc(pos, 2)
else:
case c
of '\c':
pos = handleCR(my, pos)
add(a, "\n")
of '\l':
pos = handleLF(my, pos)
add(a, "\n")
else:
add(a, c)
inc(pos)
else:
while true:
var c = buf[pos]
if c == my.sep: break
if c in {'\c', '\l', '\0'}: break
add(a, c)
inc(pos)
my.bufpos = pos
proc processedRows*(my: var TCsvParser): int =
## returns number of the processed rows
return my.currRow
proc readRow*(my: var TCsvParser, columns = 0): bool =
## reads the next row; if `columns` > 0, it expects the row to have
## exactly this many columns. Returns false if the end of the file
## has been encountered else true.
var col = 0 # current column
var oldpos = my.bufpos
while my.buf[my.bufpos] != '\0':
var oldlen = my.row.len
if oldlen < col+1:
setLen(my.row, col+1)
my.row[col] = ""
parseField(my, my.row[col])
inc(col)
if my.buf[my.bufpos] == my.sep:
inc(my.bufpos)
else:
case my.buf[my.bufpos]
of '\c', '\l':
# skip empty lines:
while true:
case my.buf[my.bufpos]
of '\c': my.bufpos = handleCR(my, my.bufpos)
of '\l': my.bufpos = handleLF(my, my.bufpos)
else: break
of '\0': nil
else: error(my, my.bufpos, my.sep & " expected")
break
setlen(my.row, col)
result = col > 0
if result and col != columns and columns > 0:
error(my, oldpos+1, $columns & " columns expected, but found " &
$col & " columns")
inc(my.currRow)
proc close*(my: var TCsvParser) {.inline.} =
## closes the parser `my` and its associated input stream.
lexbase.close(my)
when isMainModule:
import os
var s = newFileStream(ParamStr(1), fmRead)
if s == nil: quit("cannot open the file" & ParamStr(1))
var x: TCsvParser
open(x, s, ParamStr(1))
while readRow(x):
Echo "new row: "
for val in items(x.row):
Echo "##", val, "##"
close(x)

View file

@ -22,7 +22,7 @@
## * Thus the checks would have been very difficult to implement properly with
## little benefit, especially since they are simple to implement in the
## client. The client should use the `errorMsgExpected` proc to generate
## a nice error message that fits to the other error messages this library
## a nice error message that fits the other error messages this library
## creates.
##
##

View file

@ -1,205 +0,0 @@
# This implements a new pointer set. Access time O(1). For 32 bit systems, we
# currently need 3 memory accesses.
const
PageSize = 1024 * sizeof(int)
MemAlignment = 8 # minimal memory block that can be allocated
BitsPerUnit = sizeof(int)*8
# a "unit" is a word, i.e. 4 bytes
# on a 32 bit system; I do not use the term "word" because under 32-bit
# Windows it is sometimes only 16 bits
BitsPerPage = PageSize div MemAlignment
UnitsPerPage = BitsPerPage div BitsPerUnit
# how many units do we need to describe a page:
# on 32 bit systems this is only 16 (!)
type
PPointer = ptr pointer
TCollectorData = int
TCell {.final.} = object
refcount: TCollectorData # the refcount and bit flags
typ: PNimType
stackcount: int # stack counter for debugging
drefc: int # real reference counter for debugging
PCell = ptr TCell
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 gcAlloc(size: int): pointer =
result = alloc0(size)
if result == nil: raiseOutOfMem()
# ------------------ Zero count table (ZCT) and any table (AT) -------------
# this that has to equals zero, otherwise we have to round up UnitsPerPage:
when BitsPerPage mod BitsPerUnit != 0:
{.error: "(BitsPerPage mod BitsPerUnit) should be zero!".}
# ------------------- cell set handling ------------------------------
# A cellset consists of a hash table of page descriptors. A page
# descriptor has a bit for
# every Memalignment'th byte in the page.
# However, only bits corresponding to addresses that start memory blocks
# are set.
# Page descriptors are also linked to a list; the list
# is used for easy traversing of all page descriptors; this allows a
# fast iterator.
# We use a specialized hashing scheme; the formula is :
# hash = Page bitand max
# We use linear probing with the formular: (5*h)+1
# Thus we likely get no collisions at all if the pages are given to us
# in a sequential manner by the operating system!
const
bitsPerNode = 10 # we use 10 bits per node; this means 3 memory accesses on
# 32 bit systems
type
PPageDesc = ptr TPageDesc
TBitIndex = range[0..UnitsPerPage-1]
TPageDesc {.final.} = 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.} = object
counter, max: int
head: PPageDesc
data: PPageDescArray
TSetNode {.final.} = object
n: array[0.. (1 shl bitsPerNode)-1, PSetNode]
PSetNode = ptr TSetNode
const
InitCellSetSize = 1024 # must be a power of two!
proc CellSetInit(s: var TCellSet) =
s.data = gcAlloc(InitCellSetSize * sizeof(PPageDesc))
s.max = InitCellSetSize-1
s.counter = 0
s.head = nil
proc CellSetDeinit(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 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
n: PPageDescArray
oldMax = t.max
t.max = ((t.max+1)*2)-1
n = gcAlloc((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: 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 = gcAlloc(sizeof(TPageDesc))
result.next = t.head
result.key = key
t.head = result
t.data[h] = result
# ---------- slightly higher level procs ----------------------------------
proc in_Operator(s: TCellSet, cell: PCell): bool =
var
u: TAddress
t: PPageDesc
u = cast[TAddress](cell)
t = CellSetGet(s, u /% PageSize)
if t != nil:
u = (u %% PageSize) /% MemAlignment
result = (t.bits[u /% BitsPerUnit] and (1 shl (u %% BitsPerUnit))) != 0
else:
result = false
proc incl(s: var TCellSet, cell: PCell) =
var
u: TAddress
t: PPageDesc
u = cast[TAddress](cell)
t = CellSetPut(s, u /% PageSize)
u = (u %% PageSize) /% MemAlignment
t.bits[u /% BitsPerUnit] = t.bits[u /% BitsPerUnit] or
(1 shl (u %% BitsPerUnit))
proc excl(s: var TCellSet, cell: PCell) =
var
u: TAddress
t: PPageDesc
u = cast[TAddress](cell)
t = CellSetGet(s, u /% PageSize)
if t != nil:
u = (u %% PageSize) /% MemAlignment
t.bits[u %% BitsPerUnit] = (t.bits[u /% BitsPerUnit] and
not (1 shl (u %% BitsPerUnit)))
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 *% PageSize) +%
(i*%BitsPerUnit+%j) *% MemAlignment)
inc(j)
w = w shr 1
inc(i)
r = r.next

View file

@ -104,7 +104,7 @@ proc reprSet(p: pointer, typ: PNimType): string {.compilerproc.} =
type
TReprClosure {.final.} = object # we cannot use a global variable here
# as this wouldn't be thread-safe
marked: TCellSeq
marked: TCellSet
recdepth: int # do not recurse endless
indent: int # indentation
@ -171,14 +171,14 @@ proc reprRecord(result: var string, p: pointer, typ: PNimType,
proc reprRef(result: var string, p: pointer, typ: PNimType,
cl: var TReprClosure) =
# we know that p is not nil here:
when defined(boehmGC):
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:
add(cl.marked, cell)
incl(cl.marked, cell)
add result, " --> "
reprAux(result, p, typ.base, cl)

View file

@ -865,6 +865,15 @@ proc validEmailAddress*(s: string): bool =
"aero", "jobs", "museum": return true
return false
proc validIdentifier*(s: string): bool =
## returns true if `s` is a valid identifier. A valid identifier starts
## with a character of the set `IdentStartChars` and is followed by any
## number of characters of the set `IdentChars`.
if s[0] in IdentStartChars:
for i in 1..s.len-1:
if s[i] notin IdentChars: return false
return true
proc editDistance*(a, b: string): int =
## returns the edit distance between `a` and `b`. This uses the Levenshtein
## distance algorithm with only a linear memory overhead. This implementation

View file

@ -1,7 +1,7 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2006 Andreas Rumpf
# (c) Copyright 2009 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@ -54,7 +54,12 @@ proc readLine(f: TFile): string =
rawReadLine(f, result)
proc write(f: TFile, s: string) = fputs(s, f)
proc write(f: TFile, i: int) = fprintf(f, "%ld", i)
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")

View file

@ -1,7 +1,7 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2006 Andreas Rumpf
# (c) Copyright 2009 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@ -37,27 +37,20 @@ proc eqStrings(a, b: NimString): bool {.inline, compilerProc.} =
proc rawNewString(space: int): NimString {.compilerProc.} =
var s = space
if s < 8: s = 7
when defined(boehmGC):
result = cast[NimString](boehmAllocAtomic(
sizeof(TGenericSeq) + (s+1) * sizeof(char)))
result.len = 0
result.data[0] = '\0'
else:
result = cast[NimString](newObj(addr(strDesc), sizeof(TGenericSeq) +
(s+1) * sizeof(char)))
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
when defined(boehmGC):
result.data[len] = '\0'
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' # IO.readline relies on this!
result.data[len] = '\0' # readline relies on this!
proc cstrToNimstr(str: CString): NimString {.compilerProc.} =
return toNimstr(str, c_strlen(str))
@ -184,9 +177,9 @@ proc setLengthStr(s: NimString, newLen: int): NimString {.compilerProc.} =
proc incrSeq(seq: PGenericSeq, elemSize: int): PGenericSeq {.compilerProc.} =
# increments the length by one:
# this is needed for supporting the &= operator;
# this is needed for supporting ``add``;
#
# add seq x generates:
# add(seq, x) generates:
# seq = incrSeq(seq, sizeof(x));
# seq[seq->len-1] = x;
when false:
@ -229,7 +222,7 @@ proc setLengthSeq(seq: PGenericSeq, elemSize, newLen: int): PGenericSeq {.
GenericSeqSize))
elif newLen < result.len:
# we need to decref here, otherwise the GC leaks!
when not defined(boehmGC):
when not defined(boehmGC) and not defined(nogc):
for i in newLen..result.len-1:
forAllChildrenAux(cast[pointer](cast[TAddress](result) +%
GenericSeqSize +% (i*%elemSize)),

View file

@ -1,7 +1,7 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2008 Andreas Rumpf
# (c) Copyright 2009 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
@ -415,11 +415,11 @@ proc `<=` *(x, y: int32): bool {.magic: "LeI", noSideEffect.}
proc `<=` *(x, y: int64): bool {.magic: "LeI64", noSideEffect.}
## Returns true iff `x` is less than or equal to `y`.
proc `<` *(x, y: int): bool {.magic: "LtI", noSideEffect.}
proc `<` *(x, y: int8): bool {.magic: "LtI", noSideEffect.}
proc `<` *(x, y: int16): bool {.magic: "LtI", noSideEffect.}
proc `<` *(x, y: int32): bool {.magic: "LtI", noSideEffect.}
proc `<` *(x, y: int64): bool {.magic: "LtI64", noSideEffect.}
proc `<` *(x, y: int): bool {.magic: "LtI", noSideEffect.}
proc `<` *(x, y: int8): bool {.magic: "LtI", noSideEffect.}
proc `<` *(x, y: int16): bool {.magic: "LtI", noSideEffect.}
proc `<` *(x, y: int32): bool {.magic: "LtI", noSideEffect.}
proc `<` *(x, y: int64): bool {.magic: "LtI64", noSideEffect.}
## Returns true iff `x` is less than `y`.
proc abs*(x: int): int {.magic: "AbsI", noSideEffect.}
@ -790,9 +790,10 @@ proc addQuitProc*(QuitProc: proc {.noconv.}) {.importc: "atexit", nodecl.}
# In case of an unhandled exeption the exit handlers should
# not be called explicitly! The user may decide to do this manually though.
proc copy*(s: string, first = 0): string {.importc: "copyStr", noSideEffect.}
proc copy*(s: string, first, last: int): string {.importc: "copyStrLast",
noSideEffect.}
proc copy*(s: string, first = 0): string {.
magic: "CopyStr", importc: "copyStr", noSideEffect.}
proc copy*(s: string, first, last: int): string {.
magic: "CopyStrLast", importc: "copyStrLast", noSideEffect.}
## copies a slice of `s` into a new string and returns this new
## string. The bounds `first` and `last` denote the indices of
## the first and last characters that shall be copied. If ``last``
@ -803,7 +804,8 @@ proc setLen*(s: var string, newlen: int) {.magic: "SetLengthStr".}
## If the current length is greater than the new length,
## ``s`` will be truncated.
proc newString*(len: int): string {.importc: "mnewString", noSideEffect.}
proc newString*(len: int): string {.
magic: "NewString", importc: "mnewString", noSideEffect.}
## returns a new string of length ``len`` but with uninitialized
## content. One needs to fill the string character after character
## with the index operator ``s[i]``. This procedure exists only for
@ -835,30 +837,23 @@ proc equalMem*(a, b: Pointer, size: int): bool {.
## otherwise. Like any procedure dealing with raw memory this is
## *unsafe*.
const
mallocHeader = "<stdlib.h>"
proc alloc*(size: int): pointer {.
importc: "malloc", header: mallocHeader, noconv.}
proc alloc*(size: int): pointer {.noconv.}
## allocates a new memory block with at least ``size`` bytes. The
## block has to be freed with ``realloc(block, 0)`` or
## ``dealloc(block)``. The block is not initialized, so reading
## from it before writing to it is undefined behaviour!
proc alloc0*(size: int): pointer {.
importc: "ALLOC_0", header: mallocHeader, noconv.}
proc alloc0*(size: int): pointer {.noconv.}
## allocates a new memory block with at least ``size`` bytes. The
## block has to be freed with ``realloc(block, 0)`` or
## ``dealloc(block)``. The block is initialized with all bytes
## containing zero, so it is somewhat safer than ``alloc``.
proc realloc*(p: Pointer, newsize: int): pointer {.
importc: "realloc", header: mallocHeader, noconv.}
proc realloc*(p: Pointer, newsize: int): pointer {.noconv.}
## grows or shrinks a given memory block. If p is **nil** then a new
## memory block is returned. In either way the block has at least
## ``newsize`` bytes. If ``newsize == 0`` and p is not **nil**
## ``realloc`` calls ``dealloc(p)``. In other cases the block has to
## be freed with ``dealloc``.
proc dealloc*(p: Pointer) {.
importc: "free", header: mallocHeader, noconv.}
proc dealloc*(p: Pointer) {.noconv.}
## frees the memory allocated with ``alloc``, ``alloc0`` or
## ``realloc``. This procedure is dangerous! If one forgets to
## free the memory a leak occurs; if one tries to access freed
@ -1436,66 +1431,7 @@ when not defined(EcmaScript) and not defined(NimrodVM):
else:
result = n.sons[n.len]
when defined(boehmgc):
const
boehmLib = "/opt/lib/libgc.so"
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.}
include cellsets
when defined(boehmGC):
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 growObj(old: pointer, newsize: int): pointer {.inline.} =
result = boehmRealloc(old, newsize)
proc newObj(size: int): pointer {.compilerproc.} =
result = boehmAlloc(size)
proc newSeq(baseSize, len: int): pointer {.compilerproc.} =
# XXX: overflow checks!
result = newObj(len * baseSize + GenericSeqSize)
cast[PGenericSeq](result).len = len
cast[PGenericSeq](result).space = len
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
elif not defined(nogc):
include gc
include mm
include sysstr
include assign
include repr

View file

@ -3879,7 +3879,7 @@ const
SW_SHOWNORMAL* = 1
WPF_RESTORETOMAXIMIZED* = 2
WPF_SETMINPOSITION* = 1 # Sleep
INFINITE* = 0xFFFFFFFF # SystemParametersInfo
INFINITE* = -1'i32 # SystemParametersInfo
SPI_GETBEEP* = 1
SPI_SETBEEP* = 2
SPI_GETMOUSE* = 3

108
lib/winlean.nim Normal file
View file

@ -0,0 +1,108 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2009 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements a small wrapper for some needed Win API procedures,
## so that the Nimrod compiler does not depend on the huge Windows module.
type
THandle* = int
WINBOOL* = int32
TSECURITY_ATTRIBUTES* {.final, pure.} = object
nLength*: int32
lpSecurityDescriptor*: pointer
bInheritHandle*: WINBOOL
TSTARTUPINFO* {.final, pure.} = object
cb*: int32
lpReserved*: cstring
lpDesktop*: cstring
lpTitle*: cstring
dwX*: int32
dwY*: int32
dwXSize*: int32
dwYSize*: int32
dwXCountChars*: int32
dwYCountChars*: int32
dwFillAttribute*: int32
dwFlags*: int32
wShowWindow*: int16
cbReserved2*: int16
lpReserved2*: pointer
hStdInput*: THANDLE
hStdOutput*: THANDLE
hStdError*: THANDLE
TPROCESS_INFORMATION* {.final, pure.} = object
hProcess*: THANDLE
hThread*: THANDLE
dwProcessId*: int32
dwThreadId*: int32
const
STARTF_USESHOWWINDOW* = 1'i32
STARTF_USESTDHANDLES* = 256'i32
HIGH_PRIORITY_CLASS* = 128'i32
IDLE_PRIORITY_CLASS* = 64'i32
NORMAL_PRIORITY_CLASS* = 32'i32
REALTIME_PRIORITY_CLASS* = 256'i32
WAIT_TIMEOUT* = 0x00000102'i32
INFINITE* = -1'i32
STD_INPUT_HANDLE* = -10'i32
STD_OUTPUT_HANDLE* = -11'i32
STD_ERROR_HANDLE* = -12'i32
proc CloseHandle*(hObject: THANDLE): WINBOOL {.stdcall, dynlib: "kernel32",
importc: "CloseHandle".}
proc ReadFile*(hFile: THandle, Buffer: pointer, nNumberOfBytesToRead: int32,
lpNumberOfBytesRead: var int32, lpOverlapped: pointer): WINBOOL{.
stdcall, dynlib: "kernel32", importc: "ReadFile".}
proc WriteFile*(hFile: THandle, Buffer: pointer, nNumberOfBytesToWrite: int32,
lpNumberOfBytesWritten: var int32,
lpOverlapped: pointer): WINBOOL{.
stdcall, dynlib: "kernel32", importc: "WriteFile".}
proc CreatePipe*(hReadPipe, hWritePipe: var THandle,
lpPipeAttributes: var TSECURITY_ATTRIBUTES,
nSize: int32): WINBOOL{.
stdcall, dynlib: "kernel32", importc: "CreatePipe".}
proc CreateProcess*(lpApplicationName, lpCommandLine: cstring,
lpProcessAttributes: ptr TSECURITY_ATTRIBUTES,
lpThreadAttributes: ptr TSECURITY_ATTRIBUTES,
bInheritHandles: WINBOOL, dwCreationFlags: int32,
lpEnvironment: pointer, lpCurrentDirectory: cstring,
lpStartupInfo: var TSTARTUPINFO,
lpProcessInformation: var TPROCESS_INFORMATION): WINBOOL{.
stdcall, dynlib: "kernel32", importc: "CreateProcessA".}
proc SuspendThread*(hThread: THANDLE): int32 {.stdcall, dynlib: "kernel32",
importc: "SuspendThread".}
proc ResumeThread*(hThread: THANDLE): int32 {.stdcall, dynlib: "kernel32",
importc: "ResumeThread".}
proc WaitForSingleObject*(hHandle: THANDLE, dwMilliseconds: int32): int32 {.
stdcall, dynlib: "kernel32", importc: "WaitForSingleObject".}
proc TerminateProcess*(hProcess: THANDLE, uExitCode: int): WINBOOL {.stdcall,
dynlib: "kernel32", importc: "TerminateProcess".}
proc GetExitCodeProcess*(hProcess: THANDLE, lpExitCode: var int32): WINBOOL {.
stdcall, dynlib: "kernel32", importc: "GetExitCodeProcess".}
proc GetStdHandle*(nStdHandle: int32): THANDLE {.stdcall, dynlib: "kernel32",
importc: "GetStdHandle".}
proc SetStdHandle*(nStdHandle: int32, hHandle: THANDLE): WINBOOL {.stdcall,
dynlib: "kernel32", importc: "SetStdHandle".}
proc FlushFileBuffers*(hFile: THANDLE): WINBOOL {.stdcall, dynlib: "kernel32",
importc: "FlushFileBuffers".}

File diff suppressed because it is too large Load diff

406
lib/xmlgen.nim Normal file
View file

@ -0,0 +1,406 @@
#
#
# Nimrod's Runtime Library
# (c) Copyright 2009 Andreas Rumpf
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## This module implements a simple `XML`:idx: and `HTML`:idx: code
## generator. Each commonly used HTML tag has a corresponding macro
## that generates a string with its HTML representation.
##
## Example:
##
## .. code-block:: nimrod
## var nim = "Nimrod"
## echo h1(a(href="http://force7.de/nimrod", nim))
##
## Writes the string::
##
## <h1><a href="http://force7.de/nimrod">Nimrod</a></h1>
##
import
macros, strutils
const
coreAttr* = " id class title style "
eventAttr* = " onclick ondblclick onmousedown onmouseup " &
"onmouseover onmousemove onmouseout onkeypress onkeydown onkeyup "
commonAttr* = coreAttr & eventAttr
proc getIdent(e: PNimrodNode): string {.compileTime.} =
case e.kind
of nnkIdent: result = normalize($e.ident)
of nnkAccQuoted: result = getIdent(e[0])
else: error("cannot extract identifier from node: " & toStrLit(e).strVal)
proc delete[T](s: var seq[T], attr: T): bool =
var idx = find(s, attr)
if idx >= 0:
var L = s.len
s[idx] = s[L-1]
setLen(s, L-1)
result = true
proc xmlCheckedTag*(e: PNimrodNode, tag: string,
optAttr = "", reqAttr = "",
isLeaf = false): PNimrodNode {.compileTime.} =
## use this procedure to define a new XML tag
# copy the attributes; when iterating over them these lists
# will be modified, so that each attribute is only given one value
var req = splitSeq(reqAttr)
var opt = splitSeq(optAttr)
result = newNimNode(nnkBracket, e)
result.add(newStrLitNode("<"))
result.add(newStrLitNode(tag))
# first pass over attributes:
for i in 1..e.len-1:
if e[i].kind == nnkExprEqExpr:
var name = getIdent(e[i][0])
if delete(req, name) or delete(opt, name):
result.add(newStrLitNode(" "))
result.add(newStrLitNode(name))
result.add(newStrLitNode("=\""))
result.add(e[i][1])
result.add(newStrLitNode("\""))
else:
error("invalid attribute for '" & tag & "' element: " & name)
# check each required attribute exists:
if req.len > 0:
error(req[0] & " attribute for '" & tag & "' element expected")
if isLeaf:
for i in 1..e.len-1:
if e[i].kind != nnkExprEqExpr:
error("element " & tag & " cannot be nested")
result.add(newStrLitNode(" />"))
else:
result.add(newStrLitNode(">"))
# second pass over elements:
for i in 1..e.len-1:
if e[i].kind != nnkExprEqExpr: result.add(e[i])
result.add(newStrLitNode("</"))
result.add(newStrLitNode(tag))
result.add(newStrLitNode(">"))
result = NestList(!"&", result)
macro a*(e: expr): expr =
## generates the HTML ``a`` element.
result = xmlCheckedTag(e, "a", "href charset type hreflang rel rev " &
"accesskey tabindex" & commonAttr)
macro acronym*(e: expr): expr =
## generates the HTML ``acronym`` element.
result = xmlCheckedTag(e, "acronym", commonAttr)
macro address*(e: expr): expr =
## generates the HTML ``address`` element.
result = xmlCheckedTag(e, "address", commonAttr)
macro area*(e: expr): expr =
## generates the HTML ``area`` element.
result = xmlCheckedTag(e, "area", "shape coords href nohref" &
" accesskey tabindex" & commonAttr, "alt", true)
macro b*(e: expr): expr =
## generates the HTML ``b`` element.
result = xmlCheckedTag(e, "b", commonAttr)
macro base*(e: expr): expr =
## generates the HTML ``base`` element.
result = xmlCheckedTag(e, "base", "", "href", true)
macro big*(e: expr): expr =
## generates the HTML ``big`` element.
result = xmlCheckedTag(e, "big", commonAttr)
macro blockquote*(e: expr): expr =
## generates the HTML ``blockquote`` element.
result = xmlCheckedTag(e, "blockquote", " cite" & commonAttr)
macro body*(e: expr): expr =
## generates the HTML ``body`` element.
result = xmlCheckedTag(e, "body", commonAttr)
macro br*(e: expr): expr =
## generates the HTML ``br`` element.
result = xmlCheckedTag(e, "br", "", "", true)
macro button*(e: expr): expr =
## generates the HTML ``button`` element.
result = xmlCheckedTag(e, "button", "accesskey tabindex " &
"disabled name type value" & commonAttr)
macro caption*(e: expr): expr =
## generates the HTML ``caption`` element.
result = xmlCheckedTag(e, "caption", commonAttr)
macro cite*(e: expr): expr =
## generates the HTML ``cite`` element.
result = xmlCheckedTag(e, "cite", commonAttr)
macro code*(e: expr): expr =
## generates the HTML ``code`` element.
result = xmlCheckedTag(e, "code", commonAttr)
macro col*(e: expr): expr =
## generates the HTML ``col`` element.
result = xmlCheckedTag(e, "col", "span align valign" & commonAttr, "", true)
macro colgroup*(e: expr): expr =
## generates the HTML ``colgroup`` element.
result = xmlCheckedTag(e, "colgroup", "span align valign" & commonAttr)
macro dd*(e: expr): expr =
## generates the HTML ``dd`` element.
result = xmlCheckedTag(e, "dd", commonAttr)
macro del*(e: expr): expr =
## generates the HTML ``del`` element.
result = xmlCheckedTag(e, "del", "cite datetime" & commonAttr)
macro dfn*(e: expr): expr =
## generates the HTML ``dfn`` element.
result = xmlCheckedTag(e, "dfn", commonAttr)
macro `div`*(e: expr): expr =
## generates the HTML ``div`` element.
result = xmlCheckedTag(e, "div", commonAttr)
macro dl*(e: expr): expr =
## generates the HTML ``dl`` element.
result = xmlCheckedTag(e, "dl", commonAttr)
macro dt*(e: expr): expr =
## generates the HTML ``dt`` element.
result = xmlCheckedTag(e, "dt", commonAttr)
macro em*(e: expr): expr =
## generates the HTML ``em`` element.
result = xmlCheckedTag(e, "em", commonAttr)
macro fieldset*(e: expr): expr =
## generates the HTML ``fieldset`` element.
result = xmlCheckedTag(e, "fieldset", commonAttr)
macro form*(e: expr): expr =
## generates the HTML ``form`` element.
result = xmlCheckedTag(e, "form", "method encype accept accept-charset" &
commonAttr, "action")
macro h1*(e: expr): expr =
## generates the HTML ``h1`` element.
result = xmlCheckedTag(e, "h1", commonAttr)
macro h2*(e: expr): expr =
## generates the HTML ``h2`` element.
result = xmlCheckedTag(e, "h2", commonAttr)
macro h3*(e: expr): expr =
## generates the HTML ``h3`` element.
result = xmlCheckedTag(e, "h3", commonAttr)
macro h4*(e: expr): expr =
## generates the HTML ``h4`` element.
result = xmlCheckedTag(e, "h4", commonAttr)
macro h5*(e: expr): expr =
## generates the HTML ``h5`` element.
result = xmlCheckedTag(e, "h5", commonAttr)
macro h6*(e: expr): expr =
## generates the HTML ``h6`` element.
result = xmlCheckedTag(e, "h6", commonAttr)
macro head*(e: expr): expr =
## generates the HTML ``head`` element.
result = xmlCheckedTag(e, "head", "profile")
macro html*(e: expr): expr =
## generates the HTML ``html`` element.
result = xmlCheckedTag(e, "html", "", "xmlns")
macro hr*(e: expr): expr =
## generates the HTML ``hr`` element.
result = xmlCheckedTag(e, "hr", commonAttr, "", true)
macro i*(e: expr): expr =
## generates the HTML ``i`` element.
result = xmlCheckedTag(e, "i", commonAttr)
macro img*(e: expr): expr =
## generates the HTML ``img`` element.
result = xmlCheckedTag(e, "img", "longdesc height width", "src alt", true)
macro input*(e: expr): expr =
## generates the HTML ``input`` element.
result = xmlCheckedTag(e, "input", "name type value checked maxlength src" &
" alt accept disabled readonly accesskey tabindex" & commonAttr, "", true)
macro ins*(e: expr): expr =
## generates the HTML ``ins`` element.
result = xmlCheckedTag(e, "ins", "cite datetime" & commonAttr)
macro kbd*(e: expr): expr =
## generates the HTML ``kbd`` element.
result = xmlCheckedTag(e, "kbd", commonAttr)
macro label*(e: expr): expr =
## generates the HTML ``label`` element.
result = xmlCheckedTag(e, "label", "for accesskey" & commonAttr)
macro legend*(e: expr): expr =
## generates the HTML ``legend`` element.
result = xmlCheckedTag(e, "legend", "accesskey" & commonAttr)
macro li*(e: expr): expr =
## generates the HTML ``li`` element.
result = xmlCheckedTag(e, "li", commonAttr)
macro link*(e: expr): expr =
## generates the HTML ``link`` element.
result = xmlCheckedTag(e, "link", "href charset hreflang type rel rev media" &
commonAttr, "", true)
macro map*(e: expr): expr =
## generates the HTML ``map`` element.
result = xmlCheckedTag(e, "map", "class title" & eventAttr, "id", false)
macro meta*(e: expr): expr =
## generates the HTML ``meta`` element.
result = xmlCheckedTag(e, "meta", "name http-equiv scheme", "content", true)
macro noscript*(e: expr): expr =
## generates the HTML ``noscript`` element.
result = xmlCheckedTag(e, "noscript", commonAttr)
macro `object`*(e: expr): expr =
## generates the HTML ``object`` element.
result = xmlCheckedTag(e, "object", "classid data codebase declare type " &
"codetype archive standby width height name tabindex" & commonAttr)
macro ol*(e: expr): expr =
## generates the HTML ``ol`` element.
result = xmlCheckedTag(e, "ol", commonAttr)
macro optgroup*(e: expr): expr =
## generates the HTML ``optgroup`` element.
result = xmlCheckedTag(e, "optgroup", "disabled" & commonAttr, "label", false)
macro option*(e: expr): expr =
## generates the HTML ``option`` element.
result = xmlCheckedTag(e, "option", "selected value" & commonAttr)
macro p*(e: expr): expr =
## generates the HTML ``p`` element.
result = xmlCheckedTag(e, "p", commonAttr)
macro param*(e: expr): expr =
## generates the HTML ``param`` element.
result = xmlCheckedTag(e, "param", "value id type valuetype", "name", true)
macro pre*(e: expr): expr =
## generates the HTML ``pre`` element.
result = xmlCheckedTag(e, "pre", commonAttr)
macro q*(e: expr): expr =
## generates the HTML ``q`` element.
result = xmlCheckedTag(e, "q", "cite" & commonAttr)
macro samp*(e: expr): expr =
## generates the HTML ``samp`` element.
result = xmlCheckedTag(e, "samp", commonAttr)
macro script*(e: expr): expr =
## generates the HTML ``script`` element.
result = xmlCheckedTag(e, "script", "src charset defer", "type", false)
macro select*(e: expr): expr =
## generates the HTML ``select`` element.
result = xmlCheckedTag(e, "select", "name size multiple disabled tabindex" &
commonAttr)
macro small*(e: expr): expr =
## generates the HTML ``small`` element.
result = xmlCheckedTag(e, "small", commonAttr)
macro span*(e: expr): expr =
## generates the HTML ``span`` element.
result = xmlCheckedTag(e, "span", commonAttr)
macro strong*(e: expr): expr =
## generates the HTML ``strong`` element.
result = xmlCheckedTag(e, "strong", commonAttr)
macro style*(e: expr): expr =
## generates the HTML ``style`` element.
result = xmlCheckedTag(e, "style", "media title", "type")
macro sub*(e: expr): expr =
## generates the HTML ``sub`` element.
result = xmlCheckedTag(e, "sub", commonAttr)
macro sup*(e: expr): expr =
## generates the HTML ``sup`` element.
result = xmlCheckedTag(e, "sup", commonAttr)
macro table*(e: expr): expr =
## generates the HTML ``table`` element.
result = xmlCheckedTag(e, "table", "summary border cellpadding cellspacing" &
" frame rules width" & commonAttr)
macro tbody*(e: expr): expr =
## generates the HTML ``tbody`` element.
result = xmlCheckedTag(e, "tbody", "align valign" & commonAttr)
macro td*(e: expr): expr =
## generates the HTML ``td`` element.
result = xmlCheckedTag(e, "td", "colspan rowspan abbr axis headers scope" &
" align valign" & commonAttr)
macro textarea*(e: expr): expr =
## generates the HTML ``textarea`` element.
result = xmlCheckedTag(e, "textarea", " name disabled readonly accesskey" &
" tabindex" & commonAttr, "rows cols", false)
macro tfoot*(e: expr): expr =
## generates the HTML ``tfoot`` element.
result = xmlCheckedTag(e, "tfoot", "align valign" & commonAttr)
macro th*(e: expr): expr =
## generates the HTML ``th`` element.
result = xmlCheckedTag(e, "th", "colspan rowspan abbr axis headers scope" &
" align valign" & commonAttr)
macro thead*(e: expr): expr =
## generates the HTML ``thead`` element.
result = xmlCheckedTag(e, "thead", "align valign" & commonAttr)
macro title*(e: expr): expr =
## generates the HTML ``title`` element.
result = xmlCheckedTag(e, "title")
macro tr*(e: expr): expr =
## generates the HTML ``tr`` element.
result = xmlCheckedTag(e, "tr", "align valign" & commonAttr)
macro tt*(e: expr): expr =
## generates the HTML ``tt`` element.
result = xmlCheckedTag(e, "tt", commonAttr)
macro ul*(e: expr): expr =
## generates the HTML ``ul`` element.
result = xmlCheckedTag(e, "ul", commonAttr)
macro `var`*(e: expr): expr =
## generates the HTML ``var`` element.
result = xmlCheckedTag(e, "var", commonAttr)
when isMainModule:
var nim = "Nimrod"
echo h1(a(href="http://force7.de/nimrod", nim))