aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/module-jit.c4
-rw-r--r--core/module-run.h2
-rw-r--r--core/module.c56
-rw-r--r--core/module.h8
-rw-r--r--core/test-module.c33
-rw-r--r--docs/design/008-module-format.md4
-rw-r--r--docs/design/027-computation-core.md14
-rw-r--r--research/experiments/040-against-c/2026-10-19-counts-s42.jsonl12
-rw-r--r--research/experiments/041-batches/2026-10-19-counts.jsonl10
9 files changed, 121 insertions, 22 deletions
diff --git a/core/module-jit.c b/core/module-jit.c
index 8208095..bd04361 100644
--- a/core/module-jit.c
+++ b/core/module-jit.c
@@ -5788,7 +5788,9 @@ struct PureJit *pure_jit_make(const PureModule *m) {
const char *enabled = getenv("PURE_JIT");
/* What it makes keeps its lists' and states' sets in a word each: a module of more states runs in the executor
* alone (027's 4f-2). */
- if ((enabled && !strcmp(enabled, "0")) || !m->height || !m->code_count || m->states > JIT_STATES) return NULL;
+ if ((enabled && !strcmp(enabled, "0")) || !m->height || !m->code_count || m->states > JIT_STATES ||
+ m->nesting > PURE_MODULE_NESTING) /* (its loops' tables hold PURE_MODULE_NESTING) */
+ return NULL;
Made *made = NULL;
size_t count = 0, room = 0;
bool ok = true;
diff --git a/core/module-run.h b/core/module-run.h
index cff3f71..e79aa59 100644
--- a/core/module-run.h
+++ b/core/module-run.h
@@ -135,7 +135,7 @@ typedef struct {
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 */
+ Loop *loops; /* the module's nesting of them (PURE_MODULE_NESTING on the stack, more the app's), apart: a walk points into its loop */
/* The run of a tree compiled code read an item from last (pure_run_item):
* the list as it was (its version), its positions and each field's cells. */
struct { const PureList *list; uint64_t version; size_t start, count; const void *columns[16]; } run;
diff --git a/core/module.c b/core/module.c
index 3d38966..71a0a25 100644
--- a/core/module.c
+++ b/core/module.c
@@ -581,7 +581,6 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
if (op->op == PURE_OP_LIST_REMOVE && walking(walked, walks, arg) && (int)arg != row)
REFUSE("remove from a list an outer walk walks");
if (nest && op->op == PURE_OP_REPLY) REFUSE("reply inside a loop");
- if (nest == PURE_MODULE_NESTING && (op->op == PURE_OP_EACH || op->op == PURE_OP_LOOP)) REFUSE("loops nested deeper than 8");
if (op->op == PURE_OP_BREAK && !nest) REFUSE("break outside a loop");
if (op->op == PURE_OP_BREAK && height) REFUSE("break with values on the stack");
if (op->op == PURE_OP_ITEM_SET && m->state_key[list_of(arg)] == item_field(arg) + 1)
@@ -1513,6 +1512,20 @@ PURE_COLD static void range_code(PureModule *m, PureBlock block, int input) {
}
}
+/* How deep the loops of m's verified code nest (its blocks apart, each loop's within its own); UINT32_MAX without room. */
+static uint32_t nesting_of(const PureModule *m) {
+ uint32_t *ends = malloc(((size_t)m->code_count + 1) * sizeof(*ends)), depth = 0, most = 0;
+ if (!ends) return UINT32_MAX;
+ for (uint32_t pc = 0; pc < m->code_count; ++pc) {
+ while (depth && pc >= ends[depth - 1]) --depth;
+ if (m->code[pc].op != PURE_OP_EACH && m->code[pc].op != PURE_OP_LOOP) continue;
+ ends[depth++] = pc + 1 + (m->code[pc].arg >> 16);
+ if (depth > most) most = depth;
+ }
+ free(ends);
+ return most;
+}
+
/* A name and its place among those of one namespace, in order of name and then of place. */
typedef struct { const char *name; size_t index; } Named;
static int by_name(const void *a, const void *b) {
@@ -1527,13 +1540,14 @@ static void states_add(uint64_t *set, uint32_t state) { set[state >> 6] |= UINT6
/* What a function's code names (027's 2c), read before it is verified (its arguments bounded here): the event's lists,
* the registers and the values held by name each call has its own of, the lists not an event's it changes, its locals
* and how deep its loops nest; the functions it calls (calls, a bit each). */
-PURE_COLD static void function_names(PureModule *m, uint32_t fn, uint64_t *calls) {
+PURE_COLD static bool function_names(PureModule *m, uint32_t fn, uint64_t *calls) {
PureFunction *f = &m->function[fn];
uint64_t *lists = m->function_lists + (size_t)fn * m->state_words, *registers = m->function_registers + (size_t)fn * m->state_words,
*changes = m->function_changes + (size_t)fn * m->state_words;
- uint32_t end = f->code.start + f->code.length, ends[PURE_MODULE_NESTING];
+ if (f->code.start > m->code_count || f->code.length > m->code_count - f->code.start) return true; /* refused as it is verified */
+ uint32_t end = f->code.start + f->code.length, *ends = malloc(((size_t)f->code.length + 1) * sizeof(*ends));
unsigned depth = 0;
- if (f->code.start > m->code_count || f->code.length > m->code_count - f->code.start) return; /* refused as it is verified */
+ if (!ends) return false;
for (uint32_t pc = f->code.start; pc < end; ++pc) {
const PureOp *op = &m->code[pc];
uint32_t arg = op->arg, list = UINT32_MAX;
@@ -1551,9 +1565,9 @@ PURE_COLD static void function_names(PureModule *m, uint32_t fn, uint64_t *calls
case PURE_OP_ITEM_GET: case PURE_OP_ITEM_SET: case PURE_OP_APPEND_IN: list = list_of(arg); break;
case PURE_OP_EACH: case PURE_OP_FOLD: case PURE_OP_LOOP: case PURE_OP_LOCAL_LOAD: case PURE_OP_LOCAL_STORE:
if (op->op == PURE_OP_EACH || op->op == PURE_OP_FOLD) list = arg >> 8 & 0xFF;
- if ((op->op == PURE_OP_EACH || op->op == PURE_OP_LOOP) && depth < PURE_MODULE_NESTING) {
+ if (op->op == PURE_OP_EACH || op->op == PURE_OP_LOOP) { /* (a loop an instruction at least: depth within ends) */
ends[depth++] = pc + 1 + (arg >> 16);
- if (depth > f->nesting) f->nesting = (uint8_t)depth;
+ if (depth > f->nesting) f->nesting = depth;
}
if (op->op == PURE_OP_EACH || op->op == PURE_OP_FOLD) break;
if ((arg & 0xFF) < PURE_MODULE_LOCALS && (arg & 0xFF) >= f->locals) f->locals = (uint8_t)((arg & 0xFF) + 1);
@@ -1565,6 +1579,8 @@ PURE_COLD static void function_names(PureModule *m, uint32_t fn, uint64_t *calls
}
if (event_list_state(m, list)) states_add(bound_list(m, list) ? registers : lists, list);
}
+ free(ends);
+ return true;
}
PURE_COLD PureResult pure_module_read(const char *path, PureModule *out, char *error, size_t error_size) {
@@ -2082,7 +2098,11 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
}
/* What each function names, and the lists it changes with those the functions it calls change (until none adds). */
uint64_t calls[PURE_MODULE_FUNCTIONS] = {0};
- for (uint32_t i = 0; i < out->functions; ++i) function_names(out, i, &calls[i]);
+ for (uint32_t i = 0; i < out->functions; ++i)
+ if (!function_names(out, i, &calls[i])) {
+ result = PURE_ALLOCATION;
+ REFUSE("out of memory", i);
+ }
for (bool more = out->functions > 0; more;) {
more = false;
for (uint32_t i = 0; i < out->functions; ++i)
@@ -2233,7 +2253,13 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
for (uint32_t i = 0; i < out->views; ++i)
for (uint32_t pc = out->view_code[i].start; pc < out->view_code[i].start + out->view_code[i].length; ++pc)
if (out->code[pc].op == PURE_OP_KEPT_GET) out->view_body[i] = true;
- out->runner = out->calls ? 2 : out->batches ? 1 : 0;
+ /* How deep its loops nest (027's 4f-4): past what a run keeps on its stack, its blocks run in the executor that calls,
+ * its loops in its app's room. */
+ if ((out->nesting = nesting_of(out)) == UINT32_MAX) {
+ result = PURE_ALLOCATION;
+ REFUSE("out of memory", 0);
+ }
+ out->runner = out->calls || out->nesting > PURE_MODULE_NESTING ? 2 : out->batches ? 1 : 0;
out->jit = pure_jit_make(out); /* none: the executor runs it all */
return PURE_OK;
refused:
@@ -4829,10 +4855,21 @@ static __attribute__((noinline, cold)) void unwind_calls(Frame *frame, Call *top
/* The executor's run of a block of a module with functions (027's 2c): run_batches' steps and batches, and calls: a
* CALL opens its function's frame and its block runs on in this loop, a RETURN closes it. Its own copy of the steps,
* as run_batches is. */
+/* Room for a run's loops nested deeper than PURE_MODULE_NESTING (027's 4f-4): the app's, grown to nesting; NULL without. */
+static __attribute__((cold, noinline)) Loop *deep_loops(PureApp *app, uint32_t nesting) {
+ if (app->deep_room < nesting) {
+ Loop *grown = realloc(app->deep, nesting * sizeof(*grown));
+ if (!grown) return NULL;
+ app->deep = grown, app->deep_room = nesting;
+ }
+ return app->deep;
+}
+
static __attribute__((noinline, aligned(64))) PureResult run_calls(const PureModule *m, PureValue *values, PureBlock block, Event *ev,
Slot *result) {
Slot stack[PURE_MODULE_STACK];
- Loop loops[PURE_MODULE_NESTING];
+ Loop few[PURE_MODULE_NESTING], *loops = m->nesting > PURE_MODULE_NESTING ? deep_loops(ev->app, m->nesting) : few;
+ if (!loops) return PURE_ALLOCATION;
Frame frame;
open_frame(&frame, m, values, block, ev, stack, loops);
Call *top = NULL;
@@ -5161,6 +5198,7 @@ void pure_app_stop(PureApp *app) {
pure_draft_close(app->stored_values);
free(app->carried);
free(app->by_input);
+ free(app->deep);
pure_draft_close(app->stored_links);
pure_draft_close(app->stored_seq);
pure_save_close_start(app);
diff --git a/core/module.h b/core/module.h
index 26259fe..8a84ddb 100644
--- a/core/module.h
+++ b/core/module.h
@@ -194,6 +194,8 @@ enum { PURE_FOLD_SLICED = 0x80, PURE_EACH_SLICED = 1 };
* Loops (LOOP and EACH) nest 8 deep. Within a walk, code that a loop runs
* may change the items of any list but not add to or remove from a list an
* outer walk walks; the innermost walk may remove from its own. */
+/* PURE_MODULE_NESTING: the loops nested that a run keeps on its stack; a module nesting deeper (027's 4f-4) runs in the
+ * executor that calls, its loops in its app's room (PureApp.deep). */
enum { PURE_LOOP_INTEGER = 256, PURE_MODULE_NESTING = 8 };
/* What a unit (an event, or a view's evaluation) may run when its app sets
* no work budget (pure_app_work), and what an event may hold for what it
@@ -331,7 +333,8 @@ typedef struct {
uint16_t first;
uint8_t params, results;
uint32_t held;
- uint8_t height, locals, nesting;
+ uint8_t height, locals;
+ uint32_t nesting;
} PureFunction;
typedef struct { char name[PURE_MODULE_NAME]; uint8_t type; uint16_t rows; uint32_t offset; } PureField;
typedef struct {
@@ -441,6 +444,7 @@ typedef struct {
uint8_t *signature_type;
uint16_t *signature_record;
bool calls; /* it has functions: its blocks run in the executor that calls (module.c) */
+ uint32_t nesting; /* its loops' deepest nesting (set at load) */
} PureModule;
/* Whether a state of m is transient (PURE_STATE_TRANSIENT). */
@@ -623,6 +627,8 @@ typedef struct {
struct { unsigned char **blocks; size_t *sizes; unsigned count, at; size_t used; } calls;
struct PureSpare { PureList **lists; unsigned count, capacity; } *spare;
PureValue *pending; /* the values an event works on until it ends */
+ void *deep; /* a run's loops, a module nesting past PURE_MODULE_NESTING's (its nesting of them) */
+ uint32_t deep_room;
PureVersion *marked_texts; /* where the event running began: each text's version, each stored list's journal, */
size_t *marked_lists;
/* Each input's: its last number sent and as saved, and where the event running began; one block (by_input) for
diff --git a/core/test-module.c b/core/test-module.c
index 5bd1c8b..cb8e228 100644
--- a/core/test-module.c
+++ b/core/test-module.c
@@ -7100,14 +7100,17 @@ static void loops(void) {
deep[3 * d + 1] = (O){C, 0, 1};
deep[3 * d + 2] = (O){LOOP, BODY(0, 26 - 3 * d - 3), 0};
}
- O deeper[9 * 3 + 2];
- for (unsigned d = 0; d < 9; ++d) {
+ enum { DEEPER = 40 }; /* nested past what a run keeps on its stack (027's 4f-4): its app's room */
+ O deeper[DEEPER * 3 + 4];
+ for (unsigned d = 0; d < DEEPER; ++d) {
deeper[3 * d] = (O){C, 0, 0};
- deeper[3 * d + 1] = (O){C, 0, 1};
- deeper[3 * d + 2] = (O){LOOP, BODY(0, 29 - 3 * d - 3), 0};
+ deeper[3 * d + 1] = (O){C, 0, 2}; /* each twice: the innermost 2^40 times past any budget, so its first fault */
+ deeper[3 * d + 2] = (O){LOOP, BODY(0, DEEPER * 3 + 4 - 3 * d - 3), 0};
}
- deeper[27] = (O){C, 0, 1};
- deeper[28] = (O){ST, S_, 0};
+ deeper[3 * DEEPER] = (O){PURE_OP_LOAD, S_, 0};
+ deeper[3 * DEEPER + 1] = (O){C, 0, 1};
+ deeper[3 * DEEPER + 2] = (O){PURE_OP_ADD, 0, 0};
+ deeper[3 * DEEPER + 3] = (O){ST, S_, 0};
const struct { const O *code; size_t count; const char *why; } refused[] = {
{outside, LEN(outside), "break outside a loop"},
{valued, LEN(valued), "break with values on the stack"},
@@ -7116,7 +7119,6 @@ static void loops(void) {
{flagged, LEN(flagged), "no such state"},
{two_ways, LEN(two_ways), "local used as two things"},
{into_outer, LEN(into_outer), "insert into the list walked"},
- {deeper, LEN(deeper), "loops nested deeper than 8"},
};
for (size_t i = 0; i < LEN(refused); ++i) {
H one[] = {{1, 0, (uint32_t)refused[i].count}};
@@ -7136,7 +7138,22 @@ static void loops(void) {
nested.no = LEN(deep);
nested.h = eight;
nested.nh = 1;
- CHECK(load(nested, &module_b) == PURE_OK); /* eight deep is allowed */
+ CHECK(load(nested, &module_b) == PURE_OK && module_b.nesting == 8 && module_b.runner != 2); /* on the stack */
+ H forty[] = {{1, 0, LEN(deeper)}};
+ nested.o = deeper, nested.no = LEN(deeper), nested.h = forty;
+ CHECK(load(nested, &module_b) == PURE_OK && module_b.nesting == DEEPER && module_b.runner == 2 && !module_b.jit);
+ PureApp deep_app;
+ CHECK(pure_app_start(&deep_app, &module_b, (PureData){0}) == PURE_OK);
+ pure_app_work(&deep_app, 100000); /* (2^40 rounds would never end) */
+ CHECK(press(&deep_app, 0) == PURE_LIMIT && deep_app.fault == PURE_FAULT_WORK && deep_app.values[S_].num == 0);
+ pure_app_work(&deep_app, 0);
+ pure_app_stop(&deep_app);
+ for (unsigned d = 0; d < DEEPER; ++d) deeper[3 * d + 1] = (O){C, 0, d % 2 ? 1 : 2}; /* 2^20 rounds */
+ CHECK(load(nested, &module_b) == PURE_OK && pure_app_start(&deep_app, &module_b, (PureData){0}) == PURE_OK);
+ pure_app_work(&deep_app, UINT64_C(1) << 40);
+ CHECK(press(&deep_app, 0) == PURE_OK && deep_app.values[S_].num == 1 << 20 && press(&deep_app, 0) == PURE_OK &&
+ deep_app.values[S_].num == 2 << 20);
+ pure_app_stop(&deep_app);
pure_module_release(&module_b);
pure_module_release(&module_a);
#undef BODY
diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md
index fa59692..dde292e 100644
--- a/docs/design/008-module-format.md
+++ b/docs/design/008-module-format.md
@@ -153,7 +153,7 @@ v7은 027의 4단계(패턴 모으기)의 판이다. 4b로 **키별 문서**를
| 60 | MOD | n n → n | 나눗셈을 내림한 나머지. 결과는 나누는 수의 부호를 따른다(0도). 0으로 나누면 `divide` |
| 61 | SEND | f… → | 출력 `인자`의 필드 값을 필드 순서대로 받아(u32·수는 n, bool은 bool, 바이트는 b, text는 t) 메시지 페이로드(접두부와 가변 필드)로 만들고 이벤트의 일부로 보낸다. u32 필드의 값이 0–2³²−1의 정수가 아니면 `limit`. 핸들러 전용 |
| 62 | OUTER_ROW | → i | 열 안의 집계 블록에서 그 열의 행(집계의 항목은 ROW). 걷기 안의 집계에서는 걷는 항목 |
-| 63 | EACH | | 걷기. `인자`의 비트 8–15는 리스트 상태, 16–31은 뒤따르는 블록의 길이 L. 그 L개 명령이 리스트가 시작할 때 가진 항목마다 차례로 핸들러의 코드로 돈다(`ROW`가 항목). 블록은 상태, 그 리스트의 항목(필드 바꾸기, 지우기), 다른 리스트를 바꿀 수 있지만 걷는 리스트에 넣을 수는 없다. 걷는 항목이나 그 앞의 항목을 지우면 걷기가 따라가 다음 항목을 건너뛰지 않는다. 걷기와 LOOP는 서로 안에 8겹까지 둔다. 안쪽의 코드는 바깥 걷기가 도는 리스트에 넣거나 지우지 못한다. 핸들러 전용 |
+| 63 | EACH | | 걷기. `인자`의 비트 8–15는 리스트 상태, 16–31은 뒤따르는 블록의 길이 L. 그 L개 명령이 리스트가 시작할 때 가진 항목마다 차례로 핸들러의 코드로 돈다(`ROW`가 항목). 블록은 상태, 그 리스트의 항목(필드 바꾸기, 지우기), 다른 리스트를 바꿀 수 있지만 걷는 리스트에 넣을 수는 없다. 걷는 항목이나 그 앞의 항목을 지우면 걷기가 따라가 다음 항목을 건너뛰지 않는다. 걷기와 LOOP는 서로 안에 몇 겹이든 둔다(027의 4f-4: 8겹보다 깊은 모듈은 부르는 실행기가 앱의 칸으로 돈다). 안쪽의 코드는 바깥 걷기가 도는 리스트에 넣거나 지우지 못한다. 핸들러 전용 |
| 64 | LOCAL_LOAD | → n, bool 또는 i | 이벤트의 지역 칸. `인자`의 비트 0–7은 칸(64개), 8–15는 담는 것(0 수, 1 인덱스, 2 bool), 16–31은 인덱스의 리스트. 칸은 이벤트가 시작할 때 0이고 저장되지 않는다. 핸들러와 그 안의 걷기, 그 안의 집계에서 읽는다 |
| 65 | LOCAL_STORE | n, bool, i64 또는 i → | 그 칸에 넣는다. 핸들러와 그 안의 걷기에서. 담는 것 3은 i64. 한 핸들러는 한 칸을 한 가지(수, bool, i64, 또는 한 리스트의 인덱스)로만 쓴다 |
| 66–68 | I64_ADD I64_SUB I64_MUL | i64 i64 → i64 | 범위를 넘으면 `overflow` |
@@ -227,7 +227,7 @@ bool은 실행 중에 수의 칸에 든 0이나 1이고, i64는 같은 8바이�
걷는 리스트에 넣지(LIST_APPEND·LIST_INSERT) 못하며, 응답(REPLY)하지 않는다(응답은 이벤트에 하나다). 안의 집계는 걷는 항목을
OUTER_ROW로 읽는다. 둘레의 블록은 EACH와 그 블록을 명령 하나로 보고, 앞의 분기가 블록 안으로 들어오지 못한다. 걷는 리스트는
줄기만 하므로 한 걷기는 시작할 때의 항목 수 × 블록 길이를 넘지 않는다.
-- 반복(EACH와 LOOP, [018](018-lifetimes-and-computation.md))은 서로 안에 8겹까지 둔다. 검증기는 둘레의 걷기들이 도는 리스트의
+- 반복(EACH와 LOOP, [018](018-lifetimes-and-computation.md))은 서로 안에 몇 겹이든 둔다(4f-4 전에는 8겹). 검증기는 둘레의 걷기들이 도는 리스트의
집합을 들고 내려가, 안의 코드가 그 가운데 어느 것에 넣는 것과, 가장 안쪽 걷기의 것이 아닌 것에서 지우는 것을 거절한다. 그래서
실행기는 가장 안쪽 걷기의 자리만 고친다. LOOP의 블록은 둘레 블록과 같은 자리(핸들러나 걷기)의 코드로 검증하고 둘레는 LOOP와
그 블록을 명령 하나로 본다. LOOP는 수 둘이나 i64 둘을 꺼내고 그 칸을 그 타입의 지역 칸으로 쓴다(한 핸들러에서 한 칸은 한
diff --git a/docs/design/027-computation-core.md b/docs/design/027-computation-core.md
index 06bfe02..687a51f 100644
--- a/docs/design/027-computation-core.md
+++ b/docs/design/027-computation-core.md
@@ -479,6 +479,20 @@
- 명령 수(b41 대 b40): 묶음과 함께 BFS +0.12%, 실행기만 BFS −0.09%, 나머지 ±0.001% 안이다. 은행 질의(s40과 s41을 한 번에 번갈아
여섯 번)는 in_credit +0.09%, per_branch +0.05%, ranks +0.03%, 나머지 ±0.01% 안이다. 처음 세 번의 in_credit +1.16%는 그 질의의
흔들림이었다(같은 빌드의 여섯 번 사이 2.75%).
+ - **4f-4 만듦(2026-10-19).**
+ - Pure: 반복은 몇 겹이든 둔다(검증기의 8겹 거절이 없어졌다). 로드가 모듈의 가장 깊은 겹을 센다(`nesting`). 8겹까지는 전처럼 실행의
+ 스택에 겹 칸을 두고, 더 깊은 모듈은 부르는 실행기(runner 2)가 앱의 칸(`PureApp.deep`, 그 겹만큼 자람)으로 돈다. 그래서 `run()`의
+ 뜨거운 길은 그대로다. JIT은 8겹까지인 모듈만 컴파일한다. 함수의 겹(호출 프레임의 크기)도 막지 않고 센다(u32).
+ - aup: 소스의 32 KB가 없어졌다(파일과 표준 입력을 크기대로 읽는다). 컴파일러의 표는 고정 배열 그대로이되 Pure의 너비에 맞춰
+ 올렸다(상태 4,096, 입력 1,024, 보기 4,096, 핸들러 4,096, 레코드 1,024, 레코드 필드 16,384): 컴파일러는 15 MB를 calloc으로
+ 잡고, 쓰지 않는 칸은 메모리에 올라오지 않는다. 진단은 그 수를 말한다. 출력은 255까지(Pure의 저장 너비), 걷기와 집계가 도는
+ 리스트는 상태 번호 256 아래(그 인자의 바이트)라고 aup가 미리 말한다. 표를 정말 모듈에 맞춰 자라게 하는 것은 컴파일러의 표
+ 전체를 바꾸는 일이라 남겼다.
+ - 시험: 단위 시험이 40겹의 반복을 로드해 끝까지(2^20번) 돌리고, 2^40번은 일의 예산에서 멈추게 한다(8겹은 스택에서 돈다). aup의
+ 시험이 상태 301, 입력 40, 보기 70, 레코드 20인 48 KB 소스를 짓고 pure-run으로 마지막 것들까지 돌린다.
+ - 명령 수(b42 대 b41): 묶음과 함께 BFS −0.12%, 실행기만 BFS +0.09%, 나머지 ±0.001% 안이다. 은행 질의(s41과 s42를 한 번에 번갈아
+ 여섯 번)는 in_credit +1.04%, per_branch +0.60%, ranks +0.36%, 나머지 ±0.01% 안이다. 그 셋은 흔들림이다: 은행 앱에는 함수가 없어
+ 바뀐 길(부르는 실행기)을 지나지 않고, s42의 in_credit 여섯(3.51M–3.55M)은 같은 s41 빌드가 앞서 낸 여섯(3.49M–3.59M) 안에 있다.
- **차례의 까닭.**
- 4a는 작고, 3단계의 끝을 맺는다.
- 4b는 가장 많이 지운다. 키 있는 메시지와 기준 리비전이 사라지면 4d가 단순해진다.
diff --git a/research/experiments/040-against-c/2026-10-19-counts-s42.jsonl b/research/experiments/040-against-c/2026-10-19-counts-s42.jsonl
new file mode 100644
index 0000000..1acc4ee
--- /dev/null
+++ b/research/experiments/040-against-c/2026-10-19-counts-s42.jsonl
@@ -0,0 +1,12 @@
+{"serve": "pure-serve", "per_branch": {"instructions": 27138300, "cycles": 11675408}, "in_credit": {"instructions": 3517583, "cycles": 1585017}, "large": {"instructions": 1412854, "cycles": 596196}, "ranks": {"instructions": 24467336, "cycles": 14962238}, "none": {"instructions": 617338, "cycles": 189167}, "field": {"instructions": 8917669, "cycles": 3092238}, "found": {"instructions": 14070023, "cycles": 10250271}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27283267, "cycles": 11981665}, "in_credit": {"instructions": 3549891, "cycles": 1656175}, "large": {"instructions": 1412822, "cycles": 585341}, "ranks": {"instructions": 24546190, "cycles": 15156831}, "none": {"instructions": 617346, "cycles": 191549}, "field": {"instructions": 8917574, "cycles": 3114628}, "found": {"instructions": 14067807, "cycles": 10348701}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27003084, "cycles": 11688137}, "in_credit": {"instructions": 3487109, "cycles": 1635503}, "large": {"instructions": 1412886, "cycles": 633989}, "ranks": {"instructions": 24391174, "cycles": 14719131}, "none": {"instructions": 617338, "cycles": 188619}, "field": {"instructions": 8917669, "cycles": 3095214}, "found": {"instructions": 14072079, "cycles": 10272068}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27117645, "cycles": 11781646}, "in_credit": {"instructions": 3512871, "cycles": 1594941}, "large": {"instructions": 1412862, "cycles": 599706}, "ranks": {"instructions": 24455022, "cycles": 14950055}, "none": {"instructions": 617346, "cycles": 189601}, "field": {"instructions": 8917574, "cycles": 3098532}, "found": {"instructions": 14068918, "cycles": 10488036}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27267050, "cycles": 11812291}, "in_credit": {"instructions": 3546268, "cycles": 1648298}, "large": {"instructions": 1412830, "cycles": 588335}, "ranks": {"instructions": 24536708, "cycles": 14891183}, "none": {"instructions": 617330, "cycles": 190155}, "field": {"instructions": 8917558, "cycles": 3094820}, "found": {"instructions": 14068054, "cycles": 10326354}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27233123, "cycles": 11925662}, "in_credit": {"instructions": 3538687, "cycles": 1655252}, "large": {"instructions": 1412886, "cycles": 586989}, "ranks": {"instructions": 24518500, "cycles": 14994917}, "none": {"instructions": 617346, "cycles": 218887}, "field": {"instructions": 8917574, "cycles": 3188384}, "found": {"instructions": 14071543, "cycles": 10873920}}
+{"serve": "pure-serve", "per_branch": {"instructions": 26975385, "cycles": 11681165}, "in_credit": {"instructions": 3480991, "cycles": 2115596}, "large": {"instructions": 1412838, "cycles": 602620}, "ranks": {"instructions": 24377285, "cycles": 14689482}, "none": {"instructions": 617338, "cycles": 193643}, "field": {"instructions": 8917566, "cycles": 3188568}, "found": {"instructions": 14071342, "cycles": 10541964}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27232539, "cycles": 12061757}, "in_credit": {"instructions": 3538541, "cycles": 1702765}, "large": {"instructions": 1412830, "cycles": 593655}, "ranks": {"instructions": 24517700, "cycles": 14984273}, "none": {"instructions": 617346, "cycles": 191378}, "field": {"instructions": 8917677, "cycles": 3098210}, "found": {"instructions": 14069223, "cycles": 10389444}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27239514, "cycles": 11839390}, "in_credit": {"instructions": 3540056, "cycles": 1603441}, "large": {"instructions": 1412806, "cycles": 587818}, "ranks": {"instructions": 24521732, "cycles": 14985222}, "none": {"instructions": 617338, "cycles": 190917}, "field": {"instructions": 8917669, "cycles": 3102972}, "found": {"instructions": 14069663, "cycles": 10261150}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27221091, "cycles": 11866260}, "in_credit": {"instructions": 3535901, "cycles": 1585627}, "large": {"instructions": 1412830, "cycles": 581226}, "ranks": {"instructions": 24511352, "cycles": 15001625}, "none": {"instructions": 617346, "cycles": 189639}, "field": {"instructions": 8917677, "cycles": 3112460}, "found": {"instructions": 14070503, "cycles": 10528476}}
+{"serve": "pure-serve", "per_branch": {"instructions": 26992615, "cycles": 11753080}, "in_credit": {"instructions": 3484688, "cycles": 1589798}, "large": {"instructions": 1412902, "cycles": 597026}, "ranks": {"instructions": 24385483, "cycles": 14773173}, "none": {"instructions": 617338, "cycles": 188651}, "field": {"instructions": 8917566, "cycles": 3091770}, "found": {"instructions": 14071862, "cycles": 10409981}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27288493, "cycles": 11877149}, "in_credit": {"instructions": 3551253, "cycles": 1643562}, "large": {"instructions": 1412838, "cycles": 585097}, "ranks": {"instructions": 24549267, "cycles": 14913525}, "none": {"instructions": 617346, "cycles": 189643}, "field": {"instructions": 8917677, "cycles": 3091112}, "found": {"instructions": 14068887, "cycles": 10454306}}
diff --git a/research/experiments/041-batches/2026-10-19-counts.jsonl b/research/experiments/041-batches/2026-10-19-counts.jsonl
index 747b71e..df0c154 100644
--- a/research/experiments/041-batches/2026-10-19-counts.jsonl
+++ b/research/experiments/041-batches/2026-10-19-counts.jsonl
@@ -8,3 +8,13 @@
{"version":"b41","way":"rounds","work":"matmul","instructions":21186408240,"cycles":7051314065}
{"version":"b41","way":"batches","work":"bfs","instructions":3345460567,"cycles":1663687287}
{"version":"b41","way":"rounds","work":"bfs","instructions":4705144592,"cycles":2077470928}
+{"version":"b42","way":"batches","work":"roots","instructions":157246340,"cycles":109891554}
+{"version":"b42","way":"rounds","work":"roots","instructions":2715504006,"cycles":1021982755}
+{"version":"b42","way":"batches","work":"knapsack","instructions":348031404,"cycles":105085352}
+{"version":"b42","way":"rounds","work":"knapsack","instructions":22162562573,"cycles":7853201015}
+{"version":"b42","way":"batches","work":"join","instructions":1783720533,"cycles":1009306459}
+{"version":"b42","way":"rounds","work":"join","instructions":7723677573,"cycles":3393842728}
+{"version":"b42","way":"batches","work":"matmul","instructions":702230637,"cycles":227827451}
+{"version":"b42","way":"rounds","work":"matmul","instructions":21186409896,"cycles":7038688726}
+{"version":"b42","way":"batches","work":"bfs","instructions":3341462103,"cycles":1673340823}
+{"version":"b42","way":"rounds","work":"bfs","instructions":4709148541,"cycles":2184940702}