323 lines
8.2 KiB
C
323 lines
8.2 KiB
C
#include "vmstate.h"
|
|
#include "vmval.h"
|
|
#include "vm.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 inline uint64_t encode_ref(struct vm_value *ref) {
|
|
uintptr_t addr = (uintptr_t) ref;
|
|
assert(!(addr & 1));
|
|
if (sizeof(uintptr_t) == 8) {
|
|
return (addr >> 1) | FLAG_REF;
|
|
} else {
|
|
abort();
|
|
}
|
|
}
|
|
|
|
uint64_t pop(struct vm_state *vm) {
|
|
assert(vm->sp != vm->stack_size);
|
|
return vm->stack[vm->sp++];
|
|
}
|
|
|
|
void push(struct vm_state *vm, uint64_t w) {
|
|
assert(vm->sp > 0);
|
|
vm->stack[--vm->sp] = w;
|
|
}
|
|
|
|
void push_ref(struct vm_state *vm, struct vm_value *ref) {
|
|
assert(vm->sp > 0);
|
|
vm->stack[--vm->sp] = encode_ref(ref);
|
|
}
|
|
|
|
void push_integer(struct vm_state *vm, int64_t w) {
|
|
push(vm, w & ~FLAG_REF);
|
|
}
|
|
|
|
uint64_t pop_integer(struct vm_state *vm) {
|
|
uint64_t w = pop(vm);
|
|
assert(!(w & FLAG_REF));
|
|
if (w & (1ULL << 62)) {
|
|
w |= 1ULL << 63;
|
|
}
|
|
return w;
|
|
}
|
|
|
|
void vm_state_init(struct vm_state *vm, size_t stack_size) {
|
|
vm->stack = calloc(sizeof(uint64_t), stack_size);
|
|
vm->sp = stack_size;
|
|
vm->stack_size = stack_size;
|
|
|
|
vm->call_stack_size = 1024;
|
|
vm->call_stack = calloc(sizeof(uint64_t), vm->call_stack_size);
|
|
vm->csp = vm->call_stack_size;
|
|
|
|
vector_init(&vm->units, sizeof(struct vm_unit));
|
|
|
|
vm->lp = 0;
|
|
vm->fp = 0;
|
|
vm->ip = 0;
|
|
}
|
|
|
|
struct vm_unit *vm_add_unit(struct vm_state *vm, size_t global_pool_size) {
|
|
struct vm_unit *unit = vector_append(&vm->units);
|
|
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));
|
|
return unit;
|
|
}
|
|
|
|
struct vm_ref_entry *unit_add_ref(struct vm_unit *u) {
|
|
return vector_append(&u->ref_table);
|
|
}
|
|
|
|
struct vm_func_entry *unit_add_function(struct vm_unit *u) {
|
|
return vector_append(&u->functions);
|
|
}
|
|
|
|
// Call an entry from function table of a unit
|
|
void vm_call_unit_index(struct vm_state *vm, size_t lib_index, size_t fn_index) {
|
|
struct vm_func_entry *func;
|
|
struct vm_unit *unit;
|
|
assert(vm->csp >= 3);
|
|
|
|
unit = vector_ref(&vm->units, lib_index);
|
|
func = vector_ref(&unit->functions, fn_index);
|
|
|
|
vm->call_stack[--vm->csp] = vm->lp;
|
|
vm->call_stack[--vm->csp] = vm->fp;
|
|
vm->call_stack[--vm->csp] = vm->ip;
|
|
vm->lp = lib_index;
|
|
vm->fp = fn_index;
|
|
vm->ip = 0;
|
|
|
|
assert(func->argc <= MAXARG);
|
|
for (size_t i = 0; i < func->argc; ++i) {
|
|
func->arg_regs[i] = pop(vm);
|
|
}
|
|
}
|
|
|
|
void vm_call_ref(struct vm_state *vm, struct vm_value *ref) {
|
|
assert(ref->type == VT_FUNC);
|
|
vm_call_unit_index(vm, ref->v_func.lib_index, ref->v_func.fn_index);
|
|
}
|
|
|
|
static uint64_t vm_read_ext_ref(struct vm_state *vm, struct vm_ref_entry *ref) {
|
|
struct vm_unit *unit;
|
|
unit = vector_ref(&vm->units, ref->unit_index);
|
|
if (!unit->is_loaded) {
|
|
// Before trying to access target value, eval the unit first
|
|
int res = vm_eval_unit(vm, ref->unit_index);
|
|
assert(res == 0);
|
|
}
|
|
assert(ref->ref_index < unit->global_pool_size);
|
|
return unit->global_pool[ref->ref_index];
|
|
}
|
|
|
|
static int vm_eval_debug(struct vm_state *vm, struct vm_func_entry *func, uint32_t arg) {
|
|
(void) func;
|
|
uint64_t w0;
|
|
switch (arg) {
|
|
case 0x01:
|
|
w0 = pop(vm);
|
|
printf("trace: ");
|
|
vm_print(w0);
|
|
printf("\n");
|
|
return 0;
|
|
default:
|
|
fprintf(stderr, "Undefined debug opcode: %02x\n", arg);
|
|
abort();
|
|
}
|
|
}
|
|
|
|
int vm_eval_step(struct vm_state *vm) {
|
|
uint64_t w0, w1;
|
|
int64_t sw0, sw1;
|
|
size_t i0;
|
|
ssize_t ii0;
|
|
struct vm_ref_entry *r0;
|
|
|
|
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:
|
|
sw0 = pop_integer(vm);
|
|
sw1 = pop_integer(vm);
|
|
push_integer(vm, sw0 + sw1);
|
|
return 0;
|
|
case OP_NOT:
|
|
w0 = pop(vm);
|
|
if (null_q(w0)) {
|
|
push_integer(vm, 1);
|
|
} else {
|
|
push_ref(vm, NULL);
|
|
}
|
|
return 0;
|
|
case OP_EQ:
|
|
w0 = pop(vm);
|
|
w1 = pop(vm);
|
|
if (ref_q(w0) || ref_q(w1)) {
|
|
assert(0 && "Ref cmp not implemented yet");
|
|
}
|
|
if (w0 == w1) {
|
|
push_integer(vm, 1);
|
|
} else {
|
|
push_ref(vm, NULL);
|
|
}
|
|
return 0;
|
|
//
|
|
case OP_LDNIL:
|
|
push_ref(vm, NULL);
|
|
return 0;
|
|
case OP_LDI:
|
|
push_integer(vm, sximm(opcode & 0xFFFFFF));
|
|
return 0;
|
|
case OP_CAR:
|
|
w0 = pop(vm);
|
|
assert(pair_q(w0));
|
|
push(vm, getref(w0)->v_cons.fat_ar);
|
|
return 0;
|
|
case OP_CDR:
|
|
w0 = pop(vm);
|
|
assert(pair_q(w0));
|
|
push(vm, getref(w0)->v_cons.fat_dr);
|
|
return 0;
|
|
case OP_CONS:
|
|
w0 = pop(vm);
|
|
w1 = pop(vm);
|
|
push_ref(vm, vm_cons(w0, w1));
|
|
return 0;
|
|
//
|
|
case OP_LDARG:
|
|
i0 = opcode & 0xFFFFFF;
|
|
assert(i0 < MAXARG);
|
|
push(vm, func->arg_regs[i0]);
|
|
return 0;
|
|
case OP_STARG:
|
|
i0 = opcode & 0xFFFFFF;
|
|
w0 = pop(vm);
|
|
assert(i0 < MAXARG);
|
|
func->arg_regs[i0] = w0;
|
|
return 0;
|
|
case OP_LDG:
|
|
i0 = opcode & 0xFFFFFF;
|
|
assert(i0 < unit->global_pool_size);
|
|
push(vm, unit->global_pool[i0]);
|
|
return 0;
|
|
case OP_STG:
|
|
w0 = pop(vm);
|
|
i0 = opcode & 0xFFFFFF;
|
|
assert(i0 < unit->global_pool_size);
|
|
unit->global_pool[i0] = w0;
|
|
return 0;
|
|
case OP_LDF:
|
|
i0 = opcode & 0xFFFFFF;
|
|
assert(i0 < unit->functions.size);
|
|
push_ref(vm, vm_func(vm->lp, i0));
|
|
return 0;
|
|
case OP_STL:
|
|
i0 = opcode & 0xFFFFFF;
|
|
assert(i0 < func->local_count);
|
|
func->local_regs[i0] = pop(vm);
|
|
return 0;
|
|
case OP_LDL:
|
|
i0 = opcode & 0xFFFFFF;
|
|
assert(i0 < func->local_count);
|
|
push(vm, func->local_regs[i0]);
|
|
return 0;
|
|
//
|
|
case OP_ISZ:
|
|
w0 = pop(vm);
|
|
if (null_q(w0)) {
|
|
push_integer(vm, 1);
|
|
} else {
|
|
push_ref(vm, NULL);
|
|
}
|
|
return 0;
|
|
//
|
|
case OP_XCALL:
|
|
i0 = opcode & 0xFFFFFF;
|
|
r0 = vector_ref(&unit->ref_table, i0);
|
|
if (r0->flags & REF_NATIVE) {
|
|
int (*func) (struct vm_state *) = (void *) r0->ref_native;
|
|
assert(func);
|
|
return func(vm);
|
|
} else {
|
|
w0 = vm_read_ext_ref(vm, r0);
|
|
assert(func_q(w0));
|
|
vm_call_ref(vm, getref(w0));
|
|
return 0;
|
|
}
|
|
case OP_GCALL:
|
|
i0 = opcode & 0xFFFFFF;
|
|
assert(i0 < unit->global_pool_size);
|
|
w0 = unit->global_pool[i0];
|
|
assert(func_q(w0));
|
|
vm_call_ref(vm, getref(w0));
|
|
return 0;
|
|
case OP_CALL:
|
|
w0 = pop(vm);
|
|
assert(func_q(w0));
|
|
vm_call_ref(vm, getref(w0));
|
|
return 0;
|
|
case OP_BF:
|
|
w0 = pop(vm);
|
|
ii0 = sximm(opcode & 0xFFFFFF);
|
|
if (null_q(w0)) {
|
|
assert(ii0 != 0);
|
|
vm->ip += ii0 - 1;
|
|
}
|
|
return 0;
|
|
case OP_JMP:
|
|
ii0 = sximm(opcode & 0xFFFFFF);
|
|
assert(ii0 != 0);
|
|
vm->ip += ii0 - 1;
|
|
return 0;
|
|
case OP_RET:
|
|
assert(vm->call_stack_size - vm->csp >= 2);
|
|
vm->ip = vm->call_stack[vm->csp++];
|
|
vm->fp = vm->call_stack[vm->csp++];
|
|
vm->lp = vm->call_stack[vm->csp++];
|
|
return 0;
|
|
//
|
|
case OP_DEBUG:
|
|
return vm_eval_debug(vm, func, opcode & 0xFFFFFF);
|
|
default:
|
|
fprintf(stderr, "Undefined opcode: 0x%02hhx\n", opcode >> 24);
|
|
abort();
|
|
}
|
|
}
|
|
|
|
int vm_eval_unit(struct vm_state *vm, size_t index) {
|
|
struct vm_unit *unit;
|
|
size_t csp = vm->csp;
|
|
|
|
unit = vector_ref(&vm->units, index);
|
|
vm_call_unit_index(vm, index, 0);
|
|
// Run until return
|
|
while (vm->csp != csp) {
|
|
int res = vm_eval_step(vm);
|
|
if (res == -1) {
|
|
return -1;
|
|
}
|
|
}
|
|
unit->is_loaded = 1;
|
|
|
|
return 0;
|
|
}
|