Fixed closure variables and debugger output with nested CFI data (also removed unneeded peon files)

This commit is contained in:
Mattia Giambirtone 2022-05-26 18:31:40 +02:00
parent 0f0a442578
commit 71c05ec1bf
7 changed files with 121 additions and 139 deletions

View File

@ -27,7 +27,7 @@ when len(PEON_COMMIT_HASH) != 40:
const PEON_BRANCH* = "master" const PEON_BRANCH* = "master"
when len(PEON_BRANCH) > 255: when len(PEON_BRANCH) > 255:
{.fatal: "The git branch name's length must be less than or equal to 255 characters".} {.fatal: "The git branch name's length must be less than or equal to 255 characters".}
const DEBUG_TRACE_VM* = true # Traces VM execution const DEBUG_TRACE_VM* = false # Traces VM execution
const DEBUG_TRACE_GC* = false # Traces the garbage collector (TODO) const DEBUG_TRACE_GC* = false # Traces the garbage collector (TODO)
const DEBUG_TRACE_ALLOCATION* = false # Traces memory allocation/deallocation const DEBUG_TRACE_ALLOCATION* = false # Traces memory allocation/deallocation
const DEBUG_TRACE_COMPILER* = false # Traces the compiler const DEBUG_TRACE_COMPILER* = false # Traces the compiler

View File

