diff options
| author | 2026-10-15 17:03:03 +0330 | |
|---|---|---|
| committer | 2026-10-15 17:03:03 +0330 | |
| commit | 7cb983bbea98d2038b2a8015a6d1f1afd40991c9 (patch) | |
| tree | ec3d88a15801207fc6d8d3c6d142617ffec85460 /src/core | |
| parent | merge work/borrow: borrow returns before definition, borrows clause, borrow m... (diff) | |
| parent | bench: rss probe path fix (diff) | |
merge work/actors: wasm seed bootstrap (seed/MANIFEST), shared copy-on-write actor memory, top-level hold blocks on all backends and lsp
Diffstat (limited to 'src/core')
| -rw-r--r-- | src/core/core.c | 250 | ||||
| -rw-r--r-- | src/core/hold.h | 116 | ||||
| -rw-r--r-- | src/core/io_linux.c | 21 |
3 files changed, 344 insertions, 43 deletions
diff --git a/src/core/core.c b/src/core/core.c index ce9e5ca..7d1b91e 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -74,6 +74,10 @@ #include "memmap.h" #include "fops.h" #include "bops.h" +#if !defined(_WIN32) && !defined(__wasm__) +#define HOLD_THREADS +#endif +#include "hold.h" #ifndef JTYPE #define JTYPE char @@ -256,6 +260,7 @@ 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); +void io_context_release(void); #else static VM_TLS int context_default_stop; #endif @@ -294,6 +299,12 @@ static Cell hostcall(Cell op, Cell a, Cell b, Cell c) #else return -1; #endif + case 57: { + Cell r = hold_enter(m, (uint64_t)MW, a, b); + if (r == -2) { fflush(stdout); fprintf(stderr, "zy: hold block %d was entered while block %d was held; hold blocks are entered in the order they are declared\n", (int)a, hold_top); exit(2); } + return r; + } + case 58: return hold_leave(m, (uint64_t)MW, a, b); case 56: return cb_r; /* the return stack pointer as of the gate store: the word after the top return address */ case 1: if (zcstr(a, path, 1024) < 0) return -1; @@ -622,11 +633,44 @@ typedef struct { Uop *u; signed char *reg; /* physical register per value, for the jit; 0 if it didn't fit */ } Enh; -static VM_TLS Enh enh[NENH]; +/* large per-thread tables live behind a pointer and are mapped on first use, so a + worker thread that never compiles anything pays nothing for them (as static tls + they were zeroed into every thread's stack mapping: about 13 MB per actor) */ +static VM_TLS void **tls_big_slots[48]; static VM_TLS size_t tls_big_sizes[48]; static VM_TLS int tls_big_n; +static void *tls_big(size_t bytes, void **slot) +{ + void *p; +#if !defined(_WIN32) && !defined(__wasm__) + p = mmap(0, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); if (p == MAP_FAILED) p = 0; +#else + p = calloc(1, bytes); +#endif + if (!p) { fprintf(stderr, "zy: out of memory for compiler tables\n"); exit(2); } + if (tls_big_n < 48) { tls_big_slots[tls_big_n] = slot; tls_big_sizes[tls_big_n++] = bytes; } + *slot = p; return p; +} +static void tls_big_free(void) +{ + while (tls_big_n > 0) { + tls_big_n--; +#if !defined(_WIN32) && !defined(__wasm__) + munmap(*tls_big_slots[tls_big_n], tls_big_sizes[tls_big_n]); +#else + free(*tls_big_slots[tls_big_n]); +#endif + *tls_big_slots[tls_big_n] = 0; + } +} +#define TLS_BIG(p) (*((p) ? (p) : (__typeof__(p))tls_big(sizeof *(p), (void **)&(p)))) +static VM_TLS Enh (*enh_tls)[NENH]; +#define enh TLS_BIG(enh_tls) + static VM_TLS int need_flush; #define MAXUOP 4096 -static VM_TLS Uop ub[MAXUOP]; static VM_TLS int un, unv, uerr; /* the program being built */ +static VM_TLS Uop (*ub_tls)[MAXUOP]; +#define ub TLS_BIG(ub_tls) +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; @@ -838,7 +882,9 @@ static void umagic(UCell d, UCell *mul, int *add, int *sh) /* hacker's delight, *mul = q2 + 1; *sh = p - 64; } -static VM_TLS Uop ob[MAXUOP]; static VM_TLS int on, onv; +static VM_TLS Uop (*ob_tls)[MAXUOP]; +#define ob TLS_BIG(ob_tls) +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)); } @@ -939,8 +985,8 @@ 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_; + static VM_TLS int (*omap_)[MAXUOP + 16], (*isk_)[MAXUOP * 4]; static VM_TLS Cell (*kv_)[MAXUOP * 4]; + omap = TLS_BIG(omap_); isk = TLS_BIG(isk_); kv = TLS_BIG(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) @@ -1121,11 +1167,19 @@ 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 VM_TLS Rg *rgs[MAXRG]; +static VM_TLS Rg * (*rgs_tls)[MAXRG]; +#define rgs TLS_BIG(rgs_tls) + 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 (*fn_pc_tls)[MAXRG]; +#define fn_pc TLS_BIG(fn_pc_tls) + +static VM_TLS Rg * (*fn_rg__tls)[MAXRG]; +#define fn_rg_ TLS_BIG(fn_rg__tls) + +static VM_TLS Rg * (*fn_cache_tls)[1024]; +#define fn_cache TLS_BIG(fn_cache_tls) + static VM_TLS int nfn; /* ---- bookkeeping for translated code, shared by the backends ---- @@ -1142,12 +1196,18 @@ static VM_TLS int nfn; #define NPG ((code_words >> PGSH) + 1) #define MAXPGN (1 << 19) #define MAXDEP (1 << 18) -static VM_TLS struct { int a, b; } ix_blk[MAXB]; +static VM_TLS struct { int a, b; } (*ix_blk_tls)[MAXB]; +#define ix_blk TLS_BIG(ix_blk_tls) + 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 struct { int blk, next; } (*pg_node_tls)[MAXPGN]; +#define pg_node TLS_BIG(pg_node_tls) + 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 struct { int blk, next; } (*dep_node_tls)[MAXDEP]; +#define dep_node TLS_BIG(dep_node_tls) + 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 */ @@ -1770,7 +1830,9 @@ 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 VM_TLS Ncd ncd[256]; +static VM_TLS Ncd (*ncd_tls)[256]; +#define ncd TLS_BIG(ncd_tls) + static VM_TLS int nncd; static int ncd_get(Cell blk) { @@ -1878,8 +1940,12 @@ static VM_TLS int lv_[RMAXV], lw_[RMAXV]; #define SB 1100 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 VM_TLS int (*afp_tls)[MAXUOP * 4 + 16]; +#define afp TLS_BIG(afp_tls) +/* value -> 1 + its offset from fp, if it is fp + k */ +static VM_TLS int (*emap_tls)[MAXUOP * 4 + 16]; +#define emap TLS_BIG(emap_tls) + static int rvar(int v) { @@ -2360,7 +2426,13 @@ 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 VM_TLS int g_isk[MAXUOP * 4]; static VM_TLS Cell g_kv[MAXUOP * 4]; static VM_TLS int g_omap[MAXUOP + 16]; +static VM_TLS int (*g_isk_tls)[MAXUOP * 4]; +#define g_isk TLS_BIG(g_isk_tls) +static VM_TLS Cell (*g_kv_tls)[MAXUOP * 4]; +#define g_kv TLS_BIG(g_kv_tls) +static VM_TLS int (*g_omap_tls)[MAXUOP + 16]; +#define g_omap TLS_BIG(g_omap_tls) + static int cjoin(CIn *d, const CIn *s) { int j, ch = 0; @@ -3313,7 +3385,9 @@ static VM_TLS void **tab; /* vm address -> translation */ typedef struct { void *stub; int fslots; int argc; } FnNat; static VM_TLS FnNat *fn_nat; #define LINKN 1024 -static VM_TLS void *linkstk[LINKN]; +static VM_TLS void * (*linkstk_tls)[LINKN]; +#define linkstk TLS_BIG(linkstk_tls) + static VM_TLS Cell jit_depth; #endif 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); } @@ -3963,7 +4037,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 VM_TLS int mp[MAXUOP * 4 + 16]; + static VM_TLS int (*mp_tls)[MAXUOP * 4 + 16]; int *mp = TLS_BIG(mp_tls); if (e->nv > MAXUOP * 4) return -1; un = 0; unv = nin; uerr = 0; for (j = 0; j < nin; j++) mp[j] = j; @@ -5237,9 +5311,13 @@ static Blk lcompile(int p) #if !defined(ZYLLVM) && !defined(ZYJIT_X64) static VM_TLS unsigned char *rcov; /* 1 on the words of a lifted region */ #endif -static VM_TLS Cell rvals[MAXUOP * 4 + 16]; +static VM_TLS Cell (*rvals_tls)[MAXUOP * 4 + 16]; +#define rvals TLS_BIG(rvals_tls) + #define RPOOL 40 -static VM_TLS Cell rpool[RPOOL][MAXUOP * 4 + 16]; +static VM_TLS Cell (*rpool_tls)[RPOOL][MAXUOP * 4 + 16]; +#define rpool TLS_BIG(rpool_tls) + static VM_TLS int rpool_n; static int rdrop(int a) /* a store landed on region code: drop those regions */ { @@ -6090,7 +6168,11 @@ static void float_op(int i, int k) static const int xreg[] = { r_si, r_di, r_8, r_9, r_10, r_11, r_bp }; #define XNREG 7 #define XMAXOP 400 -static VM_TLS int xkc[MAXUOP * 4 + 16]; static VM_TLS Cell xkv[MAXUOP * 4 + 16]; +static VM_TLS int (*xkc_tls)[MAXUOP * 4 + 16]; +#define xkc TLS_BIG(xkc_tls) +static VM_TLS Cell (*xkv_tls)[MAXUOP * 4 + 16]; +#define xkv TLS_BIG(xkv_tls) + static VM_TLS signed char *xr; static int x64_op_ok(int op) { @@ -6110,7 +6192,7 @@ static int x64_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 VM_TLS int mp[MAXUOP * 4 + 16]; + static VM_TLS int (*mp_tls)[MAXUOP * 4 + 16]; int *mp = TLS_BIG(mp_tls); if (e->nv > MAXUOP * 4 || e->n > XMAXOP) return -1; for (i = 0; i < e->n; i++) if (!x64_op_ok(e->u[i].op)) return -1; un = 0; unv = nin; uerr = 0; @@ -6131,7 +6213,9 @@ static int x64_spec_enh(int s, Cell *ks, int nk, Enh *te) return 0; } /* the register value v lives in, or scratch with the constant v loaded into it */ -static VM_TLS int xvh[MAXUOP * 4 + 16]; /* regions: 1 + the var a value reads at block entry, 0 if none */ +static VM_TLS int (*xvh_tls)[MAXUOP * 4 + 16]; +#define xvh TLS_BIG(xvh_tls) +/* regions: 1 + the var a value reads at block entry, 0 if none */ static VM_TLS Rg *xR; static VM_TLS Cell xsyn[RMAXV]; /* the home of a var the lifter made (a hoisted value), which has no frame slot */ static int xv(int v, int scratch) @@ -6250,7 +6334,9 @@ static void x64_enh(Enh *e) under ZYO3INTERP=1. */ static VM_TLS int x64_o3interp; static void perfmap_note(JW *start, JW *end, const char *kind, int a, int b); -static VM_TLS signed char rxr[MAXUOP * 4 + 16]; +static VM_TLS signed char (*rxr_tls)[MAXUOP * 4 + 16]; +#define rxr TLS_BIG(rxr_tls) + #define RXNREG 6 static void rsave(void) { int j; for (j = 0; j < RXNREG; j++) push_r(xreg[j]); } static void rrest(void) { int j; for (j = RXNREG - 1; j >= 0; j--) pop_r(xreg[j]); } @@ -6258,7 +6344,7 @@ static Cell xev(long op, Cell a, Cell b, Cell c) { return ev((int)op, a, b, c); /* a value's last use, registers on the six, -1 if the block doesn't fit or can't be compiled */ static int x64_ralloc(RB *B) { - static VM_TLS int last[MAXUOP * 4 + 16]; + static VM_TLS int (*last_tls)[MAXUOP * 4 + 16]; int *last = TLS_BIG(last_tls); int i, j, k, na, fr = (1 << RXNREG) - 1, n = B->n; if (B->nv > MAXUOP * 4) return -1; for (i = 0; i < B->nv; i++) { last[i] = -1; xkc[i] = 0; xvh[i] = 0; rxr[i] = -1; } @@ -7363,6 +7449,85 @@ static int code_range_live(Cell start, Cell count) /* each worker owns a host thread, vm memory and translation state. */ #if !defined(_WIN32) && !defined(__wasm__) +#include <sys/mman.h> +#ifdef __linux__ +#include <sys/syscall.h> +#endif +/* vm memory for a worker is copy-on-write against a snapshot of its parent. + the first spawn writes the parent's live words [0, memory_words) to an anonymous + shared file and maps the parent's memory privately over it at the same address, + so the parent reads the same pages and its later writes stay its own. a child maps + the same file privately, so code, genome and everything the parent has not + touched since are one set of physical pages for all of them, and only the pages + the parent changed since the snapshot are copied in. the file never changes, so a + child never sees a later write of its parent. a parent whose memory has drifted + far from its snapshot takes a fresh one. without a file (or on failure) a spawn + falls back to a private copy. */ +typedef struct { int fd; Cell *view; size_t words; } MemSnap; +static VM_TLS MemSnap mem_snap = { -1, 0, 0 }; +static VM_TLS int memory_mapped; /* m came from vm_memory_map */ +#define SNAP_PAGE 512 /* words per 4 KB page */ +static Cell *vm_memory_map(void) +{ + void *p = mmap(0, (size_t)MW * sizeof(Cell), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); + return p == MAP_FAILED ? 0 : p; +} +static void vm_memory_unmap(Cell *p) { if (p) munmap(p, (size_t)MW * sizeof(Cell)); } +static int snap_file(size_t bytes) +{ + int fd = -1; +#ifdef __linux__ +#ifdef SYS_memfd_create + fd = (int)syscall(SYS_memfd_create, "zy-snapshot", 1u /* MFD_CLOEXEC */); +#endif +#else + char name[64]; snprintf(name, sizeof name, "/zy-%d-%p", (int)getpid(), (void *)&name); + fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600); if (fd >= 0) shm_unlink(name); +#endif + if (fd < 0) return -1; + if (ftruncate(fd, (off_t)bytes) < 0) { close(fd); return -1; } + return fd; +} +static void snap_drop(void) +{ + if (mem_snap.view) munmap(mem_snap.view, mem_snap.words * sizeof(Cell)); + if (mem_snap.fd >= 0) close(mem_snap.fd); + mem_snap.fd = -1; mem_snap.view = 0; mem_snap.words = 0; +} +/* pages of [0, words) where m differs from the snapshot */ +static size_t snap_dirty(size_t words) +{ + size_t p, n = 0, lim = words < mem_snap.words ? words : mem_snap.words; + for (p = 0; p < lim; p += SNAP_PAGE) if (memcmp(m + p, mem_snap.view + p, SNAP_PAGE * sizeof(Cell))) n++; + return n + (words > mem_snap.words ? (words - mem_snap.words) / SNAP_PAGE : 0); +} +static int snap_take(void) +{ + size_t words = ((size_t)memory_words + SNAP_PAGE - 1) / SNAP_PAGE * SNAP_PAGE, bytes = words * sizeof(Cell), done = 0; + int fd; void *view, *over; + if (words > (size_t)MW) return -1; + fd = snap_file(bytes); if (fd < 0) return -1; + while (done < bytes) { ssize_t k = pwrite(fd, (char *)m + done, bytes - done, (off_t)done); if (k <= 0) { close(fd); return -1; } done += (size_t)k; } + view = mmap(0, bytes, PROT_READ, MAP_SHARED, fd, 0); + if (view == MAP_FAILED) { close(fd); return -1; } + over = mmap(m, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, fd, 0); + if (over == MAP_FAILED) { munmap(view, bytes); close(fd); return -1; } /* m is untouched when the map fails */ + snap_drop(); mem_snap.fd = fd; mem_snap.view = view; mem_snap.words = words; return 0; +} +/* a child's memory: the snapshot, plus the pages the parent changed since */ +static Cell *snap_clone(void) +{ + size_t p, words = (size_t)memory_words; + Cell *c; + if (mem_snap.fd < 0 || snap_dirty(words) * 4 > mem_snap.words / SNAP_PAGE) { if (snap_take() < 0) return 0; } + c = vm_memory_map(); if (!c) return 0; + if (mmap(c, mem_snap.words * sizeof(Cell), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, mem_snap.fd, 0) == MAP_FAILED) { vm_memory_unmap(c); return 0; } + for (p = 0; p < words; p += SNAP_PAGE) { + size_t k = words - p < SNAP_PAGE ? words - p : SNAP_PAGE; + if (p >= mem_snap.words || memcmp(m + p, mem_snap.view + p, k * sizeof(Cell))) memcpy(c + p, m + p, k * sizeof(Cell)); + } + return c; +} typedef struct ContextChannel { Cell handle; int fd; struct ContextChannel *next; } ContextChannel; static VM_TLS ContextChannel *context_channels; static atomic_llong context_id_sequence; @@ -7374,6 +7539,7 @@ struct VmContext { Cell token, entry, sp, rp, stack, words, limit, growth, code_size; Cell frame_floor, frame_top, frame_guard; Cell *memory; + int memory_mapped; int exit_code, requested_exit; void *input_snapshot; int argc, envc; @@ -7387,6 +7553,7 @@ struct VmContext { int region_count; unsigned short *entries; unsigned char *entry_indices; + int *entry_at, entry_count; }; static int context_descriptor(Cell handle) { ContextChannel *channel; @@ -7405,7 +7572,7 @@ 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; + free(context->input_snapshot); free(context->entries); free(context->entry_indices); free(context->entry_at); if (context->memory) { if (context->memory_mapped) vm_memory_unmap(context->memory); else 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); } @@ -7425,7 +7592,7 @@ static void context_engine_free(void) { free(lcov); free(lt); free(hot); #endif #endif - free(m); m = 0; atomic_fetch_sub(&context_memory_live, 1); + if (memory_mapped) vm_memory_unmap(m); else free(m); m = 0; snap_drop(); tls_big_free(); io_context_release(); atomic_fetch_sub(&context_memory_live, 1); } static void *context_worker(void *opaque) { VmContext *context = opaque; @@ -7434,7 +7601,7 @@ static void *context_worker(void *opaque) { 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; + m = context->memory; context->memory = 0; memory_mapped = context->memory_mapped; 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; @@ -7463,8 +7630,8 @@ static void *context_worker(void *opaque) { 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; + for (i = 0; i < context->entry_count; i++) { o3ent[context->entry_at[i]] = context->entries[i]; o3ei[context->entry_at[i]] = context->entry_indices[i]; } + free(context->entries); context->entries = 0; free(context->entry_indices); context->entry_indices = 0; free(context->entry_at); context->entry_at = 0; #ifdef ZYJIT_X64 if (x64_boot(sp, rp, &context->stop)) { context->exit_code = -1; context_channel_close(context->worker_channel.handle); context_engine_free(); return 0; } atomic_fetch_add(&context_code_live, 1); @@ -7489,7 +7656,7 @@ static void *context_worker(void *opaque) { 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; + hold_release_all(); 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; @@ -7506,12 +7673,17 @@ static Cell context_start(Cell entry, Cell channel) { 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)); + context->memory = snap_clone(); context->memory_mapped = context->memory != 0; + if (!context->memory) 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); + /* o3 entry points are few and the tables mostly zero: hand over only the set ones */ + { Cell k; context->entry_count = 0; for (k = 0; k < code_words; k++) if (o3ent[k]) context->entry_count++; } + context->entry_at = malloc((size_t)(context->entry_count + 1) * sizeof *context->entry_at); + context->entries = malloc((size_t)(context->entry_count + 1) * sizeof *o3ent); context->entry_indices = malloc((size_t)(context->entry_count + 1)); + if (!context->memory || !context->entries || !context->entry_indices || !context->entry_at) goto failed; + if (context->memory_mapped) context->memory[channel] = context->worker_channel.handle; + else { saved = m[channel]; m[channel] = context->worker_channel.handle; memcpy(context->memory, m, (size_t)memory_words * sizeof(Cell)); m[channel] = saved; } + { Cell k; int j = 0; for (k = 0; k < code_words; k++) if (o3ent[k]) { context->entry_at[j] = (int)k; context->entries[j] = o3ent[k]; context->entry_indices[j] = o3ei[k]; j++; } } 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)); } @@ -7526,7 +7698,7 @@ static Cell context_start(Cell entry, Cell channel) { 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); + pthread_attr_init(&attributes); pthread_attr_setstacksize(&attributes, 4u << 20); /* the vm keeps its own stacks in vm memory; 4 MB of address space covers the deepest native recursion (jit compile, o3) */ 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; @@ -7603,6 +7775,10 @@ int main(int argc, char **argv) memory_words = memory_option("TINYVM_MEMORY_WORDS", HEAP_HI > PW ? HEAP_HI : PW, HEAP_HI > PW ? HEAP_HI : PW); if (memory_words < 0) return 1; if (memory_words > memory_limit) { fprintf(stderr, "zy: initial memory exceeds its limit\n"); return 1; } +#if !defined(_WIN32) && !defined(__wasm__) + m = vm_memory_map(); memory_mapped = m != 0; + if (!m) +#endif m = calloc((size_t)MW, sizeof *m); #ifdef ZYNATIVE cov = calloc((size_t)MW, 1); diff --git a/src/core/hold.h b/src/core/hold.h new file mode 100644 index 0000000..d4ab1f0 --- /dev/null +++ b/src/core/hold.h @@ -0,0 +1,116 @@ +/* hold blocks (spec 4.16): the host side of a monitor. a function of a hold block + stores op 57 (enter) at its start and op 58 (leave) at its exit, with the block's + rank and the address of its descriptor word. the descriptor points at a table in vm + memory: n, then n pairs (address, words) of the block's shared variables. + + the shared values live here, outside every vm, so all actors (each with its own + memory) see one copy. entering a block from outside takes its lock and copies the + values into the vm's variables; leaving copies them back and unlocks. inside, they + are plain globals. the lock is per block and reentrant per thread (a nested call + only counts), and blocks are taken in rank (declaration) order: entering a block + while holding a later one is an error, which rules out lock-order deadlocks between + blocks. a worker that ends while holding blocks releases them without writing back. + + included by src/core/core.c and src/micro/micro.c; HOLD_THREADS selects pthreads, + otherwise a single machine needs no locks (wasm, bare metal). no malloc. */ +#ifndef HOLD_H +#define HOLD_H +#define HOLD_BLOCKS 256 +#define HOLD_CELLS 65536 +#ifdef HOLD_THREADS +#include <pthread.h> +static pthread_mutex_t hold_lock[HOLD_BLOCKS], hold_meta = PTHREAD_MUTEX_INITIALIZER; +static pthread_once_t hold_once = PTHREAD_ONCE_INIT; +static void hold_init(void) { int i; for (i = 0; i < HOLD_BLOCKS; i++) pthread_mutex_init(&hold_lock[i], 0); } +#define HOLD_TLS _Thread_local +#else +#define HOLD_TLS +#endif +static int64_t hold_cells[HOLD_CELLS]; +static int hold_base[HOLD_BLOCKS], hold_size[HOLD_BLOCKS], hold_ready[HOLD_BLOCKS], hold_next; +static HOLD_TLS int hold_depth[HOLD_BLOCKS], hold_top; /* this thread: nesting per block, highest rank held */ +static HOLD_TLS unsigned char hold_order[HOLD_BLOCKS]; /* ranks held, in the order taken */ +static HOLD_TLS int hold_n; + +/* the table's entries, checked against the machine. -1 when it is malformed */ +static int64_t hold_table(int64_t *mem, uint64_t words, int64_t desc, int64_t *n) +{ + int64_t t, i; + if (desc <= 0 || (uint64_t)desc >= words) return -1; + t = mem[desc]; + if (t <= 0 || (uint64_t)t >= words) return -1; + *n = mem[t]; + if (*n < 0 || *n > 64 || (uint64_t)(t + 1 + 2 * *n) > words) return -1; + for (i = 0; i < *n; i++) { + int64_t a = mem[t + 1 + 2 * i], w = mem[t + 2 + 2 * i]; + if (a <= 0 || w <= 0 || (uint64_t)(a + w) > words) return -1; + } + return t; +} +static void hold_copy(int64_t *mem, int64_t t, int64_t n, int rank, int in) +{ + int64_t i, k, c = hold_base[rank]; + for (i = 0; i < n; i++) { + int64_t a = mem[t + 1 + 2 * i], w = mem[t + 2 + 2 * i]; + for (k = 0; k < w; k++, c++) { if (in) mem[a + k] = hold_cells[c]; else hold_cells[c] = mem[a + k]; } + } +} +/* op 57: 0, or -1 (bad block), or -2 (taken out of rank order; the caller stops) */ +static int64_t hold_enter(int64_t *mem, uint64_t words, int64_t rank, int64_t desc) +{ + int64_t t, n, i, total = 0; + if (rank <= 0 || rank >= HOLD_BLOCKS) return -1; + if (hold_depth[rank]) { hold_depth[rank]++; return 0; } + if (hold_top > rank) return -2; + t = hold_table(mem, words, desc, &n); if (t < 0) return -1; + for (i = 0; i < n; i++) total += mem[t + 2 + 2 * i]; +#ifdef HOLD_THREADS + pthread_once(&hold_once, hold_init); + pthread_mutex_lock(&hold_lock[rank]); + pthread_mutex_lock(&hold_meta); +#endif + if (!hold_ready[rank]) { /* the first entry anywhere: the image's initial values become the shared ones */ + if (hold_next + total > HOLD_CELLS) { +#ifdef HOLD_THREADS + pthread_mutex_unlock(&hold_meta); pthread_mutex_unlock(&hold_lock[rank]); +#endif + return -1; + } + hold_base[rank] = hold_next; hold_size[rank] = (int)total; hold_next += (int)total; hold_ready[rank] = 1; + hold_copy(mem, t, n, (int)rank, 0); + } +#ifdef HOLD_THREADS + pthread_mutex_unlock(&hold_meta); +#endif + hold_copy(mem, t, n, (int)rank, 1); + hold_depth[rank] = 1; hold_order[hold_n++] = (unsigned char)rank; hold_top = (int)rank; + return 0; +} +/* op 58 */ +static int64_t hold_leave(int64_t *mem, uint64_t words, int64_t rank, int64_t desc) +{ + int64_t t, n; + if (rank <= 0 || rank >= HOLD_BLOCKS || !hold_depth[rank]) return -1; + if (--hold_depth[rank]) return 0; + t = hold_table(mem, words, desc, &n); + if (t >= 0) hold_copy(mem, t, n, (int)rank, 0); + if (hold_n > 0 && hold_order[hold_n - 1] == rank) hold_n--; + hold_top = hold_n ? hold_order[hold_n - 1] : 0; +#ifdef HOLD_THREADS + pthread_mutex_unlock(&hold_lock[rank]); +#endif + return 0; +} +/* a machine that ends while inside blocks gives them up, without writing back */ +static void hold_release_all(void) +{ + while (hold_n > 0) { + int r = hold_order[--hold_n]; + hold_depth[r] = 0; +#ifdef HOLD_THREADS + pthread_mutex_unlock(&hold_lock[r]); +#endif + } + hold_top = 0; +} +#endif diff --git a/src/core/io_linux.c b/src/core/io_linux.c index aaf6a18..c3e05dd 100644 --- a/src/core/io_linux.c +++ b/src/core/io_linux.c @@ -1,3 +1,4 @@ +#include <stdlib.h> #include <stdio.h> #include <string.h> #ifdef _WIN32 @@ -25,7 +26,11 @@ 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 VM_TLS unsigned char ib[65536]; +#define IBN 65536 +/* each vm thread's buffer is allocated when it first reads, so an actor that never + reads standard input carries none */ +static VM_TLS unsigned char *ibp; +#define ib (ibp ? ibp : (ibp = malloc(IBN)) ? ibp : (fputs("zy: out of memory\n", stderr), exit(2), ibp)) 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) @@ -37,7 +42,7 @@ static int fill(int wait) struct pollfd f; f.fd = 0; f.events = POLLIN; f.revents = 0; if (poll(&f, 1, 0) <= 0) return 0; /* nothing yet: not the end, not a byte */ } else fflush(stdout); - n = (int)read(0, ib, sizeof ib); + n = (int)read(0, ib, IBN); if (n <= 0) { ieof = 1; return 0; } ip = 0; in = n; return 1; @@ -62,16 +67,20 @@ int input_pending(void) #endif } #ifndef _WIN32 -struct IoSnapshot { unsigned char bytes[65536]; int position, count, ended, flag; }; -size_t io_context_size(void) { return sizeof(struct IoSnapshot); } +/* what a new worker inherits: the bytes read ahead and not consumed yet, and the flags */ +struct IoSnapshot { int position, count, ended, flag; unsigned char bytes[]; }; +size_t io_context_size(void) { return sizeof(struct IoSnapshot) + (size_t)(ip < in ? in - ip : 0); } 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; + if (ip < in) memcpy(snapshot->bytes, ib + ip, (size_t)(in - ip)); + 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; + ip = snapshot->position; in = snapshot->count; ieof = snapshot->ended; J = snapshot->flag; + if (ip < in) memcpy(ib + ip, snapshot->bytes, (size_t)(in - ip)); } +void io_context_release(void) { free(ibp); ibp = 0; } #endif void G(char c){putchar(c);} |
