aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-05 07:32:17 +0100
committereunhhu <[email protected]>2026-10-05 07:38:00 +0100
commit75b9c1c1e936e2b7c55275139897e1c78d7939c1 (patch)
tree63f4a2c15a9ca651b9ce7f961683692626e551e0 /core
parent71f1de8416a01ce6305ec9c5ed29895f4344886b (diff)
downloadpure-75b9c1c1e936e2b7c55275139897e1c78d7939c1.tar.gz
pure-75b9c1c1e936e2b7c55275139897e1c78d7939c1.zip
Run a block through a frame and step, and compile blocks to AArch64 that call the same steps (021)
- run() keeps a block's operand stack and open loops in a Frame; step() runs one instruction (branches and loops too, one dispatch), and enter_each, enter_loop, leave_loop and iterate are their own functions. The executor is the same machine code, 7% fewer instructions. - The verifier records each instruction's stack height (from its block's start), so compiled code knows each slot's place. - module-jit.c (AArch64 Linux; elsewhere, or PURE_JIT=0, none): a module's handlers', views' and folds' blocks compiled when it loads. Branches and loops are branches; every other instruction calls the executor's own step, so both run the same meaning. A loop that is not opened lands where its iteration would have ended; an empty loop iterates in place. - test-module recompiles a module whose code a test changes after load. - A search (100,000 nodes, 1,000,000 edges, core only) is 150 ms compiled, against 122 ms executed: a call of step for every instruction costs more than the executor's switch; the hot instructions are written as code next.
Diffstat (limited to 'core')
-rw-r--r--core/module-jit.c418
-rw-r--r--core/module-run.h94
-rw-r--r--core/module.c1196
-rw-r--r--core/module.h2
-rw-r--r--core/test-module.c5
5 files changed, 1158 insertions, 557 deletions
diff --git a/core/module-jit.c b/core/module-jit.c
new file mode 100644
index 0000000..887ff03
--- /dev/null
+++ b/core/module-jit.c
@@ -0,0 +1,418 @@
+#include "module-run.h"
+#include <stdlib.h>
+#include <string.h>
+
+/* ---- Compiled code (docs/design/021)
+ * A module's blocks (its handlers' and views' code, and the folds' blocks
+ * in them) as AArch64 code, made when the module loads. A block's code runs
+ * a frame as run does: branches and loops are branches and calls, every
+ * other instruction a call of the executor's step (module.c) unless it is
+ * written here, as the hot ones are. The operand stack stays in the frame;
+ * its height at each instruction is known when the module loads (the
+ * verifier records it), so a slot is a fixed place in it. */
+
+#if defined(__aarch64__) && defined(__linux__)
+#include <stddef.h>
+#include <sys/mman.h>
+
+struct PureJit {
+ unsigned char *memory; /* the code, executable */
+ size_t size;
+ struct Entry { uint32_t length; PureCompiled code; } *by_start; /* a block's code by its start */
+ uint32_t code_count;
+};
+
+/* ---- Emitting instructions */
+
+typedef struct { size_t at; int label; unsigned kind; } Fixup; /* kind: 0 B/BL, 1 B.cond, CBZ/CBNZ */
+typedef struct {
+ uint32_t *words;
+ size_t count, room;
+ long *labels; /* each label's word, or -1 */
+ size_t label_count, label_room;
+ Fixup *fixups;
+ size_t fixup_count, fixup_room;
+ bool failed;
+} Emit;
+
+static bool grow_array(void **array, size_t *room, size_t need, size_t size) {
+ if (need <= *room) return true;
+ size_t next = *room ? *room : 256;
+ while (next < need) next *= 2;
+ void *grown = realloc(*array, next * size);
+ if (!grown) return false;
+ *array = grown;
+ *room = next;
+ return true;
+}
+
+static void put(Emit *e, uint32_t word) {
+ if (!grow_array((void **)&e->words, &e->room, e->count + 1, sizeof(*e->words))) {
+ e->failed = true;
+ return;
+ }
+ e->words[e->count++] = word;
+}
+
+static int new_label(Emit *e) {
+ if (!grow_array((void **)&e->labels, &e->label_room, e->label_count + 1, sizeof(*e->labels))) {
+ e->failed = true;
+ return 0;
+ }
+ e->labels[e->label_count] = -1;
+ return (int)e->label_count++;
+}
+
+static void bind(Emit *e, int label) {
+ if (!e->failed) e->labels[label] = (long)e->count;
+}
+
+static void branch_to(Emit *e, uint32_t word, int label, unsigned kind) {
+ if (!grow_array((void **)&e->fixups, &e->fixup_room, e->fixup_count + 1, sizeof(*e->fixups))) {
+ e->failed = true;
+ return;
+ }
+ e->fixups[e->fixup_count++] = (Fixup){e->count, label, kind};
+ put(e, word);
+}
+
+/* Branches given their offsets, once every label is bound. */
+static bool resolve(Emit *e) {
+ for (size_t i = 0; i < e->fixup_count && !e->failed; ++i) {
+ Fixup f = e->fixups[i];
+ long target = e->labels[f.label];
+ if (target < 0) return false;
+ long offset = target - (long)f.at;
+ if (f.kind == 0) {
+ if (offset < -(1L << 25) || offset >= (1L << 25)) return false;
+ e->words[f.at] |= (uint32_t)offset & 0x3FFFFFF;
+ } else {
+ if (offset < -(1L << 18) || offset >= (1L << 18)) return false;
+ e->words[f.at] |= ((uint32_t)offset & 0x7FFFF) << 5;
+ }
+ }
+ return !e->failed;
+}
+
+enum { X0 = 0, X1 = 1, X9 = 9, X10 = 10, X16 = 16, X19 = 19, X20 = 20, X21 = 21, X22 = 22, X29 = 29, X30 = 30, SP = 31, XZR = 31 };
+enum { EQ = 0, NE = 1, HS = 2, LO = 3, MI = 4, PL = 5, VS = 6, VC = 7, HI = 8, LS = 9, GE = 10, LT = 11, GT = 12, LE = 13 };
+
+/* A 64-bit constant into register d. */
+static void mov_imm(Emit *e, unsigned d, uint64_t value) {
+ put(e, 0xD2800000 | (uint32_t)(value & 0xFFFF) << 5 | d); /* MOVZ */
+ for (unsigned k = 1; k < 4; ++k)
+ if (value >> (16 * k) & 0xFFFF) put(e, 0xF2800000 | k << 21 | (uint32_t)(value >> (16 * k) & 0xFFFF) << 5 | d); /* MOVK */
+}
+
+/* LDR/STR of 64 bits at base + offset (a multiple of 8 below 32 KiB), or through x16 past it. */
+static void load64(Emit *e, unsigned t, unsigned base, size_t offset) {
+ if (offset % 8 == 0 && offset < 32768) {
+ put(e, 0xF9400000 | (uint32_t)(offset / 8) << 10 | base << 5 | t);
+ return;
+ }
+ mov_imm(e, X16, offset);
+ put(e, 0xF8606800 | X16 << 16 | base << 5 | t); /* LDR Xt, [Xbase, X16] */
+}
+
+static void store64(Emit *e, unsigned t, unsigned base, size_t offset) {
+ if (offset % 8 == 0 && offset < 32768) {
+ put(e, 0xF9000000 | (uint32_t)(offset / 8) << 10 | base << 5 | t);
+ return;
+ }
+ mov_imm(e, X16, offset);
+ put(e, 0xF8206800 | X16 << 16 | base << 5 | t); /* STR Xt, [Xbase, X16] */
+}
+
+static __attribute__((unused)) void load_d(Emit *e, unsigned t, unsigned base, size_t offset) { put(e, 0xFD400000 | (uint32_t)(offset / 8) << 10 | base << 5 | t); }
+static __attribute__((unused)) void store_d(Emit *e, unsigned t, unsigned base, size_t offset) { put(e, 0xFD000000 | (uint32_t)(offset / 8) << 10 | base << 5 | t); }
+static __attribute__((unused)) void store_h(Emit *e, unsigned t, unsigned base, size_t offset) { put(e, 0x79000000 | (uint32_t)(offset / 2) << 10 | base << 5 | t); }
+static __attribute__((unused)) void store_w(Emit *e, unsigned t, unsigned base, size_t offset) { put(e, 0xB9000000 | (uint32_t)(offset / 4) << 10 | base << 5 | t); }
+static void mov_reg(Emit *e, unsigned d, unsigned n) { put(e, 0xAA0003E0 | n << 16 | d); }
+static void mov_w(Emit *e, unsigned d, uint32_t value) { /* MOVZ (and MOVK) Wd */
+ put(e, 0x52800000 | (value & 0xFFFF) << 5 | d);
+ if (value >> 16) put(e, 0x72A00000 | (value >> 16) << 5 | d);
+}
+
+/* A call of a C function at address (in x16). */
+static void call(Emit *e, uintptr_t address) {
+ mov_imm(e, X16, address);
+ put(e, 0xD63F0000 | X16 << 5); /* BLR x16 */
+}
+
+static void b(Emit *e, int label) { branch_to(e, 0x14000000, label, 0); }
+static void b_cond(Emit *e, unsigned cond, int label) { branch_to(e, 0x54000000 | cond, label, 1); }
+static void cbz_w(Emit *e, unsigned t, int label) { branch_to(e, 0x34000000 | t, label, 1); }
+static void cbnz_w(Emit *e, unsigned t, int label) { branch_to(e, 0x35000000 | t, label, 1); }
+static void cmp_w_imm(Emit *e, unsigned n, uint32_t imm) { put(e, 0x7100001F | imm << 10 | n << 5); }
+
+/* ---- A block */
+
+enum { SLOT = sizeof(Slot) };
+
+typedef struct { uint32_t start, end; int parent; int again, after; } LoopAt; /* again: its iteration's label; after: past it */
+
+typedef struct {
+ const PureModule *m;
+ PureBlock block;
+ Emit *e;
+ uint8_t *height; /* absolute, by instruction of the block */
+ int *loop_of; /* the innermost loop each instruction is in, or -1 */
+ int *opens; /* the loop an EACH or a LOOP opens */
+ LoopAt *loops;
+ unsigned loop_count;
+ int *op_label; /* each instruction's, and the block's end */
+ int exit; /* w0 holds how it went */
+} Block;
+
+static void set_sp(Block *k, unsigned height) {
+ mov_imm(k->e, X9, height);
+ store64(k->e, X9, X19, offsetof(Frame, sp));
+}
+
+/* Where a branch at from to target lands: the iteration of the innermost loop around from that ends at target, else target's instruction. */
+static int landing(const Block *k, uint32_t from, uint32_t target) {
+ for (int l = k->loop_of[from - k->block.start]; l >= 0; l = k->loops[l].parent)
+ if (k->loops[l].end == target) return k->loops[l].again;
+ return k->op_label[target - k->block.start];
+}
+
+/* The iterations of the loops ending at pc, innermost first. */
+static void loop_ends(Block *k, uint32_t pc) {
+ Emit *e = k->e;
+ for (uint64_t done = 0;;) { /* innermost first: the loop ending at pc nested deepest among those not done */
+ int pick = -1, deepest = -1;
+ for (unsigned l = 0; l < k->loop_count; ++l) {
+ if (k->loops[l].end != pc || (l < 64 && done >> l & 1)) continue;
+ int depth = 0;
+ for (int p = k->loops[l].parent; p >= 0; p = k->loops[p].parent) ++depth;
+ if (depth > deepest) deepest = depth, pick = (int)l;
+ }
+ if (pick < 0) break;
+ done |= UINT64_C(1) << pick;
+ bind(e, k->loops[pick].again);
+ mov_reg(e, X0, X19);
+ call(e, (uintptr_t)pure_run_iterate);
+ /* again: its block's first instruction; an empty block's is this */
+ const LoopAt *l = &k->loops[pick];
+ cbz_w(e, X0, l->start == l->end ? l->again : k->op_label[l->start - k->block.start]);
+ cmp_w_imm(e, X0, STEP_DONE);
+ b_cond(e, NE, k->exit);
+ bind(e, k->loops[pick].after);
+ }
+}
+
+static void call_step(Block *k, uintptr_t function, uint32_t pc, unsigned height) {
+ Emit *e = k->e;
+ set_sp(k, height);
+ mov_reg(e, X0, X19);
+ mov_w(e, X1, pc);
+ call(e, function);
+}
+
+static void compile_op(Block *k, uint32_t pc) {
+ Emit *e = k->e;
+ const PureOp *op = &k->m->code[pc];
+ unsigned height = k->height[pc - k->block.start];
+ switch (op->op) {
+ case PURE_OP_JZ:
+ load_d(e, 0, X20, (height - 1) * SLOT);
+ put(e, 0x1E602008); /* FCMP d0, #0.0 */
+ b_cond(e, EQ, landing(k, pc, op->arg));
+ return;
+ case PURE_OP_JMP:
+ b(e, landing(k, pc, op->arg));
+ return;
+ case PURE_OP_EACH: case PURE_OP_LOOP: {
+ call_step(k, op->op == PURE_OP_EACH ? (uintptr_t)pure_run_each : (uintptr_t)pure_run_loop, pc, height);
+ int open = new_label(e);
+ cbz_w(e, X0, open);
+ cmp_w_imm(e, X0, STEP_SKIP);
+ b_cond(e, NE, k->exit);
+ b(e, k->loops[k->opens[pc - k->block.start]].after); /* none: past its block, where its iteration would end */
+ bind(e, open);
+ return;
+ }
+ case PURE_OP_BREAK: {
+ int l = k->loop_of[pc - k->block.start];
+ mov_reg(e, X0, X19);
+ call(e, (uintptr_t)pure_run_break);
+ b(e, k->loops[l].after);
+ return;
+ }
+ default:
+ call_step(k, (uintptr_t)pure_run_step, pc, height);
+ cbnz_w(e, X0, k->exit);
+ return;
+ }
+}
+
+/* The block's heights (absolute: a loop's block is verified from its own start), its loops, and each instruction's. */
+static bool shape_block(Block *k) {
+ const PureModule *m = k->m;
+ uint32_t n = k->block.length;
+ k->height = malloc(n + 1);
+ k->loop_of = malloc((n + 1) * sizeof(*k->loop_of));
+ k->op_label = malloc((n + 1) * sizeof(*k->op_label));
+ k->loops = malloc((n + 1) * sizeof(*k->loops));
+ k->opens = malloc((n + 1) * sizeof(*k->opens));
+ if (!k->height || !k->loop_of || !k->op_label || !k->loops || !k->opens) return false;
+ int open[PURE_MODULE_NESTING + 1];
+ unsigned base[PURE_MODULE_NESTING + 1], depth = 0;
+ base[0] = 0;
+ for (uint32_t pc = k->block.start; pc < k->block.start + n;) {
+ while (depth && pc >= k->loops[open[depth - 1]].end) --depth;
+ const PureOp *op = &m->code[pc];
+ unsigned at = base[depth] + m->height[pc];
+ k->height[pc - k->block.start] = (uint8_t)at;
+ k->loop_of[pc - k->block.start] = depth ? open[depth - 1] : -1;
+ if (op->op == PURE_OP_EACH || op->op == PURE_OP_LOOP) {
+ unsigned pops = op->op == PURE_OP_LOOP || op->arg & PURE_EACH_SLICED ? 2 : 0;
+ LoopAt *l = &k->loops[k->loop_count];
+ *l = (LoopAt){pc + 1, pc + 1 + (op->arg >> 16), depth ? open[depth - 1] : -1, new_label(k->e), new_label(k->e)};
+ k->opens[pc - k->block.start] = (int)k->loop_count;
+ open[depth] = (int)k->loop_count++;
+ base[++depth] = at - pops;
+ ++pc;
+ continue;
+ }
+ if (op->op == PURE_OP_FOLD) { /* its block is its own: not this one's code */
+ for (uint32_t i = pc + 1; i < after(op, pc); ++i) k->loop_of[i - k->block.start] = -1, k->height[i - k->block.start] = 0;
+ }
+ pc = after(op, pc);
+ }
+ for (uint32_t i = 0; i <= n; ++i) k->op_label[i] = new_label(k->e);
+ k->exit = new_label(k->e);
+ return !k->e->failed;
+}
+
+/* The block's code into e (leaving the stack end_height high), its start's word in *entry. */
+static bool compile_block(const PureModule *m, PureBlock block, unsigned end_height, Emit *e, size_t *entry) {
+ Block k = {.m = m, .block = block, .e = e};
+ bool ok = shape_block(&k);
+ if (ok) {
+ *entry = e->count;
+ put(e, 0xA9BC7BFD); /* STP x29, x30, [sp, #-64]! */
+ put(e, 0x910003FD); /* MOV x29, sp */
+ put(e, 0xA90153F3); /* STP x19, x20, [sp, #16] */
+ put(e, 0xA9025BF5); /* STP x21, x22, [sp, #32] */
+ mov_reg(e, X19, X0);
+ load64(e, X20, X19, offsetof(Frame, stack));
+ load64(e, X21, X19, offsetof(Frame, values));
+ load64(e, X22, X19, offsetof(Frame, ev));
+ for (uint32_t pc = block.start; pc < block.start + block.length;) {
+ loop_ends(&k, pc);
+ bind(e, k.op_label[pc - block.start]);
+ compile_op(&k, pc);
+ const PureOp *op = &m->code[pc];
+ pc = op->op == PURE_OP_EACH || op->op == PURE_OP_LOOP ? pc + 1 : after(op, pc);
+ }
+ uint32_t end = block.start + block.length;
+ loop_ends(&k, end);
+ bind(e, k.op_label[block.length]);
+ set_sp(&k, end_height); /* the block ran: what it leaves (a view's value) is the frame's to give */
+ mov_w(e, X0, STEP_NEXT);
+ bind(e, k.exit);
+ put(e, 0xA94153F3); /* LDP x19, x20, [sp, #16] */
+ put(e, 0xA9425BF5); /* LDP x21, x22, [sp, #32] */
+ put(e, 0xA8C47BFD); /* LDP x29, x30, [sp], #64 */
+ put(e, 0xD65F03C0); /* RET */
+ }
+ free(k.height);
+ free(k.loop_of);
+ free(k.op_label);
+ free(k.loops);
+ free(k.opens);
+ return ok && !e->failed;
+}
+
+/* ---- The module */
+
+typedef struct { PureBlock block; unsigned leaves; size_t entry; } Made;
+
+/* A block to make, leaving leaves values on the stack. */
+static bool want(PureBlock block, unsigned leaves, Made **made, size_t *count, size_t *room) {
+ for (size_t i = 0; i < *count; ++i)
+ if ((*made)[i].block.start == block.start && (*made)[i].block.length == block.length) return true;
+ if (!grow_array((void **)made, room, *count + 1, sizeof(**made))) return false;
+ (*made)[(*count)++] = (Made){block, leaves, 0};
+ return true;
+}
+
+struct PureJit *pure_jit_make(const PureModule *m) {
+ const char *enabled = getenv("PURE_JIT");
+ if ((enabled && !strcmp(enabled, "0")) || !m->height || !m->code_count) return NULL;
+ Made *made = NULL;
+ size_t count = 0, room = 0;
+ bool ok = true;
+ /* The handlers' and the views' code, then the folds' blocks in what is made (they run as blocks of their own). */
+ for (uint32_t h = 0; ok && h < m->handlers; ++h) ok = want(m->handler[h].code, 0, &made, &count, &room);
+ for (uint32_t v = 0; ok && v < m->views; ++v)
+ if (m->view_code[v].length) ok = want(m->view_code[v], 1, &made, &count, &room);
+ for (size_t i = 0; ok && i < count; ++i)
+ for (uint32_t pc = made[i].block.start; ok && pc < made[i].block.start + made[i].block.length; ++pc) {
+ const PureOp *op = &m->code[pc];
+ unsigned how = op->arg & PURE_FOLD_KIND; /* count and first leave a bool; the others a value and a bool */
+ if (op->op == PURE_OP_FOLD)
+ ok = want((PureBlock){pc + 1, op->arg >> 16}, how == PURE_FOLD_COUNT || how == PURE_FOLD_FIRST ? 1 : 2, &made, &count,
+ &room);
+ }
+ Emit e = {0};
+ for (size_t i = 0; ok && i < count; ++i) ok = compile_block(m, made[i].block, made[i].leaves, &e, &made[i].entry);
+ ok = ok && resolve(&e);
+ struct PureJit *jit = NULL;
+ /* by start, an empty block's at the code's end too */
+ if (ok && (jit = calloc(1, sizeof(*jit))) && (jit->by_start = calloc(m->code_count + 1, sizeof(*jit->by_start)))) {
+ jit->size = (e.count * sizeof(uint32_t) + 4095) & ~(size_t)4095;
+ jit->code_count = m->code_count;
+ void *memory = mmap(NULL, jit->size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (memory != MAP_FAILED) {
+ jit->memory = memory;
+ memcpy(memory, e.words, e.count * sizeof(uint32_t));
+ __builtin___clear_cache((char *)memory, (char *)memory + e.count * sizeof(uint32_t));
+ if (mprotect(memory, jit->size, PROT_READ | PROT_EXEC)) ok = false;
+ for (size_t i = 0; ok && i < count; ++i)
+ jit->by_start[made[i].block.start] =
+ (struct Entry){made[i].block.length, (PureCompiled)((uintptr_t)jit->memory + made[i].entry * sizeof(uint32_t))};
+ } else
+ ok = false;
+ } else
+ ok = false;
+ free(e.words);
+ free(e.labels);
+ free(e.fixups);
+ free(made);
+ if (!ok) {
+ pure_jit_free(jit);
+ return NULL;
+ }
+ return jit;
+}
+
+void pure_jit_free(struct PureJit *jit) {
+ if (!jit) return;
+ if (jit->memory) munmap(jit->memory, jit->size);
+ free(jit->by_start);
+ free(jit);
+}
+
+PureCompiled pure_jit_code(const struct PureJit *jit, PureBlock block) {
+ if (!jit || block.start > jit->code_count) return NULL;
+ const struct Entry *entry = &jit->by_start[block.start];
+ return entry->code && entry->length == block.length ? entry->code : NULL;
+}
+
+#else
+
+struct PureJit *pure_jit_make(const PureModule *m) {
+ (void)m;
+ return NULL;
+}
+
+void pure_jit_free(struct PureJit *jit) { (void)jit; }
+
+PureCompiled pure_jit_code(const struct PureJit *jit, PureBlock block) {
+ (void)jit, (void)block;
+ return NULL;
+}
+
+#endif
diff --git a/core/module-run.h b/core/module-run.h
new file mode 100644
index 0000000..49bdf35
--- /dev/null
+++ b/core/module-run.h
@@ -0,0 +1,94 @@
+#ifndef PURE_MODULE_RUN_H
+#define PURE_MODULE_RUN_H
+/* How a block runs, shared by the executor (module.c) and the compiled code
+ * (module-jit.c): their frame, and the steps the compiled code calls. Not a
+ * public interface. */
+#include "module.h"
+
+/* An item's or a key's op->plan: its cell a text; a FIND joined to the ITEM_GET after it. */
+enum { PLAN_TEXT = 1, PLAN_JOINED = 2 };
+
+typedef struct { union { double num; int64_t i64; }; PureBytes bytes; } Slot;
+
+/* A walk (EACH): the list, the item it runs for, where it ends (items the
+ * walk removes move it), and whether that item left. */
+typedef struct { uint32_t list; size_t at, end; bool removed; } Walk;
+/* A loop that runs in its block's run (LOOP, EACH): its block and where
+ * the code around it ends, the arena as it began, a walk's walk and the
+ * event's walk and row around it, a count's values (low + k of count) and
+ * slot. */
+typedef struct {
+ uint32_t start, end, outer;
+ size_t mark;
+ bool walking, integer;
+ Walk walk, *around;
+ size_t row;
+ uint64_t count, k;
+ PureValue low, *slot;
+} Loop;
+typedef struct {
+ PureApp *app;
+ const PureInputShape *shape; /* message, or NULL */
+ const char *key; /* a keyed message's key */
+ int64_t base; /* the revision a message's edit was made against, or -1 */
+ PureArena *arena;
+ double dt;
+ size_t row; /* the row a column is evaluated for (in a fold, the fold's item) */
+ size_t outer; /* in a fold, the row of the column it runs in */
+ Walk *walk; /* the walk the code runs in, or NULL */
+ PureValue *locals; /* the event's locals (a handler's, and what runs in it), or NULL */
+ bool arguments; /* FIELD reads the arguments given for a view, not the message */
+ bool modified, exists;
+ uint32_t (*spans)[2]; /* a message's variable fields: start and size in the payload */
+} Event;
+
+/* ---- Running a block (docs/design/021). A frame holds what a block reads
+ * and changes as it runs: its operand stack and the loops open in it. step
+ * runs one instruction but a branch or a loop's; run takes those itself, as
+ * the compiled code does (module-jit.c), so both run the same steps. */
+typedef struct {
+ const PureModule *m;
+ PureValue *values;
+ Event *ev;
+ PureApp *app;
+ PureArena *arena;
+ Slot *stack;
+ size_t sp;
+ uint32_t end; /* where the innermost loop's block ends, or the block's */
+ unsigned depth; /* the loops open */
+ PureFault kind; /* a fault's */
+ PureResult status; /* what refused or stopped it */
+ uint32_t fault_pc; /* the instruction that faulted or refused */
+ Loop *loops; /* PURE_MODULE_NESTING of them, apart: a walk points into its loop */
+} Frame;
+
+/* How a step went: on to the next instruction; a fault (kind at fault_pc);
+ * refused (status: past a limit or nothing found is a fault there, else it
+ * is returned); stopped (status returned as it is). */
+enum { STEP_NEXT, STEP_FAULT, STEP_REFUSED, STEP_RETURN };
+
+/* The instruction after op at pc: past a fold's block, or the ITEM_GET a FIND was joined to. */
+static inline uint32_t after(const PureOp *op, uint32_t pc) {
+ return op->op == PURE_OP_FOLD ? pc + 1 + (op->arg >> 16) : pc + 1 + (op->op == PURE_OP_LIST_FIND && op->plan & PLAN_JOINED);
+}
+
+/* The steps compiled code takes from the executor (module.c): an
+ * instruction but a branch or a loop's (how; the next is after()); a walk or
+ * a loop opened at pc (STEP_NEXT: its block next, STEP_SKIP: none, past its
+ * block); BREAK; a loop's block ran once (STEP_NEXT: again, STEP_DONE: the
+ * loop ended). */
+enum { STEP_SKIP = 4, STEP_DONE = 5 };
+int pure_run_step(Frame *, uint32_t pc);
+int pure_run_each(Frame *, uint32_t pc);
+int pure_run_loop(Frame *, uint32_t pc);
+void pure_run_break(Frame *);
+int pure_run_iterate(Frame *);
+
+/* A module's code compiled (AArch64 Linux; elsewhere, or with PURE_JIT=0 in
+ * the environment, none): a block's code runs a frame as run does, giving how
+ * it went (a STEP_). */
+typedef int (*PureCompiled)(Frame *);
+struct PureJit *pure_jit_make(const PureModule *);
+void pure_jit_free(struct PureJit *);
+PureCompiled pure_jit_code(const struct PureJit *, PureBlock);
+#endif
diff --git a/core/module.c b/core/module.c
index c883c1f..2b484a6 100644
--- a/core/module.c
+++ b/core/module.c
@@ -1,4 +1,5 @@
#include "module.h"
+#include "module-run.h"
#include "module-save.h"
#include "log.h"
#include "module-text.h"
@@ -354,9 +355,6 @@ static unsigned char state_type(const PureModule *m, uint32_t state) {
return m->state_type[state] == PURE_STATE_BOOL ? T_BOOL : m->state_type[state] == PURE_STATE_I64 ? T_I64 : T_NUM;
}
-/* An item's or a key's op->plan: its cell a text; a FIND joined to the ITEM_GET after it. */
-enum { PLAN_TEXT = 1, PLAN_JOINED = 2 };
-
/* Whether a list's record field is a text. */
static bool text_field(const PureModule *m, uint32_t list, uint32_t field) {
return m->record_field_type[m->record[m->state_ref[list]].first + field] == PURE_RECORD_TEXT;
@@ -474,6 +472,7 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
}
if (i == block.length) break;
if (!reachable) REFUSE("unreachable instruction");
+ if (m->height) m->height[pc] = (uint8_t)height; /* the compiled code's slots (021) */
const PureOp *op = &m->code[pc];
if (op->op >= PURE_OP_COUNT) REFUSE("unknown opcode");
unsigned allowed = signature[op->op].where, kind = signature[op->op].arg, of = signature[op->op].of;
@@ -1019,6 +1018,10 @@ PURE_COLD PureResult pure_module_read(const char *path, PureModule *out, char *e
}
void pure_module_release(PureModule *m) {
+ pure_jit_free(m->jit);
+ m->jit = NULL;
+ free(m->height);
+ m->height = NULL;
free(m->storage);
m->storage = NULL;
m->code = NULL;
@@ -1074,6 +1077,10 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
REFUSE("out of memory", 0);
}
out->code = out->storage;
+ if (!(out->height = calloc(out->code_count + 1, 1))) {
+ result = PURE_ALLOCATION;
+ REFUSE("out of memory", 0);
+ }
out->constant_offset = (uint32_t *)(out->code + out->code_count);
out->constant_length = out->constant_offset + out->constants;
out->constant_data = (unsigned char *)(out->constant_length + out->constants);
@@ -1370,6 +1377,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
/* The lists an index state refers to: their inserts tell where (move_indices). */
for (uint32_t i = 0; i < out->states; ++i)
if (out->state_type[i] == PURE_STATE_INDEX) out->state_indexed[out->state_ref[i]] = true;
+ out->jit = pure_jit_make(out); /* none: the executor runs it all */
return PURE_OK;
refused:
if (error_size) snprintf(error, error_size, "%s (%u)", why, bad);
@@ -1501,44 +1509,12 @@ int pure_module_column(const PureModule *m, uint32_t table, const char *name) {
static double minimum(double a, double b) { return a < b || (a == b && signbit(a)) ? a : b; }
static double maximum(double a, double b) { return a > b || (a == b && !signbit(a)) ? a : b; }
-typedef struct { union { double num; int64_t i64; }; PureBytes bytes; } Slot;
/* Whether two values that order as order (below zero: the first below) are related as a relation says (PureRelation). */
static bool related(uint32_t relation, int order) {
return relation == PURE_LESS ? order < 0 : relation == PURE_LESS_EQUAL ? order <= 0 : relation == PURE_GREATER ? order > 0 :
relation == PURE_GREATER_EQUAL ? order >= 0 : relation == PURE_EQUAL ? order == 0 : order != 0;
}
-/* A walk (EACH): the list, the item it runs for, where it ends (items the
- * walk removes move it), and whether that item left. */
-typedef struct { uint32_t list; size_t at, end; bool removed; } Walk;
-/* A loop that runs in its block's run (LOOP, EACH): its block and where
- * the code around it ends, the arena as it began, a walk's walk and the
- * event's walk and row around it, a count's values (low + k of count) and
- * slot. */
-typedef struct {
- uint32_t start, end, outer;
- size_t mark;
- bool walking, integer;
- Walk walk, *around;
- size_t row;
- uint64_t count, k;
- PureValue low, *slot;
-} Loop;
-typedef struct {
- PureApp *app;
- const PureInputShape *shape; /* message, or NULL */
- const char *key; /* a keyed message's key */
- int64_t base; /* the revision a message's edit was made against, or -1 */
- PureArena *arena;
- double dt;
- size_t row; /* the row a column is evaluated for (in a fold, the fold's item) */
- size_t outer; /* in a fold, the row of the column it runs in */
- Walk *walk; /* the walk the code runs in, or NULL */
- PureValue *locals; /* the event's locals (a handler's, and what runs in it), or NULL */
- bool arguments; /* FIELD reads the arguments given for a view, not the message */
- bool modified, exists;
- uint32_t (*spans)[2]; /* a message's variable fields: start and size in the payload */
-} Event;
static PureResult fault(PureApp *app, PureFault kind, uint32_t pc) {
app->fault = kind;
@@ -2453,554 +2429,660 @@ static bool fold_shaped(const PureModule *m, PureValue *values, const PureOp *op
return true;
}
-static PureResult run(const PureModule *m, PureValue *values, PureBlock block, Event *ev, Slot *result) {
- Slot stack[PURE_MODULE_STACK];
- size_t sp = 0;
- PureApp *app = ev->app;
- PureArena *arena = ev->arena;
- PureResult status;
- PureFault kind;
- const PureOp *op = NULL;
- /* The loops open here (LOOP, EACH), innermost last; end is where the innermost's block ends. */
- Loop loops[PURE_MODULE_NESTING];
- unsigned depth = 0;
- for (uint32_t pc = block.start, end = block.start + block.length;;) {
- if (pc >= end) { /* the block ran; or a loop's ran once: again, or the loop ends (its end may be its outer's) */
- if (!depth) break;
- Loop *loop = &loops[depth - 1];
- arena->used = loop->mark;
- if (loop->walking) {
- Walk *walk = &loop->walk;
- if (!walk->removed) ++walk->at;
- walk->removed = false;
- if (walk->at < walk->end && walk->at < pure_list_count(app->lists[walk->list])) {
- ev->row = walk->at;
- pc = loop->start;
- continue;
- }
- ev->walk = loop->around, ev->row = loop->row;
- } else if (++loop->k < loop->count) {
- if (!charge(app, (uint64_t)(loop->end - loop->start) + 1)) {
- kind = PURE_FAULT_WORK;
- op = &m->code[loop->start - 1];
- goto failed;
- }
- if (loop->integer) loop->slot->i64 = (int64_t)((uint64_t)loop->low.i64 + loop->k);
- else loop->slot->num = loop->low.num + (double)loop->k;
- pc = loop->start;
- continue;
- }
- end = loop->outer;
- --depth;
- continue;
+/* EACH at pc: its walk opened (its block next), or nothing to walk (past its block). */
+static inline __attribute__((always_inline)) int enter_each(Frame *frame, const PureOp *op, uint32_t pc, uint32_t *next) {
+ const PureModule *m = frame->m;
+ PureApp *app = frame->app;
+ Event *ev = frame->ev;
+ Slot *stack = frame->stack;
+ uint32_t arg = op->arg, length = arg >> 16;
+ /* The block that follows runs for each item the list has, as the
+ * handler's code with ROW the item; the list only shrinks meanwhile.
+ * It runs here, a loop opened (its end takes pc back), not by recursion. */
+ Loop *loop = &frame->loops[frame->depth];
+ loop->walking = true;
+ loop->walk = (Walk){arg >> 8 & 0xFF, 0, SIZE_MAX, false};
+ if (arg & PURE_EACH_SLICED) { /* the items with keys from low up to before high */
+ Slot high = stack[--frame->sp], low = stack[--frame->sp];
+ slice_of(app->lists[loop->walk.list], m->state_key[loop->walk.list] - 1u, low, high, &loop->walk.at, &loop->walk.end);
+ }
+ size_t items = pure_list_count(app->lists[loop->walk.list]); /* as written: what it starts with, the list only shrinking */
+ if (!charge(app, (uint64_t)length * ((loop->walk.end < items ? loop->walk.end : items) - loop->walk.at))) {
+ frame->kind = PURE_FAULT_WORK;
+ frame->fault_pc = pc;
+ return STEP_FAULT;
+ }
+ if (!(arg & PURE_EACH_SLICED) && op->range) /* a where that bounds the key: only the items in bounds */
+ range_bounds(m, frame->values, &m->range[op->range - 1], app->lists[loop->walk.list], m->state_key[loop->walk.list] - 1u,
+ ev, &loop->walk.at, &loop->walk.end);
+ *next = pc + 1;
+ if (!(loop->walk.at < loop->walk.end && loop->walk.at < items)) { /* none */
+ *next += length;
+ return STEP_NEXT;
+ }
+ loop->start = pc + 1, loop->end = pc + 1 + length, loop->mark = frame->arena->used;
+ loop->around = ev->walk, loop->row = ev->row;
+ ev->walk = &loop->walk;
+ ev->row = loop->walk.at;
+ loop->outer = frame->end, frame->end = loop->end, ++frame->depth;
+ return STEP_NEXT;
+}
+
+/* LOOP at pc: its block once for each value from low up to before high,
+ * the slot holding it; its work counts as it runs (the LOOP too, so an
+ * empty block counts). It runs here, as a walk does. */
+static inline __attribute__((always_inline)) int enter_loop(Frame *frame, const PureOp *op, uint32_t pc, uint32_t *next) {
+ Slot *stack = frame->stack;
+ uint32_t arg = op->arg, length = arg >> 16;
+ Slot high = stack[--frame->sp], low = stack[--frame->sp];
+ bool integer = arg & PURE_LOOP_INTEGER;
+ uint64_t count = 0;
+ frame->fault_pc = pc;
+ if (integer) count = high.i64 > low.i64 ? (uint64_t)high.i64 - (uint64_t)low.i64 : 0;
+ else if (!(fabs(low.num) <= 0x1p53 && fabs(high.num) <= 0x1p53 && low.num == floor(low.num) &&
+ high.num == floor(high.num))) {
+ frame->kind = PURE_FAULT_LIMIT;
+ return STEP_FAULT;
+ } else count = high.num > low.num ? (uint64_t)(high.num - low.num) : 0;
+ *next = pc + 1;
+ if (!count) {
+ *next += length;
+ return STEP_NEXT;
+ }
+ if (!charge(frame->app, (uint64_t)length + 1)) {
+ frame->kind = PURE_FAULT_WORK;
+ return STEP_FAULT;
+ }
+ Loop *loop = &frame->loops[frame->depth];
+ loop->walking = false;
+ loop->integer = integer;
+ loop->count = count, loop->k = 0;
+ loop->low = (PureValue){.i64 = low.i64};
+ loop->slot = &frame->ev->locals[arg & 0xFF];
+ if (integer) loop->slot->i64 = low.i64;
+ else loop->slot->num = low.num;
+ loop->start = pc + 1, loop->end = pc + 1 + length, loop->mark = frame->arena->used;
+ loop->outer = frame->end, frame->end = loop->end, ++frame->depth;
+ return STEP_NEXT;
+}
+
+/* BREAK: the innermost loop ends, the code after its block next. */
+static inline __attribute__((always_inline)) uint32_t leave_loop(Frame *frame) {
+ Loop *loop = &frame->loops[--frame->depth];
+ frame->arena->used = loop->mark;
+ if (loop->walking) frame->ev->walk = loop->around, frame->ev->row = loop->row;
+ frame->end = loop->outer;
+ return loop->end;
+}
+
+/* The innermost loop's block ran once: again (*next its start), or the loop
+ * ends (*next stays: its end may be its outer's too). */
+static inline __attribute__((always_inline)) int iterate(Frame *frame, uint32_t *next) {
+ Loop *loop = &frame->loops[frame->depth - 1];
+ PureApp *app = frame->app;
+ frame->arena->used = loop->mark;
+ if (loop->walking) {
+ Walk *walk = &loop->walk;
+ if (!walk->removed) ++walk->at;
+ walk->removed = false;
+ if (walk->at < walk->end && walk->at < pure_list_count(app->lists[walk->list])) {
+ frame->ev->row = walk->at;
+ *next = loop->start;
+ return STEP_NEXT;
}
- op = &m->code[pc++];
- uint32_t arg = op->arg;
- Slot *top = sp ? &stack[sp - 1] : stack;
- switch (op->op) {
- case PURE_OP_CONST: stack[sp++].i64 = op->integer; continue; /* the bits: a number's, a bool's or an i64's */
- case PURE_OP_LOAD: stack[sp++].i64 = values[arg].i64; continue;
- case PURE_OP_DT: stack[sp++].num = ev->dt; continue;
- case PURE_OP_NOW: stack[sp++].num = app->now; continue;
- case PURE_OP_FLOOR: top->num = floor(top->num); continue;
- case PURE_OP_STORE: case PURE_OP_CUR_STORE: values[arg].i64 = stack[--sp].i64; continue;
- case PURE_OP_JZ: if (stack[--sp].num == 0) pc = arg; continue;
- case PURE_OP_JMP: pc = arg; continue;
- case PURE_OP_NEG: top->num = -top->num; continue;
- case PURE_OP_NOT: top->num = top->num == 0; continue;
- case PURE_OP_CLAMP: {
- double high = stack[--sp].num, low = stack[--sp].num;
- if (low > high) { kind = PURE_FAULT_CLAMP; goto failed; }
- stack[sp - 1].num = maximum(low, minimum(stack[sp - 1].num, high));
- continue;
+ frame->ev->walk = loop->around, frame->ev->row = loop->row;
+ } else if (++loop->k < loop->count) {
+ if (!charge(app, (uint64_t)(loop->end - loop->start) + 1)) {
+ frame->kind = PURE_FAULT_WORK;
+ frame->fault_pc = loop->start - 1;
+ return STEP_FAULT;
}
- case PURE_OP_FIELD: {
- const PureField *field = &m->field[ev->shape->field_start + arg];
- const unsigned char *given = ev->arguments ? app->arguments.data : app->payload.data;
- if (field->type == PURE_FIELD_U32) stack[sp++].num = read32(given + field->offset);
- else if (field->type == PURE_FIELD_NUMBER) stack[sp++].num = read_double(given + field->offset);
- else if (field->type == PURE_FIELD_BOOL) stack[sp++].num = given[field->offset];
- else if (field->type == PURE_FIELD_I64) stack[sp++].i64 = read_i64(given + field->offset);
- else stack[sp++].bytes = (PureBytes){ev->arguments ? FROM_ARGUMENTS : FROM_PAYLOAD, 0, {0, 0}, ev->spans[arg][0],
- ev->spans[arg][1]};
- continue;
+ if (loop->integer) loop->slot->i64 = (int64_t)((uint64_t)loop->low.i64 + loop->k);
+ else loop->slot->num = loop->low.num + (double)loop->k;
+ *next = loop->start;
+ return STEP_NEXT;
+ }
+ frame->end = loop->outer;
+ --frame->depth;
+ return STEP_NEXT;
+}
+
+#define NEXT do { frame->sp = sp; return STEP_NEXT; } while (0)
+#define FAILED do { frame->sp = sp; frame->kind = kind; frame->fault_pc = pc; return STEP_FAULT; } while (0)
+#define REFUSED do { frame->sp = sp; frame->status = status; frame->fault_pc = pc; return STEP_REFUSED; } while (0)
+#define STOPPED(why) do { frame->status = (why); return STEP_RETURN; } while (0)
+
+/* The instruction op at pc; *next the one to run after it. */
+static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp *op, uint32_t pc, uint32_t *next) {
+ const PureModule *m = frame->m;
+ PureValue *values = frame->values;
+ Event *ev = frame->ev;
+ PureApp *app = frame->app;
+ PureArena *arena = frame->arena;
+ Slot *stack = frame->stack;
+ size_t sp = frame->sp;
+ PureResult status;
+ PureFault kind;
+ uint32_t arg = op->arg;
+ Slot *top = sp ? &stack[sp - 1] : stack;
+ *next = pc + 1;
+ switch (op->op) {
+ case PURE_OP_JZ:
+ frame->sp = sp - 1;
+ if (top->num == 0) *next = arg;
+ return STEP_NEXT;
+ case PURE_OP_JMP: *next = arg; return STEP_NEXT;
+ case PURE_OP_EACH: return enter_each(frame, op, pc, next);
+ case PURE_OP_LOOP: return enter_loop(frame, op, pc, next);
+ case PURE_OP_BREAK: *next = leave_loop(frame); return STEP_NEXT;
+ case PURE_OP_CONST: stack[sp++].i64 = op->integer; NEXT; /* the bits: a number's, a bool's or an i64's */
+ case PURE_OP_LOAD: stack[sp++].i64 = values[arg].i64; NEXT;
+ case PURE_OP_DT: stack[sp++].num = ev->dt; NEXT;
+ case PURE_OP_NOW: stack[sp++].num = app->now; NEXT;
+ case PURE_OP_FLOOR: top->num = floor(top->num); NEXT;
+ case PURE_OP_STORE: case PURE_OP_CUR_STORE: values[arg].i64 = stack[--sp].i64; NEXT;
+ case PURE_OP_NEG: top->num = -top->num; NEXT;
+ case PURE_OP_NOT: top->num = top->num == 0; NEXT;
+ case PURE_OP_CLAMP: {
+ double high = stack[--sp].num, low = stack[--sp].num;
+ if (low > high) { kind = PURE_FAULT_CLAMP; FAILED; }
+ stack[sp - 1].num = maximum(low, minimum(stack[sp - 1].num, high));
+ NEXT;
+ }
+ case PURE_OP_FIELD: {
+ const PureField *field = &m->field[ev->shape->field_start + arg];
+ const unsigned char *given = ev->arguments ? app->arguments.data : app->payload.data;
+ if (field->type == PURE_FIELD_U32) stack[sp++].num = read32(given + field->offset);
+ else if (field->type == PURE_FIELD_NUMBER) stack[sp++].num = read_double(given + field->offset);
+ else if (field->type == PURE_FIELD_BOOL) stack[sp++].num = given[field->offset];
+ else if (field->type == PURE_FIELD_I64) stack[sp++].i64 = read_i64(given + field->offset);
+ else stack[sp++].bytes = (PureBytes){ev->arguments ? FROM_ARGUMENTS : FROM_PAYLOAD, 0, {0, 0}, ev->spans[arg][0],
+ ev->spans[arg][1]};
+ NEXT;
+ }
+ case PURE_OP_DOC: case PURE_OP_DOC_SIZE: {
+ if (op->op == PURE_OP_DOC && !ev->exists) { kind = PURE_FAULT_NOT_FOUND; FAILED; }
+ PureVersion version = app->data.version(app->data.context, app->document);
+ if (op->op == PURE_OP_DOC) stack[sp++].bytes = (PureBytes){FROM_DOCUMENT, 0, version, 0, version.bytes};
+ else stack[sp++].num = (double)version.bytes;
+ NEXT;
+ }
+ case PURE_OP_LEN: case PURE_OP_TEXT_BYTES: top->num = (double)top->bytes.length; NEXT;
+ case PURE_OP_REPLACE: {
+ PureBytes insert = stack[--sp].bytes;
+ double last = stack[--sp].num, first = stack[--sp].num;
+ double size = (double)app->data.version(app->data.context, app->document).bytes;
+ if (first != floor(first) || last != floor(last) || first < 0 || last < first || last > size) {
+ kind = PURE_FAULT_LIMIT;
+ FAILED;
}
- case PURE_OP_DOC: case PURE_OP_DOC_SIZE: {
- if (op->op == PURE_OP_DOC && !ev->exists) { kind = PURE_FAULT_NOT_FOUND; goto failed; }
- PureVersion version = app->data.version(app->data.context, app->document);
- if (op->op == PURE_OP_DOC) stack[sp++].bytes = (PureBytes){FROM_DOCUMENT, 0, version, 0, version.bytes};
- else stack[sp++].num = (double)version.bytes;
- continue;
+ PureSpan span;
+ size_t mark = arena->used;
+ if ((status = contiguous(app, arena, insert, &span)) == PURE_OK)
+ status = app->data.splice(app->data.context, app->document, (size_t)first, (size_t)(last - first), span);
+ arena->used = mark;
+ if (status != PURE_OK) REFUSED;
+ ev->modified = ev->exists = true;
+ NEXT;
+ }
+ /* A reply outlives the arena mark of later statements: keep it by value. */
+ case PURE_OP_REPLY: app->reply = stack[--sp].bytes; app->has_reply = true; NEXT;
+ case PURE_OP_REQUIRE: if (stack[--sp].num == 0) { kind = PURE_FAULT_LIMIT; FAILED; } NEXT;
+ case PURE_OP_TEXT_CONST:
+ stack[sp++].bytes = (PureBytes){FROM_CONSTANT, 0, {0, 0}, m->constant_offset[arg], m->constant_length[arg]};
+ NEXT;
+ case PURE_OP_TEXT_LOAD: stack[sp++].bytes = text_of(app, arg); NEXT;
+ case PURE_OP_TEXT_INPUT: stack[sp++].bytes = (PureBytes){FROM_PAYLOAD, 0, {0, 0}, 0, app->payload.size}; NEXT;
+ case PURE_OP_NUM_TEXT: {
+ char digits[32];
+ size_t length = arg == 2 ? (size_t)snprintf(digits, sizeof(digits), "%" PRId64, top->i64)
+ : arg ? (size_t)snprintf(digits, sizeof(digits), "%s", top->num ? "true" : "false")
+ : pure_format_number(top->num, digits);
+ if ((status = keep_bytes(arena, digits, length, &top->bytes)) != PURE_OK) STOPPED(status);
+ NEXT;
+ }
+ case PURE_OP_CONCAT: {
+ PureBytes right = stack[--sp].bytes, left = stack[sp - 1].bytes;
+ size_t start = arena->used;
+ if ((status = append_value(app, arena, left)) != PURE_OK ||
+ (status = append_value(app, arena, right)) != PURE_OK) STOPPED(status);
+ stack[sp - 1].bytes = (PureBytes){FROM_ARENA, 0, {0, 0}, start, left.length + right.length};
+ NEXT;
+ }
+ case PURE_OP_CHARS: case PURE_OP_LINES: case PURE_OP_TEXT_FROM: {
+ Measure count = {0, op->op == PURE_OP_LINES ? 1 : op->op == PURE_OP_TEXT_FROM ? 2 : 0, 0, true};
+ if ((status = visit_value(app, arena, top->bytes, measure, &count)) != PURE_OK) STOPPED(status);
+ if (op->op != PURE_OP_TEXT_FROM) top->num = (double)(count.count + (op->op == PURE_OP_LINES));
+ else if (!count.valid || !pure_utf8_complete(count.utf8)) { kind = PURE_FAULT_ENCODING; FAILED; }
+ NEXT;
+ }
+ /* Positions: a cursor or an index, with the text or list it is in. */
+ case PURE_OP_CUR_LOAD: case PURE_OP_INDEX_LOAD:
+ stack[sp].num = values[arg].num;
+ stack[sp++].bytes.owner = m->state_ref[arg];
+ NEXT;
+ case PURE_OP_CUR_START: case PURE_OP_CUR_END: case PURE_OP_INDEX_FIRST: case PURE_OP_INDEX_LAST:
+ case PURE_OP_ROW: {
+ size_t last = op->op == PURE_OP_CUR_END ? pure_draft_size(app->texts[arg]) :
+ op->op == PURE_OP_INDEX_LAST ? pure_list_count(app->lists[arg]) : 0;
+ stack[sp].num = op->op == PURE_OP_ROW ? (double)ev->row :
+ op->op == PURE_OP_INDEX_LAST && last ? (double)(last - 1) : (double)last;
+ stack[sp++].bytes.owner = (uint16_t)arg;
+ NEXT;
+ }
+ case PURE_OP_OUTER_ROW:
+ stack[sp].num = (double)ev->outer;
+ stack[sp++].bytes.owner = (uint16_t)arg;
+ NEXT;
+ case PURE_OP_LOCAL_LOAD:
+ stack[sp].i64 = ev->locals[arg & 0xFF].i64; /* the bits: a number's, an index's, a bool's or an i64's */
+ stack[sp++].bytes.owner = (uint16_t)(arg >> 16); /* an index's list */
+ NEXT;
+ case PURE_OP_LOCAL_STORE: ev->locals[arg & 0xFF].i64 = stack[--sp].i64; NEXT;
+ case PURE_OP_CUR_MOVE: case PURE_OP_INDEX_MOVE: {
+ double count = stack[--sp].num;
+ Slot *at = &stack[sp - 1];
+ if (count != floor(count)) { kind = PURE_FAULT_LIMIT; FAILED; }
+ if (op->op == PURE_OP_INDEX_MOVE) {
+ size_t items = pure_list_count(app->lists[at->bytes.owner]);
+ at->num = maximum(0, minimum(at->num + count, items ? (double)(items - 1) : 0));
+ NEXT;
}
- case PURE_OP_LEN: case PURE_OP_TEXT_BYTES: top->num = (double)top->bytes.length; continue;
- case PURE_OP_REPLACE: {
- PureBytes insert = stack[--sp].bytes;
- double last = stack[--sp].num, first = stack[--sp].num;
- double size = (double)app->data.version(app->data.context, app->document).bytes;
- if (first != floor(first) || last != floor(last) || first < 0 || last < first || last > size) {
+ PureDraft *text = app->texts[at->bytes.owner];
+ PureVersion version = pure_draft_version(text);
+ at->num = (double)(arg == PURE_UNIT_CHAR ? pure_text_step(text, version, (size_t)at->num, count)
+ : pure_text_line_step(text, version, (size_t)at->num, count));
+ NEXT;
+ }
+ case PURE_OP_CUR_COUNT: {
+ PureDraft *text = app->texts[top->bytes.owner];
+ top->num = (double)pure_text_count(text, pure_draft_version(text), (size_t)top->num, arg);
+ NEXT;
+ }
+ case PURE_OP_TEXT_INSERT: case PURE_OP_TEXT_SET: {
+ PureBytes insert = stack[--sp].bytes;
+ size_t at = op->op == PURE_OP_TEXT_INSERT ? (size_t)stack[--sp].num : 0;
+ size_t removed = op->op == PURE_OP_TEXT_SET ? pure_draft_size(app->texts[arg]) : 0;
+ PureSpan span;
+ size_t mark = arena->used;
+ Held before = text_held(app, arg);
+ if ((status = contiguous(app, arena, insert, &span)) == PURE_OK)
+ status = pure_draft_splice(app->texts[arg], at, removed, span);
+ arena->used = mark;
+ if (status != PURE_OK) REFUSED;
+ if (!grows(app, before, text_held(app, arg))) { kind = PURE_FAULT_MEMORY; FAILED; }
+ if (op->op == PURE_OP_TEXT_SET) {
+ for (uint32_t c = 0; c < m->states; ++c)
+ if (m->state_type[c] == PURE_STATE_CURSOR && m->state_ref[c] == arg) values[c].num = 0;
+ } else move_cursors(m, values, arg, at, 0, span.size);
+ NEXT;
+ }
+ case PURE_OP_TEXT_REMOVE: {
+ double y = stack[--sp].num, x = stack[--sp].num;
+ size_t first = (size_t)minimum(x, y), last = (size_t)maximum(x, y);
+ if ((status = pure_draft_splice(app->texts[arg], first, last - first, (PureSpan){0})) != PURE_OK) REFUSED;
+ (void)grows(app, (Held){last - first, 0}, (Held){0, 0}); /* it only shrinks */
+ if (last > first) move_cursors(m, values, arg, first, last - first, 0);
+ NEXT;
+ }
+ case PURE_OP_TEXT_LINE: {
+ PureDraft *text = app->texts[arg];
+ PureVersion version = pure_draft_version(text);
+ size_t first = pure_text_line_start(text, version, (size_t)top->num);
+ size_t last = pure_text_line_end(text, version, (size_t)top->num);
+ top->bytes = (PureBytes){FROM_TEXT, (uint16_t)arg, version, first, last - first};
+ NEXT;
+ }
+ case PURE_OP_BYTES_FROM: case PURE_OP_INDEX_NUM: NEXT; /* the same bytes or position, retyped */
+ case PURE_OP_LIST_COUNT: stack[sp++].num = (double)pure_list_count(app->lists[arg]); NEXT;
+ case PURE_OP_SEND: {
+ const PureInputShape *output = &m->input[arg];
+ size_t base = sp - output->field_count, size = output->prefix;
+ sp = base;
+ for (uint32_t f = 0; f < output->field_count; ++f) {
+ uint8_t type = m->field[output->field_start + f].type;
+ double number = stack[base + f].num;
+ if (type == PURE_FIELD_BYTES || type == PURE_FIELD_TEXT) size += (size_t)stack[base + f].bytes.length;
+ else if (type == PURE_FIELD_U32 && !(number >= 0 && number <= UINT32_MAX && number == floor(number))) {
kind = PURE_FAULT_LIMIT;
- goto failed;
+ FAILED;
}
- PureSpan span;
- size_t mark = arena->used;
- if ((status = contiguous(app, arena, insert, &span)) == PURE_OK)
- status = app->data.splice(app->data.context, app->document, (size_t)first, (size_t)(last - first), span);
- arena->used = mark;
- if (status != PURE_OK) goto refused;
- ev->modified = ev->exists = true;
- continue;
- }
- /* A reply outlives the arena mark of later statements: keep it by value. */
- case PURE_OP_REPLY: app->reply = stack[--sp].bytes; app->has_reply = true; continue;
- case PURE_OP_REQUIRE: if (stack[--sp].num == 0) { kind = PURE_FAULT_LIMIT; goto failed; } continue;
- case PURE_OP_TEXT_CONST:
- stack[sp++].bytes = (PureBytes){FROM_CONSTANT, 0, {0, 0}, m->constant_offset[arg], m->constant_length[arg]};
- continue;
- case PURE_OP_TEXT_LOAD: stack[sp++].bytes = text_of(app, arg); continue;
- case PURE_OP_TEXT_INPUT: stack[sp++].bytes = (PureBytes){FROM_PAYLOAD, 0, {0, 0}, 0, app->payload.size}; continue;
- case PURE_OP_NUM_TEXT: {
- char digits[32];
- size_t length = arg == 2 ? (size_t)snprintf(digits, sizeof(digits), "%" PRId64, top->i64)
- : arg ? (size_t)snprintf(digits, sizeof(digits), "%s", top->num ? "true" : "false")
- : pure_format_number(top->num, digits);
- if ((status = keep_bytes(arena, digits, length, &top->bytes)) != PURE_OK) return status;
- continue;
- }
- case PURE_OP_CONCAT: {
- PureBytes right = stack[--sp].bytes, left = stack[sp - 1].bytes;
- size_t start = arena->used;
- if ((status = append_value(app, arena, left)) != PURE_OK ||
- (status = append_value(app, arena, right)) != PURE_OK) return status;
- stack[sp - 1].bytes = (PureBytes){FROM_ARENA, 0, {0, 0}, start, left.length + right.length};
- continue;
}
- case PURE_OP_CHARS: case PURE_OP_LINES: case PURE_OP_TEXT_FROM: {
- Measure count = {0, op->op == PURE_OP_LINES ? 1 : op->op == PURE_OP_TEXT_FROM ? 2 : 0, 0, true};
- if ((status = visit_value(app, arena, top->bytes, measure, &count)) != PURE_OK) return status;
- if (op->op != PURE_OP_TEXT_FROM) top->num = (double)(count.count + (op->op == PURE_OP_LINES));
- else if (!count.valid || !pure_utf8_complete(count.utf8)) { kind = PURE_FAULT_ENCODING; goto failed; }
- continue;
+ if (size > UINT32_MAX) { kind = PURE_FAULT_LIMIT; FAILED; }
+ Held before = list_held(app, app->outbox);
+ if ((status = send(app, arg, stack + base, arena, size)) != PURE_OK) STOPPED(status);
+ if (!grows(app, before, list_held(app, app->outbox))) { kind = PURE_FAULT_MEMORY; FAILED; }
+ NEXT;
+ }
+ case PURE_OP_LIST_APPEND: case PURE_OP_LIST_INSERT: {
+ PureList *list = app->lists[arg];
+ const PureRecord *record = &m->record[m->state_ref[arg]];
+ size_t base = sp - record->count, count = pure_list_count(list), at = count;
+ sp = base;
+ if (op->op == PURE_OP_LIST_INSERT && (at = (size_t)stack[--sp].num) > count) {
+ kind = PURE_FAULT_NOT_FOUND;
+ FAILED;
}
- /* Positions: a cursor or an index, with the text or list it is in. */
- case PURE_OP_CUR_LOAD: case PURE_OP_INDEX_LOAD:
- stack[sp].num = values[arg].num;
- stack[sp++].bytes.owner = m->state_ref[arg];
- continue;
- case PURE_OP_CUR_START: case PURE_OP_CUR_END: case PURE_OP_INDEX_FIRST: case PURE_OP_INDEX_LAST:
- case PURE_OP_ROW: {
- size_t last = op->op == PURE_OP_CUR_END ? pure_draft_size(app->texts[arg]) :
- op->op == PURE_OP_INDEX_LAST ? pure_list_count(app->lists[arg]) : 0;
- stack[sp].num = op->op == PURE_OP_ROW ? (double)ev->row :
- op->op == PURE_OP_INDEX_LAST && last ? (double)(last - 1) : (double)last;
- stack[sp++].bytes.owner = (uint16_t)arg;
- continue;
- }
- case PURE_OP_OUTER_ROW:
- stack[sp].num = (double)ev->outer;
- stack[sp++].bytes.owner = (uint16_t)arg;
- continue;
- case PURE_OP_LOCAL_LOAD:
- stack[sp].i64 = ev->locals[arg & 0xFF].i64; /* the bits: a number's, an index's, a bool's or an i64's */
- stack[sp++].bytes.owner = (uint16_t)(arg >> 16); /* an index's list */
- continue;
- case PURE_OP_LOCAL_STORE: ev->locals[arg & 0xFF].i64 = stack[--sp].i64; continue;
- case PURE_OP_CUR_MOVE: case PURE_OP_INDEX_MOVE: {
- double count = stack[--sp].num;
- Slot *at = &stack[sp - 1];
- if (count != floor(count)) { kind = PURE_FAULT_LIMIT; goto failed; }
- if (op->op == PURE_OP_INDEX_MOVE) {
- size_t items = pure_list_count(app->lists[at->bytes.owner]);
- at->num = maximum(0, minimum(at->num + count, items ? (double)(items - 1) : 0));
- continue;
- }
- PureDraft *text = app->texts[at->bytes.owner];
- PureVersion version = pure_draft_version(text);
- at->num = (double)(arg == PURE_UNIT_CHAR ? pure_text_step(text, version, (size_t)at->num, count)
- : pure_text_line_step(text, version, (size_t)at->num, count));
- continue;
- }
- case PURE_OP_CUR_COUNT: {
- PureDraft *text = app->texts[top->bytes.owner];
- top->num = (double)pure_text_count(text, pure_draft_version(text), (size_t)top->num, arg);
- continue;
- }
- case PURE_OP_TEXT_INSERT: case PURE_OP_TEXT_SET: {
- PureBytes insert = stack[--sp].bytes;
- size_t at = op->op == PURE_OP_TEXT_INSERT ? (size_t)stack[--sp].num : 0;
- size_t removed = op->op == PURE_OP_TEXT_SET ? pure_draft_size(app->texts[arg]) : 0;
- PureSpan span;
- size_t mark = arena->used;
- Held before = text_held(app, arg);
- if ((status = contiguous(app, arena, insert, &span)) == PURE_OK)
- status = pure_draft_splice(app->texts[arg], at, removed, span);
- arena->used = mark;
- if (status != PURE_OK) goto refused;
- if (!grows(app, before, text_held(app, arg))) { kind = PURE_FAULT_MEMORY; goto failed; }
- if (op->op == PURE_OP_TEXT_SET) {
- for (uint32_t c = 0; c < m->states; ++c)
- if (m->state_type[c] == PURE_STATE_CURSOR && m->state_ref[c] == arg) values[c].num = 0;
- } else move_cursors(m, values, arg, at, 0, span.size);
- continue;
- }
- case PURE_OP_TEXT_REMOVE: {
- double y = stack[--sp].num, x = stack[--sp].num;
- size_t first = (size_t)minimum(x, y), last = (size_t)maximum(x, y);
- if ((status = pure_draft_splice(app->texts[arg], first, last - first, (PureSpan){0})) != PURE_OK) goto refused;
- (void)grows(app, (Held){last - first, 0}, (Held){0, 0}); /* it only shrinks */
- if (last > first) move_cursors(m, values, arg, first, last - first, 0);
- continue;
- }
- case PURE_OP_TEXT_LINE: {
- PureDraft *text = app->texts[arg];
- PureVersion version = pure_draft_version(text);
- size_t first = pure_text_line_start(text, version, (size_t)top->num);
- size_t last = pure_text_line_end(text, version, (size_t)top->num);
- top->bytes = (PureBytes){FROM_TEXT, (uint16_t)arg, version, first, last - first};
- continue;
- }
- case PURE_OP_BYTES_FROM: case PURE_OP_INDEX_NUM: continue; /* the same bytes or position, retyped */
- case PURE_OP_LIST_COUNT: stack[sp++].num = (double)pure_list_count(app->lists[arg]); continue;
- case PURE_OP_SEND: {
- const PureInputShape *output = &m->input[arg];
- size_t base = sp - output->field_count, size = output->prefix;
- sp = base;
- for (uint32_t f = 0; f < output->field_count; ++f) {
- uint8_t type = m->field[output->field_start + f].type;
- double number = stack[base + f].num;
- if (type == PURE_FIELD_BYTES || type == PURE_FIELD_TEXT) size += (size_t)stack[base + f].bytes.length;
- else if (type == PURE_FIELD_U32 && !(number >= 0 && number <= UINT32_MAX && number == floor(number))) {
- kind = PURE_FAULT_LIMIT;
- goto failed;
- }
- }
- if (size > UINT32_MAX) { kind = PURE_FAULT_LIMIT; goto failed; }
- Held before = list_held(app, app->outbox);
- if ((status = send(app, arg, stack + base, arena, size)) != PURE_OK) return status;
- if (!grows(app, before, list_held(app, app->outbox))) { kind = PURE_FAULT_MEMORY; goto failed; }
- continue;
- }
- case PURE_OP_LIST_APPEND: case PURE_OP_LIST_INSERT: {
- PureList *list = app->lists[arg];
- const PureRecord *record = &m->record[m->state_ref[arg]];
- size_t base = sp - record->count, count = pure_list_count(list), at = count;
- sp = base;
- if (op->op == PURE_OP_LIST_INSERT && (at = (size_t)stack[--sp].num) > count) {
- kind = PURE_FAULT_NOT_FOUND;
- goto failed;
- }
- /* Room for every text first, so no span moves while the next is copied. */
- size_t mark = arena->used, total = 0;
- for (unsigned f = 0; f < record->count; ++f)
- if (text_field(m, arg, f)) total += (size_t)stack[base + f].bytes.length;
- if (!reserve(arena, total)) return PURE_ALLOCATION;
- PureCell cells[PURE_RECORD_FIELDS];
- for (unsigned f = 0; f < record->count; ++f) {
- PureSpan span = {0};
- if (text_field(m, arg, f) && (status = contiguous(app, arena, stack[base + f].bytes, &span)) != PURE_OK)
- return status;
- cells[f] = (PureCell){.i64 = stack[base + f].i64, .text = (unsigned char *)span.data, .size = span.size};
- }
- Held before = list_held(app, list);
- status = PURE_INVALID;
- if (m->state_key[arg]) { /* a keyed list places the item by its key: found and placed at once when kept by a number */
- bool found;
- /* where it went matters to an index state's alone */
- if ((status = pure_list_add(list, cells, undo_for(app, arg), m->state_indexed[arg] ? &at : NULL, &found)) ==
- PURE_INVALID) {
- unsigned key = m->state_key[arg] - 1u;
- at = pure_list_seek(list, key, cells[key], &found);
- }
- if (found) { arena->used = mark; kind = PURE_FAULT_EXISTS; goto failed; }
- }
- if (status == PURE_INVALID) status = pure_list_insert(list, at, cells, undo_for(app, arg));
- arena->used = mark;
- if (status != PURE_OK) goto refused;
- move_indices(m, values, arg, pure_list_count(list), at, true);
- if (!grows(app, before, list_held(app, list))) { kind = PURE_FAULT_MEMORY; goto failed; }
- continue;
- }
- case PURE_OP_LIST_REMOVE: {
- size_t at = (size_t)stack[--sp].num;
- Held before = list_held(app, app->lists[arg]);
- if ((status = pure_list_remove(app->lists[arg], at, undo_for(app, arg))) != PURE_OK) goto refused;
- /* the item goes, its cells kept to undo and its record to save */
- if (!grows(app, before, list_held(app, app->lists[arg]))) { kind = PURE_FAULT_MEMORY; goto failed; }
- move_indices(m, values, arg, pure_list_count(app->lists[arg]), at, false);
- if (ev->walk && ev->walk->list == arg) { /* the walk goes on with the item after */
- if (at < ev->walk->at) --ev->walk->at;
- else if (at == ev->walk->at) ev->walk->removed = true;
- if (at < ev->walk->end && ev->walk->end != SIZE_MAX) --ev->walk->end;
- }
- continue;
- }
- case PURE_OP_ITEM_GET: {
- uint32_t list = list_of(arg), field = item_field(arg);
- size_t at = (size_t)top->num;
- if (at >= pure_list_count(app->lists[list])) { kind = PURE_FAULT_NOT_FOUND; goto failed; }
- PureCell cell = pure_list_get(app->lists[list], at, field);
- /* A text is copied: the item may change or go before the value is used. */
- if (!(op->plan & PLAN_TEXT)) top->i64 = cell.i64; /* the bits: a number's, a bool's or an i64's (else a text) */
- else if ((status = keep_bytes(arena, cell.text, cell.size, &top->bytes)) != PURE_OK) return status;
- continue;
- }
- case PURE_OP_ITEM_SET: {
- uint32_t list = list_of(arg), field = item_field(arg);
- Slot value = stack[--sp];
- size_t at = (size_t)stack[--sp].num, mark = arena->used;
- Held before = list_held(app, app->lists[list]);
+ /* Room for every text first, so no span moves while the next is copied. */
+ size_t mark = arena->used, total = 0;
+ for (unsigned f = 0; f < record->count; ++f)
+ if (text_field(m, arg, f)) total += (size_t)stack[base + f].bytes.length;
+ if (!reserve(arena, total)) STOPPED(PURE_ALLOCATION);
+ PureCell cells[PURE_RECORD_FIELDS];
+ for (unsigned f = 0; f < record->count; ++f) {
PureSpan span = {0};
- status = op->plan & PLAN_TEXT ? contiguous(app, arena, value.bytes, &span) : PURE_OK;
- if (status == PURE_OK)
- status = pure_list_set(app->lists[list], at, field,
- (PureCell){.i64 = value.i64, .text = (unsigned char *)span.data, .size = span.size},
- undo_for(app, list));
- arena->used = mark;
- if (status != PURE_OK) goto refused;
- if (!grows(app, before, list_held(app, app->lists[list]))) { kind = PURE_FAULT_MEMORY; goto failed; }
- continue;
+ if (text_field(m, arg, f) && (status = contiguous(app, arena, stack[base + f].bytes, &span)) != PURE_OK)
+ STOPPED(status);
+ cells[f] = (PureCell){.i64 = stack[base + f].i64, .text = (unsigned char *)span.data, .size = span.size};
}
- case PURE_OP_INDEX_STORE: {
- /* An index from before a remove may be past the end. */
- double position = stack[--sp].num;
- if (position && position >= (double)pure_list_count(app->lists[m->state_ref[arg]])) {
- kind = PURE_FAULT_NOT_FOUND;
- goto failed;
+ Held before = list_held(app, list);
+ status = PURE_INVALID;
+ if (m->state_key[arg]) { /* a keyed list places the item by its key: found and placed at once when kept by a number */
+ bool found;
+ /* where it went matters to an index state's alone */
+ if ((status = pure_list_add(list, cells, undo_for(app, arg), m->state_indexed[arg] ? &at : NULL, &found)) ==
+ PURE_INVALID) {
+ unsigned key = m->state_key[arg] - 1u;
+ at = pure_list_seek(list, key, cells[key], &found);
}
- values[arg].num = position;
- continue;
+ if (found) { arena->used = mark; kind = PURE_FAULT_EXISTS; FAILED; }
}
- case PURE_OP_FOLD: {
- /* The block that follows runs once per item, as a column of the
- * list: a leaf of it at a time when planned so. */
- PureBlock inner = {pc, arg >> 16};
- PureList *list = app->lists[arg >> 8 & 0xFF];
- pc += inner.length;
- Fold fold = {.how = arg & PURE_FOLD_KIND, .integer = arg & PURE_FOLD_INTEGER};
- Event per = *ev;
- per.outer = ev->row;
- size_t from = 0, to = pure_list_count(list);
- if (arg & PURE_FOLD_SLICED) { /* the items with keys from low up to before high */
- Slot high = stack[--sp], low = stack[--sp];
- slice_of(list, m->state_key[arg >> 8 & 0xFF] - 1u, low, high, &from, &to);
- }
- if (!charge(app, (uint64_t)inner.length * (to - from))) { kind = PURE_FAULT_WORK; goto failed; } /* as written */
- if (!op->shape || !fold_shaped(m, values, op, list, &per, &fold)) {
- if (op->range) /* only the items a range of keys allows can count */
- range_bounds(m, values, &m->range[op->range - 1], list, m->state_key[arg >> 8 & 0xFF] - 1u, &per, &from, &to);
- status = op->plan ? fold_runs(m, values, inner, list, &per, &fold, from, to)
- : fold_rows(m, values, inner, &per, from, to, &fold);
- free(fold.seen.keys);
- if (status != PURE_OK) return status;
- }
- if (fold.overflow) { kind = PURE_FAULT_OVERFLOW; goto failed; }
- if (!fold.any && (fold.how == PURE_FOLD_MAX || fold.how == PURE_FOLD_MIN || fold.how == PURE_FOLD_FIRST)) {
- kind = PURE_FAULT_NOT_FOUND;
- goto failed;
- }
- if (fold.integer && fold.how != PURE_FOLD_DISTINCT) stack[sp].i64 = fold.whole;
- else if (!isfinite(fold.total)) { kind = PURE_FAULT_NON_FINITE; goto failed; }
- else stack[sp].num = fold.total;
- stack[sp++].bytes.owner = (uint16_t)(arg >> 8 & 0xFF); /* first: the list the index is in */
- continue;
+ if (status == PURE_INVALID) status = pure_list_insert(list, at, cells, undo_for(app, arg));
+ arena->used = mark;
+ if (status != PURE_OK) REFUSED;
+ move_indices(m, values, arg, pure_list_count(list), at, true);
+ if (!grows(app, before, list_held(app, list))) { kind = PURE_FAULT_MEMORY; FAILED; }
+ NEXT;
+ }
+ case PURE_OP_LIST_REMOVE: {
+ size_t at = (size_t)stack[--sp].num;
+ Held before = list_held(app, app->lists[arg]);
+ if ((status = pure_list_remove(app->lists[arg], at, undo_for(app, arg))) != PURE_OK) REFUSED;
+ /* the item goes, its cells kept to undo and its record to save */
+ if (!grows(app, before, list_held(app, app->lists[arg]))) { kind = PURE_FAULT_MEMORY; FAILED; }
+ move_indices(m, values, arg, pure_list_count(app->lists[arg]), at, false);
+ if (ev->walk && ev->walk->list == arg) { /* the walk goes on with the item after */
+ if (at < ev->walk->at) --ev->walk->at;
+ else if (at == ev->walk->at) ev->walk->removed = true;
+ if (at < ev->walk->end && ev->walk->end != SIZE_MAX) --ev->walk->end;
}
- case PURE_OP_EACH: {
- /* The block that follows runs for each item the list has, as the
- * handler's code with ROW the item; the list only shrinks meanwhile.
- * It runs here, a loop opened (its end takes pc back), not by recursion. */
- uint32_t length = arg >> 16;
- Loop *loop = &loops[depth];
- loop->walking = true;
- loop->walk = (Walk){arg >> 8 & 0xFF, 0, SIZE_MAX, false};
- if (arg & PURE_EACH_SLICED) { /* the items with keys from low up to before high */
- Slot high = stack[--sp], low = stack[--sp];
- slice_of(app->lists[loop->walk.list], m->state_key[loop->walk.list] - 1u, low, high, &loop->walk.at,
- &loop->walk.end);
- }
- size_t items = pure_list_count(app->lists[loop->walk.list]); /* as written: what it starts with, the list only shrinking */
- if (!charge(app, (uint64_t)length * ((loop->walk.end < items ? loop->walk.end : items) - loop->walk.at))) {
- kind = PURE_FAULT_WORK;
- goto failed;
- }
- if (!(arg & PURE_EACH_SLICED) && op->range) /* a where that bounds the key: only the items in bounds */
- range_bounds(m, values, &m->range[op->range - 1], app->lists[loop->walk.list], m->state_key[loop->walk.list] - 1u,
- ev, &loop->walk.at, &loop->walk.end);
- if (!(loop->walk.at < loop->walk.end && loop->walk.at < items)) { pc += length; continue; } /* none */
- loop->start = pc, loop->end = pc + length, loop->mark = arena->used;
- loop->around = ev->walk, loop->row = ev->row;
- ev->walk = &loop->walk;
- ev->row = loop->walk.at;
- loop->outer = end, end = loop->end, ++depth;
- continue;
+ NEXT;
+ }
+ case PURE_OP_ITEM_GET: {
+ uint32_t list = list_of(arg), field = item_field(arg);
+ size_t at = (size_t)top->num;
+ if (at >= pure_list_count(app->lists[list])) { kind = PURE_FAULT_NOT_FOUND; FAILED; }
+ PureCell cell = pure_list_get(app->lists[list], at, field);
+ /* A text is copied: the item may change or go before the value is used. */
+ if (!(op->plan & PLAN_TEXT)) top->i64 = cell.i64; /* the bits: a number's, a bool's or an i64's (else a text) */
+ else if ((status = keep_bytes(arena, cell.text, cell.size, &top->bytes)) != PURE_OK) STOPPED(status);
+ NEXT;
+ }
+ case PURE_OP_ITEM_SET: {
+ uint32_t list = list_of(arg), field = item_field(arg);
+ Slot value = stack[--sp];
+ size_t at = (size_t)stack[--sp].num, mark = arena->used;
+ Held before = list_held(app, app->lists[list]);
+ PureSpan span = {0};
+ status = op->plan & PLAN_TEXT ? contiguous(app, arena, value.bytes, &span) : PURE_OK;
+ if (status == PURE_OK)
+ status = pure_list_set(app->lists[list], at, field,
+ (PureCell){.i64 = value.i64, .text = (unsigned char *)span.data, .size = span.size},
+ undo_for(app, list));
+ arena->used = mark;
+ if (status != PURE_OK) REFUSED;
+ if (!grows(app, before, list_held(app, app->lists[list]))) { kind = PURE_FAULT_MEMORY; FAILED; }
+ NEXT;
+ }
+ case PURE_OP_INDEX_STORE: {
+ /* An index from before a remove may be past the end. */
+ double position = stack[--sp].num;
+ if (position && position >= (double)pure_list_count(app->lists[m->state_ref[arg]])) {
+ kind = PURE_FAULT_NOT_FOUND;
+ FAILED;
}
- case PURE_OP_LOOP: {
- /* The block that follows runs once for each value from low up to before high, the slot holding it; its
- * work counts as it runs (the LOOP too, so an empty block counts). It runs here, as a walk does. */
- uint32_t length = arg >> 16;
+ values[arg].num = position;
+ NEXT;
+ }
+ case PURE_OP_FOLD: {
+ /* The block that follows runs once per item, as a column of the
+ * list: a leaf of it at a time when planned so. */
+ PureBlock inner = {pc + 1, arg >> 16};
+ *next = pc + 1 + inner.length;
+ PureList *list = app->lists[arg >> 8 & 0xFF];
+ Fold fold = {.how = arg & PURE_FOLD_KIND, .integer = arg & PURE_FOLD_INTEGER};
+ Event per = *ev;
+ per.outer = ev->row;
+ size_t from = 0, to = pure_list_count(list);
+ if (arg & PURE_FOLD_SLICED) { /* the items with keys from low up to before high */
Slot high = stack[--sp], low = stack[--sp];
- bool integer = arg & PURE_LOOP_INTEGER;
- uint64_t count = 0;
- if (integer) count = high.i64 > low.i64 ? (uint64_t)high.i64 - (uint64_t)low.i64 : 0;
- else if (!(fabs(low.num) <= 0x1p53 && fabs(high.num) <= 0x1p53 && low.num == floor(low.num) &&
- high.num == floor(high.num))) { kind = PURE_FAULT_LIMIT; goto failed; }
- else count = high.num > low.num ? (uint64_t)(high.num - low.num) : 0;
- if (!count) { pc += length; continue; }
- if (!charge(app, (uint64_t)length + 1)) { kind = PURE_FAULT_WORK; goto failed; }
- Loop *loop = &loops[depth];
- loop->walking = false;
- loop->integer = integer;
- loop->count = count, loop->k = 0;
- loop->low = (PureValue){.i64 = low.i64};
- loop->slot = &ev->locals[arg & 0xFF];
- if (integer) loop->slot->i64 = low.i64;
- else loop->slot->num = low.num;
- loop->start = pc, loop->end = pc + length, loop->mark = arena->used;
- loop->outer = end, end = loop->end, ++depth;
- continue;
+ slice_of(list, m->state_key[arg >> 8 & 0xFF] - 1u, low, high, &from, &to);
}
- case PURE_OP_BREAK: { /* the innermost loop ends: the code after its block runs next */
- Loop *loop = &loops[--depth];
- arena->used = loop->mark;
- if (loop->walking) ev->walk = loop->around, ev->row = loop->row;
- pc = loop->end;
- end = loop->outer;
- continue;
+ if (!charge(app, (uint64_t)inner.length * (to - from))) { kind = PURE_FAULT_WORK; FAILED; } /* as written */
+ if (!op->shape || !fold_shaped(m, values, op, list, &per, &fold)) {
+ if (op->range) /* only the items a range of keys allows can count */
+ range_bounds(m, values, &m->range[op->range - 1], list, m->state_key[arg >> 8 & 0xFF] - 1u, &per, &from, &to);
+ status = op->plan ? fold_runs(m, values, inner, list, &per, &fold, from, to)
+ : fold_rows(m, values, inner, &per, from, to, &fold);
+ free(fold.seen.keys);
+ if (status != PURE_OK) STOPPED(status);
}
- case PURE_OP_TEXT_COMPARE: {
- /* Texts order by their bytes: a shorter text before a longer one it begins. */
- PureBytes right = stack[--sp].bytes, left = stack[sp - 1].bytes;
- size_t mark = arena->used;
- PureSpan a, b;
- if (!reserve(arena, (size_t)(left.length + right.length))) return PURE_ALLOCATION;
- if ((status = contiguous(app, arena, left, &a)) != PURE_OK ||
- (status = contiguous(app, arena, right, &b)) != PURE_OK) return status;
- size_t shorter = a.size < b.size ? a.size : b.size;
- int order = shorter ? memcmp(a.data, b.data, shorter) : 0;
- if (!order) order = (a.size > b.size) - (a.size < b.size);
- arena->used = mark;
- stack[sp - 1].num = related(arg, order);
- continue;
+ if (fold.overflow) { kind = PURE_FAULT_OVERFLOW; FAILED; }
+ if (!fold.any && (fold.how == PURE_FOLD_MAX || fold.how == PURE_FOLD_MIN || fold.how == PURE_FOLD_FIRST)) {
+ kind = PURE_FAULT_NOT_FOUND;
+ FAILED;
}
- case PURE_OP_LIST_FIND: case PURE_OP_LIST_HAS: {
- /* A keyed list's item by key, found by halving: FIND gives its index, HAS whether it is there. */
- unsigned key = m->state_key[arg] - 1u;
- size_t mark = arena->used;
- PureCell cell = {.i64 = top->i64};
- if (op->plan & PLAN_TEXT) { /* a text key (set at load) */
- PureSpan span;
- if ((status = contiguous(app, arena, top->bytes, &span)) != PURE_OK) return status;
- cell = (PureCell){.text = (unsigned char *)span.data, .size = span.size};
- }
- bool found;
- if (op->plan & PLAN_JOINED) { /* FIND, then ITEM_GET of what it found: the field by key */
- const PureOp *get = &m->code[pc];
- PureCell value;
- found = pure_list_lookup(app->lists[arg], key, cell, item_field(get->arg), &value);
- arena->used = mark;
- if (!found) { kind = PURE_FAULT_NOT_FOUND; goto failed; }
- ++pc;
- if (!(get->plan & PLAN_TEXT)) top->i64 = value.i64;
- else if ((status = keep_bytes(arena, value.text, value.size, &top->bytes)) != PURE_OK) return status;
- continue;
- }
- /* An event's list keeps its keys as a set: HAS asks it alone (020). */
- if (op->op == PURE_OP_LIST_HAS && pure_list_contains(app->lists[arg], cell.num, &found)) {
- top->num = found;
- continue;
- }
- size_t at = pure_list_seek(app->lists[arg], key, cell, &found);
- arena->used = mark;
- if (op->op == PURE_OP_LIST_HAS) { top->num = found; continue; }
- if (!found) { kind = PURE_FAULT_NOT_FOUND; goto failed; }
- top->num = (double)at;
- top->bytes.owner = (uint16_t)arg;
- continue;
+ if (fold.integer && fold.how != PURE_FOLD_DISTINCT) stack[sp].i64 = fold.whole;
+ else if (!isfinite(fold.total)) { kind = PURE_FAULT_NON_FINITE; FAILED; }
+ else stack[sp].num = fold.total;
+ stack[sp++].bytes.owner = (uint16_t)(arg >> 8 & 0xFF); /* first: the list the index is in */
+ NEXT;
+ }
+ case PURE_OP_TEXT_COMPARE: {
+ /* Texts order by their bytes: a shorter text before a longer one it begins. */
+ PureBytes right = stack[--sp].bytes, left = stack[sp - 1].bytes;
+ size_t mark = arena->used;
+ PureSpan a, b;
+ if (!reserve(arena, (size_t)(left.length + right.length))) STOPPED(PURE_ALLOCATION);
+ if ((status = contiguous(app, arena, left, &a)) != PURE_OK ||
+ (status = contiguous(app, arena, right, &b)) != PURE_OK) STOPPED(status);
+ size_t shorter = a.size < b.size ? a.size : b.size;
+ int order = shorter ? memcmp(a.data, b.data, shorter) : 0;
+ if (!order) order = (a.size > b.size) - (a.size < b.size);
+ arena->used = mark;
+ stack[sp - 1].num = related(arg, order);
+ NEXT;
+ }
+ case PURE_OP_LIST_FIND: case PURE_OP_LIST_HAS: {
+ /* A keyed list's item by key, found by halving: FIND gives its index, HAS whether it is there. */
+ unsigned key = m->state_key[arg] - 1u;
+ size_t mark = arena->used;
+ PureCell cell = {.i64 = top->i64};
+ if (op->plan & PLAN_TEXT) { /* a text key (set at load) */
+ PureSpan span;
+ if ((status = contiguous(app, arena, top->bytes, &span)) != PURE_OK) STOPPED(status);
+ cell = (PureCell){.text = (unsigned char *)span.data, .size = span.size};
}
- case PURE_OP_PICK: stack[sp] = stack[sp - 1 - arg]; ++sp; continue;
- case PURE_OP_SLIDE: stack[sp - 1 - arg] = stack[sp - 1]; sp -= arg; continue;
- /* i64s: exact, and failing as OVERFLOW past their range */
- case PURE_OP_I64_ADD: case PURE_OP_I64_SUB: case PURE_OP_I64_MUL: {
- int64_t b = stack[--sp].i64, a = stack[sp - 1].i64, r;
- bool over = op->op == PURE_OP_I64_ADD ? __builtin_add_overflow(a, b, &r) :
- op->op == PURE_OP_I64_SUB ? __builtin_sub_overflow(a, b, &r) : __builtin_mul_overflow(a, b, &r);
- if (over) { kind = PURE_FAULT_OVERFLOW; goto failed; }
- stack[sp - 1].i64 = r;
- continue;
+ bool found;
+ if (op->plan & PLAN_JOINED) { /* FIND, then ITEM_GET of what it found: the field by key */
+ const PureOp *get = &m->code[pc + 1];
+ PureCell value;
+ found = pure_list_lookup(app->lists[arg], key, cell, item_field(get->arg), &value);
+ arena->used = mark;
+ if (!found) { kind = PURE_FAULT_NOT_FOUND; FAILED; }
+ *next = pc + 2;
+ if (!(get->plan & PLAN_TEXT)) top->i64 = value.i64;
+ else if ((status = keep_bytes(arena, value.text, value.size, &top->bytes)) != PURE_OK) STOPPED(status);
+ NEXT;
}
- case PURE_OP_I64_DIV: case PURE_OP_I64_MOD: { /* floored: the remainder takes the divisor's sign */
- int64_t b = stack[--sp].i64, a = stack[sp - 1].i64;
- if (b == 0) { kind = PURE_FAULT_DIVIDE; goto failed; }
- if (b == -1) { /* the least i64 over -1 is past the range; any remainder by -1 is 0 */
- if (op->op == PURE_OP_I64_DIV && a == INT64_MIN) { kind = PURE_FAULT_OVERFLOW; goto failed; }
- stack[sp - 1].i64 = op->op == PURE_OP_I64_DIV ? -a : 0;
- continue;
- }
- int64_t q = a / b, r = a % b;
- if (r != 0 && (r < 0) != (b < 0)) { --q; r += b; }
- stack[sp - 1].i64 = op->op == PURE_OP_I64_DIV ? q : r;
- continue;
+ /* An event's list keeps its keys as a set: HAS asks it alone (020). */
+ if (op->op == PURE_OP_LIST_HAS && pure_list_contains(app->lists[arg], cell.num, &found)) {
+ top->num = found;
+ NEXT;
}
- case PURE_OP_I64_NEG:
- if (top->i64 == INT64_MIN) { kind = PURE_FAULT_OVERFLOW; goto failed; }
- top->i64 = -top->i64;
- continue;
- case PURE_OP_I64_MIN: case PURE_OP_I64_MAX: {
- int64_t b = stack[--sp].i64, a = stack[sp - 1].i64;
- stack[sp - 1].i64 = (op->op == PURE_OP_I64_MIN ? b < a : b > a) ? b : a;
- continue;
+ size_t at = pure_list_seek(app->lists[arg], key, cell, &found);
+ arena->used = mark;
+ if (op->op == PURE_OP_LIST_HAS) { top->num = found; NEXT; }
+ if (!found) { kind = PURE_FAULT_NOT_FOUND; FAILED; }
+ top->num = (double)at;
+ top->bytes.owner = (uint16_t)arg;
+ NEXT;
+ }
+ case PURE_OP_PICK: stack[sp] = stack[sp - 1 - arg]; ++sp; NEXT;
+ case PURE_OP_SLIDE: stack[sp - 1 - arg] = stack[sp - 1]; sp -= arg; NEXT;
+ /* i64s: exact, and failing as OVERFLOW past their range */
+ case PURE_OP_I64_ADD: case PURE_OP_I64_SUB: case PURE_OP_I64_MUL: {
+ int64_t b = stack[--sp].i64, a = stack[sp - 1].i64, r;
+ bool over = op->op == PURE_OP_I64_ADD ? __builtin_add_overflow(a, b, &r) :
+ op->op == PURE_OP_I64_SUB ? __builtin_sub_overflow(a, b, &r) : __builtin_mul_overflow(a, b, &r);
+ if (over) { kind = PURE_FAULT_OVERFLOW; FAILED; }
+ stack[sp - 1].i64 = r;
+ NEXT;
+ }
+ case PURE_OP_I64_DIV: case PURE_OP_I64_MOD: { /* floored: the remainder takes the divisor's sign */
+ int64_t b = stack[--sp].i64, a = stack[sp - 1].i64;
+ if (b == 0) { kind = PURE_FAULT_DIVIDE; FAILED; }
+ if (b == -1) { /* the least i64 over -1 is past the range; any remainder by -1 is 0 */
+ if (op->op == PURE_OP_I64_DIV && a == INT64_MIN) { kind = PURE_FAULT_OVERFLOW; FAILED; }
+ stack[sp - 1].i64 = op->op == PURE_OP_I64_DIV ? -a : 0;
+ NEXT;
}
- case PURE_OP_I64_COMPARE: {
- int64_t b = stack[--sp].i64, a = stack[sp - 1].i64;
- stack[sp - 1].num = related(arg, (a > b) - (a < b));
- continue;
+ int64_t q = a / b, r = a % b;
+ if (r != 0 && (r < 0) != (b < 0)) { --q; r += b; }
+ stack[sp - 1].i64 = op->op == PURE_OP_I64_DIV ? q : r;
+ NEXT;
+ }
+ case PURE_OP_I64_NEG:
+ if (top->i64 == INT64_MIN) { kind = PURE_FAULT_OVERFLOW; FAILED; }
+ top->i64 = -top->i64;
+ NEXT;
+ case PURE_OP_I64_MIN: case PURE_OP_I64_MAX: {
+ int64_t b = stack[--sp].i64, a = stack[sp - 1].i64;
+ stack[sp - 1].i64 = (op->op == PURE_OP_I64_MIN ? b < a : b > a) ? b : a;
+ NEXT;
+ }
+ case PURE_OP_I64_COMPARE: {
+ int64_t b = stack[--sp].i64, a = stack[sp - 1].i64;
+ stack[sp - 1].num = related(arg, (a > b) - (a < b));
+ NEXT;
+ }
+ case PURE_OP_I64_FROM: case PURE_OP_NUM_FROM: { /* a whole number in range; an i64 within 2^53 */
+ PureCell cell = {.i64 = top->i64};
+ bool to_i64 = op->op == PURE_OP_I64_FROM;
+ if (!pure_cell_as(to_i64 ? PURE_CELL_NUMBER : PURE_CELL_INTEGER, to_i64 ? PURE_CELL_INTEGER : PURE_CELL_NUMBER, &cell)) {
+ kind = PURE_FAULT_LIMIT;
+ FAILED;
}
- case PURE_OP_I64_FROM: case PURE_OP_NUM_FROM: { /* a whole number in range; an i64 within 2^53 */
- PureCell cell = {.i64 = top->i64};
- bool to_i64 = op->op == PURE_OP_I64_FROM;
- if (!pure_cell_as(to_i64 ? PURE_CELL_NUMBER : PURE_CELL_INTEGER, to_i64 ? PURE_CELL_INTEGER : PURE_CELL_NUMBER, &cell)) {
- kind = PURE_FAULT_LIMIT;
- goto failed;
- }
- top->i64 = cell.i64;
+ top->i64 = cell.i64;
+ NEXT;
+ }
+ default: break;
+ }
+ double b = stack[--sp].num, a = stack[sp - 1].num, value;
+ switch (op->op) {
+ case PURE_OP_ADD: value = a + b; break;
+ case PURE_OP_SUB: value = a - b; break;
+ case PURE_OP_MUL: value = a * b; break;
+ case PURE_OP_DIV:
+ if (b == 0) { kind = PURE_FAULT_DIVIDE; FAILED; }
+ value = a / b;
+ break;
+ case PURE_OP_LT: value = a < b; break;
+ case PURE_OP_LE: value = a <= b; break;
+ case PURE_OP_GT: value = a > b; break;
+ case PURE_OP_GE: value = a >= b; break;
+ case PURE_OP_EQ: value = a == b; break;
+ case PURE_OP_NE: value = a != b; break;
+ case PURE_OP_AND: value = a != 0 && b != 0; break;
+ case PURE_OP_OR: value = a != 0 || b != 0; break;
+ case PURE_OP_MIN: value = minimum(a, b); break;
+ case PURE_OP_MOD: /* the divisor's sign, as floor division leaves it (a zero too) */
+ if (b == 0) { kind = PURE_FAULT_DIVIDE; FAILED; }
+ value = fmod(a, b);
+ if (value != 0 && (value < 0) != (b < 0)) value += b;
+ else if (value == 0) value = copysign(0.0, b);
+ break;
+ default: value = maximum(a, b); break;
+ }
+ if (!isfinite(value)) { kind = PURE_FAULT_NON_FINITE; FAILED; }
+ stack[sp - 1].num = value;
+ NEXT;
+}
+
+/* What a run ends with: its value (a view's, or a fold block's numbers), a fault, or a result as it is. */
+static inline __attribute__((always_inline)) PureResult finish(Frame *frame, int how, Slot *result) {
+ if (how == STEP_NEXT) {
+ if (result) memcpy(result, frame->stack, frame->sp * sizeof(*frame->stack));
+ return PURE_OK;
+ }
+ if (how == STEP_RETURN) return frame->status;
+ if (how == STEP_REFUSED) {
+ PureResult status = frame->status;
+ frame->kind = status == PURE_LIMIT ? PURE_FAULT_LIMIT : status == PURE_NOT_FOUND ? PURE_FAULT_NOT_FOUND : PURE_FAULT_NONE;
+ if (frame->kind == PURE_FAULT_NONE) return status;
+ }
+ return fault(frame->app, frame->kind, frame->fault_pc);
+}
+
+#undef NEXT
+#undef FAILED
+#undef REFUSED
+#undef STOPPED
+
+/* The steps compiled code calls (module-run.h). */
+int pure_run_step(Frame *frame, uint32_t pc) {
+ uint32_t next;
+ return step(frame, &frame->m->code[pc], pc, &next);
+}
+
+int pure_run_each(Frame *frame, uint32_t pc) {
+ uint32_t next;
+ unsigned depth = frame->depth;
+ int how = enter_each(frame, &frame->m->code[pc], pc, &next);
+ return how != STEP_NEXT ? how : frame->depth > depth ? STEP_NEXT : STEP_SKIP;
+}
+
+int pure_run_loop(Frame *frame, uint32_t pc) {
+ uint32_t next;
+ unsigned depth = frame->depth;
+ int how = enter_loop(frame, &frame->m->code[pc], pc, &next);
+ return how != STEP_NEXT ? how : frame->depth > depth ? STEP_NEXT : STEP_SKIP;
+}
+
+void pure_run_break(Frame *frame) { (void)leave_loop(frame); }
+
+int pure_run_iterate(Frame *frame) {
+ uint32_t next = UINT32_MAX;
+ int how = iterate(frame, &next);
+ return how != STEP_NEXT ? how : next != UINT32_MAX ? STEP_NEXT : STEP_DONE;
+}
+
+/* A block run by its compiled code. */
+static __attribute__((noinline)) PureResult run_compiled(PureCompiled code, const PureModule *m, PureValue *values,
+ PureBlock block, Event *ev, Slot *result) {
+ Slot stack[PURE_MODULE_STACK];
+ Loop loops[PURE_MODULE_NESTING];
+ Frame frame = {.m = m, .values = values, .ev = ev, .app = ev->app, .arena = ev->arena, .stack = stack,
+ .end = block.start + block.length, .loops = loops};
+ return finish(&frame, code(&frame), result);
+}
+
+static PureResult run(const PureModule *m, PureValue *values, PureBlock block, Event *ev, Slot *result) {
+ PureCompiled code = m->jit ? pure_jit_code(m->jit, block) : NULL;
+ if (code) return run_compiled(code, m, values, block, ev, result);
+ Slot stack[PURE_MODULE_STACK];
+ Loop loops[PURE_MODULE_NESTING];
+ Frame frame = {.m = m, .values = values, .ev = ev, .app = ev->app, .arena = ev->arena, .stack = stack,
+ .end = block.start + block.length, .loops = loops};
+ int how = STEP_NEXT;
+ for (uint32_t pc = block.start;;) {
+ if (pc >= frame.end) { /* the block ran; or a loop's ran once: again, or the loop ends (its end may be its outer's) */
+ if (!frame.depth || (how = iterate(&frame, &pc)) != STEP_NEXT) break;
continue;
}
- default: break;
- }
- double b = stack[--sp].num, a = stack[sp - 1].num, value;
- switch (op->op) {
- case PURE_OP_ADD: value = a + b; break;
- case PURE_OP_SUB: value = a - b; break;
- case PURE_OP_MUL: value = a * b; break;
- case PURE_OP_DIV:
- if (b == 0) { kind = PURE_FAULT_DIVIDE; goto failed; }
- value = a / b;
- break;
- case PURE_OP_LT: value = a < b; break;
- case PURE_OP_LE: value = a <= b; break;
- case PURE_OP_GT: value = a > b; break;
- case PURE_OP_GE: value = a >= b; break;
- case PURE_OP_EQ: value = a == b; break;
- case PURE_OP_NE: value = a != b; break;
- case PURE_OP_AND: value = a != 0 && b != 0; break;
- case PURE_OP_OR: value = a != 0 || b != 0; break;
- case PURE_OP_MIN: value = minimum(a, b); break;
- case PURE_OP_MOD: /* the divisor's sign, as floor division leaves it (a zero too) */
- if (b == 0) { kind = PURE_FAULT_DIVIDE; goto failed; }
- value = fmod(a, b);
- if (value != 0 && (value < 0) != (b < 0)) value += b;
- else if (value == 0) value = copysign(0.0, b);
- break;
- default: value = maximum(a, b); break;
- }
- if (!isfinite(value)) { kind = PURE_FAULT_NON_FINITE; goto failed; }
- stack[sp - 1].num = value;
+ if ((how = step(&frame, &m->code[pc], pc, &pc)) != STEP_NEXT) break;
}
- if (result) memcpy(result, stack, sp * sizeof(*stack)); /* a view's value, or a fold block's numbers */
- return PURE_OK;
-refused:
- kind = status == PURE_LIMIT ? PURE_FAULT_LIMIT : status == PURE_NOT_FOUND ? PURE_FAULT_NOT_FOUND : PURE_FAULT_NONE;
- if (kind == PURE_FAULT_NONE) return status;
-failed:
- return fault(app, kind, (uint32_t)(op - m->code));
+ return finish(&frame, how, result);
}
PURE_COLD static PureResult new_text(PureDraft **out, const PureModule *m, uint32_t constant) {
diff --git a/core/module.h b/core/module.h
index 9562358..9b425b2 100644
--- a/core/module.h
+++ b/core/module.h
@@ -255,6 +255,8 @@ typedef struct {
* (numbers by value, texts by bytes), each key once, and keys do not change. */
uint8_t state_key[PURE_MODULE_STATES];
bool state_indexed[PURE_MODULE_STATES]; /* list: an index state refers to it (set at load) */
+ uint8_t *height; /* the operand stack's height at each instruction, from its block's start (the verifier's) */
+ struct PureJit *jit; /* its blocks compiled (module-jit.c, docs/design/021), or NULL */
PureInputShape input[PURE_MODULE_INPUTS];
PureField field[PURE_MODULE_FIELDS];
PureValue initial[PURE_MODULE_STATES];
diff --git a/core/test-module.c b/core/test-module.c
index a509349..b7fea70 100644
--- a/core/test-module.c
+++ b/core/test-module.c
@@ -3,6 +3,7 @@
#define _DARWIN_C_SOURCE /* mkdtemp, which _POSIX_C_SOURCE hides there */
#endif
#include "module.h"
+#include "module-run.h"
#include "module-save.h"
#include "module-store.h"
#include "module-text.h"
@@ -3171,6 +3172,8 @@ static void lane_folds(void) {
}
planned += module_a.view_plan[VIEWS];
memset(module_b.view_plan, 0, sizeof(module_b.view_plan));
+ pure_jit_free(module_b.jit); /* its code changed: compiled again (021) */
+ module_b.jit = pure_jit_make(&module_b);
PureApp a, b;
CHECK(pure_app_start(&a, &module_a, (PureData){0}) == PURE_OK && pure_app_start(&b, &module_b, (PureData){0}) == PURE_OK);
pure_app_work(&a, UINT64_MAX); /* counted, never past it */
@@ -3765,6 +3768,8 @@ static void ranges(void) {
!module_a.code[118].range && module_a.code[118].plan);
for (uint32_t i = 0; i < module_b.code_count; ++i) module_b.code[i].range = 0;
memset(module_b.view_range, 0, sizeof(module_b.view_range));
+ pure_jit_free(module_b.jit); /* its code changed: compiled again (021) */
+ module_b.jit = pure_jit_make(&module_b);
uint32_t seed = 0x7a45e11u;
unsigned compared = 0, walked = 0;
for (unsigned round = 0; round < 60; ++round) {