diff options
| author | eunhhu <[email protected]> | 2026-10-13 07:40:30 +0100 |
|---|---|---|
| committer | eunhhu <[email protected]> | 2026-10-13 07:40:30 +0100 |
| commit | 8481b12fb794690cce0d9c54032ffba3de1fe31d (patch) | |
| tree | 55a7f3c74b8521b84262acb4cf444ddda9ef5070 /core/module.c | |
| parent | d83ccb2e3b3d276577c61b30a4a323909a654690 (diff) | |
| download | pure-8481b12fb794690cce0d9c54032ffba3de1fe31d.tar.gz pure-8481b12fb794690cce0d9c54032ffba3de1fe31d.zip | |
Module format and executor: 027's stage 2b, lists in records: a record field of type LIST (its items' record in its default) holds a list the event made, its number an i64 in the cell (0 an empty one), so lists keep their inside, batches and the JIT as they were (the JIT's append fast path leaves such records alone); LIST_VALUE (a copy of an event's list, nothing made for an empty one), LIST_BIND (a register, an event's list flagged PURE_LIST_BOUND, read and walked, never changed by code), APPEND_IN (an item into the list in a field of an event's list's item, copied first when another value shares it or a register is bound to it), sharing counted where cells take them; such records only in event lists (no stored, transient or message list, no key); the event's lists go as each event or view's body begins, registers given back their own; record field names may be paths (at.x: records in records, flattened by aup); NUM_PARSE (a text written as a JSON number, its nearest double, core's pure_parse_number); unit tests (values copied, shared then appended, a bound register seeing what it was bound to, three events alike, 6 refused; under ASan and UBSan too, which found the record kinds' missing LIST)
Diffstat (limited to 'core/module.c')
| -rw-r--r-- | core/module.c | 232 |
1 files changed, 219 insertions, 13 deletions
diff --git a/core/module.c b/core/module.c index efa6547..db0703c 100644 --- a/core/module.c +++ b/core/module.c @@ -17,7 +17,7 @@ enum { STATE_BYTES = 16, COLLECTION_BYTES = 12, INPUT_BYTES = 16, FIELD_BYTES = * the named local holds, T_STATE for the type of the number, bool or i64 * state named. */ enum { T_NUM = 1, T_BYTES = 2, T_TEXT = 3, T_CURSOR = 4, T_FIELD = 5, T_INDEX = 6, T_ITEM = 7, T_KEY = 8, T_LOCAL = 9, - T_BOOL = 10, T_STATE = 11, T_I64 = 12 }; + T_BOOL = 10, T_STATE = 11, T_I64 = 12, T_LIST = 13 }; /* T_LIST: a list value, its owner the record of its items */ enum { FROM_PAYLOAD, FROM_DOCUMENT, FROM_COPY, FROM_CONSTANT, FROM_TEXT, FROM_ARENA, FROM_ARGUMENTS, FROM_HELD }; /* Where an opcode may appear. */ /* IN_ARGUMENTS: fields read where nothing is done with them (a view's arguments, a message's fields in a fold). */ @@ -144,6 +144,11 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi [PURE_OP_BYTE] = {1, {T_I64}, T_BYTES, READ, A_NONE, O_ANY}, [PURE_OP_BYTES_LOAD] = {0, {0}, T_BYTES, IN_LOCALS, A_HELD, O_ANY}, [PURE_OP_BYTES_STORE] = {1, {T_BYTES}, 0, IN_HANDLER | IN_COMPUTE, A_HELD, O_ANY}, + [PURE_OP_LIST_VALUE] = {0, {0}, T_LIST, IN_HANDLER | IN_COMPUTE, STATE(LIST), O_ANY}, + [PURE_OP_LIST_BIND] = {1, {T_LIST}, 0, IN_HANDLER | IN_COMPUTE, STATE(LIST), O_ANY}, + /* APPEND_IN also takes as many values as the record of the list in the field has fields. */ + [PURE_OP_APPEND_IN] = {1, {T_INDEX}, 0, IN_HANDLER | IN_COMPUTE, A_ITEM, O_LIST}, + [PURE_OP_NUM_PARSE] = {1, {T_TEXT}, T_NUM, READ, A_NONE, O_ANY}, }; #undef NUMS #undef COMPARE @@ -340,10 +345,44 @@ static bool state_of(const PureModule *m, uint32_t state, uint8_t type) { static unsigned char field_type(const PureModule *m, uint32_t list, uint32_t field) { static const unsigned char types[] = {[PURE_RECORD_NUMBER] = T_NUM, [PURE_RECORD_TEXT] = T_TEXT, - [PURE_RECORD_BOOL] = T_BOOL, [PURE_RECORD_I64] = T_I64}; + [PURE_RECORD_BOOL] = T_BOOL, [PURE_RECORD_I64] = T_I64, [PURE_RECORD_LIST] = T_LIST}; return types[m->record_field_type[m->record[m->state_ref[list]].first + field]]; } +/* The record of the lists a LIST field of a record holds (field: the module's numbering), or UINT32_MAX. */ +static uint32_t nested_record(const PureModule *m, uint32_t field) { + return m->record_field_type[field] == PURE_RECORD_LIST ? (uint32_t)m->record_field_default[field].i64 : UINT32_MAX; +} + +/* An event's list, and one bound by LIST_BIND (a register: never changed by code). */ +static bool event_list_state(const PureModule *m, uint32_t list) { + return list < m->states && m->state_type[list] == PURE_STATE_LIST && m->state_flags[list] & PURE_LIST_ROWS >> 8; +} +static bool bound_list(const PureModule *m, uint32_t list) { + return event_list_state(m, list) && m->state_flags[list] & PURE_LIST_BOUND >> 8; +} + +/* The stack type of a record field (the module's numbering). */ +static unsigned char record_type(const PureModule *m, uint32_t field) { + static const unsigned char types[] = {[PURE_RECORD_NUMBER] = T_NUM, [PURE_RECORD_TEXT] = T_TEXT, + [PURE_RECORD_BOOL] = T_BOOL, [PURE_RECORD_I64] = T_I64, [PURE_RECORD_LIST] = T_LIST}; + return types[m->record_field_type[field]]; +} + +/* The record of the list value an instruction takes as its operand k (bottom 0). */ +static unsigned list_wanted(const PureModule *m, const PureOp *op, unsigned k) { + uint32_t arg = op->arg; + switch (op->op) { + case PURE_OP_LIST_BIND: return m->state_ref[arg]; + case PURE_OP_LIST_APPEND: return nested_record(m, m->record[m->state_ref[arg]].first + k); + case PURE_OP_LIST_INSERT: return nested_record(m, m->record[m->state_ref[arg]].first + k - 1); /* the index first */ + case PURE_OP_ITEM_SET: return nested_record(m, m->record[m->state_ref[list_of(arg)]].first + item_field(arg)); + case PURE_OP_APPEND_IN: + return nested_record(m, m->record[nested_record(m, m->record[m->state_ref[list_of(arg)]].first + item_field(arg))].first + k - 1); + default: return UINT32_MAX; + } +} + /* The stack type of a message field's value (a u32 is a number). */ static unsigned char message_type(uint8_t type) { return type == PURE_FIELD_TEXT ? T_TEXT : type == PURE_FIELD_BYTES ? T_BYTES : type == PURE_FIELD_BOOL ? T_BOOL : @@ -517,6 +556,16 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i REFUSE("change of a keyed list's key"); if (op->op == PURE_OP_BYTES_OF && (m->record[m->state_ref[arg]].count != 1 || field_type(m, arg, 0) != T_I64)) REFUSE("bytes of a list not of one i64"); + /* Lists in records (027's 2b): a register is read alone; values are of an event's lists; APPEND_IN appends to a + * list in a field of an event's list. */ + if (((op->op == PURE_OP_LIST_APPEND || op->op == PURE_OP_LIST_INSERT || op->op == PURE_OP_LIST_REMOVE) && bound_list(m, arg)) || + ((op->op == PURE_OP_ITEM_SET || op->op == PURE_OP_APPEND_IN) && bound_list(m, list_of(arg)))) + REFUSE("change of a bound list"); + if (op->op == PURE_OP_LIST_VALUE && !event_list_state(m, arg)) REFUSE("value of a list not an event's"); + if (op->op == PURE_OP_LIST_BIND && !bound_list(m, arg)) REFUSE("list bound to a list not bound"); + if (op->op == PURE_OP_APPEND_IN && + (!event_list_state(m, list_of(arg)) || field_type(m, list_of(arg), item_field(arg)) != T_LIST)) + REFUSE("append into a field not an event's list's list"); /* A view's body changes an event's lists alone: its states are a snapshot's. */ if (body && (op->op == PURE_OP_LIST_APPEND || op->op == PURE_OP_LIST_INSERT || op->op == PURE_OP_LIST_REMOVE || op->op == PURE_OP_ITEM_SET) && @@ -575,6 +624,10 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i pops += record->count; } else if (op->op == PURE_OP_ITEM_SET) { need[pops++] = field_type(m, list_of(arg), item_field(arg)); + } else if (op->op == PURE_OP_APPEND_IN) { /* an item of the list in the field */ + const PureRecord *record = &m->record[nested_record(m, m->record[m->state_ref[list_of(arg)]].first + item_field(arg))]; + for (unsigned f = 0; f < record->count; ++f) need[pops + f] = record_type(m, record->first + f); + pops += record->count; } else if (need[0] == T_KEY) { need[0] = field_type(m, arg, m->state_key[arg] - 1u); } else if (need[0] == T_LOCAL) { @@ -606,6 +659,8 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i height -= (int)pops; for (unsigned k = 0; k < pops; ++k) if (types[height + (int)k] != need[k]) REFUSE("operand type"); + for (unsigned k = 0; k < pops; ++k) /* a list value goes where a list of its record goes */ + if (need[k] == T_LIST && owners[height + (int)k] != list_wanted(m, op, k)) REFUSE("list of another record"); unsigned owner = of == O_ARG ? arg : of == O_REF ? m->state_ref[arg] : of == O_LIST ? list_of(arg) : 0; if (kind == A_LOCAL && arg >> 8 & 0xFF) { /* an index local belongs to its list */ owner = arg >> 16; @@ -634,6 +689,9 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i if (out) { if (height == PURE_MODULE_STACK) REFUSE("stack overflow"); owners[height] = (unsigned char)(out != T_CURSOR && out != T_INDEX ? 0 : of == O_OPERAND ? owners[height] : owner); + if (out == T_LIST) /* the record of its items */ + owners[height] = (unsigned char)(op->op == PURE_OP_LIST_VALUE ? m->state_ref[arg] : + nested_record(m, m->record[m->state_ref[list_of(arg)]].first + item_field(arg))); types[height++] = out; } if (kind == A_TARGET) { @@ -1116,11 +1174,11 @@ PURE_COLD static unsigned arity(uint8_t op) { switch (op) { case PURE_OP_CONST: case PURE_OP_LOAD: case PURE_OP_INDEX_LOAD: case PURE_OP_ROW: case PURE_OP_OUTER_ROW: case PURE_OP_LIST_COUNT: case PURE_OP_INDEX_FIRST: case PURE_OP_INDEX_LAST: case PURE_OP_FIELD: case PURE_OP_LOCAL_LOAD: - case PURE_OP_BYTES_OF: case PURE_OP_BYTES_LOAD: + case PURE_OP_BYTES_OF: case PURE_OP_BYTES_LOAD: case PURE_OP_LIST_VALUE: return 0; case PURE_OP_ITEM_GET: case PURE_OP_LIST_FIND: case PURE_OP_LIST_HAS: case PURE_OP_NEG: case PURE_OP_NOT: case PURE_OP_FLOOR: case PURE_OP_INDEX_NUM: case PURE_OP_I64_NEG: case PURE_OP_I64_FROM: case PURE_OP_NUM_FROM: - case PURE_OP_INDEX_AT: case PURE_OP_BYTE: + case PURE_OP_INDEX_AT: case PURE_OP_BYTE: case PURE_OP_NUM_PARSE: return 1; case PURE_OP_CLAMP: case PURE_OP_BYTES_SLICE: return 3; default: return 2; @@ -1132,7 +1190,7 @@ PURE_COLD static int list_read(const PureOp *op) { switch (op->op) { case PURE_OP_ITEM_GET: return (int)list_of(op->arg); case PURE_OP_LIST_COUNT: case PURE_OP_INDEX_FIRST: case PURE_OP_INDEX_LAST: case PURE_OP_LIST_FIND: - case PURE_OP_LIST_HAS: case PURE_OP_ROW: case PURE_OP_BYTES_OF: case PURE_OP_INDEX_AT: + case PURE_OP_LIST_HAS: case PURE_OP_ROW: case PURE_OP_BYTES_OF: case PURE_OP_INDEX_AT: case PURE_OP_LIST_VALUE: return (int)op->arg; default: return -1; } @@ -1331,8 +1389,10 @@ PURE_COLD static bool bounds_fixed(const PureModule *m, const PureRange *range, states |= (uint64_t)1 << (op->arg & 63); break; case PURE_OP_LOCAL_STORE: case PURE_OP_LOOP: locals |= (uint64_t)1 << (op->arg & 63); break; - case PURE_OP_LIST_APPEND: case PURE_OP_LIST_INSERT: case PURE_OP_LIST_REMOVE: lists |= (uint64_t)1 << (op->arg & 63); break; - case PURE_OP_ITEM_SET: lists |= (uint64_t)1 << (list_of(op->arg) & 63); break; + case PURE_OP_LIST_APPEND: case PURE_OP_LIST_INSERT: case PURE_OP_LIST_REMOVE: case PURE_OP_LIST_BIND: + lists |= (uint64_t)1 << (op->arg & 63); + break; + case PURE_OP_ITEM_SET: case PURE_OP_APPEND_IN: lists |= (uint64_t)1 << (list_of(op->arg) & 63); break; default: break; } } @@ -1504,7 +1564,9 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule uint32_t flags = value ? 0 : read32(at + 12); bool cursor = out->state_type[i] == PURE_STATE_CURSOR, list = out->state_type[i] == PURE_STATE_LIST; bool rows = list && flags & PURE_LIST_ROWS; /* an event's list, keyed or not */ - uint32_t key = list ? flags & ~(uint32_t)PURE_LIST_ROWS : 0; + 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); 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]) || @@ -1520,7 +1582,7 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule if ((flags && !(cursor && flags == PURE_CURSOR_STAYS) && !(list && key <= PURE_RECORD_FIELDS)) || read32(at + 8) > UINT16_MAX) REFUSE("state reference", i); out->state_ref[i] = (uint16_t)read32(at + 8); - out->state_flags[i] = cursor ? (uint8_t)flags : rows ? PURE_LIST_ROWS >> 8 : 0; + out->state_flags[i] = cursor ? (uint8_t)flags : (uint8_t)((rows ? PURE_LIST_ROWS >> 8 : 0) | (bound ? PURE_LIST_BOUND >> 8 : 0)); out->state_key[i] = (uint8_t)key; } } @@ -1618,18 +1680,21 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule const unsigned char *entry = fields_at + (size_t)f * RECORD_FIELD_BYTES; uint32_t type = read32(entry + 4); /* PureRecordType */ out->record_field_type[f] = (uint8_t)type; - if (!name_at(text, strings, read32(entry), NAME_IDENTIFIER, out->record_field_names[f]) || type > PURE_RECORD_I64) + if (!name_at(text, strings, read32(entry), NAME_PATH, out->record_field_names[f]) || type > PURE_RECORD_LIST) REFUSE("record field", f); for (uint32_t k = record->first; k < f; ++k) if (!strcmp(out->record_field_names[k], out->record_field_names[f])) REFUSE("duplicate record field", f); /* A default: a text's constant, an i64's 8 bytes, a number (a bool's +0 or 1). */ if (type == PURE_RECORD_TEXT) out->record_field_default[f].num = read32(entry + 8); - else if (type == PURE_RECORD_I64) out->record_field_default[f].i64 = (int64_t)(read32(entry + 8) | (uint64_t)read32(entry + 12) << 32); + else if (type == PURE_RECORD_I64 || type == PURE_RECORD_LIST) /* a LIST's: the record of its items */ + out->record_field_default[f].i64 = (int64_t)(read32(entry + 8) | (uint64_t)read32(entry + 12) << 32); else out->record_field_default[f].num = read_double(entry + 8); double given = out->record_field_default[f].num; if (type == PURE_RECORD_TEXT ? read32(entry + 12) || read32(entry + 8) >= out->constants + : type == PURE_RECORD_LIST ? (uint64_t)out->record_field_default[f].i64 >= out->records : type != PURE_RECORD_I64 && (!isfinite(given) || (type == PURE_RECORD_BOOL && !canonical_bool(given)))) REFUSE("record field default", f); + if (type == PURE_RECORD_LIST) out->record_nested[i] = true; } next_record_field += record->count; } @@ -1644,6 +1709,19 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule out->record_field_type[out->record[out->state_ref[i]].first + out->state_key[i] - 1u] == PURE_RECORD_BOOL))) || (out->state_type[i] == PURE_STATE_INDEX && !state_of(out, out->state_ref[i], PURE_STATE_LIST))) REFUSE("state reference", i); + /* A list in a record is an event's alone (027's stage 2b): no stored, transient or message's list holds one, and no + * list is keyed by one; a register is no message's rows. */ + for (uint32_t i = 0; i < out->states; ++i) { + if (out->state_type[i] != PURE_STATE_LIST) continue; + bool nested = out->record_nested[out->state_ref[i]]; + if ((nested && !(out->state_flags[i] & PURE_LIST_ROWS >> 8)) || + (out->state_key[i] && out->record_field_type[out->record[out->state_ref[i]].first + out->state_key[i] - 1u] == PURE_RECORD_LIST)) + REFUSE("list in a record outside an event's list", i); + } + for (uint32_t f = 0; f < out->fields; ++f) + if (out->field[f].type == PURE_FIELD_ROWS && + (out->record_nested[out->state_ref[out->field[f].rows]] || out->state_flags[out->field[f].rows] & PURE_LIST_BOUND >> 8)) + REFUSE("rows field", f); /* 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) && @@ -2171,6 +2249,53 @@ static int hold(PureApp *app, PureArena *arena, unsigned slot, PureBytes v, bool return 0; } +/* ---- Lists in records (027's 2b): the event's lists made for LIST fields, numbered from 1 */ + +PURE_COLD static PureList *new_list(const PureModule *m, uint32_t record); + +/* The values that share each list in the LIST fields of cells (an item's, just stored). */ +static void share_cells(PureApp *app, const PureModule *m, uint32_t record, const PureCell *cells) { + const PureRecord *r = &m->record[record]; + for (unsigned f = 0; f < r->count; ++f) + if (m->record_field_type[r->first + f] == PURE_RECORD_LIST && cells[f].i64) ++app->nested.shared[cells[f].i64 - 1]; +} + +/* A new list of record's items among the event's (empty, or a copy of from's items): its number, or 0 out of memory. */ +static uint64_t nest(PureApp *app, uint32_t record, const PureList *from) { + const PureModule *m = app->module; + if (app->nested.count == app->nested.capacity) { + size_t capacity = app->nested.capacity ? app->nested.capacity * 2 : 16; + PureList **lists = realloc(app->nested.list, capacity * sizeof(*lists)); + if (lists) app->nested.list = lists; + uint32_t *shared = lists ? realloc(app->nested.shared, capacity * sizeof(*shared)) : NULL; + if (!shared) return 0; + app->nested.shared = shared; + app->nested.capacity = capacity; + } + PureList *list = new_list(m, record); + if (!list) return 0; + const PureRecord *r = &m->record[record]; + for (size_t at = 0, count = from ? pure_list_count(from) : 0; at < count; ++at) { + PureCell cells[PURE_RECORD_FIELDS]; + for (unsigned f = 0; f < r->count; ++f) cells[f] = pure_list_get(from, at, f); + if (pure_list_insert(list, at, cells, NULL) != PURE_OK) { pure_list_free(list); return 0; } + if (m->record_nested[record]) share_cells(app, m, record, cells); + } + app->nested.list[app->nested.count] = list; + app->nested.shared[app->nested.count] = 0; + return ++app->nested.count; +} + +/* 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) + if (app->unbound[i]) app->lists[i] = app->unbound[i], app->unbound[i] = NULL; +} +static void drop_nested(PureApp *app) { + for (size_t i = 0; i < app->nested.count; ++i) pure_list_free(app->nested.list[i]); + app->nested.count = 0; +} + /* NUM_TEXT's text of v in a format (the argument past its type, PureFormat): false where an integer's form meets a * number that is not a whole one within 2^53. */ static bool formatted(Slot v, uint32_t arg, char *out, size_t *length) { @@ -3365,6 +3490,7 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp if (status == PURE_INVALID) status = pure_list_insert(list, at, cells, undo_for(app, arg)); arena->used = mark; if (status != PURE_OK) REFUSED; + if (m->record_nested[m->state_ref[arg]]) share_cells(app, m, m->state_ref[arg], cells); if (m->state_indexed[arg]) move_indices(m, values, arg, pure_list_count(list), at, true); if (!grows(app, before, list_held(app, list))) { kind = PURE_FAULT_MEMORY; FAILED; } NEXT; @@ -3401,11 +3527,18 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp PureSpan span = {0}; status = op->plan & PLAN_TEXT ? contiguous(app, arena, value.bytes, &span) : PURE_OK; PureCell cell = {.i64 = value.i64, .text = (unsigned char *)span.data, .size = span.size}; + bool nested = m->record_field_type[m->record[m->state_ref[list]].first + field] == PURE_RECORD_LIST; + uint64_t was = nested && status == PURE_OK && at < pure_list_count(app->lists[list]) ? + (uint64_t)pure_list_get(app->lists[list], at, field).i64 : 0; if (status == PURE_OK) /* where its FIND found it (PLAN_ROW: a HASHED list's slot), else its position */ status = op->plan & PLAN_ROW ? pure_list_set_row(app->lists[list], at, field, cell, undo_for(app, list)) : pure_list_set(app->lists[list], at, field, cell, undo_for(app, list)); arena->used = mark; if (status != PURE_OK) REFUSED; + if (nested) { /* the list it holds now is shared once more, the one it held once less */ + if (cell.i64) ++app->nested.shared[cell.i64 - 1]; + if (was) --app->nested.shared[was - 1]; + } if (!grows(app, before, list_held(app, app->lists[list]))) { kind = PURE_FAULT_MEMORY; FAILED; } NEXT; } @@ -3628,6 +3761,68 @@ static inline __attribute__((always_inline)) int step(Frame *frame, const PureOp if (held) STOPPED(PURE_ALLOCATION); NEXT; } + case PURE_OP_NUM_PARSE: { /* a text written as a JSON number, its nearest number */ + PureSpan span; + size_t mark = arena->used; + if ((status = contiguous(app, arena, top->bytes, &span)) != PURE_OK) STOPPED(status); + double number; + bool read = pure_parse_number(span.data, span.size, &number); + arena->used = mark; + if (!read) { kind = PURE_FAULT_LIMIT; FAILED; } + top->num = number; + NEXT; + } + case PURE_OP_LIST_VALUE: { /* a copy of an event's list's items (an empty one is 0, made of nothing) */ + if (!pure_list_count(app->lists[arg])) { stack[sp++].i64 = 0; NEXT; } + uint64_t made = nest(app, m->state_ref[arg], app->lists[arg]); + if (!made) STOPPED(PURE_ALLOCATION); + if (!grows(app, (Held){0, 0}, (Held){0, pure_list_size(app->nested.list[made - 1])})) { kind = PURE_FAULT_MEMORY; FAILED; } + stack[sp++].i64 = (int64_t)made; + NEXT; + } + case PURE_OP_LIST_BIND: { /* the register reads the list (shared: no one changes it in place from now) */ + uint64_t list = (uint64_t)stack[--sp].i64; + if (!app->unbound[arg]) app->unbound[arg] = app->lists[arg]; + app->lists[arg] = list ? app->nested.list[list - 1] : app->unbound[arg]; + if (list) ++app->nested.shared[list - 1]; + NEXT; + } + case PURE_OP_APPEND_IN: { /* an item into the list in a field, made the field's own first when another shares it */ + uint32_t list = list_of(arg), field = item_field(arg), inner = nested_record(m, m->record[m->state_ref[list]].first + field); + const PureRecord *record = &m->record[inner]; + size_t base = sp - record->count, at = (size_t)stack[base - 1].num; + sp = base - 1; + if (at >= pure_list_count(app->lists[list])) { kind = PURE_FAULT_NOT_FOUND; FAILED; } + uint64_t into = (uint64_t)pure_list_get(app->lists[list], at, field).i64; + if (!into || app->nested.shared[into - 1] > 1) { + uint64_t own = nest(app, inner, into ? app->nested.list[into - 1] : NULL); + if (!own) STOPPED(PURE_ALLOCATION); + if (into) --app->nested.shared[into - 1]; + app->nested.shared[own - 1] = 1; + if ((status = pure_list_set(app->lists[list], at, field, (PureCell){.i64 = (int64_t)own}, NULL)) != PURE_OK) REFUSED; + into = own; + } + PureList *in = app->nested.list[into - 1]; + size_t mark = arena->used, total = 0; + for (unsigned f = 0; f < record->count; ++f) + if (m->record_field_type[record->first + f] == PURE_RECORD_TEXT) total += (size_t)stack[base + f].bytes.length; + if (!reserve(arena, total)) STOPPED(PURE_ALLOCATION); + PureCell cells[PURE_RECORD_FIELDS]; + for (unsigned f = 0; f < record->count; ++f) { + PureSpan span = {0}; + if (m->record_field_type[record->first + f] == PURE_RECORD_TEXT && + (status = contiguous(app, arena, stack[base + f].bytes, &span)) != PURE_OK) + STOPPED(status); + cells[f] = (PureCell){.i64 = stack[base + f].i64, .text = (unsigned char *)span.data, .size = span.size}; + } + Held before = {0, pure_list_size(in)}; + status = pure_list_insert(in, pure_list_count(in), cells, NULL); + arena->used = mark; + if (status != PURE_OK) REFUSED; + if (m->record_nested[inner]) share_cells(app, m, inner, cells); + if (!grows(app, before, (Held){0, pure_list_size(in)})) { kind = PURE_FAULT_MEMORY; FAILED; } + NEXT; + } default: break; } double b = stack[--sp].num, a = stack[sp - 1].num, value; @@ -4031,6 +4226,7 @@ void pure_snapshot_release(PureSnapshot *snapshot) { static void let_own_go(PureApp *app); 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); @@ -4050,6 +4246,9 @@ void pure_app_stop(PureApp *app) { } pure_retired_free(&app->retired); for (unsigned i = 0; i < PURE_MODULE_HELD; ++i) free(app->held[i].data); + drop_nested(app); + free(app->nested.list); + free(app->nested.shared); pure_draft_close(app->stored_values); free(app->carried); pure_draft_close(app->stored_links); @@ -4147,6 +4346,7 @@ static PureResult keep_reply(PureApp *app); /* Every list of a message's rows emptied: an event's rows are its own. */ static void clear_rows(PureApp *app) { const PureModule *m = app->module; + unbind(app); for (uint32_t i = 0; i < m->states; ++i) if (m->state_type[i] == PURE_STATE_LIST && m->state_flags[i] & PURE_LIST_ROWS >> 8) pure_list_clear(app->lists[i]); } @@ -4186,6 +4386,7 @@ static PureResult dispatch(PureApp *app, uint32_t event, Event *ev) { app->grown = app->spent = 0; app->room = app->memory > PURE_GROWTH_DEFAULT ? app->memory : PURE_GROWTH_DEFAULT; for (unsigned i = 0; m->held && i < PURE_MODULE_HELD; ++i) app->held[i].size = 0; + if (app->nested.count) 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)); @@ -4534,6 +4735,8 @@ static PureResult evaluate(PureApp *app, uint32_t view, size_t row, Slot *out) { app->grown = app->spent = 0; app->room = app->memory > PURE_GROWTH_DEFAULT ? app->memory : PURE_GROWTH_DEFAULT; for (unsigned i = 0; m->held && i < PURE_MODULE_HELD; ++i) app->held[i].size = 0; + unbind(app); + if (app->nested.count) drop_nested(app); } PureFault fault = app->fault; uint32_t fault_pc = app->fault_pc; @@ -4810,7 +5013,7 @@ PureSnapshot *pure_save_snapshot(PureApp *app, uint32_t runtime) { static void let_own_go(PureApp *app) { for (uint32_t i = 0; i < PURE_MODULE_STATES; ++i) { pure_list_free(app->own[i]); - app->own[i] = NULL; + app->own[i] = app->unbound[i] = NULL; } } @@ -4826,7 +5029,8 @@ void pure_app_read(PureApp *reader, const PureSnapshot *snapshot) { uint8_t pure_record_kind(const PureModule *m, uint32_t field) { static const uint8_t kinds[] = {[PURE_RECORD_NUMBER] = PURE_CELL_NUMBER, [PURE_RECORD_TEXT] = PURE_CELL_TEXT, - [PURE_RECORD_BOOL] = PURE_CELL_BOOL, [PURE_RECORD_I64] = PURE_CELL_INTEGER}; + [PURE_RECORD_BOOL] = PURE_CELL_BOOL, [PURE_RECORD_I64] = PURE_CELL_INTEGER, + [PURE_RECORD_LIST] = PURE_CELL_INTEGER}; /* a list in a record: its number */ return kinds[m->record_field_type[field]]; } @@ -5223,6 +5427,8 @@ PURE_COLD PureResult pure_app_start(PureApp *app, const PureModule *module, Pure } PURE_COLD PureResult pure_app_replace(PureApp *app, const PureModule *module) { + unbind(app); /* lists are laid out again: none bound, the event's in records let go */ + drop_nested(app); pure_app_finish(app); if (app->open || app->pending_count) { PureResult saved = pure_app_save(app); /* the burst belongs to the module that made it */ |
