aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/compiler/base.zc6
-rw-r--r--src/compiler/decl.zc56
-rw-r--r--src/compiler/expr.zc10
-rw-r--r--src/core/core.c12
-rw-r--r--src/core/hold.h116
-rw-r--r--src/micro/micro.c11
6 files changed, 208 insertions, 3 deletions
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 <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/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;