aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-18 22:33:19 +0100
committereunhhu <[email protected]>2026-10-18 22:33:19 +0100
commit1d255b735e92c2d6b360ed5ea150a1e42f6dd535 (patch)
tree211072355a181bc9ddfa20ecfcb5beda27021ffe
parenta72a1a7acdf6c5ecc4a6668f6b9a3af16ce48555 (diff)
downloadpure-1d255b735e92c2d6b360ed5ea150a1e42f6dd535.tar.gz
pure-1d255b735e92c2d6b360ed5ea150a1e42f6dd535.zip
027's 4f-1: a module's tables sized to it: PureModule's thirty-four tables of states, inputs, message fields, views (the columns the load makes among them, counted from the view and record entries first), handlers, records, record fields, functions and signature items point into one block the load lays out (lay_tables) and pure_module_release gives back, so PureModule is 2,752 bytes, not 24,320; a sanitized build poisons 16 bytes between tables, so reading past one is caught (none was); limits as they were; a test and five of research 044's timers gave the load a module never zeroed (it gives back what one holds), now zeroed; counts: b38 against b37 with batches within 0.25%, without batches matmul -0.45%, join +0.36%, knapsack +0.28%, BFS -0.25%; the bank's queries (s37 and s38 on one aup module in turns, three each) field -0.68%, found -0.51%, large -0.47%, the rest -0.11% to -0.20%; 027's 4f designed in four steps and 4f-1 as made
-rw-r--r--core/module.c73
-rw-r--r--core/module.h76
-rw-r--r--core/test-module.c6
-rw-r--r--docs/design/027-computation-core.md21
-rw-r--r--research/experiments/040-against-c/2026-10-18-counts-s38.jsonl6
-rw-r--r--research/experiments/041-batches/2026-10-18-counts.jsonl10
-rw-r--r--research/experiments/044-specs/append_cost.c2
-rw-r--r--research/experiments/044-specs/json_rec_time.c2
-rw-r--r--research/experiments/044-specs/json_time.c2
-rw-r--r--research/experiments/044-specs/json_tree_time.c2
-rw-r--r--research/experiments/044-specs/time.c2
11 files changed, 156 insertions, 46 deletions
diff --git a/core/module.c b/core/module.c
index e7118f0..f388ae7 100644
--- a/core/module.c
+++ b/core/module.c
@@ -1563,7 +1563,59 @@ PURE_COLD PureResult pure_module_read(const char *path, PureModule *out, char *e
return result;
}
+#ifdef __SANITIZE_ADDRESS__
+#include <sanitizer/asan_interface.h>
+enum { TABLE_GAP = 16 }; /* poisoned between tables: reading past one is caught */
+#else
+enum { TABLE_GAP = 0 };
+#endif
+
+/* The module's tables laid out from base (027's 4f-1), each a run of its entries aligned to 8; the bytes they take. */
+static size_t lay_tables(PureModule *m, uintptr_t base, size_t views, size_t signatures) {
+ size_t at = 0;
+#define LAY(member, count) \
+ (m->member = (void *)(base + at), at += (((size_t)(count) * sizeof(*m->member) + 7) & ~(size_t)7) + TABLE_GAP)
+ LAY(state_names, m->states), LAY(state_type, m->states), LAY(state_ref, m->states), LAY(state_flags, m->states);
+ LAY(state_key, m->states), LAY(state_indexed, m->states), LAY(initial, m->states);
+ LAY(input_names, m->inputs), LAY(input_keys, m->inputs), LAY(input, m->inputs), LAY(field, m->fields);
+ LAY(view_names, views), LAY(view_type, views), LAY(view_table, views), LAY(view_list, views), LAY(view_record, views);
+ LAY(view_field, views), LAY(view_computed, views), LAY(view_code, views), LAY(view_plan, views);
+ LAY(view_arguments, views), LAY(view_body, views), LAY(view_lists, views), LAY(view_range, views);
+ LAY(handler, m->handlers);
+ LAY(record_names, m->records), LAY(record, m->records), LAY(record_nested, m->records), LAY(record_held, m->records);
+ LAY(record_field_names, m->record_fields), LAY(record_field_type, m->record_fields);
+ LAY(record_field_default, m->record_fields);
+ LAY(function, m->functions), LAY(signature_type, signatures), LAY(signature_record, signatures);
+#undef LAY
+ return at;
+}
+
+static bool make_tables(PureModule *m, size_t views, size_t signatures) {
+ size_t size = lay_tables(m, 0, views, signatures);
+ unsigned char *base = calloc(1, size ? size : 1);
+ if (!base) return false;
+ m->tables = base;
+ lay_tables(m, (uintptr_t)base, views, signatures);
+#ifdef __SANITIZE_ADDRESS__
+ for (size_t at = 0; at + TABLE_GAP <= size;) { /* each run's gap, found again as it was laid out */
+ unsigned char *next = base + size;
+#define GAP(member) if ((unsigned char *)m->member > base + at && (unsigned char *)m->member < next) next = (unsigned char *)m->member;
+ GAP(state_type) GAP(state_ref) GAP(state_flags) GAP(state_key) GAP(state_indexed) GAP(initial) GAP(input_names)
+ GAP(input_keys) GAP(input) GAP(field) GAP(view_names) GAP(view_type) GAP(view_table) GAP(view_list) GAP(view_record)
+ GAP(view_field) GAP(view_computed) GAP(view_code) GAP(view_plan) GAP(view_arguments) GAP(view_body) GAP(view_lists)
+ GAP(view_range) GAP(handler) GAP(record_names) GAP(record) GAP(record_nested) GAP(record_held) GAP(record_field_names)
+ GAP(record_field_type) GAP(record_field_default) GAP(function) GAP(signature_type) GAP(signature_record)
+#undef GAP
+ ASAN_POISON_MEMORY_REGION(next - TABLE_GAP, TABLE_GAP);
+ at = (size_t)(next - base);
+ }
+#endif
+ return true;
+}
+
void pure_module_release(PureModule *m) {
+ free(m->tables);
+ m->tables = NULL;
pure_jit_free(m->jit);
m->jit = NULL;
m->batches = m->calls = false;
@@ -1664,6 +1716,23 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
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);
+ /* Its tables, the columns the load makes among the views (027's 4e: a record's fields for each list view of a
+ * record), counted from the bytes first. */
+ size_t records_at = functions_at - (size_t)out->views * VIEW_BYTES - (size_t)out->handlers * HANDLER_BYTES -
+ (size_t)out->record_fields * RECORD_FIELD_BYTES - (size_t)out->records * RECORD_BYTES;
+ size_t made = 0, views_at = records_at + (size_t)out->records * RECORD_BYTES + (size_t)out->record_fields * RECORD_FIELD_BYTES;
+ for (uint32_t i = 0; i < out->views; ++i) {
+ const unsigned char *v = p + views_at + (size_t)i * VIEW_BYTES;
+ uint32_t record = read16(v + 8);
+ if (v[4] == PURE_VIEW_LIST && !read16(v + 6) && record && record <= out->records)
+ made += read16(p + records_at + (size_t)(record - 1) * RECORD_BYTES + 6);
+ }
+ if (out->views + made > PURE_MODULE_VIEWS) REFUSE("views and their columns", out->views);
+ size_t capacity = out->views + made;
+ if (!make_tables(out, capacity, signatures)) {
+ result = PURE_ALLOCATION;
+ REFUSE("out of memory", 0);
+ }
const unsigned char *at = p + PURE_MODULE_HEADER;
const unsigned char *text = p + size - constant_bytes - strings;
if (constant_bytes) memcpy(out->constant_data, p + size - constant_bytes, constant_bytes);
@@ -1879,7 +1948,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
if (!list_view(out, i) || !out->view_record[i]) continue;
const PureRecord *r = &out->record[out->view_record[i] - 1];
for (unsigned f = 0; f < r->count; ++f) {
- if (out->views == PURE_MODULE_VIEWS) REFUSE("views and their columns", i);
+ if (out->views == capacity) REFUSE("views and their columns", i);
uint32_t c = out->views++, field = r->first + f;
memcpy(out->view_names[c], out->record_field_names[field], PURE_MODULE_NAME);
out->view_type[c] = out->record_field_type[field];
@@ -2130,7 +2199,7 @@ refused:
#undef REFUSE
}
-static int find(const char (*names)[PURE_MODULE_NAME], unsigned count, const char *name) {
+static int find(char (*names)[PURE_MODULE_NAME], unsigned count, const char *name) {
for (unsigned i = 0; i < count; ++i)
if (!strcmp(names[i], name)) return (int)i;
return -1;
diff --git a/core/module.h b/core/module.h
index 20e5fba..8ea0574 100644
--- a/core/module.h
+++ b/core/module.h
@@ -367,18 +367,22 @@ _Static_assert(PURE_MODULE_STATES <= 32, "a state is a bit of PureApp.counted");
typedef struct {
uint16_t states, inputs, views, handlers, fields, constants, records, record_fields;
uint32_t code_count;
- char state_names[PURE_MODULE_STATES][PURE_MODULE_NAME];
- char input_names[PURE_MODULE_INPUTS][PURE_MODULE_NAME];
- char view_names[PURE_MODULE_VIEWS][PURE_MODULE_NAME];
- char input_keys[PURE_MODULE_INPUTS][PURE_MODULE_NAME];
- uint8_t state_type[PURE_MODULE_STATES];
- uint16_t state_ref[PURE_MODULE_STATES]; /* text: constant; cursor: text; list: record; index: list */
- uint8_t state_flags[PURE_MODULE_STATES]; /* cursor: PURE_CURSOR_STAYS; list: PURE_LIST_ROWS >> 8 */
+ /* Its tables (027's 4f-1), each sized to it in one block (tables) that load takes and pure_module_release gives
+ * back: a state's, an input's, a message field's, a view's (the columns the load makes among them), a handler's, a
+ * record's, a record field's, a function's and a signature item's entries. */
+ void *tables;
+ char (*state_names)[PURE_MODULE_NAME];
+ char (*input_names)[PURE_MODULE_NAME];
+ char (*view_names)[PURE_MODULE_NAME];
+ char (*input_keys)[PURE_MODULE_NAME];
+ uint8_t *state_type;
+ uint16_t *state_ref; /* text: constant; cursor: text; list: record; index: list */
+ uint8_t *state_flags; /* cursor: PURE_CURSOR_STAYS; list: PURE_LIST_ROWS >> 8 */
uint32_t transient; /* a bit per transient state */
/* A keyed list: its key field + 1 (0: none). Its items stay in key order
* (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 *state_key;
+ bool *state_indexed; /* 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) */
/* In a batch's block (module-batch.c), a value computed again by the same code as an earlier one whose operands
* nothing changed between: at its code's first instruction, the earlier root + 1 and its code's length (reuse_from,
@@ -391,45 +395,45 @@ typedef struct {
bool batches; /* a loop of it is planned to run in batches (module-batch.c, docs/design/024) */
uint8_t runner; /* which executor runs its blocks: 0 run's own steps, 1 with batches, 2 with calls (module.c) */
bool held; /* it holds bytes or texts by name (BYTES_STORE): events and views empty them as they begin */
- PureInputShape input[PURE_MODULE_INPUTS];
- PureField field[PURE_MODULE_FIELDS];
- PureValue initial[PURE_MODULE_STATES];
- uint8_t view_type[PURE_MODULE_VIEWS];
- uint16_t view_table[PURE_MODULE_VIEWS]; /* a column's list view + 1, or 0 */
- uint16_t view_list[PURE_MODULE_VIEWS]; /* the list state of a list view over one, or its column's */
- uint16_t view_record[PURE_MODULE_VIEWS]; /* a list view's or a list column's items' record + 1 (a map's: 0) */
- uint8_t view_field[PURE_MODULE_VIEWS]; /* a column the load made: its item's field + 1 (read as it is), or 0 */
- bool view_computed[PURE_MODULE_VIEWS]; /* a list view whose code leaves its list (a column's: its view's) */
- PureBlock view_code[PURE_MODULE_VIEWS];
- uint8_t view_plan[PURE_MODULE_VIEWS]; /* a table's filter runs a leaf of its list at once (as a fold may) */
- uint8_t view_arguments[PURE_MODULE_VIEWS]; /* the input of a view's arguments + 1 (a column's: its table's), or 0 */
+ PureInputShape *input;
+ PureField *field;
+ PureValue *initial;
+ uint8_t *view_type;
+ uint16_t *view_table; /* a column's list view + 1, or 0 */
+ uint16_t *view_list; /* the list state of a list view over one, or its column's */
+ uint16_t *view_record; /* a list view's or a list column's items' record + 1 (a map's: 0) */
+ uint8_t *view_field; /* a column the load made: its item's field + 1 (read as it is), or 0 */
+ bool *view_computed; /* a list view whose code leaves its list (a column's: its view's) */
+ PureBlock *view_code;
+ uint8_t *view_plan; /* a table's filter runs a leaf of its list at once (as a fold may) */
+ uint8_t *view_arguments; /* the input of a view's arguments + 1 (a column's: its table's), or 0 */
/* Set at load: a view's code computes (its locals and loops: a body), and the event's lists it changes, a bit each. */
- bool view_body[PURE_MODULE_VIEWS];
- uint32_t view_lists[PURE_MODULE_VIEWS];
+ bool *view_body;
+ uint32_t *view_lists;
PureShape shape[PURE_MODULE_SHAPES];
uint8_t shapes;
PureRange range[PURE_MODULE_RANGES];
uint8_t ranges;
- uint8_t view_range[PURE_MODULE_VIEWS]; /* a table's filter's PureRange + 1, or 0 */
- PureHandler handler[PURE_MODULE_HANDLERS];
+ uint8_t *view_range; /* a table's filter's PureRange + 1, or 0 */
+ PureHandler *handler;
/* The code and the text constants, sized to the module in one block
* (storage) that load takes and pure_module_release gives back. */
PureOp *code;
uint32_t *constant_offset, *constant_length;
unsigned char *constant_data;
void *storage;
- char record_names[PURE_MODULE_RECORDS][PURE_MODULE_NAME];
- PureRecord record[PURE_MODULE_RECORDS];
- char record_field_names[PURE_MODULE_RECORD_FIELDS][PURE_MODULE_NAME];
- uint8_t record_field_type[PURE_MODULE_RECORD_FIELDS]; /* PureRecordType */
- PureValue record_field_default[PURE_MODULE_RECORD_FIELDS]; /* a number (a bool's 0 or 1), an i64, a text constant's index
- * or a list's record (LIST, its i64) */
- bool record_nested[PURE_MODULE_RECORDS]; /* a record with a LIST field (set at load) */
- uint16_t record_held[PURE_MODULE_RECORDS]; /* its fields held as bytes are (pure_record_bytes), a bit each (set at load) */
+ char (*record_names)[PURE_MODULE_NAME];
+ PureRecord *record;
+ char (*record_field_names)[PURE_MODULE_NAME];
+ uint8_t *record_field_type; /* PureRecordType */
+ PureValue *record_field_default; /* a number (a bool's 0 or 1), an i64, a text constant's index or a list's record
+ * (LIST, its i64) */
+ bool *record_nested; /* a record with a LIST field (set at load) */
+ uint16_t *record_held; /* its fields held as bytes are (pure_record_bytes), a bit each (set at load) */
uint16_t functions;
- PureFunction function[PURE_MODULE_FUNCTIONS];
- uint8_t signature_type[PURE_MODULE_SIGNATURES];
- uint16_t signature_record[PURE_MODULE_SIGNATURES];
+ PureFunction *function;
+ uint8_t *signature_type;
+ uint16_t *signature_record;
bool calls; /* it has functions: its blocks run in the executor that calls (module.c) */
} PureModule;
diff --git a/core/test-module.c b/core/test-module.c
index 8950018..8014d63 100644
--- a/core/test-module.c
+++ b/core/test-module.c
@@ -3483,7 +3483,7 @@ static void lane_folds(void) {
module_b.code[i].plan = module_b.code[i].shape = 0;
}
planned += module_a.view_plan[VIEWS];
- memset(module_b.view_plan, 0, sizeof(module_b.view_plan));
+ memset(module_b.view_plan, 0, module_b.views * 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;
@@ -4079,7 +4079,7 @@ static void ranges(void) {
!module_a.code[45].range && module_a.view_range[6] && module_a.code[78].range && !module_a.code[102].range &&
!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));
+ memset(module_b.view_range, 0, module_b.views * 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;
@@ -5426,7 +5426,7 @@ static void output_meaning(void) {
NULL, 0};
Spec none = {nums, 1, none_inputs, 1, NULL, 0, none_handlers, 1, none_code, 2, NULL, 0, NULL, 0, NULL, 0, NULL, 0,
NULL, 0, NULL, 0};
- PureModule without;
+ PureModule without = {0};
CHECK(load(was, &module_a) == PURE_OK && load(now, &module_b) == PURE_OK && load(none, &without) == PURE_OK);
CHECK(pure_module_shape(&module_a, 1) != pure_module_shape(&module_b, 1));
char *path = new_store();
diff --git a/docs/design/027-computation-core.md b/docs/design/027-computation-core.md
index 16cb149..5951f1f 100644
--- a/docs/design/027-computation-core.md
+++ b/docs/design/027-computation-core.md
@@ -414,6 +414,27 @@
- 상태, 입력, 레코드, 필드, 중첩의 수를 모듈에 맞춰 잡는다. 비트 하나씩 쓰던 집합은 길이를 가진다.
- aup 소스의 32 KB도 뺀다.
- 앞의 걸음들이 표를 바꾸므로 마지막에 한다.
+ - **설계(2026-10-18).** 뜻에 남는 것은 예산(실행량, 메모리)과 형식의 너비(머리말의 u16 수, 명령의 인자)다. 구현은 표를 모듈에
+ 맞춰 잡고, 빠른 길이 작은 모듈에만 맞는 곳(JIT가 리스트를 32개까지 비트로 세는 것 따위)은 그 길의 조건으로 남긴다(넘으면 실행기).
+ 네 걸음으로 하고, 걸음마다 시험과 명령 수의 막대를 지킨다:
+ - 4f-1: 모듈의 표를 모듈에 맞춰 잡는다. `PureModule`의 고정 배열(상태, 입력, 보기, 핸들러, 메시지 필드, 레코드, 레코드 필드,
+ 함수, 서명)이 로드가 머리말의 수(와 지은 열)로 잡는 한 블록을 가리킨다. 쓰는 곳의 모양(`m->state_type[i]`)은 그대로이고,
+ 한도는 아직 그대로다. 표현만 바뀐다.
+ - 4f-2: 상태 32를 뺀다. 상태의 비트 집합(transient, 앱이 세는 상태, 보기와 함수가 바꾸는 리스트, 걷는 리스트)은 상태마다의
+ 표시나 길이를 가진 목록이 된다. 앱의 상태마다의 배열(값, text, 리스트, 읽는 쪽의 리스트)과 스냅숏의 것도 모듈에 맞춰 잡고,
+ 이벤트와 보기가 값을 베끼던 스택의 32칸은 앱의 칸이 된다.
+ - 4f-3: 입력 32, 레코드 16, 레코드의 필드 16, 보기 64, 핸들러 64, 메시지 필드 64를 뺀다. 항목의 칸을 담던 스택의 16칸은 모듈의
+ 가장 큰 레코드에 맞춘 앱의 칸이 되고, 레코드마다의 비트 집합(바이트로 담는 필드)은 필드마다의 표시가 된다. 호스트(serve의
+ 타이머, Vega의 타이머)의 입력마다의 배열도 모듈에 맞춘다.
+ - 4f-4: 반복의 겹 8을 뺀다(실행기의 겹 칸이 자란다). aup의 소스 32 KB와 aup 안의 같은 한도들(상태, 입력, 레코드, 필드,
+ 보기, 핸들러)을 뺀다.
+ - **4f-1 만듦(2026-10-18).** `PureModule`의 표 서른넷이 로드가 잡는 한 블록(`tables`)의 자리를 가리킨다(`lay_tables`가 머리말의 수와
+ 지을 열의 수로 놓는다. 지을 열은 보기 항목과 레코드 항목의 바이트에서 먼저 센다). sanitizer 빌드는 표 사이에 독을 둔 16바이트를
+ 두어, 한 표를 넘어 읽는 것을 잡는다(시험에서 걸린 것은 없다). `PureModule`은 24,320바이트에서 2,752바이트가 되었다(코어 22,383 → 22,456줄). 시험 하나와
+ 연구의 측정 다섯이 0으로 채우지 않은 모듈을 로드에 넘겼다(로드는 전의 것을 돌려주므로 0이어야 한다). 이제 0으로 시작한다.
+ - 명령 수(b38 대 b37): 묶음과 함께 BFS −0.24%, matmul −0.21%, 나머지 ±0.1% 안이다. 실행기만은 matmul −0.45%, join +0.36%,
+ knapsack +0.28%, BFS −0.25%, roots 0.00%다. 은행 질의(s37과 s38, 같은 aup 모듈을 한 번에 번갈아 세 번의 중앙값)는 field −0.68%,
+ found −0.51%, large −0.47%, none −0.20%, in_credit −0.19%, ranks −0.12%, per_branch −0.11%다.
- **차례의 까닭.**
- 4a는 작고, 3단계의 끝을 맺는다.
- 4b는 가장 많이 지운다. 키 있는 메시지와 기준 리비전이 사라지면 4d가 단순해진다.
diff --git a/research/experiments/040-against-c/2026-10-18-counts-s38.jsonl b/research/experiments/040-against-c/2026-10-18-counts-s38.jsonl
new file mode 100644
index 0000000..1f42341
--- /dev/null
+++ b/research/experiments/040-against-c/2026-10-18-counts-s38.jsonl
@@ -0,0 +1,6 @@
+{"serve": "pure-serve", "per_branch": {"instructions": 26882993, "cycles": 11591444}, "in_credit": {"instructions": 3463530, "cycles": 1578356}, "large": {"instructions": 1431957, "cycles": 606184}, "ranks": {"instructions": 24353508, "cycles": 14592354}, "none": {"instructions": 617958, "cycles": 194299}, "field": {"instructions": 9130366, "cycles": 3276393}, "found": {"instructions": 14283102, "cycles": 10549487}}
+{"serve": "pure-serve", "per_branch": {"instructions": 26981160, "cycles": 11735192}, "in_credit": {"instructions": 3485848, "cycles": 1600650}, "large": {"instructions": 1425150, "cycles": 615394}, "ranks": {"instructions": 24395965, "cycles": 14718379}, "none": {"instructions": 616741, "cycles": 192127}, "field": {"instructions": 9068166, "cycles": 3309134}, "found": {"instructions": 14210927, "cycles": 10867715}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27279333, "cycles": 11875510}, "in_credit": {"instructions": 3552478, "cycles": 1606311}, "large": {"instructions": 1431845, "cycles": 608216}, "ranks": {"instructions": 24571658, "cycles": 14808241}, "none": {"instructions": 617958, "cycles": 190338}, "field": {"instructions": 9130469, "cycles": 3268205}, "found": {"instructions": 14280287, "cycles": 10521880}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27199381, "cycles": 11813156}, "in_credit": {"instructions": 3534764, "cycles": 1608765}, "large": {"instructions": 1425086, "cycles": 608142}, "ranks": {"instructions": 24516404, "cycles": 14877701}, "none": {"instructions": 616741, "cycles": 190908}, "field": {"instructions": 9068063, "cycles": 3313632}, "found": {"instructions": 14207590, "cycles": 10633858}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27230259, "cycles": 11911492}, "in_credit": {"instructions": 3541457, "cycles": 1606705}, "large": {"instructions": 1431789, "cycles": 602954}, "ranks": {"instructions": 24545122, "cycles": 14981424}, "none": {"instructions": 617950, "cycles": 191656}, "field": {"instructions": 9130358, "cycles": 3268257}, "found": {"instructions": 14279831, "cycles": 10508190}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27329624, "cycles": 12359635}, "in_credit": {"instructions": 3563802, "cycles": 2339609}, "large": {"instructions": 1425094, "cycles": 749512}, "ranks": {"instructions": 24586049, "cycles": 15314398}, "none": {"instructions": 616733, "cycles": 190282}, "field": {"instructions": 9068158, "cycles": 3309742}, "found": {"instructions": 14207319, "cycles": 10912495}}
diff --git a/research/experiments/041-batches/2026-10-18-counts.jsonl b/research/experiments/041-batches/2026-10-18-counts.jsonl
index f493618..7b29e36 100644
--- a/research/experiments/041-batches/2026-10-18-counts.jsonl
+++ b/research/experiments/041-batches/2026-10-18-counts.jsonl
@@ -128,3 +128,13 @@
{"version":"b37","way":"rounds","work":"matmul","instructions":21259969629,"cycles":6204053361}
{"version":"b37","way":"batches","work":"bfs","instructions":3347488616,"cycles":1663777730}
{"version":"b37","way":"rounds","work":"bfs","instructions":4709044317,"cycles":2143909347}
+{"version":"b38","way":"batches","work":"roots","instructions":157248661,"cycles":109895799}
+{"version":"b38","way":"rounds","work":"roots","instructions":2719817740,"cycles":1102038842}
+{"version":"b38","way":"batches","work":"knapsack","instructions":348023990,"cycles":106312579}
+{"version":"b38","way":"rounds","work":"knapsack","instructions":22053584918,"cycles":8200983416}
+{"version":"b38","way":"batches","work":"join","instructions":1783615210,"cycles":1338885351}
+{"version":"b38","way":"rounds","work":"join","instructions":7705975089,"cycles":4071828568}
+{"version":"b38","way":"batches","work":"matmul","instructions":702228751,"cycles":227165183}
+{"version":"b38","way":"rounds","work":"matmul","instructions":21163848735,"cycles":7467484636}
+{"version":"b38","way":"batches","work":"bfs","instructions":3339436314,"cycles":1648001735}
+{"version":"b38","way":"rounds","work":"bfs","instructions":4697444852,"cycles":2103018632}
diff --git a/research/experiments/044-specs/append_cost.c b/research/experiments/044-specs/append_cost.c
index a3ef0bb..d91e82e 100644
--- a/research/experiments/044-specs/append_cost.c
+++ b/research/experiments/044-specs/append_cost.c
@@ -9,7 +9,7 @@
#include <time.h>
static double now_ns(void) { struct timespec t; clock_gettime(CLOCK_MONOTONIC, &t); return t.tv_sec * 1e9 + t.tv_nsec; }
int main(int argc, char **argv) {
- PureModule m; char error[160];
+ 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; }
PureApp app;
if (pure_app_start(&app, &m, (PureData){0}) != PURE_OK) return 1;
diff --git a/research/experiments/044-specs/json_rec_time.c b/research/experiments/044-specs/json_rec_time.c
index 03c02c4..0d5b225 100644
--- a/research/experiments/044-specs/json_rec_time.c
+++ b/research/experiments/044-specs/json_rec_time.c
@@ -73,7 +73,7 @@ int main(int argc, char **argv) {
n += (size_t)sprintf(doc + n, "%s{\"key%d\": [1.5e3, true, null, \"text \\u00e9 %d\"]}", k ? "," : "", k, k);
doc[n++] = ']';
doc[n] = 0;
- PureModule module;
+ 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;
diff --git a/research/experiments/044-specs/json_time.c b/research/experiments/044-specs/json_time.c
index b401ece..b4b2eae 100644
--- a/research/experiments/044-specs/json_time.c
+++ b/research/experiments/044-specs/json_time.c
@@ -100,7 +100,7 @@ int main(int argc, char **argv) {
for (int k = 0; k < 2000; ++k)
n += (size_t)sprintf((char *)doc + n, "%s{\"key%d\": [1.5e3, true, null, \"text \\u00e9 %d\"]}", k ? "," : "", k, k);
doc[n++] = ']';
- PureModule module;
+ 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;
diff --git a/research/experiments/044-specs/json_tree_time.c b/research/experiments/044-specs/json_tree_time.c
index 1cac21a..0ec4bec 100644
--- a/research/experiments/044-specs/json_tree_time.c
+++ b/research/experiments/044-specs/json_tree_time.c
@@ -73,7 +73,7 @@ int main(int argc, char **argv) {
n += (size_t)sprintf(doc + n, "%s{\"key%d\": [1.5e3, true, null, \"text \\u00e9 %d\"]}", k ? "," : "", k, k);
doc[n++] = ']';
doc[n] = 0;
- PureModule module;
+ 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;
diff --git a/research/experiments/044-specs/time.c b/research/experiments/044-specs/time.c
index 248889b..f296ece 100644
--- a/research/experiments/044-specs/time.c
+++ b/research/experiments/044-specs/time.c
@@ -83,7 +83,7 @@ int main(int argc, char **argv) {
uint32_t seed = 140;
for (size_t i = 0; i < size; ++i) seed = seed * 1664525u + 1013904223u, data[i] = (unsigned char)(seed >> 24);
- PureModule module;
+ 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;