Revert "BROKEN gc"

This reverts commit 0f9440dfb0.
This commit is contained in:
prod2 2022-02-14 07:15:11 +01:00
parent 0f9440dfb0
commit 545322bf96
13 changed files with 68 additions and 340 deletions

View File

@ -16,7 +16,7 @@ requires "nim >= 1.6.2"
task test, "run tests":
exec "nim c --gc:arc -d:release --skipProjCfg --skipParentCfg --out:bin/nds src/nds.nim"
exec "nim c --gc:arc -d:release --skipProjCfg --skipParentCfg -r tests/test.nim"
exec "nim c --gc:arc -d:debug --skipProjCfg --skipParentCfg -r tests/test.nim"
exec "rm tests/test"
task debug, "build nds for debugging":

View File

@ -1,8 +1,6 @@
import ../scanner
import ../chunk
import ../config
import ../types/ndobject
import ../memory
import types
import utils
@ -15,8 +13,6 @@ import functions
import statement
proc compile*(comp: Compiler) =
let enableGCOld = enableGC
enableGC = false
comp.scanner = newScanner(comp.source)
comp.writeChunk(0, opNil)
# the starting stackIndex is 0, which points to this nil
@ -29,4 +25,3 @@ proc compile*(comp: Compiler) =
when debugDumpChunk:
if not comp.hadError:
comp.chunk.disassembleChunk()
enableGC = enableGCOld

View File

@ -14,8 +14,6 @@ const assertionsVM* = defined(debug) or defined(release) # sanity checks in the
const boundsChecks* = defined(debug) or defined(release)
const profileInstructions* = defined(ndsprofile) # if true, the time spent on every opcode is measured
const debugClosures* = defined(debug) # specific closure debug switches
const debugGC* = defined(debug) # debug GC
const stressGC* = true # garbage collect on every allocation
# choose a line editor for the repl
const lineEditor = leRdstdin

View File

@ -1,146 +0,0 @@
# marking for mark'n'sweep
import types/ndstack
import types/ndobject
import types/value
import types/ndstring
import types/hashtable
import types/closure
import types/ndlist
import memory
import config
import strformat
import strutils
import pointerutils
# globals
var
stack*: Stack[NdValue] = newStack[NdValue](256)
globals*: Table[NdValue, NdValue]
openUpvalues*: Upvalue[NdValue] = nil
var grayObjects: seq[ptr NdObject]
proc markObject*(obj: ptr NdObject) =
if obj.isMarked:
return
when debugGC:
let niceAddr = cast[uint](obj).toHex()
echo &"Marking object {niceAddr}."
obj.isMarked = true
grayObjects.add(obj)
proc markValue*(val: NdValue) =
if not val.isObject():
return
let obj: ptr NdObject = val.asObject()
obj.markObject()
proc markRoots =
var slot = 0
var stacklen = stack.len()
while slot < stacklen:
stack.getIndex(slot).markValue()
slot.inc
cast[NdTable[NdValue, NdValue]](globals.addr).markObject()
var upvalue = openUpvalues
while upvalue != nil:
upvalue.markObject()
upvalue = upvalue.next
proc blackenObject(obj: ptr NdObject) =
when debugGC:
let niceAddr = cast[uint](obj).toHex()
echo &"blackening {niceAddr}."
case obj.kind:
of noInvalid:
raise newException(Defect, "Somewhere an object with invalid object type was created.")
of noString:
discard
of noClosure:
let clos = cast[Closure[NdValue]](obj)
let count = clos.upvalueCount
var i = 0
while i < count:
let upval = clos.get(i)
if upval != nil:
markObject(upval)
i.inc
of noUpvalue:
let upval = cast[Upvalue[NdValue]](obj)
markValue(upval.location[])
of noList:
let list = cast[List[NdValue]](obj)
let count = list.getLength()
var i = 0
while i < count:
let elem = list.getIndex(i)
markValue(elem)
i.inc
of noTable:
let tbl = cast[NdTable[NdValue, NdValue]](obj)
for elem in tbl[].iterate():
markValue(elem)
proc traceReferences =
while grayObjects.len() > 0:
let obj = grayObjects.pop()
obj.blackenObject()
proc freeObj(obj: ptr NdObject) =
case obj.kind:
of noInvalid:
raise newException(Defect, "Somewhere an object with invalid object type was created.")
of noString:
let str = cast[NdString](obj)
str.free()
of noTable:
let tbl = cast[NdTable[NdValue, NdValue]](obj)
tbl.free()
of noClosure:
let clos = cast[Closure[NdValue]](obj)
clos.free()
of noList:
let list = cast[List[NdValue]](obj)
list.free()
of noUpvalue:
let upval = cast[Upvalue[NdValue]](obj)
upval.free()
proc sweep =
var previous: ptr NdObject = nil
var obj = objects
while obj != nil:
if obj.isMarked:
obj.isMarked = false
previous = obj
obj = obj.nextObj
else:
let unreached = obj
obj = obj.nextObj
if previous != nil:
previous.nextObj = obj
else:
objects = obj
unreached.freeObj()
proc icollectGarbage =
if not enableGC:
return
when debugGC:
echo "-- gc begin"
markRoots()
traceReferences()
tableRemoveWhite() # defined in types/ndstring
sweep()
when debugGC:
echo "-- gc end"
collectGarbage = icollectGarbage

