Improved VM debugger, fixed bugs with fixing CFI offsets, minor docs and code changes

This commit is contained in:
Mattia Giambirtone 2022-10-24 13:53:27 +02:00
parent 6c305d6382
commit dc626a90d9
3 changed files with 126 additions and 94 deletions

View File

@ -40,6 +40,9 @@ when debugVM or debugMem or debugGC:
import std/sequtils import std/sequtils
import std/terminal import std/terminal
when debugVM:
proc clearerr(stream: File) {.header: "stdio.h", importc.}
type type
PeonVM* = ref object PeonVM* = ref object
@ -53,20 +56,22 @@ type
## value represents a pointer to ## value represents a pointer to
## some stack- or heap-allocated ## some stack- or heap-allocated
## object. The VM has no concept ## object. The VM has no concept
## of type by itself and it relies ## of type by itself: everything
## on the compiler to produce the ## is lost after the compilation
## correct results ## phase
ip: uint64 # Instruction pointer ip: uint64 # The instruction pointer
chunk: Chunk # Piece of bytecode to execute chunk: Chunk # The chunk of bytecode to execute
calls: seq[uint64] # Our call stack calls: seq[uint64] # The call stack
operands: seq[uint64] # Our operand stack operands: seq[uint64] # The operand stack
cache: array[6, uint64] # Singletons cache cache: array[6, uint64] # The singletons cache
frames: seq[uint64] # Stores the bottom of stack frames frames: seq[uint64] # Stores the bottom of stack frames
closures: seq[uint64] # Stores closure offsets closures: seq[uint64] # Stores closure environment offsets
envs: seq[uint64] # Stores variables that do not have stack semantics envs: seq[uint64] # Stores closure variables
results: seq[uint64] # Stores function's results (return values) results: seq[uint64] # Stores function return values
gc: PeonGC # Our memory manager gc: PeonGC # A reference to the VM's garbage collector
breakpoints: seq[uint64] # Breakpoints where we call our debugger 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 ObjectKind* = enum
## A tag for heap-allocated ## A tag for heap-allocated
## peon objects ## peon objects
@ -198,7 +203,7 @@ proc markRoots(self: PeonGC): seq[ptr HeapObject] =
echo "DEBUG - GC: Starting mark phase" echo "DEBUG - GC: Starting mark phase"
# Unlike what bob does in his book, # Unlike what bob does in his book,
# we keep track of objects in a different # 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 # Specifically, we don't have neat structs for
# all peon objects: When we allocate() an object, # all peon objects: When we allocate() an object,
# we keep track of the small wrapper it created # we keep track of the small wrapper it created
@ -251,6 +256,7 @@ proc trace(self: PeonGC, roots: seq[ptr HeapObject]) =
## objects ## objects
when debugGC: when debugGC:
echo &"DEBUG - GC: Tracing indirect references from {len(roots)} roots" echo &"DEBUG - GC: Tracing indirect references from {len(roots)} roots"
var count = 0
for root in roots: for root in roots:
case root.kind: case root.kind:
of String: of String:
@ -258,7 +264,7 @@ proc trace(self: PeonGC, roots: seq[ptr HeapObject]) =
else: else:
discard # TODO: Other types discard # TODO: Other types
when debugGC: when debugGC:
echo &"DEBUG - GC: Tracing phase complete" echo &"DEBUG - GC: Traced {count} indirect references"
proc free(self: PeonGC, obj: ptr HeapObject) = proc free(self: PeonGC, obj: ptr HeapObject) =
@ -332,6 +338,8 @@ proc collect(self: PeonGC) =
echo &"DEBUG - GC: Next cycle at {self.nextGC} bytes" echo &"DEBUG - GC: Next cycle at {self.nextGC} bytes"
# Implementation of the peon VM
proc initCache*(self: PeonVM) = proc initCache*(self: PeonVM) =
## Initializes the VM's ## Initializes the VM's
## singletons cache ## singletons cache
@ -633,55 +641,84 @@ when debugVM: # So nim shuts up
## debugger ## debugger
styledEcho fgMagenta, "IP: ", fgYellow, &"{self.ip}" styledEcho fgMagenta, "IP: ", fgYellow, &"{self.ip}"
styledEcho fgBlue, "Instruction: ", fgRed, &"{OpCode(self.chunk.code[self.ip])} (", fgYellow, $self.chunk.code[self.ip], fgRed, ")" styledEcho fgBlue, "Instruction: ", fgRed, &"{OpCode(self.chunk.code[self.ip])} (", fgYellow, $self.chunk.code[self.ip], fgRed, ")"
if self.calls.len() !> 0: var command = ""
stdout.styledWrite(fgGreen, "Call Stack: ", fgMagenta, "[") while true:
for i, e in self.calls: stdout.styledWrite(fgGreen, "=> ")
stdout.styledWrite(fgYellow, $e) stdout.flushFile()
if i < self.calls.high(): try:
stdout.styledWrite(fgYellow, ", ") command = readLine(stdin)
styledEcho fgMagenta, "]" except EOFError:
if self.operands.len() !> 0: styledEcho(fgYellow, "Use Ctrl+C to exit")
stdout.styledWrite(fgBlue, "Operand Stack: ", fgMagenta, "[") clearerr(stdin)
for i, e in self.operands: break
stdout.styledWrite(fgYellow, $e) except IOError:
if i < self.operands.high(): styledEcho(fgRed, "An error occurred while reading command: ", fgYellow, getCurrentExceptionMsg())
stdout.styledWrite(fgYellow, ", ") break
styledEcho fgMagenta, "]" if command == "":
if self.frames.len() !> 0: if self.lastDebugCommand == "":
stdout.styledWrite(fgCyan, "Current Frame: ", fgMagenta, "[") command = "n"
for i, e in self.calls[self.frames[^1]..^1]: else:
stdout.styledWrite(fgYellow, $e) command = self.lastDebugCommand
if i < (self.calls.high() - self.frames[^1].int): case command:
stdout.styledWrite(fgYellow, ", ") of "n", "next":
styledEcho fgMagenta, "]", fgCyan self.debugNext = true
stdout.styledWrite(fgRed, "Live stack frames: ", fgMagenta, "[") break
for i, e in self.frames: of "continue":
stdout.styledWrite(fgYellow, $e) self.debugNext = false
if i < self.frames.high(): break
stdout.styledWrite(fgYellow, ", ") of "s", "stack":
styledEcho fgMagenta, "]" stdout.styledWrite(fgGreen, "Call Stack: ", fgMagenta, "[")
if self.closures.len() !> 0: for i, e in self.calls:
stdout.styledWrite(fgBlue, "Closure offsets: ", fgMagenta, "[") stdout.styledWrite(fgYellow, $e)
for i, e in self.closures: if i < self.calls.high():
stdout.styledWrite(fgYellow, $e) stdout.styledWrite(fgYellow, ", ")
if i < self.closures.high(): styledEcho fgMagenta, "]"
stdout.styledWrite(fgYellow, ", ") of "o", "operands":
styledEcho fgMagenta, "]" stdout.styledWrite(fgBlue, "Operand Stack: ", fgMagenta, "[")
if self.envs.len() !> 0: for i, e in self.operands:
stdout.styledWrite(fgGreen, "Environments: ", fgMagenta, "[") stdout.styledWrite(fgYellow, $e)
for i, e in self.envs: if i < self.operands.high():
stdout.styledWrite(fgYellow, $e) stdout.styledWrite(fgYellow, ", ")
if i < self.envs.high(): styledEcho fgMagenta, "]"
stdout.styledWrite(fgYellow, ", ") of "f", "frame":
styledEcho fgMagenta, "]" stdout.styledWrite(fgCyan, "Current Frame: ", fgMagenta, "[")
if self.results.len() !> 0: for i, e in self.calls[self.frames[^1]..^1]:
stdout.styledWrite(fgYellow, "Function Results: ", fgMagenta, "[") stdout.styledWrite(fgYellow, $e)
for i, e in self.results: if i < (self.calls.high() - self.frames[^1].int):
stdout.styledWrite(fgYellow, $e) stdout.styledWrite(fgYellow, ", ")
if i < self.results.high(): styledEcho fgMagenta, "]", fgCyan
stdout.styledWrite(fgYellow, ", ") of "frames":
styledEcho fgMagenta, "]" stdout.styledWrite(fgRed, "Live stack frames: ", fgMagenta, "[")
discard readLine stdin 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) = proc dispatch*(self: PeonVM) =
@ -690,7 +727,7 @@ proc dispatch*(self: PeonVM) =
while true: while true:
{.computedgoto.} # https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma {.computedgoto.} # https://nim-lang.org/docs/manual.html#pragmas-computedgoto-pragma
when debugVM: 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() self.debug()
instruction = OpCode(self.readByte()) instruction = OpCode(self.readByte())
case instruction: case instruction:
@ -1046,6 +1083,7 @@ proc run*(self: PeonVM, chunk: Chunk, breakpoints: seq[uint64] = @[]) =
self.breakpoints = breakpoints self.breakpoints = breakpoints
self.results = @[] self.results = @[]
self.ip = 0 self.ip = 0
self.lastDebugCommand = ""
try: try:
self.dispatch() self.dispatch()
except NilAccessDefect: except NilAccessDefect:

