/* 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 #include #include /* ================================================================= * 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; } } } }