View File

@ -1,41 +0,0 @@
import config
import strutils
import strformat
var collectGarbage*: proc: void {.closure.}
var enableGC* = false
# set by the compiler/VM as needed
proc ndAlloc*(size: int): pointer =
when debugGC:
echo &"Allocating {$size} bytes."
when stressGC:
if enableGC:
collectGarbage()
alloc(size)
proc ndAlloc0*(size: int): pointer =
when debugGC:
echo &"Allocating and zeroing {$size} bytes."
when stressGC:
if enableGC:
collectGarbage()
alloc0(size)
proc ndRealloc*[T](source: ptr T, oldsize: int, newsize: int): ptr T =
when debugGC:
let niceAddr = cast[uint](source).toHex()
echo &"Reallocating {niceAddr} from size {$oldsize} to {$newsize}."
if newsize > oldsize:
when stressGC:
if enableGC:
collectGarbage()
discard
cast[ptr T](realloc(source, newsize))
proc ndDealloc*[T](mem: ptr T) =
when debugGC:
let niceAddr = cast[uint](mem).toHex()
echo &"Freeing {niceAddr}"
dealloc(mem)

View File

@ -1,33 +1,23 @@
import strformat
import strutils
import ndobject
import ../memory
type
UpvalueObj[T] = object of NdObject
UpvalueObj[T] = object
location*: ptr T
next*: Upvalue[T]
closed*: T
Upvalue*[T] = ptr UpvalueObj[T]
ClosureObj[T] = object of NdObject
ClosureObj[T] = object
start: ptr uint8
upvalueCount*: int
upvalues: UncheckedArray[Upvalue[T]]
Closure*[T] = ptr ClosureObj[T]
proc free*[T](clos: Closure[T]) =
ndDealloc(clos)
proc free*[T](upval: Upvalue[T]) =
ndDealloc(upval)
proc newClosure*[T](start: ptr uint8, upvalueCount: int): Closure[T] =
result = cast[Closure[T]](ndAlloc0(8 * upvalueCount + sizeof(ClosureObj[T])))
result.kind = noClosure
result.newObject()
result = cast[Closure[T]](alloc0(8 * upvalueCount + sizeof(ClosureObj[T])))
result.start = start
result.upvalueCount = upvalueCount
for i in 0 .. upvalueCount:
@ -57,9 +47,7 @@ proc debugStr*[T](clos: Closure[T]): string =
result &= ")"
proc newUpvalue*[T](location: ptr T): Upvalue[T] =
result = cast[Upvalue[T]](ndAlloc0(sizeof(UpvalueObj[T])))
result.kind = noUpvalue
result.newObject()
result = cast[Upvalue[T]](alloc0(sizeof(UpvalueObj[T])))
result.location = location
result.next = nil