@ -79,10 +79,9 @@ type
isLet: bool isLet: bool
# The name's type # The name's type
valueType: Type valueType: Type
# For variables, the position in the bytecode
# where its StoreVar instruction was emitted.
# For functions, this marks where the function's # For functions, this marks where the function's
# code begins # code begins. For variables, this stores their
# position in the stack (used for closures)
codePos: int codePos: int
Loop = object Loop = object
## A "loop object" used ## A "loop object" used
@ -144,7 +143,7 @@ type
# be empty) # be empty)
deferred: seq[uint8] deferred: seq[uint8]
# List of closed-over variables # List of closed-over variables
closedOver: seq[IdentExpr] closedOver: seq[Name]
@ -181,8 +180,8 @@ proc patchReturnAddress(self: Compiler, retAddr: int)
proc getCurrentNode*(self: Compiler): ASTNode = (if self.current >= proc getCurrentNode*(self: Compiler): ASTNode = (if self.current >=
self.ast.len(): self.ast[^1] else: self.ast[self.current - 1]) self.ast.len(): self.ast[^1] else: self.ast[self.current - 1])
proc getCurrentFunction*(self: Compiler): Declaration {.inline.} = self.currentFunction proc getCurrentFunction*(self: Compiler): Declaration {.inline.} = self.currentFunction
proc getFile*(self: COmpiler): string {.inline.} = self.file proc getFile*(self: Compiler): string {.inline.} = self.file
proc getModule*(self: COmpiler): string {.inline.} = self.currentModule proc getModule*(self: Compiler): string {.inline.} = self.currentModule
## Utility functions ## Utility functions
@ -351,7 +350,7 @@ proc resolve(self: Compiler, name: IdentExpr,
for obj in reversed(self.names): for obj in reversed(self.names):
if obj.name.token.lexeme == name.token.lexeme: if obj.name.token.lexeme == name.token.lexeme:
if obj.isPrivate and obj.owner != self.currentModule: if obj.isPrivate and obj.owner != self.currentModule:
continue # There may be a name in the current module that continue # There may be a name in the current module that
# matches, so we skip this # matches, so we skip this
return obj return obj
return nil return nil
@ -370,18 +369,20 @@ proc getStackPos(self: Compiler, name: IdentExpr,
if name.name.lexeme == variable.name.name.lexeme: if name.name.lexeme == variable.name.name.lexeme:
if variable.isPrivate and variable.owner != self.currentModule: if variable.isPrivate and variable.owner != self.currentModule:
continue continue
if variable.depth == depth or variable.depth == 0: elif variable.depth == depth or variable.depth == 0:
# variable.depth == 0 for globals! # variable.depth == 0 for globals!
return (false, i) return (false, i)
elif variable.depth > 0: elif variable.depth > 0:
for j, closure in reversed(self.closedOver): var j: int = self.closedOver.high()
if closure.name.lexeme == name.name.lexeme: for closure in reversed(self.closedOver):
if closure.name.token.lexeme == name.name.lexeme:
return (true, j) return (true, j)
inc(j)
dec(i) dec(i)
return (false, -1) return (false, -1)
proc detectClosureVariable(self: Compiler, name: IdentExpr, proc detectClosureVariable(self: Compiler, name: Name,
depth: int = self.scopeDepth) = depth: int = self.scopeDepth) =
## Detects if the given name is used in a local scope deeper ## Detects if the given name is used in a local scope deeper
## than the given one and modifies the code emitted for it ## than the given one and modifies the code emitted for it
@ -391,24 +392,23 @@ proc detectClosureVariable(self: Compiler, name: IdentExpr,
## each time a name is referenced in order for closed-over variables ## each time a name is referenced in order for closed-over variables
## to be emitted properly, otherwise the runtime may behave ## to be emitted properly, otherwise the runtime may behave
## unpredictably or crash ## unpredictably or crash
let entry = self.resolve(name) if name == nil:
if entry == nil:
return return
if entry.depth < depth: if name.depth < depth:
# Ding! The given name is closed over: we need to # Ding! The given name is closed over: we need to
# change the StoreVar instruction that created this # change the NoOp instructions that self.declareName
# name entry into a StoreHeap. We don't need to change # put in place for us into a StoreHeap. We don't need to change
# other pieces of code because self.identifier() already # other pieces of code because self.identifier() already
# emits LoadHeap if it detects the variable is closed over, # emits LoadHeap if it detects the variable is closed over,
# whether or not this function is called # whether or not this function is called
self.closedOver.add(entry.name) self.closedOver.add(name)
if self.closedOver.len() >= 16777216: if self.closedOver.len() >= 16777216:
self.error("too many consecutive closed-over variables (max is 16777216)") self.error("too many consecutive closed-over variables (max is 16777216)")
let idx = self.closedOver.high().toTriple() let idx = self.closedOver.high().toTriple()
self.chunk.code[entry.codePos] = StoreHeap.uint8 self.chunk.code[name.codePos] = StoreHeap.uint8
self.chunk.code[entry.codePos + 1] = idx[0] self.chunk.code[name.codePos + 1] = idx[0]
self.chunk.code[entry.codePos + 2] = idx[1] self.chunk.code[name.codePos + 2] = idx[1]
self.chunk.code[entry.codePos + 3] = idx[2] self.chunk.code[name.codePos + 3] = idx[2]
proc compareTypesWithNullNode(self: Compiler, a, b: Type): bool = proc compareTypesWithNullNode(self: Compiler, a, b: Type): bool =
@ -874,10 +874,10 @@ proc declareName(self: Compiler, node: Declaration) =
isPrivate: node.isPrivate, isPrivate: node.isPrivate,
owner: self.currentModule, owner: self.currentModule,
isConst: node.isConst, isConst: node.isConst,
valueType: Type(kind: self.inferType( valueType: Type(kind: self.inferType(node.value).kind, node: node),
node.value).kind, node: node),
codePos: self.chunk.code.len(), codePos: self.chunk.code.len(),
isLet: node.isLet)) isLet: node.isLet))
self.emitBytes([NoOp, NoOp, NoOp, NoOp])
of NodeKind.funDecl: of NodeKind.funDecl:
var node = FunDecl(node) var node = FunDecl(node)
# TODO: Emit some optional debugging # TODO: Emit some optional debugging
@ -932,13 +932,11 @@ proc identifier(self: Compiler, node: IdentExpr) =
if s == nil: if s == nil:
self.error(&"reference to undeclared name '{node.token.lexeme}'") self.error(&"reference to undeclared name '{node.token.lexeme}'")
elif s.isConst: elif s.isConst:
# Constants are emitted as, you guessed it, LoadConstant instructions # Constants are always emitted as Load* instructions
# no matter the scope depth. If optimizations are enabled, the compiler # no matter the scope depth
# will reuse the same constant every time it is referenced instead of
# allocating a new one each time
self.emitConstant(node, self.inferType(node)) self.emitConstant(node, self.inferType(node))
else: else:
self.detectClosureVariable(s.name) self.detectClosureVariable(s)
let t = self.getStackPos(node) let t = self.getStackPos(node)
var index = t.pos var index = t.pos
# We don't check if index is -1 because if it # We don't check if index is -1 because if it
@ -950,8 +948,6 @@ proc identifier(self: Compiler, node: IdentExpr) =
inc(index) # Skip the return address! inc(index) # Skip the return address!
self.emitBytes((index - self.frames[self.scopeDepth]).toTriple()) self.emitBytes((index - self.frames[self.scopeDepth]).toTriple())
else: else:
if self.closedOver.len() == 0:
self.error("error: closure variable array is empty but LoadHeap would be emitted (this is an internal error and most likely a bug)")
# Heap-allocated closure variable. Stored in a separate "closure array" in the VM that does not have stack semantics. # Heap-allocated closure variable. Stored in a separate "closure array" in the VM that does not have stack semantics.
# This makes closures work as expected and is not comparatively slower than indexing our stack (since they're both # This makes closures work as expected and is not comparatively slower than indexing our stack (since they're both
# dynamic arrays at runtime anyway) # dynamic arrays at runtime anyway)
@ -1015,16 +1011,22 @@ proc beginScope(self: Compiler) =
inc(self.scopeDepth) inc(self.scopeDepth)
proc endScope(self: Compiler) = proc endScope(self: Compiler) =
## Ends the current local scope ## Ends the current local scope
if self.scopeDepth == 0: if self.scopeDepth == 0:
self.error("cannot call endScope with scopeDepth == 0 (This is an internal error and most likely a bug)") self.error("cannot call endScope with scopeDepth == 0 (This is an internal error and most likely a bug)")
dec(self.scopeDepth) dec(self.scopeDepth)
var popped: int = 0 var popped: int = 0
var name: Name
for i, ident in reversed(self.names): for i, ident in reversed(self.names):
if ident.depth > self.scopeDepth: if ident.depth > self.scopeDepth:
inc(popped) inc(popped)
name = self.names[self.names.high() - i]
if name.valueType.kind != Function and OpCode(self.chunk.code[name.codePos]) == NoOp:
self.chunk.code.delete(name.codePos)
self.chunk.code.delete(name.codePos + 1)
self.chunk.code.delete(name.codePos + 2)
self.chunk.code.delete(name.codePos + 3)
self.names.delete(self.names.len() - i) self.names.delete(self.names.len() - i)
if not self.enableOptimizations: if not self.enableOptimizations:
# All variables with a scope depth larger than the current one # All variables with a scope depth larger than the current one
@ -1233,8 +1235,7 @@ proc breakStmt(self: Compiler, node: BreakStmt) =
# Emits dummy jump offset, this is # Emits dummy jump offset, this is
# patched later # patched later
discard self.emitJump(OpCode.Jump) self.currentLoop.breakPos.add(self.emitJump(OpCode.Jump))
self.currentLoop.breakPos.add(self.chunk.code.high() - 4)
if self.currentLoop.depth > self.scopeDepth: if self.currentLoop.depth > self.scopeDepth:
# Breaking out of a loop closes its scope # Breaking out of a loop closes its scope
self.endScope() self.endScope()
@ -1325,7 +1326,6 @@ proc funDecl(self: Compiler, node: FunDecl) =
var function = self.currentFunction var function = self.currentFunction
self.declareName(node) self.declareName(node)
self.frames.add(self.names.high()) self.frames.add(self.names.high())
# TODO: Forward declarations # TODO: Forward declarations
if node.body != nil: if node.body != nil:
if BlockStmt(node.body).code.len() == 0: if BlockStmt(node.body).code.len() == 0:
@ -1355,11 +1355,11 @@ proc funDecl(self: Compiler, node: FunDecl) =
# code # code
var deferStart = self.deferred.len() var deferStart = self.deferred.len()
# We let our debugger know a function is starting # We let our debugger know a function is starting
self.chunk.cfi.add(self.chunk.code.high().toTriple()) let start = self.chunk.code.high()
self.blockStmt(BlockStmt(node.body)) self.blockStmt(BlockStmt(node.body))
# Yup, we're done. That was easy, huh? # Yup, we're done. That was easy, huh?
# But after all functions are just named # But, after all, functions are just named
# scopes, and we compile them just like that: # scopes, and we compile them just like that:
# we declare their name and arguments (before # we declare their name and arguments (before
# their body so recursion works) and then just # their body so recursion works) and then just
@ -1370,6 +1370,7 @@ proc funDecl(self: Compiler, node: FunDecl) =
# that's about it # that's about it
# Function is ending! # Function is ending!
self.chunk.cfi.add(start.toTriple())
self.chunk.cfi.add(self.chunk.code.high().toTriple()) self.chunk.cfi.add(self.chunk.code.high().toTriple())
self.chunk.cfi.add(self.frames[^1].toTriple()) self.chunk.cfi.add(self.frames[^1].toTriple())
self.chunk.cfi.add(uint8(node.arguments.len())) self.chunk.cfi.add(uint8(node.arguments.len()))
@ -1385,10 +1386,10 @@ proc funDecl(self: Compiler, node: FunDecl) =
of NodeKind.funDecl: of NodeKind.funDecl:
if not self.currentFunction.hasExplicitReturn: if not self.currentFunction.hasExplicitReturn:
let typ = self.inferType(self.currentFunction) let typ = self.inferType(self.currentFunction)
if self.currentFunction.returnType == nil and typ != nil: if self.currentFunction.returnType == nil and typ.returnType != nil:
self.error("non-empty return statement is not allowed in void functions") self.error("non-empty return statement is not allowed in void functions")
if self.currentFunction.returnType != nil: if self.currentFunction.returnType != nil:
self.error("function has an explicit return type, but no explicit return statement was found") self.error("function has an explicit return type, but no return statement was found")
self.emitByte(OpCode.Return) self.emitByte(OpCode.Return)
of NodeKind.lambdaExpr: of NodeKind.lambdaExpr:
if not LambdaExpr(Declaration(self.currentFunction)).hasExplicitReturn: if not LambdaExpr(Declaration(self.currentFunction)).hasExplicitReturn:
@ -1448,3 +1449,4 @@ proc compile*(self: Compiler, ast: seq[Declaration], file: string): Chunk =
result = self.chunk result = self.chunk
if self.ast.len() > 0 and self.scopeDepth != 0: if self.ast.len() > 0 and self.scopeDepth != 0:
self.error(&"invalid state: invalid scopeDepth value (expected 0, got {self.scopeDepth}), did you forget to call endScope/beginScope?") self.error(&"invalid state: invalid scopeDepth value (expected 0, got {self.scopeDepth}), did you forget to call endScope/beginScope?")

