aboutsummaryrefslogtreecommitdiffstats
path: root/core/module-run.h
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/module-run.h
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/module-run.h')
-rw-r--r--core/module-run.h94
1 files changed, 94 insertions, 0 deletions
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