View File

@ -1,8 +1,6 @@
# The hash table implementation for string interning and globals
import bitops
import ndobject
import ../memory
const tableMaxLoad = 0.75
const tableInitSize = 8
@ -16,7 +14,7 @@ type
key: U
value: V
Table*[U, V] = object of NdObject
Table*[U, V] = object
count: int
cap: int
entries: ptr UncheckedArray[Entry[U, V]]
@ -26,24 +24,16 @@ type
proc newTable*[U, V]: Table[U, V] =
result.cap = 0
result.count = 0
result.kind = noTable
# not calling result.newObject()
proc newNdTable*[U, V]: NdTable[U, V] =
result = cast[NdTable[U, V]](ndAlloc(sizeof(Table[U, V])))
result[].kind = noTable
result.newObject()
result = cast[NdTable[U, V]](alloc(sizeof(Table[U, V])))
result[].cap = 0
result[].count = 0
result[].entries = nil # must be set, because dealloc will be ran on it otherwise
proc free*[U, V](tbl: Table[U, V]) =
proc free*[U, V](tbl: var Table[U, V]) =
if tbl.entries != nil:
ndDealloc(tbl.entries)
proc free*[U, V](tbl: NdTable[U, V]) =
tbl[].free()
ndDealloc(tbl)
dealloc(tbl.entries)
proc findEntry[U, V](entries: ptr UncheckedArray[Entry[U, V]], cap: int, key: U): ptr Entry[U, V] =
mixin fnv1a, equal
@ -70,7 +60,7 @@ proc grow[U, V](tbl: var Table[U, V]): int =
tableInitSize
proc adjustCapacity[U, V](tbl: var Table[U, V], newcap: int) =
let entries: ptr UncheckedArray[Entry[U, V]] = cast[ptr UncheckedArray[Entry[U, V]]](ndAlloc0(newcap * sizeof(Entry[U, V])))
let entries: ptr UncheckedArray[Entry[U, V]] = cast[ptr UncheckedArray[Entry[U, V]]](alloc0(newcap * sizeof(Entry[U, V])))
tbl.count = 0
if tbl.entries != nil:
@ -83,7 +73,7 @@ proc adjustCapacity[U, V](tbl: var Table[U, V], newcap: int) =
dest[].entryStatus = esAlive
tbl.count.inc
ndDealloc(tbl.entries)
dealloc(tbl.entries)
tbl.entries = entries
tbl.cap = newcap
@ -151,14 +141,6 @@ proc tableDelete*[U, V](tbl: Table[U, V], key: U): bool =
proc getLength*[U, V](tbl: NdTable[U, V]): int =
tbl.count.int
iterator iterate*[T](tbl: Table[T, T]): T =
if tbl.entries != nil:
for i in countup(0, tbl.cap-1):
let entry = tbl.entries[i]
if entry.entryStatus == esAlive:
yield entry.key
yield entry.value
proc `$`*[U, V](tbl: NdTable[U, V], tagged: V): string =
if tbl[].count == 0:
return "@{}"

View File

