diff --git a/nds.nimble b/nds.nimble index e93eae1..a5bbefe 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:debug --skipProjCfg --skipParentCfg -r tests/test.nim" + exec "nim c --gc:arc -d:release --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 e1f3182..0be245b 100644 --- a/src/ndspkg/compiler/compiler.nim +++ b/src/ndspkg/compiler/compiler.nim @@ -1,6 +1,8 @@ import ../scanner import ../chunk import ../config +import ../types/ndobject +import ../memory import types import utils @@ -13,6 +15,8 @@ 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 @@ -25,3 +29,4 @@ 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 c5e74e9..40f1e25 100644 --- a/src/ndspkg/config.nim +++ b/src/ndspkg/config.nim @@ -14,6 +14,8 @@ 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 new file mode 100644 index 0000000..47d8374 --- /dev/null +++ b/src/ndspkg/gc.nim @@ -0,0 +1,146 @@ +# 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 new file mode 100644 index 0000000..79df116 --- /dev/null +++ b/src/ndspkg/memory.nim @@ -0,0 +1,41 @@ +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 a4d0a60..6a391f8 100644 --- a/src/ndspkg/types/closure.nim +++ b/src/ndspkg/types/closure.nim @@ -1,23 +1,33 @@ import strformat import strutils +import ndobject +import ../memory type - UpvalueObj[T] = object + UpvalueObj[T] = object of NdObject location*: ptr T next*: Upvalue[T] closed*: T Upvalue*[T] = ptr UpvalueObj[T] - ClosureObj[T] = object + ClosureObj[T] = object of NdObject 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]](alloc0(8 * upvalueCount + sizeof(ClosureObj[T]))) + result = cast[Closure[T]](ndAlloc0(8 * upvalueCount + sizeof(ClosureObj[T]))) + result.kind = noClosure + result.newObject() result.start = start result.upvalueCount = upvalueCount for i in 0 .. upvalueCount: @@ -47,7 +57,9 @@ proc debugStr*[T](clos: Closure[T]): string = result &= ")" proc newUpvalue*[T](location: ptr T): Upvalue[T] = - result = cast[Upvalue[T]](alloc0(sizeof(UpvalueObj[T]))) + result = cast[Upvalue[T]](ndAlloc0(sizeof(UpvalueObj[T]))) + result.kind = noUpvalue + result.newObject() result.location = location result.next = nil diff --git a/src/ndspkg/types/hashtable.nim b/src/ndspkg/types/hashtable.nim index 11818cf..7764227 100644 --- a/src/ndspkg/types/hashtable.nim +++ b/src/ndspkg/types/hashtable.nim @@ -1,6 +1,8 @@ # The hash table implementation for string interning and globals import bitops +import ndobject +import ../memory const tableMaxLoad = 0.75 const tableInitSize = 8 @@ -14,7 +16,7 @@ type key: U value: V - Table*[U, V] = object + Table*[U, V] = object of NdObject count: int cap: int entries: ptr UncheckedArray[Entry[U, V]] @@ -24,16 +26,24 @@ 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]](alloc(sizeof(Table[U, V]))) + result = cast[NdTable[U, V]](ndAlloc(sizeof(Table[U, V]))) + result[].kind = noTable + result.newObject() 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: var Table[U, V]) = +proc free*[U, V](tbl: Table[U, V]) = if tbl.entries != nil: - dealloc(tbl.entries) + ndDealloc(tbl.entries) + +proc free*[U, V](tbl: NdTable[U, V]) = + tbl[].free() + ndDealloc(tbl) proc findEntry[U, V](entries: ptr UncheckedArray[Entry[U, V]], cap: int, key: U): ptr Entry[U, V] = mixin fnv1a, equal @@ -60,7 +70,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]]](alloc0(newcap * sizeof(Entry[U, V]))) + let entries: ptr UncheckedArray[Entry[U, V]] = cast[ptr UncheckedArray[Entry[U, V]]](ndAlloc0(newcap * sizeof(Entry[U, V]))) tbl.count = 0 if tbl.entries != nil: @@ -73,7 +83,7 @@ proc adjustCapacity[U, V](tbl: var Table[U, V], newcap: int) = dest[].entryStatus = esAlive tbl.count.inc - dealloc(tbl.entries) + ndDealloc(tbl.entries) tbl.entries = entries tbl.cap = newcap @@ -141,6 +151,14 @@ 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 21c3a15..a2c4690 100644 --- a/src/ndspkg/types/ndlist.nim +++ b/src/ndspkg/types/ndlist.nim @@ -1,4 +1,6 @@ import strformat +import ndobject +import ../memory # configure ndlist here import ../config @@ -11,37 +13,41 @@ const growthFactor = 2 const startCap = 8 type - ListObj[T] = object + ListObj[T] = object of NdObject len: int cap: int entries: ptr UncheckedArray[T] List*[T] = ptr ListObj[T] proc newList*[T](): List[T] = - result = cast[List[T]](alloc(sizeof(ListObj[T]))) + result = cast[List[T]](ndAlloc(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]](alloc(sizeof(T) * len)) + result.entries = cast[ptr UncheckedArray[T]](ndAlloc(sizeof(T) * len)) copyMem(result.entries, start, len * sizeof(T)) -proc free*[T](list: var List[T]) = +proc free*[T](list: List[T]) = ## dealloc's the list object - list.dealloc() + list.ndDealloc() proc grow[T](list: var List[T]) = ## growth the list's capacity - let newcap = if list.cap == 0: startCap else: list.cap * growthFactor + let oldcap = list.cap + let oldsize = oldcap * sizeof(T) + let newcap = if oldcap == 0: startCap else: oldcap * growthFactor let size = newcap * sizeof(T) - if list.cap == 0: - list.entries = cast[ptr UncheckedArray[T]](size.alloc()) + if oldcap == 0: + list.entries = cast[ptr UncheckedArray[T]](size.ndAlloc()) list.cap = newcap else: - list.entries = cast[ptr UncheckedArray[T]](list.entries.realloc(size)) + list.entries = cast[ptr UncheckedArray[T]](list.entries.ndRealloc(oldsize, 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 new file mode 100644 index 0000000..33ce98e --- /dev/null +++ b/src/ndspkg/types/ndobject.nim @@ -0,0 +1,22 @@ +# 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/stack.nim b/src/ndspkg/types/ndstack.nim similarity index 92% rename from src/ndspkg/types/stack.nim rename to src/ndspkg/types/ndstack.nim index 903098d..56cc669 100644 --- a/src/ndspkg/types/stack.nim +++ b/src/ndspkg/types/ndstack.nim @@ -1,5 +1,8 @@ import ../pointerutils +import ndobject +import ../memory + # configure stacks here import ../config #const boundsChecks = defined(debug) @@ -15,7 +18,7 @@ type cap*: int proc newStack*[T](startingCap: int): Stack[T] = - result.start = cast[ptr T](alloc(startingCap * sizeof(T))) + result.start = cast[ptr T](ndAlloc(startingCap * sizeof(T))) result.top = result.start.psub(sizeof(T)) result.cap = startingCap @@ -23,14 +26,16 @@ 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.dealloc() + stack.start.ndDealloc() 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 - stack.cap *= growthFactor - stack.start = cast[ptr T](realloc(stack.start, stack.cap * sizeof(T))) + 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.top = stack.start.padd(len * sizeof(T)) proc shrink[T](stack: var Stack[T]) = diff --git a/src/ndspkg/types/ndstring.nim b/src/ndspkg/types/ndstring.nim index 1c128f2..06affe7 100644 --- a/src/ndspkg/types/ndstring.nim +++ b/src/ndspkg/types/ndstring.nim @@ -1,13 +1,16 @@ import hashtable +import ndobject +import ../memory type - NdString* = ptr object + NdStringObj* = object of NdObject len*: uint32 hash*: uint32 chars*: UncheckedArray[char] + NdString* = ptr NdStringObj -proc free*(ndStr: var NdString) = - dealloc(ndStr) +proc free*(ndStr: NdString) = + ndDealloc(ndStr) # hashes @@ -32,7 +35,6 @@ 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 = @@ -40,6 +42,13 @@ 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() @@ -53,10 +62,12 @@ proc newString*(str: string): NdString = if interned != nil: return interned - let len = 8 + strlen - result = cast[NdString](alloc(len)) + let len = sizeof(NdStringObj) + strlen + result = cast[NdString](ndAlloc(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) @@ -69,15 +80,16 @@ proc newString*(str: char): NdString = if interned != nil: return interned - let len = 8 + 1 - result = cast[NdString](alloc(len)) + let len = sizeof(NdStringObj) + 1 + result = cast[NdString](ndAlloc(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/value.nim b/src/ndspkg/types/value.nim index 8e3d2cc..3539dc7 100644 --- a/src/ndspkg/types/value.nim +++ b/src/ndspkg/types/value.nim @@ -1,7 +1,12 @@ import strformat import strutils import bitops +export bitops +import ../config + +import ndobject +export ndobject import ndstring import ndlist import hashtable @@ -21,12 +26,12 @@ type # bits 49-48 determine type: # if bit 63 is 0: # 00 -> nil or bool (singletons) - # 01 -> string + # 01 -> object (string, closure, list, table, upvalue) (formerly string) # 10 -> funct - # 11 -> closure + # 11 -> (formerly closure) free # if bit 63 is 1: - # 00 -> list - # 01 -> table + # 00 -> (formerly list) free + # 01 -> (formerly table) free # 10 -> native funct # 11 -> unused for now @@ -37,13 +42,14 @@ 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 tagString* = 0x7ffd000000000000'u +const tagObject* = 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 @@ -59,20 +65,26 @@ 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.bitand(mask48) == tagString + val.isObject() and val.asObject().kind == noString template isFunct*(val: NdValue): bool = val.bitand(mask48) == tagFunct template isClosure*(val: NdValue): bool = - val.bitand(mask48) == tagClosure + val.isObject() and val.asObject().kind == noClosure template isList*(val: NdValue): bool = - val.bitand(mask48) == tagList + val.isObject() and val.asObject().kind == noList template isTable*(val: NdValue): bool = - val.bitand(mask48) == tagTable + val.isObject() and val.asObject().kind == noTable template isNative*(val: NdValue): bool = val.bitand(mask48) == tagNative @@ -112,7 +124,7 @@ template fromFloat*(val: float): NdValue = cast[NdValue](val) template fromNdString*(val: NdString): NdValue = - cast[uint](val).bitor(tagString) + cast[uint](val).bitor(tagObject) template fromNimString*(sval: string): NdValue = fromNdString(newString(sval)) @@ -121,13 +133,13 @@ template fromFunct*(val: ptr uint8): NdValue = cast[uint](val).bitor(tagFunct) template fromClosure*(val: Closure[NdValue]): NdValue = - cast[uint](val).bitor(tagClosure) + cast[uint](val).bitor(tagObject) template fromList*(val: List[NdValue]): NdValue = - cast[uint](val).bitor(tagList) + cast[uint](val).bitor(tagObject) template fromTable*(val: NdTable[NdValue, NdValue]): NdValue = - cast[uint](val).bitor(tagTable) + cast[uint](val).bitor(tagObject) template fromNative*(val: Native): NdValue = cast[uint](val).bitor(tagNative) diff --git a/src/ndspkg/vm.nim b/src/ndspkg/vm.nim index 3631d9a..071f6e5 100644 --- a/src/ndspkg/vm.nim +++ b/src/ndspkg/vm.nim @@ -4,7 +4,8 @@ import chunk import config import pointerutils -import types/stack +import types/ndstack +import types/ndobject import types/ndstring import bitops # needed for value's templates import types/value @@ -15,6 +16,9 @@ import types/native import lib/main +import memory +import gc + when debugVM: import terminal @@ -32,20 +36,17 @@ 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) - openUpvalues: Upvalue[NdValue] = nil + objects: ptr NdObject proc runtimeError(msg: string) = let ii = ip.pdiff(chunk.code[0].unsafeAddr) @@ -141,6 +142,7 @@ 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 @@ -159,15 +161,6 @@ 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) @@ -361,7 +354,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: {frames.peek().closure.upvalueCount}" + echo &"CLOSURES - setupvalue is setting {$stack.peek} to slot {slot}, number of slots: {cclosure.upvalueCount}" cclosure.get(slot).write(stack.peek()) of opCloseUpvalue: let slot = ip.readDU8() @@ -398,9 +391,7 @@ proc run*(chunk: Chunk): InterpretResult = let times = runcounts[op] echo &"OpCode: {op} total duration {dur} ms {times} times" - stack.free() - frames.free() - globals.free() + enableGC = false if hadError: irRuntimeError