diff options
| -rw-r--r-- | core/module-jit.c | 26 | ||||
| -rw-r--r-- | core/module-save.c | 64 | ||||
| -rw-r--r-- | core/module-save.h | 9 | ||||
| -rw-r--r-- | core/module-store.c | 13 | ||||
| -rw-r--r-- | core/module.c | 385 | ||||
| -rw-r--r-- | core/module.h | 74 | ||||
| -rw-r--r-- | core/test-module.c | 33 | ||||
| -rw-r--r-- | docs/design/008-module-format.md | 8 | ||||
| -rw-r--r-- | docs/design/027-computation-core.md | 18 | ||||
| -rw-r--r-- | research/experiments/040-against-c/2026-10-18-counts-s39.jsonl | 6 | ||||
| -rw-r--r-- | research/experiments/040-against-c/2026-10-18-counts-s39b.jsonl | 6 | ||||
| -rw-r--r-- | research/experiments/041-batches/2026-10-18-counts.jsonl | 20 | ||||
| -rw-r--r-- | serve/serve.c | 16 |
13 files changed, 459 insertions, 219 deletions
diff --git a/core/module-jit.c b/core/module-jit.c index 5ba9f03..8208095 100644 --- a/core/module-jit.c +++ b/core/module-jit.c @@ -28,6 +28,9 @@ #include <stddef.h> #include <sys/mman.h> +/* The states a module it compiles has at most: its sets of lists and states are a word's bits (027's 4f-2). */ +enum { JIT_STATES = 32 }; + struct PureJit { unsigned char *memory; /* the code, executable */ size_t size; @@ -4131,7 +4134,7 @@ static int tlist(Tier *t, TList *lists, unsigned list, unsigned part) { /* What each list access needs, as values where the tier begins: a column (its address tagged with its cells' width), the count, the key bits and their reach. */ static void tier_lists(Tier *t, TList *lists) { - for (unsigned i = 0; i < PURE_MODULE_STATES; ++i) { + for (unsigned i = 0; i < JIT_STATES; ++i) { for (unsigned p = 0; p < L_PARTS; ++p) lists[i].part[p] = -1; lists[i].position = lists[i].key = lists[i].full = false; } @@ -4167,7 +4170,7 @@ static void tier_lists(Tier *t, TList *lists) { } for (size_t i = 0; i < made; ++i) if (t->vals[i].op == T_FIND) lists[t->vals[i].list].full = true; - for (unsigned i = 0; i < PURE_MODULE_STATES; ++i) { /* found by key and by position: DENSE and full, its count its keys' reach */ + for (unsigned i = 0; i < JIT_STATES; ++i) { /* found by key and by position: DENSE and full, its count its keys' reach */ if (lists[i].key && lists[i].position) lists[i].full = true; if (lists[i].full) (void)tlist(t, lists, i, L_COUNT); } @@ -4583,10 +4586,9 @@ static unsigned tbase(Tier *t, unsigned which, unsigned into) { case B_LOCALS: load64(e, into, X19, offsetof(Frame, ev)), load64(e, into, into, offsetof(Event, locals)); break; case B_VALUES: load64(e, into, X19, offsetof(Frame, values)); break; case B_APP: load64(e, into, X19, offsetof(Frame, app)); break; - case B_LISTS: + case B_LISTS: /* the app's lists (an array of them since 027's 4f-2) */ load64(e, into, X19, offsetof(Frame, app)); - mov_imm(e, X10, offsetof(PureApp, lists)); - add_reg(e, into, into, X10); + load64(e, into, into, offsetof(PureApp, lists)); break; case B_LOOPS: load64(e, into, X19, offsetof(Frame, loops)); break; case B_EV: load64(e, into, X19, offsetof(Frame, ev)); break; @@ -4602,8 +4604,7 @@ static void tier_fixed(Tier *t) { load64(e, X21, X19, offsetof(Frame, values)); load64(e, X22, X19, offsetof(Frame, ev)); load64(e, X23, X19, offsetof(Frame, app)); - mov_imm(e, X24, offsetof(PureApp, lists)); - add_reg(e, X24, X23, X24); + load64(e, X24, X23, offsetof(PureApp, lists)); load64(e, X27, X19, offsetof(Frame, loops)); load64(e, X28, X22, offsetof(Event, locals)); } @@ -5354,7 +5355,7 @@ static void tier_emit(Tier *t, int entry, int fallback) { Emit *e = k->e; const LoopAt *l0 = &k->loops[t->nest]; bind(e, entry); - for (unsigned list = 0; list < PURE_MODULE_STATES; ++list) { /* each list as the tier reads it (pure_list_suits) */ + for (unsigned list = 0; list < JIT_STATES; ++list) { /* each list as the tier reads it (pure_list_suits) */ const TList *l = &t->lists[list]; unsigned needs = 0, narrow = 0; for (unsigned f = 0; f < 16; ++f) narrow |= l->part[f] >= 0 ? 1u << f : 0; @@ -5606,7 +5607,7 @@ static Tier *tier_plan(Block *k, unsigned li) { if (!k->loops[l].walk && k->loops[l].at >= l0->at && k->loops[l].end <= l0->end) t->slots |= UINT64_C(1) << (k->m->code[k->loops[l].at].arg & 0x3F); tier_liveness(t); tier_build(t); - if (!t->failed && !(t->lists = calloc(PURE_MODULE_STATES, sizeof(*t->lists)))) t->failed = true; + if (!t->failed && !(t->lists = calloc(JIT_STATES, sizeof(*t->lists)))) t->failed = true; if (!t->failed) tier_lists(t, t->lists); const char *hoist = getenv("PURE_TIER_HOIST"); if (!t->failed && !(hoist && !strcmp(hoist, "0"))) tier_hoist(t); @@ -5656,8 +5657,7 @@ static bool compile_block(const PureModule *m, PureBlock block, unsigned end_hei load64(e, X21, X19, offsetof(Frame, values)); load64(e, X22, X19, offsetof(Frame, ev)); load64(e, X23, X19, offsetof(Frame, app)); - mov_imm(e, X24, offsetof(PureApp, lists)); - add_reg(e, X24, X23, X24); /* the app's lists */ + load64(e, X24, X23, offsetof(PureApp, lists)); /* the app's lists */ load64(e, X27, X19, offsetof(Frame, loops)); load64(e, X28, X22, offsetof(Event, locals)); mov_imm(e, X9, UINT64_C(0x7FF0000000000000)); @@ -5786,7 +5786,9 @@ static bool want(const PureModule *m, PureBlock block, unsigned leaves, Made **m struct PureJit *pure_jit_make(const PureModule *m) { const char *enabled = getenv("PURE_JIT"); - if ((enabled && !strcmp(enabled, "0")) || !m->height || !m->code_count) return NULL; + /* 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; Made *made = NULL; size_t count = 0, room = 0; bool ok = true; diff --git a/core/module-save.c b/core/module-save.c index c70a181..63553e0 100644 --- a/core/module-save.c +++ b/core/module-save.c @@ -67,7 +67,7 @@ enum { JOURNAL_MIN = 1 << 20 }; static const unsigned char links_magic[4] = {'P', 'L', 'K', '4'}, links_magic_3[4] = {'P', 'L', 'K', '3'}, links_magic_2[4] = {'P', 'L', 'K', '2'}, links_magic_1[4] = {'P', 'L', 'K', '1'}; typedef struct { const char *name; unsigned fields; uint8_t kinds[3]; char names[3][PURE_MODULE_NAME]; } RuntimeList; -static const RuntimeList runtime_lists[PURE_LISTS_STORED - PURE_LIST_HEARD] = { +static const RuntimeList runtime_lists[PURE_LIST_RUNTIME] = { {"0heard", 2, {1, 0}, {"source", "seq"}}, {"0sent", 3, {1, 0, 2}, {"output", "seq", "payload"}}, }; @@ -429,7 +429,7 @@ PURE_COLD PureResult pure_save_open_log(PureApp *app) { void pure_save_release(PureApp *app) { if (!app->log) return; uint64_t kept = app->seq + 1; - for (uint32_t i = 0; i < PURE_LISTS_STORED; ++i) + for (size_t i = 0; i < pure_slots(app); ++i) if (app->logged_since[i] && app->logged_since[i] < kept) kept = app->logged_since[i]; pure_log_release(app->log, kept - 1); } @@ -673,35 +673,36 @@ PureCheckpoint *pure_app_checkpoint(PureApp *app) { if (!app->log || app->checkpoint || app->open || app->uncertain || app->reading) return NULL; /* A list whose logged changes outgrew its items, or, when the log grew * past LOG_KEPT, the one that holds it back most: the heard list is one. */ - int due = -1, oldest = -1; + int due = -1, oldest = -1; /* slots */ uint64_t items = 0; - for (uint32_t i = 0; i < PURE_LISTS_STORED && due < 0; ++i) { - const PureList *list = stored_list(app, i); + for (size_t i = 0; i < pure_slots(app) && due < 0; ++i) { + const PureList *list = stored_list(app, pure_place(app, i)); size_t size = list ? pure_list_size(list) : 0; items += size; if (!list || !app->logged_since[i]) continue; if (app->logged[i] > (size > JOURNAL_MIN ? size : JOURNAL_MIN)) due = (int)i; else if (oldest < 0 || app->logged_since[i] < app->logged_since[oldest]) oldest = (int)i; } - int pick = due >= 0 ? due : pure_log_bytes(app->log) > (items > LOG_KEPT ? items : LOG_KEPT) ? oldest : -1; - if (pick < 0) return NULL; + int slot = due >= 0 ? due : pure_log_bytes(app->log) > (items > LOG_KEPT ? items : LOG_KEPT) ? oldest : -1; + if (slot < 0) return NULL; + uint32_t pick = pure_place(app, (size_t)slot); PureCheckpoint *c = calloc(1, sizeof(*c)); const char *directory = directory_of(app); size_t length = strlen(directory) + 1; if (c && (c->directory = malloc(length))) memcpy(c->directory, directory, length); - if (!c || !c->directory || !(c->snapshot = pure_save_snapshot(app, pick >= PURE_LIST_HEARD ? (uint32_t)pick : UINT32_MAX))) { + if (!c || !c->directory || !(c->snapshot = pure_save_snapshot(app, pick >= PURE_LIST_HEARD ? pick : UINT32_MAX))) { if (c) free(c->directory); free(c); return NULL; } - c->state = (uint32_t)pick; - snprintf(c->name, sizeof(c->name), "%s", stored_name(app, (uint32_t)pick)); + c->state = pick; + snprintf(c->name, sizeof(c->name), "%s", stored_name(app, pick)); c->seq = app->seq; c->header_size = pick == PURE_LIST_OUTBOX ? outbox_header(m, c->seq, c->header) : - pick >= PURE_LIST_HEARD ? runtime_header((uint32_t)pick, c->seq, c->header) : + pick >= PURE_LIST_HEARD ? runtime_header(pick, c->seq, c->header) : image_header(m, m->state_ref[pick], c->seq, c->header); - c->logged = app->logged[pick]; - c->since = app->logged_since[pick]; + c->logged = app->logged[slot]; + c->since = app->logged_since[slot]; app->checkpoint = c; return c; } @@ -712,7 +713,7 @@ PureResult pure_checkpoint_write(PureCheckpoint *c) { void pure_app_checkpointed(PureApp *app, PureCheckpoint *c, PureResult result) { if (!c) return; - uint32_t i = c->state; + size_t i = pure_slot(app, c->state); if (result == PURE_OK && app->checkpoint == c) { /* The image holds every change logged up to its save: those logged * since stay (counted from the save after it). */ @@ -769,7 +770,9 @@ static size_t encode_values(const PureModule *m, const PureValue *values, unsign return at; } -enum { VALUES_BYTES = 4 + PURE_MODULE_STATES * (2 + PURE_MODULE_NAME + 8) }; +/* The values' bytes at most for states of them (and as many for a few on the stack). */ +static size_t values_bytes(uint32_t states) { return 4 + (size_t)states * (2 + PURE_MODULE_NAME + 8); } +enum { FEW_STATES = 64, FEW_VALUES_BYTES = 4 + FEW_STATES * (2 + PURE_MODULE_NAME + 8) }; PURE_COLD PureResult pure_save_open_values(PureApp *app, const PureModule *m, PureValue *values, const int *kept, PureDraft *const *texts, PureList *const *lists) { @@ -1040,12 +1043,14 @@ PURE_COLD PureResult pure_save_open_links(PureApp *app, const PureModule *m, boo * its name and its records. NULL with size 0 when no list changed. */ static unsigned char *list_changes(const PureApp *app, size_t *size) { *size = 0; - for (uint32_t i = 0; i < PURE_LISTS_STORED; ++i) { + for (size_t s = 0; s < pure_slots(app); ++s) { + uint32_t i = pure_place(app, s); const PureList *list = stored_list(app, i); if (list && pure_list_changes(list).size) *size += 1 + strlen(stored_name(app, i)) + 4 + pure_list_changes(list).size; } unsigned char *payload = *size ? malloc(*size) : NULL, *at = payload; - for (uint32_t i = 0; payload && i < PURE_LISTS_STORED; ++i) { + for (size_t s = 0; payload && s < pure_slots(app); ++s) { + uint32_t i = pure_place(app, s); const PureList *list = stored_list(app, i); PureSpan changes = list ? pure_list_changes(list) : (PureSpan){0}; if (!changes.size) continue; @@ -1065,8 +1070,15 @@ PureResult pure_save_commit(PureApp *app, const PureValue *values) { * from what the store has, no save follows it. */ if (app->uncertain) return PURE_CONFLICT; const PureModule *m = app->module; - PureDraft *drafts[2 * PURE_MODULE_STATES + 3]; - uint32_t states[2 * PURE_MODULE_STATES + 3]; + /* A draft a state at most, and three more: on the stack for a few states (027's 4f-2). */ + PureDraft *few_drafts[FEW_STATES + 3], **drafts = few_drafts; + uint32_t few_states[FEW_STATES + 3], *states = few_states; + if (m->states > FEW_STATES && (!(drafts = malloc((m->states + 3) * sizeof(*drafts))) | + !(states = malloc((m->states + 3) * sizeof(*states))))) { + if (drafts != few_drafts) free(drafts); + if (states != few_states) free(states); + return PURE_ALLOCATION; + } size_t count = 0; for (uint32_t i = 0; i < m->states; ++i) { PureDraft *draft = pure_state_transient(m, i) ? NULL : @@ -1081,9 +1093,11 @@ PureResult pure_save_commit(PureApp *app, const PureValue *values) { PureVersion values_before = {0, 0}, links_before = {0, 0}, seq_before = {0, 0}; bool values_changed = false, links_written = false, seq_written = false; PureResult result = PURE_OK; - if (app->stored_values) { - unsigned char encoded[VALUES_BYTES], *stored; - size_t length = encode_values(m, values, encoded), size; + unsigned char few_encoded[FEW_VALUES_BYTES], *encoded = few_encoded; + if (app->stored_values && m->states > FEW_STATES && !(encoded = malloc(values_bytes(m->states)))) result = PURE_ALLOCATION; + if (app->stored_values && result == PURE_OK) { + unsigned char *stored; + size_t length = encode_values(m, values, encoded), size = 0; result = read_whole(app->stored_values, &stored, &size); if (result == PURE_OK) { values_changed = size != length || memcmp(stored, encoded, length); @@ -1149,8 +1163,8 @@ PureResult pure_save_commit(PureApp *app, const PureValue *values) { if (result == PURE_OK) { for (size_t k = 0; k < count; ++k) if (states[k] != UINT32_MAX) app->saved[states[k]] = pure_draft_version(drafts[k]); - for (uint32_t i = 0; i < PURE_LISTS_STORED; ++i) { - PureList *list = stored_list(app, i); + for (size_t i = 0; i < pure_slots(app); ++i) { + PureList *list = stored_list(app, pure_place(app, i)); PureSpan changes = list ? pure_list_changes(list) : (PureSpan){0}; if (!changes.size) continue; app->logged[i] += changes.size; @@ -1171,5 +1185,7 @@ PureResult pure_save_commit(PureApp *app, const PureValue *values) { if (result != PURE_OK) pure_draft_restore(app->stored_seq, seq_before); pure_draft_hold(app->stored_seq, false); } + if (encoded != few_encoded) free(encoded); + if (drafts != few_drafts) free(drafts), free(states); return result; } diff --git a/core/module-save.h b/core/module-save.h index 83ffe60..4ec50a2 100644 --- a/core/module-save.h +++ b/core/module-save.h @@ -95,4 +95,13 @@ bool pure_layout_same(const PureLayout *, const PureLayout *); * them in their stored form, else an event's, holding none yet). */ PureResult pure_list_migrate(PureList **out, const PureList *from, const PureLayout *from_layout, const PureModule *m, uint32_t record, bool kept); +/* A stored list's place (a state's number, PURE_LIST_HEARD or PURE_LIST_OUTBOX) and its slot in an app's arrays of + * them (its states', then the runtime's two), and how many slots (027's 4f-2). */ +static inline size_t pure_slot(const PureApp *app, uint32_t place) { + return place >= PURE_LIST_HEARD ? app->laid + (place - PURE_LIST_HEARD) : place; +} +static inline uint32_t pure_place(const PureApp *app, size_t slot) { + return slot < app->laid ? (uint32_t)slot : PURE_LIST_HEARD + (uint32_t)(slot - app->laid); +} +static inline size_t pure_slots(const PureApp *app) { return (size_t)app->laid + PURE_LIST_RUNTIME; } #endif diff --git a/core/module-store.c b/core/module-store.c index 7ca2dc0..c27c92b 100644 --- a/core/module-store.c +++ b/core/module-store.c @@ -1,17 +1,18 @@ #include "module-store.h" +#include <stdlib.h> static PureResult state(void *context, const char *name, PureDraft **out) { return pure_state_open(context, name, out); } -enum { SAVED = 2 * PURE_MODULE_STATES + 3 }; - -/* The app's changed states in one tree commit. */ +/* The app's changed states in one tree commit (their revisions on the stack for a few). */ static PureResult save(void *context, PureDraft *const *states, size_t count) { (void)context; - int64_t revisions[SAVED]; - if (count > SAVED) return PURE_LIMIT; - return pure_draft_commit_all(states, count, revisions); + int64_t few[67], *revisions = count <= 67 ? few : malloc(count * sizeof(*revisions)); + if (!revisions) return PURE_ALLOCATION; + PureResult result = pure_draft_commit_all(states, count, revisions); + if (revisions != few) free(revisions); + return result; } static const char *path_directory(void *context) { return context; } diff --git a/core/module.c b/core/module.c index f388ae7..3bc98f2 100644 --- a/core/module.c +++ b/core/module.c @@ -486,16 +486,24 @@ static bool same_stack(const Join *join, int height, const unsigned char *types, * into it. Returns why the block is refused, with at moved to the * instruction when one is at fault, or NULL. */ PURE_COLD static const char *verify_block(const PureModule *, PureBlock, int view, int row, int outer, uint32_t event, - bool locals, unsigned nest, uint32_t walked, int fn, uint32_t *at); + bool locals, unsigned nest, uint32_t *walked, unsigned walks, int fn, uint32_t *at); + +/* Whether a walk among walks walks list. */ +static bool walking(const uint32_t *walked, unsigned walks, uint32_t list) { + for (unsigned k = 0; k < walks; ++k) + if (walked[k] == list) return true; + return false; +} /* target marks, per instruction of the block, a branch into it with an empty stack. */ /* outer: the row of the column a fold's block runs in (-1: none). */ /* locals: the block is a handler's, or runs in one (a walk, a fold): it reads the handler's locals. */ -/* nest: the loops (LOOP, EACH) the block runs in; walked: a bit for each list the walks among them walk. */ +/* nest: the loops (LOOP, EACH) the block runs in; walked: the lists the walks among them walk, walks of them (its room + * the module's code: a walk's list goes after them for the walk's block). */ /* fn: the function whose code the block is (its own block, FUNCTION, or a loop's in it), or -1. */ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, int view, int row, int outer, - uint32_t event, bool locals, unsigned nest, uint32_t walked, int fn, uint32_t *at, - unsigned char *target) { + uint32_t event, bool locals, unsigned nest, uint32_t *walked, unsigned walks, int fn, + uint32_t *at, unsigned char *target) { #define REFUSE(why) do { *at = pc; return why; } while (0) bool steps = view == STEPS || view == STEPS_WALK, handler = view < 0 || view == WALK || steps || view == FUNCTION; bool folding = view == FOLD_ONE || view == FOLD_TWO || view == FOLD_I64; @@ -568,9 +576,9 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i /* A keyed list places its items by key, and an item's key stays. */ if (op->op == PURE_OP_LIST_INSERT && m->state_key[arg]) REFUSE("insert by position into a keyed list"); /* A walk ends: no list a walk walks grows while it runs, and only the innermost walk's may shrink (it moves with it). */ - if ((op->op == PURE_OP_LIST_APPEND || op->op == PURE_OP_LIST_INSERT) && walked >> arg & 1) + if ((op->op == PURE_OP_LIST_APPEND || op->op == PURE_OP_LIST_INSERT) && walking(walked, walks, arg)) REFUSE("insert into the list walked"); - if (op->op == PURE_OP_LIST_REMOVE && walked >> arg & 1 && (int)arg != row) + 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"); @@ -617,15 +625,16 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i unsigned how = arg & PURE_FOLD_KIND; uint32_t list = arg >> 8 & 0xFF; /* A fold in a walk reads the walk's item as a fold in a column reads its row; a loop's block is its block's code. */ + if (kind == A_EACH) walked[walks] = list; /* its block's walks: these and its own */ const char *why = kind == A_EACH ? verify_block(m, (PureBlock){pc + 1, skip}, body ? STEPS_WALK : WALK, (int)list, -1, - event, locals, nest + 1, walked | UINT32_C(1) << (list & 31), fn, &inner) + event, locals, nest + 1, walked, walks + 1, fn, &inner) : kind == A_LOOP ? verify_block(m, (PureBlock){pc + 1, skip}, body && !steps ? STEPS : view == FUNCTION ? -1 : view, row, outer, event, locals, nest + 1, - walked, fn, &inner) + walked, walks, fn, &inner) : verify_block(m, (PureBlock){pc + 1, skip}, how == PURE_FOLD_COUNT || how == PURE_FOLD_FIRST ? FOLD_ONE : arg & PURE_FOLD_INTEGER ? FOLD_I64 : FOLD_TWO, - (int)list, row, event, locals, nest, walked, -1, &inner); + (int)list, row, event, locals, nest, walked, walks, -1, &inner); if (why) { *at = inner; return why; } for (uint32_t k = i + 1; k <= i + skip; ++k) { const char *into = kind == A_FOLD ? "branch into a fold" : kind == A_EACH ? "branch into an each" : @@ -688,7 +697,8 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i /* A loop's block begins at the bottom of its run's stack: its operands are all the stack holds. */ if ((kind == A_LOOP || kind == A_EACH) && height != (int)pops) REFUSE("loop above values"); /* What a walk keeps to, a call keeps to as well: a call changes no list a walk around it walks. */ - if (op->op == PURE_OP_CALL && m->function[arg].changes & walked) REFUSE("call that changes a list walked"); + for (unsigned k = 0; op->op == PURE_OP_CALL && k < walks; ++k) + if (pure_states_has(m->function_changes + (size_t)arg * m->state_words, walked[k])) REFUSE("call that changes a list walked"); if (height < (int)pops) REFUSE("stack underflow"); height -= (int)pops; for (unsigned k = 0; k < pops; ++k) @@ -780,12 +790,17 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i } PURE_COLD static const char *verify_block(const PureModule *m, PureBlock block, int view, int row, int outer, - uint32_t event, bool locals, unsigned nest, uint32_t walked, int fn, uint32_t *at) { + uint32_t event, bool locals, unsigned nest, uint32_t *walked, unsigned walks, int fn, + uint32_t *at) { if (block.start > m->code_count || block.length > m->code_count - block.start) return "code range outside module"; unsigned char *target = calloc((size_t)block.length + 1, 1); - if (!target) return "out of memory"; - const char *why = verify_code(m, block, view, row, outer, event, locals, nest, walked, fn, at, target); - free(target); + uint32_t *own = walked ? NULL : malloc(((size_t)m->code_count + 1) * sizeof(*own)); /* a walk's an instruction at least */ + if (!target || (!walked && !own)) { + free(target); + return "out of memory"; + } + const char *why = verify_code(m, block, view, row, outer, event, locals, nest, walked ? walked : own, walks, fn, at, target); + free(target), free(own); return why; } @@ -1498,10 +1513,24 @@ PURE_COLD static void range_code(PureModule *m, PureBlock block, int input) { } } +/* 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) { + const Named *x = a, *y = b; + int order = strcmp(x->name, y->name); + return order ? order : (x->index > y->index) - (x->index < y->index); +} + +/* A state added to a set of them (027's 4f-2). */ +static void states_add(uint64_t *set, uint32_t state) { set[state >> 6] |= UINT64_C(1) << (state & 63); } + /* 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(const PureModule *m, PureFunction *f, uint64_t *calls) { +PURE_COLD static void 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]; 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 */ @@ -1512,7 +1541,7 @@ PURE_COLD static void function_names(const PureModule *m, PureFunction *f, uint6 switch (op->op) { case PURE_OP_LIST_APPEND: case PURE_OP_LIST_INSERT: case PURE_OP_LIST_REMOVE: list = arg; - if (state_of(m, list, PURE_STATE_LIST) && !event_list_state(m, list)) f->changes |= UINT32_C(1) << list; + if (state_of(m, list, PURE_STATE_LIST) && !event_list_state(m, list)) states_add(changes, list); break; case PURE_OP_LIST_COUNT: case PURE_OP_LIST_VALUE: case PURE_OP_LIST_BIND: case PURE_OP_INDEX_FIRST: case PURE_OP_INDEX_LAST: case PURE_OP_INDEX_AT: case PURE_OP_BYTES_OF: case PURE_OP_ROW: case PURE_OP_OUTER_ROW: case PURE_OP_LIST_FIND: @@ -1534,7 +1563,7 @@ PURE_COLD static void function_names(const PureModule *m, PureFunction *f, uint6 case PURE_OP_CALL: if (arg < PURE_MODULE_FUNCTIONS) *calls |= UINT64_C(1) << arg; break; default: break; } - if (event_list_state(m, list)) *(bound_list(m, list) ? &f->registers : &f->lists) |= UINT32_C(1) << list; + if (event_list_state(m, list)) states_add(bound_list(m, list) ? registers : lists, list); } } @@ -1577,15 +1606,18 @@ static size_t lay_tables(PureModule *m, uintptr_t base, size_t views, size_t sig (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(state_transient, m->states), LAY(state_counted, 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(view_arguments, views), LAY(view_body, views), LAY(view_lists, views * m->state_words), 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); + LAY(function_lists, (size_t)m->functions * m->state_words), LAY(function_registers, (size_t)m->functions * m->state_words); + LAY(function_changes, (size_t)m->functions * m->state_words); #undef LAY return at; } @@ -1605,6 +1637,7 @@ static bool make_tables(PureModule *m, size_t views, size_t signatures) { 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) + GAP(state_transient) GAP(state_counted) GAP(function_lists) GAP(function_registers) GAP(function_changes) #undef GAP ASAN_POISON_MEMORY_REGION(next - TABLE_GAP, TABLE_GAP); at = (size_t)(next - base); @@ -1676,7 +1709,8 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule uint32_t constant_bytes = read32(p + 30); out->record_fields = (uint16_t)read16(p + 34); out->functions = (uint16_t)read16(p + 36); - if (out->states > PURE_MODULE_STATES || out->inputs > PURE_MODULE_INPUTS || + out->state_words = (uint16_t)((out->states + 63) / 64 + !out->states); + if (out->inputs > PURE_MODULE_INPUTS || out->views > PURE_MODULE_VIEWS || out->handlers > PURE_MODULE_HANDLERS || out->code_count > PURE_MODULE_CODE || strings > PURE_MODULE_STRINGS || out->fields > PURE_MODULE_FIELDS || out->constants > PURE_MODULE_CONSTANTS || constant_bytes > PURE_MODULE_CONSTANT_BYTES || @@ -1746,7 +1780,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule uint32_t type = read32(at + 4); if ((type & ~(uint32_t)PURE_STATE_TRANSIENT) > PURE_MODULE_I64) REFUSE("state type", i); out->state_type[i] = held_as[type & 0xFF]; - if (type & PURE_STATE_TRANSIENT) out->transient |= UINT32_C(1) << i; + out->state_transient[i] = type & PURE_STATE_TRANSIENT; /* A number's, a bool's or an i64's value is its 8 bytes; others have flags: * a mark (cursor), a keyed list's key field + 1, or a list of a message's rows. */ bool value = out->state_type[i] == PURE_STATE_NUM || out->state_type[i] == PURE_STATE_BOOL || @@ -1757,6 +1791,8 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule bool bound = list && flags & PURE_LIST_BOUND; /* a register: an event's list, not keyed */ uint32_t key = list ? flags & ~(uint32_t)(PURE_LIST_ROWS | PURE_LIST_BOUND) : 0; if (bound && (!rows || key)) REFUSE("state type", i); + /* What the states hold counts a text and a list, a message's rows aside (they are its own, gone when it ends). */ + out->state_counted[i] = out->state_type[i] == PURE_STATE_TEXT || (list && !rows); if (rows && type & PURE_STATE_TRANSIENT) REFUSE("state type", i); /* an event's own, saved or not */ /* A list of a message's rows is named by the field's path (input.field), no other state. */ if (!name_at(text, strings, read32(at), rows ? NAME_PATH : NAME_IDENTIFIER, out->state_names[i]) || @@ -1917,7 +1953,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule /* A stored position into what is not stored would come back meaning nothing. */ for (uint32_t i = 0; i < out->states; ++i) if ((out->state_type[i] == PURE_STATE_CURSOR || out->state_type[i] == PURE_STATE_INDEX) && - !(out->transient >> i & 1) && out->transient >> out->state_ref[i] & 1) + !out->state_transient[i] && out->state_transient[out->state_ref[i]]) REFUSE("stored position in a transient state", i); /* Views (027's 4e): a list view names its list state (+ 1; its block a filter) or none (its block leaves its list), * and its items' record (+ 1; a map of a list state 0, its columns following it, each naming it + 1). */ @@ -2001,16 +2037,23 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule if (at[0] >= PURE_OP_COUNT) REFUSE("unknown opcode", i); out->code[i] = (PureOp){.op = anchor_step(out, at[0], read32(at + 4)), .arg = read32(at + 4), .value = read_double(at + 8)}; } - /* States, records, inputs and views share one namespace. */ - const char *names[PURE_MODULE_STATES + PURE_MODULE_RECORDS + PURE_MODULE_INPUTS + PURE_MODULE_VIEWS]; - size_t count = 0; - for (uint32_t i = 0; i < out->states; ++i) names[count++] = out->state_names[i]; - for (uint32_t i = 0; i < out->records; ++i) names[count++] = out->record_names[i]; - for (uint32_t i = 0; i < out->inputs; ++i) names[count++] = out->input_names[i]; - for (uint32_t i = 0; i < out->views; ++i) if (!out->view_table[i]) names[count++] = out->view_names[i]; - for (size_t i = 0; i < count; ++i) - for (size_t k = 0; k < i; ++k) - if (!strcmp(names[i], names[k])) REFUSE("duplicate name", i); + /* States, records, inputs and views share one namespace: in order of name, the first name again (in their order) is + * refused. */ + Named *names = malloc(((size_t)out->states + out->records + out->inputs + out->views + 1) * sizeof(*names)); + if (!names) { + result = PURE_ALLOCATION; + REFUSE("out of memory", 0); + } + size_t count = 0, again = SIZE_MAX; + for (uint32_t i = 0; i < out->states; ++i) names[count] = (Named){out->state_names[i], count}, ++count; + for (uint32_t i = 0; i < out->records; ++i) names[count] = (Named){out->record_names[i], count}, ++count; + for (uint32_t i = 0; i < out->inputs; ++i) names[count] = (Named){out->input_names[i], count}, ++count; + for (uint32_t i = 0; i < out->views; ++i) if (!out->view_table[i]) names[count] = (Named){out->view_names[i], count}, ++count; + qsort(names, count, sizeof(*names), by_name); + for (size_t i = 1; i < count; ++i) + if (!strcmp(names[i].name, names[i - 1].name) && names[i].index < again) again = names[i].index; + free(names); + if (again != SIZE_MAX) REFUSE("duplicate name", again); /* Column names are unique within their table. */ for (uint32_t i = 0; i < out->views; ++i) for (uint32_t k = 0; k < i; ++k) @@ -2024,7 +2067,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule int kind = table ? FILTER : out->view_type[i] == PURE_VIEW_LIST ? LIST_OF + out->view_record[i] - 1 : out->view_type[i]; if (!out->view_field[i] && (!table || out->view_code[i].length) && (why = verify_block(out, out->view_code[i], kind, table || out->view_table[i] ? out->view_list[i] : -1, -1, event, - computes, 0, 0, -1, &bad))) + computes, 0, NULL, 0, -1, &bad))) goto refused; if (computes && !locals_agree(out, out->view_code[i])) { why = "local used as two things"; goto refused; } for (uint32_t pc = out->view_code[i].start; computes && pc < out->view_code[i].start + out->view_code[i].length; ++pc) { @@ -2032,34 +2075,36 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule if (op->op == PURE_OP_LOCAL_LOAD || op->op == PURE_OP_LOCAL_STORE || op->op == PURE_OP_LOOP || op->op == PURE_OP_EACH) out->view_body[i] = true; if (op->op == PURE_OP_LIST_APPEND || op->op == PURE_OP_LIST_INSERT || op->op == PURE_OP_LIST_REMOVE) - out->view_lists[i] |= UINT32_C(1) << (op->arg & 31); - if (op->op == PURE_OP_ITEM_SET) out->view_lists[i] |= UINT32_C(1) << (list_of(op->arg) & 31); + states_add(out->view_lists + (size_t)i * out->state_words, op->arg); + if (op->op == PURE_OP_ITEM_SET) states_add(out->view_lists + (size_t)i * out->state_words, list_of(op->arg)); } - if (out->view_lists[i]) out->view_body[i] = true; + for (uint32_t w = 0; w < out->state_words; ++w) + if (out->view_lists[(size_t)i * out->state_words + w]) out->view_body[i] = true; } /* 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, &out->function[i], &calls[i]); + for (uint32_t i = 0; i < out->functions; ++i) function_names(out, i, &calls[i]); for (bool more = out->functions > 0; more;) { more = false; for (uint32_t i = 0; i < out->functions; ++i) - for (uint64_t left = calls[i]; left; left &= left - 1) { - uint32_t was = out->function[i].changes; - out->function[i].changes |= out->function[__builtin_ctzll(left)].changes; - more = more || out->function[i].changes != was; - } + for (uint64_t left = calls[i]; left; left &= left - 1) + for (uint32_t w = 0; w < out->state_words; ++w) { + uint64_t *mine = &out->function_changes[(size_t)i * out->state_words + w], was = *mine; + *mine |= out->function_changes[(size_t)__builtin_ctzll(left) * out->state_words + w]; + more = more || *mine != was; + } } for (uint32_t i = 0; i < out->functions; ++i) { bad = i; PureFunction *f = &out->function[i]; - if ((why = verify_block(out, f->code, FUNCTION, -1, -1, 0, true, 0, 0, (int)i, &bad))) goto refused; + if ((why = verify_block(out, f->code, FUNCTION, -1, -1, 0, true, 0, NULL, 0, (int)i, &bad))) goto refused; if (!locals_agree(out, f->code)) { why = "local used as two things"; goto refused; } for (uint32_t pc = f->code.start; pc < f->code.start + f->code.length; ++pc) /* its frame's stack: as the verifier went */ if (out->height[pc] >= f->height) f->height = (uint8_t)(out->height[pc] + 1); } for (uint32_t i = 0; i < out->handlers; ++i) { bad = i; - if ((why = verify_block(out, out->handler[i].code, -1, -1, -1, out->handler[i].event + 1, true, 0, 0, -1, &bad))) + if ((why = verify_block(out, out->handler[i].code, -1, -1, -1, out->handler[i].event + 1, true, 0, NULL, 0, -1, &bad))) goto refused; if (!locals_agree(out, out->handler[i].code)) { why = "local used as two things"; goto refused; } } @@ -2591,7 +2636,7 @@ static uint64_t nest(PureApp *app, uint32_t record, const PureList *from) { /* The bound lists given back their own, and the event's lists in records let go (an event or a view begins). */ static void unbind(PureApp *app) { - for (uint32_t i = 0; i < PURE_MODULE_STATES; ++i) + for (uint32_t i = 0; i < app->laid; ++i) /* (a start that failed laid none, or none for a module) */ if (app->unbound[i]) app->lists[i] = app->unbound[i], app->unbound[i] = NULL; } static void drop_nested(PureApp *app) { @@ -4619,32 +4664,55 @@ static void let_list_go(PureApp *app, uint32_t state, PureList *list) { } static void drop_spares(PureApp *app) { - for (unsigned i = 0; i < PURE_MODULE_STATES; ++i) { + for (unsigned i = 0; i < app->laid; ++i) { for (unsigned k = 0; k < app->spare[i].count; ++k) pure_list_free(app->spare[i].lists[k]); free(app->spare[i].lists); app->spare[i].lists = NULL, app->spare[i].count = app->spare[i].capacity = 0; } } -/* The first count of the lists and registers a function's code names given back what a call kept of them (its own - * lists let go). */ -static void restore_lists(PureApp *app, const PureFunction *f, PureList **kept, unsigned count) { +/* The next state from on in the union of two sets of them (words each; b may be NULL), or UINT32_MAX. */ +static uint32_t states_next(const uint64_t *a, const uint64_t *b, unsigned words, uint32_t from) { + for (uint32_t w = from >> 6; w < words; ++w) { + uint64_t bits = (a[w] | (b ? b[w] : 0)) & (w == from >> 6 ? ~UINT64_C(0) << (from & 63) : ~UINT64_C(0)); + if (bits) return w * 64 + (uint32_t)__builtin_ctzll(bits); + } + return UINT32_MAX; +} +static unsigned states_count(const uint64_t *set, unsigned words) { + unsigned count = 0; + for (unsigned w = 0; w < words; ++w) count += (unsigned)__builtin_popcountll(set[w]); + return count; +} + +/* The first count of the lists and registers function's code names given back what a call kept of them (its own lists + * let go). */ +static void restore_lists(PureApp *app, uint32_t function, PureList **kept, unsigned count) { + const PureModule *m = app->module; + const uint64_t *lists = m->function_lists + (size_t)function * m->state_words, + *registers = m->function_registers + (size_t)function * m->state_words; unsigned k = 0; - for (uint32_t left = f->lists | f->registers; left && k < count; left &= left - 1, ++k) { - uint32_t state = (uint32_t)__builtin_ctz(left); - if (!(f->registers >> state & 1)) let_list_go(app, state, app->lists[state]); + for (uint32_t state = states_next(lists, registers, m->state_words, 0); state != UINT32_MAX && k < count; + state = states_next(lists, registers, m->state_words, state + 1), ++k) { + if (!pure_states_has(registers, state)) let_list_go(app, state, app->lists[state]); app->lists[state] = kept[k]; } } +/* What a call of function keeps of its caller's: the lists and registers its code names (its own). */ +static unsigned call_keeps(const PureModule *m, uint32_t function) { + return states_count(m->function_lists + (size_t)function * m->state_words, m->state_words) + + states_count(m->function_registers + (size_t)function * m->state_words, m->state_words); +} + /* A frame closed: the caller's lists, registers and held values given back (the callee's let go), its locals and walk, * the frame's room in the blocks and in the event's room. The frame below. */ static Call *give_back(PureApp *app, Call *call, Event *ev) { const PureFunction *f = &app->module->function[call->function]; - unsigned keeps = (unsigned)__builtin_popcount(f->lists | f->registers), j = 0; + unsigned keeps = call_keeps(app->module, call->function), j = 0; PureList **kept = (PureList **)(call + 1); Kept *held = (Kept *)(kept + keeps); - restore_lists(app, f, kept, keeps); + restore_lists(app, call->function, kept, keeps); for (uint32_t left = f->held; left; left &= left - 1, ++j) { Kept *h = &app->held[__builtin_ctz(left)]; if (!h->by_ref) (void)grows(app, (Held){0, h->size}, (Held){0, 0}); @@ -4675,12 +4743,14 @@ static __attribute__((noinline, cold)) int enter_call(Frame *frame, Call **top, Event *ev = frame->ev; uint32_t function = m->code[*pc - 1].arg; const PureFunction *f = &m->function[function]; - unsigned keeps = (unsigned)__builtin_popcount(f->lists | f->registers), holds = (unsigned)__builtin_popcount(f->held); + const uint64_t *lists = m->function_lists + (size_t)function * m->state_words, + *registers = m->function_registers + (size_t)function * m->state_words; + unsigned keeps = call_keeps(m, function), holds = (unsigned)__builtin_popcount(f->held); size_t bytes = (sizeof(Call) + keeps * sizeof(PureList *) + holds * sizeof(Kept) + f->height * sizeof(Slot) + f->locals * sizeof(PureValue) + f->nesting * sizeof(Loop) + 15) & ~(size_t)15; size_t counted = PURE_CALL_BYTES + (size_t)f->height * PURE_CALL_VALUE + (size_t)f->locals * PURE_CALL_LOCAL + - (size_t)f->nesting * PURE_CALL_LOOP + (size_t)__builtin_popcount(f->lists) * PURE_CALL_LIST + - (size_t)holds * PURE_CALL_HELD + (size_t)__builtin_popcount(f->registers) * PURE_CALL_REGISTER; + (size_t)f->nesting * PURE_CALL_LOOP + (size_t)states_count(lists, m->state_words) * PURE_CALL_LIST + + (size_t)holds * PURE_CALL_HELD + (size_t)states_count(registers, m->state_words) * PURE_CALL_REGISTER; frame->fault_pc = *pc - 1; if (!charge(app, f->code.length)) { frame->kind = PURE_FAULT_WORK; return STEP_FAULT; } if (!grows(app, (Held){0, 0}, (Held){0, counted})) { frame->kind = PURE_FAULT_MEMORY; return STEP_FAULT; } @@ -4698,12 +4768,12 @@ static __attribute__((noinline, cold)) int enter_call(Frame *frame, Call **top, stack[i] = frame->stack[base + i]; whole = kept_apart(app, frame->arena, f, m->signature_type[f->first + i], &stack[i].bytes); } - for (uint32_t left = f->lists | f->registers; whole && left; left &= left - 1, ++k) { /* its own lists, registers */ - uint32_t state = (uint32_t)__builtin_ctz(left); - PureList *own = f->registers >> state & 1 ? (app->unbound[state] ? app->unbound[state] : app->lists[state]) - : spare_list(app, state); + for (uint32_t state = states_next(lists, registers, m->state_words, 0); whole && state != UINT32_MAX; + state = states_next(lists, registers, m->state_words, state + 1), ++k) { /* its own lists, registers */ + PureList *own = pure_states_has(registers, state) ? (app->unbound[state] ? app->unbound[state] : app->lists[state]) + : spare_list(app, state); if (!own) { - restore_lists(app, f, kept, k); + restore_lists(app, function, kept, k); whole = false; break; } @@ -4897,7 +4967,7 @@ void pure_app_finish(PureApp *app) { static void open_burst(PureApp *app) { if (app->open) return; const PureModule *m = app->module; - memcpy(app->durable_values, app->values, sizeof(app->values)); + memcpy(app->durable_values, app->values, m->states * sizeof(*app->values)); app->durable_events = app->events; for (uint32_t i = 0; i < m->states; ++i) { if (app->texts[i]) { @@ -4928,7 +4998,7 @@ static void close_burst(PureApp *app, bool keep) { if (keep) pure_undo_commit(&app->undo); else { pure_undo_rollback(&app->undo, 0); - memcpy(app->values, app->durable_values, sizeof(app->values)); + memcpy(app->values, app->durable_values, m->states * sizeof(*app->values)); app->events = app->durable_events; ++app->changes; } @@ -4989,10 +5059,11 @@ struct PureSnapshot { unsigned refs; const PureModule *module; uint64_t changes, epoch; - PureValue values[PURE_MODULE_STATES]; - PureDraft *texts[PURE_MODULE_STATES]; - PureList *lists[PURE_MODULE_STATES]; - PureList *runtime[PURE_LISTS_STORED - PURE_LIST_HEARD]; /* only in a checkpoint's of one */ + uint32_t states; + PureValue *values; /* each state's (027's 4f-2), after it in its block */ + PureDraft **texts; + PureList **lists; + PureList *runtime[PURE_LIST_RUNTIME]; /* only in a checkpoint's of one */ PureRetired retired; struct PureSnapshot *older, *newer; /* the held ones beside it */ Register *all; @@ -5021,11 +5092,11 @@ void pure_snapshot_release(PureSnapshot *snapshot) { unlock_register(all); } pure_retired_free(&snapshot->retired); /* what it had when never linked, else its room */ - for (uint32_t i = 0; i < PURE_MODULE_STATES; ++i) { + for (uint32_t i = 0; i < snapshot->states; ++i) { pure_draft_close(snapshot->texts[i]); pure_list_free(snapshot->lists[i]); } - for (unsigned r = 0; r < PURE_LISTS_STORED - PURE_LIST_HEARD; ++r) pure_list_free(snapshot->runtime[r]); + for (unsigned r = 0; r < PURE_LIST_RUNTIME; ++r) pure_list_free(snapshot->runtime[r]); free(snapshot); if (all && !__atomic_sub_fetch(&all->refs, 1, __ATOMIC_ACQ_REL)) { pure_retired_free(&all->returned); /* the app is gone: no list takes it back */ @@ -5035,12 +5106,38 @@ void pure_snapshot_release(PureSnapshot *snapshot) { static void let_own_go(PureApp *app); +/* The arrays of each state an app holds (027's 4f-2) laid out from base for states of them: the bytes they take (a + * stored list's slots then the runtime's two). */ +static size_t lay_states(PureApp *app, uint32_t states, uintptr_t base) { + size_t at = 0, slots = (size_t)states + PURE_LIST_RUNTIME; +#define LAY(member, count) (app->member = (void *)(base + at), at += ((size_t)(count) * sizeof(*app->member) + 7) & ~(size_t)7) + LAY(values, states), LAY(texts, states), LAY(lists, states), LAY(list_drafts, states); + LAY(image_seq, slots), LAY(logged_since, slots), LAY(logged, slots); + LAY(saved, states), LAY(durable_values, states), LAY(durable_texts, states), LAY(durable_lists, states); + LAY(own, states), LAY(unbound, states), LAY(spare, states), LAY(pending, states); + LAY(marked_texts, states), LAY(marked_lists, states); +#undef LAY + return at; +} + +/* A new block of them for states, zeroed, laid out in app (its old one the caller's to give back first, or after it + * moved what it keeps); false without room (app as it was). */ +static bool lay_app(PureApp *app, uint32_t states) { + PureApp laid = *app; + size_t size = lay_states(&laid, states, 0); + unsigned char *base = calloc(1, size ? size : 1); + if (!base) return false; + lay_states(app, states, (uintptr_t)base); + app->states = base, app->laid = states; + return true; +} + void pure_app_stop(PureApp *app) { unbind(app); /* a register's own list goes with the lists, the bound one with the event's */ pure_app_finish(app); close_burst(app, true); /* unsaved changes are dropped with the app */ let_own_go(app); - for (uint32_t i = 0; i < PURE_MODULE_STATES && !app->reading; ++i) { + for (uint32_t i = 0; i < app->laid && !app->reading; ++i) { pure_draft_close(app->texts[i]); pure_list_free(app->lists[i]); pure_draft_close(app->list_drafts[i]); @@ -5074,13 +5171,13 @@ void pure_app_stop(PureApp *app) { drop_passes(app); free(app->arena.bytes); free(app->view_arena.bytes); + free(app->states); memset(app, 0, sizeof(*app)); } /* Where one event began inside the burst: a failed event returns there. */ -typedef struct { - PureVersion texts[PURE_MODULE_STATES]; - size_t lists[PURE_MODULE_STATES], heard, outbox; /* stored lists' journal lengths */ +typedef struct { /* (each state's text version and list journal length in the app's marked_texts, marked_lists) */ + size_t heard, outbox; /* stored lists' journal lengths */ size_t undo, sent_saved; uint64_t last_sent[PURE_MODULE_INPUTS]; bool opened; @@ -5090,8 +5187,8 @@ static void mark_event(PureApp *app, Mark *mark) { mark->opened = !app->open; open_burst(app); for (uint32_t i = 0; i < app->module->states; ++i) { - if (app->texts[i]) mark->texts[i] = pure_draft_version(app->texts[i]); - if (app->lists[i]) mark->lists[i] = pure_list_changes(app->lists[i]).size; + if (app->texts[i]) app->marked_texts[i] = pure_draft_version(app->texts[i]); + if (app->lists[i]) app->marked_lists[i] = pure_list_changes(app->lists[i]).size; } mark->heard = app->heard ? pure_list_changes(app->heard).size : 0; mark->outbox = app->outbox ? pure_list_changes(app->outbox).size : 0; @@ -5102,8 +5199,8 @@ static void mark_event(PureApp *app, Mark *mark) { static void undo_event(PureApp *app, const Mark *mark) { for (uint32_t i = 0; i < app->module->states; ++i) { - if (app->texts[i]) pure_draft_restore(app->texts[i], mark->texts[i]); - if (app->lists[i]) pure_list_forget(app->lists[i], mark->lists[i]); + if (app->texts[i]) pure_draft_restore(app->texts[i], app->marked_texts[i]); + if (app->lists[i]) pure_list_forget(app->lists[i], app->marked_lists[i]); } if (app->heard) pure_list_forget(app->heard, mark->heard); if (app->outbox) pure_list_forget(app->outbox, mark->outbox); @@ -5174,10 +5271,9 @@ PureFault pure_app_view_fault(const PureApp *app) { return app->view_fault; } size_t pure_app_state_bytes(const PureApp *app) { size_t bytes = (app->outbox ? pure_list_size(app->outbox) : 0) + (app->heard ? pure_list_size(app->heard) : 0); - for (uint32_t left = app->counted; left; left &= left - 1) { - unsigned i = (unsigned)__builtin_ctz(left); - bytes += app->lists[i] ? pure_list_size(app->lists[i]) : app->texts[i] ? pure_draft_size(app->texts[i]) : 0; - } + for (uint32_t i = 0; i < app->module->states; ++i) + if (app->module->state_counted[i]) + bytes += app->lists[i] ? pure_list_size(app->lists[i]) : app->texts[i] ? pure_draft_size(app->texts[i]) : 0; return bytes; } @@ -5188,7 +5284,7 @@ size_t pure_app_state_bytes(const PureApp *app) { * holding more than before, fails. */ static PureResult dispatch(PureApp *app, uint32_t event, Event *ev) { const PureModule *m = app->module; - PureValue pending[PURE_MODULE_STATES], locals[PURE_MODULE_LOCALS] = {{0}}; + PureValue *pending = app->pending, locals[PURE_MODULE_LOCALS] = {{0}}; ev->locals = locals; size_t held = app->memory ? pure_app_state_bytes(app) : 0; /* What the event may add as it runs, whatever it gives back later: the @@ -5200,7 +5296,7 @@ static PureResult dispatch(PureApp *app, uint32_t event, Event *ev) { if (app->nested.count && !ev->nested) unbind(app), drop_nested(app); /* a view's body bound some on the app itself */ Mark mark; mark_event(app, &mark); - memcpy(pending, app->values, sizeof(app->values)); + memcpy(pending, app->values, m->states * sizeof(*pending)); app->fault = PURE_FAULT_NONE; app->fault_pc = UINT32_MAX; app->work = 0; @@ -5229,7 +5325,7 @@ static PureResult dispatch(PureApp *app, uint32_t event, Event *ev) { * last changed is packed now (docs/design/022). */ for (uint32_t i = 0; i < m->states; ++i) if (app->lists[i]) (void)pure_list_settle(app->lists[i]); - memcpy(app->values, pending, sizeof(pending)); + memcpy(app->values, pending, m->states * sizeof(*pending)); ++app->events; ++app->changes; app->delivering.pending = false; @@ -5461,8 +5557,8 @@ static bool own_lists(PureApp *app, uint32_t lists) { static PureResult evaluate(PureApp *app, uint32_t view, size_t row, Slot *out) { const PureModule *m = app->module; - PureValue values[PURE_MODULE_STATES], locals[PURE_MODULE_LOCALS]; - memcpy(values, app->values, sizeof(values)); + /* The states as they are: a view's code changes none (the verifier holds it to that). */ + PureValue *values = app->values, locals[PURE_MODULE_LOCALS]; app->view_arena.used = 0; Event ev = {.app = app, .arena = &app->view_arena, .row = row}; if (!argue(app, view, &ev)) return PURE_INVALID; @@ -5782,14 +5878,16 @@ static void reuse_returned(PureApp *app, Register *all) { PureRetired returned = all->returned; all->returned = (PureRetired){0}; unlock_register(all); - if (returned.count) { - PureList *lists[PURE_MODULE_STATES + 2]; + PureList *few[64], **lists = !returned.count ? NULL : app->module->states + 2 <= 64 ? few : + malloc((app->module->states + 2) * sizeof(*lists)); + if (lists) { unsigned count = 0; for (uint32_t i = 0; i < app->module->states; ++i) if (app->lists[i]) lists[count++] = app->lists[i]; lists[count++] = app->outbox; lists[count++] = app->heard; pure_retired_reuse(&returned, lists, count); + if (lists != few) free(lists); } lock_register(all); bool kept = !all->returned.entries; /* none came back meanwhile: the room stays for the next */ @@ -5801,7 +5899,8 @@ static void reuse_returned(PureApp *app, Register *all) { PureSnapshot *pure_save_snapshot(PureApp *app, uint32_t runtime) { if (app->open || app->reading) return NULL; const PureModule *m = app->module; - PureSnapshot *made = calloc(1, sizeof(*made)), *last = app->snapshot; + size_t each = sizeof(PureValue) + sizeof(PureDraft *) + sizeof(PureList *); + PureSnapshot *made = calloc(1, sizeof(*made) + m->states * each), *last = app->snapshot; Register *all = last ? last->all : calloc(1, sizeof(*all)); if (!made || !all) { free(made); @@ -5810,9 +5909,13 @@ PureSnapshot *pure_save_snapshot(PureApp *app, uint32_t runtime) { } made->refs = 1; made->module = m; + made->states = m->states; + made->values = (PureValue *)(made + 1); + made->texts = (PureDraft **)(made->values + m->states); + made->lists = (PureList **)(made->texts + m->states); made->changes = app->changes; made->epoch = all->epoch + 1; /* the app's thread alone makes them */ - memcpy(made->values, app->values, sizeof(made->values)); + memcpy(made->values, app->values, m->states * sizeof(*made->values)); bool ok = true; for (uint32_t i = 0; i < m->states && ok; ++i) { /* A stored text is shared as its store holds it; a transient one, never stored, is copied. */ @@ -5820,14 +5923,14 @@ PureSnapshot *pure_save_snapshot(PureApp *app, uint32_t runtime) { ok = (made->texts[i] = pure_state_transient(m, i) ? text_copy(app->texts[i]) : pure_draft_freeze(app->texts[i])) != NULL; if (!ok || !app->lists[i]) continue; - if (last && last->lists[i] && pure_list_current(last->lists[i], app->lists[i])) { + if (last && i < last->states && last->lists[i] && pure_list_current(last->lists[i], app->lists[i])) { pure_list_hold(last->lists[i]); made->lists[i] = last->lists[i]; pure_list_mark(app->lists[i], made->epoch); } else ok = (made->lists[i] = pure_list_share(app->lists[i], &app->retired, made->epoch)) != NULL; } - if (ok && runtime >= PURE_LIST_HEARD && runtime < PURE_LISTS_STORED) { + if (ok && runtime >= PURE_LIST_HEARD && runtime < PURE_LIST_HEARD + PURE_LIST_RUNTIME) { PureList *list = runtime == PURE_LIST_HEARD ? app->heard : app->outbox; ok = (made->runtime[runtime - PURE_LIST_HEARD] = pure_list_share(list, &app->retired, made->epoch)) != NULL; } @@ -5856,20 +5959,26 @@ PureSnapshot *pure_save_snapshot(PureApp *app, uint32_t runtime) { /* A reader's own lists go: its next views compute in new ones. */ static void let_own_go(PureApp *app) { - for (uint32_t i = 0; i < PURE_MODULE_STATES; ++i) { + for (uint32_t i = 0; i < app->laid; ++i) { pure_list_free(app->own[i]); app->own[i] = app->unbound[i] = NULL; } } -void pure_app_read(PureApp *reader, const PureSnapshot *snapshot) { +bool pure_app_read(PureApp *reader, const PureSnapshot *snapshot) { if (reader->module != snapshot->module) drop_passes(reader), let_own_go(reader); /* laid out for its module */ + if (!reader->states || reader->laid != snapshot->states) { /* its arrays for as many states (027's 4f-2) */ + void *was = reader->states; + if (!lay_app(reader, snapshot->states)) return false; + free(was); + } reader->module = snapshot->module; reader->reading = true; reader->changes = snapshot->changes; /* what the row cache is fresh for */ - memcpy(reader->values, snapshot->values, sizeof(reader->values)); - memcpy(reader->texts, snapshot->texts, sizeof(reader->texts)); - memcpy(reader->lists, snapshot->lists, sizeof(reader->lists)); + memcpy(reader->values, snapshot->values, snapshot->states * sizeof(*reader->values)); + memcpy(reader->texts, snapshot->texts, snapshot->states * sizeof(*reader->texts)); + memcpy(reader->lists, snapshot->lists, snapshot->states * sizeof(*reader->lists)); + return true; } uint8_t pure_record_kind(const PureModule *m, uint32_t field) { @@ -6171,20 +6280,41 @@ PURE_COLD static PureResult remap_outputs(PureApp *app, const PureModule *old, c return PURE_OK; } +/* The arrays of each state next laid out given to app, its old ones given back (027's 4f-2). */ +static void take_states(PureApp *app, const PureApp *next) { + free(app->states); + app->states = next->states, app->laid = next->laid; + app->values = next->values, app->texts = next->texts, app->lists = next->lists, app->list_drafts = next->list_drafts; + app->image_seq = next->image_seq, app->logged_since = next->logged_since, app->logged = next->logged; + app->saved = next->saved, app->durable_values = next->durable_values, app->durable_texts = next->durable_texts; + app->durable_lists = next->durable_lists, app->own = next->own, app->unbound = next->unbound, app->spare = next->spare; + app->pending = next->pending, app->marked_texts = next->marked_texts; + app->marked_lists = next->marked_lists; +} + PURE_COLD static PureResult open_module(PureApp *app, const PureModule *module) { const PureModule *old = app->module; bool stored = pure_save_enabled(app); - PureValue values[PURE_MODULE_STATES] = {0}; - PureDraft *texts[PURE_MODULE_STATES] = {0}, *list_drafts[PURE_MODULE_STATES] = {0}; - PureList *lists[PURE_MODULE_STATES] = {0}; - PureVersion saved[PURE_MODULE_STATES] = {{0, 0}}; - PureSaved listed[PURE_MODULE_STATES] = {{0}}; - int kept[PURE_MODULE_STATES]; - bool rewrite[PURE_MODULE_STATES] = {false}; - /* Lists are stored where the host names a directory for them (module-save.c). */ - bool lists_stored = stored && app->data.directory && app->data.directory(app->data.context); app->why[0] = 0; if (old && app->checkpoint) return PURE_CONFLICT; /* the host has a checkpoint of the running module out */ + /* The new arrays of each state, laid out apart (next) and given to app once nothing can fail; what is worked out + * for each state. */ + PureApp next = {0}; + uint32_t states = module->states; + int *kept = malloc((states + 1) * sizeof(*kept)); + PureSaved *listed = calloc(states + 1, sizeof(*listed)); + bool *rewrite = calloc(states + 1, sizeof(*rewrite)); + size_t **moved = calloc(states + 1, sizeof(*moved)); + if (!kept || !listed || !rewrite || !moved || !lay_app(&next, states)) { + free(kept), free(listed), free(rewrite), free(moved), free(next.states); + return PURE_ALLOCATION; + } + PureValue *values = next.values; + PureDraft **texts = next.texts, **list_drafts = next.list_drafts; + PureList **lists = next.lists; + PureVersion *saved = next.saved; + /* Lists are stored where the host names a directory for them (module-save.c). */ + bool lists_stored = stored && app->data.directory && app->data.directory(app->data.context); PureResult result = !old && lists_stored ? pure_save_open_log(app) : PURE_OK; /* The numbers heard and the outbox belong to the app, whatever its module. */ PureSaved heard = {0}, sent = {0}; @@ -6236,7 +6366,6 @@ PURE_COLD static PureResult open_module(PureApp *app, const PureModule *module) /* A keyed list keeps its items in key order: one that was not keyed, or * keyed by another field, is put in order and its indices follow their * items. Two items with one key refuse the module: nothing is dropped. */ - size_t *moved[PURE_MODULE_STATES] = {0}; for (uint32_t i = 0; i < module->states && result == PURE_OK; ++i) { if (module->state_type[i] != PURE_STATE_LIST || !module->state_key[i]) continue; size_t count = pure_list_count(lists[i]); @@ -6316,6 +6445,7 @@ PURE_COLD static PureResult open_module(PureApp *app, const PureModule *module) pure_list_free(lists[i]); pure_draft_close(list_drafts[i]); } + free(kept), free(listed), free(rewrite), free(moved), free(next.states); return result; } for (uint32_t i = 0; old && i < old->states; ++i) { @@ -6330,25 +6460,23 @@ PURE_COLD static PureResult open_module(PureApp *app, const PureModule *module) } if (!draft_reused) pure_draft_close(app->list_drafts[i]); } - memcpy(app->values, values, sizeof(values)); - memcpy(app->texts, texts, sizeof(texts)); - memcpy(app->lists, lists, sizeof(lists)); - memcpy(app->list_drafts, list_drafts, sizeof(list_drafts)); - memcpy(app->saved, saved, sizeof(saved)); - for (uint32_t i = 0; i < PURE_MODULE_STATES; ++i) { - app->image_seq[i] = listed[i].image; - app->logged_since[i] = listed[i].since; - app->logged[i] = listed[i].logged; + for (uint32_t i = 0; i < states; ++i) { + next.image_seq[i] = listed[i].image; + next.logged_since[i] = listed[i].since; + next.logged[i] = listed[i].logged; } if (!old) { - app->image_seq[PURE_LIST_HEARD] = heard.image; - app->logged_since[PURE_LIST_HEARD] = heard.since; - app->logged[PURE_LIST_HEARD] = heard.logged; - app->image_seq[PURE_LIST_OUTBOX] = sent.image; - app->logged_since[PURE_LIST_OUTBOX] = sent.since; - app->logged[PURE_LIST_OUTBOX] = sent.logged; + next.image_seq[states] = heard.image, next.logged_since[states] = heard.since, next.logged[states] = heard.logged; + next.image_seq[states + 1] = sent.image, next.logged_since[states + 1] = sent.since, next.logged[states + 1] = sent.logged; if (lists_stored) pure_list_journal(app->heard, true), pure_list_journal(app->outbox, true); - } + } else + for (unsigned r = 0; r < PURE_LIST_RUNTIME; ++r) { /* the runtime's lists, past the new module's states */ + next.image_seq[states + r] = app->image_seq[app->laid + r]; + next.logged_since[states + r] = app->logged_since[app->laid + r]; + next.logged[states + r] = app->logged[app->laid + r]; + } + take_states(app, &next); + free(kept), free(listed), free(rewrite), free(moved); pure_save_release(app); for (uint32_t i = 0; i < module->states; ++i) { /* A stored list's changes are recorded from now on; a transient one's never. */ @@ -6370,17 +6498,12 @@ PURE_COLD static PureResult open_module(PureApp *app, const PureModule *module) if (lists_stored) pure_list_journal(recast, true); } if (outbox_imaged) { - app->image_seq[PURE_LIST_OUTBOX] = app->seq; - app->logged_since[PURE_LIST_OUTBOX] = 0; - app->logged[PURE_LIST_OUTBOX] = 0; + app->image_seq[states + 1] = app->seq; + app->logged_since[states + 1] = 0; + app->logged[states + 1] = 0; } drop_passes(app); /* kept by instruction of the module they were made for */ app->module = module; - app->counted = 0; - for (uint32_t i = 0; i < module->states; ++i) - if (module->state_type[i] == PURE_STATE_TEXT || - (module->state_type[i] == PURE_STATE_LIST && !(module->state_flags[i] & PURE_LIST_ROWS >> 8))) - app->counted |= UINT32_C(1) << i; /* a message's rows are its own, gone when it ends */ ++app->changes; return PURE_OK; } diff --git a/core/module.h b/core/module.h index 8ea0574..645fb71 100644 --- a/core/module.h +++ b/core/module.h @@ -14,7 +14,7 @@ * belongs to), stack heights and the stack at every join, forward-only * branches, names, records, tables, constants and effects. */ enum { - PURE_MODULE_STATES = 32, PURE_MODULE_INPUTS = 32, PURE_MODULE_VIEWS = 64, + PURE_MODULE_INPUTS = 32, PURE_MODULE_VIEWS = 64, PURE_MODULE_HANDLERS = 64, PURE_MODULE_CODE = 65535, PURE_MODULE_STACK = 64, PURE_MODULE_NAME = 32, PURE_MODULE_STRINGS = 32768, PURE_MODULE_HEADER = 38, PURE_MODULE_FIELDS = 64, PURE_MODULE_CONSTANTS = 4096, @@ -329,7 +329,7 @@ typedef struct { PureBlock code; uint16_t first; uint8_t params, results; - uint32_t lists, registers, held, changes; + uint32_t held; uint8_t height, locals, nesting; } PureFunction; typedef struct { char name[PURE_MODULE_NAME]; uint8_t type; uint16_t rows; uint32_t offset; } PureField; @@ -362,7 +362,6 @@ typedef struct { uint8_t form, relation; uint16_t field; uint32_t test; PureBloc enum { PURE_MODULE_RANGES = 64 }; typedef struct { uint8_t lower_relation, upper_relation; PureBlock lower, upper; } PureRange; -_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; @@ -378,7 +377,9 @@ typedef struct { 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 */ + bool *state_transient; /* PURE_STATE_TRANSIENT */ + bool *state_counted; /* what the states hold counts it (a text, a list not a message's rows: the memory budget's) */ + uint16_t state_words; /* a set of states' words, a bit each (027's 4f-2): (states + 63) / 64, at least 1 */ /* 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; @@ -407,9 +408,10 @@ typedef struct { 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. */ + /* Set at load: a view's code computes (its locals and loops: a body), and the event's lists it changes (a set of + * states, state_words for each view). */ bool *view_body; - uint32_t *view_lists; + uint64_t *view_lists; PureShape shape[PURE_MODULE_SHAPES]; uint8_t shapes; PureRange range[PURE_MODULE_RANGES]; @@ -432,13 +434,18 @@ typedef struct { uint16_t *record_held; /* its fields held as bytes are (pure_record_bytes), a bit each (set at load) */ uint16_t functions; PureFunction *function; + /* Each function's sets of states (state_words each): the event's lists and the registers its code names, each + * call's own, and the lists not an event's it changes (or a function it calls does). */ + uint64_t *function_lists, *function_registers, *function_changes; uint8_t *signature_type; uint16_t *signature_record; bool calls; /* it has functions: its blocks run in the executor that calls (module.c) */ } PureModule; /* Whether a state of m is transient (PURE_STATE_TRANSIENT). */ -static inline bool pure_state_transient(const PureModule *m, uint32_t state) { return m->transient >> state & 1; } +static inline bool pure_state_transient(const PureModule *m, uint32_t state) { return m->state_transient[state]; } +/* Whether a set of states (state_words words) holds state. */ +static inline bool pure_states_has(const uint64_t *set, uint32_t state) { return set[state >> 6] >> (state & 63) & 1; } /* Decode and verify into out, which must be zeroed (a static one is) or hold * a loaded module: what it held is given back first. On failure out holds @@ -507,24 +514,29 @@ typedef struct { unsigned char *bytes; size_t used, capacity; } PureArena; * each key, text and message event saves before it returns (a frame waits for * the next one). Deferred, events apply at once and a host saves a burst with * one commit: an event is durable once a later save succeeds. */ -/* The runtime's lists' places among the stored lists' records (PureApp): - * the numbers heard, and the outbox. */ -enum { PURE_LIST_HEARD = PURE_MODULE_STATES, PURE_LIST_OUTBOX, PURE_LISTS_STORED }; +/* The runtime's lists' places among the stored lists' records (PureApp), + * past any state's: the numbers heard, and the outbox. A place's slot in + * the arrays of them is a state's number, or states and states + 1. */ +enum { PURE_LIST_HEARD = 0x10000, PURE_LIST_OUTBOX, PURE_LIST_RUNTIME = 2 }; typedef struct { const PureModule *module; PureData data; - PureValue values[PURE_MODULE_STATES]; /* numbers, bools, cursor byte positions and list indices */ - PureDraft *texts[PURE_MODULE_STATES]; - PureList *lists[PURE_MODULE_STATES]; + /* The arrays of each state (027's 4f-2), sized to the module in one block (states) for the states it was laid out + * for (laid). */ + void *states; + uint32_t laid; + PureValue *values; /* numbers, bools, cursor byte positions and list indices */ + PureDraft **texts; + PureList **lists; PureUndo undo; /* list changes of the current event */ - PureDraft *list_drafts[PURE_MODULE_STATES]; /* lists the tree kept before images, until emptied */ - /* Stored lists (module-save.c), by state, then the runtime's (the heard - * list and the outbox): the save each one's image holds, the first save - * logged since that changed it (0: none), and the bytes of its changes - * logged since; the log, the tree's number of its last save, the last - * save's number, and a checkpoint being written. */ - uint64_t image_seq[PURE_LISTS_STORED], logged_since[PURE_LISTS_STORED]; - size_t logged[PURE_LISTS_STORED]; + PureDraft **list_drafts; /* lists the tree kept before images, until emptied */ + /* Stored lists (module-save.c), by slot (a state's, then the runtime's: + * the heard list and the outbox): the save each one's image holds, the + * first save logged since that changed it (0: none), and the bytes of its + * changes logged since; the log, the tree's number of its last save, the + * last save's number, and a checkpoint being written. */ + uint64_t *image_seq, *logged_since; + size_t *logged; struct PureLog *log; PureDraft *stored_seq; uint64_t seq; @@ -533,23 +545,22 @@ typedef struct { bool background_checkpoints; bool uncertain; /* a save may or may not be stored: none follows until a start */ PureDraft *stored_values; /* numbers, cursors and indices, when kept */ - PureVersion saved[PURE_MODULE_STATES]; /* text and list versions at the last save */ + PureVersion *saved; /* text and list versions at the last save */ /* Events since the last save: what a failed save returns to. */ bool deferred, open; - PureValue durable_values[PURE_MODULE_STATES]; - PureVersion durable_texts[PURE_MODULE_STATES]; - size_t durable_lists[PURE_MODULE_STATES]; /* each stored list's journal length */ + PureValue *durable_values; + PureVersion *durable_texts; + size_t *durable_lists; /* each stored list's journal length */ uint64_t durable_events; PureFault fault; uint32_t fault_pc; size_t memory; /* the budget of what the states hold (0: none): pure_app_memory */ uint64_t work_budget, work; /* pure_app_work's budget (0: PURE_WORK_DEFAULT), and what the unit running has used */ int64_t grown, spent; /* what the event running has added to the states (and its lists), as stored and in all */ - PureList *own[PURE_MODULE_STATES]; /* a reader's: the event's lists its views compute in, its own */ + PureList **own; /* a reader's: the event's lists its views compute in, its own */ size_t room; /* what it may hold for that in all: PURE_GROWTH_DEFAULT, or the memory budget if larger */ PureFault view_fault; /* why the last view evaluated had no value (pure_app_view_fault) */ char why[128]; /* why the last start or replace was refused, when it says (else empty; a failed start keeps it) */ - uint32_t counted; /* the states it counts, a bit each: lists (not a message's rows) and texts */ uint64_t events; /* The time events see as now, in milliseconds since 1970 (UTC). The host * sets it, so a script can replay any time; views never see it. */ @@ -605,11 +616,14 @@ typedef struct { /* The lists in records an event or a view's body made (LIST fields: a value is a number here, 1 on), how many * values share each, and the bound lists' own (a register's list while another is bound to it). */ struct { PureList **list; uint32_t *shared; size_t count, capacity; } nested; - PureList *unbound[PURE_MODULE_STATES]; + PureList **unbound; /* Calls (027's 2c): the frames of those open, in blocks that never move (code points into a frame), the block in * use and how much of it; and the event's lists calls let go, emptied, each state's for its next calls. */ struct { unsigned char **blocks; size_t *sizes; unsigned count, at; size_t used; } calls; - struct { PureList **lists; unsigned count, capacity; } spare[PURE_MODULE_STATES]; + struct PureSpare { PureList **lists; unsigned count, capacity; } *spare; + PureValue *pending; /* the values an event works on until it ends */ + PureVersion *marked_texts; /* where the event running began: each text's version, each stored list's journal */ + size_t *marked_lists; } PureApp; PureResult pure_app_start(PureApp *, const PureModule *, PureData data); @@ -764,5 +778,5 @@ void pure_snapshot_release(PureSnapshot *); * views read them while the snapshot is held, with the reader's own arena and * row cache (one thread at a time). It applies no events. pure_app_stop frees * the reader and leaves the snapshot alone. */ -void pure_app_read(PureApp *reader, const PureSnapshot *); +bool pure_app_read(PureApp *reader, const PureSnapshot *); /* false without room for its states: it shows none */ #endif diff --git a/core/test-module.c b/core/test-module.c index 8014d63..14a2335 100644 --- a/core/test-module.c +++ b/core/test-module.c @@ -190,10 +190,10 @@ static Spec expand(Spec spec) { static size_t build(unsigned char *out, Spec spec) { spec = expand(spec); - unsigned char strings[4096], blob[4096]; + static unsigned char strings[16384], blob[4096]; size_t used = 0, blob_used = 0; - const char *text[512]; - uint32_t offset[512]; + static const char *text[2048]; + static uint32_t offset[2048]; size_t names = 0; for (size_t i = 0; i < spec.ns; ++i) text[names++] = spec.s[i].name; for (size_t i = 0; i < spec.nt; ++i) text[names++] = spec.t[i].name; @@ -555,10 +555,21 @@ static void rejections(void) { abort(); } } - static S many[33]; - static char many_names[33][8]; - for (unsigned i = 0; i < 33; ++i) { snprintf(many_names[i], 8, "s%u", i % 100u); many[i] = (S){many_names[i], 0}; } - CHECK(load((Spec){many, 33, i1, 0, clash, 0, none, 0, load_a, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, &module_a) == PURE_LIMIT); + /* No fixed count of states (027's 4f-2): 300 of them (sets of them past a word), the last one counted by a key; a + * module of as many runs in the executor alone (the JIT's sets are a word). */ + enum { MANY = 300 }; + static S many[MANY]; + static char many_names[MANY][8]; + for (unsigned i = 0; i < MANY; ++i) { snprintf(many_names[i], 8, "s%u", i); many[i] = (S){many_names[i], 0}; } + static const O count_last[] = {{L, MANY - 1, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, MANY - 1, 0}}; + static const I key_in[] = {KEY("go", "KeyG")}; + static const H go[] = {{1, 0, 4}}; + CHECK(load((Spec){many, MANY, key_in, 1, clash, 0, go, 1, count_last, 4, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}, + &module_a) == PURE_OK && module_a.state_words == 5 && !module_a.jit); + PureApp wide; + CHECK(pure_app_start(&wide, &module_a, (PureData){0}) == PURE_OK && press(&wide, 0) == PURE_OK && press(&wide, 0) == PURE_OK && + wide.values[MANY - 1].num == 2 && wide.values[0].num == 0); + pure_app_stop(&wide); static unsigned char bytes[65536]; size_t size = build(bytes, (Spec){s1, 1, i1, 0, clash, 0, none, 0, load_a, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0}); CHECK(pure_module_load(bytes, size - 1, &module_a, error, sizeof(error)) == PURE_INVALID); @@ -6343,8 +6354,9 @@ static void functions_and_characters(void) { Spec spec = {nums, 1, inputs, LEN(inputs), NULL, 0, handlers, LEN(handlers), code, LEN(code), NULL, 0, others, LEN(others), constants, LEN(constants), NULL, 0, records, 2, NULL, 0}; if (load_functions(spec, fns, LEN(fns), &module_b) != PURE_OK) { fprintf(stderr, "functions: %s\n", error); abort(); } - CHECK(module_b.function[2].lists == UINT32_C(1) << OWN && module_b.function[1].registers == UINT32_C(1) << R_ && - module_b.function[3].held == UINT32_C(1) << 16 && module_b.runner == 2); + CHECK(module_b.state_words == 1 && module_b.function_lists[2] == UINT64_C(1) << OWN && + module_b.function_registers[1] == UINT64_C(1) << R_ && module_b.function[3].held == UINT32_C(1) << 16 && + module_b.runner == 2); PureApp app; CHECK(pure_app_start(&app, &module_b, (PureData){0}) == PURE_OK); for (int round = 0; round < 3; ++round) { /* the frames' room used again, each event alike */ @@ -7109,7 +7121,8 @@ static void view_bodies(void) { Spec spec = {nums, 2, inputs, 1, views, 4, handlers, 1, code, LEN(code), NULL, 0, states, 2, NULL, 0, NULL, 0, records, 1, NULL, 0}; CHECK(load(spec, &module_a) == PURE_OK); - CHECK(module_a.view_body[0] && module_a.view_body[1] && !module_a.view_lists[0] && module_a.view_lists[1] == 1u << SEEN); + CHECK(module_a.view_body[0] && module_a.view_body[1] && module_a.state_words == 1 && !module_a.view_lists[0] && + module_a.view_lists[1] == UINT64_C(1) << SEEN); char *path = new_store(); PureApp app; CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK); diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md index f67cc37..816d835 100644 --- a/docs/design/008-module-format.md +++ b/docs/design/008-module-format.md @@ -189,9 +189,11 @@ bool은 실행 중에 수의 칸에 든 0이나 1이고, i64는 같은 8바이� ## 로드할 때 검증하는 것 -- 머리말, 예약 바이트, 명령 패딩이 0인지. 한도(상태 32, 입력 32, 보기 64(열 포함), 핸들러 64, 명령 65,535, - text 상수 4,096개·1 MiB, 메시지 필드 64, 이름 문자열 32 KiB)와 정확한 파일 크기. 명령과 상수는 모듈 크기에 맞춘 - 블록 하나에 담긴다(`PureModule`의 다른 표는 고정 크기로 13 KiB). 그래서 한도를 올려도 작은 앱의 메모리는 그대로다. +- 머리말, 예약 바이트, 명령 패딩이 0인지. 한도(입력 32, 보기 64(열 포함), 핸들러 64, 명령 65,535, + text 상수 4,096개·1 MiB, 메시지 필드 64, 이름 문자열 32 KiB)와 정확한 파일 크기. 상태의 수는 머리말의 u16이 정할 뿐이다 + (027의 4f-2). 명령과 상수, 그리고 상태·입력·보기·레코드 따위의 표는 모듈 크기에 맞춘 블록에 담긴다(4f-1). 앱이 상태마다 + 가지는 배열도 모듈의 상태 수에 맞춘다. JIT은 상태가 32개까지인 모듈만 컴파일한다(그 집합이 한 단어의 비트다). 넘는 모듈은 + 실행기가 모두 돈다. 걷기와 집계의 인자는 리스트를 8비트에 적으므로(아래 표) 그것이 도는 리스트는 상태 번호 256 아래다. 호스트는 모듈 파일을 크기대로 읽는다(`pure_module_read`). - 이름은 ASCII 식별자 1–31바이트이고, 상태·레코드·입력·보기가 한 이름공간을 쓴다. 필드 이름은 메시지나 레코드 안에서 유일하다. 메시지 필드의 이름은 식별자를 점으로 이은 경로일 수 있고(그룹 `user` 안의 `user.name`), 한 diff --git a/docs/design/027-computation-core.md b/docs/design/027-computation-core.md index 5951f1f..1d41488 100644 --- a/docs/design/027-computation-core.md +++ b/docs/design/027-computation-core.md @@ -435,6 +435,24 @@ - 명령 수(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%다. + - **4f-2 만듦(2026-10-18).** 상태의 수는 머리말의 u16이 정할 뿐이다(`PURE_MODULE_STATES`가 없어졌다). + - 모듈: transient와 앱이 세는 상태(memory 예산의 것)는 상태마다의 표시이고, 보기와 함수가 바꾸거나 이름하는 리스트는 상태의 + 집합(`state_words` 단어, 한 상태가 한 비트)이다. 검증기의 걷는 리스트는 길이를 가진 목록이다(걷기 하나가 그 리스트를 덧붙인다). + 이름공간의 겹침은 이름을 정렬해 찾는다(전에는 짝마다 견주었다). + - 앱: 상태마다의 배열 열여덟(값, text, 리스트, 저장 리스트의 슬롯 셋, 마지막 저장과 묶음의 것, 읽는 쪽의 것, 이벤트가 일하는 + 값과 표시)이 모듈의 상태 수로 놓은 한 블록에 있다. 교체는 새 모듈의 블록을 따로 짓고, 실패할 수 없게 된 뒤 갈아 끼운다. + 런타임의 리스트(들은 번호, 보낼 것)의 자리는 상태의 번호 밖(`PURE_LIST_HEARD` = 0x10000)이고, 배열의 슬롯은 상태 뒤의 둘이다. + 스냅숏의 배열도 그 수다. `pure_app_read`는 그 수의 배열을 짓지 못하면 false다(serve는 그때 보이지 않는다). + - 저장: 커밋의 draft 목록, 값의 인코딩, 트리 커밋의 리비전은 몇(64) 상태까지 스택에, 넘으면 힙에 둔다. + - JIT: 그 집합이 한 단어의 비트라 상태가 32개까지인 모듈만 컴파일한다(넘으면 실행기). 리스트의 표를 앱 안의 배열이 아닌 + 포인터로 읽는다. + - 보기의 평가는 앱의 값을 베끼지 않고 그대로 읽는다(보기는 상태를 바꾸지 못한다). 전에는 평가마다 32칸을 베꼈다. + - 시험: 단위 시험이 300개의 상태(집합 다섯 단어)를 가진 모듈을 로드하고 마지막 상태를 센다(JIT 없이). 실패한 시작이 놓지 않은 + 배열을 `pure_app_stop`이 읽던 것을 고쳤다. `PureApp`은 8,504바이트에서 4,256바이트가 되었다. 코어 22,456 → 22,621줄. + - 명령 수(b39b 대 b38): 묶음과 함께 BFS +0.06%, 나머지 ±0.01% 안이다. 실행기만은 knapsack +0.49%, join +0.23%, BFS +0.16%, + roots −0.16%, matmul +0.11%다. 은행 질의(s38과 s39b를 한 번에 번갈아 세 번)는 field −1.99%, found −1.18%, in_credit −1.10%, + large −0.99%, per_branch −0.58%, ranks −0.43%, none +0.11%다. 첫 빌드(s39)는 in_credit +2.4%, per_branch +1.4%였다(보기의 + 평가가 실행 시 크기로 상태 값을 베꼈다). 그 베끼기를 없앤 것이 위다. - **차례의 까닭.** - 4a는 작고, 3단계의 끝을 맺는다. - 4b는 가장 많이 지운다. 키 있는 메시지와 기준 리비전이 사라지면 4d가 단순해진다. diff --git a/research/experiments/040-against-c/2026-10-18-counts-s39.jsonl b/research/experiments/040-against-c/2026-10-18-counts-s39.jsonl new file mode 100644 index 0000000..b3974b3 --- /dev/null +++ b/research/experiments/040-against-c/2026-10-18-counts-s39.jsonl @@ -0,0 +1,6 @@ +{"serve": "pure-serve", "per_branch": {"instructions": 26865370, "cycles": 12036945}, "in_credit": {"instructions": 3459783, "cycles": 1544059}, "large": {"instructions": 1425182, "cycles": 613806}, "ranks": {"instructions": 24331855, "cycles": 14797872}, "none": {"instructions": 616733, "cycles": 189978}, "field": {"instructions": 9068055, "cycles": 3315372}, "found": {"instructions": 14212606, "cycles": 10506721}} +{"serve": "pure-serve", "per_branch": {"instructions": 27478337, "cycles": 11973088}, "in_credit": {"instructions": 3593583, "cycles": 1683318}, "large": {"instructions": 1432214, "cycles": 623081}, "ranks": {"instructions": 24671480, "cycles": 14969381}, "none": {"instructions": 617421, "cycles": 190697}, "field": {"instructions": 9139254, "cycles": 3188551}, "found": {"instructions": 14290286, "cycles": 10504663}} +{"serve": "pure-serve", "per_branch": {"instructions": 27239776, "cycles": 11894390}, "in_credit": {"instructions": 3543744, "cycles": 1646137}, "large": {"instructions": 1425142, "cycles": 605810}, "ranks": {"instructions": 24538183, "cycles": 14871801}, "none": {"instructions": 616741, "cycles": 196591}, "field": {"instructions": 9068063, "cycles": 3325094}, "found": {"instructions": 14208759, "cycles": 10712719}} +{"serve": "pure-serve", "per_branch": {"instructions": 27304131, "cycles": 11738981}, "in_credit": {"instructions": 3554533, "cycles": 1587122}, "large": {"instructions": 1432198, "cycles": 603337}, "ranks": {"instructions": 24574447, "cycles": 14918480}, "none": {"instructions": 617429, "cycles": 194561}, "field": {"instructions": 9139262, "cycles": 3195565}, "found": {"instructions": 14289814, "cycles": 10579366}} +{"serve": "pure-serve", "per_branch": {"instructions": 26920520, "cycles": 11706646}, "in_credit": {"instructions": 3471996, "cycles": 1591913}, "large": {"instructions": 1425214, "cycles": 614624}, "ranks": {"instructions": 24360461, "cycles": 15245805}, "none": {"instructions": 616741, "cycles": 191201}, "field": {"instructions": 9068166, "cycles": 3325358}, "found": {"instructions": 14212487, "cycles": 10701986}} +{"serve": "pure-serve", "per_branch": {"instructions": 27236206, "cycles": 11723950}, "in_credit": {"instructions": 3539403, "cycles": 1599065}, "large": {"instructions": 1432238, "cycles": 600774}, "ranks": {"instructions": 24538532, "cycles": 14802465}, "none": {"instructions": 617421, "cycles": 189548}, "field": {"instructions": 9139255, "cycles": 3194542}, "found": {"instructions": 14292022, "cycles": 10576899}} diff --git a/research/experiments/040-against-c/2026-10-18-counts-s39b.jsonl b/research/experiments/040-against-c/2026-10-18-counts-s39b.jsonl new file mode 100644 index 0000000..a539cef --- /dev/null +++ b/research/experiments/040-against-c/2026-10-18-counts-s39b.jsonl @@ -0,0 +1,6 @@ +{"serve": "pure-serve", "per_branch": {"instructions": 27360804, "cycles": 11545807}, "in_credit": {"instructions": 3571052, "cycles": 1643630}, "large": {"instructions": 1425086, "cycles": 638653}, "ranks": {"instructions": 24605299, "cycles": 15131628}, "none": {"instructions": 616733, "cycles": 189773}, "field": {"instructions": 9068158, "cycles": 3292104}, "found": {"instructions": 14207567, "cycles": 10574932}} +{"serve": "pure-serve", "per_branch": {"instructions": 27135595, "cycles": 11877425}, "in_credit": {"instructions": 3516959, "cycles": 1701094}, "large": {"instructions": 1410989, "cycles": 596882}, "ranks": {"instructions": 24462411, "cycles": 14996706}, "none": {"instructions": 617412, "cycles": 191578}, "field": {"instructions": 8887523, "cycles": 3098617}, "found": {"instructions": 14040405, "cycles": 10512668}} +{"serve": "pure-serve", "per_branch": {"instructions": 27293600, "cycles": 12094763}, "in_credit": {"instructions": 3556001, "cycles": 1645229}, "large": {"instructions": 1425126, "cycles": 610823}, "ranks": {"instructions": 24569038, "cycles": 14957957}, "none": {"instructions": 616741, "cycles": 191024}, "field": {"instructions": 9068064, "cycles": 3388688}, "found": {"instructions": 14206167, "cycles": 10807883}} +{"serve": "pure-serve", "per_branch": {"instructions": 26973339, "cycles": 11639213}, "in_credit": {"instructions": 3480613, "cycles": 1591285}, "large": {"instructions": 1411029, "cycles": 607636}, "ranks": {"instructions": 24373751, "cycles": 14914522}, "none": {"instructions": 617412, "cycles": 191694}, "field": {"instructions": 8887420, "cycles": 3106773}, "found": {"instructions": 14041324, "cycles": 10311028}} +{"serve": "pure-serve", "per_branch": {"instructions": 27079702, "cycles": 11842700}, "in_credit": {"instructions": 3507813, "cycles": 1608816}, "large": {"instructions": 1425238, "cycles": 627439}, "ranks": {"instructions": 24449335, "cycles": 14965019}, "none": {"instructions": 616741, "cycles": 191783}, "field": {"instructions": 9068064, "cycles": 3317887}, "found": {"instructions": 14211959, "cycles": 10770860}} +{"serve": "pure-serve", "per_branch": {"instructions": 27231972, "cycles": 11876324}, "in_credit": {"instructions": 3538419, "cycles": 1736665}, "large": {"instructions": 1410965, "cycles": 615990}, "ranks": {"instructions": 24514950, "cycles": 15068801}, "none": {"instructions": 617412, "cycles": 207334}, "field": {"instructions": 8887420, "cycles": 3101559}, "found": {"instructions": 14039620, "cycles": 10365033}} diff --git a/research/experiments/041-batches/2026-10-18-counts.jsonl b/research/experiments/041-batches/2026-10-18-counts.jsonl index 7b29e36..45e7776 100644 --- a/research/experiments/041-batches/2026-10-18-counts.jsonl +++ b/research/experiments/041-batches/2026-10-18-counts.jsonl @@ -138,3 +138,23 @@ {"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} +{"version":"b39","way":"batches","work":"roots","instructions":157245460,"cycles":109743101} +{"version":"b39","way":"rounds","work":"roots","instructions":2715503224,"cycles":1022247002} +{"version":"b39","way":"batches","work":"knapsack","instructions":348029618,"cycles":105495098} +{"version":"b39","way":"rounds","work":"knapsack","instructions":22162560908,"cycles":7850938130} +{"version":"b39","way":"batches","work":"join","instructions":1783718615,"cycles":1081415678} +{"version":"b39","way":"rounds","work":"join","instructions":7723675399,"cycles":3499418387} +{"version":"b39","way":"batches","work":"matmul","instructions":702228698,"cycles":227384027} +{"version":"b39","way":"rounds","work":"matmul","instructions":21186408154,"cycles":7049343147} +{"version":"b39","way":"batches","work":"bfs","instructions":3345454177,"cycles":1709328930} +{"version":"b39","way":"rounds","work":"bfs","instructions":4705142450,"cycles":2084825460} +{"version":"b39b","way":"batches","work":"roots","instructions":157250010,"cycles":109528701} +{"version":"b39b","way":"rounds","work":"roots","instructions":2715503306,"cycles":1021040066} +{"version":"b39b","way":"batches","work":"knapsack","instructions":348029584,"cycles":105163550} +{"version":"b39b","way":"rounds","work":"knapsack","instructions":22162560899,"cycles":7840125192} +{"version":"b39b","way":"batches","work":"join","instructions":1783718499,"cycles":1021101298} +{"version":"b39b","way":"rounds","work":"join","instructions":7723675521,"cycles":3522966551} +{"version":"b39b","way":"batches","work":"matmul","instructions":702228822,"cycles":227369118} +{"version":"b39b","way":"rounds","work":"matmul","instructions":21186408258,"cycles":7035750569} +{"version":"b39b","way":"batches","work":"bfs","instructions":3341451010,"cycles":1659602824} +{"version":"b39b","way":"rounds","work":"bfs","instructions":4705138988,"cycles":2092014946} diff --git a/serve/serve.c b/serve/serve.c index 5881ffd..2dca1cb 100644 --- a/serve/serve.c +++ b/serve/serve.c @@ -2279,7 +2279,10 @@ static PureSnapshot *read_newest(PureApp *reader, App *app) { PureSnapshot *snapshot = published->snapshot; if (snapshot) pure_snapshot_hold(snapshot); pthread_mutex_unlock(&published->lock); - if (snapshot) pure_app_read(reader, snapshot); + if (snapshot && !pure_app_read(reader, snapshot)) { /* no room for its states: none shown */ + pure_snapshot_release(snapshot); + snapshot = NULL; + } reader->work_budget = atomic_load_explicit(&app->work, memory_order_relaxed); reader->memory = atomic_load_explicit(&app->memory, memory_order_relaxed); /* what a view's body may add to its own lists */ return snapshot; @@ -2847,7 +2850,11 @@ static PureResult run_query(const Job *job, PureSnapshot *snapshot, Applied *a) return PURE_BUSY; } PureApp reader = {0}; - pure_app_read(&reader, snapshot); + if (!pure_app_read(&reader, snapshot)) { + pure_app_stop(&reader); + let_go_query(held); + return PURE_ALLOCATION; + } if (reader.module != checked && !same_states(&held->module, reader.module)) { /* its app's states are laid out anew */ pure_app_stop(&reader); let_go_query(held); @@ -3178,7 +3185,10 @@ static PureSnapshot *surface_read(PureSurfaceHost *s, PureApp *reader) { PureSnapshot *snapshot = published->snapshot; if (snapshot) pure_snapshot_hold(snapshot); pthread_mutex_unlock(&published->lock); - if (snapshot) pure_app_read(reader, snapshot); + if (snapshot && !pure_app_read(reader, snapshot)) { /* no room for its states: none shown */ + pure_snapshot_release(snapshot); + snapshot = NULL; + } return snapshot; } |