@ -1,6 +1,4 @@
import strformat
import ndobject
import ../memory
# configure ndlist here
import ../config
@ -13,41 +11,37 @@ const growthFactor = 2
const startCap = 8
type
ListObj[T] = object of NdObject
ListObj[T] = object
len: int
cap: int
entries: ptr UncheckedArray[T]
List*[T] = ptr ListObj[T]
proc newList*[T](): List[T] =
result = cast[List[T]](ndAlloc(sizeof(ListObj[T])))
result = cast[List[T]](alloc(sizeof(ListObj[T])))
result.len = 0
result.cap = 0
result.kind = noList
result.newObject()
proc newListCopymem*[T](start: ptr T, len: int): List[T] =
result = newList[T]()
result.len = len
result.cap = len
result.entries = cast[ptr UncheckedArray[T]](ndAlloc(sizeof(T) * len))
result.entries = cast[ptr UncheckedArray[T]](alloc(sizeof(T) * len))
copyMem(result.entries, start, len * sizeof(T))
proc free*[T](list: List[T]) =
proc free*[T](list: var List[T]) =
## dealloc's the list object
list.ndDealloc()
list.dealloc()
proc grow[T](list: var List[T]) =
## growth the list's capacity
let oldcap = list.cap
let oldsize = oldcap * sizeof(T)
let newcap = if oldcap == 0: startCap else: oldcap * growthFactor
let newcap = if list.cap == 0: startCap else: list.cap * growthFactor
let size = newcap * sizeof(T)
if oldcap == 0:
list.entries = cast[ptr UncheckedArray[T]](size.ndAlloc())
if list.cap == 0:
list.entries = cast[ptr UncheckedArray[T]](size.alloc())
list.cap = newcap
else:
list.entries = cast[ptr UncheckedArray[T]](list.entries.ndRealloc(oldsize, size))
list.entries = cast[ptr UncheckedArray[T]](list.entries.realloc(size))
list.cap = newcap
proc add*[T](list: var List[T], item: T) =

View File

@ -1,22 +0,0 @@
# generic object type
import ../config
type
NdObjectKind* = enum
noInvalid, noString, noClosure, noUpvalue, noList, noTable
NdObject* = object of RootObj
kind*: NdObjectKind
isMarked*: bool
nextObj*: ptr NdObject
var objects*: ptr NdObject
template newObject*(obj: ptr NdObject) =
when assertionsVM:
if obj.kind == noInvalid:
raise newException(Defect, "Somewhere an object with invalid object type was created.")
obj.nextObj = objects
objects = obj

View File

@ -1,16 +1,13 @@
import hashtable
import ndobject
import ../memory
type
NdStringObj* = object of NdObject
NdString* = ptr object
len*: uint32
hash*: uint32
chars*: UncheckedArray[char]
NdString* = ptr NdStringObj
proc free*(ndStr: NdString) =
ndDealloc(ndStr)
proc free*(ndStr: var NdString) =
dealloc(ndStr)
# hashes
@ -35,6 +32,7 @@ proc fnv1a*(str: char): int = # SHOULD RETURN THE SAME AS A 1 LENGTH STRING
hash *= 16777619
return hash.int
# equals
proc equal*(left, right: NdString): bool =
@ -42,13 +40,6 @@ proc equal*(left, right: NdString): bool =
var ndStrings = newTable[NdString, NdString]()
proc tableRemoveWhite* =
for elem in ndStrings.iterate():
if elem != nil:
# vals are nil, keys are not
if not elem.isMarked:
discard tableDelete(ndStrings, elem)
proc newString*(str: string): NdString =
let strlen = str.len()
let hash = str.fnv1a()
@ -62,12 +53,10 @@ proc newString*(str: string): NdString =
if interned != nil:
return interned
let len = sizeof(NdStringObj) + strlen
result = cast[NdString](ndAlloc(len))
let len = 8 + strlen
result = cast[NdString](alloc(len))
result.len = strlen.uint32
result.hash = hash.uint32
result.kind = noString
result.newObject()
if strlen > 0:
copyMem(result.chars[0].unsafeAddr, str[0].unsafeAddr, strlen)
@ -80,16 +69,15 @@ proc newString*(str: char): NdString =
if interned != nil:
return interned
let len = sizeof(NdStringObj) + 1
result = cast[NdString](ndAlloc(len))
let len = 8 + 1
result = cast[NdString](alloc(len))
result.len = 1.uint32
result.hash = hash.uint32
result.chars[0] = str
result.kind = noString
result.newObject()
discard ndStrings.tableSet(result, nil)
proc resetInternedStrings* =
ndStrings.free()
ndStrings = newTable[NdString, NdString]()