View File

@ -69,7 +69,8 @@ type
# or 24 bit numbers that are defined statically # or 24 bit numbers that are defined statically
# at compilation time into the bytecode # at compilation time into the bytecode
# These push a constant onto the stack # These push a constant at position x in the
# constant table onto the stack
LoadInt64 = 0u8, LoadInt64 = 0u8,
LoadUInt64, LoadUInt64,
LoadInt32, LoadInt32,

View File

@ -32,7 +32,7 @@ proc getLineEditor: LineEditor
# Handy dandy compile-time constants # Handy dandy compile-time constants
const debugLexer = false const debugLexer = false
const debugParser = false const debugParser = false
const debugCompiler = false const debugCompiler = true
const debugSerializer = false const debugSerializer = false
const debugRuntime = false const debugRuntime = false
@ -88,11 +88,7 @@ proc repl =
echo "" echo ""
compiled = compiler.compile(tree, "stdin") compiled = compiler.compile(tree, "stdin")
when debugCompiler: when debugCompiler:
styledEcho fgCyan, "Compilation step:" styledEcho fgCyan, "Compilation step:\n"
styledEcho fgCyan, "\tRaw byte stream: ", fgGreen, "[", fgYellow, compiled.code.join(", "), fgGreen, "]"
styledEcho fgCyan, "\tConstant table: ", fgGreen, "[", fgYellow, compiled.consts.join(", "), fgGreen, "]"
styledEcho fgCyan, "\tCFI data: ", fgGreen, "[", fgYellow, compiled.cfi.join(", "), fgGreen, "]"
styledEcho fgCyan, "\nBytecode disassembler output below:\n"
debugger.disassembleChunk(compiled, "stdin") debugger.disassembleChunk(compiled, "stdin")
echo "" echo ""
@ -209,17 +205,12 @@ proc runFile(f: string) =
echo "" echo ""
compiled = compiler.compile(tree, f) compiled = compiler.compile(tree, f)
when debugCompiler: when debugCompiler:
styledEcho fgCyan, "Compilation step:" styledEcho fgCyan, "Compilation step:\n"
styledEcho fgCyan, "\tRaw byte stream: ", fgGreen, "[", fgYellow, compiled.code.join(", "), fgGreen, "]"
styledEcho fgCyan, "\tConstant table: ", fgGreen, "[", fgYellow, compiled.consts.join(", "), fgGreen, "]"
styledEcho fgCyan, "\tCFI data: ", fgGreen, "[", fgYellow, compiled.cfi.join(", "), fgGreen, "]"
styledEcho fgCyan, "\nBytecode disassembler output below:\n"
debugger.disassembleChunk(compiled, f) debugger.disassembleChunk(compiled, f)
echo "" echo ""
serializer.dumpFile(compiled, input, f, splitFile(f).name & ".pbc") serializer.dumpFile(compiled, input, f, splitFile(f).dir & "/" & splitFile(f).name & ".pbc")
serialized = serializer.loadFile(splitFile(f).name & ".pbc") serialized = serializer.loadFile(splitFile(f).dir & "/" & splitFile(f).name & ".pbc")
when debugSerializer: when debugSerializer:
var hashMatches = computeSHA256(input).toHex().toLowerAscii() == serialized.fileHash var hashMatches = computeSHA256(input).toHex().toLowerAscii() == serialized.fileHash
styledEcho fgCyan, "Serialization step: " styledEcho fgCyan, "Serialization step: "

