# Copyright 2022 Mattia Giambirtone & All Contributors # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import ../frontend/meta/bytecode import multibyte import std/strformat import std/strutils import std/terminal type CFIElement = ref object start, stop, bottom, argc: int name: string started, stopped: bool Debugger* = ref object chunk: Chunk cfiData: seq[CFIElement] current: int proc newDebugger*: Debugger = ## Initializes a new, empty ## debugger object new(result) result.cfiData = @[] proc nl = stdout.write("\n") proc printDebug(s: string, newline: bool = false) = stdout.styledWrite(fgMagenta, "DEBUG - Disassembler -> ") stdout.styledWrite(fgGreen, s) if newline: nl() proc printName(opcode: OpCode, newline: bool = false) = stdout.styledWrite(fgRed, $opcode, " (", fgYellow, $uint8(opcode), fgRed, ")") if newline: nl() proc printInstruction(instruction: OpCode, newline: bool = false) = printDebug("Instruction: ") printName(instruction) if newline: nl() proc checkFrameStart(self: Debugger, n: int) = ## Checks if a call frame begins at the given ## bytecode offset for i, e in self.cfiData: if n == e.start and not (e.started or e.stopped): e.started = true 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- End offset: ", fgYellow, $e.stop styledEcho fgGreen, "\t- Argument count: ", fgYellow, $e.argc proc checkFrameEnd(self: Debugger, n: int) = ## Checks if a call frame ends at the given ## bytecode offset for i, e in self.cfiData: if n == e.stop and e.started and not e.stopped: e.stopped = true styledEcho fgBlue, "\n==== Peon Bytecode Debugger - End Frame ", fgYellow, &"'{e.name}' ", fgBlue, "(", fgYellow, $i, fgBlue, ") ====" proc simpleInstruction(self: Debugger, instruction: OpCode) = ## Debugs simple instructions printInstruction(instruction, true) self.current += 1 if instruction == Return: printDebug("Void: ") if self.chunk.code[self.current] == 0: stdout.styledWriteLine(fgYellow, "Yes") else: stdout.styledWriteLine(fgYellow, "No") self.current += 1 self.checkFrameEnd(self.current - 2) self.checkFrameEnd(self.current - 1) self.checkFrameEnd(self.current) proc stackTripleInstruction(self: Debugger, instruction: OpCode) = ## Debugs instructions that operate on a single value on the stack using a 24-bit operand var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple() printInstruction(instruction) stdout.styledWriteLine(fgGreen, &", points to index ", fgYellow, $slot) self.current += 4 proc stackDoubleInstruction(self: Debugger, instruction: OpCode) = ## Debugs instructions that operate on a single value on the stack using a 16-bit operand var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2]].fromDouble() printInstruction(instruction) stdout.write(&", points to index ") stdout.styledWriteLine(fgGreen, &", points to index ", fgYellow, $slot) self.current += 3 proc argumentDoubleInstruction(self: Debugger, instruction: OpCode) = ## Debugs instructions that operate on a hardcoded value on the stack using a 16-bit operand var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2]].fromDouble() printInstruction(instruction) stdout.styledWriteLine(fgGreen, &", has argument ", fgYellow, $slot) self.current += 3 proc argumentTripleInstruction(self: Debugger, instruction: OpCode) {.used.} = ## Debugs instructions that operate on a hardcoded value on the stack using a 24-bit operand var slot = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple() printInstruction(instruction) stdout.styledWriteLine(fgGreen, ", has argument ", fgYellow, $slot) self.current += 4 proc storeClosureInstruction(self: Debugger, instruction: OpCode) = ## Debugs instructions that operate on a hardcoded value on the stack using a 24-bit operand var idx = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple() var idx2 = [self.chunk.code[self.current + 4], self.chunk.code[self.current + 5], self.chunk.code[self.current + 6]].fromTriple() printInstruction(instruction) stdout.styledWriteLine(fgGreen, ", stores element at position ", fgYellow, $idx, fgGreen, " into position ", fgYellow, $idx2) self.current += 7 proc callInstruction(self: Debugger, instruction: OpCode) = ## Debugs function calls var size = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple() self.current += 3 printInstruction(instruction) styledEcho fgGreen, &", creates frame of size ", fgYellow, $(size + 2), fgGreen self.current += 1 proc constantInstruction(self: Debugger, instruction: OpCode) = ## Debugs instructions that operate on the constant table var size: uint if instruction == LoadString: size = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple() self.current += 3 var constant = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple() printInstruction(instruction) stdout.styledWrite(fgGreen, &", points to constant at position ", fgYellow, $constant) self.current += 4 if instruction == LoadString: stdout.styledWriteLine(fgGreen, " of length ", fgYellow, $size) else: stdout.write("\n") proc jumpInstruction(self: Debugger, instruction: OpCode) = ## Debugs jumps var orig = self.current var jump = [self.chunk.code[self.current + 1], self.chunk.code[self.current + 2], self.chunk.code[self.current + 3]].fromTriple().int() printInstruction(instruction, true) printDebug("Jump size: ") stdout.styledWrite(fgYellow, $jump) nl() self.current += 4 while self.chunk.code[self.current] == NoOp.uint8: inc(self.current) for i in countup(orig, self.current + 1): self.checkFrameStart(i) proc disassembleInstruction*(self: Debugger) = ## Takes one bytecode instruction and prints it printDebug("Offset: ") stdout.styledWriteLine(fgYellow, $(self.current)) printDebug("Line: ") stdout.styledWriteLine(fgYellow, &"{self.chunk.getLine(self.current)}") var opcode = OpCode(self.chunk.code[self.current]) case opcode: of simpleInstructions: self.simpleInstruction(opcode) of constantInstructions: self.constantInstruction(opcode) of stackDoubleInstructions: self.stackDoubleInstruction(opcode) of stackTripleInstructions: self.stackTripleInstruction(opcode) of argumentDoubleInstructions: self.argumentDoubleInstruction(opcode) of StoreClosure: self.storeClosureInstruction(opcode) of Call: self.callInstruction(opcode) of jumpInstructions: self.jumpInstruction(opcode) else: echo &"DEBUG - Unknown opcode {opcode} at index {self.current}" self.current += 1 proc parseCFIData(self: Debugger) = ## Parses CFI information in the chunk var start, stop, argc: int name: string idx = 0 size = 0 while idx < len(self.chunk.cfi) - 1: start = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1], self.chunk.cfi[idx + 2]].fromTriple()) idx += 3 stop = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1], self.chunk.cfi[idx + 2]].fromTriple()) idx += 3 argc = int(self.chunk.cfi[idx]) inc(idx) size = int([self.chunk.cfi[idx], self.chunk.cfi[idx + 1]].fromDouble()) idx += 2 name = self.chunk.cfi[idx..