Stopped using ref objects and removed recursive dependency between gc and VM
This commit is contained in:
parent
e046981f4b
commit
c1deebf43b
|
@ -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.}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
|
|
Loading…
Reference in New Issue