View File

@ -205,7 +205,7 @@ type
# Stores the position of all jumps # Stores the position of all jumps
jumps: seq[tuple[patched: bool, offset: int]] jumps: seq[tuple[patched: bool, offset: int]]
# List of CFI start offsets into our CFI data # 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 # We store these objects to compile modules
lexer: Lexer lexer: Lexer
parser: Parser parser: Parser
@ -450,20 +450,22 @@ proc fixCFIOffsets(self: Compiler, oldLen: int, modifiedAt: int) =
let offset = self.chunk.code.len() - oldLen let offset = self.chunk.code.len() - oldLen
var newCFI: array[3, uint8] var newCFI: array[3, uint8]
var tmp: int var tmp: int
var i = 0
for cfi in self.cfiOffsets.mitems(): for cfi in self.cfiOffsets.mitems():
if cfi.start >= modifiedAt: if cfi.start >= modifiedAt:
newCFI = (cfi.start + offset).toTriple() newCFI = (cfi.start + offset).toTriple()
self.chunk.cfi[cfi.start] = newCFI[0] self.chunk.cfi[cfi.pos] = newCFI[0]
self.chunk.cfi[cfi.start + 1] = newCFI[1] self.chunk.cfi[cfi.pos + 1] = newCFI[1]
self.chunk.cfi[cfi.start + 2] = newCFI[2] self.chunk.cfi[cfi.pos + 2] = newCFI[2]
tmp = [self.chunk.cfi[cfi.start + 3], self.chunk.cfi[cfi.start + 4], self.chunk.cfi[cfi.start + 5]].fromTriple().int 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() newCFI = (tmp + offset).toTriple()
self.chunk.cfi[cfi.start + 3] = newCFI[0] self.chunk.cfi[cfi.pos + 3] = newCFI[0]
self.chunk.cfi[cfi.start + 4] = newCFI[1] self.chunk.cfi[cfi.pos + 4] = newCFI[1]
self.chunk.cfi[cfi.start + 5] = newCFI[2] self.chunk.cfi[cfi.pos + 5] = newCFI[2]
cfi.fn.codePos += offset cfi.fn.codePos += offset
cfi.start += offset cfi.start += offset
cfi.stop += offset cfi.stop += offset
inc(i)
proc fixJumps(self: Compiler, oldLen: int, modifiedAt: int) = proc fixJumps(self: Compiler, oldLen: int, modifiedAt: int) =
@ -572,7 +574,6 @@ proc getStackPos(self: Compiler, name: Name): int =
found = true found = true
break break
inc(result) inc(result)
echo variable
if not found: if not found:
return -1 return -1
@ -637,11 +638,9 @@ proc compare(self: Compiler, a, b: Type): bool =
# Functions are a bit trickier # Functions are a bit trickier
if a.args.len() != b.args.len(): if a.args.len() != b.args.len():
return false return false
if a.isClosure != b.isClosure:
return false
if a.isCoroutine != b.isCoroutine: if a.isCoroutine != b.isCoroutine:
return false return false
elif not self.compare(a.returnType, b.returnType): if not self.compare(a.returnType, b.returnType):
return false return false
for (argA, argB) in zip(a.args, b.args): for (argA, argB) in zip(a.args, b.args):
if not self.compare(argA.kind, argB.kind): if not self.compare(argA.kind, argB.kind):
@ -1097,10 +1096,12 @@ proc endScope(self: Compiler) =
continue continue
if name.depth > self.scopeDepth: if name.depth > self.scopeDepth:
names.add(name) names.add(name)
if not name.resolved: #[if not name.resolved:
# TODO: Emit a warning? # TODO: Emit a warning?
continue]#
if name.owner != self.currentModule and self.scopeDepth > -1:
continue 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 # We don't increase the pop count for some kinds of objects
# because they're not stored the same way as regular variables # because they're not stored the same way as regular variables
# (for types, generics and function declarations) # (for types, generics and function declarations)
@ -2128,7 +2129,8 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) =
name.codePos = self.chunk.code.len() name.codePos = self.chunk.code.len()
# We let our debugger know this function's boundaries # We let our debugger know this function's boundaries
self.chunk.cfi.add(self.chunk.code.high().toTriple()) 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() let idx = self.chunk.cfi.len()
self.chunk.cfi.add(0.toTriple()) # Patched it later self.chunk.cfi.add(0.toTriple()) # Patched it later
self.chunk.cfi.add(uint8(node.arguments.len())) self.chunk.cfi.add(uint8(node.arguments.len()))
@ -2161,7 +2163,7 @@ proc funDecl(self: Compiler, node: FunDecl, name: Name) =
of NodeKind.funDecl: of NodeKind.funDecl:
hasVal = self.currentFunction.valueType.fun.hasExplicitReturn hasVal = self.currentFunction.valueType.fun.hasExplicitReturn
of NodeKind.lambdaExpr: of NodeKind.lambdaExpr:
hasVal = LambdaExpr(Declaration(self.currentFunction.valueType.fun)).hasExplicitReturn hasVal = LambdaExpr(self.currentFunction.node).hasExplicitReturn
else: else:
discard # Unreachable discard # Unreachable
if not hasVal and not typ.isNil(): 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] = stop[0]
self.chunk.cfi[idx + 1] = stop[1] self.chunk.cfi[idx + 1] = stop[1]
self.chunk.cfi[idx + 2] = stop[2] 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 # Currently defer is not functional, so we
# just pop the instructions # just pop the instructions
for _ in deferStart..self.deferred.high(): for _ in deferStart..self.deferred.high():

View File

@ -1,21 +1,13 @@
# Tests closures # Tests closures
import std;
fn makeClosure(n: int): fn: int {
fn makeClosure(n: int): fn: fn: int { fn inner: int {
fn inner: fn: int { return n;
fn deep: int {
return n;
}
return deep;
} }
return inner; return inner;
} }
var closure = makeClosure(38); var c = makeClosure(38);
var inner = closure(); c();
print(inner()); # 38
print(inner()); # 38
print(makeClosure(420)()()); # 420