diff --git a/Makefile b/Makefile index a2636a5..6913818 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ VM_OBJS=$(O)/vm/main.o \ $(O)/vm/unit.o \ $(O)/vm/stack.o \ $(O)/vm/error.o \ + $(O)/vm/debug.o \ $(O)/core/vector.o \ $(O)/core/hash.o COMPILER_OBJS=$(O)/compiler/main.o \ diff --git a/compiler/compile.c b/compiler/compile.c index 6aad69f..f3103ea 100644 --- a/compiler/compile.c +++ b/compiler/compile.c @@ -304,8 +304,10 @@ void emit(struct function *fn, struct context *ctx, struct node *expr) { emit_insn(fn, OP(OP_CAR, 0)); } else if (!strcmp(n0->n_ident, "cdr") && c0 == 1) { emit_insn(fn, OP(OP_CDR, 0)); - } else if (!strcmp(n0->n_ident, "trace") && c0 == 1) { + } else if (!strcmp(n0->n_ident, "debug/trace") && c0 == 1) { emit_insn(fn, OP(OP_DEBUG, OP_DEBUG_TRACE)); + } else if (!strcmp(n0->n_ident, "debug/break")) { + emit_insn(fn, OP(OP_DEBUG, OP_DEBUG_BREAKPOINT)); } else { size_t index; int kind; diff --git a/core/include/op.h b/core/include/op.h index ae79109..da8f55e 100644 --- a/core/include/op.h +++ b/core/include/op.h @@ -46,3 +46,4 @@ #define OP_DEBUG 0x80 #define OP_DEBUG_TRACE 0x01 +#define OP_DEBUG_BREAKPOINT 0x02 diff --git a/mod1.vml b/mod1.vml index c0570c5..631786e 100644 --- a/mod1.vml +++ b/mod1.vml @@ -3,4 +3,11 @@ (define a (cons 1 (cons 2 (cons 3 (cons 4 nil))))) +(define (f x) + (debug/break) + (+ x 1)) + +(print (f 1)) + +(print (list-ref a 1)) (print (list-ref a 1)) diff --git a/vm/debug.c b/vm/debug.c new file mode 100644 index 0000000..8ca2475 --- /dev/null +++ b/vm/debug.c @@ -0,0 +1,95 @@ +#include +#include + +#include "error.h" +#include "debug.h" +#include "vmstate.h" +#include "vmval.h" +#include "unit.h" + +int debug_breakpoint(struct vm *vm) { + struct vm_func_entry *func; + struct vm_unit *unit; + uint64_t w0; + uint32_t opcode; + char line[256]; + char *p; + + unit = vector_ref(&vm->units, vm->lp); + func = vector_ref(&unit->functions, vm->fp); + opcode = func->bytecode[vm->ip]; + + fprintf(stderr, "%02zx:%02zx:%04zx: %08x\n", vm->lp, vm->fp, vm->ip, opcode); + + while (1) { + fprintf(stderr, ">> "); + if (!fgets(line, sizeof(line), stdin)) { + return 0; + } + if ((p = strchr(line, '\n')) != NULL) { + *p = 0; + } + + if (!strcmp(line, "s")) { + vm->flags |= VM_SINGLESTEP; + return 0; + } else if (!strcmp(line, "c")) { + vm->flags &= ~VM_SINGLESTEP; + return 0; + } else if (!strcmp(line, "q")) { + return -ERR_ERROR_EXIT; + } else if (!strcmp(line, "regs")) { + fprintf(stderr, " lp = 0x%04zx, fp = 0x%04zx\n", vm->lp, vm->fp); + fprintf(stderr, " ip = 0x%04zx\n", vm->ip); + fprintf(stderr, " dsp = 0x%04zx\n", vm->data_stack.sp); + fprintf(stderr, " csp = 0x%04zx, cbp = 0x%04zx\n", vm->call_stack.sp, vm->bp); + } else if (!strcmp(line, "locals")) { + fprintf(stderr, "Local variables:\n"); + for (size_t i = 0; i < func->local_count; ++i) { + w0 = vm->call_stack.data[vm->bp - i - 1]; + fprintf(stderr, "[bp - %zu]: ", i + 1); + vm_print(stderr, w0); + fprintf(stderr, "\n"); + } + fprintf(stderr, "Arguments:\n"); + for (size_t i = 0; i < func->argc; ++i) { + w0 = vm->call_stack.data[vm->bp + i]; + fprintf(stderr, "[bp + %zu]: ", i); + vm_print(stderr, w0); + fprintf(stderr, "\n"); + } + } else if (!strcmp(line, "bt")) { + fprintf(stderr, "Call frame trace:\n"); + size_t bp, lp, fp, ip, sp; + size_t count; + struct vm_func_entry *f; + struct vm_unit *u; + + bp = vm->bp; + lp = vm->lp; + fp = vm->fp; + ip = vm->ip; + count = 0; + while (1) { + u = vector_ref(&vm->units, lp); + f = vector_ref(&u->functions, fp); + sp = bp + f->argc; + fprintf(stderr, "%2zu: %02zx:%02zx:%04zx: %08x\n", + count, lp, fp, ip, f->bytecode[ip]); + + if (count == 5) { + break; + } + bp = vm->call_stack.data[sp + 3]; + lp = vm->call_stack.data[sp + 2]; + fp = vm->call_stack.data[sp + 1]; + ip = vm->call_stack.data[sp + 0]; + ++count; + + if (bp == 0) { + break; + } + } + } + } +} diff --git a/vm/error.c b/vm/error.c index b271f93..5404c63 100644 --- a/vm/error.c +++ b/vm/error.c @@ -13,6 +13,8 @@ const char *vm_strerror(int e) { case -ERR_STACK_UNDERFLOW: return "Stack underflow"; case -ERR_STACK_OVERFLOW: return "Stack overflow"; case -ERR_NOT_FOUND: return "Unit not found"; + case -ERR_INVALID_STATE: return "Invalid VM state"; + case -ERR_ERROR_EXIT: return "Program exited with an error"; default: return "Unknown error"; } diff --git a/vm/include/debug.h b/vm/include/debug.h new file mode 100644 index 0000000..5d2c109 --- /dev/null +++ b/vm/include/debug.h @@ -0,0 +1,5 @@ +#pragma once + +struct vm; + +int debug_breakpoint(struct vm *vm); diff --git a/vm/include/error.h b/vm/include/error.h index 5ccb64a..fb554bc 100644 --- a/vm/include/error.h +++ b/vm/include/error.h @@ -11,5 +11,7 @@ #define ERR_STACK_UNDERFLOW 9 #define ERR_STACK_OVERFLOW 10 #define ERR_NOT_FOUND 11 +#define ERR_INVALID_STATE 12 +#define ERR_ERROR_EXIT 13 const char *vm_strerror(int e); diff --git a/vm/include/load.h b/vm/include/load.h index b95a9cb..2497eaf 100644 --- a/vm/include/load.h +++ b/vm/include/load.h @@ -4,7 +4,7 @@ #include "list.h" #include "vector.h" -struct vm_state; +struct vm; struct vm_ref_entry; struct vm_unresolved_ref { @@ -29,11 +29,11 @@ struct vm_unit_info { struct vm_unit *unit; }; -int vm_load_unit_file(struct vm_state *vm, +int vm_load_unit_file(struct vm *vm, struct vm_unit_info *info, struct list_head *refs, FILE *fp); -int vm_load_unit(struct vm_state *vm, +int vm_load_unit(struct vm *vm, struct vm_unit_info *info, struct list_head *refs, const char *name); diff --git a/vm/include/vmstate.h b/vm/include/vmstate.h index cb5775f..a0db1ce 100644 --- a/vm/include/vmstate.h +++ b/vm/include/vmstate.h @@ -10,8 +10,18 @@ #define MAXARG 12 #define MAXLOC 64 +#define VM_SINGLESTEP (1 << 0) + struct vm_value; +enum vm_state { + STATE_INIT = 0, + STATE_RUNNING, + STATE_BREAKPOINT, + STATE_EXITED, + STATE_ERROR +}; + static inline uint64_t encode_ref(struct vm_value *ref) { uintptr_t addr = (uintptr_t) ref; assert(!(addr & 1)); @@ -22,29 +32,33 @@ static inline uint64_t encode_ref(struct vm_value *ref) { } } -struct vm_state { +struct vm { struct stack data_stack; struct stack call_stack; struct vector units; size_t bp, lp, fp, ip; + + uint32_t flags; + + enum vm_state state; }; -int vm_state_init(struct vm_state *vm, size_t stack_size); -void vm_state_free(struct vm_state *vm); +int vm_init(struct vm *vm, size_t stack_size); +void vm_free(struct vm *vm); -struct vm_unit *vm_add_unit(struct vm_state *vm, size_t stack_size); +struct vm_unit *vm_add_unit(struct vm *vm, size_t stack_size); // Bytecode interpretation -int vm_eval_step(struct vm_state *vm); -int vm_eval_unit(struct vm_state *vm, size_t index); +int vm_eval_step(struct vm *vm); +int vm_eval_unit(struct vm *vm, size_t index); // Stack operation -int vm_pop(struct vm_state *vm, uint64_t *w); -int vm_pop_integer(struct vm_state *vm, int64_t *v); +int vm_pop(struct vm *vm, uint64_t *w); +int vm_pop_integer(struct vm *vm, int64_t *v); -int vm_push_integer(struct vm_state *vm, int64_t v); -int vm_push_bool(struct vm_state *vm, int v); -int vm_push_ref(struct vm_state *vm, struct vm_value *obj); +int vm_push_integer(struct vm *vm, int64_t v); +int vm_push_bool(struct vm *vm, int v); +int vm_push_ref(struct vm *vm, struct vm_value *obj); -uint64_t vm_get_arg(struct vm_state *vm, size_t index); +uint64_t vm_get_arg(struct vm *vm, size_t index); diff --git a/vm/include/vmval.h b/vm/include/vmval.h index 8cf2e2a..025b9ba 100644 --- a/vm/include/vmval.h +++ b/vm/include/vmval.h @@ -2,6 +2,7 @@ #include #include #include +#include #include "vmstring.h" #include "vmstate.h" @@ -76,4 +77,4 @@ struct vm_value *vm_cons(uint64_t w0, uint64_t w1); struct vm_value *vm_makestr(const char *str); struct vm_value *vm_func(size_t lib_index, size_t fn_index); -void vm_print(uint64_t w); +void vm_print(FILE *dst, uint64_t w); diff --git a/vm/load.c b/vm/load.c index b82be6f..b75316d 100644 --- a/vm/load.c +++ b/vm/load.c @@ -12,14 +12,14 @@ #include "vmstate.h" #include "vmval.h" -static int c_print(struct vm_state *vm) { +static int c_print(struct vm *vm) { uint64_t w; int res; if ((res = vm_pop(vm, &w)) != 0) { return res; } - vm_print(w); + vm_print(stdout, w); printf("\n"); vm_unref(w); return 0; @@ -210,7 +210,7 @@ static int vm_load_exports(struct vm_unit_info *info, return 0; } -int vm_load_unit_file(struct vm_state *vm, +int vm_load_unit_file(struct vm *vm, struct vm_unit_info *info, struct list_head *refs, FILE *fp) { @@ -251,7 +251,7 @@ int vm_load_unit_file(struct vm_state *vm, return 0; } -int vm_load_unit(struct vm_state *vm, +int vm_load_unit(struct vm *vm, struct vm_unit_info *info, struct list_head *refs, const char *name) { diff --git a/vm/main.c b/vm/main.c index 666d76d..0d88034 100644 --- a/vm/main.c +++ b/vm/main.c @@ -31,11 +31,11 @@ static int execute_file(const char *filename) { struct list_head unresolved; struct vm_export_entry *export; struct vm_ref_entry *ref_entry; - struct vm_state vm; + struct vm vm; struct vm_unit_info *info; struct vm_unresolved_ref *ref; - if ((res = vm_state_init(&vm, 4096)) != 0) { + if ((res = vm_init(&vm, 4096)) != 0) { return res; } @@ -112,11 +112,12 @@ static int execute_file(const char *filename) { hash_free(&unit_map); // Start execution + vm.state = STATE_RUNNING; if ((res = vm_eval_unit(&vm, 0)) != 0) { return res; } - vm_state_free(&vm); + vm_free(&vm); return 0; } diff --git a/vm/unit.c b/vm/unit.c index 5b369b1..db79293 100644 --- a/vm/unit.c +++ b/vm/unit.c @@ -13,7 +13,6 @@ struct vm_func_entry *unit_add_function(struct vm_unit *u) { } void unit_free(struct vm_unit *u) { - printf("Release unit\n"); struct vm_func_entry *func; vector_free(&u->ref_table); diff --git a/vm/vmstack.c b/vm/vmstack.c index 37340b1..614e32e 100644 --- a/vm/vmstack.c +++ b/vm/vmstack.c @@ -2,11 +2,11 @@ #include -int vm_pop(struct vm_state *vm, uint64_t *w) { +int vm_pop(struct vm *vm, uint64_t *w) { return stack_pop(&vm->data_stack, w); } -int vm_pop_integer(struct vm_state *vm, int64_t *v) { +int vm_pop_integer(struct vm *vm, int64_t *v) { int res; uint64_t w; if ((res = stack_pop(&vm->data_stack, &w)) != 0) { @@ -22,11 +22,11 @@ int vm_pop_integer(struct vm_state *vm, int64_t *v) { return 0; } -int vm_push_integer(struct vm_state *vm, int64_t v) { +int vm_push_integer(struct vm *vm, int64_t v) { return stack_push(&vm->data_stack, v & ~FLAG_REF); } -int vm_push_bool(struct vm_state *vm, int v) { +int vm_push_bool(struct vm *vm, int v) { if (v) { return stack_push(&vm->data_stack, 1); } else { @@ -34,6 +34,6 @@ int vm_push_bool(struct vm_state *vm, int v) { } } -int vm_push_ref(struct vm_state *vm, struct vm_value *obj) { +int vm_push_ref(struct vm *vm, struct vm_value *obj) { return stack_push(&vm->data_stack, encode_ref(obj)); } diff --git a/vm/vmstate.c b/vm/vmstate.c index dadeb33..3c945c0 100644 --- a/vm/vmstate.c +++ b/vm/vmstate.c @@ -1,3 +1,4 @@ +#include "debug.h" #include "error.h" #include "op.h" #include "unit.h" @@ -16,7 +17,7 @@ static inline int64_t sximm(uint32_t in) { } } -static int vm_push_context(struct vm_state *vm) { +static int vm_push_context(struct vm *vm) { int res; if ((res = stack_push(&vm->call_stack, vm->bp)) != 0) { return res; @@ -33,7 +34,7 @@ static int vm_push_context(struct vm_state *vm) { return 0; } -static int vm_pop_context(struct vm_state *vm) { +static int vm_pop_context(struct vm *vm) { int res; if ((res = stack_pop(&vm->call_stack, &vm->ip)) != 0) { return res; @@ -50,7 +51,7 @@ static int vm_pop_context(struct vm_state *vm) { return 0; } -int vm_state_init(struct vm_state *vm, size_t stack_size) { +int vm_init(struct vm *vm, size_t stack_size) { int res; if ((res = stack_init(&vm->data_stack, stack_size)) != 0) { @@ -62,6 +63,8 @@ int vm_state_init(struct vm_state *vm, size_t stack_size) { vector_init(&vm->units, sizeof(struct vm_unit)); + vm->state = STATE_INIT; + vm->flags = 0; vm->lp = 0; vm->fp = 0; vm->ip = 0; @@ -69,7 +72,7 @@ int vm_state_init(struct vm_state *vm, size_t stack_size) { return 0; } -void vm_state_free(struct vm_state *vm) { +void vm_free(struct vm *vm) { struct vm_unit *unit; stack_free(&vm->data_stack); stack_free(&vm->call_stack); @@ -80,7 +83,7 @@ void vm_state_free(struct vm_state *vm) { vector_free(&vm->units); } -struct vm_unit *vm_add_unit(struct vm_state *vm, size_t global_pool_size) { +struct vm_unit *vm_add_unit(struct vm *vm, size_t global_pool_size) { struct vm_unit *unit = vector_append(&vm->units); if (!unit) { return unit; @@ -96,7 +99,7 @@ struct vm_unit *vm_add_unit(struct vm_state *vm, size_t global_pool_size) { } // Call an entry from function table of a unit -int vm_call_unit_index(struct vm_state *vm, size_t lib_index, size_t fn_index) { +int vm_call_unit_index(struct vm *vm, size_t lib_index, size_t fn_index) { struct vm_func_entry *func; struct vm_unit *unit; int res; @@ -136,26 +139,26 @@ int vm_call_unit_index(struct vm_state *vm, size_t lib_index, size_t fn_index) { return 0; } -static uint64_t *vm_local_ref(struct vm_state *vm, size_t index) { +static uint64_t *vm_local_ref(struct vm *vm, size_t index) { // TODO index sanity checks // [bp - index - 1] ++index; return &vm->call_stack.data[vm->bp - index]; } -static uint64_t *vm_arg_ref(struct vm_state *vm, size_t index) { +static uint64_t *vm_arg_ref(struct vm *vm, size_t index) { // [bp + index] return &vm->call_stack.data[vm->bp + index]; } -int vm_call_ref(struct vm_state *vm, struct vm_value *ref) { +int vm_call_ref(struct vm *vm, struct vm_value *ref) { if (ref->type != VT_FUNC) { return -ERR_OPERAND_TYPE; } return vm_call_unit_index(vm, ref->v_func.lib_index, ref->v_func.fn_index); } -static int vm_read_ext_ref(struct vm_state *vm, struct vm_ref_entry *ref, uint64_t *w) { +static int vm_read_ext_ref(struct vm *vm, struct vm_ref_entry *ref, uint64_t *w) { struct vm_unit *unit; int res; unit = vector_ref(&vm->units, ref->unit_index); @@ -173,26 +176,29 @@ static int vm_read_ext_ref(struct vm_state *vm, struct vm_ref_entry *ref, uint64 return 0; } -static int vm_eval_debug(struct vm_state *vm, struct vm_func_entry *func, uint32_t arg) { +static int vm_eval_debug(struct vm *vm, struct vm_func_entry *func, uint32_t arg) { (void) func; uint64_t w0; int res; switch (arg) { - case 0x01: + case OP_DEBUG_TRACE: if ((res = stack_pop(&vm->data_stack, &w0)) != 0) { return res; } printf("trace: "); - vm_print(w0); + vm_print(stdout, w0); printf("\n"); vm_unref(w0); return 0; + case OP_DEBUG_BREAKPOINT: + vm->state = STATE_BREAKPOINT; + return 0; default: return -ERR_OPCODE_UNDEFINED; } } -int vm_eval_step(struct vm_state *vm) { +int vm_eval_step(struct vm *vm) { uint64_t w0, w1; int64_t sw0, sw1; uint64_t *wr0; @@ -202,13 +208,16 @@ int vm_eval_step(struct vm_state *vm) { struct vm_value *v0; int res; + if (vm->state != STATE_RUNNING) { + return -ERR_INVALID_STATE; + } + assert(vm->lp < vm->units.size); struct vm_unit *unit = vector_ref(&vm->units, vm->lp); assert(vm->fp < unit->functions.size); struct vm_func_entry *func = vector_ref(&unit->functions, vm->fp); uint32_t opcode = func->bytecode[vm->ip++]; - printf("%02zx:%02zx:%02zx: %08x\n", vm->lp, vm->fp, vm->ip, opcode); switch (opcode >> 24) { case OP_ADD: if ((res = vm_pop_integer(vm, &sw0)) != 0) { @@ -368,7 +377,7 @@ int vm_eval_step(struct vm_state *vm) { } r0 = vector_ref(&unit->ref_table, i0); if (r0->flags & REF_NATIVE) { - int (*func) (struct vm_state *) = (void *) r0->ref_native; + int (*func) (struct vm *) = (void *) r0->ref_native; if (!func) { return -ERR_OPERAND_TYPE; } @@ -447,8 +456,9 @@ int vm_eval_step(struct vm_state *vm) { } } -int vm_eval_unit(struct vm_state *vm, size_t index) { - struct vm_unit *unit; +int vm_eval_unit(struct vm *vm, size_t index) { + struct vm_unit *unit, *curr_unit; + struct vm_func_entry *curr_func; size_t csp; int res; @@ -459,11 +469,37 @@ int vm_eval_unit(struct vm_state *vm, size_t index) { // Run until return while (vm->call_stack.sp != csp) { + if (vm->flags & VM_SINGLESTEP) { + curr_unit = vector_ref(&vm->units, vm->lp); + curr_func = vector_ref(&curr_unit->functions, vm->fp); + + // Check if it's a breakpoint instruction and skip it + if (curr_func->bytecode[vm->ip] == (uint32_t) OP(OP_DEBUG, OP_DEBUG_BREAKPOINT)) { + ++vm->ip; + continue; + } + + if ((res = debug_breakpoint(vm)) != 0) { + return res; + } + } + if (vm->state == STATE_BREAKPOINT) { + if ((res = debug_breakpoint(vm)) != 0) { + return res; + } + vm->state = STATE_RUNNING; + } else if (vm->state == STATE_ERROR) { + res = -ERR_ERROR_EXIT; + break; + } else if (vm->state == STATE_EXITED) { + break; + } + if ((res = vm_eval_step(vm)) != 0) { return res; } } unit->is_loaded = 1; - return 0; + return res; } diff --git a/vm/vmval.c b/vm/vmval.c index 7add3f6..3e2c223 100644 --- a/vm/vmval.c +++ b/vm/vmval.c @@ -3,51 +3,51 @@ #include #include -static void vm_print2(uint64_t w, int cdepth); +static void vm_print2(FILE *fp, uint64_t w, int cdepth); -static void vm_print_ref(struct vm_value *value, int cdepth) { +static void vm_print_ref(FILE *fp, struct vm_value *value, int cdepth) { uint64_t w0; if (!value) { - printf("nil"); + fprintf(fp, "nil"); return; } switch (value->type) { case VT_STRING: - printf("\"%s\"", vm_cstr(&value->v_string)); + fprintf(fp, "\"%s\"", vm_cstr(&value->v_string)); break; case VT_CONS: if (!cdepth) { printf("("); } - vm_print2(value->v_cons.fat_ar, 0); + vm_print2(fp, value->v_cons.fat_ar, 0); w0 = value->v_cons.fat_dr; if (!null_q(w0)) { - printf(" "); + fprintf(fp, " "); if (cons_q(w0)) { - vm_print2(w0, cdepth + 1); + vm_print2(fp, w0, cdepth + 1); } else { - printf(". "); - vm_print2(w0, cdepth + 1); + fprintf(fp, ". "); + vm_print2(fp, w0, cdepth + 1); } } if (!cdepth) { - printf(")"); + fprintf(fp, ")"); } break; case VT_FUNC: - printf("", value->v_func.lib_index, value->v_func.fn_index); + fprintf(fp, "", value->v_func.lib_index, value->v_func.fn_index); break; } } -static void vm_print2(uint64_t w, int cdepth) { +static void vm_print2(FILE *fp, uint64_t w, int cdepth) { if (w & FLAG_REF) { - vm_print_ref((void *) (w << 1), cdepth); + vm_print_ref(fp, (void *) (w << 1), cdepth); } else { if (w & (1ULL << 62)) { w |= 1ULL << 63; } - printf("%ld", (int64_t) w); + fprintf(fp, "%ld", (int64_t) w); } } @@ -131,6 +131,6 @@ struct vm_value *vm_func(size_t lib_index, size_t fn_index) { return v; } -void vm_print(uint64_t w) { - vm_print2(w, 0); +void vm_print(FILE *fp, uint64_t w) { + vm_print2(fp, w, 0); }