Files
spl/stage0/spl_vm.c

1227 lines
57 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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_mcode.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_vm_kind_t t) { return t == SPL_F32 || t == SPL_F64; }
static int spl_is_signed(spl_vm_kind_t t) {
switch (t) {
case SPL_I8:
case SPL_I16:
case SPL_I32:
case SPL_I64:
case SPL_ISIZE:
return 1;
default:
return 0;
}
}
static int spl_type_size(spl_vm_kind_t t) {
switch (t) {
case SPL_VOID:
return 0;
case SPL_BOOL:
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)
#define CHECK_ADDR(addr, label) \
do { \
if (vm->debug_addr && (uintptr_t)(addr) < 0x1000) { \
fprintf(stderr, "vm: %s at ip=%zd: LOW ADDR=%p sp=%zd fp=%zd\n", label, vm->ip - 1, \
(void *)(uintptr_t)(addr), vm->sp, vm->fp); \
spl_vm_stackdump(vm, vm->sp); \
spl_vm_backtrace(vm, vm->fp); \
vm->exit_code = 1; \
return -1; \
} \
} while (0)
/* ================================================================
* Stack push/pop (stacks.data is pre-allocated in init)
* ================================================================ */
#define PUSH(v) \
do { \
spl_vm_val_t _pv = (spl_vm_val_t)(v); \
if (vm->sp >= vm->config.max_stack_depth) \
VM_ERROR("stack overflow"); \
vm->stacks.data[(vm->sp)++] = _pv; \
} 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_vm_val_t _b = POP(), _a = POP(); \
spl_vm_val_t _r = 0; \
if (spl_is_float((spl_vm_kind_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_vm_val_t)((int8_t)_a OP(int8_t) _b); \
break; \
case SPL_U8: \
_r = (spl_vm_val_t)((uint8_t)_a OP(uint8_t) _b); \
break; \
case SPL_I16: \
_r = (spl_vm_val_t)((int16_t)_a OP(int16_t) _b); \
break; \
case SPL_U16: \
_r = (spl_vm_val_t)((uint16_t)_a OP(uint16_t) _b); \
break; \
case SPL_I32: \
_r = (spl_vm_val_t)((int32_t)_a OP(int32_t) _b); \
break; \
case SPL_U32: \
_r = (spl_vm_val_t)((uint32_t)_a OP(uint32_t) _b); \
break; \
case SPL_I64: \
_r = (spl_vm_val_t)((int64_t)_a OP(int64_t) _b); \
break; \
case SPL_U64: \
case SPL_PTR: \
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_vm_val_t _b = POP(), _a = POP(); \
if (_b == 0) \
VM_ERROR("division by zero"); \
spl_vm_val_t _r = 0; \
if (spl_is_float((spl_vm_kind_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_vm_val_t)((int8_t)_a OP(int8_t) _b); \
break; \
case SPL_U8: \
_r = (spl_vm_val_t)((int8_t)_a OP(int8_t) _b); \
break; \
case SPL_I16: \
_r = (spl_vm_val_t)((int16_t)_a OP(int16_t) _b); \
break; \
case SPL_U16: \
_r = (spl_vm_val_t)((int16_t)_a OP(int16_t) _b); \
break; \
case SPL_I32: \
_r = (spl_vm_val_t)((int32_t)_a OP(int32_t) _b); \
break; \
case SPL_U32: \
_r = (spl_vm_val_t)((int32_t)_a OP(int32_t) _b); \
break; \
case SPL_I64: \
_r = (spl_vm_val_t)((int64_t)_a OP(int64_t) _b); \
break; \
case SPL_U64: \
case SPL_PTR: \
case SPL_USIZE: \
case SPL_ISIZE: \
_r = (spl_vm_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_vm_val_t _b = POP(), _a = POP(); \
if (_b == 0) \
VM_ERROR("division by zero"); \
spl_vm_val_t _r = 0; \
switch (ins->type) { \
case SPL_I8: \
_r = (spl_vm_val_t)((uint8_t)_a OP(uint8_t) _b); \
break; \
case SPL_U8: \
_r = (spl_vm_val_t)((uint8_t)_a OP(uint8_t) _b); \
break; \
case SPL_I16: \
_r = (spl_vm_val_t)((uint16_t)_a OP(uint16_t) _b); \
break; \
case SPL_U16: \
_r = (spl_vm_val_t)((uint16_t)_a OP(uint16_t) _b); \
break; \
case SPL_I32: \
_r = (spl_vm_val_t)((uint32_t)_a OP(uint32_t) _b); \
break; \
case SPL_U32: \
_r = (spl_vm_val_t)((uint32_t)_a OP(uint32_t) _b); \
break; \
case SPL_I64: \
_r = (spl_vm_val_t)((uint64_t)_a OP(uint64_t) _b); \
break; \
case SPL_U64: \
case SPL_PTR: \
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_vm_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: \
case SPL_PTR: \
_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_vm_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_PTR: \
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_vm_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_PTR: \
case SPL_USIZE: \
case SPL_ISIZE: \
_r = _a OP _b; \
break; \
default: \
_r = 0; \
break; \
} \
PUSH(_r); \
} while (0)
#ifdef _WIN32
// 辅助函数:将异常代码转为可读字符串
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;
}
#endif
/* ================================================================
* spl_vm_init / spl_vm_drop
* ================================================================ */
void spl_vm_init_ex(spl_vm_t *vm, int stack_size, int call_depth) {
#ifdef _WIN32
SetErrorMode(SEM_FAILCRITICALERRORS);
SetUnhandledExceptionFilter(UnhandledExceptionFilterImpl);
SetConsoleOutputCP(CP_UTF8);
SetConsoleCP(CP_UTF8);
#endif
if (!vm)
return;
vm->config.max_stack_depth = stack_size > 0 ? stack_size : 1024 * 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);
vec_init(vm->breakpoints);
vec_init(vm->fn_breakpoints);
vm->sp = vm->fp = vm->ip = vm->cp = vm->gp = 0;
vm->prog = NULL;
vm->trace = 0;
vm->debug = 1;
vm->debug_addr = 0;
vm->skip_bp = 0;
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);
for (usize i = 0; i < vec_size(vm->fn_breakpoints); i++)
free(vm->fn_breakpoints.data[i]);
vec_free(vm->fn_breakpoints);
vec_free(vm->breakpoints);
}
void spl_vm_add_breakpoint(spl_vm_t *vm, usize ip) {
if (!vm)
return;
for (usize i = 0; i < vec_size(vm->breakpoints); i++)
if (vm->breakpoints.data[i] == ip)
return;
vec_push(vm->breakpoints, ip);
}
void spl_vm_add_breakpoint_fn(spl_vm_t *vm, const char *name) {
if (!vm || !name)
return;
for (usize i = 0; i < vec_size(vm->fn_breakpoints); i++)
if (strcmp(vm->fn_breakpoints.data[i], name) == 0)
return;
vec_push(vm->fn_breakpoints, strdup(name));
}
void spl_vm_clear_breakpoints(spl_vm_t *vm) {
if (!vm)
return;
vm->breakpoints.size = 0;
for (usize i = 0; i < vec_size(vm->fn_breakpoints); i++)
free(vm->fn_breakpoints.data[i]);
vm->fn_breakpoints.size = 0;
}
void spl_vm_skip_breakpoint(spl_vm_t *vm) {
if (vm)
vm->skip_bp = 1;
}
/* 前向声明(定义在文件尾部 dump 区域) */
static const char *func_name_by_ip(spl_prog_t *prog, spl_vm_val_t ip);
/* 函数名断点命中addr 所在函数名是否在断点表 */
static int fn_breakpoint_hit(spl_vm_t *vm, spl_vm_val_t addr) {
if (!vm || !vm->prog || !vec_size(vm->fn_breakpoints))
return 0;
const char *fn = func_name_by_ip(vm->prog, addr);
if (!fn)
return 0;
for (usize i = 0; i < vec_size(vm->fn_breakpoints); i++)
if (strcmp(vm->fn_breakpoints.data[i], fn) == 0)
return 1;
return 0;
}
/* ip 断点命中vm->ip即将执行的指令在断点表 */
static int ip_breakpoint_hit(spl_vm_t *vm) {
if (!vm || !vec_size(vm->breakpoints))
return 0;
for (usize i = 0; i < vec_size(vm->breakpoints); i++)
if (vm->breakpoints.data[i] == (usize)vm->ip)
return 1;
return 0;
}
/* ================================================================
* 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;
vm->debug_addr = enabled ? 1 : 0;
}
#define STACK_CANARY(vm) (vm)->stacks.data[(vm)->fp - 1]
static inline int spl_vm_call(spl_vm_t *vm, spl_vm_val_t addr, spl_vm_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_vm_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_vm_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;
}
/* 断点:即将执行的指令命中 ip 断点 -> 暂停(返回 2
* skip_bp 置位时执行当前指令continue 越过当前断点)。 */
if (ip_breakpoint_hit(vm)) {
if (vm->skip_bp) {
vm->skip_bp = 0;
} else {
return 2;
}
}
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_vm_opcode_name(ins->opcode), spl_vm_type_kind_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_vm_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_vm_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_vm_val_t _a = vm->stacks.data[vm->sp - 3];
spl_vm_val_t _b = vm->stacks.data[vm->sp - 2];
spl_vm_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_vm_val_t _a = POP();
if (spl_is_float((spl_vm_kind_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_vm_val_t _b = POP(), _a = POP();
PUSH(_a & _b);
break;
}
case SPL_OR: {
spl_vm_val_t _b = POP(), _a = POP();
PUSH(_a | _b);
break;
}
case SPL_XOR: {
spl_vm_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_vm_val_t _nargs = ins->imm;
spl_vm_val_t _addr = POP();
int _hit = fn_breakpoint_hit(vm, _addr);
spl_vm_call(vm, _addr, _nargs);
if (_hit)
return 2; /* 已进入函数,暂停在函数入口 */
break;
}
case SPL_CALLI: {
spl_vm_val_t _addr = POP();
spl_vm_val_t _nargs = POP();
int _hit = fn_breakpoint_hit(vm, _addr);
spl_vm_call(vm, _addr, _nargs);
if (_hit)
return 2;
break;
}
case SPL_RET: {
spl_vm_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:
if (ins->imm)
vm->exit_code = (int)ins->imm;
return 1;
/* ========== Stack / Frame Local Memory ========== */
case SPL_ALLOC: {
spl_vm_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_vm_val_t)((char *)(vm->stacks.data + vm->fp) + ins->imm));
break;
case SPL_GADDR: {
spl_vm_val_t _idx = ins->imm;
if (_idx >= vec_size(prog->gdata))
VM_ERROR("GADDR: global data index out of range");
PUSH((spl_vm_val_t)(uintptr_t)vec_at(prog->gdata, _idx).data);
break;
}
/* ========== Indirect Memory (load/store with types) ========== */
case SPL_LOAD: {
void *_addr = (void *)POP();
CHECK_ADDR(_addr, "LOAD");
spl_vm_val_t _v = 0;
usize _sz = spl_type_size(ins->type);
memcpy(&_v, _addr, _sz);
/* Sign-extend signed integer types smaller than 64 bits */
if (_sz > 0 && _sz < sizeof(spl_vm_val_t) && spl_is_signed(ins->type)) {
usize _shift = (sizeof(spl_vm_val_t) - _sz) * 8;
_v = (spl_vm_val_t)(((isize)(_v << _shift)) >> _shift);
}
PUSH(_v);
break;
}
case SPL_STORE: {
spl_vm_val_t _v = POP();
void *_addr = (void *)POP();
CHECK_ADDR(_addr, "STORE");
memcpy(_addr, &_v, spl_type_size(ins->type));
break;
}
/* ========== Type Conversion ========== */
case SPL_TRUNC: {
spl_vm_val_t _v = POP();
intptr_t _bits = ins->imm;
if (_bits < 1 || _bits > 64)
VM_ERROR("TRUNC: bad bit-width");
if (_bits < 64) {
spl_vm_val_t _mask = ((spl_vm_val_t)1 << _bits) - 1;
_v &= _mask;
}
PUSH(_v);
break;
}
case SPL_SEXT: {
spl_vm_val_t _v = POP();
intptr_t _bits = ins->imm;
if (_bits < 1 || _bits > 64)
VM_ERROR("SEXT: bad bit-width");
if (_bits < 64) {
spl_vm_val_t _sign = (spl_vm_val_t)1 << (_bits - 1);
spl_vm_val_t _mask = ((spl_vm_val_t)1 << _bits) - 1;
_v &= _mask;
if (_v & _sign)
_v |= ~_mask;
}
PUSH(_v);
break;
}
case SPL_ZEXT: {
spl_vm_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_vm_val_t)1 << _bits) - 1;
PUSH(_v);
break;
}
/* ========== Native Interface ========== */
case SPL_NCALL: {
intptr_t _nargs = ins->imm;
spl_vm_val_t _nat_idx = POP();
spl_vm_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_vm_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_vm_val_t _result = _nat->impl_fn(_nargs, _arg_base);
vm->sp -= _nargs;
PUSH(_result);
break;
}
case SPL_NLIB: {
spl_vm_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 2; /* 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_vm_val_t ip) {
vec_for(prog->funcs, i) {
spl_vm_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_vm_val_t ip) {
if (!vm || !vm->prog)
return;
if (ip >= vec_size(vm->prog->insns))
return;
spl_vm_ins_t *ins = &vec_at(vm->prog->insns, ip);
fprintf(stderr, " instr at ip=%zd: op=%s type=%s imm=%zu\n", ip,
spl_vm_opcode_name(ins->opcode), spl_vm_type_kind_name(ins->type), ins->imm);
}
void spl_vm_stackdump(spl_vm_t *vm, spl_vm_val_t sp) {
if (!vm)
return;
fprintf(stderr, "stack dump (sp=%zd, fp=%zd):\n", sp, vm->fp);
spl_vm_val_t start = sp > 32 ? sp - 32 : 0;
for (spl_vm_val_t i = start; i <= sp; i++) {
fprintf(stderr, " [%3zd] = [addr 0x%p] 0x%016zx (%zd)\n", i, &vm->stacks.data[i],
vm->stacks.data[i], vm->stacks.data[i]);
}
}
int spl_vm_backtrace(spl_vm_t *vm, spl_vm_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_vm_val_t _saved_ip = vm->frames.data[i].saved_ip;
spl_vm_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;
}