/* 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 #include #include #if defined(_WIN32) || defined(_WIN64) #define WIN32_LEAN_AND_MEAN #include #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 #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 *) / 8; 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: \ _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: \ _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: \ _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: \ _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: \ _r = (int64_t)_a OP(int64_t) _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: \ _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); #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; } 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"); 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; /* Transparent canary at fp-1 — invisible to normal LADDR/ST */ if (vm->fp > 0) vm->stacks.data[vm->fp - 1] = SPL_STACK_CANARY; 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 < 0 || 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 (_idx >= vm->sp) VM_ERROR("PICK: index out of range"); PUSH(vm->stacks.data[vm->sp - 1 - _idx]); 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_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 = vm->fp; vm->fp = _saved_fp; vm->ip = _saved_ip; if (ins->type != SPL_VOID) PUSH(_retval); 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(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_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: { spl_vm_backtrace(vm, vm->fp); 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) { spl_val_t val = vm->stacks.data[vm->fp - 1]; if (val != 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, " [%zd] = 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, " [%zd] %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; }