Files
spl/stage0/spl_syscall.c
T

506 lines
20 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* spl_syscall.c - built-in syscall implementations + registration
*
* All syscalls validate nargs, cast spl_val_t args to the expected C types,
* execute, and return the result as spl_val_t.
*
* Simple syscalls use the SYSCALL_N macros; complex ones are written
* manually but follow the same template.
*/
#include "spl_syscall.h"
#include "include/core_map.h"
#include "include/core_vec.h"
#include "spl_mcode.h"
#include "spl_vm.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* =================================================================
* Macro templates
*
* Each SYSCALL_N macro:
* 1. Validates nargs (prints error and returns -1 on mismatch)
* 2. Casts args[n] from spl_val_t to the specified C type via
* (type)(uintptr_t) - this handles both integer and pointer types
* 3. Evaluates the expression and returns the result as spl_val_t
* ================================================================= */
#define CHECK_NARGS(fname, expected) \
do { \
if (nargs != (expected)) { \
fprintf(stderr, fname ": expected " #expected " args, got %d\n", nargs); \
return -1; \
} \
} while (0)
#define SYSCALL_0(name, ret_expr) \
static spl_vm_val_t name(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) { \
(void)_vm; \
CHECK_NARGS(#name, 0); \
(void)args; \
return (spl_vm_val_t)(uintptr_t)(ret_expr); \
}
#define SYSCALL_1(name, t1, ret_expr) \
static spl_vm_val_t name(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) { \
(void)_vm; \
CHECK_NARGS(#name, 1); \
t1 a1 = (t1)(uintptr_t)args[0]; \
return (spl_vm_val_t)(uintptr_t)(ret_expr); \
}
#define SYSCALL_2(name, t1, t2, ret_expr) \
static spl_vm_val_t name(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) { \
(void)_vm; \
CHECK_NARGS(#name, 2); \
t1 a1 = (t1)(uintptr_t)args[0]; \
t2 a2 = (t2)(uintptr_t)args[1]; \
return (spl_vm_val_t)(uintptr_t)(ret_expr); \
}
#define SYSCALL_3(name, t1, t2, t3, ret_expr) \
static spl_vm_val_t name(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) { \
(void)_vm; \
CHECK_NARGS(#name, 3); \
t1 a1 = (t1)(uintptr_t)args[0]; \
t2 a2 = (t2)(uintptr_t)args[1]; \
t3 a3 = (t3)(uintptr_t)args[2]; \
return (spl_vm_val_t)(uintptr_t)(ret_expr); \
}
#define SYSCALL_4(name, t1, t2, t3, t4, ret_expr) \
static spl_vm_val_t name(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) { \
(void)_vm; \
CHECK_NARGS(#name, 4); \
t1 a1 = (t1)(uintptr_t)args[0]; \
t2 a2 = (t2)(uintptr_t)args[1]; \
t3 a3 = (t3)(uintptr_t)args[2]; \
t4 a4 = (t4)(uintptr_t)args[3]; \
return (spl_vm_val_t)(uintptr_t)(ret_expr); \
}
/* syscall / VM 内部统一地址检查宏。
* 失败时打印 ctx + 地址 + size + ip,置 native_error,return 0。
* 要求所在函数有可用的 _vm 变量。 */
#define ADDRCHECK(vm, ptr, size, ctx) \
do { \
uintptr_t _acp = (uintptr_t)(ptr); \
usize _acs = (usize)(size); \
if (!spl_vm_addr_valid(_acp, _acs)) { \
fprintf(stderr, "%s: invalid address %p size=%zu (ip=%zu fp=%zu sp=%zu)\n", (ctx), \
(void *)_acp, _acs, (vm)->ip, (vm)->fp, (vm)->sp); \
spl_vm_backtrace((vm), (vm)->fp); \
(vm)->exit_code = 2; \
return 0; \
} \
} while (0)
/* syscall 内部:把当前栈和调用栈打印出来,供出错时定位 */
#define SYSCALL_DUMP(vm) \
do { \
fprintf(stderr, " --- syscall dump (sp=%zu fp=%zu cp=%zu ip=%zu) ---\n", (vm)->sp, \
(vm)->fp, (vm)->cp, (vm)->ip); \
spl_vm_backtrace((vm), (vm)->fp); \
spl_vm_stackdump((vm), (vm)->sp); \
} while (0)
/* syscall 内部:报告致命错误,dump 后让 VM 停在当前 ip */
#define SYSCALL_FAIL(vm, ...) \
do { \
fprintf(stderr, "syscall error: "); \
fprintf(stderr, __VA_ARGS__); \
fprintf(stderr, "\n"); \
SYSCALL_DUMP(vm); \
(vm)->exit_code = 1; \
return 0; \
} while (0)
/* =================================================================
* OS syscalls
* ================================================================= */
/* vm_exit - terminate process with the given exit code */
SYSCALL_1(vm_exit, int, (exit(a1), (spl_vm_val_t)0))
/* vm_putchar - write a single character to stdout */
SYSCALL_1(vm_putchar, int, putchar(a1))
/* vm_getchar - read a single character from stdin */
SYSCALL_0(vm_getchar, getchar())
/* vm_putint - print an integer to stdout */
SYSCALL_1(vm_putint, int, (fprintf(stdout, "%d", a1), (spl_vm_val_t)0))
/* vm_putstr - print a string to stdout (no trailing newline) */
SYSCALL_1(vm_putstr, const char *, (fputs(a1, stdout), (spl_vm_val_t)0))
/* vm_fopen - open a file, returns FILE* as spl_val_t */
SYSCALL_2(vm_fopen, const char *, const char *, (uintptr_t)fopen(a1, a2))
/* vm_fclose - close a file, returns 0 on success */
SYSCALL_1(vm_fclose, FILE *, fclose(a1))
/* vm_fread - read from file, returns number of items read */
SYSCALL_4(vm_fread, void *, size_t, size_t, FILE *, fread(a1, a2, a3, a4))
/* vm_fwrite - write to file, returns number of items written */
SYSCALL_4(vm_fwrite, const void *, size_t, size_t, FILE *, fwrite(a1, a2, a3, a4))
/* vm_fsize - get file size in bytes */
static spl_vm_val_t vm_fsize(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
(void)_vm;
CHECK_NARGS("vm_fsize", 1);
FILE *f = (FILE *)(uintptr_t)args[0];
long cur = ftell(f);
fseek(f, 0, SEEK_END);
long sz = ftell(f);
fseek(f, cur, SEEK_SET);
return (spl_vm_val_t)(uintptr_t)sz;
}
SYSCALL_0(vm_stdin, stdin)
SYSCALL_0(vm_stdout, stdout)
SYSCALL_0(vm_stderr, stderr)
/* vm_read_file - read entire file into a malloc'd, null-terminated buffer */
static spl_vm_val_t vm_read_file(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
(void)_vm;
CHECK_NARGS("vm_read_file", 1);
const char *path = (const char *)(uintptr_t)args[0];
if (path == nullptr) {
fprintf(stderr, "filepath can't be null");
return 0;
}
FILE *f = fopen(path, "rb");
if (!f) {
fprintf(stderr, "filepath %s can't be open", path);
return 1;
}
fseek(f, 0, SEEK_END);
long sz = ftell(f);
fseek(f, 0, SEEK_SET);
char *buf = (char *)malloc((size_t)sz + 1);
if (!buf) {
fclose(f);
return 0;
}
size_t nread = fread(buf, 1, (size_t)sz, f);
fclose(f);
buf[nread] = '\0';
return (spl_vm_val_t)(uintptr_t)buf;
}
static spl_vm_val_t vm_alloc(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
CHECK_NARGS("vm_alloc", 1);
size_t sz = (size_t)args[0];
if (sz == 0)
SYSCALL_FAIL(_vm, "vm_alloc: zero size");
if (sz > (size_t)1 << 32)
SYSCALL_FAIL(_vm, "vm_alloc: size too large (%zu)", sz);
void *p = malloc(sz);
if (!p)
SYSCALL_FAIL(_vm, "vm_alloc: malloc(%zu) failed", sz);
return (spl_vm_val_t)(uintptr_t)p;
}
static spl_vm_val_t vm_free(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
(void)_vm;
CHECK_NARGS("vm_free", 1);
void *p = (void *)(uintptr_t)args[0];
if (p)
free(p);
return 0;
}
static spl_vm_val_t vm_realloc(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
CHECK_NARGS("vm_realloc", 2);
void *p = (void *)(uintptr_t)args[0];
size_t sz = (size_t)args[1];
if (!p)
SYSCALL_FAIL(_vm, "vm_realloc: NULL ptr");
if (sz == 0)
SYSCALL_FAIL(_vm, "vm_realloc: zero size");
ADDRCHECK(_vm, p, 1, "vm_realloc");
void *np = realloc(p, sz);
if (!np)
SYSCALL_FAIL(_vm, "vm_realloc: realloc(%p, %zu) failed", p, sz);
return (spl_vm_val_t)(uintptr_t)np;
}
static spl_vm_val_t vm_memcpy(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
CHECK_NARGS("vm_memcpy", 3);
void *dst = (void *)(uintptr_t)args[0];
const void *src = (const void *)(uintptr_t)args[1];
size_t size = (size_t)args[2];
if (size == 0)
return (spl_vm_val_t)(uintptr_t)dst;
ADDRCHECK(_vm, dst, size, "vm_memcpy(dst)");
ADDRCHECK(_vm, src, size, "vm_memcpy(src)");
memcpy(dst, src, size);
return (spl_vm_val_t)(uintptr_t)dst;
}
static spl_vm_val_t vm_memcmp(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
CHECK_NARGS("vm_memcmp", 3);
const void *a = (const void *)(uintptr_t)args[0];
const void *b = (const void *)(uintptr_t)args[1];
size_t size = (size_t)args[2];
if (size == 0)
return 0;
ADDRCHECK(_vm, a, size, "vm_memcmp(a)");
ADDRCHECK(_vm, b, size, "vm_memcmp(b)");
return (spl_vm_val_t)memcmp(a, b, size);
}
static spl_vm_val_t vm_printf(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
(void)_vm;
if (nargs <= 0)
return 0;
/* args[0] 是 SPL 的 []u8 format 参数。VM 里 slice 作为参数传递时,
栈上压的是"指向 slice 结构体的指针",结构为 {ptr:*u8, len:u64} */
const spl_vm_val_t *fmt_slice = (const spl_vm_val_t *)args[0];
const char *fmt = (const char *)fmt_slice[0];
usize fmt_len = (usize)fmt_slice[1];
if (nargs <= 1) {
fwrite(fmt, 1, fmt_len, stdout);
return (spl_vm_val_t)nargs;
}
typedef VEC(char) string_builder_t;
string_builder_t buffer;
vec_init(buffer);
int arg_idx = 1; /* 0 是 format */
char tmp_buf[32];
for (usize i = 0; i < fmt_len; ++i) {
if (fmt[i] != '%') {
vec_push(buffer, fmt[i]);
continue;
}
if (++i >= fmt_len)
break;
if (arg_idx >= nargs)
break;
switch (fmt[i]) {
case 'd': {
memset(tmp_buf, 0, sizeof(tmp_buf));
snprintf(tmp_buf, sizeof(tmp_buf), "%zd", (isize)args[arg_idx]);
for (usize j = 0; tmp_buf[j] != '\0'; ++j)
vec_push(buffer, tmp_buf[j]);
arg_idx++;
break;
}
case 'c':
vec_push(buffer, (char)args[arg_idx]);
arg_idx++;
break;
case 's': {
const spl_vm_val_t *s = (const spl_vm_val_t *)args[arg_idx];
const char *p = (const char *)s[0];
usize l = (usize)s[1];
for (usize j = 0; j < l; ++j)
vec_push(buffer, p[j]);
arg_idx++;
break;
}
default:
vec_push(buffer, '%');
vec_push(buffer, fmt[i]);
break;
}
}
if (vec_size(buffer) > 0)
fwrite(buffer.data, 1, vec_size(buffer), stdout);
vec_free(buffer);
return (spl_vm_val_t)nargs;
}
/* =================================================================
* VM syscalls (for comptime - compile-time code execution)
*
* These let SPL code create isolated sub-VMs, load compiled .sir
* programs into them, push arguments, call functions, and run.
* ================================================================= */
/* vm_new - create a new sub-VM instance, returns spl_vm_t* */
static spl_vm_val_t vm_new(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
(void)_vm;
CHECK_NARGS("vm_new", 0);
(void)args;
spl_vm_t *vm = (spl_vm_t *)malloc(sizeof(spl_vm_t));
if (!vm)
return 0;
spl_vm_init(vm);
return (spl_vm_val_t)(uintptr_t)vm;
}
/* vm_drop - destroy a sub-VM and its loaded program */
static spl_vm_val_t vm_drop(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
(void)_vm;
CHECK_NARGS("vm_drop", 1);
spl_vm_t *vm = (spl_vm_t *)(uintptr_t)args[0];
if (!vm)
return 0;
if (vm->prog) {
spl_prog_drop(vm->prog);
free(vm->prog);
vm->prog = NULL;
}
spl_vm_drop(vm);
free(vm);
return 0;
}
/* vm_load - load a .sir file, register syscalls, return spl_prog_t* */
static spl_vm_val_t vm_load(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
(void)_vm;
CHECK_NARGS("vm_load", 2);
spl_vm_t *vm = (spl_vm_t *)(uintptr_t)args[0];
const char *path = (const char *)(uintptr_t)args[1];
if (!vm || !path)
return -1;
spl_prog_t *prog = (spl_prog_t *)malloc(sizeof(spl_prog_t));
if (!prog)
return -1;
if (spl_prog_load_from_file(path, prog) != 0) {
free(prog);
return -1;
}
spl_syscall_register(prog);
if (spl_vm_load_prog(vm, prog) != 0) {
spl_prog_drop(prog);
free(prog);
return -1;
}
return (spl_vm_val_t)(uintptr_t)prog;
}
/* vm_push - push a value onto the sub-VM's stack (for passing arguments) */
static spl_vm_val_t vm_push(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
(void)_vm;
CHECK_NARGS("vm_push", 2);
spl_vm_t *vm = (spl_vm_t *)(uintptr_t)args[0];
spl_vm_val_t val = args[1];
if (!vm)
return -1;
if (vm->sp >= vm->config.max_stack_depth) {
fprintf(stderr, "vm_push: stack overflow\n");
return -1;
}
vm->stacks.data[vm->sp++] = val;
return 0;
}
/* vm_call - call a function by name with pre-pushed arguments
*
* Args (3): vm, function_name, nargs
* The arguments should already be on the sub-VM's stack via vm_push.
*
* Mirrors the CALL instruction's frame setup:
* - Pushes a sentinel frame (saved_ip=-1) if cp==0 so RET halts properly
* - Pushes the actual call frame
* - Sets fp = sp - nargs (so arg0 = data[fp], arg1 = data[fp+1], ...)
* - Sets ip to the function address
*/
static spl_vm_val_t vm_call(spl_vm_t *_vm, int nargs, spl_vm_val_t *args) {
(void)_vm;
CHECK_NARGS("vm_call", 3);
spl_vm_t *vm = (spl_vm_t *)(uintptr_t)args[0];
const char *name = (const char *)(uintptr_t)args[1];
spl_vm_val_t call_nargs = args[2];
if (!vm || !name)
return -1;
spl_vm_func_t *func = spl_prog_get_func(vm->prog, name);
if (!func) {
fprintf(stderr, "vm_call: function '%s' not found\n", name);
return -1;
}
if (vm->cp >= vm->config.max_call_depth - 1) {
fprintf(stderr, "vm_call: call stack overflow\n");
return -1;
}
/* Sentinel frame so the entry function's RET halts cleanly */
if (vm->cp == 0) {
vm->frames.data[vm->cp].saved_fp = 0;
vm->frames.data[vm->cp].saved_ip = (uintptr_t)-1;
vm->frames.data[vm->cp].nargs = 0;
vm->cp++;
}
/* Actual call frame (same logic as the CALL instruction) */
vm->frames.data[vm->cp].saved_fp = vm->fp;
vm->frames.data[vm->cp].saved_ip = vm->ip;
vm->frames.data[vm->cp].nargs = call_nargs;
vm->cp++;
vm->fp = vm->sp - call_nargs;
vm->ip = (uintptr_t)func->address;
return 0;
}
/* vm_run - run a sub-VM to completion, returns exit_code */
SYSCALL_1(vm_run, spl_vm_t *, spl_vm_run_until(a1, 0))
/* =================================================================
* Registration
*
* Called after loading a .sir file. Matches native declarations in
* prog->natives against known syscall names and hooks up impl_fn.
* ================================================================= */
void spl_syscall_register(spl_prog_t *prog) {
static spl_vm_native_t table[] = {
/* OS operations */
{"vm_exit", 0, vm_exit},
{"vm_putchar", 0, vm_putchar},
{"vm_getchar", 0, vm_getchar},
{"vm_putint", 0, vm_putint},
{"vm_putstr", 0, vm_putstr},
{"vm_fopen", 0, vm_fopen},
{"vm_fclose", 0, vm_fclose},
{"vm_fread", 0, vm_fread},
{"vm_fwrite", 0, vm_fwrite},
{"vm_fsize", 0, vm_fsize},
{"vm_stdin", 0, vm_stdin},
{"vm_stdout", 0, vm_stdout},
{"vm_stderr", 0, vm_stderr},
{"vm_read_file", 0, vm_read_file},
{"vm_alloc", 0, vm_alloc},
{"vm_free", 0, vm_free},
{"vm_realloc", 0, vm_realloc},
{"vm_memcpy", 0, vm_memcpy},
{"vm_memcmp", 0, vm_memcmp},
{"vm_printf", 0, vm_printf},
/* VM operations (comptime) */
{"vm_new", 0, vm_new},
{"vm_drop", 0, vm_drop},
{"vm_load", 0, vm_load},
{"vm_push", 0, vm_push},
{"vm_call", 0, vm_call},
{"vm_run", 0, vm_run},
};
int n = (int)(sizeof(table) / sizeof(table[0]));
if (!prog)
return;
vec_for(prog->natives, i) {
spl_vm_native_t *nat = &vec_at(prog->natives, i);
if (!nat->name || nat->impl_fn)
continue;
for (int j = 0; j < n; j++) {
if (strcmp(nat->name, table[j].name) == 0) {
nat->impl_fn = table[j].impl_fn;
break;
}
}
}
}