From a3b72f0c6d02f96606c200cde04735fd57151805 Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 14:10:03 +0330 Subject: boot: build the compiler from a pinned wasm seed (seed/zc.wasm, WASI) under wasmtime or node, proven by the fixpoint; seed/MANIFEST hashes; make seed; legacy c bootstrap kept as make genome-c --- src/micro/micro.c | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'src/micro/micro.c') diff --git a/src/micro/micro.c b/src/micro/micro.c index 23994dd..39a22b2 100644 --- a/src/micro/micro.c +++ b/src/micro/micro.c @@ -776,6 +776,13 @@ int micro_run(void) micro_msg("zy: genome does not fit\n"); return 1; } +#ifdef MICRO_PORT_IMAGE + /* the port may hand over an image of its own in place of the embedded one + (the wasm bootstrap seed does, to run a freshly built compiler) */ + i = micro_port_image(m, (UCell)GORG, (UCell)FBOT); + if (i < 0) { micro_msg("zy: bad image\n"); return 1; } + if (i == 0) +#endif for (i = 0; i < GLEN; i++) m[GORG + i] = genome_word(i); jknown = 0; return run(GORG); -- cgit v1.2.3 From 8c0c0b0dad6a7ddf2b3075db2285dce8130ef2d0 Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 14:25:01 +0330 Subject: actors: micro workers map a copy-on-write snapshot too; inherited input is only the unread bytes; worker stacks 4 MB --- src/core/core.c | 2 +- src/micro/micro.c | 32 ++++++++++++++--- src/micro/port_linux.c | 94 +++++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 113 insertions(+), 15 deletions(-) (limited to 'src/micro/micro.c') diff --git a/src/core/core.c b/src/core/core.c index 77c4a99..648f54a 100644 --- a/src/core/core.c +++ b/src/core/core.c @@ -7688,7 +7688,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; diff --git a/src/micro/micro.c b/src/micro/micro.c index 39a22b2..9924d5e 100644 --- a/src/micro/micro.c +++ b/src/micro/micro.c @@ -647,6 +647,11 @@ extern size_t micro_context_port_size(void); extern void micro_context_port_save(void *); extern void micro_context_port_restore(const void *); extern void micro_context_port_free(void); +#ifndef _WIN32 +extern Cell *micro_mem_clone(void); +extern void micro_mem_adopt(Cell *mem, UCell words); +extern void micro_mem_release(Cell *mem); +#endif #define CONTEXT_HANDLE_LO 0x40000000 typedef struct ContextChannel { Cell handle; int fd; struct ContextChannel *next; } ContextChannel; typedef struct VmContext { @@ -658,6 +663,7 @@ typedef struct VmContext { UCell words, limit, initial; int sp, rp, exit_code, requested_exit, enhancement_count, input_flag, input_known; Cell *memory; + int memory_mapped; void *port; Xop operations[XPOOL]; Enh enhancements[NENH]; @@ -683,15 +689,26 @@ static int context_close(Cell handle) { } static void context_snapshot_free(VmContext *context) { free(context->port); - if (context->memory) { free(context->memory); atomic_fetch_sub(&context_memory_live, 1); } + if (context->memory) { +#ifndef _WIN32 + if (context->memory_mapped) micro_mem_release(context->memory); else +#endif + free(context->memory); + atomic_fetch_sub(&context_memory_live, 1); + } } static void *context_worker(void *opaque) { VmContext *context = opaque; context_current = context; context->worker_channel.next = context_channels; context_channels = &context->worker_channel; micro_context_port_restore(context->port); free(context->port); context->port = 0; memory_limit = context->limit; memory_initial = context->initial; + if (context->memory_mapped) { +#ifndef _WIN32 + micro_mem_adopt(context->memory, context->words); context->memory = 0; +#endif + } if (!mem_grow_words(context->words)) { context->exit_code = -1; context_close(context->worker_channel.handle); return 0; } - memcpy(m, context->memory, (size_t)context->words * sizeof(Cell)); free(context->memory); context->memory = 0; + if (context->memory) { memcpy(m, context->memory, (size_t)context->words * sizeof(Cell)); free(context->memory); context->memory = 0; } stack_control = context->stack; jflag = context->input_flag; jknown = context->input_known; xuse = context->enhancement_count; memcpy(xops, context->operations, sizeof xops); memcpy(enh, context->enhancements, sizeof enh); int sp = context->sp, rp = context->rp; Cell entry = context->entry; @@ -714,12 +731,17 @@ static Cell context_start(Cell entry, Cell channel, int sp, int rp) { context->parent_channel.handle = CONTEXT_HANDLE_LO + atomic_fetch_add(&context_sequence, 1) + 1; context->worker_channel.handle = CONTEXT_HANDLE_LO + atomic_fetch_add(&context_sequence, 1) + 1; context->parent_channel.fd = fd[0]; context->worker_channel.fd = fd[1]; atomic_init(&context->stop, 0); - context->port = malloc(micro_context_port_size()); context->memory = malloc((size_t)mw * sizeof(Cell)); + context->port = malloc(micro_context_port_size()); +#ifndef _WIN32 + context->memory = micro_mem_clone(); context->memory_mapped = context->memory != 0; + if (!context->memory) +#endif + context->memory = malloc((size_t)mw * sizeof(Cell)); if (context->memory) atomic_fetch_add(&context_memory_live, 1); if (!context->port || !context->memory) goto failed; - micro_context_port_save(context->port); memcpy(context->memory, m, (size_t)mw * sizeof(Cell)); context->memory[channel] = context->worker_channel.handle; + micro_context_port_save(context->port); if (!context->memory_mapped) memcpy(context->memory, m, (size_t)mw * sizeof(Cell)); context->memory[channel] = context->worker_channel.handle; memcpy(context->operations, xops, sizeof xops); memcpy(context->enhancements, enh, sizeof enh); - pthread_attr_t attributes; pthread_attr_init(&attributes); pthread_attr_setstacksize(&attributes, 16u << 20); + pthread_attr_t attributes; 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) */ int result = pthread_create(&context->thread, &attributes, context_worker, context); pthread_attr_destroy(&attributes); if (result) goto failed; context->parent_channel.next = context_channels; context_channels = &context->parent_channel; diff --git a/src/micro/port_linux.c b/src/micro/port_linux.c index 6245f4f..a39e3e8 100644 --- a/src/micro/port_linux.c +++ b/src/micro/port_linux.c @@ -38,16 +38,38 @@ #define VM_TLS #endif -/* the machine's memory: a block that grows by realloc, zero in the new part. the - base can move on a grow, which micro.c takes into account (it re-reads m). */ +/* the machine's memory. with threads (actors) on a posix host it is one large + reservation that grows in place, so that a worker's memory can be a + copy-on-write map of a snapshot of its parent (micro_mem_clone); a machine that + outgrows the reservation moves to a block that grows by realloc. without threads it + is a block that grows by realloc, zero in the new part. the base can move on a + grow, which micro.c takes into account (it re-reads m). */ static VM_TLS Cell *lmem; static VM_TLS UCell lcap; +#if defined(ZY_THREADS) && !defined(_WIN32) +#include +#ifdef __linux__ +#include +#endif +#define RESW ((UCell)1 << 28) /* words reserved per machine: 2 GB of address space */ +static VM_TLS int lres; /* lmem is a reservation of RESW words */ +static Cell *res_map(void) { void *p = mmap(0, RESW * sizeof(Cell), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, -1, 0); return p == MAP_FAILED ? 0 : p; } +#endif Cell *micro_mem_alloc(UCell words) { Cell *n; if (words <= lcap && lmem) return lmem; if (words > ((UCell)-1) / sizeof(Cell)) return NULL; +#if defined(ZY_THREADS) && !defined(_WIN32) + if (!lmem && words <= RESW && (lmem = res_map())) { lres = 1; lcap = words; return lmem; } + if (lres && words <= RESW) { lcap = words; return lmem; } /* the reservation reads zero past lcap */ + if (lres) { + n = malloc(words * sizeof(Cell)); if (!n) return NULL; + memcpy(n, lmem, lcap * sizeof(Cell)); memset(n + lcap, 0, (words - lcap) * sizeof(Cell)); + munmap(lmem, RESW * sizeof(Cell)); lres = 0; lmem = n; lcap = words; return n; + } +#endif n = realloc(lmem, words * sizeof(Cell)); if (!n) return NULL; memset(n + lcap, 0, (words - lcap) * sizeof(Cell)); @@ -56,15 +78,62 @@ Cell *micro_mem_alloc(UCell words) return n; } +#if defined(ZY_THREADS) && !defined(_WIN32) +/* a worker's memory is a private map of an unchanging snapshot of its parent's, + plus the pages the parent changed since; see the same scheme in src/core/core.c */ +#define SNAP_PAGE 512 +static VM_TLS int snap_fd = -1; static VM_TLS Cell *snap_view; static VM_TLS UCell snap_words; +static void snap_drop(void) { if (snap_view) munmap(snap_view, snap_words * sizeof(Cell)); if (snap_fd >= 0) close(snap_fd); snap_fd = -1; snap_view = 0; snap_words = 0; } +static int snap_take(void) +{ + UCell words = (lcap + SNAP_PAGE - 1) / SNAP_PAGE * SNAP_PAGE; size_t bytes = words * sizeof(Cell), done = 0; int fd = -1; void *view; +#ifdef __linux__ +#ifdef SYS_memfd_create + fd = (int)syscall(SYS_memfd_create, "zym-snapshot", 1u); +#endif +#else + { char name[64]; snprintf(name, sizeof name, "/zym-%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; } + while (done < bytes) { ssize_t k = pwrite(fd, (char *)lmem + 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; } + if (mmap(lmem, bytes, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, fd, 0) == MAP_FAILED) { munmap(view, bytes); close(fd); return -1; } + snap_drop(); snap_fd = fd; snap_view = view; snap_words = words; return 0; +} +Cell *micro_mem_clone(void) +{ + UCell p, dirty = 0; Cell *c; + if (!lres || !lmem) return 0; + if (snap_fd >= 0) { + for (p = 0; p < lcap; p += SNAP_PAGE) if (p >= snap_words || memcmp(lmem + p, snap_view + p, SNAP_PAGE * sizeof(Cell))) dirty++; + if (dirty * 4 > snap_words / SNAP_PAGE) snap_drop(); + } + if (snap_fd < 0 && snap_take() < 0) return 0; + c = res_map(); if (!c) return 0; + if (mmap(c, snap_words * sizeof(Cell), PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE, snap_fd, 0) == MAP_FAILED) { munmap(c, RESW * sizeof(Cell)); return 0; } + for (p = 0; p < lcap; p += SNAP_PAGE) { + UCell k = lcap - p < SNAP_PAGE ? lcap - p : SNAP_PAGE; + if (p >= snap_words || memcmp(lmem + p, snap_view + p, k * sizeof(Cell))) memcpy(c + p, lmem + p, k * sizeof(Cell)); + } + return c; +} +void micro_mem_adopt(Cell *mem, UCell words) { lmem = mem; lcap = words; lres = 1; } +void micro_mem_release(Cell *mem) { if (mem) munmap(mem, RESW * sizeof(Cell)); } +#endif + #if defined(ZY_THREADS) -static VM_TLS unsigned char input_bytes[65536]; +/* allocated when the machine first reads, so a worker that never reads has none */ +static VM_TLS unsigned char *input_buffer; +#define input_bytes (input_buffer ? input_buffer : (input_buffer = malloc(65536)) ? input_buffer : (fputs("zym: out of memory\n", stderr), exit(2), input_buffer)) static VM_TLS int input_position, input_count, input_ended; static int input_fill(int wait) { if (input_position < input_count) return 1; if (input_ended) return 0; if (!wait) { struct pollfd descriptor = {0, POLLIN, 0}; if (poll(&descriptor, 1, 0) <= 0) return 0; } else fflush(stdout); - int n = (int)read(0, input_bytes, sizeof input_bytes); + int n = (int)read(0, input_bytes, 65536); if (n <= 0) { input_ended = 1; return 0; } input_position = 0; input_count = n; return 1; } @@ -392,22 +461,29 @@ int main(int argc, char **argv) { #ifdef ZY_THREADS struct PortSnapshot { - unsigned char bytes[65536]; int position, count, ended, argc, envc; char **argv, **envp; + unsigned char bytes[]; /* the input read ahead and not consumed yet */ }; -size_t micro_context_port_size(void) { return sizeof(struct PortSnapshot); } +size_t micro_context_port_size(void) { return sizeof(struct PortSnapshot) + (size_t)(input_position < input_count ? input_count - input_position : 0); } void micro_context_port_save(void *destination) { struct PortSnapshot *snapshot = destination; - memcpy(snapshot->bytes, input_bytes, sizeof input_bytes); + if (input_position < input_count) memcpy(snapshot->bytes, input_bytes + input_position, (size_t)(input_count - input_position)); snapshot->position = input_position; snapshot->count = input_count; snapshot->ended = input_ended; snapshot->argc = zy_argc; snapshot->argv = zy_argv; snapshot->envc = zy_envc; snapshot->envp = zy_envp; } void micro_context_port_restore(const void *source) { const struct PortSnapshot *snapshot = source; - memcpy(input_bytes, snapshot->bytes, sizeof input_bytes); input_position = snapshot->position; input_count = snapshot->count; input_ended = snapshot->ended; + if (input_position < input_count) memcpy(input_bytes + input_position, snapshot->bytes, (size_t)(input_count - input_position)); zy_argc = snapshot->argc; zy_argv = snapshot->argv; zy_envc = snapshot->envc; zy_envp = snapshot->envp; } -void micro_context_port_free(void) { free(lmem); lmem = 0; lcap = 0; } +void micro_context_port_free(void) { + free(input_buffer); input_buffer = 0; +#ifndef _WIN32 + if (lres) { munmap(lmem, RESW * sizeof(Cell)); lres = 0; snap_drop(); } else +#endif + free(lmem); + lmem = 0; lcap = 0; +} #endif -- cgit v1.2.3 From 253ac862c696d73fb17c14a847270b6e08aa8769 Mon Sep 17 00:00:00 2001 From: zinc Date: Thu, 15 Oct 2026 14:32:10 +0330 Subject: hold blocks: top-level hold { shared let ...; fn ... } monitors; shared values kept by the host and copied in/out at the outermost call, reentrant per-block locks in declaration order (core, micro) --- src/compiler/base.zc | 6 +++ src/compiler/decl.zc | 56 ++++++++++++++++++++++++- src/compiler/expr.zc | 10 +++++ src/core/core.c | 12 +++++- src/core/hold.h | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/micro/micro.c | 11 ++++- 6 files changed, 208 insertions(+), 3 deletions(-) create mode 100644 src/core/hold.h (limited to 'src/micro/micro.c') diff --git a/src/compiler/base.zc b/src/compiler/base.zc index 7e4b5bc..bd27e28 100644 --- a/src/compiler/base.zc +++ b/src/compiler/base.zc @@ -323,6 +323,12 @@ int s_ld8, s_st8, s_ld16, s_st16, s_ld32, s_st32; // ---- o3 region state ---- // each promoted slot maps to a register; pending regions use 67-word records and a descriptor buffer. +// hold blocks: hold_cur is the rank of the block being compiled (0 outside one), fhold the +// rank of the function being compiled, hold_desc the word that will point at the block's +// table of shared variables, hold_count the blocks so far. a shared variable's symbol has +// F_SHARED and its block's rank above it in sflag. +const int F_SHARED = 131072, F_SHARED_RANK = 262144; +int hold_cur, fhold, hold_count, hold_desc, hold_nshared; word[128] hold_tab; int o3on, o3nxt, o3one; word[32] o3sl; word[32] o3rg; int no3sl; word[O3_REGIONS * 67] o3pend_initial; word o3pend = o3pend_initial; int o3pendcap = O3_REGIONS, no3pend; word[80] o3dsc; int o3fs, o3na, o3fn0; // o3free holds registers released by closed scopes; o3cache holds the items pointer and length slots for fixed arrays. word[8] o3free; int no3free, o3nreg; word[NL] o3cache_initial; word o3cache = o3cache_initial; int argloc, s_hitems, s_hlen, s_fatten; diff --git a/src/compiler/decl.zc b/src/compiler/decl.zc index 52d08c7..fed7b8d 100644 --- a/src/compiler/decl.zc +++ b/src/compiler/decl.zc @@ -230,11 +230,61 @@ int prologue() { setlit(k, here); setlit(done, here); op(O_DROP); setlit(busy, here); return 0; } +// a function of a hold block takes the block's lock on entry (op 57 of the syscall gate) +// and gives it back at its one exit (op 58), with the return value left on the stack. the +// host keeps the shared values: entering the block from outside copies them into the +// block's variables, leaving copies them back, so inside, every read and write is a plain +// global access on every backend. the lock is reentrant and held for the whole call. +int hold_gate(int code) { + if (!fhold || nclo) return 0; + lit(fhold); lit(rt.SYSGATE + 1); op(O_ST); + lit(hold_desc); lit(rt.SYSGATE + 2); op(O_ST); + lit(code); lit(rt.SYSGATE); op(O_ST); + return 0; +} +// hold { shared let c; fn a() { ... } ... }: a monitor. the shared variables can be used +// only by the functions written in the block; each call holds the block's lock. +int kw_hold() { + int rank, d, before, s, i, w; word t; + if (hold_cur) serr("hold blocks don't nest"); + next(); expect(s_lb); + rank = hold_count + 1; hold_count = rank; + reserve_code(1); d = here; emit(0); + hold_cur = rank; hold_desc = d; hold_nshared = 0; + while (!is_op(s_rb)) { + if (tk == T_EOF) { die("missing } at the end of a hold block"); break; } + if (panic) { topsync(); continue; } + if (tk == T_ID && rt.eq_w(tv, "shared")) { + next(); before = nsym; + if (is_id(s_var)) gdecl(0); + else if (tstart()) { rettype(); gdecl2(dty, dtag, name()); } + else { die("expected let or a type after shared"); continue; } + s = before; + while (s < nsym) { + if (skind[s] == K_GVAR || skind[s] == K_GARR) { + t = stype[s]; w = 1; + if (skind[s] == K_GARR) { w = tsz(t); if (t[0] == TY_ARR) t = t[1]; } + if (t && (isptr(t) || isagg(t) || t == tbstr || t[0] == TY_DYN || t[0] == TY_FN)) serrn(sname[s], "is shared, so it holds numbers: no pointers, strings or structs (each actor has its own heap)"); + sflag[s] = sflag[s] | F_SHARED | rank * F_SHARED_RANK; + if (hold_nshared >= 64) serr("too many shared variables in one hold block (64)"); + else { hold_tab[hold_nshared * 2] = sval[s]; hold_tab[hold_nshared * 2 + 1] = w; hold_nshared++; } + } + s++; + } + } else topitem0(); + } + next(); + reserve_code(1 + hold_nshared * 2); *(d + hdelta) = here; emit(hold_nshared); + i = 0; while (i < hold_nshared * 2) { emit(hold_tab[i]); i++; } + hold_cur = 0; hold_desc = 0; + return 0; +} // emit the function epilogue, patching every return to it. int epilogue() { int k, node, done, helper = !nclo && (sname[finsym] == s_frame_grow || sname[finsym] == s_frame_leave); lit(0); fixall(epichain, here); + hold_gate(58); lit(rt.ZFP); op(O_LD); lit(fslots); op(O_ADD); lit(rt.ZFP); op(O_ST); if (!helper) { lit(sval[lookup(s_frame_node)]); op(O_LD); node = hole(); op(O_JZ); @@ -404,7 +454,9 @@ int fndef(int chk, int rty, int ret, int n) { if (typed && explicit_rty) fty = rty; no3pend = 0; if (o3f) { o3on = 1; o3reset(); sflag[s] = sflag[s] | F_O3; } - prologue(); borrow_prologue(); + fhold = hold_cur; + if (fhold && o3f) serrn(n, "is in a hold block and can't be o3"); + prologue(); borrow_prologue(); hold_gate(57); i = 0; while (i < np) { t = newloc(pn[i]); lrskind[t] = RS_PARAM; lrsoff[t] = pnoffs[i]; lpos[t] = pnplaces[i]; fslots++; lsmark(1, pn[i]); @@ -431,6 +483,7 @@ int fndef(int chk, int rty, int ret, int n) { } hi = here; epilogue(); + fhold = 0; lend = here; if (o3f) { o3on = 0; @@ -720,6 +773,7 @@ int topitem1() { if (!is_op(s_lp)) { die("expected a function"); return 0; } return fndone(fndef(chk, dty, dtag == S_U, n), imm); } + if (tk == T_ID && rt.eq_w(tv, "hold")) { peek(); if (pk_tk == T_OP && pk_tv == s_lb) return kw_hold(); } if (is_id(s_var)) return gdecl(0); if (is_id(s_struct)) { kw_struct(); return 0; } if (is_id(s_type)) return kw_type(); diff --git a/src/compiler/expr.zc b/src/compiler/expr.zc index a6cc55c..141ca16 100644 --- a/src/compiler/expr.zc +++ b/src/compiler/expr.zc @@ -657,6 +657,15 @@ word closure_layout(int closure_index, int count) { // the code that creates the closure runs, it allocates the block (code, then // captured values), writes the three code words and copies each captured // variable in. ctx_push / ctx_pop save and restore the enclosing function's +// a shared variable is reachable only from a function of its own hold block, which holds +// the block's lock for the whole call. a closure might run after the call returns. +int shared_use(int s) { + int rank = sflag[s] / F_SHARED_RANK; + if (fhold == rank && !nclo && infn) return 0; + if (fhold == rank && nclo) { serrn(sname[s], "is shared; a closure can't use it, because it may run after the hold block's lock is released"); return 0; } + serrn(sname[s], "is shared in a hold block; only the functions written inside that block can use it (they hold its lock)"); + return 0; +} // compiler state around the body. int closure() { char* xcf; @@ -1095,6 +1104,7 @@ int primary() { xcval = sval[s]; return C_PLAIN; } + if (s >= 0 && (skind[s] == K_GVAR || skind[s] == K_GARR) && (sflag[s] & F_SHARED)) shared_use(s); if (s >= 0 && skind[s] == K_GVAR) { if (cmode) { vf_symbols(); xvk = svkind[s]; xvv = svvalue[s]; vf_at(rsn - 1, xvk, xvv); } lit(sval[s]); diff --git a/src/core/core.c b/src/core/core.c index 648f54a..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 @@ -295,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; @@ -7646,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; 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 +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/micro/micro.c b/src/micro/micro.c index 9924d5e..e095451 100644 --- a/src/micro/micro.c +++ b/src/micro/micro.c @@ -34,6 +34,10 @@ #include "micro_port.h" #include "fops.h" #include "bops.h" +#ifdef ZY_THREADS +#define HOLD_THREADS +#endif +#include "hold.h" #define NENH 512 #define XOP0 22 #define XPOOL 4096 @@ -537,6 +541,11 @@ static int run_from(int p, int s, int r) /* poll: a streaming port suspends once, so its host can deliver input that is waiting */ result = micro_syscall(m, b, 0, 0, 0); if (result == MICRO_WAIT) { sus_p = p - 1; sus_s = s; sus_r = r; return MICRO_SUSPENDED; } + } else if (b == 57) { + result = hold_enter(m, mw, m[a + 1], m[a + 2]); + if (result == -2) { micro_msg("zy: a hold block was entered while a later one was held; hold blocks are entered in the order they are declared\n"); context_call(7, 2, 0, 0, s + 2, r); return 1; } /* a trap: exit status 2 */ + } else if (b == 58) { + result = hold_leave(m, mw, m[a + 1], m[a + 2]); } else result = context_call(b, m[a + 1], m[a + 2], m[a + 3], s + 2, r); m[a] = result; s += 2; @@ -718,7 +727,7 @@ static void *context_worker(void *opaque) { if (!context->requested_exit) context->exit_code = context_cancelled() ? 0 : result ? -1 : inmem(final_s) ? (int)m[final_s] : 0; context_close(context->worker_channel.handle); while (context_children) context_join(context_children->token, 1); - micro_context_port_free(); m = 0; atomic_fetch_sub(&context_memory_live, 1); context_current = 0; return 0; + hold_release_all(); micro_context_port_free(); m = 0; atomic_fetch_sub(&context_memory_live, 1); context_current = 0; return 0; } static Cell context_start(Cell entry, Cell channel, int sp, int rp) { if (!inmem(entry) || !inmem(channel) || (entry >= HEAP_LO && (!inmem(entry - 3) || m[entry - 3] != (entry ^ 0x2B7E1516)))) return -1; -- cgit v1.2.3