Stopped using ref objects and removed recursive dependency between gc and VM

This commit is contained in:
Mattia Giambirtone 2022-11-02 13:16:43 +01:00
parent e046981f4b
commit c1deebf43b
3 changed files with 141 additions and 127 deletions

View File

@ -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..<idx + size].fromBytes()
result = self.gc.allocate(String, char, len(str))
result = self.allocate(String, char, len(str))
for i, c in str:
result.str[i] = c
when debugAlloc:
@ -636,7 +632,7 @@ proc constReadString(self: PeonVM, size, idx: int): ptr HeapObject =
when debugVM: # So nim shuts up
proc debug(self: PeonVM) =
proc debug(self: var PeonVM) =
## Implements the VM's runtime
## debugger
styledEcho fgMagenta, "IP: ", fgYellow, &"{self.ip}"
@ -721,7 +717,7 @@ when debugVM: # So nim shuts up
styledEcho(fgRed, "Unknown command ", fgYellow, &"'{command}'")
proc dispatch*(self: PeonVM) =
proc dispatch*(self: var PeonVM) =
## Main bytecode dispatch loop
var instruction {.register.}: OpCode
while true:
@ -1074,7 +1070,7 @@ proc dispatch*(self: PeonVM) =
discard
proc run*(self: PeonVM, chunk: Chunk, breakpoints: seq[uint64] = @[]) =
proc run*(self: var PeonVM, chunk: Chunk, breakpoints: seq[uint64] = @[]) =
## Executes a piece of Peon bytecode
self.chunk = chunk
self.frames = @[]
@ -1090,7 +1086,7 @@ proc run*(self: PeonVM, chunk: Chunk, breakpoints: seq[uint64] = @[]) =
stderr.writeLine("Memory Access Violation: SIGSEGV")
quit(1)
# We clean up after ourselves!
self.gc.collect()
self.collect()
{.pop.}

View File

@ -161,7 +161,7 @@ type
names: seq[Name]
# The current scope depth. If > 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

View File

@ -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");