1499 lines
67 KiB
C
1499 lines
67 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 "include/core_map.h"
|
||
#include "spl_mcode.h"
|
||
|
||
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <time.h>
|
||
|
||
static void spl_vm_profile_free(spl_vm_t *vm);
|
||
|
||
#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 *);
|
||
default:
|
||
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; \
|
||
} \
|
||
case SPL_BOOL: \
|
||
_r = (uint8_t)_a OP(uint8_t) _b; \
|
||
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 * 64;
|
||
vm->config.max_call_depth = call_depth > 0 ? call_depth : 65536;
|
||
|
||
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->steps = 0;
|
||
vm->ncalls = 0;
|
||
vm->profile = NULL;
|
||
vm->profile_interval = 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);
|
||
spl_vm_profile_free(vm);
|
||
}
|
||
|
||
// FIXME
|
||
int spl_vm_addr_valid(uintptr_t p, usize size) {
|
||
if (p == 0)
|
||
return 0;
|
||
if (p < 0x1000)
|
||
return 0;
|
||
uintptr_t max =
|
||
(sizeof(void *) == 8) ? (uintptr_t)0x00007FFFFFFFFFFFULL : (uintptr_t)0x7FFFFFFF;
|
||
if (p > max)
|
||
return 0;
|
||
if (size > 0) {
|
||
if (p + size < p)
|
||
return 0;
|
||
if (p + size > max)
|
||
return 0;
|
||
}
|
||
return 1;
|
||
}
|
||
|
||
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;
|
||
char *cpname = malloc(strlen(name) + 1);
|
||
Assert(cpname != NULL);
|
||
strcpy(cpname, name);
|
||
vec_push(vm->fn_breakpoints, cpname);
|
||
}
|
||
|
||
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);
|
||
static void spl_vm_profile_free(spl_vm_t *vm);
|
||
|
||
/* 函数名断点命中: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("call depth exceeded");
|
||
|
||
uintptr_t saved_sp;
|
||
if (vm->debug) {
|
||
/* debug:在 fp-1 留金丝雀位,需要把实参右移一格。 */
|
||
PUSH(SPL_STACK_CANARY);
|
||
for (usize i = vm->sp; i >= vm->sp - nargs; --i)
|
||
vm->stacks.data[i] = vm->stacks.data[i - 1];
|
||
saved_sp = vm->sp - nargs - 1;
|
||
} else {
|
||
/* 非 debug:不做金丝雀搬移;实参在 sp-nargs..sp-1,fp 指向 arg0。 */
|
||
saved_sp = vm->sp - nargs;
|
||
}
|
||
|
||
spl_callframe_t *fr = &vm->frames.data[vm->cp];
|
||
fr->saved_sp = saved_sp;
|
||
fr->saved_fp = vm->fp;
|
||
fr->saved_ip = vm->ip;
|
||
fr->nargs = nargs;
|
||
vm->cp++;
|
||
vm->ip = addr;
|
||
vm->fp = vm->sp - nargs;
|
||
if (vm->debug && vm->fp > 0)
|
||
STACK_CANARY(vm) = 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_vm_func_t *fn = NULL;
|
||
const char *entry_point = "_start";
|
||
fn = spl_prog_get_func(vm->prog, entry_point);
|
||
if (fn == NULL) {
|
||
entry_point = entry ? entry : "main";
|
||
fn = spl_prog_get_func(vm->prog, entry_point);
|
||
}
|
||
if (!fn) {
|
||
fprintf(stderr, "vm: entry point '%s' not found\n", entry_point);
|
||
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
|
||
* ================================================================ */
|
||
|
||
/* ================= native 采样 profiler =================
|
||
* 每隔 interval 条指令采样一次当前调用栈(root;...;leaf),累积计数。
|
||
* 输出折叠格式(folded)供火焰图工具使用,以及每函数 self 采样表。 */
|
||
|
||
static int func_index_by_ip(spl_prog_t *prog, spl_vm_val_t ip) {
|
||
if (!prog)
|
||
return -1;
|
||
isize lo = 0, hi = (isize)vec_size(prog->funcs) - 1;
|
||
while (lo <= hi) {
|
||
isize mid = (lo + hi) / 2;
|
||
spl_vm_func_t *f = &vec_at(prog->funcs, (usize)mid);
|
||
if (ip < f->address)
|
||
hi = mid - 1;
|
||
else if (ip >= f->address + f->ninsns)
|
||
lo = mid + 1;
|
||
else
|
||
return (int)mid;
|
||
}
|
||
for (usize i = 0; i < vec_size(prog->funcs); i++) {
|
||
spl_vm_func_t *f = &vec_at(prog->funcs, i);
|
||
if (ip >= f->address && ip < f->address + f->ninsns)
|
||
return (int)i;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
static uint64_t prof_now_ns(void) {
|
||
struct timespec ts;
|
||
if (timespec_get(&ts, TIME_UTC) != TIME_UTC)
|
||
return 0;
|
||
return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
|
||
}
|
||
|
||
static void spl_vm_sample(spl_vm_t *vm) {
|
||
spl_prof_t *p = (spl_prof_t *)vm->profile;
|
||
spl_prog_t *prog = vm->prog;
|
||
if (!p || !prog)
|
||
return;
|
||
|
||
char buf[8192];
|
||
usize len = 0;
|
||
for (usize i = 0; i < vm->cp; i++) {
|
||
intptr_t s = (intptr_t)vm->frames.data[i].saved_ip;
|
||
if (s <= 0)
|
||
continue;
|
||
int fi = func_index_by_ip(prog, (spl_vm_val_t)(s - 1));
|
||
const char *n = fi >= 0 ? vec_at(prog->funcs, (usize)fi).name : "?";
|
||
size_t l = strlen(n);
|
||
if (len + l + 2 < sizeof(buf)) {
|
||
if (len)
|
||
buf[len++] = ';';
|
||
memcpy(buf + len, n, l);
|
||
len += l;
|
||
}
|
||
}
|
||
int leaf = func_index_by_ip(prog, vm->ip > 0 ? vm->ip - 1 : 0);
|
||
|
||
/* time-slice:把两次采样之间的时间记到当前活跃函数(leaf)。 */
|
||
uint64_t now = prof_now_ns();
|
||
if (p->last_ns != 0 && now > p->last_ns) {
|
||
uint64_t d = now - p->last_ns;
|
||
p->total_ns += d;
|
||
if (leaf >= 0 && (usize)leaf < vec_size(p->self_ns))
|
||
p->self_ns.data[leaf] += d;
|
||
}
|
||
p->last_ns = now;
|
||
|
||
const char *ln = leaf >= 0 ? vec_at(prog->funcs, (usize)leaf).name : "?";
|
||
size_t ll = strlen(ln);
|
||
if (len + ll + 1 < sizeof(buf)) {
|
||
if (len)
|
||
buf[len++] = ';';
|
||
memcpy(buf + len, ln, ll);
|
||
len += ll;
|
||
}
|
||
buf[len] = 0;
|
||
|
||
usize old = 0;
|
||
if (map_get(p->stacks, buf, &old)) {
|
||
map_put(p->stacks, buf, old + 1);
|
||
} else {
|
||
map_put(p->stacks, strdup(buf), (usize)1);
|
||
}
|
||
if (leaf >= 0 && (usize)leaf < vec_size(p->self_samples))
|
||
p->self_samples.data[leaf]++;
|
||
p->samples++;
|
||
}
|
||
|
||
void spl_vm_profile_enable(spl_vm_t *vm, usize interval) {
|
||
if (!vm)
|
||
return;
|
||
spl_prof_t *p = (spl_prof_t *)malloc(sizeof(*p));
|
||
if (!p)
|
||
return;
|
||
memset(p, 0, sizeof(*p));
|
||
map_init(p->stacks, MAP_HASH_STR, MAP_CMP_STR);
|
||
vec_init(p->self_samples);
|
||
vec_init(p->self_ns);
|
||
if (vm->prog) {
|
||
usize n = vec_size(vm->prog->funcs);
|
||
for (usize i = 0; i < n; i++) {
|
||
vec_push(p->self_samples, (usize)0);
|
||
vec_push(p->self_ns, (uint64_t)0);
|
||
}
|
||
}
|
||
vm->profile = p;
|
||
vm->profile_interval = interval ? interval : 100000;
|
||
}
|
||
|
||
static void spl_vm_profile_free(spl_vm_t *vm) {
|
||
spl_prof_t *p = (spl_prof_t *)vm->profile;
|
||
if (!p)
|
||
return;
|
||
map_for(p->stacks, i) { free((void *)p->stacks.data[i].key); }
|
||
map_free(p->stacks);
|
||
vec_free(p->self_samples);
|
||
vec_free(p->self_ns);
|
||
free(p);
|
||
vm->profile = NULL;
|
||
}
|
||
|
||
void spl_vm_profile_dump_folded(spl_vm_t *vm, const char *path) {
|
||
spl_prof_t *p = vm ? (spl_prof_t *)vm->profile : NULL;
|
||
if (!p || !path)
|
||
return;
|
||
FILE *f = fopen(path, "wb");
|
||
if (!f)
|
||
return;
|
||
map_for(p->stacks, i) { fprintf(f, "%s %zu\n", p->stacks.data[i].key, p->stacks.data[i].val); }
|
||
fclose(f);
|
||
}
|
||
|
||
void spl_vm_profile_dump_table(spl_vm_t *vm, const char *path) {
|
||
spl_prof_t *p = vm ? (spl_prof_t *)vm->profile : NULL;
|
||
if (!p || !path || !vm->prog)
|
||
return;
|
||
FILE *f = fopen(path, "wb");
|
||
if (!f)
|
||
return;
|
||
fprintf(f, "samples=%zu interval=%zu total_ns=%llu\n", p->samples, vm->profile_interval,
|
||
(unsigned long long)p->total_ns);
|
||
fprintf(f, "func\tself_samples\tpct_samples\tself_ns\tpct_ns\tns_per_step\n");
|
||
for (usize i = 0; i < vec_size(vm->prog->funcs); i++) {
|
||
usize c = i < vec_size(p->self_samples) ? p->self_samples.data[i] : 0;
|
||
if (c == 0)
|
||
continue;
|
||
uint64_t t = i < vec_size(p->self_ns) ? p->self_ns.data[i] : 0;
|
||
spl_vm_func_t *fn = &vec_at(vm->prog->funcs, i);
|
||
double ps = p->samples ? (100.0 * (double)c / (double)p->samples) : 0.0;
|
||
double pt = p->total_ns ? (100.0 * (double)t / (double)p->total_ns) : 0.0;
|
||
double nps = c ? ((double)t / (double)c) : 0.0;
|
||
fprintf(f, "%s\t%zu\t%.2f\t%llu\t%.2f\t%.1f\n", fn->name, c, ps, (unsigned long long)t, pt,
|
||
nps);
|
||
}
|
||
fclose(f);
|
||
}
|
||
|
||
/* 单步执行核心(static,便于内联进热循环)。 */
|
||
/* computed-goto 调度:每个 opcode 直接跳到对应标签;limit=SIZE_MAX 表示不限步。 */
|
||
#define DT(op) [op] = &&L_##op
|
||
|
||
#define FETCH() \
|
||
do { \
|
||
if (vm->ip >= (spl_vm_val_t)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++; \
|
||
vm->steps++; \
|
||
if (vm->profile && vm->profile_interval && (vm->steps % vm->profile_interval) == 0) \
|
||
spl_vm_sample(vm); \
|
||
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); \
|
||
} \
|
||
} while (0)
|
||
|
||
#define DISPATCH() \
|
||
do { \
|
||
if (vm->debug && vm->fp > 0 && 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); \
|
||
} \
|
||
if (limit && --limit == 0) \
|
||
return 0; \
|
||
if (ip_breakpoint_hit(vm)) { \
|
||
if (vm->skip_bp) \
|
||
vm->skip_bp = 0; \
|
||
else \
|
||
return 2; \
|
||
} \
|
||
FETCH(); \
|
||
goto *vm_dispatch[ins->opcode]; \
|
||
} while (0)
|
||
|
||
static int vm_loop(spl_vm_t *vm, size_t limit) {
|
||
const spl_vm_ins_t *ins;
|
||
spl_prog_t *prog;
|
||
|
||
if (!vm || !vm->prog)
|
||
return -1;
|
||
prog = vm->prog;
|
||
|
||
static void *vm_dispatch[SPL_OPCODE_COUNT] = {
|
||
DT(SPL_PUSH), DT(SPL_DUP), DT(SPL_DROP),
|
||
DT(SPL_SWAP), DT(SPL_PICK), DT(SPL_ROT),
|
||
DT(SPL_ADD), DT(SPL_SUB), DT(SPL_MUL),
|
||
DT(SPL_DIV_S), DT(SPL_DIV_U), DT(SPL_REM_S),
|
||
DT(SPL_REM_U), DT(SPL_NEG), DT(SPL_AND),
|
||
DT(SPL_OR), DT(SPL_XOR), DT(SPL_NOT),
|
||
DT(SPL_SHL), DT(SPL_SHR_U), DT(SPL_SHR_S),
|
||
DT(SPL_EQ), DT(SPL_NE), DT(SPL_SLT),
|
||
DT(SPL_SLE), DT(SPL_ULT), DT(SPL_ULE),
|
||
DT(SPL_SGT), DT(SPL_SGE), DT(SPL_UGT),
|
||
DT(SPL_UGE), DT(SPL_TRUNC), DT(SPL_SEXT),
|
||
DT(SPL_ZEXT), DT(SPL_JMP), DT(SPL_BZ),
|
||
DT(SPL_BNZ), DT(SPL_HALT), DT(SPL_CALL),
|
||
DT(SPL_CALLI), DT(SPL_RET), DT(SPL_ALLOC),
|
||
DT(SPL_LADDR), DT(SPL_GADDR), DT(SPL_LOAD),
|
||
DT(SPL_STORE), DT(SPL_NCALL), DT(SPL_NLIB),
|
||
DT(SPL_BK), DT(SPL_DBG), [SPL_ERROR] = &&L_default,
|
||
};
|
||
|
||
if (limit == 0)
|
||
return 0;
|
||
if (ip_breakpoint_hit(vm)) {
|
||
if (vm->skip_bp)
|
||
vm->skip_bp = 0;
|
||
else
|
||
return 2;
|
||
}
|
||
FETCH();
|
||
goto *vm_dispatch[ins->opcode];
|
||
|
||
/* ========== Stack ========== */
|
||
L_SPL_PUSH:
|
||
PUSH(ins->imm);
|
||
DISPATCH();
|
||
|
||
L_SPL_DUP:
|
||
{
|
||
if (vm->sp < 1)
|
||
VM_ERROR("DUP: stack underflow");
|
||
spl_vm_val_t _v = vm->stacks.data[vm->sp - 1];
|
||
PUSH(_v);
|
||
DISPATCH();
|
||
}
|
||
|
||
L_SPL_DROP:
|
||
if (vm->sp < 1)
|
||
VM_ERROR("DROP: stack underflow");
|
||
vm->sp--;
|
||
DISPATCH();
|
||
|
||
L_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;
|
||
DISPATCH();
|
||
}
|
||
|
||
L_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]);
|
||
DISPATCH();
|
||
}
|
||
|
||
L_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;
|
||
DISPATCH();
|
||
}
|
||
|
||
/* ========== Arithmetic ========== */
|
||
L_SPL_ADD:
|
||
ARITH_BINOP(+);
|
||
DISPATCH();
|
||
L_SPL_SUB:
|
||
ARITH_BINOP(-);
|
||
DISPATCH();
|
||
L_SPL_MUL:
|
||
ARITH_BINOP(*);
|
||
DISPATCH();
|
||
|
||
L_SPL_DIV_S:
|
||
DIV_REM_S(/);
|
||
DISPATCH();
|
||
L_SPL_DIV_U:
|
||
DIV_REM_U(/);
|
||
DISPATCH();
|
||
L_SPL_REM_S:
|
||
DIV_REM_S(%);
|
||
DISPATCH();
|
||
L_SPL_REM_U:
|
||
DIV_REM_U(%);
|
||
DISPATCH();
|
||
|
||
L_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);
|
||
}
|
||
DISPATCH();
|
||
}
|
||
|
||
/* ========== Bitwise ========== */
|
||
L_SPL_AND:
|
||
{
|
||
spl_vm_val_t _b = POP(), _a = POP();
|
||
PUSH(_a & _b);
|
||
DISPATCH();
|
||
}
|
||
L_SPL_OR:
|
||
{
|
||
spl_vm_val_t _b = POP(), _a = POP();
|
||
PUSH(_a | _b);
|
||
DISPATCH();
|
||
}
|
||
L_SPL_XOR:
|
||
{
|
||
spl_vm_val_t _b = POP(), _a = POP();
|
||
PUSH(_a ^ _b);
|
||
DISPATCH();
|
||
}
|
||
L_SPL_NOT:
|
||
{
|
||
PUSH(~POP());
|
||
DISPATCH();
|
||
}
|
||
L_SPL_SHL:
|
||
{
|
||
int _s = (int)(POP() & 63);
|
||
PUSH(POP() << _s);
|
||
DISPATCH();
|
||
}
|
||
L_SPL_SHR_U:
|
||
{
|
||
int _s = (int)(POP() & 63);
|
||
PUSH(POP() >> _s);
|
||
DISPATCH();
|
||
}
|
||
L_SPL_SHR_S:
|
||
{
|
||
int _s = (int)(POP() & 63);
|
||
PUSH(POP() >> _s);
|
||
DISPATCH();
|
||
}
|
||
|
||
/* ========== Comparison ========== */
|
||
L_SPL_EQ:
|
||
CMP_ALL(==);
|
||
DISPATCH();
|
||
L_SPL_NE:
|
||
CMP_ALL(!=);
|
||
DISPATCH();
|
||
L_SPL_SLT:
|
||
CMP_S(<);
|
||
DISPATCH();
|
||
L_SPL_SLE:
|
||
CMP_S(<=);
|
||
DISPATCH();
|
||
L_SPL_SGT:
|
||
CMP_S(>);
|
||
DISPATCH();
|
||
L_SPL_SGE:
|
||
CMP_S(>=);
|
||
DISPATCH();
|
||
L_SPL_ULT:
|
||
CMP_U(<);
|
||
DISPATCH();
|
||
L_SPL_ULE:
|
||
CMP_U(<=);
|
||
DISPATCH();
|
||
L_SPL_UGT:
|
||
CMP_U(>);
|
||
DISPATCH();
|
||
L_SPL_UGE:
|
||
CMP_U(>=);
|
||
DISPATCH();
|
||
|
||
/* ========== Control Flow (relative offset) ========== */
|
||
L_SPL_JMP:
|
||
vm->ip = vm->ip + ins->imm;
|
||
DISPATCH();
|
||
|
||
L_SPL_BZ:
|
||
{
|
||
if (POP() == 0)
|
||
vm->ip = vm->ip + ins->imm;
|
||
DISPATCH();
|
||
}
|
||
|
||
L_SPL_BNZ:
|
||
{
|
||
if (POP() != 0)
|
||
vm->ip = vm->ip + ins->imm;
|
||
DISPATCH();
|
||
}
|
||
|
||
L_SPL_CALL:
|
||
{
|
||
spl_vm_val_t _nargs = ins->imm;
|
||
spl_vm_val_t _addr = POP();
|
||
vm->ncalls++;
|
||
int _hit = fn_breakpoint_hit(vm, _addr);
|
||
spl_vm_call(vm, _addr, _nargs);
|
||
if (_hit)
|
||
return 2; /* 已进入函数,暂停在函数入口 */
|
||
DISPATCH();
|
||
}
|
||
|
||
L_SPL_CALLI:
|
||
{
|
||
spl_vm_val_t _addr = POP();
|
||
spl_vm_val_t _nargs = POP();
|
||
vm->ncalls++;
|
||
int _hit = fn_breakpoint_hit(vm, _addr);
|
||
spl_vm_call(vm, _addr, _nargs);
|
||
if (_hit)
|
||
return 2;
|
||
DISPATCH();
|
||
}
|
||
|
||
L_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);
|
||
DISPATCH();
|
||
}
|
||
|
||
L_SPL_HALT:
|
||
if (ins->imm)
|
||
vm->exit_code = (int)ins->imm;
|
||
return 1;
|
||
|
||
/* ========== Stack / Frame Local Memory ========== */
|
||
L_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;
|
||
DISPATCH();
|
||
}
|
||
|
||
L_SPL_LADDR:
|
||
PUSH((spl_vm_val_t)((char *)(vm->stacks.data + vm->fp) + ins->imm));
|
||
DISPATCH();
|
||
|
||
L_SPL_GADDR:
|
||
{
|
||
spl_vm_val_t _idx = ins->imm;
|
||
if (_idx == 0 || _idx >= vec_size(prog->gdata))
|
||
VM_ERROR("GADDR: global data index out of range");
|
||
spl_vm_gdata_t *gd = &vec_at(prog->gdata, _idx);
|
||
if (!gd->data)
|
||
VM_ERROR("GADDR: null gdata entry");
|
||
PUSH((spl_vm_val_t)(uintptr_t)gd->data);
|
||
DISPATCH();
|
||
}
|
||
|
||
/* ========== Indirect Memory (load/store with types) ========== */
|
||
L_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);
|
||
DISPATCH();
|
||
}
|
||
L_SPL_STORE:
|
||
{
|
||
spl_vm_val_t _v = POP();
|
||
void *_addr = (void *)POP();
|
||
CHECK_ADDR(_addr, "STORE");
|
||
memcpy(_addr, &_v, spl_type_size(ins->type));
|
||
DISPATCH();
|
||
}
|
||
|
||
/* ========== Type Conversion ========== */
|
||
L_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);
|
||
DISPATCH();
|
||
}
|
||
|
||
L_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);
|
||
DISPATCH();
|
||
}
|
||
|
||
L_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);
|
||
DISPATCH();
|
||
}
|
||
|
||
/* ========== Native Interface ========== */
|
||
L_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_val_t _result = _nat->impl_fn(vm, _nargs, _arg_base);
|
||
if (vm->exit_code) {
|
||
snprintf(vm->error_msg, sizeof(vm->error_msg), "NCALL %s failed (ip=%zd)",
|
||
_nat->name ? _nat->name : "?", vm->ip - 1);
|
||
fprintf(stderr, "native call: %s\n", _nat->name ? _nat->name : "?");
|
||
fprintf(stderr, " nargs=%zd arg_base=%p sp=%zu\n", _nargs, (void *)_arg_base,
|
||
vm->sp);
|
||
spl_vm_backtrace(vm, vm->fp);
|
||
spl_vm_stackdump(vm, vm->sp);
|
||
return -1;
|
||
}
|
||
vm->sp -= _nargs;
|
||
PUSH(_result);
|
||
DISPATCH();
|
||
}
|
||
|
||
L_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);
|
||
}
|
||
DISPATCH();
|
||
}
|
||
|
||
/* ========== Debug ========== */
|
||
L_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");
|
||
DISPATCH();
|
||
}
|
||
|
||
L_SPL_BK:
|
||
{
|
||
return 2; /* breakpoint: pause execution */
|
||
}
|
||
|
||
L_default:
|
||
fprintf(stderr, "vm: unknown opcode %d at ip=%zd\n", ins->opcode, vm->ip - 1);
|
||
vm->exit_code = 1;
|
||
return -1;
|
||
}
|
||
|
||
int spl_vm_run_once(spl_vm_t *vm) { return vm_loop(vm, 1); }
|
||
|
||
int spl_vm_run_until(spl_vm_t *vm, size_t step) {
|
||
if (!vm)
|
||
return -1;
|
||
return vm_loop(vm, step ? step : (size_t)-1);
|
||
}
|
||
|
||
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;
|
||
}
|