From 8d0901ba060165ad0622a3be09b09e3acd4a8899 Mon Sep 17 00:00:00 2001 From: eunhhu Date: Sun, 4 Oct 2026 14:43:11 +0100 Subject: Let a view compute in its body, on a reader's own lists (018 stage 3) --- core/module.c | 112 ++++++++++++++++++++++++++++++++++++++++------------- core/module.h | 9 ++++- core/test-module.c | 109 ++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 201 insertions(+), 29 deletions(-) (limited to 'core') diff --git a/core/module.c b/core/module.c index e90684e..ffcc9cb 100644 --- a/core/module.c +++ b/core/module.c @@ -21,8 +21,9 @@ enum { FROM_PAYLOAD, FROM_DOCUMENT, FROM_COPY, FROM_CONSTANT, FROM_TEXT, FROM_AR /* 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). */ /* IN_LOCALS: a handler's locals, read in its walks and the folds in them. */ +/* IN_COMPUTE: what a view's body may do too (018): loops, locals, and changes to an event's lists alone. */ enum { IN_VIEW = 1, IN_HANDLER = 2, IN_MESSAGE = 4, IN_KEYED = 8, IN_TEXT = 16, IN_ROW = 32, IN_OUTER = 64, IN_ARGUMENTS = 128, - IN_LOCALS = 256 }; + IN_LOCALS = 256, IN_COMPUTE = 512 }; enum { READ = IN_VIEW | IN_HANDLER }; /* What an argument names: nothing (it is 0), a state of one type (A_STATE + * the type), a list and one of its record's fields, a message field, the @@ -37,8 +38,10 @@ enum { A_NONE, A_STATE, A_ITEM = A_STATE + PURE_STATE_I64 + 1, A_FIELD, A_COLLEC * or a value and whether to include it (a bool): a number (FOLD_TWO) or an * i64 (FOLD_I64). A table's filter is verified as a column that leaves a * bool: these stand for a view type. A walk's block (EACH) is verified as - * its handler's code that may read the item it runs for (WALK). */ -enum { FOLD_ONE = 100, FOLD_TWO, FOLD_I64, FILTER, WALK }; + * its handler's code that may read the item it runs for (WALK). In a view's + * body a loop's block leaves nothing either (STEPS), and a walk's reads its + * item (STEPS_WALK); neither changes a state. */ +enum { FOLD_ONE = 100, FOLD_TWO, FOLD_I64, FILTER, WALK, STEPS, STEPS_WALK }; /* The text or list an opcode works on: its cursor and index operands belong * to it, and so does a cursor or index it pushes. O_ANY: no rule. O_OPERAND: * it pushes a position in its operand's text or list. O_ARG: the argument. @@ -94,11 +97,11 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi [PURE_OP_CUR_COUNT] = {1, {T_CURSOR}, T_NUM, READ, A_UNIT, O_ANY}, /* Record operations also take as many values as the list's record has fields. */ [PURE_OP_LIST_COUNT] = {0, {0}, T_NUM, READ, STATE(LIST), O_ANY}, - [PURE_OP_LIST_APPEND] = {0, {0}, 0, IN_HANDLER, STATE(LIST), O_ANY}, - [PURE_OP_LIST_INSERT] = {1, {T_INDEX}, 0, IN_HANDLER, STATE(LIST), O_ARG}, - [PURE_OP_LIST_REMOVE] = {1, {T_INDEX}, 0, IN_HANDLER, STATE(LIST), O_ARG}, + [PURE_OP_LIST_APPEND] = {0, {0}, 0, IN_HANDLER | IN_COMPUTE, STATE(LIST), O_ANY}, + [PURE_OP_LIST_INSERT] = {1, {T_INDEX}, 0, IN_HANDLER | IN_COMPUTE, STATE(LIST), O_ARG}, + [PURE_OP_LIST_REMOVE] = {1, {T_INDEX}, 0, IN_HANDLER | IN_COMPUTE, STATE(LIST), O_ARG}, [PURE_OP_ITEM_GET] = {1, {T_INDEX}, T_ITEM, READ, A_ITEM, O_LIST}, - [PURE_OP_ITEM_SET] = {1, {T_INDEX}, 0, IN_HANDLER, A_ITEM, O_LIST}, + [PURE_OP_ITEM_SET] = {1, {T_INDEX}, 0, IN_HANDLER | IN_COMPUTE, A_ITEM, O_LIST}, [PURE_OP_INDEX_LOAD] = {0, {0}, T_INDEX, READ, STATE(INDEX), O_REF}, [PURE_OP_INDEX_STORE] = {1, {T_INDEX}, 0, IN_HANDLER, STATE(INDEX), O_REF}, [PURE_OP_INDEX_MOVE] = {2, {T_INDEX, T_NUM}, T_INDEX, READ, A_NONE, O_OPERAND}, @@ -117,12 +120,12 @@ static const struct { unsigned char pops, in[3], out; unsigned short where; unsi [PURE_OP_LIST_HAS] = {1, {T_KEY}, T_BOOL, READ, A_KEYED, O_ANY}, [PURE_OP_NOW] = {0, {0}, T_NUM, IN_HANDLER, A_NONE, O_ANY}, [PURE_OP_SEND] = {0, {0}, 0, IN_HANDLER, A_OUTPUT, O_ANY}, - [PURE_OP_EACH] = {0, {0}, 0, IN_HANDLER, A_EACH, O_ANY}, + [PURE_OP_EACH] = {0, {0}, 0, IN_HANDLER | IN_COMPUTE, A_EACH, O_ANY}, [PURE_OP_LOCAL_LOAD] = {0, {0}, T_LOCAL, IN_LOCALS, A_LOCAL, O_ANY}, - [PURE_OP_LOCAL_STORE] = {1, {T_LOCAL}, 0, IN_HANDLER, A_LOCAL, O_ANY}, + [PURE_OP_LOCAL_STORE] = {1, {T_LOCAL}, 0, IN_HANDLER | IN_COMPUTE, A_LOCAL, O_ANY}, /* LOOP takes its low and high, two numbers or two i64s (the operands are its argument's). */ - [PURE_OP_LOOP] = {0, {0}, 0, IN_HANDLER, A_LOOP, O_ANY}, - [PURE_OP_BREAK] = {0, {0}, 0, IN_HANDLER, A_NONE, O_ANY}, + [PURE_OP_LOOP] = {0, {0}, 0, IN_HANDLER | IN_COMPUTE, A_LOOP, O_ANY}, + [PURE_OP_BREAK] = {0, {0}, 0, IN_HANDLER | IN_COMPUTE, A_NONE, O_ANY}, [PURE_OP_FLOOR] = NUMS(1), [PURE_OP_MOD] = NUMS(2), [PURE_OP_I64_ADD] = I64S(2), [PURE_OP_I64_SUB] = I64S(2), [PURE_OP_I64_MUL] = I64S(2), [PURE_OP_I64_DIV] = I64S(2), [PURE_OP_I64_MOD] = I64S(2), [PURE_OP_I64_NEG] = I64S(1), [PURE_OP_I64_MIN] = I64S(2), [PURE_OP_I64_MAX] = I64S(2), @@ -422,16 +425,21 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i uint32_t event, bool locals, unsigned nest, uint32_t walked, uint32_t *at, unsigned char *target) { #define REFUSE(why) do { *at = pc; return why; } while (0) - bool handler = view < 0 || view == WALK, folding = view == FOLD_ONE || view == FOLD_TWO || view == FOLD_I64; + bool steps = view == STEPS || view == STEPS_WALK, handler = view < 0 || view == WALK || steps; + bool folding = view == FOLD_ONE || view == FOLD_TWO || view == FOLD_I64; + /* A view outside a table computes (locals: its code reads them): a body, and the loops in it. */ + bool body = steps || (!handler && !folding && view != FILTER && row < 0 && locals); if (!handler && !block.length) return "empty view"; /* A handler's message or text; a view's arguments, or the message of the handler a fold is in. */ int input = event_input(event); const PureInputShape *shape = input >= 0 ? &m->input[input] : NULL; - unsigned where = !handler ? IN_VIEW | (row >= 0 ? IN_ROW : 0) | (folding && outer >= 0 ? IN_OUTER : 0) + unsigned where = steps ? IN_VIEW | IN_COMPUTE | (view == STEPS_WALK ? IN_ROW : 0) + : !handler ? IN_VIEW | (row >= 0 ? IN_ROW : 0) | (folding && outer >= 0 ? IN_OUTER : 0) | (body ? IN_COMPUTE : 0) : IN_HANDLER | (view == WALK ? IN_ROW : 0); - if (shape && handler && shape->kind == PURE_KIND_MESSAGE) where |= IN_MESSAGE | (shape->keyed ? IN_KEYED : 0); - if (shape && handler && shape->kind == PURE_KIND_TEXT) where |= IN_TEXT; - if (shape && !handler && (shape->kind == PURE_KIND_MESSAGE || shape->kind == PURE_KIND_ARGUMENTS)) where |= IN_ARGUMENTS; + if (shape && handler && !steps && shape->kind == PURE_KIND_MESSAGE) where |= IN_MESSAGE | (shape->keyed ? IN_KEYED : 0); + if (shape && handler && !steps && shape->kind == PURE_KIND_TEXT) where |= IN_TEXT; + if (shape && (!handler || steps) && (shape->kind == PURE_KIND_MESSAGE || shape->kind == PURE_KIND_ARGUMENTS)) + where |= IN_ARGUMENTS; if (locals) where |= IN_LOCALS; unsigned char types[PURE_MODULE_STACK], owners[PURE_MODULE_STACK]; Join joins[PURE_MODULE_JOINS]; @@ -467,7 +475,7 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i if (allowed & IN_LOCALS) REFUSE("local outside a handler"); if (view == FILTER) REFUSE("filter with an effect"); if (folding) REFUSE("fold with an effect"); - if (!handler) REFUSE("view with an effect"); + if (!handler || steps) REFUSE("view with an effect"); if (allowed & IN_KEYED) REFUSE("document operation outside a keyed message handler"); if (allowed & IN_TEXT) REFUSE("text input outside a text handler"); REFUSE("message operation outside a message handler"); @@ -492,6 +500,11 @@ PURE_COLD static const char *verify_code(const PureModule *m, PureBlock block, i if (op->op == PURE_OP_BREAK && height) REFUSE("break with values on the stack"); if (op->op == PURE_OP_ITEM_SET && m->state_key[list_of(arg)] == item_field(arg) + 1) REFUSE("change of a keyed list's key"); + /* 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) && + !(m->state_flags[op->op == PURE_OP_ITEM_SET ? list_of(arg) : arg] & PURE_LIST_ROWS >> 8)) + REFUSE("view changes a state"); /* PICK copies the value arg below the top; SLIDE drops arg values under the top. */ if (kind == A_DEPTH) { bool pick = op->op == PURE_OP_PICK; @@ -515,10 +528,10 @@ 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. */ - const char *why = kind == A_EACH ? verify_block(m, (PureBlock){pc + 1, skip}, WALK, (int)list, -1, event, locals, - nest + 1, walked | UINT32_C(1) << (list & 31), &inner) - : kind == A_LOOP ? verify_block(m, (PureBlock){pc + 1, skip}, view, row, outer, event, locals, - nest + 1, walked, &inner) + 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), &inner) + : kind == A_LOOP ? verify_block(m, (PureBlock){pc + 1, skip}, body && !steps ? STEPS : view, row, + outer, event, locals, nest + 1, walked, &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, @@ -1280,10 +1293,21 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule bad = i; bool table = out->view_type[i] == PURE_VIEW_TABLE; uint32_t event = out->view_arguments[i] ? 2u * out->view_arguments[i] - 1u : 0; /* the arguments' input, as an arrival */ + bool computes = !table && !out->view_table[i]; /* a view outside a table may have a body */ if ((!table || out->view_code[i].length) && (why = verify_block(out, out->view_code[i], table ? FILTER : out->view_type[i], - table || out->view_table[i] ? out->view_list[i] : -1, -1, event, false, 0, 0, &bad))) + table || out->view_table[i] ? out->view_list[i] : -1, -1, event, computes, 0, 0, &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) { + const PureOp *op = &out->code[pc]; + 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); + } + if (out->view_lists[i]) out->view_body[i] = true; } for (uint32_t i = 0; i < out->handlers; ++i) { bad = i; @@ -3102,9 +3126,12 @@ void pure_snapshot_release(PureSnapshot *snapshot) { } } +static void let_own_go(PureApp *app); + void pure_app_stop(PureApp *app) { 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) { pure_draft_close(app->texts[i]); pure_list_free(app->lists[i]); @@ -3567,22 +3594,47 @@ static bool argue(const PureApp *app, uint32_t view, Event *ev) { return true; } +/* The event's lists a view's body changes, empty: a reader's own (its + * states are a snapshot's, which other readers read), the app's otherwise + * (empty between events). */ +static bool own_lists(PureApp *app, uint32_t lists) { + const PureModule *m = app->module; + for (uint32_t left = lists; app->reading && left; left &= left - 1) { + unsigned i = (unsigned)__builtin_ctz(left); + if (!app->own[i]) { + if (!(app->own[i] = new_list(m, m->state_ref[i]))) return false; + (void)pure_list_keyed(app->own[i], m->state_key[i] ? (int)m->state_key[i] - 1 : -1); + } + app->lists[i] = app->own[i]; + } + return true; +} + static PureResult evaluate(PureApp *app, uint32_t view, size_t row, Slot *out) { - PureValue values[PURE_MODULE_STATES]; + const PureModule *m = app->module; + PureValue values[PURE_MODULE_STATES], locals[PURE_MODULE_LOCALS]; memcpy(values, app->values, sizeof(values)); app->view_arena.used = 0; Event ev = {.app = app, .arena = &app->view_arena, .row = row}; if (!argue(app, view, &ev)) return PURE_INVALID; + if (m->view_body[view]) { /* a body computes: its locals from 0, its lists its own, its additions counted */ + memset(locals, 0, sizeof(locals)); + ev.locals = locals; + if (!own_lists(app, m->view_lists[view])) return PURE_ALLOCATION; + app->grown = app->spent = 0; + app->room = app->memory > PURE_GROWTH_DEFAULT ? app->memory : PURE_GROWTH_DEFAULT; + } PureFault fault = app->fault; uint32_t fault_pc = app->fault_pc; - PureBlock code = app->module->view_code[view]; + PureBlock code = m->view_code[view]; app->fault = PURE_FAULT_NONE; app->work = 0; bool counted = charge(app, code.length); - PureResult result = counted ? run(app->module, values, code, &ev, out) : PURE_LIMIT; + PureResult result = counted ? run(m, values, code, &ev, out) : PURE_LIMIT; if (result != PURE_OK) app->view_fault = counted ? app->fault : PURE_FAULT_WORK; /* why it has no value */ app->fault = fault; app->fault_pc = fault_pc; + for (uint32_t left = m->view_lists[view]; left; left &= left - 1) pure_list_clear(app->lists[__builtin_ctz(left)]); return result; } @@ -3843,8 +3895,16 @@ PureSnapshot *pure_save_snapshot(PureApp *app, uint32_t runtime) { return made; } +/* 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) { + pure_list_free(app->own[i]); + app->own[i] = NULL; + } +} + void pure_app_read(PureApp *reader, const PureSnapshot *snapshot) { - if (reader->module != snapshot->module) drop_passes(reader); + if (reader->module != snapshot->module) drop_passes(reader), let_own_go(reader); /* laid out for its module */ reader->module = snapshot->module; reader->reading = true; reader->changes = snapshot->changes; /* what the row cache is fresh for */ diff --git a/core/module.h b/core/module.h index e71559b..c5b7ba0 100644 --- a/core/module.h +++ b/core/module.h @@ -175,7 +175,10 @@ enum { PURE_CURSOR_STAYS = 1, PURE_LIST_ROWS = 256 }; enum { PURE_STATE_TRANSIENT = 256 }; /* A table view has one row per item of a list, or per item its filter * keeps; its columns are number, bool, i64 or text views evaluated for one - * row at a time. */ + * row at a time. A view outside a table may compute (docs/design/018): its + * code may hold LOOP, EACH, BREAK and locals, and change an event's lists, + * but no state; each evaluation begins with its locals 0 and those lists + * empty (a reader's own: readers of one snapshot compute apart). */ typedef enum { PURE_VIEW_NUM = 0, PURE_VIEW_TEXT = 1, PURE_VIEW_TABLE = 2, PURE_VIEW_BOOL = 3, PURE_VIEW_I64 = 4 } PureViewType; typedef enum { PURE_UNIT_CHAR = 0, PURE_UNIT_LINE = 1 } PureUnit; typedef enum { PURE_EDGE_DOWN, PURE_EDGE_UP } PureEdge; @@ -259,6 +262,9 @@ typedef struct { PureBlock view_code[PURE_MODULE_VIEWS]; uint8_t view_plan[PURE_MODULE_VIEWS]; /* a table's filter runs a leaf of its list at once (as a fold may) */ uint8_t view_arguments[PURE_MODULE_VIEWS]; /* 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. */ + bool view_body[PURE_MODULE_VIEWS]; + uint32_t view_lists[PURE_MODULE_VIEWS]; PureShape shape[PURE_MODULE_SHAPES]; uint8_t shapes; PureRange range[PURE_MODULE_RANGES]; @@ -413,6 +419,7 @@ typedef struct { 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 */ 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) */ diff --git a/core/test-module.c b/core/test-module.c index 0f9cda9..9bf34ff 100644 --- a/core/test-module.c +++ b/core/test-module.c @@ -1429,7 +1429,7 @@ static void lists(void) { static const V append_view[] = {{"v", 0, 3}}; bad = (Spec){nums, 1, inputs, 0, append_view, 1, NULL, 0, (O[]){{TK, 0, 0}, {C, 0, 0}, {LA, 1, 0}}, 3, NULL, 0, two_lists, 3, constants, 3, NULL, 0, records, 1, NULL, 0}; - CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "view with an effect")); + CHECK(load(bad, &module_b) == PURE_INVALID && strstr(error, "view changes a state")); /* a body changes its event's lists alone */ static const T index_of_number[] = {{"ia", 4, 0}}; bad = (Spec){nums, 1, inputs, 0, NULL, 0, NULL, 0, NULL, 0, NULL, 0, index_of_number, 1, constants, 3, NULL, 0, records, 1, NULL, 0}; @@ -3262,8 +3262,10 @@ static void locals(void) { static const O other_list[] = {{XF, L_XS, 0}, {LOCAL_S, LOCAL(0, 3), 0}}; static const O far_slot[] = {{C, 0, 1}, {LOCAL_S, LOCAL(64, -1), 0}}; static const H first2[] = {{1, 0, 2}}, first4[] = {{1, 0, 4}}, first5[] = {{1, 0, 5}}; + /* A view outside a table computes (018): its locals begin at 0. */ + CHECK(load(local_spec(view_local, 1, NULL, 0, local_view, 1), &module_b) == PURE_OK && module_b.view_body[0]); + pure_module_release(&module_b); const struct { Spec spec; const char *reason; } refused[] = { - {local_spec(view_local, 1, NULL, 0, local_view, 1), "local outside a handler"}, {local_spec(fold_store, LEN(fold_store), first5, 1, NULL, 0), "fold with an effect"}, {local_spec(two_things, LEN(two_things), first4, 1, NULL, 0), "local used as two things"}, {local_spec(other_list, LEN(other_list), first2, 1, NULL, 0), "no such state"}, @@ -5453,6 +5455,108 @@ static void loops(void) { puts("{\"loops\":\"count-break-nested-walks-event-lists-work-memory-refusals\"}"); } +/* Views that compute (docs/design/018): a body's locals begin at 0 and + * the event's lists it fills begin empty at each evaluation, on the app and + * on readers of one snapshot at once (each its own lists); its loops count + * as a view's work, its lists as an event's growth. */ +typedef struct { PureSnapshot *snapshot; double total, distinct; unsigned rounds; } BodyReads; + +static void *read_bodies(void *context) { + BodyReads *r = context; + PureApp reader = {0}; + for (unsigned k = 0; k < r->rounds; ++k) { + pure_app_read(&reader, r->snapshot); + double total, distinct; + CHECK(pure_app_view(&reader, 1, &distinct) && distinct == r->distinct); + CHECK(pure_app_view(&reader, 0, &total) && total == r->total); + } + CHECK(reader.own[3] && !pure_list_count(reader.own[3])); + pure_app_stop(&reader); + return NULL; +} + +static void view_bodies(void) { + enum { N, XS = 2, SEEN, LOOP = PURE_OP_LOOP, LL = PURE_OP_LOCAL_LOAD, LS = PURE_OP_LOCAL_STORE, HAS_ = PURE_OP_LIST_HAS, + EACH_ = PURE_OP_EACH, MOD_ = PURE_OP_MOD }; + static const S nums[] = {{"n", 0}, {"m", 0}}; + static const T states[] = {{"xs", 3, 0}, {"let.seen", 3 | (uint32_t)(PURE_LIST_ROWS | 1) << 8, 0}}; + static const RF fields[] = {{"value", 0, 0}}; + static const R records[] = {{"row", fields, 1}}; + static const I inputs[] = {KEY("add", "KeyA")}; + static const O code[] = { + /* add: xs.append(row { n mod 7 }); n = n + 1 (8) */ + {L, N, 0}, {C, 0, 7}, {MOD_, 0, 0}, {LA, XS, 0}, {L, N, 0}, {C, 0, 1}, {ADD, 0, 0}, {ST, N, 0}, + /* total at 8: s = 0; for i in 0 .. xs.count { s = s + (xs.first + i).value }; s (13) */ + {C, 0, 0}, {LS, 1, 0}, {C, 0, 0}, {LC, XS, 0}, {LOOP, 0 | 7u << 16, 0}, + {LL, 1, 0}, {XF, XS, 0}, {LL, 0, 0}, {XM, 0, 0}, {IG, XS, 0}, {ADD, 0, 0}, {LS, 1, 0}, {LL, 1, 0}, + /* distinct at 21: for x in xs { if !seen.has(x.value) { seen.add(row { x.value }) } }; seen.count (10) */ + {EACH_, WALKING(XS, 8), 0}, {ROW, XS, 0}, {IG, XS, 0}, {HAS_, SEEN, 0}, {NOT, 0, 0}, {JZ, 30, 0}, {ROW, XS, 0}, + {IG, XS, 0}, {LA, SEEN, 0}, {LC, SEEN, 0}, + /* spin at 31: for i in 0 .. 1e15 { }; 1 (4) */ + {C, 0, 0}, {C, 0, 1e15}, {LOOP, 0, 0}, {C, 0, 1}, + /* fill at 35: for i in 0 .. 1e6 { seen.add(row { i }) }; seen.count (6) */ + {C, 0, 0}, {C, 0, 1e6}, {LOOP, 0 | 2u << 16, 0}, {LL, 0, 0}, {LA, SEEN, 0}, {LC, SEEN, 0}, + }; + static const H handlers[] = {{1, 0, 8}}; + static const V views[] = {{"total", 8, 13}, {"distinct", 21, 10}, {"spin", 31, 4}, {"fill", 35, 6}}; + 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); + char *path = new_store(); + PureApp app; + CHECK(pure_app_start(&app, &module_a, pure_tree_data(path)) == PURE_OK); + double total = 0, value; + for (int k = 0; k < 20; ++k) { + CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK); + total += k % 7; + } + for (int round = 0; round < 2; ++round) /* each evaluation begins anew */ + CHECK(number_view(&app, 0) == total && number_view(&app, 1) == 7 && !pure_list_count(app.lists[SEEN])); + CHECK(!pure_app_view(&app, 2, &value) && pure_app_view_fault(&app) == PURE_FAULT_WORK); + CHECK(number_view(&app, 3) == 1e6 && !pure_list_count(app.lists[SEEN])); + pure_app_memory(&app, 4096); + CHECK(!pure_app_view(&app, 3, &value) && pure_app_view_fault(&app) == PURE_FAULT_MEMORY && !pure_list_count(app.lists[SEEN])); + pure_app_memory(&app, 0); + CHECK(pure_app_input(&app, 0, PURE_EDGE_DOWN) == PURE_OK); /* the app goes on */ + total += 20 % 7; + /* Readers of one snapshot compute at once, each in its own lists. */ + BodyReads reads = {pure_app_snapshot(&app), total, 7, 2000}; + CHECK(reads.snapshot); + pthread_t threads[3]; + for (int t = 0; t < 3; ++t) CHECK(!pthread_create(&threads[t], NULL, read_bodies, &reads)); + for (int t = 0; t < 3; ++t) pthread_join(threads[t], NULL); + pure_snapshot_release(reads.snapshot); + pure_app_stop(&app); + remove_store(path); + + /* A body changes no state. */ + static const O stores[] = {{C, 0, 0}, {C, 0, 2}, {LOOP, 0 | 2u << 16, 0}, {C, 0, 1}, {ST, N, 0}, {C, 0, 1}}; + static const O appends[] = {{C, 0, 1}, {LA, XS, 0}, {C, 0, 1}}; + static const O breaks[] = {{PURE_OP_BREAK, 0, 0}, {C, 0, 1}}; + const struct { const O *code; size_t count; const char *why; } refused[] = { + {stores, LEN(stores), "view with an effect"}, + {appends, LEN(appends), "view changes a state"}, + {breaks, LEN(breaks), "break outside a loop"}, + }; + for (size_t i = 0; i < LEN(refused); ++i) { + V one[] = {{"v", 0, (uint32_t)refused[i].count}}; + Spec bad = spec; + bad.o = refused[i].code; + bad.no = refused[i].count; + bad.h = NULL; + bad.nh = 0; + bad.v = one; + bad.nv = 1; + if (load(bad, &module_b) == PURE_OK || !strstr(error, refused[i].why)) { + fprintf(stderr, "body case %zu: expected '%s', got '%s'\n", i, refused[i].why, error); + abort(); + } + } + pure_module_release(&module_a); + puts("{\"view_bodies\":\"locals-loops-own-lists-readers-work-memory-refusals\"}"); +} + int main(void) { number_texts(); behaviour(); @@ -5477,6 +5581,7 @@ int main(void) { view_arguments(); locals(); loops(); + view_bodies(); ranges(); keyed(); keyed_mutations(); -- cgit v1.2.3