From c1deebf43b7ede74df76c1f021df5c819ae203ac Mon Sep 17 00:00:00 2001 From: Mattia Giambirtone Date: Wed, 2 Nov 2022 13:16:43 +0100 Subject: [PATCH] Stopped using ref objects and removed recursive dependency between gc and VM --- src/backend/vm.nim | 200 +++++++++++++++++++------------------- src/frontend/compiler.nim | 65 ++++++++----- tests/gc.pn | 3 +- 3 files changed, 141 insertions(+), 127 deletions(-) diff --git a/src/backend/vm.nim b/src/backend/vm.nim index d4c7652..41a6bc1 100644 --- a/src/backend/vm.nim +++ b/src/backend/vm.nim @@ -45,7 +45,33 @@ when debugVM: type - PeonVM* = ref object + ObjectKind* = enum + ## A tag for heap-allocated + ## peon objects + String, List, + Dict, Tuple, + CustomType, + HeapObject* = object + ## A tagged box for a heap-allocated + ## peon object + marked*: bool # Used in the GC phase + case kind*: ObjectKind + of String: + str*: ptr UncheckedArray[char] + len*: int + else: + discard # TODO + PeonGC* = object + ## A simple Mark&Sweep collector + ## to manage peon's heap space. + ## All heap allocation goes through + ## this system and is not handled + ## manually by the VM + bytesAllocated: tuple[total, current: int] + nextGC: int + pointers: HashSet[uint64] + objects: seq[ptr HeapObject] + PeonVM* = object ## The Peon Virtual Machine. ## Note how the only data ## type we handle here is @@ -72,34 +98,6 @@ type breakpoints: seq[uint64] # Breakpoints where we call our debugger debugNext: bool # Whether to debug the next instruction lastDebugCommand: string # The last debugging command input by the user - ObjectKind* = enum - ## A tag for heap-allocated - ## peon objects - String, List, - Dict, Tuple, - CustomType, - Closure - HeapObject* = object - ## A tagged box for a heap-allocated - ## peon object - marked*: bool # Used in the GC phase - case kind*: ObjectKind - of String: - str*: ptr UncheckedArray[char] - len*: int - else: - discard # TODO - PeonGC* = ref object - ## A simple Mark&Sweep collector - ## to manage peon's heap space. - ## All heap allocation goes through - ## this system and is not handled - ## manually by the VM - vm: PeonVM - bytesAllocated: tuple[total, current: int] - nextGC: int - pointers: HashSet[uint64] - objects: seq[ptr HeapObject] # Implementation of peon's memory manager @@ -107,19 +105,18 @@ type proc newPeonGC*: PeonGC = ## Initializes a new, blank ## garbage collector - new(result) result.bytesAllocated = (0, 0) result.objects = @[] result.nextGC = FirstGC -proc collect*(self: PeonGC) +proc collect*(self: var PeonVM) -proc reallocate*(self: PeonGC, p: pointer, oldSize: int, newSize: int): pointer = +proc reallocate*(self: var PeonVM, p: pointer, oldSize: int, newSize: int): pointer = ## Simple wrapper around realloc/dealloc with ## built-in garbage collection - self.bytesAllocated.current += newSize - oldSize + self.gc.bytesAllocated.current += newSize - oldSize try: if newSize == 0 and not p.isNil(): when debugMem: @@ -129,11 +126,11 @@ proc reallocate*(self: PeonGC, p: pointer, oldSize: int, newSize: int): pointer echo "DEBUG - Memory manager: Deallocating 1 byte of memory" dealloc(p) elif (oldSize > 0 and not p.isNil() and newSize > oldSize) or oldSize == 0: - self.bytesAllocated.total += newSize - oldSize + self.gc.bytesAllocated.total += newSize - oldSize when debugStressGC: self.collect() else: - if self.bytesAllocated.current > self.nextGC: + if self.gc.bytesAllocated.current > self.gc.nextGC: self.collect() when debugMem: if oldSize == 0: @@ -154,37 +151,38 @@ proc reallocate*(self: PeonGC, p: pointer, oldSize: int, newSize: int): pointer quit(139) # For now, there's not much we can do if we can't get the memory we need, so we exit -template resizeArray*(self: PeonGC, kind: untyped, p: pointer, oldCount, newCount: int): untyped = + +template resizeArray*(self: var PeonVM, kind: untyped, p: pointer, oldCount, newCount: int): untyped = ## Handy template to resize a dynamic array cast[ptr UncheckedArray[kind]](reallocate(self, p, sizeof(kind) * oldCount, sizeof(kind) * newCount)) -template freeArray*(self: PeonGC, kind: untyped, p: pointer, size: int): untyped = +template freeArray*(self: var PeonVM, kind: untyped, p: pointer, size: int): untyped = ## Frees a dynamic array discard reallocate(self, p, sizeof(kind) * size, 0) -template free*(self: PeonGC, kind: typedesc, p: pointer): untyped = +template free*(self: var PeonVM, kind: typedesc, p: pointer): untyped = ## Frees a pointer by reallocating its ## size to 0 discard reallocate(self, p, sizeof(kind), 0) -proc allocate*(self: PeonGC, kind: ObjectKind, size: typedesc, count: int): ptr HeapObject {.inline.} = +proc allocate*(self: var PeonVM, kind: ObjectKind, size: typedesc, count: int): ptr HeapObject {.inline.} = ## Allocates aobject on the heap result = cast[ptr HeapObject](self.reallocate(nil, 0, sizeof(HeapObject))) result.marked = false - self.bytesAllocated.total += sizeof(result) - self.bytesAllocated.current += sizeof(result) + self.gc.bytesAllocated.total += sizeof(HeapObject) + self.gc.bytesAllocated.current += sizeof(HeapObject) case kind: of String: result.str = cast[ptr UncheckedArray[char]](self.reallocate(nil, 0, sizeof(size) * count)) result.len = count - self.bytesAllocated.current += sizeof(size) * count + self.gc.bytesAllocated.current += sizeof(size) * count else: discard # TODO - self.objects.add(result) - self.pointers.incl(cast[uint64](result)) + self.gc.objects.add(result) + self.gc.pointers.incl(cast[uint64](result)) proc mark(self: ptr HeapObject): bool = @@ -195,7 +193,7 @@ proc mark(self: ptr HeapObject): bool = return true -proc markRoots(self: PeonGC): seq[ptr HeapObject] = +proc markRoots(self: var PeonVM): seq[ptr HeapObject] = ## Marks root objects *not* to be ## collected by the GC and returns ## their addresses @@ -222,15 +220,15 @@ proc markRoots(self: PeonGC): seq[ptr HeapObject] = # we allocated and that would cause a memory leak, but # with a 64-bit address-space it probably hardly matters, # so I guess this is a mostly-precise Mark&Sweep collector - var live = initHashSet[uint64](self.pointers.len()) - for obj in self.vm.calls: - if obj in self.pointers: + var live = initHashSet[uint64](self.gc.pointers.len()) + for obj in self.calls: + if obj in self.gc.pointers: live.incl(obj) - for obj in self.vm.operands: - if obj in self.pointers: + for obj in self.operands: + if obj in self.gc.pointers: live.incl(obj) - for obj in self.vm.envs: - if obj in self.pointers: + for obj in self.envs: + if obj in self.gc.pointers: live.incl(obj) # We preallocate the space on the seq result = newSeqOfCap[ptr HeapObject](len(live)) @@ -245,7 +243,7 @@ proc markRoots(self: PeonGC): seq[ptr HeapObject] = echo "DEBUG - GC: Mark phase complete" -proc trace(self: PeonGC, roots: seq[ptr HeapObject]) = +proc trace(self: var PeonVM, roots: seq[ptr HeapObject]) = ## Traces references to other ## objects starting from the ## roots. The second argument @@ -267,7 +265,7 @@ proc trace(self: PeonGC, roots: seq[ptr HeapObject]) = echo &"DEBUG - GC: Traced {count} indirect references" -proc free(self: PeonGC, obj: ptr HeapObject) = +proc free(self: var PeonVM, obj: ptr HeapObject) = ## Frees a single heap-allocated ## peon object and all the memory ## it directly or indirectly owns @@ -282,10 +280,10 @@ proc free(self: PeonGC, obj: ptr HeapObject) = else: discard # TODO self.free(HeapObject, obj) - self.pointers.excl(cast[uint64](obj)) + self.gc.pointers.excl(cast[uint64](obj)) -proc sweep(self: PeonGC) = +proc sweep(self: var PeonVM) = ## Sweeps unmarked objects ## that have been left behind ## during the mark phase. @@ -300,21 +298,21 @@ proc sweep(self: PeonGC) = var idx = 0 when debugGC: var count = 0 - while j < self.objects.high(): + while j < self.gc.objects.high(): inc(j) - if self.objects[j].marked: + if self.gc.objects[j].marked: # Object is marked: don't touch it, # but reset its mark so that it doesn't # stay alive forever when debugGC: - echo &"DEBUG - GC: Unmarking object: {self.objects[j][]}" - self.objects[j].marked = false + echo &"DEBUG - GC: Unmarking object: {self.gc.objects[j][]}" + self.gc.objects[j].marked = false inc(idx) else: # Object is unmarked: its memory is # fair game - self.free(self.objects[idx]) - self.objects.delete(idx) + self.free(self.gc.objects[idx]) + self.gc.objects.delete(idx) inc(idx) when debugGC: inc(count) @@ -322,25 +320,25 @@ proc sweep(self: PeonGC) = echo &"DEBUG - GC: Swept {count} objects" -proc collect(self: PeonGC) = +proc collect(self: var PeonVM) = ## Attempts to reclaim some ## memory from unreachable ## objects onto the heap when debugGC: - let before = self.bytesAllocated.current + let before = self.gc.bytesAllocated.current let time = getMonoTime().ticks().float() / 1_000_000 - echo &"DEBUG - GC: Starting collection cycle at heap size {self.bytesAllocated.current}" + echo &"DEBUG - GC: Starting collection cycle at heap size {self.gc.bytesAllocated.current}" self.trace(self.markRoots()) self.sweep() - self.nextGC = self.bytesAllocated.current * HeapGrowFactor + self.gc.nextGC = self.gc.bytesAllocated.current * HeapGrowFactor when debugGC: - echo &"DEBUG - GC: Collection cycle has terminated in {getMonoTime().ticks().float() / 1_000_000 - time:.2f} ms, collected {before - self.bytesAllocated.current} bytes of memory in total" - echo &"DEBUG - GC: Next cycle at {self.nextGC} bytes" + echo &"DEBUG - GC: Collection cycle has terminated in {getMonoTime().ticks().float() / 1_000_000 - time:.2f} ms, collected {before - self.gc.bytesAllocated.current} bytes of memory in total" + echo &"DEBUG - GC: Next cycle at {self.gc.nextGC} bytes" # Implementation of the peon VM -proc initCache*(self: PeonVM) = +proc initCache*(self: var PeonVM) = ## Initializes the VM's ## singletons cache self.cache[0] = 0x0 # False @@ -354,7 +352,6 @@ proc initCache*(self: PeonVM) = proc newPeonVM*: PeonVM = ## Initializes a new, blank VM ## for executing Peon bytecode - new(result) result.ip = 0 result.initCache() result.gc = newPeonGC() @@ -363,25 +360,24 @@ proc newPeonVM*: PeonVM = result.results = @[] result.envs = @[] result.calls = @[] - result.gc.vm = result # Getters for singleton types {.push inline.} -proc getNil*(self: PeonVM): uint64 = self.cache[2] +proc getNil*(self: var PeonVM): uint64 = self.cache[2] -proc getBool*(self: PeonVM, value: bool): uint64 = +proc getBool*(self: var PeonVM, value: bool): uint64 = if value: return self.cache[1] return self.cache[0] -proc getInf*(self: PeonVM, positive: bool): uint64 = +proc getInf*(self: var PeonVM, positive: bool): uint64 = if positive: return self.cache[3] return self.cache[4] -proc getNan*(self: PeonVM): uint64 = self.cache[5] +proc getNan*(self: var PeonVM): uint64 = self.cache[5] # Thanks to nim's *genius* idea of making x !> y a template @@ -403,13 +399,13 @@ proc `!>=`[T](a, b: T): auto {.inline, used.} = # that go through the getc/setc wrappers are frame-relative, # meaning that the index is added to the current stack frame's # bottom to obtain an absolute stack index -proc push(self: PeonVM, obj: uint64) = +proc push(self: var PeonVM, obj: uint64) = ## Pushes a value object onto the ## operand stack self.operands.add(obj) -proc pop(self: PeonVM): uint64 = +proc pop(self: var PeonVM): uint64 = ## Pops a value off the operand ## stack and returns it return self.operands.pop() @@ -431,13 +427,13 @@ proc peek(self: PeonVM, distance: int = 0): uint64 = return self.operands[self.operands.high() + distance] -proc pushc(self: PeonVM, val: uint64) = +proc pushc(self: var PeonVM, val: uint64) = ## Pushes a value onto the ## call stack self.calls.add(val) -proc popc(self: PeonVM): uint64 = +proc popc(self: var PeonVM): uint64 = ## Pops a value off the call ## stack and returns it return self.calls.pop() @@ -457,7 +453,7 @@ proc getc(self: PeonVM, idx: int): uint64 = return self.calls[idx.uint64 + self.frames[^1]] -proc setc(self: PeonVM, idx: int, val: uint64) = +proc setc(self: var PeonVM, idx: int, val: uint64) = ## Setter method that abstracts ## indexing our call stack through ## stack frames @@ -470,7 +466,7 @@ proc getClosure(self: PeonVM, idx: int): uint64 = return self.envs[idx.uint + self.closures[^1]] -proc setClosure(self: PeonVM, idx: int, val: uint64) = +proc setClosure(self: var PeonVM, idx: int, val: uint64) = ## Setter method that abstracts ## indexing closure environments if idx == self.envs.len(): @@ -479,7 +475,7 @@ proc setClosure(self: PeonVM, idx: int, val: uint64) = self.envs[idx.uint + self.closures[^1]] = val -proc popClosure(self: PeonVM, idx: int): uint64 = +proc popClosure(self: var PeonVM, idx: int): uint64 = ## Pop method that abstracts ## popping values off closure ## environments @@ -491,7 +487,7 @@ proc popClosure(self: PeonVM, idx: int): uint64 = # Byte-level primitives to read and decode # bytecode -proc readByte(self: PeonVM): uint8 = +proc readByte(self: var PeonVM): uint8 = ## Reads a single byte from the ## bytecode and returns it as an ## unsigned 8 bit integer @@ -499,7 +495,7 @@ proc readByte(self: PeonVM): uint8 = return self.chunk.code[self.ip - 1] -proc readShort(self: PeonVM): uint16 = +proc readShort(self: var PeonVM): uint16 = ## Reads two bytes from the ## bytecode and returns them ## as an unsigned 16 bit @@ -507,7 +503,7 @@ proc readShort(self: PeonVM): uint16 = return [self.readByte(), self.readByte()].fromDouble() -proc readLong(self: PeonVM): uint32 = +proc readLong(self: var PeonVM): uint32 = ## Reads three bytes from the ## bytecode and returns them ## as an unsigned 32 bit @@ -517,7 +513,7 @@ proc readLong(self: PeonVM): uint32 = return uint32([self.readByte(), self.readByte(), self.readByte()].fromTriple()) -proc readUInt(self: PeonVM): uint32 {.used.} = +proc readUInt(self: var PeonVM): uint32 {.used.} = ## Reads three bytes from the ## bytecode and returns them ## as an unsigned 32 bit @@ -528,7 +524,7 @@ proc readUInt(self: PeonVM): uint32 {.used.} = # Functions to read primitives from the chunk's # constants table -proc constReadInt64(self: PeonVM, idx: int): int64 = +proc constReadInt64(self: var PeonVM, idx: int): int64 = ## Reads a constant from the ## chunk's constant table and ## returns it as an int64 @@ -540,7 +536,7 @@ proc constReadInt64(self: PeonVM, idx: int): int64 = copyMem(result.addr, arr.addr, sizeof(arr)) -proc constReadUInt64(self: PeonVM, idx: int): uint64 = +proc constReadUInt64(self: var PeonVM, idx: int): uint64 = ## Reads a constant from the ## chunk's constant table and ## returns it as an uint64 @@ -552,7 +548,7 @@ proc constReadUInt64(self: PeonVM, idx: int): uint64 = copyMem(result.addr, arr.addr, sizeof(arr)) -proc constReadUInt32(self: PeonVM, idx: int): uint32 = +proc constReadUInt32(self: var PeonVM, idx: int): uint32 = ## Reads a constant from the ## chunk's constant table and ## returns it as an int32 @@ -561,7 +557,7 @@ proc constReadUInt32(self: PeonVM, idx: int): uint32 = copyMem(result.addr, arr.addr, sizeof(arr)) -proc constReadInt32(self: PeonVM, idx: int): int32 = +proc constReadInt32(self: var PeonVM, idx: int): int32 = ## Reads a constant from the ## chunk's constant table and ## returns it as an uint32 @@ -570,7 +566,7 @@ proc constReadInt32(self: PeonVM, idx: int): int32 = copyMem(result.addr, arr.addr, sizeof(arr)) -proc constReadInt16(self: PeonVM, idx: int): int16 = +proc constReadInt16(self: var PeonVM, idx: int): int16 = ## Reads a constant from the ## chunk's constant table and ## returns it as an int16 @@ -578,7 +574,7 @@ proc constReadInt16(self: PeonVM, idx: int): int16 = copyMem(result.addr, arr.addr, sizeof(arr)) -proc constReadUInt16(self: PeonVM, idx: int): uint16 = +proc constReadUInt16(self: var PeonVM, idx: int): uint16 = ## Reads a constant from the ## chunk's constant table and ## returns it as an uint16 @@ -586,21 +582,21 @@ proc constReadUInt16(self: PeonVM, idx: int): uint16 = copyMem(result.addr, arr.addr, sizeof(arr)) -proc constReadInt8(self: PeonVM, idx: int): int8 = +proc constReadInt8(self: var PeonVM, idx: int): int8 = ## Reads a constant from the ## chunk's constant table and ## returns it as an int8 result = int8(self.chunk.consts[idx]) -proc constReadUInt8(self: PeonVM, idx: int): uint8 = +proc constReadUInt8(self: var PeonVM, idx: int): uint8 = ## Reads a constant from the ## chunk's constant table and ## returns it as an uint8 result = self.chunk.consts[idx] -proc constReadFloat32(self: PeonVM, idx: int): float32 = +proc constReadFloat32(self: var PeonVM, idx: int): float32 = ## Reads a constant from the ## chunk's constant table and ## returns it as a float32 @@ -609,7 +605,7 @@ proc constReadFloat32(self: PeonVM, idx: int): float32 = copyMem(result.addr, arr.addr, sizeof(arr)) -proc constReadFloat64(self: PeonVM, idx: int): float = +proc constReadFloat64(self: var PeonVM, idx: int): float = ## Reads a constant from the ## chunk's constant table and ## returns it as a float @@ -620,13 +616,13 @@ proc constReadFloat64(self: PeonVM, idx: int): float = copyMem(result.addr, arr.addr, sizeof(arr)) -proc constReadString(self: PeonVM, size, idx: int): ptr HeapObject = +proc constReadString(self: var PeonVM, size, idx: int): ptr HeapObject = ## Reads a constant from the ## chunk's constant table and ## returns it as a pointer to ## a heap-allocated string let str = self.chunk.consts[idx.. 0, we're # in a local scope, otherwise it's global - scopeDepth: int + depth: int # Scope ownership data scopeOwners: seq[tuple[owner: Name, depth: int]] # The current function being compiled @@ -254,7 +254,7 @@ proc newCompiler*(replMode: bool = false): Compiler = result.current = 0 result.file = "" result.names = @[] - result.scopeDepth = 0 + result.depth = 0 result.lines = @[] result.jumps = @[] result.currentFunction = nil @@ -567,21 +567,38 @@ proc getStackPos(self: Compiler, name: Name): int = result = 2 for variable in self.names: if variable.kind in [NameKind.Module, NameKind.CustomType, NameKind.Enum, NameKind.Function, NameKind.None]: + # These names don't have a runtime representation on the call stack, so we skip them continue - elif variable.kind == NameKind.Argument and variable.depth > self.scopeDepth: + elif variable.kind == NameKind.Argument and variable.depth > self.depth: + # Argument of a function we haven't compiled yet (or one that we're + # not in). Ignore it, as it won't exist at runtime continue elif not variable.belongsTo.isNil() and variable.belongsTo.valueType.isBuiltinFunction: + # Builtin functions don't exist at runtime either, so variables belonging to them + # are not present in the stack continue elif not variable.valueType.isNil() and variable.valueType.kind == Generic: + # Generics are also a purely compile-time construct and are therefore + # ignored as far as stack positioning goes continue elif variable.owner != self.currentModule: - continue - if name.ident == variable.ident: + # We don't own this variable, so we check + # if the owner exported it to us. If not, + # we skip it and pretend it doesn't exist + if variable.isPrivate or not variable.exported: + continue + elif not variable.resolved: + dec(result) + elif name.ident == variable.ident: + # After all of these checks, we can + # finally check whether the name of + # the two variables match and if they + # do, bingo: We got our name object found = true break inc(result) if not found: - return -1 + result = -1 proc getClosurePos(self: Compiler, name: Name): int = @@ -1088,8 +1105,8 @@ proc handleBuiltinFunction(self: Compiler, fn: Type, args: seq[Expression], line proc beginScope(self: Compiler) = ## Begins a new local scope by incrementing the current ## scope's depth - inc(self.scopeDepth) - self.scopeOwners.add((self.currentFunction, self.scopeDepth)) + inc(self.depth) + self.scopeOwners.add((self.currentFunction, self.depth)) # Flattens our weird function tree into a linear @@ -1106,26 +1123,26 @@ proc flatten(self: Type): seq[Type] = flattenImpl(self, result) proc endScope(self: Compiler) = ## Ends the current local scope - if self.scopeDepth < 0: - self.error("cannot call endScope with scopeDepth < 0 (This is an internal error and most likely a bug)") + if self.depth < 0: + self.error("cannot call endScope with depth < 0 (This is an internal error and most likely a bug)") discard self.scopeOwners.pop() - dec(self.scopeDepth) + dec(self.depth) var names: seq[Name] = @[] var popCount = 0 - if self.scopeDepth == -1 and not self.isMainModule: + if self.depth == -1 and not self.isMainModule: # When we're compiling another module, we don't # close its global scope because self.compileModule() # needs access to it return for name in self.names: - if name.depth > self.scopeDepth: + if name.depth > self.depth: if not name.belongsTo.isNil() and not name.belongsTo.resolved: continue names.add(name) #[if not name.resolved: # TODO: Emit a warning? continue]# - if name.owner != self.currentModule and self.scopeDepth > -1: + if name.owner != self.currentModule and self.depth > -1: # Names coming from other modules only go out of scope # when the global scope is closed (i.e. at the end of # the module) @@ -1229,7 +1246,7 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false): Name = # slap myself 100 times with a sign saying "I'm dumb". Mark my words self.error("cannot declare more than 16777215 variables at a time") declaredName = node.name.token.lexeme - self.names.add(Name(depth: self.scopeDepth, + self.names.add(Name(depth: self.depth, ident: node.name, isPrivate: node.isPrivate, owner: self.currentModule, @@ -1246,7 +1263,7 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false): Name = result = self.names[^1] of NodeKind.funDecl: var node = FunDecl(node) - result = Name(depth: self.scopeDepth, + result = Name(depth: self.depth, isPrivate: node.isPrivate, isConst: false, owner: self.currentModule, @@ -1304,7 +1321,7 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false): Name = var node = ImportStmt(node) var name = node.moduleName.token.lexeme.extractFilename().replace(".pn", "") declaredName = name - self.names.add(Name(depth: self.scopeDepth, + self.names.add(Name(depth: self.depth, owner: self.currentModule, ident: newIdentExpr(Token(kind: Identifier, lexeme: name, line: node.moduleName.token.line)), line: node.moduleName.token.line, @@ -1317,7 +1334,7 @@ proc declareName(self: Compiler, node: ASTNode, mutable: bool = false): Name = for name in self.findByName(declaredName): if name == result: continue - elif (name.kind == NameKind.Var and name.depth == self.scopeDepth) or name.kind in [NameKind.Module, NameKind.CustomType, NameKind.Enum]: + elif (name.kind == NameKind.Var and name.depth == self.depth) or name.kind in [NameKind.Module, NameKind.CustomType, NameKind.Enum]: self.error(&"attempt to redeclare '{name.ident.token.lexeme}', which was previously defined in '{name.owner}' at line {name.line}") @@ -1576,7 +1593,7 @@ proc identifier(self: Compiler, node: IdentExpr) = # they're referenced self.emitByte(LoadUInt64, node.token.line) self.emitBytes(self.chunk.writeConstant(s.codePos.toLong()), node.token.line) - elif s.depth > 0 and self.scopeDepth > 0 and not self.currentFunction.isNil() and s.depth != self.scopeDepth and self.scopeOwners[s.depth].owner != self.currentFunction: + elif s.depth > 0 and self.depth > 0 and not self.currentFunction.isNil() and s.depth != self.depth and self.scopeOwners[s.depth].owner != self.currentFunction: # Loads a closure variable. Stored in a separate "closure array" in the VM that does not # align its semantics with the call stack. This makes closures work as expected and is # not much slower than indexing our stack (since they're both dynamic arrays at runtime anyway) @@ -1953,7 +1970,7 @@ proc breakStmt(self: Compiler, node: BreakStmt) = ## Compiles break statements. A break statement ## jumps to the end of the loop self.currentLoop.breakJumps.add(self.emitJump(OpCode.JumpForwards, node.token.line)) - if self.currentLoop.depth > self.scopeDepth: + if self.currentLoop.depth > self.depth: # Breaking out of a loop closes its scope self.endScope() @@ -2076,7 +2093,7 @@ proc statement(self: Compiler, node: Statement) = # for loops to while loops let loop = self.currentLoop self.currentLoop = Loop(start: self.chunk.code.len(), - depth: self.scopeDepth, breakJumps: @[]) + depth: self.depth, breakJumps: @[]) self.whileStmt(WhileStmt(node)) self.patchBreaks() self.currentLoop = loop @@ -2104,7 +2121,7 @@ proc varDecl(self: Compiler, node: VarDecl, name: Name) = var typ: Type if node.value.isNil(): # Variable has no value: the type declaration - # takes over + # takes over typ = self.inferOrError(node.valueType) elif node.valueType.isNil: # Variable has no type declaration: the type @@ -2241,7 +2258,7 @@ proc compile*(self: Compiler, ast: seq[Declaration], file: string, lines: seq[tu self.chunk = chunk self.ast = ast self.file = file - self.scopeDepth = 0 + self.depth = 0 self.currentFunction = nil self.currentModule = self.file.extractFilename().replace(".pn", "") self.current = 0 @@ -2284,7 +2301,7 @@ proc compileModule(self: Compiler, moduleName: string) = source, persist=true), path, self.lexer.getLines(), source, chunk=self.chunk, incremental=true, isMainModule=false) - self.scopeDepth = 0 + self.depth = 0 self.current = current self.ast = ast self.file = file diff --git a/tests/gc.pn b/tests/gc.pn index b5b9b5a..5acf3d3 100644 --- a/tests/gc.pn +++ b/tests/gc.pn @@ -1,6 +1,7 @@ import std; -var x: uint64 = 1000000'u64; + +var x: uint64 = 100000'u64; var y = "just a test"; print(y); print("Starting GC torture test");