91961bcec5
1. (list ...) 2. string-* functions 3. (native str) to resolve native functions at runtime 4. Memory fuckups in compiler core unit loading 5. Now can call non-identifiers in compiler
543 lines
14 KiB
C
543 lines
14 KiB
C
#include "debug.h"
|
|
#include "error.h"
|
|
#include "op.h"
|
|
#include "unit.h"
|
|
#include "vmstate.h"
|
|
#include "vmval.h"
|
|
|
|
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
|
|
static inline int64_t sximm(uint32_t in) {
|
|
if (in & 0x800000) {
|
|
return in | 0xFFFFFFFFFF000000;
|
|
} else {
|
|
return in;
|
|
}
|
|
}
|
|
|
|
static int vm_push_context(struct vm *vm) {
|
|
int res;
|
|
if ((res = stack_push(&vm->call_stack, vm->bp)) != 0) {
|
|
return res;
|
|
}
|
|
if ((res = stack_push(&vm->call_stack, vm->lp)) != 0) {
|
|
return res;
|
|
}
|
|
if ((res = stack_push(&vm->call_stack, vm->fp)) != 0) {
|
|
return res;
|
|
}
|
|
if ((res = stack_push(&vm->call_stack, vm->ip)) != 0) {
|
|
return res;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int vm_pop_context(struct vm *vm) {
|
|
int res;
|
|
if ((res = stack_pop(&vm->call_stack, &vm->ip)) != 0) {
|
|
return res;
|
|
}
|
|
if ((res = stack_pop(&vm->call_stack, &vm->fp)) != 0) {
|
|
return res;
|
|
}
|
|
if ((res = stack_pop(&vm->call_stack, &vm->lp)) != 0) {
|
|
return res;
|
|
}
|
|
if ((res = stack_pop(&vm->call_stack, &vm->bp)) != 0) {
|
|
return res;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int vm_init(struct vm *vm, size_t stack_size) {
|
|
int res;
|
|
|
|
if ((res = stack_init(&vm->data_stack, stack_size)) != 0) {
|
|
return res;
|
|
}
|
|
if ((res = stack_init(&vm->call_stack, 1024)) != 0) {
|
|
return res;
|
|
}
|
|
|
|
vector_init(&vm->units, sizeof(struct vm_unit));
|
|
|
|
vm->state = STATE_INIT;
|
|
vm->flags = 0;
|
|
vm->lp = 0;
|
|
vm->fp = 0;
|
|
vm->ip = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void vm_free(struct vm *vm) {
|
|
struct vm_unit *unit;
|
|
stack_free(&vm->data_stack);
|
|
stack_free(&vm->call_stack);
|
|
for (size_t i = 0; i < vm->units.size; ++i) {
|
|
unit = vector_ref(&vm->units, i);
|
|
unit_free(unit);
|
|
}
|
|
vector_free(&vm->units);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
unit->is_loaded = 0;
|
|
unit->global_pool = calloc(sizeof(uint64_t), global_pool_size);
|
|
unit->global_pool_size = global_pool_size;
|
|
|
|
vector_init(&unit->ref_table, sizeof(struct vm_ref_entry));
|
|
vector_init(&unit->functions, sizeof(struct vm_func_entry));
|
|
vector_init(&unit->strtab, sizeof(char *));
|
|
return unit;
|
|
}
|
|
|
|
// Call an entry from function table of a unit
|
|
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;
|
|
uint64_t w;
|
|
|
|
unit = vector_ref(&vm->units, lib_index);
|
|
func = vector_ref(&unit->functions, fn_index);
|
|
|
|
if (func->argc > MAXARG) {
|
|
return -ERR_RANGE;
|
|
}
|
|
|
|
if ((res = vm_push_context(vm)) != 0) {
|
|
return res;
|
|
}
|
|
|
|
vm->lp = lib_index;
|
|
vm->fp = fn_index;
|
|
vm->ip = 0;
|
|
|
|
if (vm->call_stack.sp < func->argc + func->local_count) {
|
|
return -ERR_STACK_OVERFLOW;
|
|
}
|
|
|
|
vm->call_stack.sp -= func->argc;
|
|
for (size_t i = 0; i < func->argc; ++i) {
|
|
if ((res = stack_pop(&vm->data_stack, &w)) != 0) {
|
|
return res;
|
|
}
|
|
vm->call_stack.data[vm->call_stack.sp + i] = w;
|
|
}
|
|
|
|
vm->bp = vm->call_stack.sp;
|
|
// Reserve stack space for locals
|
|
vm->call_stack.sp -= func->local_count;
|
|
|
|
return 0;
|
|
}
|
|
|
|
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 *vm, size_t index) {
|
|
// [bp + index]
|
|
return &vm->call_stack.data[vm->bp + index];
|
|
}
|
|
|
|
int vm_call_ref(struct vm *vm, struct vm_value *ref) {
|
|
switch (ref->type) {
|
|
case VT_CFUNC:
|
|
{
|
|
int (*func) (struct vm *) = (void *) ref->v_cfunc;
|
|
if (!func) {
|
|
return -ERR_OPERAND_TYPE;
|
|
}
|
|
return func(vm);
|
|
}
|
|
case VT_FUNC:
|
|
return vm_call_unit_index(vm, ref->v_func.lib_index, ref->v_func.fn_index);
|
|
default:
|
|
return -ERR_OPERAND_TYPE;
|
|
}
|
|
}
|
|
|
|
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);
|
|
if (!unit->is_loaded) {
|
|
// Before trying to access target value, eval the unit first
|
|
if ((res = vm_eval_unit(vm, ref->unit_index)) != 0) {
|
|
return res;
|
|
}
|
|
}
|
|
assert(ref->ref_index < unit->global_pool_size);
|
|
if (ref->ref_index >= unit->global_pool_size) {
|
|
return -ERR_RANGE;
|
|
}
|
|
*w = unit->global_pool[ref->ref_index];
|
|
return 0;
|
|
}
|
|
|
|
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 OP_DEBUG_TRACE:
|
|
if ((res = stack_pop(&vm->data_stack, &w0)) != 0) {
|
|
return res;
|
|
}
|
|
printf("trace: ");
|
|
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 *vm) {
|
|
uint64_t w0, w1;
|
|
int64_t sw0, sw1;
|
|
uint64_t *wr0;
|
|
ssize_t ii0;
|
|
size_t i0;
|
|
struct vm_ref_entry *r0;
|
|
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++];
|
|
|
|
switch (opcode >> 24) {
|
|
case OP_ADD:
|
|
if ((res = vm_pop_integer(vm, &sw0)) != 0) {
|
|
return res;
|
|
}
|
|
if ((res = vm_pop_integer(vm, &sw1)) != 0) {
|
|
return res;
|
|
}
|
|
return vm_push_integer(vm, sw0 + sw1);
|
|
case OP_SUB:
|
|
if ((res = vm_pop_integer(vm, &sw0)) != 0) {
|
|
return res;
|
|
}
|
|
if ((res = vm_pop_integer(vm, &sw1)) != 0) {
|
|
return res;
|
|
}
|
|
return vm_push_integer(vm, sw0 - sw1);
|
|
case OP_MUL:
|
|
if ((res = vm_pop_integer(vm, &sw0)) != 0) {
|
|
return res;
|
|
}
|
|
if ((res = vm_pop_integer(vm, &sw1)) != 0) {
|
|
return res;
|
|
}
|
|
return vm_push_integer(vm, sw0 * sw1);
|
|
case OP_NOT:
|
|
if ((res = stack_pop(&vm->data_stack, &w0)) != 0) {
|
|
return res;
|
|
}
|
|
res = vm_push_bool(vm, null_q(w0));
|
|
vm_unref(w0);
|
|
return res;
|
|
case OP_EQ:
|
|
if ((res = stack_pop(&vm->data_stack, &w0)) != 0) {
|
|
return res;
|
|
}
|
|
if ((res = stack_pop(&vm->data_stack, &w1)) != 0) {
|
|
return res;
|
|
}
|
|
if (ref_q(w0) || ref_q(w1)) {
|
|
// TODO what do?
|
|
return -ERR_OPERAND_TYPE;
|
|
}
|
|
res = vm_push_bool(vm, w0 == w1);
|
|
vm_unref(w0);
|
|
vm_unref(w1);
|
|
return res;
|
|
case OP_NEQ:
|
|
if ((res = stack_pop(&vm->data_stack, &w0)) != 0) {
|
|
return res;
|
|
}
|
|
if ((res = stack_pop(&vm->data_stack, &w1)) != 0) {
|
|
return res;
|
|
}
|
|
if (ref_q(w0) || ref_q(w1)) {
|
|
// TODO what do?
|
|
return -ERR_OPERAND_TYPE;
|
|
}
|
|
res = vm_push_bool(vm, w0 != w1);
|
|
vm_unref(w0);
|
|
vm_unref(w1);
|
|
return res;
|
|
//
|
|
case OP_LDNIL:
|
|
return stack_push(&vm->data_stack, FLAG_REF);
|
|
case OP_LDI:
|
|
return vm_push_integer(vm, sximm(opcode & 0xFFFFFF));
|
|
case OP_CAR:
|
|
if ((res = stack_pop(&vm->data_stack, &w0)) != 0) {
|
|
return res;
|
|
}
|
|
if (!pair_q(w0)) {
|
|
return -ERR_OPERAND_TYPE;
|
|
}
|
|
vm_ref(getref(w0)->v_cons.fat_ar);
|
|
res = stack_push(&vm->data_stack, getref(w0)->v_cons.fat_ar);
|
|
vm_unref(w0);
|
|
return res;
|
|
case OP_CDR:
|
|
if ((res = stack_pop(&vm->data_stack, &w0)) != 0) {
|
|
return res;
|
|
}
|
|
if (!pair_q(w0)) {
|
|
return -ERR_OPERAND_TYPE;
|
|
}
|
|
vm_ref(getref(w0)->v_cons.fat_dr);
|
|
res = stack_push(&vm->data_stack, getref(w0)->v_cons.fat_dr);
|
|
vm_unref(w0);
|
|
return res;
|
|
case OP_CONS:
|
|
if ((res = stack_pop(&vm->data_stack, &w0)) != 0) {
|
|
return res;
|
|
}
|
|
if ((res = stack_pop(&vm->data_stack, &w1)) != 0) {
|
|
return res;
|
|
}
|
|
v0 = vm_cons(w0, w1);
|
|
vm_value_ref(v0);
|
|
res = vm_push_ref(vm, v0);
|
|
vm_unref(w0);
|
|
vm_unref(w1);
|
|
return res;
|
|
//
|
|
case OP_LDARG:
|
|
i0 = opcode & 0xFFFFFF;
|
|
if (i0 >= MAXARG) {
|
|
return -ERR_RANGE;
|
|
}
|
|
w0 = *vm_arg_ref(vm, i0);
|
|
vm_ref(w0);
|
|
return stack_push(&vm->data_stack, w0);
|
|
case OP_STARG:
|
|
i0 = opcode & 0xFFFFFF;
|
|
if (i0 >= MAXARG) {
|
|
return -ERR_RANGE;
|
|
}
|
|
wr0 = vm_arg_ref(vm, i0);
|
|
vm_unref(*wr0);
|
|
return stack_pop(&vm->data_stack, wr0);
|
|
case OP_LDG:
|
|
i0 = opcode & 0xFFFFFF;
|
|
if (i0 >= unit->global_pool_size) {
|
|
return -ERR_RANGE;
|
|
}
|
|
w0 = unit->global_pool[i0];
|
|
vm_ref(w0);
|
|
return stack_push(&vm->data_stack, w0);
|
|
case OP_STG:
|
|
i0 = opcode & 0xFFFFFF;
|
|
if (i0 >= unit->global_pool_size) {
|
|
return -ERR_RANGE;
|
|
}
|
|
vm_unref(unit->global_pool[i0]);
|
|
return stack_pop(&vm->data_stack, &unit->global_pool[i0]);
|
|
case OP_LDF:
|
|
i0 = opcode & 0xFFFFFF;
|
|
if (i0 >= unit->functions.size) {
|
|
return -ERR_RANGE;
|
|
}
|
|
v0 = vm_func(vm->lp, i0);
|
|
vm_value_ref(v0);
|
|
return vm_push_ref(vm, v0);
|
|
case OP_LDS:
|
|
i0 = opcode & 0xFFFFFF;
|
|
if (i0 >= unit->strtab.size) {
|
|
return -ERR_RANGE;
|
|
}
|
|
{
|
|
char **ref = vector_ref(&unit->strtab, i0);
|
|
v0 = vm_makestr(*ref);
|
|
vm_value_ref(v0);
|
|
return vm_push_ref(vm, v0);
|
|
}
|
|
case OP_STL:
|
|
i0 = opcode & 0xFFFFFF;
|
|
if (i0 >= func->local_count) {
|
|
return -ERR_RANGE;
|
|
}
|
|
wr0 = vm_local_ref(vm, i0);
|
|
vm_unref(*wr0);
|
|
return stack_pop(&vm->data_stack, wr0);
|
|
case OP_LDL:
|
|
i0 = opcode & 0xFFFFFF;
|
|
if (i0 >= func->local_count) {
|
|
return -ERR_RANGE;
|
|
}
|
|
w0 = *vm_local_ref(vm, i0);
|
|
vm_ref(w0);
|
|
return stack_push(&vm->data_stack, w0);
|
|
//
|
|
case OP_ISZ:
|
|
if ((res = stack_pop(&vm->data_stack, &w0)) != 0) {
|
|
return res;
|
|
}
|
|
res = vm_push_bool(vm, null_q(w0));
|
|
vm_unref(w0);
|
|
return res;
|
|
//
|
|
case OP_XCALL:
|
|
i0 = opcode & 0xFFFFFF;
|
|
if (i0 >= unit->ref_table.size) {
|
|
return -ERR_RANGE;
|
|
}
|
|
r0 = vector_ref(&unit->ref_table, i0);
|
|
if (r0->flags & REF_NATIVE) {
|
|
int (*func) (struct vm *) = (void *) r0->ref_native;
|
|
if (!func) {
|
|
return -ERR_OPERAND_TYPE;
|
|
}
|
|
return func(vm);
|
|
} else {
|
|
if ((res = vm_read_ext_ref(vm, r0, &w0)) != 0) {
|
|
return res;
|
|
}
|
|
if (!ref_q(w0) || null_q(w0)) {
|
|
return -ERR_OPERAND_TYPE;
|
|
}
|
|
return vm_call_ref(vm, getref(w0));
|
|
}
|
|
case OP_GCALL:
|
|
i0 = opcode & 0xFFFFFF;
|
|
if (i0 >= unit->global_pool_size) {
|
|
return -ERR_RANGE;
|
|
}
|
|
w0 = unit->global_pool[i0];
|
|
if (!ref_q(w0) || null_q(w0)) {
|
|
return -ERR_OPERAND_TYPE;
|
|
}
|
|
return vm_call_ref(vm, getref(w0));
|
|
case OP_CALL:
|
|
if ((res = stack_pop(&vm->data_stack, &w0)) != 0) {
|
|
return res;
|
|
}
|
|
if (!ref_q(w0) || null_q(w0)) {
|
|
return -ERR_OPERAND_TYPE;
|
|
}
|
|
res = vm_call_ref(vm, getref(w0));
|
|
vm_unref(w0);
|
|
return res;
|
|
case OP_BF:
|
|
if ((res = stack_pop(&vm->data_stack, &w0)) != 0) {
|
|
return res;
|
|
}
|
|
ii0 = sximm(opcode & 0xFFFFFF);
|
|
if (null_q(w0)) {
|
|
if (ii0 == 0) {
|
|
return -ERR_RANGE;
|
|
}
|
|
vm->ip += ii0 - 1;
|
|
}
|
|
vm_unref(w0);
|
|
return 0;
|
|
case OP_JMP:
|
|
ii0 = sximm(opcode & 0xFFFFFF);
|
|
if (ii0 == 0) {
|
|
return -ERR_RANGE;
|
|
}
|
|
vm->ip += ii0 - 1;
|
|
return 0;
|
|
case OP_RET:
|
|
// Drop locals from call stack
|
|
for (size_t i = 0; i < func->local_count; ++i) {
|
|
if ((res = stack_pop(&vm->call_stack, &w0)) != 0) {
|
|
return res;
|
|
}
|
|
vm_unref(w0);
|
|
}
|
|
// Drop references to arguments
|
|
for (size_t i = 0; i < func->argc; ++i) {
|
|
if ((res = stack_pop(&vm->call_stack, &w0)) != 0) {
|
|
return res;
|
|
}
|
|
vm_unref(w0);
|
|
}
|
|
return vm_pop_context(vm);
|
|
//
|
|
case OP_DEBUG:
|
|
return vm_eval_debug(vm, func, opcode & 0xFFFFFF);
|
|
default:
|
|
fprintf(stderr, "Undefined opcode: %02x\n", opcode >> 24);
|
|
return -ERR_OPCODE_UNDEFINED;
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
unit = vector_ref(&vm->units, index);
|
|
|
|
csp = vm->call_stack.sp;
|
|
vm_call_unit_index(vm, index, 0);
|
|
|
|
// 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 res;
|
|
}
|