Improved VM debugger, fixed bugs with fixing CFI offsets, minor docs and code changes
This commit is contained in:
parent
6c305d6382
commit
dc626a90d9
|
@ -40,6 +40,9 @@ when debugVM or debugMem or debugGC:
|
|||
import std/sequtils
|
||||
import std/terminal
|
||||
|
||||
when debugVM:
|
||||
proc clearerr(stream: File) {.header: "stdio.h", importc.}
|
||||
|
||||
|
||||
type
|
||||
PeonVM* = ref object
|
||||
|
@ -53,20 +56,22 @@ type
|
|||
## value represents a pointer to
|
||||
## some stack- or heap-allocated
|
||||
## object. The VM has no concept
|
||||
## of type by itself and it relies
|
||||
## on the compiler to produce the
|
||||
## correct results
|
||||
ip: uint64 # Instruction pointer
|
||||
chunk: Chunk # Piece of bytecode to execute
|
||||
calls: seq[uint64] # Our call stack
|
||||
operands: seq[uint64] # Our operand stack
|
||||
cache: array[6, uint64] # Singletons cache
|
||||
## of type by itself: everything
|
||||
## is lost after the compilation
|
||||
## phase
|
||||
ip: uint64 # The instruction pointer
|
||||
chunk: Chunk # The chunk of bytecode to execute
|
||||
calls: seq[uint64] # The call stack
|
||||
operands: seq[uint64] # The operand stack
|
||||
cache: array[6, uint64] # The singletons cache
|
||||
frames: seq[uint64] # Stores the bottom of stack frames
|
||||
closures: seq[uint64] # Stores closure offsets
|
||||
envs: seq[uint64] # Stores variables that do not have stack semantics
|
||||
results: seq[uint64] # Stores function's results (return values)
|
||||
gc: PeonGC # Our memory manager
|
||||
closures: seq[uint64] # Stores closure environment offsets
|
||||
envs: seq[uint64] # Stores closure variables
|
||||
results: seq[uint64] # Stores function return values
|
||||
gc: PeonGC # A reference to the VM's garbage collector
|
||||
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
|
||||
|
@ -198,7 +203,7 @@ proc markRoots(self: PeonGC): seq[ptr HeapObject] =
|
|||
echo "DEBUG - GC: Starting mark phase"
|
||||
# Unlike what bob does in his book,
|
||||
# we keep track of objects in a different
|
||||
# way due to how the whole thing is designed.
|
||||
# way due to the difference of our design.
|
||||
# Specifically, we don't have neat structs for
|
||||
# all peon objects: When we allocate() an object,
|
||||
# we keep track of the small wrapper it created
|
||||
|
@ -251,6 +256,7 @@ proc trace(self: PeonGC, roots: seq[ptr HeapObject]) =
|
|||
## objects
|
||||
when debugGC:
|
||||
echo &"DEBUG - GC: Tracing indirect references from {len(roots)} roots"
|
||||
var count = 0
|
||||
for root in roots:
|
||||
case root.kind:
|
||||
of String:
|
||||
|
@ -258,7 +264,7 @@ proc trace(self: PeonGC, roots: seq[ptr HeapObject]) =
|
|||
else:
|
||||
discard # TODO: Other types
|
||||
when debugGC:
|
||||
echo &"DEBUG - GC: Tracing phase complete"
|
||||
echo &"DEBUG - GC: Traced {count} indirect references"
|
||||
|
||||
|
||||
proc free(self: PeonGC, obj: ptr HeapObject) =
|
||||
|
@ -332,6 +338,8 @@ proc collect(self: PeonGC) =
|
|||
echo &"DEBUG - GC: Next cycle at {self.nextGC} bytes"
|
||||
|
||||
|
||||
# Implementation of the peon VM
|
||||
|
||||
proc initCache*(self: PeonVM) =
|
||||
## Initializes the VM's
|
||||
## singletons cache
|
||||
|
@ -633,55 +641,84 @@ when debugVM: # So nim shuts up
|
|||
## debugger
|
||||
styledEcho fgMagenta, "IP: ", fgYellow, &"{self.ip}"
|
||||
styledEcho fgBlue, "Instruction: ", fgRed, &"{OpCode(self.chunk.code[self.ip])} (", fgYellow, $self.chunk.code[self.ip], fgRed, ")"
|
||||
if self.calls.len() !> 0:
|
||||
stdout.styledWrite(fgGreen, "Call Stack: ", fgMagenta, "[")
|
||||
for i, e in self.calls:
|
||||
stdout.styledWrite(fgYellow, $e)
|
||||
if i < self.calls.high():
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
if self.operands.len() !> 0:
|
||||
stdout.styledWrite(fgBlue, "Operand Stack: ", fgMagenta, "[")
|
||||
for i, e in self.operands:
|
||||
stdout.styledWrite(fgYellow, $e)
|
||||
if i < self.operands.high():
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
if self.frames.len() !> 0:
|
||||
stdout.styledWrite(fgCyan, "Current Frame: ", fgMagenta, "[")
|
||||
for i, e in self.calls[self.frames[^1]..^1]:
|
||||
stdout.styledWrite(fgYellow, $e)
|
||||
if i < (self.calls.high() - self.frames[^1].int):
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]", fgCyan
|
||||
stdout.styledWrite(fgRed, "Live stack frames: ", fgMagenta, "[")
|
||||
for i, e in self.frames:
|
||||
stdout.styledWrite(fgYellow, $e)
|
||||
if i < self.frames.high():
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
if self.closures.len() !> 0:
|
||||
stdout.styledWrite(fgBlue, "Closure offsets: ", fgMagenta, "[")
|
||||
for i, e in self.closures:
|
||||
stdout.styledWrite(fgYellow, $e)
|
||||
if i < self.closures.high():
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
if self.envs.len() !> 0:
|
||||
stdout.styledWrite(fgGreen, "Environments: ", fgMagenta, "[")
|
||||
for i, e in self.envs:
|
||||
stdout.styledWrite(fgYellow, $e)
|
||||
if i < self.envs.high():
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
if self.results.len() !> 0:
|
||||
stdout.styledWrite(fgYellow, "Function Results: ", fgMagenta, "[")
|
||||
for i, e in self.results:
|
||||
stdout.styledWrite(fgYellow, $e)
|
||||
if i < self.results.high():
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
discard readLine stdin
|
||||
var command = ""
|
||||
while true:
|
||||
stdout.styledWrite(fgGreen, "=> ")
|
||||
stdout.flushFile()
|
||||
try:
|
||||
command = readLine(stdin)
|
||||
except EOFError:
|
||||
styledEcho(fgYellow, "Use Ctrl+C to exit")
|
||||
clearerr(stdin)
|
||||
break
|
||||
except IOError:
|
||||
styledEcho(fgRed, "An error occurred while reading command: ", fgYellow, getCurrentExceptionMsg())
|
||||
break
|
||||
if command == "":
|
||||
if self.lastDebugCommand == "":
|
||||
command = "n"
|
||||
else:
|
||||
command = self.lastDebugCommand
|
||||
case command:
|
||||
of "n", "next":
|
||||
self.debugNext = true
|
||||
break
|
||||
of "continue":
|
||||
self.debugNext = false
|
||||
break
|
||||
of "s", "stack":
|
||||
stdout.styledWrite(fgGreen, "Call Stack: ", fgMagenta, "[")
|
||||
for i, e in self.calls:
|
||||
stdout.styledWrite(fgYellow, $e)
|
||||
if i < self.calls.high():
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
of "o", "operands":
|
||||
stdout.styledWrite(fgBlue, "Operand Stack: ", fgMagenta, "[")
|
||||
for i, e in self.operands:
|
||||
stdout.styledWrite(fgYellow, $e)
|
||||
if i < self.operands.high():
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
of "f", "frame":
|
||||
stdout.styledWrite(fgCyan, "Current Frame: ", fgMagenta, "[")
|
||||
for i, e in self.calls[self.frames[^1]..^1]:
|
||||
stdout.styledWrite(fgYellow, $e)
|
||||
if i < (self.calls.high() - self.frames[^1].int):
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]", fgCyan
|
||||
of "frames":
|
||||
stdout.styledWrite(fgRed, "Live stack frames: ", fgMagenta, "[")
|
||||
for i, e in self.frames:
|
||||
stdout.styledWrite(fgYellow, $e)
|
||||
if i < self.frames.high():
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
of "cl", "closures":
|
||||
stdout.styledWrite(fgBlue, "Closure offsets: ", fgMagenta, "[")
|
||||
for i, e in self.closures:
|
||||
stdout.styledWrite(fgYellow, $e)
|
||||
if i < self.closures.high():
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
of "e", "env", "environments":
|
||||
stdout.styledWrite(fgGreen, "Environments: ", fgMagenta, "[")
|
||||
for i, e in self.envs:
|
||||
stdout.styledWrite(fgYellow, $e)
|
||||
if i < self.envs.high():
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
of "r", "results":
|
||||
stdout.styledWrite(fgYellow, "Function Results: ", fgMagenta, "[")
|
||||
for i, e in self.results:
|
||||
stdout.styledWrite(fgYellow, $e)
|
||||
if i < self.results.high():
|
||||
stdout.styledWrite(fgYellow, ", ")
|
||||
styledEcho fgMagenta, "]"
|
||||
of "clear":
|
||||
stdout.write("\x1Bc")
|
||||
else:
|
||||
styledEcho(fgRed, "Unknown command ", fgYellow, &"'{command}'")
|
||||
|
||||
|
||||
proc dispatch*(self: PeonVM) =
|
||||
|
@ -690,7 +727,7 @@ proc dispatch*(self: PeonVM) =
|
|||
while true:
|
||||
{.computedgoto.} # https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma
|
||||
when debugVM:
|
||||
if self.ip in self.breakpoints or self.breakpoints.len() == 0:
|
||||
if self.ip in self.breakpoints or self.breakpoints.len() == 0 or self.debugNext:
|
||||
self.debug()
|
||||
instruction = OpCode(self.readByte())
|
||||
case instruction:
|
||||
|
@ -1046,6 +1083,7 @@ proc run*(self: PeonVM, chunk: Chunk, breakpoints: seq[uint64] = @[]) =
|
|||
self.breakpoints = breakpoints
|
||||
self.results = @[]
|
||||
self.ip = 0
|
||||
self.lastDebugCommand = ""
|
||||
try:
|
||||
self.dispatch()
|
||||
except NilAccessDefect:
|
||||
|
|
|
@ -205,7 +205,7 @@ type
|
|||
# Stores the position of all jumps
|
||||
jumps: seq[tuple[patched: bool, offset: int]]
|
||||
# List of CFI start offsets into our CFI data
|
||||
cfiOffsets: seq[tuple[start, stop: int, fn: Name]]
|
||||
cfiOffsets: seq[tuple[start, stop, pos: int, fn: Name]]
|
||||
# We store these objects to compile modules
|
||||
lexer: Lexer
|
||||
parser: Parser
|
||||
|
@ -450,20 +450,22 @@ proc fixCFIOffsets(self: Compiler, oldLen: int, modifiedAt: int) =
|
|||
let offset = self.chunk.code.len() - oldLen
|
||||
var newCFI: array[3, uint8]
|
||||
var tmp: int
|
||||
var i = 0
|
||||
for cfi in self.cfiOffsets.mitems():
|
||||
if cfi.start >= modifiedAt:
|
||||
newCFI = (cfi.start + offset).toTriple()
|
||||
self.chunk.cfi[cfi.start] = newCFI[0]
|
||||
self.chunk.cfi[cfi.start + 1] = newCFI[1]
|
||||
self.chunk.cfi[cfi.start + 2] = newCFI[2]
|
||||
tmp = [self.chunk.cfi[cfi.start + 3], self.chunk.cfi[cfi.start + 4], self.chunk.cfi[cfi.start + 5]].fromTriple().int
|
||||
self.chunk.cfi[cfi.pos] = newCFI[0]
|
||||
self.chunk.cfi[cfi.pos + 1] = newCFI[1]
|
||||
self.chunk.cfi[cfi.pos + 2] = newCFI[2]
|
||||
tmp = [self.chunk.cfi[cfi.pos + 3], self.chunk.cfi[cfi.pos + 4], self.chunk.cfi[cfi.pos + 5]].fromTriple().int
|
||||
newCFI = (tmp + offset).toTriple()
|
||||
self.chunk.cfi[cfi.start + 3] = newCFI[0]
|
||||
self.chunk.cfi[cfi.start + 4] = newCFI[1]
|
||||
self.chunk.cfi[cfi.start + 5] = newCFI[2]
|
||||
self.chunk.cfi[cfi.pos + 3] = newCFI[0]
|
||||
self.chunk.cfi[cfi.pos + 4] = newCFI[1]
|
||||
self.chunk.cfi[cfi.pos + 5] = newCFI[2]
|
||||
cfi.fn.codePos += offset
|
||||
cfi.start += offset
|
||||
cfi.stop += offset
|
||||
inc(i)
|
||||
|
||||
|
||||
proc fixJumps(self: Compiler, oldLen: int, modifiedAt: int) =
|
||||
|
@ -572,7 +574,6 @@ proc getStackPos(self: Compiler, name: Name): int =
|
|||
found = true
|
||||
break
|
||||
inc(result)
|
||||
echo variable
|
||||
if not found:
|
||||
return -1
|
||||
|
||||
|
@ -637,11 +638,9 @@ proc compare(self: Compiler, a, b: Type): bool =
|
|||
# Functions are a bit trickier
|
||||
if a.args.len() != b.args.len():
|
||||
return false
|
||||
if a.isClosure != b.isClosure:
|
||||
return false
|
||||
if a.isCoroutine != b.isCoroutine:
|
||||
return false
|
||||
elif not self.compare(a.returnType, b.returnType):
|
||||
if not self.compare(a.returnType, b.returnType):
|
||||
return false
|
||||
for (argA, argB) in zip(a.args, b.args):
|
||||
if not self.compare(argA.kind, argB.kind):
|
||||
|
@ -1097,10 +1096,12 @@ proc endScope(self: Compiler) =
|
|||
continue
|
||||
if name.depth > self.scopeDepth:
|
||||
names.add(name)
|
||||
if not name.resolved:
|
||||
#[if not name.resolved:
|
||||
# TODO: Emit a warning?
|
||||
continue]#
|
||||
if name.owner != self.currentModule and self.scopeDepth > -1:
|
||||
continue
|
||||
elif name.kind in [NameKind.Var, NameKind.Argument]:
|
||||
if name.kind in [NameKind.Var, NameKind.Argument]:
|
||||
# We don't increase the pop count for some kinds of objects
|
||||
# because they're not stored the same way as regular variables
|
||||
# (for types, generics and function declarations)
|
||||
|
@ -2128,7 +2129,8 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) =
|
|||
name.codePos = self.chunk.code.len()
|
||||
# We let our debugger know this function's boundaries
|
||||
self.chunk.cfi.add(self.chunk.code.high().toTriple())
|
||||
self.cfiOffsets.add((start: self.chunk.code.high(), stop: 0, fn: name))
|
||||
self.cfiOffsets.add((start: self.chunk.code.high(), stop: 0, pos: self.chunk.cfi.len() - 3, fn: name))
|
||||
var cfiOffset = self.cfiOffsets[^1]
|
||||
let idx = self.chunk.cfi.len()
|
||||
self.chunk.cfi.add(0.toTriple()) # Patched it later
|
||||
self.chunk.cfi.add(uint8(node.arguments.len()))
|
||||
|
@ -2161,7 +2163,7 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) =
|
|||
of NodeKind.funDecl:
|
||||
hasVal = self.currentFunction.valueType.fun.hasExplicitReturn
|
||||
of NodeKind.lambdaExpr:
|
||||
hasVal = LambdaExpr(Declaration(self.currentFunction.valueType.fun)).hasExplicitReturn
|
||||
hasVal = LambdaExpr(self.currentFunction.node).hasExplicitReturn
|
||||
else:
|
||||
discard # Unreachable
|
||||
if not hasVal and not typ.isNil():
|
||||
|
@ -2184,7 +2186,7 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) =
|
|||
self.chunk.cfi[idx] = stop[0]
|
||||
self.chunk.cfi[idx + 1] = stop[1]
|
||||
self.chunk.cfi[idx + 2] = stop[2]
|
||||
self.cfiOffsets[^1].stop = self.chunk.code.len()
|
||||
cfiOffset.stop = self.chunk.code.len()
|
||||
# Currently defer is not functional, so we
|
||||
# just pop the instructions
|
||||
for _ in deferStart..self.deferred.high():
|
||||
|
|
|
@ -1,21 +1,13 @@
|
|||
# Tests closures
|
||||
import std;
|
||||
|
||||
|
||||
|
||||
fn makeClosure(n: int): fn: fn: int {
|
||||
fn inner: fn: int {
|
||||
fn deep: int {
|
||||
return n;
|
||||
}
|
||||
return deep;
|
||||
fn makeClosure(n: int): fn: int {
|
||||
fn inner: int {
|
||||
return n;
|
||||
}
|
||||
return inner;
|
||||
}
|
||||
|
||||
|
||||
var closure = makeClosure(38);
|
||||
var inner = closure();
|
||||
print(inner()); # 38
|
||||
print(inner()); # 38
|
||||
print(makeClosure(420)()()); # 420
|
||||
var c = makeClosure(38);
|
||||
c();
|
||||
|
|
Loading…
Reference in New Issue