diff options
Diffstat (limited to 'core')
| -rw-r--r-- | core/module-list.c | 5 | ||||
| -rw-r--r-- | core/module-list.h | 2 | ||||
| -rw-r--r-- | core/module.c | 374 | ||||
| -rw-r--r-- | core/module.h | 24 | ||||
| -rw-r--r-- | core/test-module.c | 86 |
5 files changed, 474 insertions, 17 deletions
diff --git a/core/module-list.c b/core/module-list.c index 55c6812..5df66f4 100644 --- a/core/module-list.c +++ b/core/module-list.c @@ -510,6 +510,11 @@ bool pure_list_current(const PureList *copy, const PureList *list) { return copy->id == list->id && copy->version == list->version; } +void pure_list_stamp(const PureList *list, uint64_t *id, uint64_t *version) { + *id = list->id; + *version = list->version; +} + static bool log_change(PureUndo *undo, Entry entry) { if (undo->count == undo->capacity) { size_t capacity = undo->capacity ? 2 * undo->capacity : 16; diff --git a/core/module-list.h b/core/module-list.h index e370b97..5e724b8 100644 --- a/core/module-list.h +++ b/core/module-list.h @@ -60,6 +60,8 @@ PureList *pure_list_share(PureList *, PureRetired *retired); void pure_list_hold(PureList *copy); /* Whether copy shares list's items as they are now. */ bool pure_list_current(const PureList *copy, const PureList *list); +/* Which list, and its version: the same pair means the same items. */ +void pure_list_stamp(const PureList *, uint64_t *id, uint64_t *version); /* Free what was retired while no copy that shared it remains. */ void pure_retired_free(PureRetired *); /* Insert a copy of cells (text bytes are copied) before position at. A NULL diff --git a/core/module.c b/core/module.c index 36e13c3..07d98d4 100644 --- a/core/module.c +++ b/core/module.c @@ -433,6 +433,128 @@ PURE_COLD static void plan_folds(PureModule *m, PureBlock block) { } } +/* A planned fold's block as a tree (its lane forms have no branch; one + * with PICK or SLIDE is left alone): per instruction, its operands' nodes + * and the first instruction of its subtree, whether it reads the fold's + * item (item), and whether it reads nothing but the fold's list, its item + * and constants (own): what one pass over the list can compute. */ +typedef struct { uint32_t first; int kid[3]; bool item, own; } Node; + +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: + 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: + return 1; + case PURE_OP_CLAMP: return 3; + default: return 2; + } +} + +/* The list an instruction reads, if any (-1: none). */ +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: + return (int)op->arg; + default: return -1; + } +} + +PURE_COLD static bool block_tree(const PureModule *m, PureBlock block, int list, Node *nodes, int *roots, unsigned *count) { + int stack[PURE_MODULE_STACK]; + unsigned sp = 0; + for (uint32_t i = 0; i < block.length; ++i) { + const PureOp *op = &m->code[block.start + i]; + if (op->op == PURE_OP_PICK || op->op == PURE_OP_SLIDE) return false; + unsigned pops = arity(op->op); + Node *node = &nodes[i]; + int read = list_read(op); + node->first = block.start + i; + node->item = op->op == PURE_OP_ROW; + node->own = op->op != PURE_OP_LOAD && op->op != PURE_OP_INDEX_LOAD && op->op != PURE_OP_OUTER_ROW && + (read < 0 || read == list); + for (unsigned k = 0; k < 3; ++k) node->kid[k] = -1; + for (unsigned k = 0; k < pops; ++k) { + int kid = stack[sp - pops + k]; + node->kid[k] = kid; + node->item = node->item || nodes[kid].item; + node->own = node->own && nodes[kid].own; + } + if (pops) node->first = nodes[stack[sp - pops]].first; + sp -= pops; + stack[sp++] = (int)i; + } + memcpy(roots, stack, sp * sizeof(*stack)); + *count = sp; + return true; +} + +/* The comparisons a test ANDs together (at most 16). */ +PURE_COLD static bool conjuncts(const PureModule *m, PureBlock block, const Node *nodes, int at, int *out, unsigned *count) { + if (m->code[block.start + at].op == PURE_OP_AND) + return conjuncts(m, block, nodes, nodes[at].kid[0], out, count) && + conjuncts(m, block, nodes, nodes[at].kid[1], out, count); + if (*count == 16) return false; + out[(*count)++] = at; + return true; +} + +/* The shape of a planned fold in a column, if it has one (see PureShape). */ +PURE_COLD static void shape_fold(PureModule *m, uint32_t pc) { + PureOp *fold = &m->code[pc]; + unsigned how = fold->arg & 0xFF; + int list = (int)(fold->arg >> 8 & 0xFF); + PureBlock block = {pc + 1, fold->arg >> 16}; + if (!fold->plan || m->shapes == PURE_MODULE_SHAPES) return; + Node *nodes = malloc(block.length * sizeof(*nodes)); + int roots[PURE_MODULE_STACK], tests[16]; + unsigned count = 0, tested = 0; + if (!nodes) return; + if (!block_tree(m, block, list, nodes, roots, &count) || !conjuncts(m, block, nodes, roots[count - 1], tests, &tested) || + (count == 2 && !nodes[roots[0]].own)) { + free(nodes); + return; + } + /* One comparison of the item's field with a key; the rest the item's own. */ + int test = -1; + for (unsigned k = 0; k < tested; ++k) { + if (nodes[tests[k]].own) continue; + if (test >= 0) test = -2; + else test = tests[k]; + } + PureShape shape = {0}; + if (test >= 0) { + const PureOp *compare = &m->code[block.start + test]; + const Node *node = &nodes[test]; + uint8_t op = compare->op; + for (unsigned side = 0; side < 2 && !shape.form && (op == PURE_OP_EQ || op == PURE_OP_LT || op == PURE_OP_LE || + op == PURE_OP_GT || op == PURE_OP_GE); ++side) { + int field = node->kid[side], key = node->kid[1 - side]; + const PureOp *get = &m->code[block.start + field]; + if (get->op != PURE_OP_ITEM_GET || m->code[block.start + nodes[field].kid[0]].op != PURE_OP_ROW || + nodes[key].item) + continue; + static const uint8_t flipped[] = {[PURE_OP_LT] = PURE_OP_GT, [PURE_OP_LE] = PURE_OP_GE, + [PURE_OP_GT] = PURE_OP_LT, [PURE_OP_GE] = PURE_OP_LE}; + shape = (PureShape){op == PURE_OP_EQ ? PURE_SHAPE_GROUP : PURE_SHAPE_RANK, + op == PURE_OP_EQ || !side ? op : flipped[op], (uint16_t)item_field(get->arg), + block.start + (uint32_t)test, + {nodes[key].first, block.start + (uint32_t)key + 1 - nodes[key].first}}; + } + /* A rank answers a count tested by the comparison alone. */ + if (shape.form == PURE_SHAPE_RANK && (how != PURE_FOLD_COUNT || tested != 1)) shape.form = 0; + } + if (shape.form) { + m->shape[m->shapes++] = shape; + fold->shape = m->shapes; + } + free(nodes); +} + PURE_COLD PureResult pure_module_read(const char *path, PureModule *out, char *error, size_t error_size) { FILE *file = fopen(path, "rb"); unsigned char *bytes = NULL; @@ -706,6 +828,12 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule plan_folds(out, out->view_code[i]); out->view_plan[i] = out->view_type[i] == PURE_VIEW_TABLE && out->view_code[i].length && block_has_lanes(out, out->view_code[i]); + /* A fold in a column runs for every row: one pass may answer them all. */ + for (uint32_t pc = out->view_code[i].start; out->view_table[i] && pc < out->view_code[i].start + out->view_code[i].length; ++pc) + if (out->code[pc].op == PURE_OP_FOLD) { + shape_fold(out, pc); + pc += out->code[pc].arg >> 16; + } } for (uint32_t i = 0; i < out->handlers; ++i) plan_folds(out, out->handler[i].code); return PURE_OK; @@ -1053,7 +1181,7 @@ static bool lanes_op(uint8_t op, const double *a, size_t sa, const double *b, si * per stack depth, or into the list's leaf). false when run would fault for * some item. */ static bool run_lanes(const PureModule *m, const double *values, PureBlock block, const Event *ev, const PureRun *items, - Lanes *result, double (*store)[PURE_LIST_RUN], Memo *memo) { + Lanes *result, double (*store)[PURE_LIST_RUN], Memo *memo, const PureShape *shape) { PureApp *app = ev->app; Lanes stack[PURE_MODULE_STACK]; double scratch[2][PURE_LIST_RUN]; @@ -1064,6 +1192,16 @@ static bool run_lanes(const PureModule *m, const double *values, PureBlock block uint32_t arg = op->arg; Lanes *top = sp ? &stack[sp - 1] : stack; double *out = store[sp ? sp - 1 : 0]; /* where the top's new lanes go */ + /* A group's pass: every group at once, so its key is not needed and its test holds. */ + if (shape && pc == shape->key.start) { + stack[sp++] = (Lanes){NULL, 0, false, 0}; + pc = shape->key.start + shape->key.length - 1; + continue; + } + if (shape && pc == shape->test) { + stack[--sp - 1] = (Lanes){NULL, 1, false, 0}; + continue; + } switch (op->op) { case PURE_OP_CONST: stack[sp++] = (Lanes){NULL, op->value, false, 0}; continue; case PURE_OP_LOAD: stack[sp++] = (Lanes){NULL, values[arg], false, 0}; continue; @@ -1253,7 +1391,7 @@ static PureResult fold_runs(const PureModule *m, double *values, PureBlock inner memo.known = 0; for (bool more = pure_list_run(list, 0, &items); more && !fold->done; more = pure_list_next(list, &items)) { Lanes out[2]; - if (!run_lanes(m, values, inner, per, &items, out, store, &memo)) { + if (!run_lanes(m, values, inner, per, &items, out, store, &memo, NULL)) { PureResult status = fold_rows(m, values, inner, per, items.start, items.start + items.count, fold); if (status != PURE_OK) return status; continue; @@ -1279,6 +1417,225 @@ static PureResult fold_runs(const PureModule *m, double *values, PureBlock inner return PURE_OK; } +/* ---- Passes: a shaped fold in a column answered for every row -------- + * A fold in a column runs once per row, over its whole list each time. A + * shaped one (PureShape) is answered from one pass over the list instead, + * kept by the app for the list's version: a group pass folds the items of + * every group at once (the fold's test with its comparison taken as true, + * the item's field naming its group), a rank pass orders the field's values. + * A row then runs the key alone and looks it up. The pass is made at the + * second row that asks for the same list, so a fold asked once scans once. + * + * It answers exactly as the fold would: items join a group in order (a sum + * adds as it would), a key that faults leaves the row to run the fold as + * written (which faults, or not over no item, as it would), and an item + * that would fault in the pass leaves every row to run it as written. */ +typedef struct { double key; Fold fold; bool used; } Group; +struct PurePass { + const PureModule *module; + uint32_t pc; /* the fold's */ + uint64_t list, version; /* its list as it was */ + unsigned asked; /* rows that asked before it was made */ + bool made, failed; + Group *groups; /* a group pass: open addressing by key */ + size_t capacity, count; + uint64_t *ordered; /* a rank pass: the field's values in order (order_bits) */ + size_t items; +}; +enum { PASSES = 16 }; + +static void free_pass(struct PurePass *pass) { + free(pass->groups); + free(pass->ordered); +} + +unsigned pure_app_passes(const PureApp *app) { + unsigned made = 0; + for (unsigned i = 0; i < app->pass_count; ++i) made += app->passes[i].made; + return made; +} + +static void drop_passes(PureApp *app) { + for (unsigned i = 0; i < app->pass_count; ++i) free_pass(&app->passes[i]); + free(app->passes); + app->passes = NULL; + app->pass_count = 0; +} + +/* The app's pass for a fold over a list as it is: one kept, or a new one in + * place of this fold's older ones (or of the oldest). NULL without room. */ +static struct PurePass *pass_for(PureApp *app, const PureModule *m, uint32_t pc, const PureList *list) { + uint64_t id, version; + pure_list_stamp(list, &id, &version); + unsigned kept = 0; + struct PurePass *found = NULL; + for (unsigned i = 0; i < app->pass_count; ++i) { + struct PurePass *pass = &app->passes[i]; + if (pass->module == m && pass->pc == pc && pass->list == id && pass->version == version) found = pass; + else if (pass->module == m && pass->pc == pc) free_pass(pass); /* the list has moved on */ + else app->passes[kept++] = *pass; + if (found == pass) app->passes[kept++] = *pass, found = &app->passes[kept - 1]; + } + app->pass_count = kept; + if (found) return found; + if (!app->passes && !(app->passes = calloc(PASSES, sizeof(*app->passes)))) return NULL; + if (app->pass_count == PASSES) { + free_pass(&app->passes[0]); + memmove(app->passes, app->passes + 1, (PASSES - 1) * sizeof(*app->passes)); + --app->pass_count; + } + struct PurePass *pass = &app->passes[app->pass_count++]; + *pass = (struct PurePass){.module = m, .pc = pc, .list = id, .version = version}; + return pass; +} + +/* A key's bits as they order: -0 as 0, negative numbers below positive ones. */ +static uint64_t order_bits(double value) { + uint64_t bits; + value = value == 0 ? 0 : value; + memcpy(&bits, &value, sizeof(bits)); + return bits >> 63 ? ~bits : bits | (uint64_t)1 << 63; +} + +static size_t group_slot(uint64_t bits, size_t capacity) { + bits ^= bits >> 33, bits *= 0xff51afd7ed558ccdu, bits ^= bits >> 33; /* a finalizer's mixing */ + return (size_t)bits & (capacity - 1); +} + +/* key's group, added when absent (with add); NULL when absent, or without room. */ +static Group *group_of(struct PurePass *pass, double key, unsigned how, bool add) { + key = key == 0 ? 0 : key; + if (pass->capacity) { + for (size_t at = group_slot(order_bits(key), pass->capacity);; at = (at + 1) & (pass->capacity - 1)) { + if (!pass->groups[at].used) break; + if (pass->groups[at].key == key) return &pass->groups[at]; + } + } + if (!add) return NULL; + if (2 * (pass->count + 1) > pass->capacity) { /* grown to keep half the slots free */ + size_t capacity = pass->capacity ? 2 * pass->capacity : 64; + Group *groups = calloc(capacity, sizeof(*groups)); + if (!groups) return NULL; + for (size_t i = 0; i < pass->capacity; ++i) { + if (!pass->groups[i].used) continue; + size_t at = group_slot(order_bits(pass->groups[i].key), capacity); + while (groups[at].used) at = (at + 1) & (capacity - 1); + groups[at] = pass->groups[i]; + } + free(pass->groups); + pass->groups = groups; + pass->capacity = capacity; + } + size_t at = group_slot(order_bits(key), pass->capacity); + while (pass->groups[at].used) at = (at + 1) & (pass->capacity - 1); + pass->groups[at] = (Group){key, {how, 0, false, false}, true}; + ++pass->count; + return &pass->groups[at]; +} + +/* Every group's fold in one pass over the list, a leaf at a time. */ +static bool group_pass(const PureModule *m, double *values, PureBlock inner, const PureShape *shape, unsigned how, + const PureList *list, Event *per, struct PurePass *pass) { + double store[PURE_MODULE_STACK][PURE_LIST_RUN], scratch[2][PURE_LIST_RUN]; + bool one = how == PURE_FOLD_COUNT || how == PURE_FOLD_FIRST; + PureRun items; + Memo memo; + memo.known = 0; + for (bool more = pure_list_run(list, 0, &items); more; more = pure_list_next(list, &items)) { + Lanes out[2]; + if (!run_lanes(m, values, inner, per, &items, out, store, &memo, shape)) return false; + const Lanes *counts = &out[one ? 0 : 1]; + if (uniform(counts) && counts->value == 0) continue; + const double *c = lanes_of(counts, scratch[0], &items), *v = lanes_of(&out[0], scratch[1], &items); + const double *keys = items.column[shape->field].numbers; + for (unsigned i = 0; i < items.count; ++i) { + if (c[i] == 0) continue; + Group *group = group_of(pass, keys[i], how, true); + if (!group) return false; + if (!group->fold.done) fold_in(&group->fold, items.start + i, v[i], c[i]); + } + } + return true; +} + +/* The field's values in order: a radix sort of their order_bits. */ +static bool rank_pass(const PureShape *shape, const PureList *list, struct PurePass *pass) { + size_t count = pure_list_count(list), at = 0; + uint64_t *keys = malloc((count ? count : 1) * sizeof(*keys)), *spare = malloc((count ? count : 1) * sizeof(*spare)); + if (!keys || !spare) { + free(keys); + free(spare); + return false; + } + PureRun items; + for (bool more = pure_list_run(list, 0, &items); more; more = pure_list_next(list, &items)) + for (unsigned i = 0; i < items.count; ++i) keys[at++] = order_bits(items.column[shape->field].numbers[i]); + for (unsigned shift = 0; shift < 64; shift += 8) { + size_t start[257] = {0}; + for (size_t i = 0; i < count; ++i) ++start[(keys[i] >> shift & 0xFF) + 1]; + if (start[(keys[0] >> shift & 0xFF) + 1] == count) continue; /* one digit for all: in order already */ + for (unsigned d = 0; d < 256; ++d) start[d + 1] += start[d]; + for (size_t i = 0; i < count; ++i) spare[start[keys[i] >> shift & 0xFF]++] = keys[i]; + uint64_t *swap = keys; + keys = spare, spare = swap; + } + free(spare); + pass->ordered = keys; + pass->items = count; + return true; +} + +/* How many of the ordered values relate to key: the first not below key + * (or above it), counted from the start or the end. */ +static size_t ranked(const struct PurePass *pass, uint8_t relation, double key) { + uint64_t bits = order_bits(key); + bool above = relation == PURE_OP_GT || relation == PURE_OP_LE; /* the bound past key's equals */ + size_t low = 0, high = pass->items; + while (low < high) { + size_t middle = low + (high - low) / 2; + if (above ? pass->ordered[middle] <= bits : pass->ordered[middle] < bits) low = middle + 1; + else high = middle; + } + return relation == PURE_OP_GT || relation == PURE_OP_GE ? pass->items - low : low; +} + +/* A shaped fold from its list's pass: false leaves it to run as written. */ +static bool fold_shaped(const PureModule *m, double *values, const PureOp *op, const PureList *list, Event *per, + Fold *fold) { + PureApp *app = per->app; + const PureShape *shape = &m->shape[op->shape - 1]; + struct PurePass *pass = pass_for(app, m, (uint32_t)(op - m->code), list); + if (!pass || pass->failed || (!pass->made && ++pass->asked < 2)) return false; + Slot key; + PureFault fault = app->fault; + uint32_t fault_pc = app->fault_pc; + size_t mark = per->arena->used; + PureResult status = run(m, values, shape->key, per, &key); + per->arena->used = mark; + app->fault = fault, app->fault_pc = fault_pc; + if (status != PURE_OK) return false; + if (!pass->made) { + PureBlock inner = {(uint32_t)(op - m->code) + 1, op->arg >> 16}; + pass->made = shape->form == PURE_SHAPE_GROUP ? group_pass(m, values, inner, shape, fold->how, list, per, pass) + : rank_pass(shape, list, pass); + if (!pass->made) { + free_pass(pass); + pass->groups = NULL, pass->ordered = NULL; + pass->failed = true; + return false; + } + } + if (shape->form == PURE_SHAPE_RANK) { + size_t count = ranked(pass, shape->relation, key.num); + fold->total = (double)count; + fold->any = count > 0; + return true; + } + const Group *group = group_of(pass, key.num, fold->how, false); + if (group) *fold = group->fold; + return true; +} + static PureResult run(const PureModule *m, double *values, PureBlock block, Event *ev, Slot *result) { Slot stack[PURE_MODULE_STACK]; size_t sp = 0; @@ -1538,9 +1895,11 @@ static PureResult run(const PureModule *m, double *values, PureBlock block, Even Fold fold = {arg & 0xFF, 0, false, false}; Event per = *ev; per.outer = ev->row; - status = op->plan ? fold_runs(m, values, inner, list, &per, &fold) - : fold_rows(m, values, inner, &per, 0, pure_list_count(list), &fold); - if (status != PURE_OK) return status; + if (!op->shape || !fold_shaped(m, values, op, list, &per, &fold)) { + status = op->plan ? fold_runs(m, values, inner, list, &per, &fold) + : fold_rows(m, values, inner, &per, 0, pure_list_count(list), &fold); + if (status != PURE_OK) return status; + } if (!fold.any && fold.how >= PURE_FOLD_MAX) { kind = PURE_FAULT_NOT_FOUND; goto failed; } if (!isfinite(fold.total)) { kind = PURE_FAULT_NON_FINITE; goto failed; } stack[sp].num = fold.total; @@ -1818,6 +2177,7 @@ void pure_app_stop(PureApp *app) { pure_undo_free(&app->undo); for (unsigned i = 0; i < app->row_tables; ++i) free(app->rows[i].items); free(app->rows); + drop_passes(app); free(app->arena.bytes); free(app->view_arena.bytes); memset(app, 0, sizeof(*app)); @@ -2173,7 +2533,7 @@ static struct PureRows *filtered(PureApp *app, uint32_t table) { memo.known = 0; for (bool more = pure_list_run(list, 0, &run); more; more = pure_list_next(list, &run)) { Lanes keep; - if (run_lanes(m, app->values, m->view_code[table], &ev, &run, &keep, store, &memo)) { + if (run_lanes(m, app->values, m->view_code[table], &ev, &run, &keep, store, &memo, NULL)) { const double *kept = lanes_of(&keep, scratch, &run); for (unsigned i = 0; i < run.count; ++i) if (kept[i] != 0 && !keep_row(rows, run.start + i)) return NULL; @@ -2297,6 +2657,7 @@ PureSnapshot *pure_app_snapshot(PureApp *app) { } void pure_app_read(PureApp *reader, const PureSnapshot *snapshot) { + if (reader->module != snapshot->module) drop_passes(reader); reader->module = snapshot->module; reader->reading = true; reader->changes = snapshot->changes; /* what the row cache is fresh for */ @@ -2514,6 +2875,7 @@ PURE_COLD static PureResult open_module(PureApp *app, const PureModule *module) memcpy(app->list_drafts, list_drafts, sizeof(list_drafts)); memcpy(app->saved, saved, sizeof(saved)); if (old) remap_outputs(app, old, module, true); + drop_passes(app); /* kept by instruction of the module they were made for */ app->module = module; ++app->changes; return PURE_OK; diff --git a/core/module.h b/core/module.h index 9a491c9..2da5c6b 100644 --- a/core/module.h +++ b/core/module.h @@ -84,9 +84,11 @@ typedef enum { PURE_FAULT_LIMIT, PURE_FAULT_NOT_FOUND, PURE_FAULT_ENCODING, PURE_FAULT_EXISTS } PureFault; -/* plan is the runtime's, set at load: a fold whose block has a form for - * every instruction that works on a leaf of its list at once runs so. */ -typedef struct { double value; uint32_t arg; uint8_t op, plan; } PureOp; +/* plan and shape are the runtime's, set at load: a fold whose block has a + * form for every instruction that works on a leaf of its list at once runs + * so (plan), and one in a column may be answered for every row from one + * pass over its list (shape: its PureShape + 1). */ +typedef struct { double value; uint32_t arg; uint8_t op, plan, shape; } PureOp; typedef struct { uint32_t start, length; } PureBlock; typedef struct { uint32_t event; PureBlock code; } PureHandler; typedef struct { char name[PURE_MODULE_NAME]; uint8_t type; uint32_t offset; } PureField; @@ -101,6 +103,15 @@ typedef struct { /* A record type: fields numbered from first, each a number or text. */ typedef struct { uint16_t first, count; } PureRecord; +/* A fold in a column whose test compares a field of its item with a key the + * row gives (key, a block reading no item; test, the comparison), the rest + * of it reading the item alone: a group (the field equals the key: one pass + * folds every group) or a rank (a count of items whose field orders against + * the key: one sort answers every key). relation is the comparison's + * opcode with the item on its left. */ +enum { PURE_SHAPE_GROUP = 1, PURE_SHAPE_RANK = 2, PURE_MODULE_SHAPES = 64 }; +typedef struct { uint8_t form, relation; uint16_t field; uint32_t test; PureBlock key; } PureShape; + typedef struct { uint16_t states, inputs, views, handlers, fields, collections, constants, records, record_fields; uint32_t code_count; @@ -123,6 +134,8 @@ typedef struct { uint16_t view_list[PURE_MODULE_VIEWS]; /* the list of a table or column */ 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) */ + PureShape shape[PURE_MODULE_SHAPES]; + uint8_t shapes; PureHandler handler[PURE_MODULE_HANDLERS]; /* The code and the text constants, sized to the module in one block * (storage) that load takes and pure_module_release gives back. */ @@ -255,6 +268,9 @@ typedef struct { bool links_changed; /* since the last save */ bool duplicate; /* the last delivery was heard before and not applied */ struct { const char *source; uint64_t seq; bool pending; } delivering; + /* Passes over lists that answer shaped folds, by the list's version. */ + struct PurePass *passes; + unsigned pass_count; struct PureSnapshot *snapshot; /* the newest snapshot made, or NULL */ PureRetired retired; /* list items let go since it was made */ bool reading; /* a reader: its states are a snapshot's */ @@ -304,6 +320,8 @@ PureResult pure_app_view_text(PureApp *, uint32_t view, bool (*visit)(void *, Pu /* The rows of a table view (its list's items, or those its filter keeps), * or 0 for another view. A host evaluates only the cells it shows. */ size_t pure_app_rows(PureApp *, uint32_t table); +/* How many passes the app keeps, made to answer shaped folds in columns. */ +unsigned pure_app_passes(const PureApp *); /* The item a row of a table shows, and the row that shows an item (false: * its filter leaves the item out). Without a filter both are the identity. */ bool pure_app_row_item(PureApp *, uint32_t table, size_t row, size_t *item); diff --git a/core/test-module.c b/core/test-module.c index b222597..6c76510 100644 --- a/core/test-module.c +++ b/core/test-module.c @@ -2179,7 +2179,9 @@ static void filters(void) { * a handler storing them), filters and lists: a module whose folds run a * leaf at a time answers bit for bit as the same module run item by item, * faults included (their kind and instruction). */ -typedef struct { O code[8192]; size_t count; uint32_t seed; int fold_list, outer; } Gen; +/* tree: no PICK or SLIDE (a shaped fold's block is a tree). A fold_list of + * -1 makes code that reads no fold's item. */ +typedef struct { O code[8192]; size_t count; uint32_t seed; int fold_list, outer; bool tree; } Gen; enum { LANE_K = 0, LANE_L = 4, LANE_M = 5 }; static void put_op(Gen *g, uint8_t op, uint32_t arg, double value) { @@ -2213,11 +2215,13 @@ static void gen_num(Gen *g, int depth) { PURE_OP_GT, PURE_OP_GE, PURE_OP_EQ, PURE_OP_NE, PURE_OP_AND, PURE_OP_OR, PURE_OP_MIN, PURE_OP_MAX, PURE_OP_MOD}; static const uint8_t unary[] = {PURE_OP_NEG, PURE_OP_NOT, PURE_OP_FLOOR}; - switch (roll(g, depth <= 0 ? 4 : 12)) { + unsigned choice = roll(g, depth <= 0 ? 4 : 12); + if (g->tree && (choice == 9 || choice == 10)) choice = 11; + switch (choice) { case 0: put_op(g, C, 0, pool[roll(g, LEN(pool))]); return; case 1: put_op(g, L, LANE_K, 0); return; case 2: case 3: { /* a field of an item */ - int list = depth <= 0 || roll(g, 3) ? g->fold_list : roll(g, 2) ? LANE_L : LANE_M; + int list = g->fold_list >= 0 && (depth <= 0 || roll(g, 3)) ? g->fold_list : roll(g, 2) ? LANE_L : LANE_M; gen_index(g, list, depth - 1); put_op(g, PURE_OP_ITEM_GET, (uint32_t)list | (uint32_t)roll(g, list == LANE_L ? 3 : 2) << 16, 0); return; @@ -2236,6 +2240,66 @@ static void gen_num(Gen *g, int depth) { } } +/* What reads only the fold's list, its item and constants (a shaped fold's own parts). */ +static void gen_own(Gen *g, int depth) { + static const double pool[] = {0, -0.0, 1, -1, 2, 0.5, 3, 5, 1e308}; + static const uint8_t binary[] = {PURE_OP_ADD, PURE_OP_SUB, PURE_OP_MUL, PURE_OP_DIV, PURE_OP_LT, PURE_OP_GT, + PURE_OP_EQ, PURE_OP_NE, PURE_OP_AND, PURE_OP_OR, PURE_OP_MIN, PURE_OP_MOD}; + int list = g->fold_list; + switch (roll(g, depth <= 0 ? 3 : 7)) { + case 0: put_op(g, C, 0, pool[roll(g, LEN(pool))]); return; + case 1: case 2: + if (roll(g, 4)) put_op(g, PURE_OP_ROW, (uint32_t)list, 0); + else put_op(g, roll(g, 2) ? PURE_OP_INDEX_FIRST : PURE_OP_INDEX_LAST, (uint32_t)list, 0); + put_op(g, PURE_OP_ITEM_GET, (uint32_t)list | (uint32_t)roll(g, list == LANE_L ? 3 : 2) << 16, 0); + return; + case 3: put_op(g, PURE_OP_LIST_COUNT, (uint32_t)list, 0); return; + case 4: gen_own(g, depth - 1); put_op(g, roll(g, 2) ? NEG : PURE_OP_NOT, 0, 0); return; + default: gen_own(g, depth - 1); gen_own(g, depth - 1); put_op(g, binary[roll(g, LEN(binary))], 0, 0); return; + } +} + +/* A fold in a column of L with a shape's test: the item's field against a + * key the row gives, ANDed with the item's own tests, and an own value. */ +static void gen_shaped(Gen *g, int list) { + static const uint8_t relations[] = {PURE_OP_EQ, PURE_OP_EQ, PURE_OP_LT, PURE_OP_LE, PURE_OP_GT, PURE_OP_GE}; + unsigned how = roll(g, 5) < 2 ? PURE_FOLD_COUNT : roll(g, 5), before = roll(g, 3) ? 0 : 1 + roll(g, 2), + after = roll(g, 3) ? 0 : 1; + size_t at = g->count; + put_op(g, PURE_OP_FOLD, 0, 0); + int fold_list = g->fold_list, fold_outer = g->outer; + g->fold_list = list, g->outer = LANE_L, g->tree = true; + if (how != PURE_FOLD_COUNT && how != PURE_FOLD_FIRST) gen_own(g, 2); + for (unsigned k = 0; k < before; ++k) { + gen_own(g, 2); + if (k) put_op(g, PURE_OP_AND, 0, 0); + } + bool field_first = roll(g, 2); + for (unsigned side = 0; side < 2; ++side) { + if (side == !field_first) { /* the item's field */ + put_op(g, PURE_OP_ROW, (uint32_t)list, 0); + put_op(g, PURE_OP_ITEM_GET, (uint32_t)list | (uint32_t)roll(g, list == LANE_L ? 3 : 2) << 16, 0); + } else { /* the key: the column's row's field, mostly */ + g->fold_list = -1; + if (roll(g, 5)) { + put_op(g, PURE_OP_OUTER_ROW, LANE_L, 0); + put_op(g, PURE_OP_ITEM_GET, LANE_L | (uint32_t)roll(g, 3) << 16, 0); + } else + gen_num(g, 2); + g->fold_list = list; + } + } + put_op(g, relations[roll(g, LEN(relations))], 0, 0); + if (before) put_op(g, PURE_OP_AND, 0, 0); + for (unsigned k = 0; k < after; ++k) { + gen_own(g, 2); + put_op(g, PURE_OP_AND, 0, 0); + } + g->fold_list = fold_list, g->outer = fold_outer, g->tree = false; + g->code[at].arg = FOLDING(how, list, g->count - at - 1); + if (how == PURE_FOLD_FIRST) put_op(g, PURE_OP_INDEX_NUM, 0, 0); +} + /* A fold over list leaving a number (first's index as one), in a column of L (outer) or not. */ static void gen_fold(Gen *g, int list, int outer) { unsigned how = roll(g, 5); @@ -2264,7 +2328,7 @@ static void lane_folds(void) { enum { VIEWS = 6, COLUMNS = 3, HANDLED = 3 }; static Gen g; uint32_t seed = 0x1a2b3c4du; - unsigned planned = 0, folds = 0, faults = 0, cells = 0; + unsigned planned = 0, folds = 0, faults = 0, cells = 0, shaped[3] = {0}, passes = 0; for (unsigned round = 0; round < 300; ++round) { g = (Gen){.seed = next_random(&seed), .fold_list = -1, .outer = -1}; V views[VIEWS]; @@ -2284,7 +2348,8 @@ static void lane_folds(void) { for (unsigned c = 0; c < COLUMNS; ++c) { snprintf(names[VIEWS + 1 + c], sizeof(names[0]), "c%u", c); tables[1 + c] = (W){names[VIEWS + 1 + c], 0, VIEWS + 1, (uint32_t)g.count, 0}; - gen_fold(&g, roll(&g, 3) ? LANE_L : LANE_M, LANE_L); + if (roll(&g, 3)) gen_shaped(&g, roll(&g, 3) ? LANE_L : LANE_M); + else gen_fold(&g, roll(&g, 3) ? LANE_L : LANE_M, LANE_L); tables[1 + c].length = (uint32_t)(g.count - tables[1 + c].start); } H handler = {1, (uint32_t)g.count, 0}; @@ -2302,7 +2367,9 @@ static void lane_folds(void) { for (uint32_t i = 0; i < module_b.code_count; ++i) { folds += module_a.code[i].op == PURE_OP_FOLD; planned += module_a.code[i].plan; - module_b.code[i].plan = 0; + shaped[module_a.code[i].shape ? module_a.shape[module_a.code[i].shape - 1].form : 0] += + module_a.code[i].op == PURE_OP_FOLD; + module_b.code[i].plan = module_b.code[i].shape = 0; } planned += module_a.view_plan[VIEWS]; memset(module_b.view_plan, 0, sizeof(module_b.view_plan)); @@ -2341,6 +2408,7 @@ static void lane_folds(void) { } } } + passes += pure_app_passes(&a); PureResult went_a = pure_app_input(&a, 0, PURE_EDGE_DOWN), went_b = pure_app_input(&b, 0, PURE_EDGE_DOWN); if (went_a != went_b || a.fault != b.fault || a.fault_pc != b.fault_pc || memcmp(a.values, b.values, 4 * sizeof(double))) { fprintf(stderr, "lane round %u handler: %d %d %u / %d %d %u\n", round, went_a, a.fault, a.fault_pc, went_b, b.fault, @@ -2353,8 +2421,10 @@ static void lane_folds(void) { pure_module_release(&module_a); pure_module_release(&module_b); } - CHECK(planned > folds / 2 && faults > 100 && cells > 1000); - printf("{\"lane_folds\":%u,\"planned\":%u,\"faults_alike\":%u,\"cells\":%u}\n", folds, planned, faults, cells); + CHECK(planned > folds / 2 && faults > 100 && cells > 1000 && shaped[PURE_SHAPE_GROUP] > 150 && + shaped[PURE_SHAPE_RANK] > 100 && passes > 60); + printf("{\"lane_folds\":%u,\"planned\":%u,\"groups\":%u,\"ranks\":%u,\"passes\":%u,\"faults_alike\":%u," + "\"cells\":%u}\n", folds, planned, shaped[PURE_SHAPE_GROUP], shaped[PURE_SHAPE_RANK], passes, faults, cells); } /* Damaged fold and filter modules: whatever loads runs safely. */ |
