1095 lines
52 KiB
C
1095 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);
|
|
#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 < 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;
|
|
}
|
|
|
|
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(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;
|
|
}
|