Files
spl/stage0/spl_vm.c

1097 lines
52 KiB
C

/* spl_vm.c — SIR step-by-step interpreter
*
* Implements the spl_vm.h API with macro-based type dispatch to
* eliminate repetitive per-type switch cases.
*/
#include "spl_vm.h"
#include "spl_ir.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(_WIN32) || defined(_WIN64)
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#define SPL_DLOPEN(name) ((void *)LoadLibraryA(name))
#define SPL_DLSYM(lib, fn) ((void *)GetProcAddress((HMODULE)lib, fn))
#define SPL_DLCLOSE(lib) FreeLibrary((HMODULE)lib)
#else
#include <dlfcn.h>
#define SPL_DLOPEN(name) dlopen(name, RTLD_LAZY | RTLD_LOCAL)
#define SPL_DLSYM(lib, fn) dlsym(lib, fn)
#define SPL_DLCLOSE(lib) dlclose(lib)
#endif
/* ================================================================
* Type helpers
* ================================================================ */
static int spl_is_float(spl_type_t t) { return t == SPL_F32 || t == SPL_F64; }
static int spl_type_size(spl_type_t t) {
switch (t) {
case SPL_VOID:
return 0;
case SPL_I8:
case SPL_U8:
return 1;
case SPL_I16:
case SPL_U16:
return 2;
case SPL_I32:
case SPL_U32:
case SPL_F32:
return 4;
case SPL_I64:
case SPL_U64:
case SPL_F64:
return 8;
case SPL_ISIZE:
case SPL_USIZE:
case SPL_PTR:
return sizeof(void *);
case SPL_TYPE_COUNT:
return 0;
}
return 0;
}
/* ================================================================
* Error reporting macro
* ================================================================ */
#define VM_ERROR(msg) \
do { \
fprintf(stderr, "vm: error at ip=%zd: %s\n", vm->ip - 1, msg); \
vm->exit_code = 1; \
return -1; \
} while (0)
/* ================================================================
* Stack push/pop (stacks.data is pre-allocated in init)
* ================================================================ */
#define PUSH(v) \
do { \
if (vm->sp >= vm->config.max_stack_depth) \
VM_ERROR("stack overflow"); \
vm->stacks.data[(vm->sp)++] = (spl_val_t)(v); \
} while (0)
#define POP() vm->stacks.data[--(vm->sp)]
/* ================================================================
* Type-dispatch macros for arithmetic / comparison
*
* ARITH_BINOP — ADD / SUB / MUL (two's complement: op same for
* signed and unsigned at the same bit-width)
* DIV_REM_S — signed division / remainder
* DIV_REM_U — unsigned division / remainder
* CMP_ALL — EQ / NE (bitwise compare, also handles floats)
* CMP_S — signed ordering (<, <=, >, >=)
* CMP_U — unsigned ordering
* ================================================================ */
#define ARITH_BINOP(OP) \
do { \
spl_val_t _b = POP(), _a = POP(); \
spl_val_t _r = 0; \
if (spl_is_float((spl_type_t)ins->type)) { \
double _da, _db, _dr; \
if (ins->type == SPL_F32) { \
float _fa, _fb; \
memcpy(&_fa, &_a, 4); \
memcpy(&_fb, &_b, 4); \
_da = _fa; \
_db = _fb; \
} else { \
memcpy(&_da, &_a, 8); \
memcpy(&_db, &_b, 8); \
} \
_dr = _da OP _db; \
if (ins->type == SPL_F32) { \
float _fr = (float)_dr; \
memcpy(&_r, &_fr, 4); \
} else { \
memcpy(&_r, &_dr, 8); \
} \
} else { \
switch (ins->type) { \
case SPL_I8: \
_r = (spl_val_t)((int8_t)_a OP(int8_t) _b); \
break; \
case SPL_U8: \
_r = (spl_val_t)((uint8_t)_a OP(uint8_t) _b); \
break; \
case SPL_I16: \
_r = (spl_val_t)((int16_t)_a OP(int16_t) _b); \
break; \
case SPL_U16: \
_r = (spl_val_t)((uint16_t)_a OP(uint16_t) _b); \
break; \
case SPL_I32: \
_r = (spl_val_t)((int32_t)_a OP(int32_t) _b); \
break; \
case SPL_U32: \
_r = (spl_val_t)((uint32_t)_a OP(uint32_t) _b); \
break; \
case SPL_I64: \
_r = (spl_val_t)((int64_t)_a OP(int64_t) _b); \
break; \
case SPL_U64: \
case SPL_USIZE: \
case SPL_ISIZE: \
_r = _a OP _b; \
break; \
default: \
VM_ERROR("bad type for arithmetic"); \
} \
} \
PUSH(_r); \
} while (0)
/* signed division / remainder — all types cast to signed */
#define DIV_REM_S(OP) \
do { \
spl_val_t _b = POP(), _a = POP(); \
if (_b == 0) \
VM_ERROR("division by zero"); \
spl_val_t _r = 0; \
if (spl_is_float((spl_type_t)ins->type)) { \
double _da, _db, _dr; \
if (ins->type == SPL_F32) { \
float _fa, _fb; \
memcpy(&_fa, &_a, 4); \
memcpy(&_fb, &_b, 4); \
_da = _fa; \
_db = _fb; \
} else { \
memcpy(&_da, &_a, 8); \
memcpy(&_db, &_b, 8); \
} \
_dr = _da / _db; \
if (ins->type == SPL_F32) { \
float _fr = (float)_dr; \
memcpy(&_r, &_fr, 4); \
} else { \
memcpy(&_r, &_dr, 8); \
} \
} else { \
switch (ins->type) { \
case SPL_I8: \
_r = (spl_val_t)((int8_t)_a OP(int8_t) _b); \
break; \
case SPL_U8: \
_r = (spl_val_t)((int8_t)_a OP(int8_t) _b); \
break; \
case SPL_I16: \
_r = (spl_val_t)((int16_t)_a OP(int16_t) _b); \
break; \
case SPL_U16: \
_r = (spl_val_t)((int16_t)_a OP(int16_t) _b); \
break; \
case SPL_I32: \
_r = (spl_val_t)((int32_t)_a OP(int32_t) _b); \
break; \
case SPL_U32: \
_r = (spl_val_t)((int32_t)_a OP(int32_t) _b); \
break; \
case SPL_I64: \
_r = (spl_val_t)((int64_t)_a OP(int64_t) _b); \
break; \
case SPL_U64: \
case SPL_USIZE: \
case SPL_ISIZE: \
_r = (spl_val_t)((int64_t)_a OP(int64_t) _b); \
break; \
default: \
VM_ERROR("bad type for signed division"); \
} \
} \
PUSH(_r); \
} while (0)
/* unsigned division / remainder */
#define DIV_REM_U(OP) \
do { \
spl_val_t _b = POP(), _a = POP(); \
if (_b == 0) \
VM_ERROR("division by zero"); \
spl_val_t _r = 0; \
switch (ins->type) { \
case SPL_I8: \
_r = (spl_val_t)((uint8_t)_a OP(uint8_t) _b); \
break; \
case SPL_U8: \
_r = (spl_val_t)((uint8_t)_a OP(uint8_t) _b); \
break; \
case SPL_I16: \
_r = (spl_val_t)((uint16_t)_a OP(uint16_t) _b); \
break; \
case SPL_U16: \
_r = (spl_val_t)((uint16_t)_a OP(uint16_t) _b); \
break; \
case SPL_I32: \
_r = (spl_val_t)((uint32_t)_a OP(uint32_t) _b); \
break; \
case SPL_U32: \
_r = (spl_val_t)((uint32_t)_a OP(uint32_t) _b); \
break; \
case SPL_I64: \
_r = (spl_val_t)((uint64_t)_a OP(uint64_t) _b); \
break; \
case SPL_U64: \
case SPL_USIZE: \
case SPL_ISIZE: \
_r = _a OP _b; \
break; \
default: \
VM_ERROR("bad type for unsigned division"); \
} \
PUSH(_r); \
} while (0)
/* CMP_ALL — equality comparisons (all types, floats via memcpy) */
#define CMP_ALL(OP) \
do { \
spl_val_t _b = POP(), _a = POP(); \
intptr_t _r = 0; \
switch (ins->type) { \
case SPL_I8: \
_r = (int8_t)_a OP(int8_t) _b; \
break; \
case SPL_U8: \
_r = (uint8_t)_a OP(uint8_t) _b; \
break; \
case SPL_I16: \
_r = (int16_t)_a OP(int16_t) _b; \
break; \
case SPL_U16: \
_r = (uint16_t)_a OP(uint16_t) _b; \
break; \
case SPL_I32: \
_r = (int32_t)_a OP(int32_t) _b; \
break; \
case SPL_U32: \
_r = (uint32_t)_a OP(uint32_t) _b; \
break; \
case SPL_I64: \
_r = (int64_t)_a OP(int64_t) _b; \
break; \
case SPL_U64: \
case SPL_USIZE: \
case SPL_ISIZE: \
_r = _a OP _b; \
break; \
case SPL_F32: { \
float _fa, _fb; \
memcpy(&_fa, &_a, 4); \
memcpy(&_fb, &_b, 4); \
_r = _fa OP _fb; \
break; \
} \
case SPL_F64: { \
double _da, _db; \
memcpy(&_da, &_a, 8); \
memcpy(&_db, &_b, 8); \
_r = _da OP _db; \
break; \
} \
default: \
_r = 0; \
break; \
} \
PUSH(_r); \
} while (0)
/* CMP_S — signed ordering (all ints cast to signed, floats OK) */
#define CMP_S(OP) \
do { \
spl_val_t _b = POP(), _a = POP(); \
intptr_t _r = 0; \
switch (ins->type) { \
case SPL_I8: \
_r = (int8_t)_a OP(int8_t) _b; \
break; \
case SPL_U8: \
_r = (int8_t)_a OP(int8_t) _b; \
break; \
case SPL_I16: \
_r = (int16_t)_a OP(int16_t) _b; \
break; \
case SPL_U16: \
_r = (int16_t)_a OP(int16_t) _b; \
break; \
case SPL_I32: \
_r = (int32_t)_a OP(int32_t) _b; \
break; \
case SPL_U32: \
_r = (int32_t)_a OP(int32_t) _b; \
break; \
case SPL_I64: \
_r = (int64_t)_a OP(int64_t) _b; \
break; \
case SPL_U64: \
case SPL_USIZE: \
case SPL_ISIZE: \
_r = _a OP _b; \
break; \
case SPL_F32: { \
float _fa, _fb; \
memcpy(&_fa, &_a, 4); \
memcpy(&_fb, &_b, 4); \
_r = _fa OP _fb; \
break; \
} \
case SPL_F64: { \
double _da, _db; \
memcpy(&_da, &_a, 8); \
memcpy(&_db, &_b, 8); \
_r = _da OP _db; \
break; \
} \
default: \
_r = 0; \
break; \
} \
PUSH(_r); \
} while (0)
/* CMP_U — unsigned ordering (all ints cast to unsigned, no float) */
#define CMP_U(OP) \
do { \
spl_val_t _b = POP(), _a = POP(); \
intptr_t _r = 0; \
switch (ins->type) { \
case SPL_I8: \
_r = (uint8_t)_a OP(uint8_t) _b; \
break; \
case SPL_U8: \
_r = (uint8_t)_a OP(uint8_t) _b; \
break; \
case SPL_I16: \
_r = (uint16_t)_a OP(uint16_t) _b; \
break; \
case SPL_U16: \
_r = (uint16_t)_a OP(uint16_t) _b; \
break; \
case SPL_I32: \
_r = (uint32_t)_a OP(uint32_t) _b; \
break; \
case SPL_U32: \
_r = (uint32_t)_a OP(uint32_t) _b; \
break; \
case SPL_I64: \
_r = (uint64_t)_a OP(uint64_t) _b; \
break; \
case SPL_U64: \
case SPL_USIZE: \
case SPL_ISIZE: \
_r = _a OP _b; \
break; \
default: \
_r = 0; \
break; \
} \
PUSH(_r); \
} while (0)
// 辅助函数:将异常代码转为可读字符串
const char *ExceptionCodeToString(DWORD code) {
switch (code) {
case EXCEPTION_ACCESS_VIOLATION:
return "ACCESS_VIOLATION";
case EXCEPTION_ARRAY_BOUNDS_EXCEEDED:
return "ARRAY_BOUNDS_EXCEEDED";
case EXCEPTION_BREAKPOINT:
return "BREAKPOINT";
case EXCEPTION_DATATYPE_MISALIGNMENT:
return "DATATYPE_MISALIGNMENT";
case EXCEPTION_FLT_DENORMAL_OPERAND:
return "FLT_DENORMAL_OPERAND";
case EXCEPTION_FLT_DIVIDE_BY_ZERO:
return "FLT_DIVIDE_BY_ZERO";
case EXCEPTION_FLT_INEXACT_RESULT:
return "FLT_INEXACT_RESULT";
case EXCEPTION_FLT_INVALID_OPERATION:
return "FLT_INVALID_OPERATION";
case EXCEPTION_FLT_OVERFLOW:
return "FLT_OVERFLOW";
case EXCEPTION_FLT_STACK_CHECK:
return "FLT_STACK_CHECK";
case EXCEPTION_FLT_UNDERFLOW:
return "FLT_UNDERFLOW";
case EXCEPTION_ILLEGAL_INSTRUCTION:
return "ILLEGAL_INSTRUCTION";
case EXCEPTION_IN_PAGE_ERROR:
return "IN_PAGE_ERROR";
case EXCEPTION_INT_DIVIDE_BY_ZERO:
return "INT_DIVIDE_BY_ZERO";
case EXCEPTION_INT_OVERFLOW:
return "INT_OVERFLOW";
case EXCEPTION_INVALID_DISPOSITION:
return "INVALID_DISPOSITION";
case EXCEPTION_NONCONTINUABLE_EXCEPTION:
return "NONCONTINUABLE_EXCEPTION";
case EXCEPTION_PRIV_INSTRUCTION:
return "PRIV_INSTRUCTION";
case EXCEPTION_SINGLE_STEP:
return "SINGLE_STEP";
case EXCEPTION_STACK_OVERFLOW:
return "STACK_OVERFLOW";
default:
return "UNKNOWN_EXCEPTION";
}
}
// 全局未处理异常过滤器
LONG WINAPI UnhandledExceptionFilterImpl(EXCEPTION_POINTERS *pExceptionInfo) {
// 获取异常记录和上下文
PEXCEPTION_RECORD record = pExceptionInfo->ExceptionRecord;
PCONTEXT context = pExceptionInfo->ContextRecord;
// 打印基础信息
fprintf(stderr, "========================================\n");
fprintf(stderr, " Unhandled Exception Caught!\n");
fprintf(stderr, " Exception Code: 0x%08lX (%s)\n", record->ExceptionCode,
ExceptionCodeToString(record->ExceptionCode));
fprintf(stderr, " Exception Address: 0x%p\n", record->ExceptionAddress);
fprintf(stderr, " Exception Flags: %ld\n", record->ExceptionFlags);
// 针对访问违例,打印更多细节
if (record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
// ExceptionInformation[0]: 0=读, 1=写, 8=执行
// ExceptionInformation[1]: 违例的目标地址
if (record->NumberParameters >= 2) {
const char *operation;
switch (record->ExceptionInformation[0]) {
case 0:
operation = "Read";
break;
case 1:
operation = "Write";
break;
case 8:
operation = "Execute";
break;
default:
operation = "Unknown";
break;
}
fprintf(stderr, " Access Violation: %s at address 0x%p\n", operation,
(void *)(ULONG_PTR)record->ExceptionInformation[1]);
}
}
// 可选:打印发生异常时的部分寄存器(例如 EIP/RIP, EAX/RAX 等)
#ifdef _M_X64
fprintf(stderr, " Registers:\n");
fprintf(stderr, " RIP: 0x%p RSP: 0x%p RAX: 0x%p\n", (void *)context->Rip,
(void *)context->Rsp, (void *)context->Rax);
#else
fprintf(stderr, " Registers:\n");
fprintf(stderr, " EIP: 0x%p ESP: 0x%p EAX: 0x%p\n", (void *)context->Eip,
(void *)context->Esp, (void *)context->Eax);
#endif
fprintf(stderr, "========================================\n");
fflush(stderr);
// 如果附加了调试器,同步输出到调试器窗口
OutputDebugStringA("Unhandled exception occurred, check stderr.\n");
// 返回 EXCEPTION_EXECUTE_HANDLER 会终止进程
// 你可以在这里调用 exit(1) 或直接返回,进程会被终止
return EXCEPTION_EXECUTE_HANDLER;
}
/* ================================================================
* spl_vm_init / spl_vm_drop
* ================================================================ */
void spl_vm_init_ex(spl_vm_t *vm, int stack_size, int call_depth) {
#ifdef _WIN32
SetUnhandledExceptionFilter(UnhandledExceptionFilterImpl);
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
#endif
if (!vm)
return;
vm->config.max_stack_depth = stack_size > 0 ? stack_size : 1024 * 8;
vm->config.max_call_depth = call_depth > 0 ? call_depth : 128;
vec_init(vm->stacks);
vec_realloc(vm->stacks, (usize)vm->config.max_stack_depth);
vec_init(vm->frames);
vec_realloc(vm->frames, (usize)vm->config.max_call_depth);
vm->sp = vm->fp = vm->ip = vm->cp = vm->gp = 0;
vm->prog = NULL;
vm->trace = 0;
vm->debug = 1;
vm->exit_code = 0;
}
void spl_vm_init(spl_vm_t *vm) { spl_vm_init_ex(vm, 0, 0); }
void spl_vm_drop(spl_vm_t *vm) {
if (!vm)
return;
vec_free(vm->stacks);
vec_free(vm->frames);
}
/* ================================================================
* spl_vm_load_prog
* ================================================================ */
int spl_vm_load_prog(spl_vm_t *vm, spl_prog_t *prog) {
if (!vm || !prog)
return -1;
vm->prog = prog;
return 0;
}
/* ================================================================
* spl_vm_set_trace
* ================================================================ */
void spl_vm_set_trace(spl_vm_t *vm, int enabled) {
if (!vm)
return;
vm->trace = enabled ? 1 : 0;
}
void spl_vm_set_debug(spl_vm_t *vm, int enabled) {
if (!vm)
return;
vm->debug = enabled ? 1 : 0;
}
#define STACK_CANARY(vm) (vm)->stacks.data[(vm)->fp - 1]
static inline int spl_vm_call(spl_vm_t *vm, spl_val_t addr, spl_val_t nargs) {
if (vm->cp >= vm->config.max_call_depth)
VM_ERROR("CALLI: call stack overflow");
// spl_vm_stackdump(vm, vm->sp);
PUSH(SPL_STACK_CANARY);
/* Shift args right by 1 to create gap for canary at fp-1 */
for (usize i = vm->sp; i >= vm->sp - nargs; --i) {
vm->stacks.data[i] = vm->stacks.data[i - 1];
}
vm->frames.data[vm->cp].saved_sp = vm->sp - nargs - 1;
vm->frames.data[vm->cp].saved_fp = vm->fp;
vm->frames.data[vm->cp].saved_ip = vm->ip;
vm->frames.data[vm->cp].nargs = nargs;
vm->cp++;
vm->ip = addr;
vm->fp = vm->sp - nargs;
if (vm->fp > 0)
STACK_CANARY(vm) = SPL_STACK_CANARY;
// spl_vm_stackdump(vm, vm->sp);
return 0;
}
int spl_vm_prepare(spl_vm_t *vm, const char *entry, int argc, const char **argv,
const char **envp) {
if (!vm || !vm->prog) {
return -1;
}
spl_func_t *fn = spl_prog_get_func(vm->prog, entry ? entry : "main");
if (!fn) {
fprintf(stderr, "vm: entry point '%s' not found\n", entry ? entry : "main");
return -1;
}
vm->fp = 0;
vm->sp = 0;
vm->cp = 0;
vm->ip = -1;
vm->exit_code = 0;
if (fn->nargs >= 1) {
PUSH(argc);
}
if (fn->nargs >= 2) {
PUSH(argv);
}
if (fn->nargs >= 3) {
PUSH(envp);
}
if (fn->nargs >= 4) {
printf("the start symbol can't using more than 3 args");
return -1;
}
/* push sentinel frame so RET knows this is the entry return */
spl_vm_call(vm, fn->address, fn->nargs);
return 0;
}
/* ================================================================
* spl_vm_run_once — execute one instruction
*
* Returns: 0 = still running, 1 = halted, 2 = breakpoint (SPL_DBG), -1 = error
* ================================================================ */
int spl_vm_run_once(spl_vm_t *vm) {
const spl_ins_t *ins;
spl_prog_t *prog;
if (!vm || !vm->prog)
return -1;
prog = vm->prog;
if (vm->ip >= vec_size(prog->insns)) {
fprintf(stderr, "vm: ip=%zd out of bounds\n", vm->ip);
vm->exit_code = 1;
return -1;
}
ins = &vec_at(prog->insns, vm->ip);
vm->ip++;
if (vm->trace) {
fprintf(stderr, "vm: ip=%zd op=%s type=%s imm=%zu sp=%zd fp=%zd\n", vm->ip - 1,
spl_opcode_name(ins->opcode), spl_type_name(ins->type), ins->imm, vm->sp, vm->fp);
}
switch (ins->opcode) {
/* ========== Stack ========== */
case SPL_PUSH:
PUSH(ins->imm);
break;
case SPL_DUP: {
if (vm->sp < 1)
VM_ERROR("DUP: stack underflow");
spl_val_t _v = vm->stacks.data[vm->sp - 1];
PUSH(_v);
break;
}
case SPL_DROP:
if (vm->sp < 1)
VM_ERROR("DROP: stack underflow");
vm->sp--;
break;
case SPL_SWAP: {
if (vm->sp < 2)
VM_ERROR("SWAP: stack underflow");
spl_val_t _t = vm->stacks.data[vm->sp - 1];
vm->stacks.data[vm->sp - 1] = vm->stacks.data[vm->sp - 2];
vm->stacks.data[vm->sp - 2] = _t;
break;
}
case SPL_PICK: {
isize _idx = ins->imm;
if ((usize)_idx >= vm->sp)
VM_ERROR("PICK: index out of range");
PUSH(vm->stacks.data[vm->sp - 1 - _idx]);
break;
}
case SPL_ROT: {
if (vm->sp < 3)
VM_ERROR("ROT: stack underflow");
spl_val_t _a = vm->stacks.data[vm->sp - 3];
spl_val_t _b = vm->stacks.data[vm->sp - 2];
spl_val_t _c = vm->stacks.data[vm->sp - 1];
vm->stacks.data[vm->sp - 3] = _b;
vm->stacks.data[vm->sp - 2] = _c;
vm->stacks.data[vm->sp - 1] = _a;
break;
}
/* ========== Arithmetic ========== */
case SPL_ADD:
ARITH_BINOP(+);
break;
case SPL_SUB:
ARITH_BINOP(-);
break;
case SPL_MUL:
ARITH_BINOP(*);
break;
case SPL_DIV_S:
DIV_REM_S(/);
break;
case SPL_DIV_U:
DIV_REM_U(/);
break;
case SPL_REM_S:
DIV_REM_S(%);
break;
case SPL_REM_U:
DIV_REM_U(%);
break;
case SPL_NEG: {
spl_val_t _a = POP();
if (spl_is_float((spl_type_t)ins->type)) {
double _d;
if (ins->type == SPL_F32) {
float _f;
memcpy(&_f, &_a, 4);
_f = -_f;
memcpy(&_a, &_f, 4);
} else {
memcpy(&_d, &_a, 8);
_d = -_d;
memcpy(&_a, &_d, 8);
}
PUSH(_a);
} else {
PUSH(-(int64_t)_a);
}
break;
}
/* ========== Bitwise ========== */
case SPL_AND: {
spl_val_t _b = POP(), _a = POP();
PUSH(_a & _b);
break;
}
case SPL_OR: {
spl_val_t _b = POP(), _a = POP();
PUSH(_a | _b);
break;
}
case SPL_XOR: {
spl_val_t _b = POP(), _a = POP();
PUSH(_a ^ _b);
break;
}
case SPL_NOT: {
PUSH(~POP());
break;
}
case SPL_SHL: {
int _s = (int)(POP() & 63);
PUSH(POP() << _s);
break;
}
case SPL_SHR_U: {
int _s = (int)(POP() & 63);
PUSH(POP() >> _s);
break;
}
case SPL_SHR_S: {
int _s = (int)(POP() & 63);
PUSH(POP() >> _s);
break;
}
/* ========== Comparison ========== */
case SPL_EQ:
CMP_ALL(==);
break;
case SPL_NE:
CMP_ALL(!=);
break;
case SPL_SLT:
CMP_S(<);
break;
case SPL_SLE:
CMP_S(<=);
break;
case SPL_SGT:
CMP_S(>);
break;
case SPL_SGE:
CMP_S(>=);
break;
case SPL_ULT:
CMP_U(<);
break;
case SPL_ULE:
CMP_U(<=);
break;
case SPL_UGT:
CMP_U(>);
break;
case SPL_UGE:
CMP_U(>=);
break;
/* ========== Control Flow (relative offset) ========== */
case SPL_JMP:
vm->ip = vm->ip + ins->imm;
break;
case SPL_BZ: {
if (POP() == 0)
vm->ip = vm->ip + ins->imm;
break;
}
case SPL_BNZ: {
if (POP() != 0)
vm->ip = vm->ip + ins->imm;
break;
}
case SPL_CALL: {
spl_val_t _nargs = ins->imm;
spl_val_t _addr = POP();
spl_vm_call(vm, _addr, _nargs);
break;
}
case SPL_CALLI: {
spl_val_t _addr = POP();
spl_val_t _nargs = POP();
spl_vm_call(vm, _addr, _nargs);
break;
}
case SPL_RET: {
spl_val_t _retval = 0;
if (ins->type != SPL_VOID)
_retval = POP();
if (vm->cp <= 0)
VM_ERROR("RET: call stack underflow");
vm->cp--;
intptr_t _saved_sp = vm->frames.data[vm->cp].saved_sp;
intptr_t _saved_fp = vm->frames.data[vm->cp].saved_fp;
intptr_t _saved_ip = vm->frames.data[vm->cp].saved_ip;
/* entry return -> halt */
if (_saved_ip < 0) {
vm->exit_code = (int)_retval;
return 1;
}
vm->sp = _saved_sp;
vm->fp = _saved_fp;
vm->ip = _saved_ip;
if (ins->type != SPL_VOID)
PUSH(_retval);
// spl_vm_stackdump(vm, vm->sp);
break;
}
case SPL_HALT:
return 1;
/* ========== Stack / Frame Local Memory ========== */
case SPL_ALLOC: {
spl_val_t _k = ins->imm;
uintptr_t _new_sp = vm->sp + _k;
if (_new_sp > vm->config.max_stack_depth)
VM_ERROR("ALLOC: stack overflow");
for (uintptr_t _i = vm->sp; _i < _new_sp; _i++)
vm->stacks.data[_i] = 0;
vm->sp = _new_sp;
break;
}
case SPL_LADDR:
PUSH((spl_val_t)((char *)(vm->stacks.data + vm->fp) + ins->imm));
break;
case SPL_GADDR: {
spl_val_t _idx = ins->imm;
if (_idx >= vec_size(prog->gdata))
VM_ERROR("GADDR: global data index out of range");
PUSH((spl_val_t)(uintptr_t)vec_at(prog->gdata, _idx).data);
break;
}
/* ========== Indirect Memory (load/store with types) ========== */
case SPL_LOAD: {
void *_addr = (void *)POP();
spl_val_t _v = 0;
memcpy(&_v, _addr, spl_type_size(ins->type));
PUSH(_v);
break;
}
case SPL_STORE: {
spl_val_t _v = POP();
void *_addr = (void *)POP();
memcpy(_addr, &_v, spl_type_size(ins->type));
break;
}
/* ========== Type Conversion ========== */
case SPL_TRUNC: {
spl_val_t _v = POP();
intptr_t _bits = ins->imm;
if (_bits < 1 || _bits > 64)
VM_ERROR("TRUNC: bad bit-width");
if (_bits < 64) {
spl_val_t _mask = ((spl_val_t)1 << _bits) - 1;
_v &= _mask;
}
PUSH(_v);
break;
}
case SPL_SEXT: {
spl_val_t _v = POP();
intptr_t _bits = ins->imm;
if (_bits < 1 || _bits > 64)
VM_ERROR("SEXT: bad bit-width");
if (_bits < 64) {
spl_val_t _sign = (spl_val_t)1 << (_bits - 1);
spl_val_t _mask = ((spl_val_t)1 << _bits) - 1;
_v &= _mask;
if (_v & _sign)
_v |= ~_mask;
}
PUSH(_v);
break;
}
case SPL_ZEXT: {
spl_val_t _v = POP();
intptr_t _bits = ins->imm;
if (_bits < 1 || _bits > 64)
VM_ERROR("ZEXT: bad bit-width");
if (_bits < 64)
_v &= ((spl_val_t)1 << _bits) - 1;
PUSH(_v);
break;
}
/* ========== Native Interface ========== */
case SPL_NCALL: {
intptr_t _nargs = ins->imm;
spl_val_t _nat_idx = POP();
spl_native_t *_nat;
if (_nat_idx >= vec_size(prog->natives))
VM_ERROR("NCALL: native index out of range");
_nat = &vec_at(prog->natives, _nat_idx);
if (!_nat->impl_fn) {
snprintf(vm->error_msg, sizeof(vm->error_msg),
"NCALL: NULL native function pointer expect %s", _nat->name);
VM_ERROR(vm->error_msg);
}
spl_val_t *_arg_base = vm->stacks.data + vm->sp - _nargs;
// spl_vm_stackdump(vm, vm->sp);
// printf("addr %p nargs %zd sp %zd stack %p arg_base %p\n", _nat->impl_fn, _nargs, vm->sp,
// vm->stacks.data, _arg_base);
spl_val_t _result = _nat->impl_fn(_nargs, _arg_base);
vm->sp -= _nargs;
PUSH(_result);
break;
}
case SPL_NLIB: {
spl_val_t _si = ins->imm;
const char *_lib;
if (_si >= vec_size(prog->strtab) || !vec_at(prog->strtab, _si))
VM_ERROR("NLIB: invalid string index");
_lib = vec_at(prog->strtab, _si);
void *_handle = SPL_DLOPEN(_lib);
if (!_handle) {
fprintf(stderr, "vm: NLIB: dlopen(%s) failed\n", _lib);
PUSH(0);
} else {
PUSH(_handle);
}
break;
}
/* ========== Debug ========== */
case SPL_DBG: {
fprintf(stderr, "---DGB: current ip %zu---\n", vm->ip);
spl_vm_dump_instr(vm, vm->ip - 6);
spl_vm_dump_instr(vm, vm->ip - 5);
spl_vm_dump_instr(vm, vm->ip - 4);
spl_vm_dump_instr(vm, vm->ip - 3);
spl_vm_dump_instr(vm, vm->ip - 2);
spl_vm_dump_instr(vm, vm->ip - 1);
spl_vm_dump_instr(vm, vm->ip);
spl_vm_dump_instr(vm, vm->ip + 1);
spl_vm_backtrace(vm, vm->fp);
spl_vm_stackdump(vm, vm->sp);
fprintf(stderr, "---DGB END ---\n");
break;
}
case SPL_BK: {
return 0; /* breakpoint: pause execution */
}
default:
fprintf(stderr, "vm: unknown opcode %d at ip=%zd\n", ins->opcode, vm->ip - 1);
vm->exit_code = 1;
return -1;
}
/* canary check in debug mode (canary is at fp-1, invisible to compiled code) */
if (vm->debug && vm->fp > 0) {
if (STACK_CANARY(vm) != SPL_STACK_CANARY) {
snprintf(vm->error_msg, sizeof(vm->error_msg),
"STACK CANARY CORRUPTED at ip=%zd, fp=%zd\n", vm->ip - 1, vm->fp);
VM_ERROR(vm->error_msg);
}
}
return 0;
}
int spl_vm_run_until(spl_vm_t *vm, size_t step) {
size_t _count = 0;
int _ret;
if (!vm)
return -1;
while (1) {
if (step > 0 && _count >= step)
return 0;
_ret = spl_vm_run_once(vm);
if (_ret != 0)
return _ret;
_count++;
}
}
static const char *func_name_by_ip(spl_prog_t *prog, spl_val_t ip) {
vec_for(prog->funcs, i) {
spl_func_t *f = &vec_at(prog->funcs, i);
if (ip >= f->address && ip < (f->address + f->ninsns))
return f->name;
}
return "?";
}
void spl_vm_dump_instr(spl_vm_t *vm, spl_val_t ip) {
if (!vm || !vm->prog)
return;
if (ip >= vec_size(vm->prog->insns))
return;
spl_ins_t *ins = &vec_at(vm->prog->insns, ip);
fprintf(stderr, " instr at ip=%zd: op=%s type=%s imm=%zu\n", ip, spl_opcode_name(ins->opcode),
spl_type_name(ins->type), ins->imm);
}
void spl_vm_stackdump(spl_vm_t *vm, spl_val_t sp) {
if (!vm)
return;
fprintf(stderr, " stack (sp=%zd, fp=%zd):\n", sp, vm->fp);
spl_val_t start = sp > 16 ? sp - 16 : 0;
for (spl_val_t i = start; i < sp; i++) {
fprintf(stderr, " [%3zd] = 0x%016zx (%zd)\n", i, vm->stacks.data[i], vm->stacks.data[i]);
}
}
int spl_vm_backtrace(spl_vm_t *vm, spl_val_t fp) {
if (!vm || !vm->prog)
return -1;
(void)fp;
fprintf(stderr, "=== backtrace ===\n");
for (isize i = vm->cp - 1; i >= 0; i--) {
spl_val_t _saved_ip = vm->frames.data[i].saved_ip;
spl_val_t _saved_fp = vm->frames.data[i].saved_fp;
const char *_fn = func_name_by_ip(vm->prog, _saved_ip - 1);
fprintf(stderr, " [%3zd] %s (fp=%zd, ip=%zd, args=%zd)\n", i, _fn, _saved_fp, _saved_ip,
vm->frames.data[i].nargs);
}
const char *_cur = func_name_by_ip(vm->prog, vm->ip);
fprintf(stderr, " => %s (fp=%zd, ip=%zd, sp=%zd)\n", _cur, vm->fp, vm->ip, vm->sp);
return 0;
}