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/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:
|
||||||
|
|
|
@ -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():
|
||||||
|
|
|
@ -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
|
|
||||||
|
|
Loading…
Reference in New Issue