View File

@ -1,8 +1,5 @@
import ../pointerutils
import ndobject
import ../memory
# configure stacks here
import ../config
#const boundsChecks = defined(debug)
@ -18,7 +15,7 @@ type
cap*: int
proc newStack*[T](startingCap: int): Stack[T] =
result.start = cast[ptr T](ndAlloc(startingCap * sizeof(T)))
result.start = cast[ptr T](alloc(startingCap * sizeof(T)))
result.top = result.start.psub(sizeof(T))
result.cap = startingCap
@ -26,16 +23,14 @@ proc free*[T](stack: var Stack[T]) =
## dealloc's the stack object
## if the stack contains pointers, those should be freed before destroying the stack
stack.cap = 0
stack.start.ndDealloc()
stack.start.dealloc()
stack.start = nil
stack.top = nil
proc grow[T](stack: var Stack[T], len: int) =
## growth the stack's capacity and increments the top's index by one
let oldcap = stack.cap
let newcap = stack.cap * growthFactor
stack.cap = newcap
stack.start = cast[ptr T](ndRealloc(stack.start, oldcap * sizeof(T), newcap * sizeof(T)))
stack.cap *= growthFactor
stack.start = cast[ptr T](realloc(stack.start, stack.cap * sizeof(T)))
stack.top = stack.start.padd(len * sizeof(T))
proc shrink[T](stack: var Stack[T]) =

View File

@ -1,12 +1,7 @@
import strformat
import strutils
import bitops
export bitops
import ../config
import ndobject
export ndobject
import ndstring
import ndlist
import hashtable
@ -26,12 +21,12 @@ type
# bits 49-48 determine type:
# if bit 63 is 0:
# 00 -> nil or bool (singletons)
# 01 -> object (string, closure, list, table, upvalue) (formerly string)
# 01 -> string
# 10 -> funct
# 11 -> (formerly closure) free
# 11 -> closure
# if bit 63 is 1:
# 00 -> (formerly list) free
# 01 -> (formerly table) free
# 00 -> list
# 01 -> table
# 10 -> native funct
# 11 -> unused for now
@ -42,14 +37,13 @@ const ndTrue* = 0x7ffc000000000002'u
const ndFalse* = 0x7ffc000000000003'u
# 0111 1111 1111 11*01* 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
#const tagString* = 0x7ffd000000000000'u
const tagObject* = 0x7ffd000000000000'u
const tagString* = 0x7ffd000000000000'u
# 0111 1111 1111 11*10* 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000
const tagFunct* = 0x7ffe000000000000'u
# you can imagine these now...
#const tagClosure* = 0x7fff000000000000'u
#const tagList* = 0xfffc000000000000'u
#const tagTable* = 0xfffd000000000000'u
const tagClosure* = 0x7fff000000000000'u
const tagList* = 0xfffc000000000000'u
const tagTable* = 0xfffd000000000000'u
const tagNative* = 0xfffe000000000000'u
const mask48* = 0xffff000000000000'u
@ -65,26 +59,20 @@ template isBool*(val: NdValue): bool =
template isFloat*(val: NdValue): bool =
val.bitand(qNan) != qNan
template asObject*(val: NdValue): ptr NdObject =
cast[ptr NdObject](val.bitand(mask48.bitnot))
template isObject*(val: NdValue): bool =
val.bitand(mask48) == tagObject
template isString*(val: NdValue): bool =
val.isObject() and val.asObject().kind == noString
val.bitand(mask48) == tagString
template isFunct*(val: NdValue): bool =
val.bitand(mask48) == tagFunct
template isClosure*(val: NdValue): bool =
val.isObject() and val.asObject().kind == noClosure
val.bitand(mask48) == tagClosure
template isList*(val: NdValue): bool =
val.isObject() and val.asObject().kind == noList
val.bitand(mask48) == tagList
template isTable*(val: NdValue): bool =
val.isObject() and val.asObject().kind == noTable
val.bitand(mask48) == tagTable
template isNative*(val: NdValue): bool =
val.bitand(mask48) == tagNative
@ -124,7 +112,7 @@ template fromFloat*(val: float): NdValue =
cast[NdValue](val)
template fromNdString*(val: NdString): NdValue =
cast[uint](val).bitor(tagObject)
cast[uint](val).bitor(tagString)
template fromNimString*(sval: string): NdValue =
fromNdString(newString(sval))
@ -133,13 +121,13 @@ template fromFunct*(val: ptr uint8): NdValue =
cast[uint](val).bitor(tagFunct)
template fromClosure*(val: Closure[NdValue]): NdValue =
cast[uint](val).bitor(tagObject)
cast[uint](val).bitor(tagClosure)
template fromList*(val: List[NdValue]): NdValue =
cast[uint](val).bitor(tagObject)
cast[uint](val).bitor(tagList)
template fromTable*(val: NdTable[NdValue, NdValue]): NdValue =
cast[uint](val).bitor(tagObject)
cast[uint](val).bitor(tagTable)
template fromNative*(val: Native): NdValue =
cast[uint](val).bitor(tagNative)

