Files
Comet/vm_src/debugger.c

913 lines
28 KiB
C

#include "debugger.h"
#include "args.h"
#include "../lib/estr.h"
#include "../lib/ansi.h"
#include "serialized.h"
#include "vm.h"
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <inttypes.h>
static inline int64_t max(int64_t a, int64_t b) {
return a > b ? a : b;
}
static inline int64_t min(int64_t a, int64_t b) {
return a < b ? a : b;
}
void printDisassembly(CometDebugger* dbgr, Range range) {
if (range.start > range.end) {
uint32_t newEnd = range.start;
range.start = range.end;
range.end = newEnd;
}
// go from start of range to end
for (size_t i = range.start; i <= range.end; i++) {
// display breakpoints
bool isAtBreakpoint = dbgr->breakpoints[i];
if (isAtBreakpoint) {
printf(ESC_DIM " [" ESC_RESET ESC_BOLD ESC_BOLD ESC_RED_FG "B" ESC_RESET ESC_DIM "] " ESC_RESET);
isAtBreakpoint = true;
} else {
printf(" ");
}
// if...
if (i < dbgr->vm->currentFrame->ip) { // below current address? dim
printf(ESC_DIM " ");
} else if (i == dbgr->vm->currentFrame->ip) { // at current address? bright green
printf(ESC_BOLD ESC_BRIGHT_GREEN_FG " -> ");
} else { // after current address? normal colour
printf(" ");
}
// some instructions have a special colour to make it easier to
// spot loops and that kind of thing
switch (dbgr->vm->instructions[i].opcode) {
case INST_JMP:
case INST_JMP_IF_FALSE:
case INST_JMP_IF_TRUE:
case INST_CALL:
case INST_CALL_METHOD:
case INST_RET:
printf(ESC_BOLD ESC_CYAN_FG);
break;
default:
break;
}
// make sure we dont read garbage
if (i >= dbgr->vm->numInstructions) {
printf("0x%04lx ??? <invalid address>\n" ESC_RESET, i);
break;
}
// print the instruction
printf("%s\n" ESC_RESET, cometInstructionToCStr(dbgr->vm, dbgr->vm->instructions[i], i));
}
}
Range getRangeNearIP(CometDebugger* dbgr) {
return (Range){
.start = max(((int64_t)dbgr->vm->currentFrame->ip) - 5, 0),
.end = min(dbgr->vm->currentFrame->ip + 5, dbgr->vm->numInstructions-1)
};
}
CometSerializedFunc* getFuncByName(CometDebugger* dbgr, char* name) {
for (size_t i = 0; i < dbgr->vm->numFunctions; i++) {
if (strcmp(dbgr->vm->functions[i].name, name) == 0) {
return &dbgr->vm->functions[i];
}
}
return NULL;
}
ResultType(charptr, charptr) continueHandler(CometDebugger* dbgr, int argc, char** argv) {
dbgr->vm->instructionsLeftToExec = UINT64_MAX;
dbgr->running = false;
return Success(charptr, charptr, NULL);
}
ResultType(charptr, charptr) quitHandler(CometDebugger* dbgr, int argc, char** argv) {
exit(0);
}
ResultType(charptr, charptr) unbreakHandler(CometDebugger* dbgr, int argc, char** argv) {
if (argc < 1)
return Error(charptr, charptr, "1 arg required");
errno = 0;
char* end;
unsigned long address = strtoul(argv[0], &end, 0);
bool isNumber =
errno == 0 &&
end != argv[0] &&
*end == '\0';
if (!isNumber) {
CometSerializedFunc* func = getFuncByName(dbgr, argv[0]);
if (!func) {
Estr errMsg = CREATE_ESTR("Could not find a function with the name \"");
APPEND_ESTR(errMsg, argv[0]);
APPEND_ESTR(errMsg, "\"");
return Error(charptr, charptr, errMsg.str);
}
if (!dbgr->vm->breakpoints[func->startIdx]) {
char* buffer = malloc(256);
sprintf(buffer, "no breakpoint set at function \"%s\" (0x%04lx).\n", func->name, func->startIdx);
return Error(charptr, charptr, buffer);
}
dbgr->vm->breakpoints[func->startIdx] = 0;
printf("break: removed breakpoint on function %s.\n", func->name);
return Success(charptr, charptr, NULL);
}
if (!dbgr->vm->breakpoints[address]) {
char* buffer = malloc(128);
sprintf(buffer, "no breakpoint set at address 0x%04lx.\n", address);
return Error(charptr, charptr, buffer);
} else if (address >= dbgr->vm->numInstructions) {
return Error(charptr, charptr, "cannot remove breakpoint at invalid address.");
}
dbgr->vm->breakpoints[address] = 0;
printf("break: removed breakpoint on address 0x%04lx.\n", address);
return Success(charptr, charptr, NULL);
}
ResultType(charptr, charptr) breakHandler(CometDebugger* dbgr, int argc, char** argv) {
if (argc < 1)
return Error(charptr, charptr, "1 arg required");
errno = 0;
char* end;
unsigned long address = strtoul(argv[0], &end, 0);
bool isNumber =
errno == 0 &&
end != argv[0] &&
*end == '\0';
if (!isNumber) {
CometSerializedFunc* func = getFuncByName(dbgr, argv[0]);
if (!func) {
Estr errMsg = CREATE_ESTR("Could not find a function with the name \"");
APPEND_ESTR(errMsg, argv[0]);
APPEND_ESTR(errMsg, "\"");
return Error(charptr, charptr, errMsg.str);
}
if (dbgr->vm->breakpoints[func->startIdx]) {
char* buffer = malloc(256);
sprintf(buffer, "breakpoint already set at function \"%s\" (0x%04lx).\n", func->name, func->startIdx);
return Error(charptr, charptr, buffer);
}
dbgr->vm->breakpoints[func->startIdx] = 1;
printf("break: added breakpoint on function %s.\n", func->name);
return Success(charptr, charptr, NULL);
}
if (dbgr->vm->breakpoints[address]) {
char* buffer = malloc(128);
sprintf(buffer, "breakpoint already set at address 0x%04lx.\n", address);
return Error(charptr, charptr, buffer);
} else if (address >= dbgr->vm->numInstructions) {
return Error(charptr, charptr, "cannot create breakpoint at invalid address.");
}
dbgr->vm->breakpoints[address] = 1;
printf("break: added breakpoint on address 0x%04lx.\n", address);
return Success(charptr, charptr, NULL);
}
ResultType(charptr, charptr) stepHandler(CometDebugger* dbgr, int argc, char** argv) {
if (argc < 1) {
dbgr->vm->instructionsLeftToExec = 1;
} else {
char* end;
errno = 0;
uint64_t value = strtoul(argv[0], &end, 0);
bool ok =
errno == 0 &&
end != argv[0] &&
*end == '\0';
if (!ok) {
return Error(charptr, charptr, "invalid number input!");
}
dbgr->vm->instructionsLeftToExec = value;
}
dbgr->running = false;
return Success(charptr, charptr, NULL);
}
ResultType(charptr, charptr) disassembleHandler(CometDebugger* dbgr, int argc, char** argv) {
// get range to display
Range range;
if (argc < 1)
range = getRangeNearIP(dbgr);
else
range = parseRange(argv[0]);
if (range.start < 0) {
return Error(charptr, charptr, "can't start disassembly at negative address!");
}
printDisassembly(dbgr, range) ;
return Success(charptr, charptr, NULL);
}
ResultType(charptr, charptr) stackHandler(CometDebugger* dbgr, int argc, char** argv) {
if (argc > 0) {
char* end;
errno = 0;
uint64_t value = strtoul(argv[0], &end, 0);
bool ok =
errno == 0 &&
end != argv[0] &&
*end == '\0';
if (!ok) {
return Error(charptr, charptr, "invalid number input!");
}
if (value >= dbgr->vm->sp) {
return Error(charptr, charptr, "index is past length of stack!");
}
printf(ESC_BOLD "Stack[%ld]:" ESC_RESET " 0x%08llx\n", value, dbgr->vm->stack[value]);
return Success(charptr, charptr, NULL);
}
printf( ESC_BOLD "Stack: " ESC_RESET "%s\n", stackAsString(dbgr->vm->stack, dbgr->vm->sp));
printf( ESC_BOLD "SP: " ESC_RESET "%d\n", dbgr->vm->sp);
return Success(charptr, charptr, NULL);
}
ResultType(charptr, charptr) variableHandler(CometDebugger* dbgr, int argc, char** argv) {
if (argc < 1)
return Error(charptr, charptr, "1 arg required");
Range range = parseRange(argv[0]);
if (range.start < 0 || range.end >= MAX_VARIABLES)
return Error(charptr, charptr, "variable index out of bounds");
if (range.start > range.end) {
uint32_t newEnd = range.start;
range.start = range.end;
range.end = newEnd;
}
for (uint32_t i = range.start; i <= range.end; i++) {
printf(ESC_BOLD "vars[%d]" ESC_RESET " = 0x%08lx\n", i, dbgr->vm->variables[i]);
}
return Success(charptr, charptr, NULL);
}
int tokenize(char* line, char** argv, int maxArgs) {
int argc = 0;
while (*line) {
// skip whitespace
while (*line == ' ' || *line == '\t' || *line == '\n')
line++;
if (*line == '\0')
break;
if (argc >= maxArgs)
break;
argv[argc++] = line;
// find end of token
while (*line &&
*line != ' ' &&
*line != '\t' &&
*line != '\n') {
line++;
}
// terminate token
if (*line) {
*line = '\0';
line++;
}
}
return argc;
}
/// !==== INSTRUCTIONS ====! ///
static const char* helpAliases[] = {"h", NULL};
static const char* disassembleAliases[] = {"d", NULL};
static const char* breakAliases[] = {"b", NULL};
static const char* unbreakAliases[] = {"ubreak", "u", "ub", NULL};
static const char* stackAliases[] = {"st", NULL};
static const char* variableAliases[] = {"v", "var", "vars", NULL};
static const char* structsAliases[] = {"ls", NULL};
static const char* stepAliases[] = {"s", NULL};
static const char* continueAliases[] = {"c", "cont", NULL};
static const char* quitAliases[] = {"q", "stop", "exit", NULL};
ResultType(charptr, charptr) helpHandler(CometDebugger* dbgr, int argc, char** argv);
const CometDebugCommand DBGR_COMMANDS[] = {
{"help", helpHandler, "display a list of all commnads or get help about a specific command", "h | h <command>", helpAliases},
{"quit", quitHandler, "exit the debugger and stop the vm", "q", quitAliases},
{"disassemble", disassembleHandler, "disassemble a line or range of lines", "d | d <line> | d <start>:<end>", disassembleAliases},
{"break", breakHandler, "set a breakpoint", "b <address> | b <functionName>", breakAliases},
{"unbreak", unbreakHandler, "delete a breakpoint", "ub <breakpointId>", unbreakAliases},
{"stack", stackHandler, "display the current state of the stack", "st | st <index>", stackAliases},
{"variable", variableHandler, "get the value of a variable / variables", "v <index> | v <startIndex>:<endIndex>", variableAliases},
{"structs", NULL, "print all structs or display info about a struct", "ls | ls <name>", structsAliases},
{"step", stepHandler, "execute next instruction", "s | s <numInstructions>", stepAliases},
{"continue", continueHandler, "continue execution", "c", continueAliases},
};
/// !==== END OF INSTRUCTIONS ====! ///
const CometDebugCommand* getCommandByName(char* cmdName) {
for (size_t i = 0; i < sizeof(DBGR_COMMANDS)/sizeof(DBGR_COMMANDS[0]); i++) {
CometDebugCommand cmd = DBGR_COMMANDS[i];
if (strcmp(cmdName, cmd.name) == 0) {
return &DBGR_COMMANDS[i];
}
// check command aliases
size_t aliasIdx = 0;
while (true) {
if (strcmp(cmd.aliases[aliasIdx], cmdName) == 0) {
return &DBGR_COMMANDS[i];
}
if (cmd.aliases[aliasIdx+1] == NULL) {
break;
}
aliasIdx++;
}
}
return NULL;
}
ResultType(charptr, charptr) parseCommand(CometDebugger* dbgr, char* line) {
int maxArgs = 16;
char* argv[maxArgs];
int argc = tokenize(line, argv, maxArgs);
if (argc == 0)
return Success(charptr, charptr, NULL);
const CometDebugCommand* cmd = getCommandByName(argv[0]);
if (cmd == NULL) {
Estr errMsg = CREATE_ESTR("dbgr: no such command: \"");
APPEND_ESTR(errMsg, argv[0]);
APPEND_ESTR(errMsg, "\"");
fprintf(stderr, "%s\n", errMsg.str);
DESTROY_ESTR(errMsg);
return Success(charptr, charptr, NULL);
}
ResultType(charptr, charptr) cmdResult = cmd->handler(dbgr, argc - 1, &argv[1]);
if (cmdResult.error) {
fprintf(stderr, ESC_BOLD ESC_BRIGHT_RED_FG "%s: error: %s\n" ESC_RESET, cmd->name, cmdResult.as.error);
}
return Success(charptr, charptr, NULL);
}
void debuggerLoop(CometDebugger* dbgr) {
while (dbgr->running) {
char* userInput = NULL;
size_t size = 0;
ssize_t charsRead;
printf("dbgr > ");
charsRead = getline(&userInput, &size, stdin);
if (charsRead == -1) {
fprintf(stderr, "getline: failed to read input!");
return;
}
ResultType(charptr, charptr) commandResult = parseCommand(dbgr, userInput);
if (commandResult.error)
break;
}
}
void startDebugger(CometVM* vm, bool startedFromStep) {
CometDebugger* newDbgr = malloc(sizeof(CometDebugger));
newDbgr->vm = vm;
newDbgr->breakpoints = vm->breakpoints;
newDbgr->running = true;
if (startedFromStep) {
printDisassembly(newDbgr, getRangeNearIP(newDbgr));
debuggerLoop(newDbgr);
return;
}
printf(ESC_BOLD ESC_CYAN_FG "\n!--- BREAKPOINT - COMET DEBUGGER ---!" ESC_RESET "\n");
printf(ESC_BOLD "VM State:\n" ESC_RESET);
printf(" - IP: 0x%016lx\n", vm->currentFrame->ip);
printf(" - Current Inst: %s\n", cometInstructionToCStr(vm, vm->instructions[vm->currentFrame->ip], vm->currentFrame->ip));
printf(" - SP: 0x%08x\n", vm->sp);
printf(" - Stack: %s\n\n", stackAsString(vm->stack, vm->sp));
debuggerLoop(newDbgr);
free(newDbgr);
}
void printAliases(CometDebugCommand cmd) {
size_t aliasIdx = 0;
while (true) {
if (cmd.aliases[aliasIdx+1] == NULL) {
printf("%s\n", cmd.aliases[aliasIdx]);
break;
}
printf("%s, ", cmd.aliases[aliasIdx]);
aliasIdx++;
}
}
ResultType(charptr, charptr) helpHandler(CometDebugger* dbgr, int argc, char** argv) {
// look for command with given name
if (argc > 0) {
char* cmdName = argv[0];
const CometDebugCommand* cmd = getCommandByName(argv[0]);
if (cmd == NULL) {
Estr errMsg = CREATE_ESTR("no such command: \"");
APPEND_ESTR(errMsg, argv[0]);
APPEND_ESTR(errMsg, "\"")
return Error(charptr, charptr, errMsg.str);
}
printf(ESC_BOLD ESC_CYAN_FG "\\\\\\ %s ///\n\n" ESC_RESET, cmd->name);
printf(ESC_BOLD ESC_YELLOW_FG "Description:" ESC_RESET " %s\n", cmd->help);
printf(ESC_BOLD ESC_YELLOW_FG "Usage:" ESC_RESET " %s\n", cmd->usage);
printf(ESC_BOLD ESC_YELLOW_FG "Aliases: " ESC_RESET);
printAliases(*cmd);
return Success(charptr, charptr, NULL);
}
// help command with no args
printf(ESC_BOLD ESC_CYAN_FG "\\\\\\ commands ///\n\n" ESC_RESET);
for (size_t i = 0; i < sizeof(DBGR_COMMANDS)/sizeof(DBGR_COMMANDS[0]); i++) {
CometDebugCommand cmd = DBGR_COMMANDS[i];
printf(ESC_BOLD ESC_YELLOW_FG " %s" ESC_RESET " - %s | Aliases: ", cmd.name, cmd.help);
printAliases(cmd);
}
return Success(charptr, charptr, NULL);
}
Range parseRange(const char* str) {
Range r = {0};
char* colon = strchr(str, ':');
if (!colon) {
// single value: "3"
r.start = strtoul(str, NULL, 0);
r.end = r.start;
r.hasEnd = 0;
return r;
}
// split in-place copy (safe version uses temp buffer)
char buf[64];
strncpy(buf, str, sizeof(buf));
buf[63] = '\0';
char* c = strchr(buf, ':');
*c = '\0';
r.start = strtoul(buf, NULL, 0);
r.end = strtoul(c + 1, NULL, 0);
r.hasEnd = 1;
return r;
}
char* cometImmediateToCStr(CometImmediate immediate) {
switch (immediate.typeKind) {
case COMET_SMALL: {
char* buffer = malloc(4);
sprintf(buffer, "%hhd", immediate.smallVal);
return buffer;
}
case COMET_INT: {
char* buffer = malloc(32);
sprintf(buffer, "%d", immediate.intVal);
return buffer;
}
case COMET_BIG: {
char* buffer = malloc(64);
sprintf(buffer, "%zu", immediate.bigVal);
return buffer;
}
case COMET_FLOAT: {
char* buffer = malloc(128);
sprintf(buffer, "%f", immediate.floatVal);
return buffer;
}
case COMET_DOUBLE: {
char* buffer = malloc(128);
sprintf(buffer, "%f", immediate.doubleVal);
return buffer;
}
case COMET_BOOL: {
if (immediate.boolVal) {
return "true";
} else {
return "false";
}
}
case COMET_VOID: {
return "void";
}
default: return "FIXME";
}
}
char* cometOperandToCStr(CometVM* vm, CometOperand operand) {
switch (operand.type) {
case CO_IMMEDIATE:
return cometImmediateToCStr(operand.imm);
case CO_STACK: {
char* buffer = malloc(32);
sprintf(buffer, "%%%d", operand.stackIdx);
return buffer;
}
case CO_SYMBOL: {
CometSerializedFunc func = vm->functions[operand.symbolIdx];
char* buffer = malloc(64);
sprintf(buffer, "func %s, %d args", func.name, func.numArgs);
return buffer;
}
case CO_LABEL: {
char* buffer = malloc(32);
if (operand.label->resolved)
sprintf(buffer, "0x%x", operand.label->pos);
else
sprintf(buffer, "(unresolved)");
return buffer;
}
default: break;
}
return "FIXME";
}
char* cometInstOpcodeToCStr(CometInstType instType) {
switch (instType) {
case INST_PUSH_CONST : return " PUSH_CONST ";
case INST_STORE : return " STORE ";
case INST_LOAD : return " LOAD ";
case INST_ADDI : return " ADDI ";
case INST_ADDF : return " ADDF ";
case INST_SUBI : return " SUBI ";
case INST_SUBF : return " SUBF ";
case INST_MULI : return " MULI ";
case INST_MULF : return " MULF ";
case INST_DIVI : return " DIVI ";
case INST_DIVF : return " DIVF ";
case INST_LOAD_ARG : return " LOAD_ARG ";
case INST_RET : return " RET ";
case INST_CALL : return " CALL ";
case INST_EQI : return " EQI ";
case INST_EQF : return " EQF ";
case INST_NEQI : return " NEQI ";
case INST_NEQF : return " NEQF ";
case INST_LTI : return " LTI ";
case INST_LTF : return " LTF ";
case INST_GTI : return " GTI ";
case INST_GTF : return " GTF ";
case INST_LTEI : return " LTEI ";
case INST_LTEF : return " LTEF ";
case INST_GTEI : return " GTEI ";
case INST_GTEF : return " GTEF ";
case INST_JMP : return " JMP ";
case INST_JMP_IF_FALSE : return " JMP_IF_FALSE ";
case INST_JMP_IF_TRUE : return " JMP_IF_TRUE ";
case INST_NOT : return " NOT ";
case INST_I2F : return " I2F ";
case INST_DUP : return " DUP ";
case INST_NEW : return " NEW ";
case INST_GET_FIELD : return " GET_FIELD ";
case INST_SET_FIELD : return " SET_FIELD ";
case INST_CALL_METHOD : return " CALL_METHOD ";
case INST_BREAKPOINT : return " BREAKPOINT ";
default : return " FIXME ";
}
}
CometOperand createOperand(CometOperandKind type) {
return (CometOperand){
.type = type
};
}
CometOperand instArgToOperand(CometInstType opcode, uint32_t arg, uint32_t index) {
// table of sadness and despair :trollface:
const CometValueTypeKind argTypesTable[][3] = {
{COMET_SMALL, COMET_VOID, COMET_VOID}, // INST_PUSH_CONST
{COMET_SMALL, COMET_VOID, COMET_VOID}, // INST_STORE
{COMET_SMALL, COMET_VOID, COMET_VOID}, // INST_LOAD
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_ADDI
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_ADDF
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_SUBI
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_SUBF
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_MULI
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_MULF
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_DIVI
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_DIVF
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_EQI
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_EQF
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_NEQI
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_NEQF
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_GTI
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_GTF
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_LTI
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_LTF
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_GTEI
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_GTEF
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_LTEI
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_LTEF
{COMET_SMALL, COMET_VOID, COMET_VOID}, // INST_LOAD_ARG
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_RET
{COMET_FUNCTION, COMET_VOID, COMET_VOID}, // INST_CALL
{COMET_SMALL, COMET_VOID, COMET_VOID}, // INST_JMP
{COMET_INT, COMET_VOID, COMET_VOID}, // INST_JMP_IF_FALSE
{COMET_INT, COMET_VOID, COMET_VOID}, // INST_JMP_IF_TRUE
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_NOT
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_I2F
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_DUP
{COMET_SMALL, COMET_VOID, COMET_VOID}, // INST_NEW
{COMET_SMALL, COMET_VOID, COMET_VOID}, // INST_GET_FIELD
{COMET_SMALL, COMET_VOID, COMET_VOID}, // INST_SET_FIELD
{COMET_SMALL, COMET_VOID, COMET_VOID}, // INST_CALL_METHOD
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_BREAKPOINT
{COMET_VOID, COMET_VOID, COMET_VOID}, // INST_MAX
};
CometValueTypeKind argTypes[3];
memcpy(argTypes, argTypesTable[opcode], sizeof(argTypes));
CometValueTypeKind argType = argTypes[index];
switch (argType) {
case COMET_SMALL:
return (CometOperand){
.type = CO_IMMEDIATE,
.imm = (CometImmediate){
.typeKind = argType,
.smallVal = arg
}
};
case COMET_INT:
return (CometOperand){
.type = CO_IMMEDIATE,
.imm = (CometImmediate){
.typeKind = argType,
.intVal = arg
}
};
case COMET_BIG:
return (CometOperand){
.type = CO_IMMEDIATE,
.imm = (CometImmediate){
.typeKind = argType,
.bigVal = arg
}
};
case COMET_FLOAT:
return (CometOperand){
.type = CO_IMMEDIATE,
.imm = (CometImmediate){
.typeKind = argType,
.floatVal = arg
}
};
case COMET_DOUBLE:
return (CometOperand){
.type = CO_IMMEDIATE,
.imm = (CometImmediate){
.typeKind = argType,
.doubleVal = arg
}
};
case COMET_BOOL:
return (CometOperand){
.type = CO_IMMEDIATE,
.imm = (CometImmediate){
.typeKind = argType,
.boolVal = arg
}
};
case COMET_FUNCTION:
return (CometOperand){
.type = CO_IMMEDIATE,
.symbolIdx = arg
};
default:
return (CometOperand){
.type = CO_NONE
};
}
}
CometOperand* instructionArgsToOperandValues(CometSerializedInst inst) {
CometOperand* values = calloc(3, sizeof(CometOperand));
values[0] = instArgToOperand(inst.opcode, inst.a, 0);
values[1] = instArgToOperand(inst.opcode, inst.a, 1);
values[2] = instArgToOperand(inst.opcode, inst.a, 2);
return values;
}
char* cometInstructionToCStr(CometVM* vm, CometSerializedInst inst, uint64_t instPos) {
char* buffer = malloc(256);
char* extra = malloc(128);
buffer[0] = 0;
extra[0] = 0;
// print extra info if we can
switch (inst.opcode) {
case INST_PUSH_CONST:
sprintf(
extra,
"; consts[%d] = %s",
inst.a,
cometOperandToCStr(vm, vm->constants[inst.a])
);
break;
case INST_CALL_METHOD: {
CometOperand func = createOperand(CO_SYMBOL);
func.symbolIdx = inst.a;
sprintf(
extra,
"; vtable[%d] = %s",
inst.a,
cometOperandToCStr(vm, func)
);
break;
}
case INST_JMP:
case INST_JMP_IF_FALSE:
if (inst.a >= vm->numInstructions) {
sprintf(extra, "; (???)");
break;
}
sprintf(
extra,
"; (%s)",
cometInstructionToCStr(vm, vm->instructions[inst.a], inst.a)
);
break;
default: break;
}
char* argsBuffer = malloc(128);
argsBuffer[0] = 0;
CometOperand* args = instructionArgsToOperandValues(inst);
if (args[0].type != CO_NONE)
sprintf(argsBuffer, "%s", cometOperandToCStr(vm, args[0]));
if (args[1].type != CO_NONE)
sprintf(argsBuffer + strlen(argsBuffer), ", %s", cometOperandToCStr(vm, args[1]));
if (args[2].type != CO_NONE)
sprintf(argsBuffer + strlen(argsBuffer), ", %s", cometOperandToCStr(vm, args[2]));
sprintf(
buffer,
"0x%04lx%s%s %s",
instPos,
cometInstOpcodeToCStr(inst.opcode),
argsBuffer,
extra
);
return buffer;
}
char* stackAsString(int64_t* stack, uint32_t sp) {
Estr stackString = CREATE_ESTR("[");
for (size_t i = 0; i < sp; i++) {
char* buffer = malloc(64);
sprintf(buffer, "0x%" PRIx64 "%s", stack[i], i < sp-1 ? ", " : "");
APPEND_ESTR(stackString, buffer);
}
APPEND_ESTR(stackString, "]");
return stackString.str;
}
char* stackTrace(CometVM* vm) {
Estr stackTraceStr = CREATE_ESTR("\nCall Stack (most recent call first):\n");
for (size_t i = vm->callIdx; i-- > 0;) {
Frame call = vm->callStack[i];
char* funcBuffer = malloc(128);
sprintf(funcBuffer, " 0x%04lx %s (sp: 0x%x) %s\n", call.ip, call.funcName, vm->sp, stackAsString(vm->stack, vm->sp));
APPEND_ESTR(stackTraceStr, funcBuffer);
}
return stackTraceStr.str;
}