aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/module-jit.c26
-rw-r--r--core/module-save.c64
-rw-r--r--core/module-save.h9
-rw-r--r--core/module-store.c13
-rw-r--r--core/module.c385
-rw-r--r--core/module.h74
-rw-r--r--core/test-module.c33
7 files changed, 391 insertions, 213 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);