View File

@ -4,8 +4,7 @@ import chunk
import config
import pointerutils
import types/ndstack
import types/ndobject
import types/stack
import types/ndstring
import bitops # needed for value's templates
import types/value
@ -16,9 +15,6 @@ import types/native
import lib/main
import memory
import gc
when debugVM:
import terminal
@ -36,17 +32,20 @@ type
Frame = object
stackBottom: int # the absolute index of where 0 inside the frame is
returnIp: ptr uint8
# closure: Closure[NdValue]
InterpretResult* = enum
irOK, irRuntimeError
proc run*(chunk: Chunk): InterpretResult =
enableGC = false
var
ip: ptr uint8 = chunk.code[0].unsafeAddr
stack: Stack[NdValue] = newStack[NdValue](256)
hadError: bool
globals: Table[NdValue, NdValue]
frames: Stack[Frame] = newStack[Frame](4)
objects: ptr NdObject
openUpvalues: Upvalue[NdValue] = nil
proc runtimeError(msg: string) =
let ii = ip.pdiff(chunk.code[0].unsafeAddr)
@ -142,7 +141,6 @@ proc run*(chunk: Chunk): InterpretResult =
let globalsVal = cast[NdTable[NdValue, NdValue]](globals.addr)
discard globals.tableSet(globalsKey, globalsVal.fromTable())
enableGC = true
while true:
{.computedgoto.} # See https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma
@ -161,6 +159,15 @@ proc run*(chunk: Chunk): InterpretResult =
msg &= "]"
echo msg
when debugClosures:
msg = " Closures: [ "
#for i in 0 .. frames.high():
#if frames[i].closure != nil:
# msg &= debugStr(frames[i].closure) & " "
msg &= "]"
echo msg
var ii = ip.pdiff(chunk.code[0].unsafeAddr) - 1
var ll = -1
setForegroundColor(fgYellow)
@ -354,7 +361,7 @@ proc run*(chunk: Chunk): InterpretResult =
of opSetUpvalue:
let slot = ip.readDU8()
when debugClosures:
echo &"CLOSURES - setupvalue is setting {$stack.peek} to slot {slot}, number of slots: {cclosure.upvalueCount}"
echo &"CLOSURES - setupvalue is setting {$stack.peek} to slot {slot}, number of slots: {frames.peek().closure.upvalueCount}"
cclosure.get(slot).write(stack.peek())
of opCloseUpvalue:
let slot = ip.readDU8()
@ -391,7 +398,9 @@ proc run*(chunk: Chunk): InterpretResult =
let times = runcounts[op]
echo &"OpCode: {op} total duration {dur} ms {times} times"
enableGC = false
stack.free()
frames.free()
globals.free()
if hadError:
irRuntimeError