aboutsummaryrefslogtreecommitdiffstats
path: root/research/experiments/045-proof-compiler
diff options
context:
space:
mode:
Diffstat (limited to 'research/experiments/045-proof-compiler')
-rw-r--r--research/experiments/045-proof-compiler/2026-10-19-break.jsonl10
-rw-r--r--research/experiments/045-proof-compiler/cost.c132
-rw-r--r--research/experiments/045-proof-compiler/events.c103
-rw-r--r--research/experiments/045-proof-compiler/native-hook.patch25
-rw-r--r--research/experiments/045-proof-compiler/ops-count.patch62
-rw-r--r--research/experiments/045-proof-compiler/stitch.c108
-rw-r--r--research/experiments/045-proof-compiler/unit.c47
-rw-r--r--research/experiments/045-proof-compiler/units/append.aup7
-rw-r--r--research/experiments/045-proof-compiler/units/arith.aup7
-rw-r--r--research/experiments/045-proof-compiler/units/call.aup11
-rw-r--r--research/experiments/045-proof-compiler/units/mod.aup7
-rw-r--r--research/experiments/045-proof-compiler/units/read-direct.aup14
-rw-r--r--research/experiments/045-proof-compiler/units/read.aup14
-rw-r--r--research/experiments/045-proof-compiler/variants/v0-empty.patch34
-rw-r--r--research/experiments/045-proof-compiler/variants/v1-lists.patch14
-rw-r--r--research/experiments/045-proof-compiler/variants/v2-text.patch12
16 files changed, 607 insertions, 0 deletions
diff --git a/research/experiments/045-proof-compiler/2026-10-19-break.jsonl b/research/experiments/045-proof-compiler/2026-10-19-break.jsonl
new file mode 100644
index 0000000..07aef0d
--- /dev/null
+++ b/research/experiments/045-proof-compiler/2026-10-19-break.jsonl
@@ -0,0 +1,10 @@
+{"what": "(a) instructions the executor runs, UAX #14 (ops-count.patch: by opcode, and reads of the same list, version, field and place within an event)", "code_points": 61285, "executed_per_point": 549.9, "moves_per_point": 205.5, "constants_per_point": 76.7, "same_reads_per_point": 16.4, "left_per_point": 251.3, "factor": 2.19, "bar": 4, "passed": false, "note": "the budget counts blocks as written: 1,774 a point; executed 549.9"}
+{"what": "(b) the executor stitched (stitch.c, native-hook.patch: step() on each constant instruction, a function a basic block), cost.c (answers checked first)", "interpreted": {"instructions_per_point": 40702.7, "cycles_per_point": 15565.8}, "stitched": {"instructions_per_point": 28601.5, "cycles_per_point": 11849.9}, "instruction_factor": 1.42, "cycle_factor": 1.31, "bar": 3, "passed": false}
+{"what": "Vega C (text/linebreak.c) on the same lines", "instructions_per_point": 220.2, "cycles_per_point": 82.4}
+{"what": "machine instructions an executed instruction", "interpreted": 74.0, "stitched": 52.0, "dispatch_and_decode": 22.0}
+{"what": "variants of the app (events.c, executor): machine instructions a code point", "empty_handler": 1583.2, "lists_built": 24233.7, "and_text_record": 27184.8, "whole": 40172.9}
+{"what": "units (unit.c, units/*.aup): machine instructions an operation, PURE_JIT=0", "arith_loop_batches": 34.7, "arith_loop_executor": 248.0, "append_let_list_batches": 35.7, "stored_list_read_with_mod": 435.8, "stored_list_read": 330.8, "mod": 75.4, "block_fn_call": 1375.1}
+{"mode":"interp","lines":18672,"code_points":61285,"rounds":3,"jit":"0","c_instructions_per_point":220.2,"c_cycles_per_point":81.9,"pure_instructions_per_point":40702.7,"pure_cycles_per_point":15591.7}
+{"mode":"native","lines":18672,"code_points":61285,"rounds":3,"jit":"0","c_instructions_per_point":220.2,"c_cycles_per_point":82.7,"pure_instructions_per_point":28600.9,"pure_cycles_per_point":11868.1}
+{"mode":"interp","lines":18672,"code_points":61285,"rounds":3,"jit":"0","c_instructions_per_point":220.2,"c_cycles_per_point":82.2,"pure_instructions_per_point":40702.7,"pure_cycles_per_point":15539.8}
+{"mode":"native","lines":18672,"code_points":61285,"rounds":3,"jit":"0","c_instructions_per_point":220.2,"c_cycles_per_point":83.5,"pure_instructions_per_point":28602.1,"pure_cycles_per_point":11831.7}
diff --git a/research/experiments/045-proof-compiler/cost.c b/research/experiments/045-proof-compiler/cost.c
new file mode 100644
index 0000000..2b300a4
--- /dev/null
+++ b/research/experiments/045-proof-compiler/cost.c
@@ -0,0 +1,132 @@
+/* Research 045 (029's principle 1, its breaking experiment): what UAX #14 costs in machine instructions and cycles a
+ * code point, Vega's C (text/linebreak.c) and Pure's executor (aup's examples/linebreak.aup), on the lines
+ * 044's linebreak_check.py writes; user-mode counters (perf_event_open) around each, ROUNDS passes after one checked.
+ *
+ * cc -O2 -std=c17 -I../../../core -I../../../../vega/text cost.c ../../../../vega/text/linebreak.c \
+ * ../../../../vega/text/unicode-data.c ../../../build/libpure.a -lm -pthread -o cost
+ * ./cost linebreak.pmod RANGES LINES ROUNDS
+ *
+ * One JSON line. */
+#define _GNU_SOURCE
+#include "module.h"
+#include "text.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <linux/perf_event.h>
+#include <sys/ioctl.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+static double now_ns(void) {
+ struct timespec t;
+ clock_gettime(CLOCK_MONOTONIC, &t);
+ return (double)t.tv_sec * 1e9 + (double)t.tv_nsec;
+}
+
+static int by_value(const void *a, const void *b) {
+ double x = *(const double *)a, y = *(const double *)b;
+ return (x > y) - (x < y);
+}
+
+static unsigned char *whole(const char *path, size_t *size) {
+ FILE *f = fopen(path, "rb");
+ if (!f) return NULL;
+ fseek(f, 0, SEEK_END);
+ *size = (size_t)ftell(f);
+ rewind(f);
+ unsigned char *bytes = malloc(*size ? *size : 1);
+ if (fread(bytes, 1, *size, f) != *size) { free(bytes); bytes = NULL; }
+ fclose(f);
+ return bytes;
+}
+
+typedef struct { uint32_t count; const uint32_t *codes; const unsigned char *want; unsigned char *payload; } Line;
+
+/* A line's boundaries from Pure: the bytes state "breaks", compared as breaks or not with want. */
+typedef struct { const unsigned char *want; size_t at; bool same; } Compare;
+static bool compare(void *context, PureSpan span) {
+ Compare *c = context;
+ for (size_t i = 0; i < span.size; ++i, ++c->at) c->same = c->same && (span.data[i] != 0) == c->want[c->at];
+ return true;
+}
+
+
+static int counter(uint64_t config) {
+ struct perf_event_attr a;
+ memset(&a, 0, sizeof(a));
+ a.size = sizeof(a), a.type = PERF_TYPE_HARDWARE, a.config = config, a.disabled = 1, a.exclude_kernel = 1, a.exclude_hv = 1;
+ return (int)syscall(SYS_perf_event_open, &a, 0, -1, -1, 0);
+}
+
+static uint64_t read_counter(int fd) {
+ uint64_t v = 0;
+ if (read(fd, &v, sizeof(v)) != sizeof(v)) return 0;
+ return v;
+}
+
+int main(int argc, char **argv) {
+ if (argc != 5) { fprintf(stderr, "usage: %s MODULE RANGES LINES ROUNDS\n", argv[0]); return 2; }
+ size_t ranges_size, lines_size;
+ unsigned char *ranges = whole(argv[2], &ranges_size), *bytes = whole(argv[3], &lines_size);
+ int rounds = atoi(argv[4]);
+ if (!ranges || !bytes) return 1;
+ static Line lines[1 << 16];
+ size_t count = 0, points = 0;
+ for (size_t at = 0; at + 4 <= lines_size && count < sizeof(lines) / sizeof(*lines); ++count) {
+ Line *l = &lines[count];
+ memcpy(&l->count, bytes + at, 4);
+ l->codes = (const uint32_t *)(bytes + at + 4);
+ l->want = bytes + at + 4 + 4 * (size_t)l->count;
+ at += 4 + 4 * (size_t)l->count + l->count + 1;
+ l->payload = malloc(8 * (size_t)l->count + 1);
+ for (uint32_t i = 0; i < l->count; ++i) {
+ double c = l->codes[i];
+ memcpy(l->payload + 8 * i, &c, 8);
+ }
+ points += l->count;
+ }
+ PureModule module = {0};
+ char error[160];
+ if (pure_module_read(argv[1], &module, error, sizeof(error)) != PURE_OK) { fprintf(stderr, "%s\n", error); return 1; }
+ PureApp app;
+ if (pure_app_start(&app, &module, (PureData){0}) != PURE_OK) return 1;
+ int load = pure_module_input(&module, "load"), line = pure_module_input(&module, "line");
+ if (pure_app_message(&app, (uint32_t)load, (PureSpan){ranges, ranges_size}) != PURE_OK) return 1;
+ static uint8_t got[4096];
+ size_t right = 0;
+ for (size_t k = 0; k < count; ++k) { /* checked first: both give the test's boundaries */
+ bool same = pure_app_message(&app, (uint32_t)line, (PureSpan){lines[k].payload, 8 * (size_t)lines[k].count}) == PURE_OK &&
+ lines[k].count < sizeof(got) && vega_line_breaks(lines[k].codes, lines[k].count, got);
+ for (uint32_t i = 0; same && i <= lines[k].count; ++i) same = (got[i] != VEGA_BREAK_NONE) == lines[k].want[i];
+ right += same;
+ }
+ if (right != count) { fprintf(stderr, "right %zu of %zu\n", right, count); return 1; }
+ int ins = counter(PERF_COUNT_HW_INSTRUCTIONS), cyc = counter(PERF_COUNT_HW_CPU_CYCLES);
+ if (ins < 0 || cyc < 0) { perror("perf_event_open"); return 1; }
+ volatile unsigned sink = 0;
+ ioctl(ins, PERF_EVENT_IOC_RESET, 0), ioctl(cyc, PERF_EVENT_IOC_RESET, 0);
+ ioctl(ins, PERF_EVENT_IOC_ENABLE, 0), ioctl(cyc, PERF_EVENT_IOC_ENABLE, 0);
+ for (int r = 0; r < rounds; ++r)
+ for (size_t k = 0; k < count; ++k) {
+ vega_line_breaks(lines[k].codes, lines[k].count, got);
+ sink += got[0];
+ }
+ ioctl(ins, PERF_EVENT_IOC_DISABLE, 0), ioctl(cyc, PERF_EVENT_IOC_DISABLE, 0);
+ uint64_t c_ins = read_counter(ins), c_cyc = read_counter(cyc);
+ ioctl(ins, PERF_EVENT_IOC_RESET, 0), ioctl(cyc, PERF_EVENT_IOC_RESET, 0);
+ ioctl(ins, PERF_EVENT_IOC_ENABLE, 0), ioctl(cyc, PERF_EVENT_IOC_ENABLE, 0);
+ for (int r = 0; r < rounds; ++r)
+ for (size_t k = 0; k < count; ++k)
+ if (pure_app_message(&app, (uint32_t)line, (PureSpan){lines[k].payload, 8 * (size_t)lines[k].count}) != PURE_OK) return 1;
+ ioctl(ins, PERF_EVENT_IOC_DISABLE, 0), ioctl(cyc, PERF_EVENT_IOC_DISABLE, 0);
+ uint64_t p_ins = read_counter(ins), p_cyc = read_counter(cyc);
+ double n = (double)points * rounds;
+ const char *jit = getenv("PURE_JIT");
+ printf("{\"lines\":%zu,\"code_points\":%zu,\"rounds\":%d,\"jit\":\"%s\",\"c_instructions_per_point\":%.1f,\"c_cycles_per_point\":%.1f,"
+ "\"pure_instructions_per_point\":%.1f,\"pure_cycles_per_point\":%.1f}\n",
+ count, points, rounds, jit ? jit : "", c_ins / n, c_cyc / n, p_ins / n, p_cyc / n);
+ pure_app_stop(&app);
+ return 0;
+}
diff --git a/research/experiments/045-proof-compiler/events.c b/research/experiments/045-proof-compiler/events.c
new file mode 100644
index 0000000..58dd0f1
--- /dev/null
+++ b/research/experiments/045-proof-compiler/events.c
@@ -0,0 +1,103 @@
+/* Research 045: where UAX #14's machine instructions go in Pure's executor. A variant of aup's examples/linebreak.aup
+ * (the patches in variants: v0 an empty handler, v1 the lists built, v2 and the Text record, the app itself all of it) on the
+ * lines 044's linebreak_check.py writes; machine instructions and cycles a code point (perf_event_open, user mode),
+ * Vega's C beside it. No answer checked (the variants answer nothing); cost.c checks the app itself.
+ *
+ * cc -O2 -std=c17 -I../../../core -I../../../../vega/text events.c ../../../../vega/text/linebreak.c \
+ * ../../../../vega/text/unicode-data.c ../../../build/libpure.a -lm -pthread -o events
+ * ./events VARIANT.pmod RANGES LINES ROUNDS one JSON line */
+#define _GNU_SOURCE
+#include "module.h"
+#include "text.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <linux/perf_event.h>
+#include <sys/ioctl.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+static unsigned char *whole(const char *path, size_t *size) {
+ FILE *f = fopen(path, "rb");
+ if (!f) return NULL;
+ fseek(f, 0, SEEK_END);
+ *size = (size_t)ftell(f);
+ rewind(f);
+ unsigned char *bytes = malloc(*size ? *size : 1);
+ if (fread(bytes, 1, *size, f) != *size) { free(bytes); bytes = NULL; }
+ fclose(f);
+ return bytes;
+}
+
+typedef struct { uint32_t count; const uint32_t *codes; const unsigned char *want; unsigned char *payload; } Line;
+
+static int counter(uint64_t config) {
+ struct perf_event_attr a;
+ memset(&a, 0, sizeof(a));
+ a.size = sizeof(a), a.type = PERF_TYPE_HARDWARE, a.config = config, a.disabled = 1, a.exclude_kernel = 1, a.exclude_hv = 1;
+ return (int)syscall(SYS_perf_event_open, &a, 0, -1, -1, 0);
+}
+
+static uint64_t read_counter(int fd) {
+ uint64_t v = 0;
+ if (read(fd, &v, sizeof(v)) != sizeof(v)) return 0;
+ return v;
+}
+
+int main(int argc, char **argv) {
+ if (argc != 5) { fprintf(stderr, "usage: %s MODULE RANGES LINES ROUNDS\n", argv[0]); return 2; }
+ size_t ranges_size, lines_size;
+ unsigned char *ranges = whole(argv[2], &ranges_size), *bytes = whole(argv[3], &lines_size);
+ int rounds = atoi(argv[4]);
+ if (!ranges || !bytes) return 1;
+ static Line lines[1 << 16];
+ size_t count = 0, points = 0;
+ for (size_t at = 0; at + 4 <= lines_size && count < sizeof(lines) / sizeof(*lines); ++count) {
+ Line *l = &lines[count];
+ memcpy(&l->count, bytes + at, 4);
+ l->codes = (const uint32_t *)(bytes + at + 4);
+ l->want = bytes + at + 4 + 4 * (size_t)l->count;
+ at += 4 + 4 * (size_t)l->count + l->count + 1;
+ l->payload = malloc(8 * (size_t)l->count + 1);
+ for (uint32_t i = 0; i < l->count; ++i) {
+ double c = l->codes[i];
+ memcpy(l->payload + 8 * i, &c, 8);
+ }
+ points += l->count;
+ }
+ PureModule module = {0};
+ char error[160];
+ if (pure_module_read(argv[1], &module, error, sizeof(error)) != PURE_OK) { fprintf(stderr, "%s\n", error); return 1; }
+ PureApp app;
+ if (pure_app_start(&app, &module, (PureData){0}) != PURE_OK) return 1;
+ int load = pure_module_input(&module, "load"), line = pure_module_input(&module, "line");
+ if (pure_app_message(&app, (uint32_t)load, (PureSpan){ranges, ranges_size}) != PURE_OK) return 1;
+ static uint8_t got[4096];
+ int ins = counter(PERF_COUNT_HW_INSTRUCTIONS), cyc = counter(PERF_COUNT_HW_CPU_CYCLES);
+ if (ins < 0 || cyc < 0) { perror("perf_event_open"); return 1; }
+ volatile unsigned sink = 0;
+ ioctl(ins, PERF_EVENT_IOC_RESET, 0), ioctl(cyc, PERF_EVENT_IOC_RESET, 0);
+ ioctl(ins, PERF_EVENT_IOC_ENABLE, 0), ioctl(cyc, PERF_EVENT_IOC_ENABLE, 0);
+ for (int r = 0; r < rounds; ++r)
+ for (size_t k = 0; k < count; ++k) {
+ vega_line_breaks(lines[k].codes, lines[k].count, got);
+ sink += got[0];
+ }
+ (void)got;
+ ioctl(ins, PERF_EVENT_IOC_DISABLE, 0), ioctl(cyc, PERF_EVENT_IOC_DISABLE, 0);
+ uint64_t c_ins = read_counter(ins), c_cyc = read_counter(cyc);
+ ioctl(ins, PERF_EVENT_IOC_RESET, 0), ioctl(cyc, PERF_EVENT_IOC_RESET, 0);
+ ioctl(ins, PERF_EVENT_IOC_ENABLE, 0), ioctl(cyc, PERF_EVENT_IOC_ENABLE, 0);
+ for (int r = 0; r < rounds; ++r)
+ for (size_t k = 0; k < count; ++k)
+ if (pure_app_message(&app, (uint32_t)line, (PureSpan){lines[k].payload, 8 * (size_t)lines[k].count}) != PURE_OK) return 1;
+ ioctl(ins, PERF_EVENT_IOC_DISABLE, 0), ioctl(cyc, PERF_EVENT_IOC_DISABLE, 0);
+ uint64_t p_ins = read_counter(ins), p_cyc = read_counter(cyc);
+ double n = (double)points * rounds;
+ const char *jit = getenv("PURE_JIT");
+ printf("{\"lines\":%zu,\"code_points\":%zu,\"rounds\":%d,\"jit\":\"%s\",\"c_instructions_per_point\":%.1f,\"c_cycles_per_point\":%.1f,"
+ "\"pure_instructions_per_point\":%.1f,\"pure_cycles_per_point\":%.1f}\n",
+ count, points, rounds, jit ? jit : "", c_ins / n, c_cyc / n, p_ins / n, p_cyc / n);
+ pure_app_stop(&app);
+ return 0;
+}
diff --git a/research/experiments/045-proof-compiler/native-hook.patch b/research/experiments/045-proof-compiler/native-hook.patch
new file mode 100644
index 0000000..a791e86
--- /dev/null
+++ b/research/experiments/045-proof-compiler/native-hook.patch
@@ -0,0 +1,25 @@
+diff --git a/core/module.c b/core/module.c
+index 4b20e25..fd953e8 100644
+--- a/core/module.c
++++ b/core/module.c
+@@ -4967,10 +4967,20 @@ static __attribute__((noinline, aligned(64))) PureResult run_batches(const PureM
+ return finish(&frame, how, result);
+ }
+
++#ifdef PURE_NATIVE_INC /* research 045: the executor's steps stitched for one module */
++#include PURE_NATIVE_INC
++#endif
++
+ /* At a cache line's start: the steps' branches land where they would (research 126: else 10% more cycles, as many instructions). */
+ static __attribute__((aligned(64))) 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);
++#ifdef PURE_NATIVE_INC
++ static const PureModule *native_seen;
++ static bool native_ok;
++ if (m != native_seen) native_seen = m, native_ok = native_matches(m) && getenv("PURE_NATIVE");
++ if (native_ok && m->runner == 2) return run_native(m, values, block, ev, result);
++#endif
+ if (m->runner) return m->runner == 2 ? run_calls(m, values, block, ev, result) : run_batches(m, values, block, ev, result);
+ Slot stack[PURE_MODULE_STACK];
+ Loop loops[PURE_MODULE_NESTING];
diff --git a/research/experiments/045-proof-compiler/ops-count.patch b/research/experiments/045-proof-compiler/ops-count.patch
new file mode 100644
index 0000000..616ad09
--- /dev/null
+++ b/research/experiments/045-proof-compiler/ops-count.patch
@@ -0,0 +1,62 @@
+diff --git a/core/module.c b/core/module.c
+index 4b20e25..2205f77 100644
+--- a/core/module.c
++++ b/core/module.c
+@@ -3902,7 +3902,57 @@ static __attribute__((noinline, cold)) int open_batches(Frame *frame, uint32_t p
+ #define STOPPED(why) do { frame->status = (why); return STEP_RETURN; } while (0)
+
+ /* The instruction op at pc; *next the one to run after it. */
++/* Research only (029's breaking experiment): the instructions the executor runs, by opcode. */
++#include <stdio.h>
++static uint64_t research_ops[256], research_same[256];
++static const char *const research_names[] = {"CONST", "LOAD", "ADD", "SUB", "MUL", "DIV", "NEG", "LT", "LE", "GT", "GE", "EQ", "NE", "AND", "OR", "NOT", "MIN", "MAX", "CLAMP", "STORE", "JZ", "JMP", "FIELD", "LEN", "REPLY", "REQUIRE", "TEXT_CONST", "TEXT_LOAD", "NUM_TEXT", "CONCAT", "CHARS", "LINES", "TEXT_BYTES", "ANCHOR_LOAD", "ANCHOR_START", "ANCHOR_END", "ANCHOR_MOVE", "ANCHOR_STORE", "TEXT_INSERT", "TEXT_REMOVE", "TEXT_LINE", "TEXT_SET", "TEXT_FROM", "BYTES_FROM", "ANCHOR_COUNT", "LIST_COUNT", "LIST_APPEND", "LIST_INSERT", "LIST_REMOVE", "ITEM_GET", "ITEM_SET", "ROW", "PICK", "SLIDE", "FOLD", "TEXT_COMPARE", "LIST_FIND", "LIST_HAS", "NOW", "FLOOR", "MOD", "SEND", "OUTER_ROW", "EACH", "LOCAL_LOAD", "LOCAL_STORE", "I64_ADD", "I64_SUB", "I64_MUL", "I64_DIV", "I64_MOD", "I64_NEG", "I64_COMPARE", "I64_MIN", "I64_MAX", "I64_FROM", "NUM_FROM", "LOOP", "BREAK", "I64_BITS", "BYTE_AT", "BYTES_OF", "ANCHOR_AT", "BYTES_SLICE", "BYTE", "BYTES_LOAD", "BYTES_STORE", "LIST_VALUE", "LIST_BIND", "APPEND_IN", "NUM_PARSE", "CALL", "RETURN", "CHAR_AT", "CHAR", "FLOORDIV", "CUR_LOAD", "CUR_START", "CUR_END", "CUR_MOVE", "CUR_STORE", "CUR_COUNT", "CUR_AT", "INDEX_LOAD", "INDEX_STORE", "INDEX_MOVE", "INDEX_FIRST", "INDEX_LAST", "INDEX_NUM", "INDEX_AT", "KEPT_GET", "KEPT_SET", "KEPT_APPEND", "KEPT_INSERT", "KEPT_APPEND_IN"};
++static void research_reads(const Frame *frame, const PureOp *op);
++static void research_dump(void) {
++ const char *out = getenv("PURE_OPS_OUT");
++ FILE *f = out ? fopen(out, "w") : stderr;
++ for (unsigned i = 0; i < 256; ++i)
++ if (research_ops[i]) fprintf(f, "%s %llu %llu\n", i < sizeof(research_names) / sizeof(*research_names) ? research_names[i] : "?",
++ (unsigned long long)research_ops[i], (unsigned long long)research_same[i]);
++ if (out) fclose(f);
++}
++/* Reads the proof makes the same: the same list (its address and version: a change makes another), field and place
++ * read again within an event (the event's work starts again at 0). Counted by opcode. */
++static uint64_t research_last_work, research_keys[1 << 16];
++static uint32_t research_key_count;
++static void research_reads(const Frame *frame, const PureOp *op) {
++ if (frame->app->work < research_last_work) memset(research_keys, 0, sizeof(research_keys)), research_key_count = 0;
++ research_last_work = frame->app->work;
++ uint64_t list = 0, version = 0, place = 0, id = 0;
++ switch (op->op) {
++ case PURE_OP_ITEM_GET: case PURE_OP_INDEX_AT: case PURE_OP_LIST_COUNT: {
++ uint32_t state = op->op == PURE_OP_ITEM_GET ? (op->arg & 0xFFFF) : op->arg;
++ if (state >= frame->app->laid && state < PURE_LIST_HEARD) return;
++ const PureList *l = frame->app->lists[state];
++ if (!l) return;
++ pure_list_stamp(l, &id, &version);
++ list = (uint64_t)(uintptr_t)l;
++ if (op->op != PURE_OP_LIST_COUNT) memcpy(&place, &frame->stack[frame->sp - 1], sizeof(place));
++ break;
++ }
++ case PURE_OP_LIST_BIND: {
++ list = 0x51ull << 56 | op->arg;
++ memcpy(&place, &frame->stack[frame->sp - 1], sizeof(place));
++ version = 0;
++ break;
++ }
++ default: return;
++ }
++ uint64_t key = ((list * 0x9E3779B97F4A7C15ull) ^ (version * 0xC2B2AE3D27D4EB4Full) ^ (place * 0x165667B19E3779F9ull) ^
++ ((uint64_t)op->op << 32 | op->arg)) | 1;
++ for (uint32_t i = (uint32_t)(key >> 40) & 0xFFFF, k = 0; k < 1 << 16; ++k, i = (i + 1) & 0xFFFF) {
++ if (research_keys[i] == key) { ++research_same[op->op]; return; }
++ if (!research_keys[i]) { if (research_key_count < (1 << 15)) research_keys[i] = key, ++research_key_count; return; }
++ }
++}
++__attribute__((constructor)) static void research_start(void) { atexit(research_dump); }
+ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp *op, uint32_t pc, uint32_t *next, bool batches) {
++ ++research_ops[op->op];
++ research_reads(frame, op);
+ const PureModule *m = frame->m;
+ PureValue *values = frame->values;
+ Event *ev = frame->ev;
diff --git a/research/experiments/045-proof-compiler/stitch.c b/research/experiments/045-proof-compiler/stitch.c
new file mode 100644
index 0000000..8d690e0
--- /dev/null
+++ b/research/experiments/045-proof-compiler/stitch.c
@@ -0,0 +1,108 @@
+/* Research 045 (029's principle 1, experiment (b)): the executor's own instruction bodies stitched without dispatch.
+ * Reads a module as Pure loads it and writes C for Pure's core to include (native.inc): the module's code as a
+ * constant array; a function for each basic block calling the executor's step() on each of its constant instructions,
+ * so the C compiler specializes each body to its instruction alone (no fetch, no decode, no dispatch within a block);
+ * and run_native, run_calls's loop dispatching once a block. Calls, returns and the ends of loops go through
+ * run_calls's own code. The semantics are the executor's source, unchanged.
+ *
+ * cc -O2 -std=c17 -I../../../core stitch.c ../../../build/libpure.a -lm -pthread -o stitch
+ * ./stitch MODULE > native.inc (then core/module.c built with -DPURE_NATIVE_INC='"native.inc"') */
+#include "module.h"
+#include <inttypes.h>
+#include <stdio.h>
+
+int main(int argc, char **argv) {
+ if (argc != 2) { fprintf(stderr, "usage: %s MODULE\n", argv[0]); return 2; }
+ PureModule m = {0};
+ char error[160];
+ if (pure_module_read(argv[1], &m, error, sizeof(error)) != PURE_OK) { fprintf(stderr, "%s\n", error); return 1; }
+ uint32_t n = m.code_count;
+ printf("/* Stitched from %s: %u instructions (research 045). */\n", argv[1], n);
+ printf("enum { NATIVE_COUNT = %u, STEP_ENDED = 9 }; /* STEP_ENDED: a block reached its end (frame.end) */\n", n);
+ printf("static const PureOp native_code[NATIVE_COUNT] = {\n");
+ for (uint32_t pc = 0; pc < n; ++pc) {
+ const PureOp *o = &m.code[pc];
+ printf(" {.integer = INT64_C(%" PRId64 "), .arg = %" PRIu32 "u, .op = %u, .plan = %u, .shape = %u, .range = %u},\n",
+ o->integer, o->arg, o->op, o->plan, o->shape, o->range);
+ }
+ printf("};\n\n");
+ printf("static bool native_matches(const PureModule *m) {\n"
+ " if (m->code_count != NATIVE_COUNT) return false;\n"
+ " for (uint32_t pc = 0; pc < NATIVE_COUNT; ++pc)\n"
+ " if (m->code[pc].op != native_code[pc].op || m->code[pc].arg != native_code[pc].arg ||\n"
+ " m->code[pc].integer != native_code[pc].integer || m->code[pc].plan != native_code[pc].plan)\n"
+ " return false;\n"
+ " return true;\n"
+ "}\n\n");
+ /* Basic blocks: a leader at each block's and function's start, each jump's target, past each instruction that may
+ * go elsewhere (a branch, a loop's, a call, a return), and every 16 instructions. Each is a function of its own (one
+ * enormous function took the compiler past 3 GiB): stitched within, dispatched once a block. */
+ static bool leader[1 << 16];
+ for (uint32_t h = 0; h < m.handlers; ++h) leader[m.handler[h].code.start] = true;
+ for (uint32_t f = 0; f < m.functions; ++f) leader[m.function[f].code.start] = true;
+ for (uint32_t pc = 0; pc < n; ++pc) {
+ uint8_t op = m.code[pc].op;
+ bool control = op == PURE_OP_JZ || op == PURE_OP_JMP || op == PURE_OP_LOOP || op == PURE_OP_EACH || op == PURE_OP_BREAK ||
+ op == PURE_OP_CALL || op == PURE_OP_RETURN || op == PURE_OP_FOLD;
+ if ((op == PURE_OP_JZ || op == PURE_OP_JMP) && m.code[pc].arg < n) leader[m.code[pc].arg] = true;
+ if (control && pc + 1 < n) leader[pc + 1] = true;
+ }
+ leader[0] = true;
+ for (uint32_t pc = 0, run = 0; pc < n; ++pc, ++run) {
+ if (leader[pc]) run = 0;
+ else if (run == 16) leader[pc] = true, run = 0;
+ }
+ for (uint32_t pc = 0; pc < n;) {
+ uint32_t end = pc + 1;
+ while (end < n && !leader[end]) ++end;
+ printf("static __attribute__((noinline)) int native_%u(Frame *frame, uint32_t *pc) {\n int how;\n", pc);
+ for (uint32_t k = pc; k < end; ++k) {
+ printf(" if (%uu >= frame->end) { *pc = %uu; return STEP_ENDED; }\n", k, k);
+ printf(" if ((how = step(frame, &native_code[%u], %uu, pc, true)) != STEP_NEXT || *pc != %uu) return how;\n", k, k, k + 1);
+ }
+ printf(" return STEP_NEXT;\n}\n");
+ pc = end;
+ }
+ printf("typedef int (*NativeBlock)(Frame *, uint32_t *);\n");
+ printf("static const NativeBlock native_blocks[NATIVE_COUNT] = {\n");
+ for (uint32_t pc = 0; pc < n; ++pc) if (leader[pc]) printf(" [%u] = native_%u,\n", pc, pc);
+ printf("};\n\n");
+ printf("static __attribute__((noinline)) PureResult run_native(const PureModule *m, PureValue *values, PureBlock block, Event *ev,\n"
+ " Slot *result) {\n"
+ " Slot stack[PURE_MODULE_STACK];\n"
+ " Loop few[PURE_MODULE_NESTING], *loops = m->nesting > PURE_MODULE_NESTING ? deep_loops(ev->app, m->nesting) : few;\n"
+ " if (!loops) return PURE_ALLOCATION;\n"
+ " Frame frame;\n"
+ " open_frame(&frame, m, values, block, ev, stack, loops);\n"
+ " Call *top = NULL;\n"
+ " int how = STEP_NEXT;\n"
+ " for (uint32_t pc = block.start;;) {\n"
+ " if (pc >= frame.end) {\n"
+ " if (!frame.depth || (how = iterate(&frame, &pc)) != STEP_NEXT) break;\n"
+ " continue;\n"
+ " }\n"
+ " NativeBlock b = pc < NATIVE_COUNT ? native_blocks[pc] : NULL;\n"
+ " how = b ? b(&frame, &pc) : step(&frame, &m->code[pc], pc, &pc, true);\n"
+ " if (how == STEP_NEXT || how == STEP_ENDED) continue;\n"
+ " if (how != STEP_CALL && how != STEP_BACK && how != STEP_BATCH) break;\n"
+ " Frame apart = frame;\n"
+ " if (how == STEP_CALL) how = enter_call(&apart, &top, &pc);\n"
+ " else if (how == STEP_BACK) how = leave_call(&apart, &top, &pc);\n"
+ " else {\n"
+ " uint32_t past = 0;\n"
+ " how = open_batches(&apart, pc - 1, &past);\n"
+ " pc = past;\n"
+ " }\n"
+ " frame = apart;\n"
+ " if (how != STEP_NEXT) break;\n"
+ " }\n"
+ " if (top) {\n"
+ " Frame apart = frame;\n"
+ " unwind_calls(&apart, top);\n"
+ " frame = apart;\n"
+ " }\n"
+ " return finish(&frame, how, result);\n"
+ "}\n");
+ pure_module_release(&m);
+ return 0;
+}
diff --git a/research/experiments/045-proof-compiler/unit.c b/research/experiments/045-proof-compiler/unit.c
new file mode 100644
index 0000000..f85a4d9
--- /dev/null
+++ b/research/experiments/045-proof-compiler/unit.c
@@ -0,0 +1,47 @@
+/* Research 045: an operation's cost in machine instructions and cycles, from small apps (the apps in units): each takes
+ * message go { n: u32 } and does its operation n times; MESSAGES messages, counted (perf_event_open, user mode).
+ *
+ * cc -O2 -std=c17 -I../../../core unit.c ../../../build/libpure.a -lm -pthread -o unit
+ * ./unit MODULE N MESSAGES one JSON line: instructions and cycles an operation */
+#define _GNU_SOURCE
+#include "module.h"
+#include <linux/perf_event.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+static int counter(uint64_t config) {
+ struct perf_event_attr a;
+ memset(&a, 0, sizeof(a));
+ a.size = sizeof(a), a.type = PERF_TYPE_HARDWARE, a.config = config, a.disabled = 1, a.exclude_kernel = 1, a.exclude_hv = 1;
+ return (int)syscall(SYS_perf_event_open, &a, 0, -1, -1, 0);
+}
+
+int main(int argc, char **argv) {
+ if (argc != 4) { fprintf(stderr, "usage: %s MODULE N MESSAGES\n", argv[0]); return 2; }
+ uint32_t n = (uint32_t)atoi(argv[2]);
+ int messages = atoi(argv[3]);
+ PureModule module = {0};
+ char error[160];
+ if (pure_module_read(argv[1], &module, error, sizeof(error)) != PURE_OK) { fprintf(stderr, "%s\n", error); return 1; }
+ PureApp app;
+ if (pure_app_start(&app, &module, (PureData){0}) != PURE_OK) return 1;
+ int go = pure_module_input(&module, "go");
+ unsigned char payload[4];
+ memcpy(payload, &n, 4);
+ if (pure_app_message(&app, (uint32_t)go, (PureSpan){payload, 4}) != PURE_OK) { fprintf(stderr, "failed\n"); return 1; }
+ int ins = counter(PERF_COUNT_HW_INSTRUCTIONS), cyc = counter(PERF_COUNT_HW_CPU_CYCLES);
+ ioctl(ins, PERF_EVENT_IOC_ENABLE, 0), ioctl(cyc, PERF_EVENT_IOC_ENABLE, 0);
+ for (int k = 0; k < messages; ++k)
+ if (pure_app_message(&app, (uint32_t)go, (PureSpan){payload, 4}) != PURE_OK) return 1;
+ ioctl(ins, PERF_EVENT_IOC_DISABLE, 0), ioctl(cyc, PERF_EVENT_IOC_DISABLE, 0);
+ uint64_t i = 0, c = 0;
+ if (read(ins, &i, 8) != 8 || read(cyc, &c, 8) != 8) return 1;
+ double ops = (double)n * messages;
+ printf("{\"n\":%u,\"messages\":%d,\"instructions_per_op\":%.1f,\"cycles_per_op\":%.1f}\n", n, messages, i / ops, c / ops);
+ pure_app_stop(&app);
+ return 0;
+}
diff --git a/research/experiments/045-proof-compiler/units/append.aup b/research/experiments/045-proof-compiler/units/append.aup
new file mode 100644
index 0000000..def7fa5
--- /dev/null
+++ b/research/experiments/045-proof-compiler/units/append.aup
@@ -0,0 +1,7 @@
+state t = 0
+input go = message { n: u32 }
+on go {
+ let xs: list of number
+ for i in 0 .. go.n { xs.append(i) }
+ t = xs.count
+}
diff --git a/research/experiments/045-proof-compiler/units/arith.aup b/research/experiments/045-proof-compiler/units/arith.aup
new file mode 100644
index 0000000..76f48dc
--- /dev/null
+++ b/research/experiments/045-proof-compiler/units/arith.aup
@@ -0,0 +1,7 @@
+state t = 0
+input go = message { n: u32 }
+on go {
+ let s = 0
+ for i in 0 .. go.n { s = s + i * 2 }
+ t = s
+}
diff --git a/research/experiments/045-proof-compiler/units/call.aup b/research/experiments/045-proof-compiler/units/call.aup
new file mode 100644
index 0000000..ef31ef6
--- /dev/null
+++ b/research/experiments/045-proof-compiler/units/call.aup
@@ -0,0 +1,11 @@
+state t = 0
+input go = message { n: u32 }
+fn f(x) {
+ if x > 1000000 { return 0 }
+ return x + 1
+}
+on go {
+ let s = 0
+ for i in 0 .. go.n { s = s + f(i) }
+ t = s
+}
diff --git a/research/experiments/045-proof-compiler/units/mod.aup b/research/experiments/045-proof-compiler/units/mod.aup
new file mode 100644
index 0000000..dfb0f98
--- /dev/null
+++ b/research/experiments/045-proof-compiler/units/mod.aup
@@ -0,0 +1,7 @@
+state t = 0
+input go = message { n: u32 }
+on go {
+ let s = 0
+ for i in 0 .. go.n { s = s + i % 1000 }
+ t = s
+}
diff --git a/research/experiments/045-proof-compiler/units/read-direct.aup b/research/experiments/045-proof-compiler/units/read-direct.aup
new file mode 100644
index 0000000..8609a5e
--- /dev/null
+++ b/research/experiments/045-proof-compiler/units/read-direct.aup
@@ -0,0 +1,14 @@
+record R {
+ start = 0
+}
+state ranges: list of R
+state t = 0
+input go = message { n: u32 }
+on go {
+ if ranges.count == 0 {
+ for i in 0 .. 1000 { ranges.append(R { start: i }) }
+ }
+ let s = 0
+ for i in 0 .. go.n { s = s + ranges[i].start }
+ t = s
+}
diff --git a/research/experiments/045-proof-compiler/units/read.aup b/research/experiments/045-proof-compiler/units/read.aup
new file mode 100644
index 0000000..34828e7
--- /dev/null
+++ b/research/experiments/045-proof-compiler/units/read.aup
@@ -0,0 +1,14 @@
+record R {
+ start = 0
+}
+state ranges: list of R
+state t = 0
+input go = message { n: u32 }
+on go {
+ if ranges.count == 0 {
+ for i in 0 .. 1000 { ranges.append(R { start: i }) }
+ }
+ let s = 0
+ for i in 0 .. go.n { s = s + ranges[i % 1000].start }
+ t = s
+}
diff --git a/research/experiments/045-proof-compiler/variants/v0-empty.patch b/research/experiments/045-proof-compiler/variants/v0-empty.patch
new file mode 100644
index 0000000..de5b3fc
--- /dev/null
+++ b/research/experiments/045-proof-compiler/variants/v0-empty.patch
@@ -0,0 +1,34 @@
+--- linebreak.aup
++++ v0-empty.aup
+@@ -153,30 +153,6 @@
+ }
+
+ on line {
+- let codes, cls, eff, start, nonspace, ri, at: list of number
+- let joined, numeric: list of bool
+- for p in line.points {
+- let i = codes.count
+- let r = range(p.code)
+- let c = ranges[r].class
+- let mark = c == CM || c == ZWJ
+- let previous = if i > 0 { eff[i - 1] } else { SP }
+- let joins = mark && i > 0 && !end(previous) && previous != SP && previous != ZW # LB9, LB10
+- let e = if joins { previous } else if mark { AL } else { c }
+- codes.append(p.code)
+- cls.append(c)
+- at.append(r)
+- joined.append(joins)
+- eff.append(e)
+- start.append(if joins { start[i - 1] } else { i })
+- nonspace.append(if e != SP { start[i] } else if i > 0 { nonspace[i - 1] } else { -1 })
+- numeric.append(if joins { numeric[i - 1] } else { e == NU || ((e == SY || e == IS) && i > 0 && numeric[i - 1]) })
+- ri.append(if joins { ri[i - 1] } else if e == RI { (if i > 0 { ri[i - 1] } else { 0 }) + 1 } else { 0 })
+- }
+- let t = Text { codes: codes, cls: cls, eff: eff, joined: joined, start: start, nonspace: nonspace, numeric: numeric, ri: ri,
+- at: at }
+- let out: list of i64 = [0] # LB2
+- for i in 1 .. codes.count { out.append(i64(decide(t, i))) }
+- if codes.count > 0 { out.append(2) } # LB3
++ let out: list of i64 = [0]
+ breaks = bytes(out)
+ }
diff --git a/research/experiments/045-proof-compiler/variants/v1-lists.patch b/research/experiments/045-proof-compiler/variants/v1-lists.patch
new file mode 100644
index 0000000..6b73004
--- /dev/null
+++ b/research/experiments/045-proof-compiler/variants/v1-lists.patch
@@ -0,0 +1,14 @@
+--- linebreak.aup
++++ v1-lists.aup
+@@ -173,10 +173,6 @@
+ numeric.append(if joins { numeric[i - 1] } else { e == NU || ((e == SY || e == IS) && i > 0 && numeric[i - 1]) })
+ ri.append(if joins { ri[i - 1] } else if e == RI { (if i > 0 { ri[i - 1] } else { 0 }) + 1 } else { 0 })
+ }
+- let t = Text { codes: codes, cls: cls, eff: eff, joined: joined, start: start, nonspace: nonspace, numeric: numeric, ri: ri,
+- at: at }
+- let out: list of i64 = [0] # LB2
+- for i in 1 .. codes.count { out.append(i64(decide(t, i))) }
+- if codes.count > 0 { out.append(2) } # LB3
++ let out: list of i64 = [0]
+ breaks = bytes(out)
+ }
diff --git a/research/experiments/045-proof-compiler/variants/v2-text.patch b/research/experiments/045-proof-compiler/variants/v2-text.patch
new file mode 100644
index 0000000..2539f0c
--- /dev/null
+++ b/research/experiments/045-proof-compiler/variants/v2-text.patch
@@ -0,0 +1,12 @@
+--- linebreak.aup
++++ v2-text.aup
+@@ -175,8 +175,6 @@
+ }
+ let t = Text { codes: codes, cls: cls, eff: eff, joined: joined, start: start, nonspace: nonspace, numeric: numeric, ri: ri,
+ at: at }
+- let out: list of i64 = [0] # LB2
+- for i in 1 .. codes.count { out.append(i64(decide(t, i))) }
+- if codes.count > 0 { out.append(2) } # LB3
++ let out: list of i64 = [0]
+ breaks = bytes(out)
+ }