From 1c53304d03414890af0da7432d1aea87e754a14a Mon Sep 17 00:00:00 2001 From: Moon Flower Date: Wed, 14 Oct 2026 08:29:46 +0330 Subject: feat: grow memory limits, add icon/actor libraries, overhaul LSP diagnostics Compiler and VM - make every memory map limit dynamic and adjustable at both build time and runtime, including compiler tables, stacks, and every execution region - reserve and grow tables, operand and return stacks, frame segments, and image-building output without relocating live code or globals - rename i/o and string helpers consistently (puts -> put_s, putn -> put_n, scat -> cat_s) across compiler, runtime, examples, tests, and docs - recast and consume values assigned to globals from inside functions - allow fixed and const together, add typed closure returns (fn:t(...)), multi-include syntax (include rt, run, core;), and recurse type inference - never infer string literals as word, anywhere, as argument, variable, or return type - draft docs/BORROW.md: a borrow checker less intrusive than Rust's but more intelligent LSP and editor integration - require explicit includes and give every toolchain area its own namespace (rt, lsp, run, core) - hover for built-in types, inferred types, values known at hover time, array and pointer lifetime/size, library names in include statements, and inferred lifetimes with explanations - honor /* */ comments as documentation for variables and arguments, and stop misattributing section headers or trailing comments as variable hints - stream diagnostics and cancel stale requests on change - jump to definition and references, including cross-file - quick fixes with automatic library import, and namespace completion Hints and diagnostics - render hints as blue squigglies by default, with hints on/small/off per file - suppress magic-number hints via a preceding /* */ comment, and say so - warn on unused arguments; allow multiple _ discards in parameters, pattern matching, and tagged enums, warning if a _ is actually used - rewrite hint and comment prose in professional compiler-doc register Libraries - add icon.zc: run-time self-modification, find/replace syntax, type and block edits, and step-through inspection of locals and globals - add actor.zc: async actors with Actor, implicit this, spawn/recv/send, injected into the compiler machinery at include time rather than built in Tooling and docs - reorganize sources, tests, and designs into subdirectories - unify windows entry points in tinyvm.bat and add a matching tinyvm.sh - add --help across the toolchain - polish the learning guide, stdlib docs, and design notes Tests - add accept, reject, and regression coverage across the compiler, LSP, - runtime, icon, actors, threads, formatting, and the toolchain contracts, plus fuzzers and property tests --- src/core/core.c | 606 ++++++++++++++++++++++++++++++++++++++++++---------- src/core/core.zc | 19 ++ src/core/io_linux.c | 33 ++- src/core/io_win.c | 4 +- src/core/memmap.h | 39 +--- src/core/stack.h | 5 +- 6 files changed, 558 insertions(+), 148 deletions(-) create mode 100644 src/core/core.zc (limited to 'src/core') diff --git a/src/core/core.c b/src/core/core.c index 2f6032a..3a1d0ca 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -25,11 +25,22 @@ #endif #else #include +#include +#include +#include +#include #ifndef O_BINARY #define O_BINARY 0 #endif #endif #include +#if !defined(_WIN32) && !defined(__wasm__) +#include +#include +#define VM_TLS _Thread_local +#else +#define VM_TLS +#endif #if (defined(__linux__) || defined(__APPLE__)) && !defined(__wasm__) && !defined(ZY_NOFFI) #define ZYFFI 1 #include @@ -62,15 +73,16 @@ #define JTYPE char #endif char Q(void); +int input_pending(void); void G(char); -extern JTYPE J; +extern VM_TLS JTYPE J; typedef int64_t Cell; /* one vm word. memory stays word-addressed. */ typedef uint64_t UCell; enum { DEFAULT_MEMORY_LIMIT = MW }; -static int memory_limit = DEFAULT_MEMORY_LIMIT; -static int memory_words; -static int memory_growth_limit; +static VM_TLS int memory_limit = DEFAULT_MEMORY_LIMIT; +static VM_TLS int memory_words; +static VM_TLS int memory_growth_limit; /* bounds baked into native blocks use the reservation, whose base never moves. */ #undef MW #define MW memory_limit @@ -80,7 +92,7 @@ static int memory_growth_limit; #define PC_DATA_OVERFLOW (PC_HALT + 1) #define PC_RETURN_OVERFLOW (PC_HALT + 2) #define PC_FRAME_OVERFLOW (PC_HALT + 3) -static Cell code_words; +static VM_TLS Cell code_words; static int code_reserve(Cell end); static int code_range_live(Cell start, Cell count); /* why the cpu stopped, when it wasn't a halt */ @@ -95,8 +107,8 @@ static int stopped(int p) #define MAXB 65536 #define BLKW 1024 /* longest block, in vm words */ -static Cell *m; -static Cell frame_floor_word = ZFP + 6, frame_top_word = ZFP + 7, frame_guard_word = ZFP + 8; +static VM_TLS Cell *m; +static VM_TLS Cell frame_floor_word = ZFP + 6, frame_top_word = ZFP + 7, frame_guard_word = ZFP + 8; static Cell frame_bound(void) { return m[frame_floor_word] + m[frame_guard_word]; } static int b_ok(Cell addr, int n) { return bop_ok(addr, n, MW); } @@ -139,7 +151,7 @@ static Cell fop_parse_cell(Cell addr) if ((UCell)addr >= (UCell)MW) return 0; return (Cell)fop_parse_words(m + addr, (UCell)MW - (UCell)addr); } -static int trace; +static VM_TLS int trace; /* the command line and the environment, kept for ops 21 to 24. a program read from stdin has no file name, so argv[0] is , as in zy(1). the rest of @@ -148,10 +160,10 @@ static int trace; #ifndef _WIN32 extern char **environ; #endif -static int zy_argc; -static char **zy_argv; -static int zy_envc; -static char **zy_envp; +static VM_TLS int zy_argc; +static VM_TLS char **zy_argv; +static VM_TLS int zy_envc; +static VM_TLS char **zy_envp; static void zy_args(int argc, char **argv) { @@ -211,7 +223,7 @@ static Cell vecstr(char **v, int count, Cell a, Cell b, Cell c, const char *lead } static Cell host_rand(void) { - static uint64_t s; + static VM_TLS uint64_t s; if (!s) s = (uint64_t)time(0) ^ 0x9E3779B97F4A7C15ull; if (!s) s = 0x9E3779B97F4A7C15ull; s ^= s << 13; s ^= s >> 7; s ^= s << 17; @@ -221,11 +233,29 @@ static Cell host_rand(void) /* c abi: ops 14..18 of the gate. see the ffi section further down. cb_s and cb_r are the vm stack pointers as of the gate store, so that a callback from native code can run vm code on top of them. */ -static Cell cb_s, cb_r; +static VM_TLS Cell cb_s, cb_r; static Cell ffi_call(Cell blk); static Cell ffi_find(Cell name); static Cell ffi_cbmake(Cell entry, Cell sig); static Cell ffi_load(Cell name); +typedef struct VmContext VmContext; +static VM_TLS VmContext *context_current, *context_children; +static int context_cancelled(void); +static void context_exit(int value); +static Cell context_start(Cell entry, Cell channel); +static Cell context_join(Cell token, int cancel); +#if !defined(_WIN32) && !defined(__wasm__) +static VM_TLS atomic_int context_default_stop; +static atomic_int context_records_live, context_code_live, context_memory_live; +size_t io_context_size(void); +void io_context_snapshot(void *destination); +void io_context_restore(const void *source); +#else +static VM_TLS int context_default_stop; +#endif +static int context_descriptor(Cell handle); +static int context_channel_close(Cell handle); +#define CONTEXT_HANDLE_LO 0x40000000 static Cell hostcall(Cell op, Cell a, Cell b, Cell c) { char path[1024], tmp[512]; @@ -234,15 +264,36 @@ static Cell hostcall(Cell op, Cell a, Cell b, Cell c) /* a build for checking code the user has not run yet (the language server): compile-time code may read what it includes, but it may not write files, exit, load or call C code, or make raw system calls */ - if (op == 4 || op == 7 || op == 12 || (op >= 14 && op <= 17) || op == 40) return -1; + if (op == 4 || op == 7 || op == 12 || (op >= 14 && op <= 17) || op == 40 || (op >= 43 && op <= 49) || (op >= 51 && op <= 54)) return -1; if ((op == 1 || op == 10) && b != 0) return -1; #endif switch (op) { + case 51: return context_start(a, b); + case 52: return context_join(a, (int)b); + case 53: +#if !defined(_WIN32) && !defined(__wasm__) + return getpid(); +#else + return -1; +#endif + case 55: +#if !defined(_WIN32) && !defined(__wasm__) + return a == 0 ? atomic_load(&context_records_live) : a == 1 ? atomic_load(&context_code_live) : a == 2 ? atomic_load(&context_memory_live) : -1; +#else + return -1; +#endif + case 54: +#if !defined(_WIN32) && !defined(__wasm__) + return (Cell)(uintptr_t)pthread_self(); +#else + return -1; +#endif case 1: if (zcstr(a, path, 1024) < 0) return -1; if (b == 1) return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644); return open(path, O_RDONLY | O_BINARY); case 2: + if (a >= CONTEXT_HANDLE_LO) return context_channel_close(a); return close((int)a); case 3: if (c < 0 || !inmem(b)) return -1; @@ -288,6 +339,7 @@ static Cell hostcall(Cell op, Cell a, Cell b, Cell c) case 6: return (Cell)time(0); case 7: + if (context_current) { context_exit((int)a); return 0; } exit((int)a); case 8: return host_rand(); @@ -313,6 +365,101 @@ static Cell hostcall(Cell op, Cell a, Cell b, Cell c) if (a == 7 || a == 8) return stack_expand(a == 8, b, &cb_s, &cb_r); if (a == 9) return code_reserve(b); return -1; + case 43: { /* clone an isolated execution context with a bidirectional channel */ +#if !defined(_WIN32) && !defined(__wasm__) + int fd[2]; pid_t child; + if (!inmem(a) || socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) return -1; + fflush(NULL); child = fork(); + if (child < 0) { close(fd[0]); close(fd[1]); return -1; } + if (!child) { + close(fd[0]); m[a] = fd[1]; +#if defined(__APPLE__) && defined(ZYJIT) + /* fork resets the thread's jit permission state; resume executable code. */ + pthread_jit_write_protect_np(1); +#endif + return 0; + } + close(fd[1]); m[a] = fd[0]; return child; +#else + return -1; +#endif + } + case 44: case 45: { /* write/read complete word buffers on a context channel */ + a = context_descriptor(a); if (a < 0) return -1; +#if !defined(_WIN32) && !defined(__wasm__) + size_t done = 0, length; ssize_t got; unsigned char *buffer; + if (b < 0 || c < 0 || c > MW - b) return -1; + buffer = (unsigned char *)(m + b); length = (size_t)c * sizeof(Cell); + while (done < length) { + if (op == 44) { +#ifdef __APPLE__ + int enabled = 1; setsockopt((int)a, SOL_SOCKET, SO_NOSIGPIPE, &enabled, sizeof enabled); + #ifdef MSG_NOSIGNAL + got = send((int)a, buffer + done, length - done, MSG_NOSIGNAL); +#else + got = send((int)a, buffer + done, length - done, 0); +#endif +#else + got = send((int)a, buffer + done, length - done, MSG_NOSIGNAL); +#endif + } else got = read((int)a, buffer + done, length - done); + if (got < 0 && errno == EINTR) continue; + if (got <= 0) return -1; + done += (size_t)got; + } + return c; +#else + return -1; +#endif + } + case 46: { /* channel readiness, with a millisecond timeout */ + a = context_descriptor(a); if (a < 0) return -1; +#if !defined(_WIN32) && !defined(__wasm__) + struct pollfd fd; int ready; fd.fd = (int)a; fd.events = POLLIN; fd.revents = 0; + do { ready = poll(&fd, 1, (int)b); } while (ready < 0 && errno == EINTR); + return ready > 0 ? !!(fd.revents & (POLLIN | POLLHUP)) : ready; +#else + return -1; +#endif + } + case 47: case 49: { /* join a child context, optionally terminating it first */ +#if !defined(_WIN32) && !defined(__wasm__) + int status; pid_t joined; + if (a <= 0) return -1; + if (op == 49) kill((pid_t)a, SIGTERM); + do { joined = waitpid((pid_t)a, &status, 0); } while (joined < 0 && errno == EINTR); + return joined < 0 ? -1 : WIFEXITED(status) ? WEXITSTATUS(status) : -1; +#else + return -1; +#endif + } + case 50: { /* monotonic milliseconds for elapsed-time measurements */ +#if !defined(_WIN32) && !defined(__wasm__) + struct timespec now; if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) return -1; + return (Cell)now.tv_sec * 1000 + now.tv_nsec / 1000000; +#else + return -1; +#endif + } + case 48: { /* sleep for a bounded millisecond duration */ +#if !defined(_WIN32) && !defined(__wasm__) + struct timespec duration; + if (a < 0 || a > 86400000) return -1; + duration.tv_sec = (time_t)(a / 1000); duration.tv_nsec = (long)(a % 1000) * 1000000; + while (nanosleep(&duration, &duration) < 0) if (errno != EINTR) return -1; + return 0; +#else + return -1; +#endif + } + case 42: /* poll: 1 when input can be read without waiting */ + return input_pending(); + case 41: { /* shortest float text into a word buffer */ + char buf[400]; int n = fop_format_mode((uint64_t)a, 0, 17, buf), i; + if (c <= n || b < 0 || b > MW || c > MW - b) return -1; + for (i = 0; i < n; i++) b_commit(b + i, (unsigned char)buf[i]); + b_commit(b + n, 0); return n; + } case 10: if (bop_cstr(m, a, path, 1024, MW) < 0) return -1; if (b == 1) return open(path, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0644); @@ -468,11 +615,11 @@ typedef struct { Uop *u; signed char *reg; /* physical register per value, for the jit; 0 if it didn't fit */ } Enh; -static Enh enh[NENH]; -static int need_flush; +static VM_TLS Enh enh[NENH]; +static VM_TLS int need_flush; #define MAXUOP 4096 -static Uop ub[MAXUOP]; static int un, unv, uerr; /* the program being built */ +static VM_TLS Uop ub[MAXUOP]; static VM_TLS int un, unv, uerr; /* the program being built */ static int unew(int op, int a, int b, int c, Cell imm) { Uop *u; @@ -684,8 +831,8 @@ static void umagic(UCell d, UCell *mul, int *add, int *sh) /* hacker's delight, *mul = q2 + 1; *sh = p - 64; } -static Uop ob[MAXUOP]; static int on, onv; -static int *omap, *isk; static Cell *kv; /* old value -> new value; constness of new values */ +static VM_TLS Uop ob[MAXUOP]; static VM_TLS int on, onv; +static VM_TLS int *omap, *isk; static VM_TLS Cell *kv; /* old value -> new value; constness of new values */ static int pure(int op) { return !is_memop(op); } static int ispow2(UCell x) { return x && !(x & (x - 1)); } static int lg2(UCell x) { int n = 0; while (x > 1) { x >>= 1; n++; } return n; } @@ -784,12 +931,15 @@ static int oe(int op, int a, int b, int c, Cell imm) } return raw(op, a, b, c, 0); } +static void optimizer_storage(void) { + static VM_TLS int omap_[MAXUOP + 16], isk_[MAXUOP * 4]; static VM_TLS Cell kv_[MAXUOP * 4]; + omap = omap_; isk = isk_; kv = kv_; +} /* runs the optimizer over ub[0..un) with nin inputs; leaves the result in e */ static int optimize(Enh *e, int nin, int *out, int nout) { int i, *live, n = 0; - static int omap_[MAXUOP + 16], isk_[MAXUOP * 4]; static Cell kv_[MAXUOP * 4]; - omap = omap_; isk = isk_; kv = kv_; + optimizer_storage(); on = 0; onv = nin; for (i = 0; i < nin; i++) { omap[i] = i; isk[i] = 0; } for (i = 0; i < un && !uerr; i++) { @@ -962,12 +1112,12 @@ typedef struct { unsigned gen; /* bumped every time the region's code is dropped: a copy made from an older one is stale */ } Rg; #define MAXRG 4096 -static Rg *rgs[MAXRG]; -static int nrg; -static int fn_pc[MAXRG]; -static Rg *fn_rg_[MAXRG]; -static Rg *fn_cache[1024]; -static int nfn; +static VM_TLS Rg *rgs[MAXRG]; +static VM_TLS int nrg; +static VM_TLS int fn_pc[MAXRG]; +static VM_TLS Rg *fn_rg_[MAXRG]; +static VM_TLS Rg *fn_cache[1024]; +static VM_TLS int nfn; /* ---- bookkeeping for translated code, shared by the backends ---- a translation covers a range of vm words [a,b). three things have to stay @@ -983,13 +1133,13 @@ static int nfn; #define NPG ((code_words >> PGSH) + 1) #define MAXPGN (1 << 19) #define MAXDEP (1 << 18) -static struct { int a, b; } ix_blk[MAXB]; -static int ix_n; -static int *pg_head, pg_free, pg_top, pg_live; /* node numbers are stored plus one: 0 is none */ -static struct { int blk, next; } pg_node[MAXPGN]; -static int dep_head[NENH + 1], dep_n; /* slot NENH: depends on every install */ -static struct { int blk, next; } dep_node[MAXDEP]; -static int drop_epoch; /* changes whenever any translation dies */ +static VM_TLS struct { int a, b; } ix_blk[MAXB]; +static VM_TLS int ix_n; +static VM_TLS int *pg_head, pg_free, pg_top, pg_live; /* node numbers are stored plus one: 0 is none */ +static VM_TLS struct { int blk, next; } pg_node[MAXPGN]; +static VM_TLS int dep_head[NENH + 1], dep_n; /* slot NENH: depends on every install */ +static VM_TLS struct { int blk, next; } dep_node[MAXDEP]; +static VM_TLS int drop_epoch; /* changes whenever any translation dies */ static void be_drop(int j); /* the backend forgets block j's code and coverage */ static void runmark(Rg *R); /* the backend stops watching the region's words */ static void rclear(Rg *R); @@ -1048,9 +1198,9 @@ static void slot_changed(int slot) for (j = 0; j < nrg; j++) if (rgs[j]->lifted && (rgs[j]->dep[slot >> 3] & (1 << (slot & 7)))) { rclear(rgs[j]); drop_epoch++; } } -static Cell halt_at = -1; /* a nested call stopped the machine: operand or return overflow */ -static unsigned short *o3ent; /* region + 1 at an entry point */ -static unsigned char *o3ei; /* which of its entries */ +static VM_TLS Cell halt_at = -1; /* a nested call stopped the machine: operand or return overflow */ +static VM_TLS unsigned short *o3ent; /* region + 1 at an entry point */ +static VM_TLS unsigned char *o3ei; /* which of its entries */ static int o3_ready(Rg *C); static int o3_exec_region(Rg *C, int entry, Cell *ps, Cell *pr); static int vm_until(int pc, int stop_eq, int stop_ge, Rg *re, Cell *ps, Cell *pr); @@ -1189,10 +1339,10 @@ typedef double (*FfiD)(FFI_IT, FFI_DT, FFI_ST, ...); typedef float (*FfiF)(FFI_IT, FFI_DT, FFI_ST, ...); #define FFI_SYMS 256 #define FFI_LIBS 16 -static struct { char name[96]; void *p; } ffi_sym[FFI_SYMS]; -static int ffi_nsym; -static void *ffi_lib[FFI_LIBS]; -static int ffi_nlib, ffi_tried_libm; +static VM_TLS struct { char name[96]; void *p; } ffi_sym[FFI_SYMS]; +static VM_TLS int ffi_nsym; +static VM_TLS void *ffi_lib[FFI_LIBS]; +static VM_TLS int ffi_nlib, ffi_tried_libm; static void *ffi_lookup(const char *name) { int i; @@ -1388,7 +1538,7 @@ static int ffi_varargs(void *f) #if defined(__APPLE__) && defined(__aarch64__) static const char *names[] = { "printf", "fprintf", "sprintf", "snprintf" }; static const int fixed[] = { 1, 2, 2, 3 }; - static void *entry[4]; static int ready; + static VM_TLS void *entry[4]; static VM_TLS int ready; int i; if (!ready) { for (i = 0; i < 4; i++) entry[i] = dlsym(RTLD_DEFAULT, names[i]); ready = 1; } for (i = 0; i < 4; i++) if (f == entry[i]) return fixed[i]; @@ -1549,7 +1699,7 @@ static long ncall_host(long sp, long rp) expects them, pushes a return address that stops the nested run, runs the vm function and pops its result. */ #define FFI_CBS 16 -static struct { Cell entry, sig; int used; } ffi_cb[3][FFI_CBS]; +static VM_TLS struct { Cell entry, sig; int used; } ffi_cb[3][FFI_CBS]; static Cell ffi_enter(int cls, int k, Cell *iv, double *dv, Cell *sv) { Cell s = cb_s, r = cb_r, sig = ffi_cb[cls][k].sig, res = 0, sent = 1, w; @@ -1595,7 +1745,7 @@ FFI_ALL(FFI_SLOT) #define FFI_PICK(K) (void *)ffi_i##K, #define FFI_PICKD(K) (void *)ffi_d##K, #define FFI_PICKF(K) (void *)ffi_f##K, -static void *ffi_fn[3][FFI_CBS] = { { FFI_ALL(FFI_PICK) }, { FFI_ALL(FFI_PICKD) }, { FFI_ALL(FFI_PICKF) } }; +static VM_TLS void *ffi_fn[3][FFI_CBS] = { { FFI_ALL(FFI_PICK) }, { FFI_ALL(FFI_PICKD) }, { FFI_ALL(FFI_PICKF) } }; static Cell ffi_cbmake(Cell entry, Cell sig) { int cls = (sig & 15) == 2 ? 1 : (sig & 15) == 5 ? 2 : 0, k; @@ -1611,8 +1761,8 @@ static Cell ffi_cbmake(Cell entry, Cell sig) /* a native call the o3 lifter has resolved: the micro-ops narg and ncall carry an index into this table. imm of narg is the argument's position */ typedef struct { void *f; Cell sig; int n; char name[48]; } Ncd; -static Ncd ncd[256]; -static int nncd; +static VM_TLS Ncd ncd[256]; +static VM_TLS int nncd; static int ncd_get(Cell blk) { void *f; @@ -1712,15 +1862,15 @@ static void rclear(Rg *R) static void rdrop_all(void) { int j; for (j = 0; j < nrg; j++) if (rgs[j]->lifted) rclear(rgs[j]); } /* lifting one block */ -static Rg *R_; -static unsigned char *lead_; -static int *bidx_, *wl_, nw_; -static int lv_[RMAXV], lw_[RMAXV]; +static VM_TLS Rg *R_; +static VM_TLS unsigned char *lead_; +static VM_TLS int *bidx_, *wl_, nw_; +static VM_TLS int lv_[RMAXV], lw_[RMAXV]; #define SB 1100 -static int skv[2 * SB], stp, slo, shi; -static unsigned char skk[2 * SB]; -static int afp[MAXUOP * 4 + 16]; /* value -> 1 + its offset from fp, if it is fp + k */ -static int emap[MAXUOP * 4 + 16]; +static VM_TLS int skv[2 * SB], stp, slo, shi; +static VM_TLS unsigned char skk[2 * SB]; +static VM_TLS int afp[MAXUOP * 4 + 16]; /* value -> 1 + its offset from fp, if it is fp + k */ +static VM_TLS int emap[MAXUOP * 4 + 16]; static int rvar(int v) { @@ -2201,7 +2351,7 @@ enum { C_UNSEEN, C_CONST, C_VAR, C_COPY }; /* C_COPY: the variable holds the same value as variable v (both at the same point) */ static int copy_ok(const Rg *R, int j, int k) { return j > 0 && j < RMAXV && k > 0 && k < R->nv && !R->fv[j] && !R->fv[k]; } typedef struct { unsigned char st[RMAXV]; Cell v[RMAXV]; } CIn; -static int g_isk[MAXUOP * 4]; static Cell g_kv[MAXUOP * 4]; static int g_omap[MAXUOP + 16]; +static VM_TLS int g_isk[MAXUOP * 4]; static VM_TLS Cell g_kv[MAXUOP * 4]; static VM_TLS int g_omap[MAXUOP + 16]; static int cjoin(CIn *d, const CIn *s) { int j, ch = 0; @@ -2523,7 +2673,7 @@ static int opcost(int op) #define GOP_PHIM (U_N + 5) #define GNMAX (1 << 17) typedef struct { int op, a, b, c, next; Cell imm; } GN; -static GN *gn_; static int gn_n, *gh_, gn_over; +static VM_TLS GN *gn_; static VM_TLS int gn_n, *gh_, gn_over; static int gn_init(void) { if (!gn_) { gn_ = malloc(GNMAX * sizeof(GN)); gh_ = malloc(GNMAX * sizeof(int)); } @@ -3143,11 +3293,11 @@ static Rg *rat(int p) } #ifdef ZYJIT -static unsigned char *cov; /* how many live translations cover the word (255 sticks) */ -static void **tab; /* vm address -> translation */ +static VM_TLS unsigned char *cov; /* how many live translations cover the word (255 sticks) */ +static VM_TLS void **tab; /* vm address -> translation */ static void be_drop(int j) { tab[ix_blk[j].a] = 0; cdec(cov, ix_blk[j].a, ix_blk[j].b); if (trace) fprintf(stderr, "jit: dropped %d..%d\n", ix_blk[j].a, ix_blk[j].b - 1); } -static unsigned *code, *cp, *x_enter, *x_exit, *x_disp, *x_first; -static long S[14]; /* memory, stacks, hooks, limits, and the target saved on overflow */ +static VM_TLS unsigned *code, *cp, *x_enter, *x_exit, *x_disp, *x_first; +static VM_TLS long S[15]; /* memory, stacks, hooks, limits, and the target saved on overflow */ static void o3_invoke_jit(Cell *ps, Cell *pr, Cell target, Cell retpc) { o3_invoke(ps, pr, target, retpc); @@ -3240,7 +3390,11 @@ static void build_stubs(void) li(W0, PC_DATA_OVERFLOW); b_to(x_exit); /* data stack overflow */ ovr = cp; li(W0, PC_RETURN_OVERFLOW); b_to(x_exit); /* return stack overflow */ - x_disp = cp; /* w0 = target: check the stacks, chain through tab[] or fall back to c */ + x_disp = cp; /* w0 = target: check cancellation and stacks before chaining */ + if (context_current) { + ldrx_u(X9, ST, 112); E(0x88dffc00u | (X9 << 5) | X10); + unsigned *ready = cp; E(0x3400000a); li(W0, PC_HALT); b_to(x_exit); cb_fix(ready, cp); + } str_u(0, ST, 104); E(0xeb18029f); /* cmp x20, x24 */ E(0x54000003 | ((ovd - cp) & 0x7ffff) << 5); /* b.lo data overflow */ @@ -3573,7 +3727,7 @@ static void emit_uop(int op, int d, int a, int b, int c, int tmp) /* an enhancement, inlined: inputs from the data stack into x0..x15, the optimized micro-ops, outputs back. constants are made where they're used, or folded into the instruction (a fixed address, a small addend). */ -static int ekc[MAXUOP * 4 + 16]; static Cell ekv[MAXUOP * 4 + 16]; +static VM_TLS int ekc[MAXUOP * 4 + 16]; static VM_TLS Cell ekv[MAXUOP * 4 + 16]; static void emit_enh_e(Enh *e, int s) { signed char *r = e->reg; @@ -3618,7 +3772,7 @@ static int spec_enh(int s, Cell *ks, int nk, Enh *te) { Enh *e = &enh[s]; int nin = e->nin - nk, i, j, out[8], na; - static int mp[MAXUOP * 4 + 16]; + static VM_TLS int mp[MAXUOP * 4 + 16]; if (e->nv > MAXUOP * 4) return -1; un = 0; unv = nin; uerr = 0; for (j = 0; j < nin; j++) mp[j] = j; @@ -3643,16 +3797,16 @@ static int spec_enh(int s, Cell *ks, int nk, Enh *te) d8..d15 when the value is a float, so a chain of float ops does not move through an integer register. constants have no register: they are made in a scratch (x16/x17 or d16/d17) where they're used. */ -static signed char rreg[MAXUOP * 4 + 16]; -static unsigned char rk[MAXUOP * 4 + 16], rhome[MAXUOP * 4 + 16], rfp[MAXUOP * 4 + 16]; -static Cell rkv[MAXUOP * 4 + 16]; static int rlast[MAXUOP * 4 + 16]; -static unsigned **rfa; static int *rfb, rnf; /* branches to blocks, fixed up at the end */ +static VM_TLS signed char rreg[MAXUOP * 4 + 16]; +static VM_TLS unsigned char rk[MAXUOP * 4 + 16], rhome[MAXUOP * 4 + 16], rfp[MAXUOP * 4 + 16]; +static VM_TLS Cell rkv[MAXUOP * 4 + 16]; static VM_TLS int rlast[MAXUOP * 4 + 16]; +static VM_TLS unsigned **rfa; static VM_TLS int *rfb, rnf; /* branches to blocks, fixed up at the end */ #define LINKN 1024 -static void *linkstk[LINKN]; -static Cell jit_depth; +static VM_TLS void *linkstk[LINKN]; +static VM_TLS Cell jit_depth; typedef struct { void *stub; int fslots; int argc; } FnNat; -static FnNat *fn_nat; +static VM_TLS FnNat *fn_nat; static void rforget(Rg *R) { int e; @@ -3941,6 +4095,10 @@ static int remit(Rg *R, int bi) int i, j, d, a, b, c; unsigned *p; if (ralloc(R, B)) return -1; + if (context_current) { + ldrx_u(X9, ST, 112); E(0x88dffc00u | (X9 << 5) | X10); + unsigned *ready = cp; E(0x3400000a); li(W0, PC_HALT); b_to(x_exit); cb_fix(ready, cp); + } for (i = 0; i < B->n; i++) { Uop *u = &B->u[i]; switch (u->op) { @@ -4235,7 +4393,7 @@ static int vm_until(int pc, int stop_eq, int stop_ge, Rg *re, Cell *ps, Cell *pr for (i = 0; i < nent; i++) { saved_ent[i] = tab[re->ent[i]]; tab[re->ent[i]] = 0; } } S[1] = *ps; S[2] = *pr; - while ((unsigned)pc < (unsigned)code_words || pc == PC_DATA_OVERFLOW || pc == PC_RETURN_OVERFLOW) { + while (!context_cancelled() && ((unsigned)pc < (unsigned)code_words || pc == PC_DATA_OVERFLOW || pc == PC_RETURN_OVERFLOW)) { void *c; if (pc == PC_DATA_OVERFLOW || pc == PC_RETURN_OVERFLOW) { Cell sp = S[1], rp = S[2]; int ret = pc == PC_RETURN_OVERFLOW; @@ -4313,35 +4471,35 @@ static int o3_exec_region(Rg *C, int entry, Cell *ps, Cell *pr) #include #include #include -static int HOT = 5000; /* runs before a block gets compiled: ZYHOT */ -static const char *lpasses = "default"; /* ZYPASSES */ +static VM_TLS int HOT = 5000; /* runs before a block gets compiled: ZYHOT */ +static VM_TLS const char *lpasses = "default"; /* ZYPASSES */ #define LMAXB 65536 typedef Cell (*Blk)(Cell *mem, Cell *regs); -static Blk *lt; /* address -> compiled block */ -static unsigned short *hot; -static unsigned char *lcov; /* how many compiled blocks cover the word (255 sticks) */ -static void *lrt[MAXB]; /* each block's resource tracker: removing it frees the block's code */ -static int llvm_ok, nblocks; -static LLVMOrcLLJITRef jit; -static LLVMTargetMachineRef tm_; /* the host cpu, so the pass pipeline's cost models know the target */ -static LLVMOrcThreadSafeContextRef tsc; -static LLVMContextRef ctx; -static LLVMTypeRef i8t, i32t, i64t, ptrt, i1t, f64t; -static LLVMBuilderRef bd; -static LLVMValueRef rvv[RMAXV]; -static LLVMValueRef fn_, vm_, vregs, vs, vr; /* function, memory, regs, s and r slots */ -static LLVMTypeRef l_dret; /* what a register-passing function returns: the next address and s */ -static int l_dm; /* the function being built is the register-passing one */ -static Cell d_r; /* r, left here by a register-passing function that exits abnormally */ -static LLVMModuleRef mod_; +static VM_TLS Blk *lt; /* address -> compiled block */ +static VM_TLS unsigned short *hot; +static VM_TLS unsigned char *lcov; /* how many compiled blocks cover the word (255 sticks) */ +static VM_TLS void *lrt[MAXB]; /* each block's resource tracker: removing it frees the block's code */ +static VM_TLS int llvm_ok, nblocks; +static VM_TLS LLVMOrcLLJITRef jit; +static VM_TLS LLVMTargetMachineRef tm_; /* the host cpu, so the pass pipeline's cost models know the target */ +static VM_TLS LLVMOrcThreadSafeContextRef tsc; +static VM_TLS LLVMContextRef ctx; +static VM_TLS LLVMTypeRef i8t, i32t, i64t, ptrt, i1t, f64t; +static VM_TLS LLVMBuilderRef bd; +static VM_TLS LLVMValueRef rvv[RMAXV]; +static VM_TLS LLVMValueRef fn_, vm_, vregs, vs, vr; /* function, memory, regs, s and r slots */ +static VM_TLS LLVMTypeRef l_dret; /* what a register-passing function returns: the next address and s */ +static VM_TLS int l_dm; /* the function being built is the register-passing one */ +static VM_TLS Cell d_r; /* r, left here by a register-passing function that exits abnormally */ +static VM_TLS LLVMModuleRef mod_; -static unsigned char *rcov; /* how many lifted o3 regions cover the word */ +static VM_TLS unsigned char *rcov; /* how many lifted o3 regions cover the word */ static int rdrop(int a); /* code that is dropped is freed when no compiled code is running: a block can drop itself (it stores into its own words), and a callback can nest */ -static int ccode; -static LLVMOrcResourceTrackerRef pend_rt[1024]; -static int npend_rt; +static VM_TLS int ccode; +static VM_TLS LLVMOrcResourceTrackerRef pend_rt[1024]; +static VM_TLS int npend_rt; static void rt_sweep(void) { LLVMErrorRef err; @@ -4641,7 +4799,7 @@ static void llvm_init(void) pair is two. no noalias: the host helpers it calls read and write that memory. */ static void lfunc_attrs(LLVMValueRef f) { - static unsigned kn, ka, kd, knu; + static VM_TLS unsigned kn, ka, kd, knu; LLVMValueRef p0 = LLVMGetParam(f, 0); (void)p0; if (!kn) { @@ -4881,12 +5039,12 @@ static Blk lcompile(int p) /* ---- o3 regions without the arm64 jit: compiled by llvm, or run here ---- */ #ifndef ZYLLVM -static unsigned char *rcov; /* 1 on the words of a lifted region */ +static VM_TLS unsigned char *rcov; /* 1 on the words of a lifted region */ #endif -static Cell rvals[MAXUOP * 4 + 16]; +static VM_TLS Cell rvals[MAXUOP * 4 + 16]; #define RPOOL 40 -static Cell rpool[RPOOL][MAXUOP * 4 + 16]; -static int rpool_n; +static VM_TLS Cell rpool[RPOOL][MAXUOP * 4 + 16]; +static VM_TLS int rpool_n; static int rdrop(int a) /* a store landed on region code: drop those regions */ { int j, hit = 0; @@ -4913,6 +5071,7 @@ static int rexec(Rg *R, int e, int *ps, int *pr) var[0] = fp = mread(ZFP); for (j = 1; j < R->nv; j++) var[j] = mread(fp + R->slot[j]); for (;;) { + if (context_cancelled()) { t = PC_HALT; break; } RB *B = &R->b[b]; for (i = 0; i < B->n; i++) { Uop *u = &B->u[i]; @@ -4986,7 +5145,7 @@ static void rcompile(Rg *R, int eager) { (void)R; (void)eager; } mem2reg makes them ssa across blocks), each block's micro-ops as ir, then the O3 pipeline. f(mem, regs, entry) returns the next vm address. */ typedef Cell (*RFn)(Cell *mem, Cell *regs, int ent); -static int nregions; +static VM_TLS int nregions; static void rforget(Rg *R) { rt_queue(R->rt); R->rt = 0; R->nat = 0; R->dnat = 0; } static void rmark(Rg *R) { R->marked = 1; cinc(rcov, R->lo, R->hi); cinc(lcov, R->lo, R->hi); } static void runmark(Rg *R) { cdec(rcov, R->lo, R->hi); cdec(lcov, R->lo, R->hi); } @@ -5149,7 +5308,7 @@ static LLVMValueRef lncall(int di, LLVMValueRef *ai, LLVMValueRef *af, LLVMValue return res; } #endif -static LLVMValueRef l_dfn; /* the register-passing function being built or declared for this region */ +static VM_TLS LLVMValueRef l_dfn; /* the register-passing function being built or declared for this region */ static int has_ret(Rg *R) { int b; @@ -5163,12 +5322,12 @@ static int has_ret(Rg *R) #define INL_MAX 8 /* copies in one module */ #define INL_BLOCKS 24 /* largest callee copied */ typedef struct { Rg *C; unsigned gen; LLVMValueRef fn; } InlEnt; -static InlEnt inl_tab[INL_MAX]; -static int ninl, inl_depth; +static VM_TLS InlEnt inl_tab[INL_MAX]; +static VM_TLS int ninl, inl_depth; static void lbody(Rg *R, int direct, LLVMValueRef fnv); static LLVMValueRef inline_fn(Rg *C, unsigned *gen) { - static int off = -1, inl_n; + static VM_TLS int off = -1, inl_n; LLVMValueRef sfn, sdfn, svm, svregs, svs, svr, nf, srv[RMAXV]; LLVMBasicBlockRef at; LLVMTypeRef dpt[6]; @@ -5504,7 +5663,7 @@ static void *lregion(Rg *R) lfunc_attrs(f1); dir = R->fn >= 0 && R->argc <= 3 && R->fslots >= 0 && !has_ret(R); if (dir) { - static unsigned knu; + static VM_TLS unsigned knu; LLVMTypeRef rt2[2]; rt2[0] = i64t; rt2[1] = i64t; l_dret = LLVMStructTypeInContext(ctx, rt2, 2, 0); @@ -5540,9 +5699,9 @@ static void rcompile(Rg *R, int eager) } #endif #define LD(x) (inmem(x) ? m[x] : 0) -static int stop_eq = -1, stop_ge = -1, stop_return_depth = -1, run_nest; -static Rg *stop_rg; -static Cell run_s, run_r; +static VM_TLS int stop_eq = -1, stop_ge = -1, stop_return_depth = -1, run_nest; +static VM_TLS Rg *stop_rg; +static VM_TLS Cell run_s, run_r; static int run(int p) { int s = run_nest ? (int)run_s : DS0, r = run_nest ? (int)run_r : RS0, xfer = 1; @@ -5550,9 +5709,10 @@ static int run(int p) unsigned long long steps = 0; #ifdef ZYLLVM Cell regs[2]; - if (!getenv("ZYNOLLVM")) llvm_init(); + /* worker contexts interpret regions; llvm ownership stays in its compiling context. */ + if (!context_current && !getenv("ZYNOLLVM")) llvm_init(); #endif - while ((unsigned)p < (unsigned)code_words) { + while ((unsigned)p < (unsigned)code_words && !context_cancelled()) { if (run_nest) { if (stop_eq >= 0 && p == stop_eq && r - (stack_control ? stack_get(SC_RETURN_LOW) : RS0) == stop_return_depth) break; if (stop_ge >= 0 && p >= stop_ge) break; @@ -5731,8 +5891,8 @@ static void b_commit(Cell w, Cell v) } #ifdef ZYBIGSTACK -static ucontext_t uc_main, uc_vm; -static int vm_rc; +static VM_TLS ucontext_t uc_main, uc_vm; +static VM_TLS int vm_rc; static void vm_entry(void) { vm_rc = run(GORG); } #endif static int memory_option(const char *name, int fallback, int minimum) @@ -5798,8 +5958,226 @@ static int code_range_live(Cell start, Cell count) return 0; } +/* each worker owns a host thread, vm memory and translation state. */ +#if !defined(_WIN32) && !defined(__wasm__) +typedef struct ContextChannel { Cell handle; int fd; struct ContextChannel *next; } ContextChannel; +static VM_TLS ContextChannel *context_channels; +static atomic_llong context_id_sequence; +struct VmContext { + ContextChannel parent_channel, worker_channel; + pthread_t thread; + struct VmContext *next; + atomic_int stop; + Cell token, entry, sp, rp, stack, words, limit, growth, code_size; + Cell frame_floor, frame_top, frame_guard; + Cell *memory; + int exit_code, requested_exit; + void *input_snapshot; + int argc, envc; + char **argv, **envp; +#ifdef ZYFFI + unsigned char callback_snapshot[sizeof ffi_cb], symbol_snapshot[sizeof ffi_sym], library_snapshot[sizeof ffi_lib]; + int ffi_symbols, ffi_libraries, ffi_libm; +#endif + Enh enhancements[NENH]; + Rg *regions[MAXRG]; + int region_count; + unsigned short *entries; + unsigned char *entry_indices; +}; +static int context_descriptor(Cell handle) { + ContextChannel *channel; + if (handle < CONTEXT_HANDLE_LO) return handle >= 0 && handle <= 0x7fffffff ? (int)handle : -1; + for (channel = context_channels; channel; channel = channel->next) if (channel->handle == handle) return channel->fd; + return -1; +} +static int context_channel_close(Cell handle) { + ContextChannel **link = &context_channels, *channel; + while (*link && (*link)->handle != handle) link = &(*link)->next; + if (!*link) return -1; channel = *link; *link = channel->next; + if (channel->fd < 0) return -1; + int result = close(channel->fd); channel->fd = -1; return result; +} +static void context_snapshot_free(VmContext *context) { + int i; + for (i = 0; i < NENH; i++) { free(context->enhancements[i].u); free(context->enhancements[i].reg); } + for (i = 0; i < context->region_count; i++) free(context->regions[i]); + free(context->input_snapshot); free(context->entries); free(context->entry_indices); if (context->memory) { free(context->memory); atomic_fetch_sub(&context_memory_live, 1); } context->memory = 0; +} +static int context_cancelled(void) { return context_current && atomic_load_explicit(&context_current->stop, memory_order_acquire); } +static void context_exit(int value) { context_current->exit_code = value; context_current->requested_exit = 1; atomic_store_explicit(&context_current->stop, 1, memory_order_release); } +static void context_engine_free(void) { + int i; + for (i = 0; i < nrg; i++) { rclear(rgs[i]); free(rgs[i]); } + for (i = 0; i < NENH; i++) { free(enh[i].u); free(enh[i].reg); } + free(pg_head); free(o3ent); free(o3ei); free(gn_); free(gh_); +#ifdef ZYJIT + free(tab); free(fn_nat); free(cov); if (code && code != MAP_FAILED && munmap(code, CODEW * 4) == 0) atomic_fetch_sub(&context_code_live, 1); +#else + free(rcov); +#ifdef ZYLLVM + free(lcov); free(lt); free(hot); +#endif +#endif + free(m); m = 0; atomic_fetch_sub(&context_memory_live, 1); +} +static void *context_worker(void *opaque) { + VmContext *context = opaque; + Cell sp = context->sp, rp = context->rp, entry = context->entry; + int i, back; + optimizer_storage(); + context_current = context; io_context_restore(context->input_snapshot); free(context->input_snapshot); context->input_snapshot = 0; trace = getenv("ZYTRACE") != 0; + context->worker_channel.next = context_channels; context_channels = &context->worker_channel; + m = context->memory; context->memory = 0; + memory_limit = (int)context->limit; memory_words = (int)context->words; memory_growth_limit = (int)context->growth; + stack_control = context->stack; frame_floor_word = context->frame_floor; frame_top_word = context->frame_top; frame_guard_word = context->frame_guard; + zy_argc = context->argc; zy_argv = context->argv; zy_envc = context->envc; zy_envp = context->envp; +#ifdef ZYFFI + memcpy(ffi_cb, context->callback_snapshot, sizeof ffi_cb); memcpy(ffi_sym, context->symbol_snapshot, sizeof ffi_sym); memcpy(ffi_lib, context->library_snapshot, sizeof ffi_lib); + ffi_nsym = context->ffi_symbols; ffi_nlib = context->ffi_libraries; ffi_tried_libm = context->ffi_libm; +#endif +#ifdef ZYJIT + cov = calloc((size_t)MW, 1); +#else + rcov = calloc((size_t)MW, 1); +#ifdef ZYLLVM + lcov = calloc((size_t)MW, 1); +#endif +#endif +#ifdef ZYJIT + if (!cov) { context->exit_code = -1; context_channel_close(context->worker_channel.handle); context_engine_free(); return 0; } +#else + if (!rcov +#ifdef ZYLLVM + || !lcov +#endif + ) { context->exit_code = -1; context_channel_close(context->worker_channel.handle); context_engine_free(); return 0; } +#endif + if (code_reserve(context->code_size) < 0) { context->exit_code = -1; context_channel_close(context->worker_channel.handle); context_engine_free(); return 0; } + memcpy(enh, context->enhancements, sizeof enh); memset(context->enhancements, 0, sizeof context->enhancements); + for (i = 0; i < context->region_count; i++) { rgs[i] = context->regions[i]; context->regions[i] = 0; } + nrg = context->region_count; context->region_count = 0; + memcpy(o3ent, context->entries, (size_t)code_words * sizeof *o3ent); memcpy(o3ei, context->entry_indices, (size_t)code_words); + free(context->entries); context->entries = 0; free(context->entry_indices); context->entry_indices = 0; +#ifdef ZYJIT +#ifdef __APPLE__ + code = mmap(0, CODEW * 4, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON | MAP_JIT, -1, 0); +#else + code = mmap(0, CODEW * 4, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); +#endif + if (code == MAP_FAILED) { context->exit_code = -1; context_channel_close(context->worker_channel.handle); context_engine_free(); return 0; } + atomic_fetch_add(&context_code_live, 1); + jitwrite(1); build_stubs(); jitwrite(0); syncicache(code, cp); + S[0] = (long)m; S[1] = sp; S[2] = rp; S[3] = (long)tab; S[4] = (long)cov; + S[5] = (long)Q; S[6] = (long)G; S[7] = (long)&J; S[8] = (long)hook; + S[9] = (long)inst_hook; S[10] = (long)xrun; S[11] = (long)(m + stack_data_limit()); S[12] = (long)(m + stack_return_limit()); + S[14] = (long)&context->stop; cov[SYSGATE] = 1; +#endif + if (entry >= HEAP_LO && inmem(entry) && m[entry - 3] == (entry ^ 0x2B7E1516)) { m[--sp] = entry + 3; entry = m[entry]; } + m[rp++] = PC_HALT; + back = vm_until((int)entry, PC_HALT, -1, 0, &sp, &rp); + if (!context->requested_exit) context->exit_code = context_cancelled() ? 0 : stopped(back) ? -1 : inmem(sp) ? (int)m[sp] : 0; + context_channel_close(context->worker_channel.handle); + while (context_children) context_join(context_children->token, 1); + context_engine_free(); context_current = 0; return 0; +} +static Cell context_start(Cell entry, Cell channel) { + VmContext *context; int fd[2], i; pthread_attr_t attributes; Cell saved; + if (entry < 0 || (!inmem(entry)) || !inmem(channel)) return -1; + if (entry >= HEAP_LO && (!inmem(entry - 3) || m[entry - 3] != (entry ^ 0x2B7E1516))) return -1; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) return -1; + context = calloc(1, sizeof *context); if (!context) { close(fd[0]); close(fd[1]); return -1; } + context->entry = entry; context->words = memory_words; context->limit = memory_limit; context->growth = memory_growth_limit; context->code_size = code_words; + context->sp = cb_s; context->rp = cb_r; context->stack = stack_control; context->frame_floor = frame_floor_word; context->frame_top = frame_top_word; context->frame_guard = frame_guard_word; + context->argc = zy_argc; context->argv = zy_argv; context->envc = zy_envc; context->envp = zy_envp; + context->token = atomic_fetch_add(&context_id_sequence, 1) + 1; + context->parent_channel.handle = CONTEXT_HANDLE_LO + atomic_fetch_add(&context_id_sequence, 1) + 1; + context->worker_channel.handle = CONTEXT_HANDLE_LO + atomic_fetch_add(&context_id_sequence, 1) + 1; + context->parent_channel.fd = fd[0]; context->worker_channel.fd = fd[1]; atomic_init(&context->stop, 0); + context->input_snapshot = malloc(io_context_size()); + if (!context->input_snapshot) goto failed; io_context_snapshot(context->input_snapshot); + context->memory = calloc((size_t)memory_limit, sizeof(Cell)); + if (context->memory) atomic_fetch_add(&context_memory_live, 1); + context->entries = malloc((size_t)code_words * sizeof *o3ent); context->entry_indices = malloc((size_t)code_words); + if (!context->memory || !context->entries || !context->entry_indices) goto failed; + saved = m[channel]; m[channel] = context->worker_channel.handle; memcpy(context->memory, m, (size_t)memory_words * sizeof(Cell)); m[channel] = saved; + memcpy(context->entries, o3ent, (size_t)code_words * sizeof *o3ent); memcpy(context->entry_indices, o3ei, (size_t)code_words); + for (i = 0; i < NENH; i++) { + context->enhancements[i] = enh[i]; context->enhancements[i].u = 0; context->enhancements[i].reg = 0; + if (enh[i].u) { context->enhancements[i].u = malloc((size_t)(enh[i].n + 1) * sizeof(Uop)); if (!context->enhancements[i].u) goto failed; memcpy(context->enhancements[i].u, enh[i].u, (size_t)(enh[i].n + 1) * sizeof(Uop)); } + if (enh[i].reg) { context->enhancements[i].reg = malloc((size_t)enh[i].nv); if (!context->enhancements[i].reg) goto failed; memcpy(context->enhancements[i].reg, enh[i].reg, (size_t)enh[i].nv); } + } + for (i = 0; i < nrg; i++) { + Rg *region = malloc(sizeof *region); if (!region) goto failed; *region = *rgs[i]; + region->b = 0; region->nb = 0; region->lifted = 0; region->marked = 0; region->nat = 0; region->dnat = 0; region->rt = 0; memset(region->stub, 0, sizeof region->stub); + context->regions[context->region_count++] = region; + } +#ifdef ZYFFI + memcpy(context->callback_snapshot, ffi_cb, sizeof ffi_cb); memcpy(context->symbol_snapshot, ffi_sym, sizeof ffi_sym); memcpy(context->library_snapshot, ffi_lib, sizeof ffi_lib); + context->ffi_symbols = ffi_nsym; context->ffi_libraries = ffi_nlib; context->ffi_libm = ffi_tried_libm; +#endif + pthread_attr_init(&attributes); pthread_attr_setstacksize(&attributes, 16u << 20); + i = pthread_create(&context->thread, &attributes, context_worker, context); pthread_attr_destroy(&attributes); + if (i) goto failed; + context->parent_channel.next = context_channels; context_channels = &context->parent_channel; + atomic_fetch_add(&context_records_live, 1); + m[channel] = context->parent_channel.handle; context->next = context_children; context_children = context; return context->token; +failed: + context_snapshot_free(context); free(context); close(fd[0]); close(fd[1]); return -1; +} +static Cell context_join(Cell token, int cancel) { + VmContext **link = &context_children, *context; int result; + while (*link && (*link)->token != token) link = &(*link)->next; + if (!*link) return -1; context = *link; + if (cancel) { atomic_store_explicit(&context->stop, 1, memory_order_release); if (context->parent_channel.fd >= 0) shutdown(context->parent_channel.fd, SHUT_RDWR); } + if (pthread_join(context->thread, 0)) return -1; + if (context->parent_channel.fd >= 0) context_channel_close(context->parent_channel.handle); result = context->exit_code; *link = context->next; + context_snapshot_free(context); free(context); atomic_fetch_sub(&context_records_live, 1); return result; +} +#else +static int context_descriptor(Cell handle) { return -1; } +static int context_channel_close(Cell handle) { return -1; } +static int context_cancelled(void) { return 0; } +static void context_exit(int value) { } +static Cell context_start(Cell entry, Cell channel) { return -1; } +static Cell context_join(Cell token, int cancel) { return -1; } +#endif + int main(int argc, char **argv) { + if (argc > 1 && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h"))) { +#ifdef USE_LSP_GENOME + fputs("usage: zclsp [--format|--annotate|--types|--help|-h] < input\n" + "reads language-server protocol messages from stdin and writes responses to stdout.\n" + "uses content-length framing; plain source on stdin runs a diagnostic check.\n", + stdout); + fputs("source options:\n" + " --format print formatted source\n" + " --annotate print source with inferred types\n" + " --types list inferred type edits as line:column length text\n", stdout); +#else + fputs("usage: zy [program arguments] < source.zc\n" + "compiles source from stdin and runs main; input after a backtick is program input.\n" + "program arguments are available through std.argv; stdin has the name .\n", + stdout); +#endif + fputs("options:\n --help, -h show this help and exit\n" + "environment:\n" + " TINYVM_MEMORY_WORDS initial vm memory in words\n" + " TINYVM_MAX_MEMORY_WORDS maximum vm memory in words\n" + " ZYTRACE trace native translation and optimization\n", stdout); + return 0; + } + if (argc > 1 && argv[1][0] == '-' && argv[1][1] == '-') { +#ifdef USE_LSP_GENOME + if (strcmp(argv[1], "--format") && strcmp(argv[1], "--annotate") && strcmp(argv[1], "--types")) { +#endif + fprintf(stderr, "tinyvm: unknown option %s; use --help for usage\n", argv[1]); + return 2; +#ifdef USE_LSP_GENOME + } +#endif + } #ifdef _WIN32 _setmode(_fileno(stdin), _O_BINARY); _setmode(_fileno(stdout), _O_BINARY); @@ -5848,7 +6226,7 @@ int main(int argc, char **argv) syncicache(code, cp); S[0] = (long)m; S[1] = DS0; S[2] = RS0; S[3] = (long)tab; S[4] = (long)cov; S[5] = (long)Q; S[6] = (long)G; S[7] = (long)&J; S[8] = (long)hook; - S[9] = (long)inst_hook; S[10] = (long)xrun; + S[9] = (long)inst_hook; S[10] = (long)xrun; S[14] = (long)&context_default_stop; S[11] = (long)(m + DSLIM); S[12] = (long)(m + RSLIM); cov[SYSGATE] = 1; enter.u = x_enter; diff --git a/src/core/core.zc b/src/core/core.zc new file mode 100644 index 0000000..61ecd1c --- /dev/null +++ b/src/core/core.zc @@ -0,0 +1,19 @@ +namespace core; +include rt; + +// calls the host gate with an operation and three machine-word arguments. +// returns the host result; negative values indicate a failed operation. +int syscall(int operation, word a, word b, word c) { + return rt.syscall(operation, a, b, c); +} + +// the current operand-stack pointer, expressed as a word address. +word dsp() { return rt.dsp(); } +// converts a vm word address to its byte address. +word addr_b(word address) { return rt.addr_b(address); } +// loads one unsigned byte from a byte address. +int ld8(word address) { return rt.ld8(address); } +// stores the low byte of value at a byte address. +int st8(word address, int value) { return rt.st8(address, value); } +// stops execution of the current vm program. +int halt() { return rt.halt(); } diff --git a/src/core/io_linux.c b/src/core/io_linux.c index 8a024aa..aaf6a18 100644 --- a/src/core/io_linux.c +++ b/src/core/io_linux.c @@ -1,4 +1,5 @@ #include +#include #ifdef _WIN32 #include #include @@ -7,7 +8,12 @@ #include #endif -int J; +#if !defined(_WIN32) && !defined(__wasm__) +#define VM_TLS _Thread_local +#else +#define VM_TLS +#endif +VM_TLS int J; #ifdef _WIN32 static int pk(void){int c=getchar();ungetc(c,stdin);return c==EOF;} @@ -19,8 +25,8 @@ char Q(void){int c=getchar();J=pk();return c;} one would wait for a message that comes only after the answer. so J is 1 only when the end is known, and 0 when more may come. the output is flushed before any read that can wait, for the same reason. */ -static unsigned char ib[65536]; -static int ip, in, ieof; +static VM_TLS unsigned char ib[65536]; +static VM_TLS int ip, in, ieof; /* make a byte available. wait is 0 to look without waiting. returns 1 when one is ready */ static int fill(int wait) { @@ -46,6 +52,27 @@ char Q(void) return (char)c; } #endif +/* 1 when a byte can be read without waiting: buffered, or already on the pipe */ +int input_pending(void) +{ +#ifdef _WIN32 + return 0; +#else + return fill(0); +#endif +} +#ifndef _WIN32 +struct IoSnapshot { unsigned char bytes[65536]; int position, count, ended, flag; }; +size_t io_context_size(void) { return sizeof(struct IoSnapshot); } +void io_context_snapshot(void *destination) { + struct IoSnapshot *snapshot = destination; + memcpy(snapshot->bytes, ib, sizeof ib); snapshot->position = ip; snapshot->count = in; snapshot->ended = ieof; snapshot->flag = J; +} +void io_context_restore(const void *source) { + const struct IoSnapshot *snapshot = source; + memcpy(ib, snapshot->bytes, sizeof ib); ip = snapshot->position; in = snapshot->count; ieof = snapshot->ended; J = snapshot->flag; +} +#endif void G(char c){putchar(c);} static void init_io(void) { diff --git a/src/core/io_win.c b/src/core/io_win.c index e197353..073022e 100644 --- a/src/core/io_win.c +++ b/src/core/io_win.c @@ -6,12 +6,14 @@ int J; static int pk(void) { int c = _fgetc_nolock(stdin); if (c != EOF) _ungetc_nolock(c, stdin); return c == EOF; } char Q(void) { int c = _fgetc_nolock(stdin); J = pk(); return (char)c; } +int input_pending(void) { return 0; } void G(char c) { _fputc_nolock(c, stdout); } static void init_io(void) { _setmode(_fileno(stdin), _O_BINARY); _setmode(_fileno(stdout), _O_BINARY); - J = pk(); + /* the first read establishes eof; startup must not read stdin. */ + J = 0; } #if defined(__GNUC__) || defined(__clang__) diff --git a/src/core/memmap.h b/src/core/memmap.h index b2ee787..cc768d4 100644 --- a/src/core/memmap.h +++ b/src/core/memmap.h @@ -1,29 +1,10 @@ -/* machine map, in words. the default has the code area, the compiler tables, - the genome and the stacks first (PW words in all) and the heap last: it - starts at PW and grows at run time, up to MW words of memory. MW is the - host's memory size; the array behind it is untouched until the heap grows - into it. a smaller board is a different build: src/tools/genmap writes a - directory whose memmap.h and genome agree, and the C cores are compiled - against that directory. do not edit the numbers by hand in one file only. - - include genome.h before this header when both are in the translation - unit. the linked image's GORG then wins; it has to be the same value - this header would have defined. - - PW is the first word past the initial stacks. emitted code can add heap - segments, and native code-address tables grow with those reservations. - MW bounds data addresses. a fixed map (HEAP_GROW 0) has MW == PW. - - DSLIM and RSLIM are the interpreter's stack checks. a straight-line - block can push about 1024 words after the check, which is why the data - limit sits 1100 words above DSLO. the genome's prologue checks the current frame segment and adds another - segment when needed. */ +/* generated by src/tools/genmap for the default profile. do not edit. */ #ifndef MEMMAP_H #define MEMMAP_H -#define PW 0x1C5000 +#define PW 0x1D5000 #define CODE_END 0x80000 -#define HEAP_LO 0x1C5000 -#define HEAP_HI 0x225000 +#define HEAP_LO 0x1D5000 +#define HEAP_HI 0x235000 #define HEAP_GROW 1 #ifndef MW #define MW 0x8000000 @@ -39,13 +20,13 @@ #else #define GORG 0x114100 #endif -#define FBOT 0x164100 -#define FTOP 0x1A4100 -#define RS0 0x1A4100 -#define MARK0 0x1B4100 +#define FBOT 0x174100 +#define FTOP 0x1B4100 +#define RS0 0x1B4100 +#define MARK0 0x1C4100 #define MARKN 0x8000 -#define DSLO 0x1BC100 -#define DSHI 0x1C4FF0 +#define DSLO 0x1CC100 +#define DSHI 0x1D4FF0 #define DS0 DSHI #define SYSGATE (ZFP + 2) #define DSLIM (DSLO + 1100) diff --git a/src/core/stack.h b/src/core/stack.h index 49f347c..f699b52 100644 --- a/src/core/stack.h +++ b/src/core/stack.h @@ -4,8 +4,11 @@ stack globals once the heap is ready. addresses are vm word indices. */ #ifndef TINYVM_STACK_H #define TINYVM_STACK_H +#ifndef VM_TLS +#define VM_TLS +#endif -static Cell stack_control; +static VM_TLS Cell stack_control; enum { SC_FRAME_FLOOR, SC_FRAME_TOP, SC_FRAME_GUARD, SC_DATA_LOW, SC_DATA_TOP, SC_DATA_NODE, -- cgit v1.2.3