View File

@ -1,16 +0,0 @@
operator `+`(a: int): int {
return a;
}
operator `+`(a: int32): int32 {
return a;
}
fn `+`(a, b: int): int32 {
return 0'i32; # Just to test error messages
}
var `+`: int = 1; # Can't call a value!
+1; # Works: defined for int64
+1'u8; # No implementation for uint8, error!

View File

@ -28,6 +28,7 @@ type
Debugger* = ref object Debugger* = ref object
chunk: Chunk chunk: Chunk
cfiData: seq[CFIElement] cfiData: seq[CFIElement]
current: int
proc newDebugger*: Debugger = proc newDebugger*: Debugger =
@ -60,145 +61,148 @@ proc printInstruction(instruction: OpCode, newline: bool = false) =
nl() nl()
proc checkFrame(self: Debugger, n: int) = proc checkFrameStart(self: Debugger, n: int) =
for i, e in self.cfiData: for i, e in self.cfiData:
if n == e.start: if n == e.start:
styledEcho fgBlue, "==== Peon Bytecode Debugger - Begin Frame ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ====" styledEcho fgBlue, "\n==== Peon Bytecode Debugger - Begin Frame ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ===="
styledEcho fgGreen, "\t- Start offset: ", fgYellow, $e.start styledEcho fgGreen, "\t- Start offset: ", fgYellow, $e.start
styledEcho fgGreen, "\t- End offset: ", fgYellow, $e.stop styledEcho fgGreen, "\t- End offset: ", fgYellow, $e.stop
styledEcho fgGreen, "\t- Stack bottom: ", fgYellow, $e.bottom styledEcho fgGreen, "\t- Stack bottom: ", fgYellow, $e.bottom
styledEcho fgGreen, "\t- Argument count: ", fgYellow, $e.argc styledEcho fgGreen, "\t- Argument count: ", fgYellow, $e.argc
echo ""
elif n == e.stop:
styledEcho fgBlue, "==== Peon Bytecode Debugger - End Frame ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ====\n"
proc simpleInstruction(self: Debugger, instruction: OpCode, offset: int): int = proc checkFrameEnd(self: Debugger, n: int) =
for i, e in self.cfiData:
if n == e.stop:
styledEcho fgBlue, "\n==== Peon Bytecode Debugger - End Frame ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ===="
proc simpleInstruction(self: Debugger, instruction: OpCode) =
printInstruction(instruction) printInstruction(instruction)
nl() nl()
return offset + 1 self.current += 1
if instruction in {Return, ReturnValue}:
self.checkFrameEnd(self.current - 1)
self.checkFrameEnd(self.current)
proc stackTripleInstruction(self: Debugger, instruction: OpCode, proc stackTripleInstruction(self: Debugger, instruction: OpCode) =
offset: int): int =
## Debugs instructions that operate on a single value on the stack using a 24-bit operand ## Debugs instructions that operate on a single value on the stack using a 24-bit operand
var slot = [self.chunk.code[offset + 1], self.chunk.code[offset + 2], self.chunk.code[ var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[
offset + 3]].fromTriple() self.current + 3]].fromTriple()
printInstruction(instruction) printInstruction(instruction)
stdout.styledWrite(fgGreen, &", points to index ") stdout.styledWriteLine(fgGreen, &", points to index ", fgYellow, $slot)
stdout.styledWriteLine(fgYellow, &"{slot}") self.current += 4
return offset + 4
proc stackDoubleInstruction(self: Debugger, instruction: OpCode) =
proc stackDoubleInstruction(self: Debugger, instruction: OpCode,
offset: int): int =
## Debugs instructions that operate on a single value on the stack using a 16-bit operand ## Debugs instructions that operate on a single value on the stack using a 16-bit operand
var slot = [self.chunk.code[offset + 1], self.chunk.code[offset + 2]].fromDouble() var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2]].fromDouble()
printInstruction(instruction) printInstruction(instruction)
stdout.write(&", points to index ") stdout.write(&", points to index ")
stdout.styledWrite(fgGreen, &", points to index ") stdout.styledWriteLine(fgGreen, &", points to index ", fgYellow, $slot)
stdout.styledWriteLine(fgYellow, &"{slot}") self.current += 3
return offset + 3
proc argumentDoubleInstruction(self: Debugger, instruction: OpCode, offset: int): int = proc argumentDoubleInstruction(self: Debugger, instruction: OpCode) =
## Debugs instructions that operate on a hardcoded value on the stack using a 16-bit operand ## Debugs instructions that operate on a hardcoded value on the stack using a 16-bit operand
var slot = [self.chunk.code[offset + 1], self.chunk.code[offset + 2]].fromDouble() var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2]].fromDouble()
printInstruction(instruction) printInstruction(instruction)
stdout.styledWrite(fgGreen, &", has argument ") stdout.styledWriteLine(fgGreen, &", has argument ", fgYellow, $slot)
stdout.styledWriteLine(fgYellow, $slot) self.current += 3
return offset + 3
proc argumentTripleInstruction(self: Debugger, instruction: OpCode, offset: int): int = proc argumentTripleInstruction(self: Debugger, instruction: OpCode) =
## Debugs instructions that operate on a hardcoded value on the stack using a 24-bit operand ## Debugs instructions that operate on a hardcoded value on the stack using a 24-bit operand
var slot = [self.chunk.code[offset + 1], self.chunk.code[offset + 2], self.chunk.code[offset + 3]].fromTriple() var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
printInstruction(instruction) printInstruction(instruction)
stdout.styledWrite(fgGreen, ", has argument ") stdout.styledWrite(fgGreen, ", has argument ", fgYellow, $slot)
stdout.styledWriteLine(fgYellow, $slot) self.current += 4
return offset + 3
proc callInstruction(self: Debugger, instruction: OpCode, offset: int): int = proc callInstruction(self: Debugger, instruction: OpCode) =
## Debugs function calls ## Debugs function calls
var slot = [self.chunk.code[offset + 1], self.chunk.code[offset + 2], self.chunk.code[offset + 3]].fromTriple() var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple()
var args = [self.chunk.code[offset + 4], self.chunk.code[offset + 5], self.chunk.code[offset + 6]].fromTriple() var args = [self.chunk.code[self.current + 4], self.chunk.code[self.current + 5], self.chunk.code[self.current + 6]].fromTriple()
printInstruction(instruction) printInstruction(instruction)
stdout.styledWrite(fgGreen, &", jumps to address ", fgYellow, $slot, fgGreen, " with ", fgYellow, $args, fgGreen, " argument") stdout.styledWrite(fgGreen, &", jumps to address ", fgYellow, $slot, fgGreen, " with ", fgYellow, $args, fgGreen, " argument")
if args > 1: if args > 1:
stdout.styledWrite(fgYellow, "s") stdout.styledWrite(fgYellow, "s")
nl() nl()
return offset + 7 self.current += 7
proc constantInstruction(self: Debugger, instruction: OpCode, offset: int): int = proc constantInstruction(self: Debugger, instruction: OpCode) =
## Debugs instructions that operate on the constant table ## Debugs instructions that operate on the constant table
var constant = [self.chunk.code[offset + 1], self.chunk.code[offset + 2], self.chunk.code[ var constant = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[
offset + 3]].fromTriple() self.current + 3]].fromTriple()
printInstruction(instruction) printInstruction(instruction)
stdout.styledWrite(fgGreen, &", points to constant at position ", fgYellow, $constant) stdout.styledWrite(fgGreen, &", points to constant at position ", fgYellow, $constant)
nl() nl()
printDebug("Operand: ") printDebug("Operand: ")
stdout.styledWriteLine(fgYellow, &"{self.chunk.consts[constant]}") stdout.styledWriteLine(fgYellow, &"{self.chunk.consts[constant]}")
return offset + 4 self.current += 4
proc jumpInstruction(self: Debugger, instruction: OpCode, offset: int): int = proc jumpInstruction(self: Debugger, instruction: OpCode) =
## Debugs jumps ## Debugs jumps
var orig = self.current
var jump: int var jump: int
case instruction: case instruction:
of Jump, JumpIfFalse, JumpIfTrue, JumpIfFalsePop, JumpForwards, JumpBackwards: of Jump, JumpIfFalse, JumpIfTrue, JumpIfFalsePop, JumpForwards, JumpBackwards:
jump = [self.chunk.code[offset + 1], self.chunk.code[offset + 2]].fromDouble().int() jump = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2]].fromDouble().int()
of LongJump, LongJumpIfFalse, LongJumpIfTrue, LongJumpIfFalsePop, of LongJump, LongJumpIfFalse, LongJumpIfTrue, LongJumpIfFalsePop,
LongJumpForwards, LongJumpBackwards: LongJumpForwards, LongJumpBackwards:
jump = [self.chunk.code[offset + 1], self.chunk.code[offset + 2], self.chunk.code[ jump = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[
offset + 3]].fromTriple().int() self.current + 3]].fromTriple().int()
self.current += 1
else: else:
discard # Unreachable discard # Unreachable
printInstruction(instruction, true) printInstruction(instruction, true)
printDebug("Jump size: ") printDebug("Jump size: ")
stdout.styledWrite(fgYellow, $jump) stdout.styledWrite(fgYellow, $jump)
nl() nl()
return offset + 3 self.current += 3
for i in countup(orig, self.current + 1):
self.checkFrameStart(i)
proc disassembleInstruction*(self: Debugger, offset: int): int = proc disassembleInstruction*(self: Debugger) =
## Takes one bytecode instruction and prints it ## Takes one bytecode instruction and prints it
self.checkFrame(offset)
printDebug("Offset: ") printDebug("Offset: ")
stdout.styledWriteLine(fgYellow, $offset) stdout.styledWriteLine(fgYellow, $self.current)
printDebug("Line: ") printDebug("Line: ")
stdout.styledWriteLine(fgYellow, &"{self.chunk.getLine(offset)}") stdout.styledWriteLine(fgYellow, &"{self.chunk.getLine(self.current)}")
var opcode = OpCode(self.chunk.code[offset]) var opcode = OpCode(self.chunk.code[self.current])
case opcode: case opcode:
of simpleInstructions: of simpleInstructions:
result = self.simpleInstruction(opcode, offset) self.simpleInstruction(opcode)
of constantInstructions: of constantInstructions:
result = self.constantInstruction(opcode, offset) self.constantInstruction(opcode)
of stackDoubleInstructions: of stackDoubleInstructions:
result = self.stackDoubleInstruction(opcode, offset) self.stackDoubleInstruction(opcode)
of stackTripleInstructions: of stackTripleInstructions:
result = self.stackTripleInstruction(opcode, offset) self.stackTripleInstruction(opcode)
of argumentDoubleInstructions: of argumentDoubleInstructions:
result = self.argumentDoubleInstruction(opcode, offset) self.argumentDoubleInstruction(opcode)
of argumentTripleInstructions: of argumentTripleInstructions:
result = self.argumentTripleInstruction(opcode, offset) self.argumentTripleInstruction(opcode)
of callInstructions: of callInstructions:
result = self.callInstruction(opcode, offset) self.callInstruction(opcode)
of jumpInstructions: of jumpInstructions:
result = self.jumpInstruction(opcode, offset) self.jumpInstruction(opcode)
else: else:
echo &"DEBUG - Unknown opcode {opcode} at index {offset}" echo &"DEBUG - Unknown opcode {opcode} at index {self.current}"
result = offset + 1 self.current += 1
proc parseCFIData(self: Debugger) = proc parseCFIData(self: Debugger) =
## Parses CFI information in the chunk
var var
start, stop, bottom, argc: int start, stop, bottom, argc: int
fnName: string name: string
idx = 0 idx = 0
size = 0 size = 0
while idx < len(self.chunk.cfi): while idx < len(self.chunk.cfi) - 1:
start = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1], self.chunk.cfi[idx + 2]].fromTriple()) start = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1], self.chunk.cfi[idx + 2]].fromTriple())
idx += 3 idx += 3
stop = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1], self.chunk.cfi[idx + 2]].fromTriple()) stop = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1], self.chunk.cfi[idx + 2]].fromTriple())
@ -209,20 +213,21 @@ proc parseCFIData(self: Debugger) =
inc(idx) inc(idx)
size = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1]].fromDouble()) size = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1]].fromDouble())
idx += 2 idx += 2
fnName = self.chunk.cfi[idx..<idx + size].fromBytes() name = self.chunk.cfi[idx..<idx + size].fromBytes()
inc(idx, size) inc(idx, size)
self.cfiData.add(CFIElement(start: start, stop: stop, bottom: bottom, argc: argc, name: fnName)) self.cfiData.add(CFIElement(start: start, stop: stop, bottom: bottom,
argc: argc, name: name))
proc disassembleChunk*(self: Debugger, chunk: Chunk, name: string) = proc disassembleChunk*(self: Debugger, chunk: Chunk, name: string) =
## Takes a chunk of bytecode and prints it ## Takes a chunk of bytecode and prints it
self.chunk = chunk self.chunk = chunk
styledEcho fgBlue, &"==== Peon Bytecode Debugger - Chunk '{name}' ====\n" styledEcho fgBlue, &"==== Peon Bytecode Debugger: Session starting - Chunk '{name}' ====\n"
var index = 0 self.current = 0
self.parseCFIData() self.parseCFIData()
while index < self.chunk.code.len: while self.current < self.chunk.code.len:
index = self.disassembleInstruction(index) self.disassembleInstruction()
echo "" echo ""
styledEcho fgBlue, &"==== Debug session ended - Chunk '{name}' ====" styledEcho fgBlue, &"==== Peon Bytecode Debugger: Session ended - Chunk '{name}' ===="

View File

@ -1 +0,0 @@
# TODO