aboutsummaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
authorGravatar Moon Flower <moonflower@elhenney.local> 2026-10-14 08:29:46 +0330
committerGravatar Moon Flower <moonflower@elhenney.local> 2026-10-14 08:29:46 +0330
commit1c53304d03414890af0da7432d1aea87e754a14a (patch)
tree97190d86cf548c9bc63f68c86a4d2a4f09809713 /src/core
parenta (diff)
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<I,O>, 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
Diffstat (limited to 'src/core')
-rw-r--r--src/core/core.c606
-rw-r--r--src/core/core.zc19
-rw-r--r--src/core/io_linux.c33
-rw-r--r--src/core/io_win.c4
-rw-r--r--src/core/memmap.h39
-rw-r--r--src/core/stack.h5
6 files changed, 558 insertions, 148 deletions
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 <unistd.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <poll.h>
+#include <signal.h>
#ifndef O_BINARY
#define O_BINARY 0
#endif
#endif
#include <time.h>
+#if !defined(_WIN32) && !defined(__wasm__)
+#include <pthread.h>
+#include <stdatomic.h>
+#define VM_TLS _Thread_local
+#else
+#define VM_TLS
+#endif
#if (defined(__linux__) || defined(__APPLE__)) && !defined(__wasm__) && !defined(ZY_NOFFI)
#define ZYFFI 1
#include <dlfcn.h>
@@ -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 <eval>, 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 <llvm-c/Target.h>
#include <llvm-c/Transforms/PassBuilder.h>
#include <llvm-c/TargetMachine.h>
-static int HOT = 5000; /* runs before a block gets compiled: ZYHOT */
-static const char *lpasses = "default<O2>"; /* ZYPASSES */
+static VM_TLS int HOT = 5000; /* runs before a block gets compiled: ZYHOT */
+static VM_TLS const char *lpasses = "default<O2>"; /* 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 <eval>.\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 <stdio.h>
+#include <string.h>
#ifdef _WIN32
#include <fcntl.h>
#include <io.h>
@@ -7,7 +8,12 @@
#include <poll.h>
#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,