diff --git a/nds.nimble b/nds.nimble index a5bbefe..e93eae1 100644 --- a/nds.nimble +++ b/nds.nimble @@ -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": diff --git a/src/ndspkg/compiler/compiler.nim b/src/ndspkg/compiler/compiler.nim index 0be245b..e1f3182 100644 --- a/src/ndspkg/compiler/compiler.nim +++ b/src/ndspkg/compiler/compiler.nim @@ -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 diff --git a/src/ndspkg/config.nim b/src/ndspkg/config.nim index 40f1e25..c5e74e9 100644 --- a/src/ndspkg/config.nim +++ b/src/ndspkg/config.nim @@ -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 diff --git a/src/ndspkg/gc.nim b/src/ndspkg/gc.nim deleted file mode 100644 index 47d8374..0000000 --- a/src/ndspkg/gc.nim +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/ndspkg/memory.nim b/src/ndspkg/memory.nim deleted file mode 100644 index 79df116..0000000 --- a/src/ndspkg/memory.nim +++ /dev/null @@ -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) diff --git a/src/ndspkg/types/closure.nim b/src/ndspkg/types/closure.nim index 6a391f8..a4d0a60 100644 --- a/src/ndspkg/types/closure.nim +++ b/src/ndspkg/types/closure.nim @@ -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 diff --git a/src/ndspkg/types/hashtable.nim b/src/ndspkg/types/hashtable.nim index 7764227..11818cf 100644 --- a/src/ndspkg/types/hashtable.nim +++ b/src/ndspkg/types/hashtable.nim @@ -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 "@{}" diff --git a/src/ndspkg/types/ndlist.nim b/src/ndspkg/types/ndlist.nim index a2c4690..21c3a15 100644 --- a/src/ndspkg/types/ndlist.nim +++ b/src/ndspkg/types/ndlist.nim @@ -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) = diff --git a/src/ndspkg/types/ndobject.nim b/src/ndspkg/types/ndobject.nim deleted file mode 100644 index 33ce98e..0000000 --- a/src/ndspkg/types/ndobject.nim +++ /dev/null @@ -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 - \ No newline at end of file diff --git a/src/ndspkg/types/ndstring.nim b/src/ndspkg/types/ndstring.nim index 06affe7..1c128f2 100644 --- a/src/ndspkg/types/ndstring.nim +++ b/src/ndspkg/types/ndstring.nim @@ -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]() diff --git a/src/ndspkg/types/ndstack.nim b/src/ndspkg/types/stack.nim similarity index 92% rename from src/ndspkg/types/ndstack.nim rename to src/ndspkg/types/stack.nim index 56cc669..903098d 100644 --- a/src/ndspkg/types/ndstack.nim +++ b/src/ndspkg/types/stack.nim @@ -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]) = diff --git a/src/ndspkg/types/value.nim b/src/ndspkg/types/value.nim index 3539dc7..8e3d2cc 100644 --- a/src/ndspkg/types/value.nim +++ b/src/ndspkg/types/value.nim @@ -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) diff --git a/src/ndspkg/vm.nim b/src/ndspkg/vm.nim index 071f6e5..3631d9a 100644 --- a/src/ndspkg/vm.nim +++ b/src/ndspkg/vm.nim @@ -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