aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/module-save.c53
-rw-r--r--core/module.c98
-rw-r--r--core/module.h22
-rw-r--r--core/test-module.c20
-rw-r--r--docs/design/008-module-format.md6
-rw-r--r--docs/design/027-computation-core.md15
-rw-r--r--research/experiments/040-against-c/2026-10-18-counts-s40.jsonl6
-rw-r--r--research/experiments/041-batches/2026-10-18-counts.jsonl10
-rw-r--r--serve/deliver.c2
-rw-r--r--serve/message-json.h2
-rw-r--r--serve/serve.c81
11 files changed, 214 insertions, 101 deletions
diff --git a/core/module-save.c b/core/module-save.c
index 63553e0..19e9684 100644
--- a/core/module-save.c
+++ b/core/module-save.c
@@ -156,8 +156,17 @@ PURE_COLD PureResult pure_save_open_text(PureApp *app, const PureModule *m, uint
/* ---- Lists: images and the log */
-enum { OUTPUTS_NAMED = 1 + PURE_MODULE_INPUTS * (2 + PURE_MODULE_NAME) + PURE_MODULE_FIELDS * (2 + PURE_MODULE_NAME),
- IMAGE_HEADER = 6 + PURE_MODULE_RECORDS * (1 + PURE_RECORD_FIELDS * (3 + PURE_MODULE_NAME)) + 8 + OUTPUTS_NAMED };
+/* A runtime list's image header at most (its three fields at most); and what any of m's takes at most (027's 4f-3a): a
+ * record graph's, or the outbox's naming m's outputs. */
+enum { RUNTIME_HEADER = 4 + 1 + 3 * (2 + PURE_MODULE_NAME) + 8 };
+static size_t header_room(const PureModule *m) {
+ size_t records = 6 + 8 + 1, outbox = RUNTIME_HEADER + 1;
+ for (uint32_t r = 0; r < m->records; ++r) records += 1 + (size_t)m->record[r].count * (3 + PURE_MODULE_NAME);
+ for (uint32_t i = 0; i < m->inputs; ++i)
+ if (m->input[i].kind == PURE_KIND_OUTPUT)
+ outbox += 2 + PURE_MODULE_NAME + (size_t)m->input[i].field_count * (2 + PURE_MODULE_NAME);
+ return records > outbox ? records : outbox;
+}
static size_t header_of(unsigned count, const uint8_t *text, const char (*names)[PURE_MODULE_NAME], uint64_t seq,
unsigned char *out) {
@@ -270,7 +279,7 @@ static size_t outbox_header(const PureModule *m, uint64_t seq, unsigned char *ou
/* What an output's sends were laid out with, as a store names it: its
* fields (laid out), or their types alone (named false: links before PLK4). */
-typedef struct { char name[PURE_MODULE_NAME]; PureField fields[PURE_MODULE_FIELDS]; uint32_t count; bool named; } SentShape;
+typedef struct { char name[PURE_MODULE_NAME]; PureField fields[PURE_MESSAGE_FIELDS]; uint32_t count; bool named; } SentShape;
/* The outputs an outbox image names (at, size bytes): how many, or -1 when
* they are not well formed; shapes (when not NULL) takes them. */
@@ -282,7 +291,7 @@ static int read_outputs(const unsigned char *at, size_t size, SentShape *shapes,
size_t length = k < size ? at[k] : 0;
if (!length || length >= PURE_MODULE_NAME || size - k < 2 + length) return -1;
unsigned fields = at[k + 1 + length];
- if (fields > PURE_MODULE_FIELDS) return -1;
+ if (fields > PURE_MESSAGE_FIELDS) return -1;
if (shapes) {
memcpy(shapes[t].name, at + k + 1, length);
shapes[t].name[length] = 0;
@@ -473,9 +482,12 @@ static PureResult write_image(const char *directory, const char *name, const uns
}
PureResult pure_save_image(PureApp *app, const PureModule *m, uint32_t state, const PureList *list) {
- unsigned char header[IMAGE_HEADER];
+ unsigned char *header = malloc(header_room(m));
+ if (!header) return PURE_ALLOCATION;
size_t header_size = image_header(m, m->state_ref[state], app->seq, header);
- return write_image(directory_of(app), m->state_names[state], header, header_size, list);
+ PureResult result = write_image(directory_of(app), m->state_names[state], header, header_size, list);
+ free(header);
+ return result;
}
PURE_COLD PureResult pure_save_open_list(PureApp *app, const PureModule *m, uint32_t state, PureList **out,
@@ -597,7 +609,7 @@ PURE_COLD PureResult pure_save_open_runtime(PureApp *app, uint32_t place, PureLi
*saved = (PureSaved){0};
*out = NULL;
if (!directory_of(app)) return PURE_STORE_ERROR;
- unsigned char *bytes = NULL, header[IMAGE_HEADER];
+ unsigned char *bytes = NULL, header[RUNTIME_HEADER];
size_t size = 0, length = runtime_header(place, 0, header) - 8, at = length + 8;
PureResult result = pure_image_read(directory_of(app), name, &bytes, &size);
bool image = result == PURE_OK;
@@ -648,11 +660,14 @@ PURE_COLD PureResult pure_save_open_runtime(PureApp *app, uint32_t place, PureLi
}
PureResult pure_save_runtime_image(PureApp *app, const PureModule *m, uint32_t place, const PureList *list) {
- unsigned char header[IMAGE_HEADER];
+ unsigned char *header = malloc(header_room(m));
+ if (!header) return PURE_ALLOCATION;
size_t header_size = place == PURE_LIST_OUTBOX ? outbox_header(m, app->seq, header) :
runtime_header(place, app->seq, header);
- return write_image(directory_of(app), runtime_lists[place - PURE_LIST_HEARD].name, header, header_size,
- list ? list : stored_list(app, place));
+ PureResult result = write_image(directory_of(app), runtime_lists[place - PURE_LIST_HEARD].name, header, header_size,
+ list ? list : stored_list(app, place));
+ free(header);
+ return result;
}
/* ---- Checkpoints */
@@ -661,7 +676,7 @@ struct PureCheckpoint {
PureSnapshot *snapshot;
uint32_t state;
char name[PURE_MODULE_NAME], *directory;
- unsigned char header[IMAGE_HEADER];
+ unsigned char *header; /* header_room's */
size_t header_size, logged;
uint64_t seq, since;
};
@@ -690,8 +705,10 @@ PureCheckpoint *pure_app_checkpoint(PureApp *app) {
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 ? pick : UINT32_MAX))) {
- if (c) free(c->directory);
+ if (c) c->header = malloc(header_room(m));
+ if (!c || !c->directory || !c->header ||
+ !(c->snapshot = pure_save_snapshot(app, pick >= PURE_LIST_HEARD ? pick : UINT32_MAX))) {
+ if (c) free(c->directory), free(c->header);
free(c);
return NULL;
}
@@ -727,7 +744,7 @@ void pure_app_checkpointed(PureApp *app, PureCheckpoint *c, PureResult result) {
}
if (app->checkpoint == c) app->checkpoint = NULL;
pure_snapshot_release(c->snapshot);
- free(c->directory);
+ free(c->directory), free(c->header);
free(c);
}
@@ -819,11 +836,10 @@ static bool same_version(PureVersion a, PureVersion b) { return a.ref == b.ref &
static unsigned char *encode_links(const PureApp *app, size_t *size) {
const PureModule *m = app->module;
unsigned outputs = 0;
- uint8_t table[PURE_MODULE_INPUTS];
size_t total = 4 + 1;
for (uint32_t i = 0; i < m->inputs; ++i)
if (m->input[i].kind == PURE_KIND_OUTPUT) {
- table[i] = (uint8_t)outputs++;
+ ++outputs;
total += 2 + strlen(m->input_names[i]) + 8;
for (uint32_t f = 0; f < m->input[i].field_count; ++f)
total += 2 + strlen(m->field[m->input[i].field_start + f].name);
@@ -859,7 +875,6 @@ static unsigned char *encode_links(const PureApp *app, size_t *size) {
*at++ = 0;
at = put_le(at, app->carried[c].last, 8);
}
- (void)table;
*size = (size_t)(at - bytes);
return bytes;
}
@@ -938,7 +953,7 @@ PURE_COLD PureResult pure_save_open_links(PureApp *app, const PureModule *m, boo
memcpy(linked[t].name, name, length + 1);
linked[t].count = fields;
linked[t].named = named;
- if (fields > PURE_MODULE_FIELDS) result = PURE_STORE_ERROR;
+ if (fields > PURE_MESSAGE_FIELDS) result = PURE_STORE_ERROR;
for (unsigned f = 0; f < fields && result == PURE_OK; ++f) { /* its type, then (PLK4) its name */
size_t named_length = named && size - at >= 2 ? bytes[at + 1] : 0;
if (size - at < (named ? 2 + named_length : 1) || named_length >= PURE_MODULE_NAME) {
@@ -1035,7 +1050,7 @@ PURE_COLD PureResult pure_save_open_links(PureApp *app, const PureModule *m, boo
free(linked);
free(bytes);
app->sent_saved = pure_list_count(app->outbox);
- memcpy(app->saved_sent, app->last_sent, sizeof(app->saved_sent));
+ memcpy(app->saved_sent, app->last_sent, m->inputs * sizeof(*app->saved_sent));
return result;
}
diff --git a/core/module.c b/core/module.c
index 3bc98f2..ba44a85 100644
--- a/core/module.c
+++ b/core/module.c
@@ -244,10 +244,10 @@ static uint8_t field_kind(uint8_t type) {
PureResult pure_send_recast(const PureField *from, uint32_t from_count, const PureField *to, uint32_t to_count,
PureSpan payload, unsigned char **out, size_t *size) {
- FieldValue was[PURE_MODULE_FIELDS], now[PURE_MODULE_FIELDS];
+ FieldValue was[PURE_MESSAGE_FIELDS], now[PURE_MESSAGE_FIELDS];
*out = NULL;
*size = 0;
- if (from_count > PURE_MODULE_FIELDS || to_count > PURE_MODULE_FIELDS || !read_fields(from, from_count, payload, was))
+ if (from_count > PURE_MESSAGE_FIELDS || to_count > PURE_MESSAGE_FIELDS || !read_fields(from, from_count, payload, was))
return PURE_STORE_ERROR;
size_t total = 0;
for (uint32_t g = 0; g < to_count; ++g) {
@@ -644,7 +644,7 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i
}
}
/* Operand types, bottom to top; record operations take their fields. */
- unsigned char need[PURE_MODULE_FIELDS + 2];
+ unsigned char need[PURE_MESSAGE_FIELDS + 2];
unsigned pops = signature[op->op].pops;
memcpy(need, signature[op->op].in, sizeof(signature[op->op].in));
if (op->op == PURE_OP_SEND) {
@@ -1710,10 +1710,8 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
out->record_fields = (uint16_t)read16(p + 34);
out->functions = (uint16_t)read16(p + 36);
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 ||
+ if (out->code_count > PURE_MODULE_CODE || strings > PURE_MODULE_STRINGS ||
+ out->constants > PURE_MODULE_CONSTANTS || constant_bytes > PURE_MODULE_CONSTANT_BYTES ||
out->records > PURE_MODULE_RECORDS || out->record_fields > PURE_MODULE_RECORD_FIELDS ||
out->functions > PURE_MODULE_FUNCTIONS) {
result = PURE_LIMIT;
@@ -1761,7 +1759,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
if (v[4] == PURE_VIEW_LIST && !read16(v + 6) && record && record <= out->records)
made += read16(p + records_at + (size_t)(record - 1) * RECORD_BYTES + 6);
}
- if (out->views + made > PURE_MODULE_VIEWS) REFUSE("views and their columns", out->views);
+ if (out->views + made > UINT16_MAX) REFUSE("views and their columns", out->views); /* (numbered in u16s) */
size_t capacity = out->views + made;
if (!make_tables(out, capacity, signatures)) {
result = PURE_ALLOCATION;
@@ -1822,9 +1820,11 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
}
uint32_t next_field = 0;
const unsigned char *fields = at + (size_t)out->inputs * INPUT_BYTES;
+ unsigned outputs = 0; /* a store names them by a byte's count (the links, the outbox's image): 255 at most */
for (uint32_t i = 0; i < out->inputs; ++i, at += INPUT_BYTES) {
PureInputShape *shape = &out->input[i];
shape->kind = at[4];
+ if (shape->kind == PURE_KIND_OUTPUT && ++outputs > UINT8_MAX) REFUSE("outputs past 255", i);
shape->binding = at[5];
shape->field_start = (uint16_t)read16(at + 12);
shape->field_count = (uint16_t)read16(at + 14);
@@ -1848,7 +1848,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
shape->view = arguments || shape->binding == PURE_BIND_POINT ? read32(at + 8) : 0;
if ((shape->kind != PURE_KIND_MESSAGE && shape->kind != PURE_KIND_OUTPUT && !arguments) ||
(read32(at + 8) && !arguments && !shape->binding) || shape->field_start != next_field ||
- shape->field_count > out->fields - next_field) REFUSE("message shape", i);
+ shape->field_count > out->fields - next_field || shape->field_count > PURE_MESSAGE_FIELDS) REFUSE("message shape", i);
/* A binding's fields: a key's and a timer's none, a text's one text, a frame's one number, a pointing's one u32. */
static const uint8_t bound_field[] = {[PURE_BIND_TEXT] = PURE_FIELD_TEXT, [PURE_BIND_FRAME] = PURE_FIELD_NUMBER,
[PURE_BIND_POINT] = PURE_FIELD_U32};
@@ -1991,7 +1991,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
out->view_table[c] = (uint16_t)(i + 1);
out->view_list[c] = out->view_list[i];
out->view_record[c] = (uint16_t)(out->view_type[c] == PURE_VIEW_LIST ? nested_record(out, field) + 1 : 0);
- out->view_field[c] = (uint8_t)(f + 1);
+ out->view_field[c] = (uint16_t)(f + 1);
out->view_computed[c] = out->view_computed[i];
out->view_code[c] = (PureBlock){0, 0};
}
@@ -2008,7 +2008,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule
if (shape->view >= out->views || out->view_table[shape->view] || out->view_arguments[shape->view]) REFUSE("arguments", i);
snprintf(path, sizeof(path), "%s.args", out->view_names[shape->view]);
if (strcmp(path, out->input_names[i])) REFUSE("arguments", i);
- out->view_arguments[shape->view] = (uint8_t)(i + 1);
+ out->view_arguments[shape->view] = i + 1;
}
for (uint32_t i = 0; i < out->views; ++i)
if (out->view_table[i]) out->view_arguments[i] = out->view_arguments[out->view_table[i] - 1];
@@ -2289,7 +2289,7 @@ size_t pure_module_fields(const PureModule *m, uint32_t input, unsigned char out
/* Fields as a sender names them, laid out: their count, or -1 when they are
* not well named (no rows: a sender sends none). */
static int named_fields(const unsigned char *bytes, size_t size, PureField *fields) {
- if (!size || bytes[0] > PURE_MODULE_FIELDS) return -1;
+ if (!size || bytes[0] > PURE_MESSAGE_FIELDS) return -1;
unsigned count = bytes[0];
size_t at = 1;
for (unsigned f = 0; f < count; ++f) {
@@ -2309,7 +2309,7 @@ static int named_fields(const unsigned char *bytes, size_t size, PureField *fiel
}
bool pure_module_takes(const PureModule *m, uint32_t input, const unsigned char *fields, size_t size) {
- PureField sent[PURE_MODULE_FIELDS];
+ PureField sent[PURE_MESSAGE_FIELDS];
int count = named_fields(fields, size, sent);
if (count < 0) return false;
const PureInputShape *s = &m->input[input];
@@ -2327,7 +2327,7 @@ bool pure_module_takes(const PureModule *m, uint32_t input, const unsigned char
PureResult pure_module_recast(const PureModule *m, uint32_t input, const unsigned char *fields, size_t size,
PureSpan payload, unsigned char **out, size_t *out_size) {
- PureField sent[PURE_MODULE_FIELDS];
+ PureField sent[PURE_MESSAGE_FIELDS];
int count = named_fields(fields, size, sent);
*out = NULL;
*out_size = 0;
@@ -5012,10 +5012,10 @@ static void close_burst(PureApp *app, bool keep) {
static void settle_links(PureApp *app, bool saved) {
if (saved) {
app->sent_saved = app->outbox ? pure_list_count(app->outbox) : 0;
- memcpy(app->saved_sent, app->last_sent, sizeof(app->saved_sent));
+ memcpy(app->saved_sent, app->last_sent, app->module->inputs * sizeof(*app->saved_sent));
} else {
app->sent_saved = app->durable_sent_saved;
- memcpy(app->last_sent, app->saved_sent, sizeof(app->last_sent));
+ memcpy(app->last_sent, app->saved_sent, app->module->inputs * sizeof(*app->last_sent));
}
app->links_changed = false;
}
@@ -5161,6 +5161,7 @@ void pure_app_stop(PureApp *app) {
free(app->nested.shared);
pure_draft_close(app->stored_values);
free(app->carried);
+ free(app->by_input);
pure_draft_close(app->stored_links);
pure_draft_close(app->stored_seq);
pure_save_close_start(app);
@@ -5178,8 +5179,7 @@ void pure_app_stop(PureApp *app) {
/* Where one event began inside the burst: a failed event returns there. */
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];
+ size_t undo, sent_saved; /* (each output's last number in the app's marked_sent) */
bool opened;
} Mark;
@@ -5194,7 +5194,7 @@ static void mark_event(PureApp *app, Mark *mark) {
mark->outbox = app->outbox ? pure_list_changes(app->outbox).size : 0;
mark->undo = app->undo.count;
mark->sent_saved = app->sent_saved;
- memcpy(mark->last_sent, app->last_sent, sizeof(mark->last_sent));
+ memcpy(app->marked_sent, app->last_sent, app->module->inputs * sizeof(*app->marked_sent));
}
static void undo_event(PureApp *app, const Mark *mark) {
@@ -5206,7 +5206,7 @@ static void undo_event(PureApp *app, const Mark *mark) {
if (app->outbox) pure_list_forget(app->outbox, mark->outbox);
pure_undo_rollback(&app->undo, mark->undo);
app->sent_saved = mark->sent_saved;
- memcpy(app->last_sent, mark->last_sent, sizeof(app->last_sent));
+ memcpy(app->last_sent, app->marked_sent, app->module->inputs * sizeof(*app->last_sent));
if (mark->opened) close_burst(app, true); /* nothing happened: release the holds */
}
@@ -5375,7 +5375,7 @@ static PureFault place_fields(const PureModule *m, const PureInputShape *shape,
}
int pure_module_arguments(const PureModule *m, uint32_t view) {
- return view < m->views && m->view_arguments[view] ? m->view_arguments[view] - 1 : -1;
+ return view < m->views && m->view_arguments[view] ? (int)m->view_arguments[view] - 1 : -1;
}
PureResult pure_app_arguments(PureApp *app, uint32_t view, PureSpan payload, const uint32_t *placed) {
@@ -5400,7 +5400,7 @@ static PureResult message(PureApp *app, uint32_t input, PureSpan payload, const
app->fault_pc = UINT32_MAX;
if (input >= m->inputs || m->input[input].kind != PURE_KIND_MESSAGE || (payload.size && !payload.data)) return PURE_INVALID;
const PureInputShape *shape = &m->input[input];
- uint32_t spans[PURE_MODULE_FIELDS][2];
+ uint32_t spans[PURE_MESSAGE_FIELDS][2];
PureFault wrong = place_fields(m, shape, payload, placed, spans);
if (wrong != PURE_FAULT_NONE) return fault(app, wrong, UINT32_MAX);
/* Its rows fill their lists (emptied when the event ends); rows that are not whole fail it. Rows whose records hold
@@ -6193,10 +6193,26 @@ static bool same_fields(const PureModule *old, uint32_t from, const PureModule *
* app->why says), and otherwise keeps its number for later. *changed: an
* output of module is laid out otherwise than old's (or old has none of
* that name), so the outbox's image, which names them, is written again. */
+/* A block of each input's numbers for inputs of them, zeroed, its arrays to be laid out in app (by_input_of); NULL without
+ * room (027's 4f-3a). */
+static uint64_t *by_input_block(uint32_t inputs) { return calloc(3 * (size_t)inputs + 1, sizeof(uint64_t)); }
+static void by_input_of(PureApp *app, uint64_t *block, uint32_t inputs) {
+ free(app->by_input);
+ app->by_input = block;
+ app->last_sent = block, app->saved_sent = block + inputs, app->marked_sent = block + 2 * (size_t)inputs;
+}
+
+/* An output's sends and numbers carried to module by name; applied (apply), its numbers in block (by_input_block's,
+ * for module's inputs, which it then holds). */
PURE_COLD static PureResult remap_outputs(PureApp *app, const PureModule *old, const PureModule *module, bool apply,
- PureList **recast, bool *changed) {
- int map[PURE_MODULE_INPUTS];
- bool differs[PURE_MODULE_INPUTS] = {false}, recasting = false;
+ PureList **recast, bool *changed, uint64_t *block) {
+ int *map = malloc((old->inputs + 1) * sizeof(*map));
+ bool *differs = calloc(old->inputs + 1, sizeof(*differs)), recasting = false;
+ if (!map || !differs) {
+ free(map), free(differs);
+ if (apply) by_input_of(app, block, module->inputs); /* (the numbers start again) */
+ return PURE_ALLOCATION;
+ }
for (uint32_t i = 0; i < old->inputs; ++i) {
map[i] = -1;
if (old->input[i].kind != PURE_KIND_OUTPUT) continue;
@@ -6222,11 +6238,15 @@ PURE_COLD static PureResult remap_outputs(PureApp *app, const PureModule *old, c
if (i < 0 || map[i] < 0) {
snprintf(app->why, sizeof(app->why), "sends wait on the output '%s', which this module does not have",
name);
+ free(map), free(differs);
return PURE_CONFLICT;
}
recasting = recasting || differs[i];
}
- if (!recasting) return PURE_OK;
+ if (!recasting) {
+ free(map), free(differs);
+ return PURE_OK;
+ }
PureList *made = pure_runtime_list_new(PURE_LIST_OUTBOX);
PureResult result = made ? PURE_OK : PURE_ALLOCATION;
for (size_t s = 0; result == PURE_OK && s < count; ++s) {
@@ -6246,6 +6266,7 @@ PURE_COLD static PureResult remap_outputs(PureApp *app, const PureModule *old, c
if (result == PURE_OK) result = pure_list_insert(made, s, item, NULL);
free(bytes);
}
+ free(map), free(differs);
if (result != PURE_OK) {
pure_list_free(made);
return result;
@@ -6253,7 +6274,9 @@ PURE_COLD static PureResult remap_outputs(PureApp *app, const PureModule *old, c
*recast = made;
return PURE_OK;
}
- uint64_t last[PURE_MODULE_INPUTS] = {0}, saved[PURE_MODULE_INPUTS] = {0};
+ uint64_t *last = block, *saved = block + module->inputs;
+ unsigned outputs = 0; /* the store names them and those carried by a byte's count: 255 in all at most */
+ for (uint32_t k = 0; k < module->inputs; ++k) outputs += module->input[k].kind == PURE_KIND_OUTPUT;
for (uint32_t i = 0; i < old->inputs; ++i) {
if (old->input[i].kind != PURE_KIND_OUTPUT || !app->last_sent[i]) continue;
if (map[i] >= 0) {
@@ -6261,8 +6284,9 @@ PURE_COLD static PureResult remap_outputs(PureApp *app, const PureModule *old, c
saved[map[i]] = app->saved_sent[i];
continue;
}
- struct PureCarried *grown = realloc(app->carried, (app->carried_count + 1) * sizeof(*grown));
- if (!grown) continue; /* its numbers would start again: kept only while memory allows */
+ struct PureCarried *grown = outputs + app->carried_count < UINT8_MAX ?
+ realloc(app->carried, (app->carried_count + 1) * sizeof(*grown)) : NULL;
+ if (!grown) continue; /* its numbers would start again: kept only while memory and the store's count allow */
app->carried = grown;
memcpy(grown[app->carried_count].name, old->input_names[i], PURE_MODULE_NAME);
grown[app->carried_count++].last = app->last_sent[i];
@@ -6275,8 +6299,8 @@ PURE_COLD static PureResult remap_outputs(PureApp *app, const PureModule *old, c
app->carried[c] = app->carried[--app->carried_count];
app->links_changed = true;
}
- memcpy(app->last_sent, last, sizeof(last));
- memcpy(app->saved_sent, saved, sizeof(saved));
+ free(map), free(differs);
+ by_input_of(app, block, module->inputs);
return PURE_OK;
}
@@ -6305,10 +6329,12 @@ PURE_COLD static PureResult open_module(PureApp *app, const PureModule *module)
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);
+ uint64_t *numbers = by_input_block(module->inputs); /* each input's numbers: the app's from now on */
+ if (!kept || !listed || !rewrite || !moved || !numbers || !lay_app(&next, states)) {
+ free(kept), free(listed), free(rewrite), free(moved), free(numbers), free(next.states);
return PURE_ALLOCATION;
}
+ if (!old) by_input_of(app, numbers, module->inputs), numbers = NULL; /* (the stored numbers are read into them) */
PureValue *values = next.values;
PureDraft **texts = next.texts, **list_drafts = next.list_drafts;
PureList **lists = next.lists;
@@ -6396,7 +6422,7 @@ PURE_COLD static PureResult open_module(PureApp *app, const PureModule *module)
PureList *recast = NULL; /* the outbox, its sends laid out with module's fields */
bool outputs_changed = false;
if (result == PURE_OK)
- result = old ? remap_outputs(app, old, module, false, &recast, &outputs_changed) :
+ result = old ? remap_outputs(app, old, module, false, &recast, &outputs_changed, NULL) :
stored ? pure_save_open_links(app, module, &heard_moved, &sent_moved) : PURE_OK;
heard.rewrite = heard.rewrite || (heard_moved && lists_stored);
sent.rewrite = sent.rewrite || (sent_moved && lists_stored);
@@ -6445,7 +6471,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);
+ free(kept), free(listed), free(rewrite), free(moved), free(numbers), free(next.states);
return result;
}
for (uint32_t i = 0; old && i < old->states; ++i) {
@@ -6490,7 +6516,7 @@ PURE_COLD static PureResult open_module(PureApp *app, const PureModule *module)
if (module->state_type[i] == PURE_STATE_LIST && module->state_flags[i] & PURE_LIST_ROWS >> 8)
(void)pure_list_arrays(lists[i]); /* in arrays, not a tree, while it can (021) */
}
- if (old) remap_outputs(app, old, module, true, NULL, NULL);
+ if (old) remap_outputs(app, old, module, true, NULL, NULL, numbers);
if (recast) { /* in the place of the outbox, its sends now the new module's */
pure_list_free(app->outbox);
app->outbox = recast;
diff --git a/core/module.h b/core/module.h
index 645fb71..8de3e33 100644
--- a/core/module.h
+++ b/core/module.h
@@ -14,10 +14,10 @@
* belongs to), stack heights and the stack at every join, forward-only
* branches, names, records, tables, constants and effects. */
enum {
- PURE_MODULE_INPUTS = 32, PURE_MODULE_VIEWS = 64,
- PURE_MODULE_HANDLERS = 64, PURE_MODULE_CODE = 65535, PURE_MODULE_STACK = 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,
+ /* A message's fields at most: a send pushes them all (the stack's), a width of the stored sends too (027's 4f-3a). */
+ PURE_MESSAGE_FIELDS = PURE_MODULE_STACK, PURE_MODULE_CONSTANTS = 4096,
PURE_MODULE_CONSTANT_BYTES = 1 << 20, PURE_MODULE_RECORDS = 16, PURE_MODULE_RECORD_FIELDS = 128,
PURE_RECORD_FIELDS = 16, PURE_MODULE_JOINS = 8
};
@@ -403,11 +403,11 @@ typedef struct {
uint16_t *view_table; /* a column's list view + 1, or 0 */
uint16_t *view_list; /* the list state of a list view over one, or its column's */
uint16_t *view_record; /* a list view's or a list column's items' record + 1 (a map's: 0) */
- uint8_t *view_field; /* a column the load made: its item's field + 1 (read as it is), or 0 */
+ uint16_t *view_field; /* a column the load made: its item's field + 1 (read as it is), or 0 */
bool *view_computed; /* a list view whose code leaves its list (a column's: its view's) */
PureBlock *view_code;
uint8_t *view_plan; /* a table's filter runs a leaf of its list at once (as a fold may) */
- uint8_t *view_arguments; /* the input of a view's arguments + 1 (a column's: its table's), or 0 */
+ uint32_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 set of
* states, state_words for each view). */
bool *view_body;
@@ -469,7 +469,7 @@ uint32_t pure_module_shape(const PureModule *, uint32_t input);
/* An input's (or output's) fields as a sender names them to a receiver: u8
* count, each field's {u8 type, u8 name length, name}. Its size (at most
* PURE_FIELDS_NAMED), written to out. */
-enum { PURE_FIELDS_NAMED = 1 + PURE_MODULE_FIELDS * (2 + PURE_MODULE_NAME) };
+enum { PURE_FIELDS_NAMED = 1 + PURE_MESSAGE_FIELDS * (2 + PURE_MODULE_NAME) };
size_t pure_module_fields(const PureModule *, uint32_t input, unsigned char out[PURE_FIELDS_NAMED]);
/* Whether input takes messages laid out with the fields named (as
* pure_module_fields names them): it has every one by name, of the same type
@@ -585,7 +585,7 @@ typedef struct {
PureList *outbox;
size_t sent_saved, durable_sent_saved, durable_outbox;
uint64_t sends_gone; /* how many ever left it: a host's places in it move back at most that many */
- uint64_t last_sent[PURE_MODULE_INPUTS], saved_sent[PURE_MODULE_INPUTS];
+ uint64_t *last_sent, *saved_sent; /* each input's (by_input), an output's numbers */
struct PureCarried { char name[PURE_MODULE_NAME]; uint64_t last; } *carried;
unsigned carried_count;
/* The last number delivered from each source: a list of {source (text),
@@ -602,7 +602,7 @@ typedef struct {
* none), where their fields are, and a count of the times they were given. */
unsigned argued;
PureSpan arguments;
- uint32_t argument_spans[PURE_MODULE_FIELDS][2];
+ uint32_t argument_spans[PURE_MESSAGE_FIELDS][2];
uint64_t arguments_given;
/* Passes over lists that answer shaped folds, by the list's version. */
struct PurePass *passes;
@@ -622,8 +622,12 @@ typedef struct {
struct { unsigned char **blocks; size_t *sizes; unsigned count, at; size_t used; } calls;
struct PureSpare { PureList **lists; unsigned count, capacity; } *spare;
PureValue *pending; /* the values an event works on until it ends */
- PureVersion *marked_texts; /* where the event running began: each text's version, each stored list's journal */
+ PureVersion *marked_texts; /* where the event running began: each text's version, each stored list's journal, */
size_t *marked_lists;
+ /* Each input's: its last number sent and as saved, and where the event running began; one block (by_input) for
+ * the inputs of the module it was laid out for (027's 4f-3a). */
+ uint64_t *marked_sent;
+ void *by_input;
} PureApp;
PureResult pure_app_start(PureApp *, const PureModule *, PureData data);
diff --git a/core/test-module.c b/core/test-module.c
index 14a2335..4eabdfc 100644
--- a/core/test-module.c
+++ b/core/test-module.c
@@ -570,6 +570,26 @@ static void rejections(void) {
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);
+ /* Nor of inputs, views or handlers (027's 4f-3a): 40 messages (the last counted), 70 views (the last its count). */
+ enum { INPUTS = 40, VIEWS = 70 };
+ static I messages[INPUTS];
+ static V views[VIEWS];
+ static char message_names[INPUTS][8], view_names[VIEWS][8];
+ for (unsigned i = 0; i < INPUTS; ++i) {
+ snprintf(message_names[i], 8, "m%u", i);
+ messages[i] = (I){message_names[i], NULL, PURE_KIND_MESSAGE, NULL, 0};
+ }
+ for (unsigned i = 0; i < VIEWS; ++i) snprintf(view_names[i], 8, "v%u", i), views[i] = (V){view_names[i], 4, 1};
+ static const O counted[] = {{L, 0, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, 0, 0}, {L, 0, 0}};
+ static const H last[] = {{1 + 2 * (INPUTS - 1), 0, 4}};
+ CHECK(load((Spec){s1, 1, messages, INPUTS, views, VIEWS, last, 1, counted, 5, NULL, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0,
+ NULL, 0}, &module_a) == PURE_OK && module_a.inputs == INPUTS && module_a.views == VIEWS);
+ CHECK(pure_app_start(&wide, &module_a, (PureData){0}) == PURE_OK);
+ CHECK(pure_app_message(&wide, INPUTS - 1, (PureSpan){0}) == PURE_OK && pure_app_message(&wide, 0, (PureSpan){0}) == PURE_OK &&
+ pure_app_view(&wide, VIEWS - 1, &(double){0}));
+ double shown;
+ CHECK(pure_app_view(&wide, VIEWS - 1, &shown) && shown == 1);
+ 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);
diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md
index 816d835..e940e6a 100644
--- a/docs/design/008-module-format.md
+++ b/docs/design/008-module-format.md
@@ -189,9 +189,9 @@ bool은 실행 중에 수의 칸에 든 0이나 1이고, i64는 같은 8바이�
## 로드할 때 검증하는 것
-- 머리말, 예약 바이트, 명령 패딩이 0인지. 한도(입력 32, 보기 64(열 포함), 핸들러 64, 명령 65,535,
- text 상수 4,096개·1 MiB, 메시지 필드 64, 이름 문자열 32 KiB)와 정확한 파일 크기. 상태의 수는 머리말의 u16이 정할 뿐이다
- (027의 4f-2). 명령과 상수, 그리고 상태·입력·보기·레코드 따위의 표는 모듈 크기에 맞춘 블록에 담긴다(4f-1). 앱이 상태마다
+- 머리말, 예약 바이트, 명령 패딩이 0인지. 한도(명령 65,535, text 상수 4,096개·1 MiB, 이름 문자열 32 KiB)와 정확한 파일 크기.
+ 상태, 입력, 보기(지은 열까지 u16), 핸들러, 메시지 필드의 수는 머리말의 u16이 정할 뿐이다(027의 4f-2, 4f-3a). 메시지 하나의
+ 필드는 64까지다(SEND가 모두 스택에 올린다). 출력은 255까지다(저장소가 링크와 보낼 것의 이미지에 그 수를 바이트로 적는다). 명령과 상수, 그리고 상태·입력·보기·레코드 따위의 표는 모듈 크기에 맞춘 블록에 담긴다(4f-1). 앱이 상태마다
가지는 배열도 모듈의 상태 수에 맞춘다. JIT은 상태가 32개까지인 모듈만 컴파일한다(그 집합이 한 단어의 비트다). 넘는 모듈은
실행기가 모두 돈다. 걷기와 집계의 인자는 리스트를 8비트에 적으므로(아래 표) 그것이 도는 리스트는 상태 번호 256 아래다.
호스트는 모듈 파일을 크기대로 읽는다(`pure_module_read`).
diff --git a/docs/design/027-computation-core.md b/docs/design/027-computation-core.md
index 1d41488..d555204 100644
--- a/docs/design/027-computation-core.md
+++ b/docs/design/027-computation-core.md
@@ -453,6 +453,21 @@
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%였다(보기의
평가가 실행 시 크기로 상태 값을 베꼈다). 그 베끼기를 없앤 것이 위다.
+ - **4f-3a 만듦(2026-10-18).** 입력, 보기, 핸들러, 메시지 필드의 수는 머리말의 u16이 정할 뿐이다. 4f-3은 둘로 나눴다(레코드와 그
+ 필드는 4f-3b).
+ - 메시지 하나의 필드는 64까지(`PURE_MESSAGE_FIELDS`: SEND가 모두 스택에 올리고, 저장된 보낼 것의 모양도 그만큼이다). 전의 한도는
+ 모듈 전체의 64였다.
+ - 출력은 255까지다. 저장소가 링크(PLK4)와 보낼 것의 이미지에 그 수를 바이트 하나로 적는다. 모듈이 버린 출력의 번호를 이어 가는
+ 것도 그 수 안에서다(넘으면 그 번호는 다시 시작한다). 저장 형식을 넓히는 것은 뒤로 미뤘다.
+ - 앱: 입력마다의 번호(마지막 보낸 것, 저장된 것, 이벤트가 시작한 곳)가 모듈의 입력 수로 놓은 블록(`by_input`)에 있다. 교체는
+ 새 모듈의 블록을 미리 짓고, 출력을 이름으로 옮기며 채운다. 이미지의 머리는 모듈에 맞춘 크기(`header_room`)로 잡는다.
+ - 보기의 인자는 u32, 지은 열의 필드는 u16로 적는다.
+ - 호스트: serve의 타이머는 모듈의 입력 수만큼 자라고, 앱마다 잇는 보낼 곳과 아는 보낸 이는 serve 자신의 수(`OUTLETS`, 32)다.
+ 목록 보기의 JSON은 열을 몇(64)까지 스택에, 넘으면 힙에 둔다. Vega의 타이머도 입력 수만큼 자라고, 표가 보이는 열은 Vega
+ 자신의 수(`VEGA_COLUMNS`, 64)다. 접근성 트리의 자식 목록은 그 수만큼 잡는다.
+ - 시험: 단위 시험이 입력 40개와 보기 70개를 가진 모듈을 로드하고 마지막 입력과 마지막 보기를 쓴다.
+ - 명령 수(b40 대 b39b): 묶음과 함께, 실행기만 모두 ±0.01% 안이다(실행기만의 BFS +0.09%). 은행 질의(s39b와 s40을 한 번에 번갈아
+ 세 번)는 in_credit −0.64%, per_branch −0.37%, field +0.34%, found +0.22%, ranks −0.22%, large +0.13%, none −0.01%다.
- **차례의 까닭.**
- 4a는 작고, 3단계의 끝을 맺는다.
- 4b는 가장 많이 지운다. 키 있는 메시지와 기준 리비전이 사라지면 4d가 단순해진다.
diff --git a/research/experiments/040-against-c/2026-10-18-counts-s40.jsonl b/research/experiments/040-against-c/2026-10-18-counts-s40.jsonl
new file mode 100644
index 0000000..814492c
--- /dev/null
+++ b/research/experiments/040-against-c/2026-10-18-counts-s40.jsonl
@@ -0,0 +1,6 @@
+{"serve": "pure-serve", "per_branch": {"instructions": 27303845, "cycles": 11984302}, "in_credit": {"instructions": 3554637, "cycles": 1660832}, "large": {"instructions": 1410925, "cycles": 607926}, "ranks": {"instructions": 24554510, "cycles": 15132982}, "none": {"instructions": 617412, "cycles": 191670}, "field": {"instructions": 8887420, "cycles": 3108065}, "found": {"instructions": 14037493, "cycles": 10277104}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27123411, "cycles": 11827373}, "in_credit": {"instructions": 3514168, "cycles": 1595207}, "large": {"instructions": 1412790, "cycles": 590883}, "ranks": {"instructions": 24458149, "cycles": 14733028}, "none": {"instructions": 617338, "cycles": 189544}, "field": {"instructions": 8917567, "cycles": 3100659}, "found": {"instructions": 14068878, "cycles": 10323783}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27251835, "cycles": 11892299}, "in_credit": {"instructions": 3543022, "cycles": 1628013}, "large": {"instructions": 1410973, "cycles": 596939}, "ranks": {"instructions": 24526658, "cycles": 15060621}, "none": {"instructions": 617412, "cycles": 188384}, "field": {"instructions": 8887523, "cycles": 3105043}, "found": {"instructions": 14037773, "cycles": 10698227}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27150298, "cycles": 11790298}, "in_credit": {"instructions": 3520194, "cycles": 1582251}, "large": {"instructions": 1412854, "cycles": 589572}, "ranks": {"instructions": 24473068, "cycles": 14908265}, "none": {"instructions": 617338, "cycles": 189895}, "field": {"instructions": 8917669, "cycles": 3105534}, "found": {"instructions": 14070879, "cycles": 10553232}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27188950, "cycles": 12434083}, "in_credit": {"instructions": 3528940, "cycles": 1628962}, "large": {"instructions": 1411005, "cycles": 593522}, "ranks": {"instructions": 24493212, "cycles": 15096704}, "none": {"instructions": 617412, "cycles": 194288}, "field": {"instructions": 8887420, "cycles": 3112893}, "found": {"instructions": 14040078, "cycles": 10241171}}
+{"serve": "pure-serve", "per_branch": {"instructions": 27374146, "cycles": 11970829}, "in_credit": {"instructions": 3570384, "cycles": 1638751}, "large": {"instructions": 1412822, "cycles": 591408}, "ranks": {"instructions": 24596026, "cycles": 14897005}, "none": {"instructions": 617338, "cycles": 189957}, "field": {"instructions": 8917669, "cycles": 3102074}, "found": {"instructions": 14069079, "cycles": 10362448}}
diff --git a/research/experiments/041-batches/2026-10-18-counts.jsonl b/research/experiments/041-batches/2026-10-18-counts.jsonl
index 45e7776..54ecee2 100644
--- a/research/experiments/041-batches/2026-10-18-counts.jsonl
+++ b/research/experiments/041-batches/2026-10-18-counts.jsonl
@@ -158,3 +158,13 @@
{"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}
+{"version":"b40","way":"batches","work":"roots","instructions":157244381,"cycles":110023886}
+{"version":"b40","way":"rounds","work":"roots","instructions":2715503173,"cycles":1021976018}
+{"version":"b40","way":"batches","work":"knapsack","instructions":348029695,"cycles":105176316}
+{"version":"b40","way":"rounds","work":"knapsack","instructions":22162560925,"cycles":7863801511}
+{"version":"b40","way":"batches","work":"join","instructions":1783718594,"cycles":1035974654}
+{"version":"b40","way":"rounds","work":"join","instructions":7723675759,"cycles":3511172064}
+{"version":"b40","way":"batches","work":"matmul","instructions":702228931,"cycles":227222897}
+{"version":"b40","way":"rounds","work":"matmul","instructions":21186408621,"cycles":7035599078}
+{"version":"b40","way":"batches","work":"bfs","instructions":3341460802,"cycles":1651233853}
+{"version":"b40","way":"rounds","work":"bfs","instructions":4709146257,"cycles":2085067007}
diff --git a/serve/deliver.c b/serve/deliver.c
index f239ccd..139e21e 100644
--- a/serve/deliver.c
+++ b/serve/deliver.c
@@ -56,7 +56,7 @@ struct PureWire {
int answer_input; /* the input answers become events of, or -1 */
Answer *answers; /* made, for the owner to take in order */
size_t answer_head, answer_count, answer_capacity;
- uint32_t placed[2 * PURE_MODULE_FIELDS];
+ uint32_t placed[2 * PURE_MESSAGE_FIELDS];
/* Sealed sends: to peer's key, authenticated by the node's own key when
* it has one (its public half is from), in one session made at the first
* seal (and again after one fails); enc opens it. */
diff --git a/serve/message-json.h b/serve/message-json.h
index 45fae92..d59e511 100644
--- a/serve/message-json.h
+++ b/serve/message-json.h
@@ -13,7 +13,7 @@ typedef struct {
unsigned char *table; /* for each field, its start and size (u32le each) */
const bool *given; /* fields given elsewhere (a request's query), or NULL */
bool lenient; /* a member naming no field is skipped, value and all (an API's answer) */
- bool seen[PURE_MODULE_FIELDS];
+ bool seen[PURE_MESSAGE_FIELDS];
/* A rows field's array of objects is encoded in the stored form of its
* list into rows (grown with malloc: the caller frees it), which the
* caller places from rows_base on in the payload, as the table says. A
diff --git a/serve/serve.c b/serve/serve.c
index 2dca1cb..1f3a326 100644
--- a/serve/serve.c
+++ b/serve/serve.c
@@ -106,7 +106,10 @@ static struct RetiredTokens { Tokens *tokens; struct RetiredTokens *next; } *ret
* each by the key it seals with (--sender NAME=KEY). */
static PureSealKey *seal_key;
static bool sealed_only;
-static struct Sender { char name[PURE_SOURCE + 1]; unsigned char key[PURE_SEAL_KEY]; } senders[PURE_MODULE_INPUTS];
+/* The sends a host wires for an app (and their seals and replies), and the senders it knows by name, at most: a host's
+ * own count, not a module's (027's 4f-3a). */
+enum { OUTLETS = 32 };
+static struct Sender { char name[PURE_SOURCE + 1]; unsigned char key[PURE_SEAL_KEY]; } senders[OUTLETS];
static unsigned sender_count;
static size_t memory_budget; /* --memory: the app's budget of what its states hold (0: none) */
static uint64_t work_budget; /* --work: what an event or a view's evaluation may run (0: none) */
@@ -148,9 +151,9 @@ typedef struct Connection {
* Its ingress is [u32le payload size][payload]; with JSON it is [u32le
* payload size | PLACED][u32le start and size of each field][payload: the
* prefix, then the JSON as it came], its strings read where they are. */
- unsigned char fields[8 * PURE_MODULE_FIELDS];
+ unsigned char fields[8 * PURE_MESSAGE_FIELDS];
size_t field_bytes;
- bool given[PURE_MODULE_FIELDS], json;
+ bool given[PURE_MESSAGE_FIELDS], json;
uint32_t input;
unsigned char *ingress;
/* Its request's token (Authorization: Bearer), and the digest of the
@@ -230,7 +233,8 @@ typedef struct {
bool wrote; /* the last round saved an event or an edit */
uint64_t save_waited; /* ns the last save waited for the device (a sync), not working */
bool reads_first; /* the owner's: a round's reads before its writes */
- uint64_t next[PURE_MODULE_INPUTS]; /* when each timer ticks next (monotonic clock, ms; 0: not yet begun) */
+ uint64_t *next; /* when each input's timer ticks next (monotonic clock, ms; 0: not yet begun), timers of them */
+ uint32_t timers;
uint64_t ticks;
uint64_t memory_refused; /* events refused for the app's memory budget */
bool answers_wait; /* an answer the app had no room for: tried again after ANSWER_WAIT_MS */
@@ -241,25 +245,25 @@ typedef struct {
const PureModule *published_module;
struct PureSurfaceHost *surface; /* told of each snapshot published */
/* The owner's: outputs wired to HTTP receivers, and how far each was trimmed. */
- PureWire *wires[PURE_MODULE_INPUTS];
+ PureWire *wires[OUTLETS];
unsigned wire_count;
- uint64_t trimmed[PURE_MODULE_INPUTS], answers;
- bool held[PURE_MODULE_INPUTS]; /* an answer failed to apply: the wire's sends stay until a restart */
+ uint64_t trimmed[OUTLETS], answers;
+ bool held[OUTLETS]; /* an answer failed to apply: the wire's sends stay until a restart */
/* The owner's: outputs linked to Pure receivers (pure://), which output
* each is, and how far each was trimmed. */
- PureLink *links[PURE_MODULE_INPUTS];
+ PureLink *links[OUTLETS];
unsigned link_count;
- uint32_t link_output[PURE_MODULE_INPUTS];
- uint64_t link_trimmed[PURE_MODULE_INPUTS];
+ uint32_t link_output[OUTLETS];
+ uint64_t link_trimmed[OUTLETS];
/* The owner's: outputs handed to other apps of the host (send=OUTPUT=APP/INPUT), and how far each was trimmed. */
- struct Local *locals[PURE_MODULE_INPUTS];
+ struct Local *locals[OUTLETS];
unsigned local_count;
- uint64_t local_trimmed[PURE_MODULE_INPUTS], handed;
+ uint64_t local_trimmed[OUTLETS], handed;
/* Each outlet's place in the outbox, wires first, then links, then
* locals: its sends before it are queued (feed_outlets); and the app's
* sends_gone when they were last moved back. How often an outlet's
* window was full. */
- size_t outlet_from[3 * PURE_MODULE_INPUTS];
+ size_t outlet_from[3 * OUTLETS];
uint64_t gone_seen, windows_full;
struct Writer *writer; /* the thread writing the app's checkpoints, once one was due */
uint64_t checkpoints; /* images it wrote */
@@ -427,7 +431,7 @@ struct App {
char why[256];
unsigned home; /* the connection thread its connections move to (a host of apps) */
/* Its wiring in --apps (send=, seal=, reply=), as --send, --seal and --reply give the one app's. */
- const char *sends[PURE_MODULE_INPUTS], *seals[PURE_MODULE_INPUTS], *replies[PURE_MODULE_INPUTS];
+ const char *sends[OUTLETS], *seals[OUTLETS], *replies[OUTLETS];
unsigned send_count, seal_count, reply_count;
Counts past; /* its runs' before the present one */
Owner owner;
@@ -1463,8 +1467,8 @@ static unsigned char *arguments_payload(const PureModule *m, int input, const ch
size_t length = given ? strlen(given) : 0;
char *copy = malloc(length + 1);
unsigned char *payload = malloc(shape->prefix + length + 1);
- const char *values[PURE_MODULE_FIELDS] = {0};
- long lengths[PURE_MODULE_FIELDS] = {0};
+ const char *values[PURE_MESSAGE_FIELDS] = {0};
+ long lengths[PURE_MESSAGE_FIELDS] = {0};
bool ok = copy && payload;
if (ok) {
memcpy(copy, given ? given : "", length + 1);
@@ -1565,10 +1569,14 @@ static PureResult show_given(PureApp *app, const Route *route, size_t available,
size_t rows = pure_app_rows(app, view), from = route->from < rows ? route->from : rows;
if (pure_app_view_fault(app) == PURE_FAULT_WORK) status = PURE_LIMIT, rows = from = 0; /* its filter's */
size_t end = from + (route->count < rows - from ? route->count : rows - from);
- /* Each column's key, ,"name": (the first row's first without the comma), made once. */
- struct { uint32_t view; size_t length; char key[PURE_MODULE_NAME + 4]; } columns[PURE_MODULE_VIEWS];
+ /* Each column's key, ,"name": (the first row's first without the comma), made once; a few on the stack. */
+ typedef struct { uint32_t view; size_t length; char key[PURE_MODULE_NAME + 4]; } Column;
+ Column few[64], *columns = few;
unsigned count = 0;
- for (uint32_t column = 0; column < m->views; ++column)
+ for (uint32_t column = 0; column < m->views; ++column) count += m->view_table[column] == view + 1;
+ if (count > 64 && !(columns = malloc(count * sizeof(*columns)))) o.failed = true, count = 0;
+ count = 0;
+ for (uint32_t column = 0; columns && column < m->views; ++column)
if (m->view_table[column] == view + 1) {
columns[count].view = column;
columns[count].length = (size_t)snprintf(columns[count].key, sizeof(columns[count].key), ",\"%s\":",
@@ -1613,6 +1621,7 @@ static PureResult show_given(PureApp *app, const Route *route, size_t available,
put_bytes(&o, "}", 1);
}
put_bytes(&o, "]}", 2);
+ if (columns != few) free(columns);
}
if (status != PURE_OK && pure_app_view_fault(app) == PURE_FAULT_WORK) { /* past the work budget: 422 work */
answer->fault = PURE_FAULT_WORK;
@@ -1692,7 +1701,7 @@ static PureResult apply_input(PureApp *app, const Route *route, const PureEffect
if (m->input[i].binding == PURE_BIND_RELEASE && !strcmp(m->input_keys[i], m->input_keys[route->target]))
status = pure_app_message(app, i, (PureSpan){0});
} else {
- uint32_t placed[2 * PURE_MODULE_FIELDS];
+ uint32_t placed[2 * PURE_MESSAGE_FIELDS];
for (size_t i = 0; table && i < 2 * (size_t)shape->field_count; ++i)
placed[i] = get_u32(effect->value.data + lead + 4 * i);
if (header & DELIVERED) {
@@ -2164,6 +2173,12 @@ static long run_timers(Executor *ex) {
bool changes = m->states > 0;
for (uint32_t i = 0; i < m->inputs && !changes; ++i) changes = m->input[i].kind == PURE_KIND_OUTPUT;
if (!changes) return -1;
+ if (ex->timers < m->inputs) { /* as many as the module's inputs (its module may change) */
+ uint64_t *grown = realloc(ex->next, m->inputs * sizeof(*grown));
+ if (!grown) return -1; /* none ticks until there is room */
+ memset(grown + ex->timers, 0, (m->inputs - ex->timers) * sizeof(*grown));
+ ex->next = grown, ex->timers = m->inputs;
+ }
uint64_t now = now_ms(), soonest = UINT64_MAX;
bool ticked = false;
for (uint32_t i = 0; i < m->inputs; ++i) {
@@ -3479,6 +3494,8 @@ static void stop_executor(Executor *ex) {
pure_app_stop(&ex->app);
pure_store_close(ex->store);
free(ex->applied);
+ free(ex->next);
+ ex->next = NULL, ex->timers = 0;
}
/* One connection thread: its own listener on the shared port, epoll set,
@@ -4000,7 +4017,7 @@ typedef struct {
uint64_t digest, tokens_digest; /* of the module's file, and the tokens', when they were read (0: they could not be) */
size_t memory, queue;
uint64_t work;
- char *sends[PURE_MODULE_INPUTS], *seals[PURE_MODULE_INPUTS], *replies[PURE_MODULE_INPUTS];
+ char *sends[OUTLETS], *seals[OUTLETS], *replies[OUTLETS];
unsigned send_count, seal_count, reply_count;
bool kept; /* its app runs as the line says: a reading leaves it be */
bool tokens_only; /* and so but for its tokens: they are put in place, and it runs on */
@@ -4044,7 +4061,7 @@ static bool parse_apps(const char *path, Line **out, unsigned *count, char *erro
bool ok = true;
while (ok && fgets(text, sizeof(text), file)) {
++at;
- char *hash = strchr(text, '#'), *words[3 + 3 + 3 * PURE_MODULE_INPUTS], *rest = NULL;
+ char *hash = strchr(text, '#'), *words[3 + 3 + 3 * OUTLETS], *rest = NULL;
if (hash) *hash = 0;
unsigned found = 0;
for (char *word = strtok_r(text, " \t\r\n", &rest); word && found < sizeof(words) / sizeof(*words);
@@ -4082,11 +4099,11 @@ static bool parse_apps(const char *path, Line **out, unsigned *count, char *erro
if (value && !strcmp(words[w], "memory") && number(value, SIZE_MAX, &number_given)) line->memory = (size_t)number_given;
else if (value && !strcmp(words[w], "work") && number(value, UINT64_MAX, &number_given)) line->work = number_given;
else if (value && !strcmp(words[w], "queue") && number(value, SIZE_MAX, &number_given)) line->queue = (size_t)number_given;
- else if (value && !strcmp(words[w], "send") && line->send_count < PURE_MODULE_INPUTS)
+ else if (value && !strcmp(words[w], "send") && line->send_count < OUTLETS)
ok = (line->sends[line->send_count++] = strdup(value)) != NULL;
- else if (value && !strcmp(words[w], "seal") && line->seal_count < PURE_MODULE_INPUTS)
+ else if (value && !strcmp(words[w], "seal") && line->seal_count < OUTLETS)
ok = (line->seals[line->seal_count++] = strdup(value)) != NULL;
- else if (value && !strcmp(words[w], "reply") && line->reply_count < PURE_MODULE_INPUTS)
+ else if (value && !strcmp(words[w], "reply") && line->reply_count < OUTLETS)
ok = (line->replies[line->reply_count++] = strdup(value)) != NULL;
else if (value && !strcmp(words[w], "tokens") && !line->tokens && *value)
ok = (line->tokens = strdup(value)) != NULL;
@@ -4483,7 +4500,7 @@ static bool change_app(App *app) {
return false;
}
ex->module = app->module;
- memset(ex->next, 0, sizeof(ex->next));
+ if (ex->next) memset(ex->next, 0, ex->timers * sizeof(*ex->next));
memset(ex->trimmed, 0, sizeof(ex->trimmed));
memset(ex->held, 0, sizeof(ex->held));
memset(ex->link_output, 0, sizeof(ex->link_output));
@@ -4663,8 +4680,8 @@ int main(int argc, char **argv) {
options = i;
break;
}
- const char *surface_path = NULL, *module_path = NULL, *store = NULL, *name = NULL, *sends[PURE_MODULE_INPUTS], *replies[PURE_MODULE_INPUTS];
- const char *seals[PURE_MODULE_INPUTS], *named[PURE_MODULE_INPUTS], *key_path = NULL, *senders_path = NULL, *apps_path = NULL;
+ const char *surface_path = NULL, *module_path = NULL, *store = NULL, *name = NULL, *sends[OUTLETS], *replies[OUTLETS];
+ const char *seals[OUTLETS], *named[OUTLETS], *key_path = NULL, *senders_path = NULL, *apps_path = NULL;
const char *tokens_path = NULL;
if (argc == 3 && !strcmp(argv[1], "--make-token")) return make_token(argv[2]);
unsigned send_count = 0, reply_count = 0, seal_count = 0, named_count = 0;
@@ -4680,13 +4697,13 @@ int main(int argc, char **argv) {
else if (!strcmp(argv[i], "--tokens")) tokens_path = argv[i + 1];
else if (!strcmp(argv[i], "--surface")) surface_path = argv[i + 1];
else if (!strcmp(argv[i], "--key")) key_path = argv[i + 1];
- else if (!strcmp(argv[i], "--seal") && seal_count < PURE_MODULE_INPUTS) seals[seal_count++] = argv[i + 1];
+ else if (!strcmp(argv[i], "--seal") && seal_count < OUTLETS) seals[seal_count++] = argv[i + 1];
else if (!strcmp(argv[i], "--store")) store = argv[i + 1];
- else if (!strcmp(argv[i], "--send") && send_count < PURE_MODULE_INPUTS) sends[send_count++] = argv[i + 1];
+ else if (!strcmp(argv[i], "--send") && send_count < OUTLETS) sends[send_count++] = argv[i + 1];
else if (!strcmp(argv[i], "--name")) name = argv[i + 1];
else if (!strcmp(argv[i], "--send-ca")) pure_wire_trust(argv[i + 1]);
- else if (!strcmp(argv[i], "--reply") && reply_count < PURE_MODULE_INPUTS) replies[reply_count++] = argv[i + 1];
- else if (!strcmp(argv[i], "--sender") && named_count < PURE_MODULE_INPUTS) named[named_count++] = argv[i + 1];
+ else if (!strcmp(argv[i], "--reply") && reply_count < OUTLETS) replies[reply_count++] = argv[i + 1];
+ else if (!strcmp(argv[i], "--sender") && named_count < OUTLETS) named[named_count++] = argv[i + 1];
else if (!strcmp(argv[i], "--port") && number(argv[i + 1], 65535, &value)) port = (unsigned)value;
else if (!strcmp(argv[i], "--link-port") && number(argv[i + 1], 65535, &value) && value) link_port = (unsigned)value;
else if (!strcmp(argv[i], "--senders")) senders_path = argv[i + 1];