diff options
Diffstat (limited to 'core/module.c')
| -rw-r--r-- | core/module.c | 281 |
1 files changed, 258 insertions, 23 deletions
diff --git a/core/module.c b/core/module.c index dc65a51..8e2534e 100644 --- a/core/module.c +++ b/core/module.c @@ -202,7 +202,7 @@ PURE_COLD static bool names_arg(const PureModule *m, unsigned kind, uint32_t arg case A_UNIT: return arg <= PURE_UNIT_LINE; case A_ROW: return (int)arg == row; case A_OUTER: return outer >= 0 && (int)arg == outer; - case A_FOLD: return (arg & 0xFF) <= PURE_FOLD_FIRST && state_of(m, arg >> 8 & 0xFF, PURE_STATE_LIST) && arg >> 16; + case A_FOLD: return (arg & 0xFF) <= PURE_FOLD_DISTINCT && state_of(m, arg >> 8 & 0xFF, PURE_STATE_LIST) && arg >> 16; case A_EACH: return !(arg & 0xFF) && state_of(m, arg >> 8 & 0xFF, PURE_STATE_LIST) && arg >> 16; case A_LOCAL: /* a slot, and a number or an index of a list */ return (arg & 0xFF) < PURE_MODULE_LOCALS && (arg >> 8 & 0xFF) <= 1 && @@ -548,7 +548,7 @@ PURE_COLD static void shape_fold(PureModule *m, uint32_t 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; + if (!fold->plan || m->shapes == PURE_MODULE_SHAPES || how == PURE_FOLD_DISTINCT) return; Node *nodes = malloc(block.length * sizeof(*nodes)); int roots[PURE_MODULE_STACK], tests[16]; unsigned count = 0, tested = 0; @@ -608,6 +608,141 @@ PURE_COLD static bool locals_agree(const PureModule *m, PureBlock block) { return true; } +/* Whether a node's subtree cannot fault: no division, no overflow of a sum + * or product, no key or item that may be missing (an item read through the + * item itself or the column's row is there). */ +PURE_COLD static bool faultless(const PureModule *m, PureBlock block, const Node *nodes, int at) { + const PureOp *op = &m->code[block.start + at]; + switch (op->op) { + case PURE_OP_CONST: case PURE_OP_LOAD: case PURE_OP_FIELD: case PURE_OP_LOCAL_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_INDEX_LOAD: case PURE_OP_INDEX_NUM: case PURE_OP_LT: case PURE_OP_LE: case PURE_OP_GT: case PURE_OP_GE: + case PURE_OP_EQ: case PURE_OP_NE: case PURE_OP_AND: case PURE_OP_OR: case PURE_OP_NOT: case PURE_OP_NEG: + case PURE_OP_FLOOR: case PURE_OP_MIN: case PURE_OP_MAX: case PURE_OP_LIST_HAS: + break; + case PURE_OP_ITEM_GET: { + uint8_t index = m->code[block.start + nodes[at].kid[0]].op; + if (index != PURE_OP_ROW && index != PURE_OP_OUTER_ROW) return false; + break; + } + default: return false; + } + for (unsigned k = 0; k < 3; ++k) + if (nodes[at].kid[k] >= 0 && !faultless(m, block, nodes, nodes[at].kid[k])) return false; + return true; +} + +/* The range of a keyed list's keys a planned block's test allows (see + * PureRange): a filter's other parts may fault, a fold's or walk's may not; + * a fold's value (the first of two) neither. */ +PURE_COLD static bool block_range(const PureModule *m, PureBlock block, int list, bool faults, PureRange *out) { + if (!m->state_key[list] || field_type(m, (uint32_t)list, m->state_key[list] - 1u) != T_NUM || !block.length) return false; + uint32_t key = m->state_key[list] - 1u; + Node *nodes = malloc(block.length * sizeof(*nodes)); + int roots[PURE_MODULE_STACK], tests[16]; + unsigned count = 0, tested = 0; + bool ok = nodes && block_tree(m, block, list, nodes, roots, &count) && count >= 1 && + conjuncts(m, block, nodes, roots[count - 1], tests, &tested) && + (faults || count == 1 || faultless(m, block, nodes, roots[0])); + PureRange range = {0}; + for (unsigned k = 0; ok && k < tested; ++k) { + const Node *node = &nodes[tests[k]]; + uint8_t op = m->code[block.start + tests[k]].op; + bool bounded = false; + for (unsigned side = 0; side < 2 && !bounded && op >= PURE_OP_LT && op <= PURE_OP_EQ; ++side) { + int item = node->kid[side], value = node->kid[1 - side]; + const PureOp *get = &m->code[block.start + item]; + if (get->op != PURE_OP_ITEM_GET || list_of(get->arg) != (uint32_t)list || item_field(get->arg) != key || + m->code[block.start + nodes[item].kid[0]].op != PURE_OP_ROW || nodes[value].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, [PURE_OP_EQ] = PURE_OP_EQ}; + uint8_t relation = side ? flipped[op] : op; /* the key REL the value */ + PureBlock bound = {nodes[value].first, block.start + (uint32_t)value + 1 - nodes[value].first}; + bool lower = relation == PURE_OP_GT || relation == PURE_OP_GE || relation == PURE_OP_EQ, + upper = relation == PURE_OP_LT || relation == PURE_OP_LE || relation == PURE_OP_EQ; + if ((lower && range.lower_relation) || (upper && range.upper_relation)) continue; /* one of each */ + if (lower) range.lower_relation = relation == PURE_OP_EQ ? PURE_OP_GE : relation, range.lower = bound; + if (upper) range.upper_relation = relation == PURE_OP_EQ ? PURE_OP_LE : relation, range.upper = bound; + bounded = true; + } + if (!bounded && !faults && !faultless(m, block, nodes, tests[k])) ok = false; + } + free(nodes); + if (!ok || (!range.lower_relation && !range.upper_relation)) return false; + *out = range; + return true; +} + +PURE_COLD static uint8_t keep_range(PureModule *m, const PureRange *range) { + if (m->ranges == PURE_MODULE_RANGES) return 0; + m->range[m->ranges++] = *range; + return m->ranges; +} + +/* Whether a walk's bounds read nothing its block writes: the states and + * locals it stores, the lists it changes. A bound is found once, before + * the walk; the walk's where reads it anew for every item. */ +PURE_COLD static bool bounds_fixed(const PureModule *m, const PureRange *range, PureBlock body) { + uint64_t states = 0, locals = 0, lists = 0; + for (uint32_t pc = body.start; pc < body.start + body.length; ++pc) { + const PureOp *op = &m->code[pc]; + switch (op->op) { + case PURE_OP_STORE: case PURE_OP_CUR_STORE: case PURE_OP_INDEX_STORE: case PURE_OP_TEXT_INSERT: + case PURE_OP_TEXT_REMOVE: case PURE_OP_TEXT_SET: + states |= (uint64_t)1 << (op->arg & 63); + break; + case PURE_OP_LOCAL_STORE: 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; + default: break; + } + } + for (unsigned side = 0; side < 2; ++side) { + PureBlock bound = side ? range->upper : range->lower; + if (!(side ? range->upper_relation : range->lower_relation)) continue; + for (uint32_t pc = bound.start; pc < bound.start + bound.length; ++pc) { + const PureOp *op = &m->code[pc]; + int list = list_read(op); + if ((op->op == PURE_OP_LOAD || op->op == PURE_OP_INDEX_LOAD) && states >> (op->arg & 63) & 1) return false; + if (op->op == PURE_OP_LOCAL_LOAD && locals >> (op->arg & 63) & 1) return false; + if (list >= 0 && lists >> (list & 63) & 1) return false; + } + } + return true; +} + +/* A walk whose block begins with its where (a test, then a jump to the + * block's end when it fails) and a planned fold: the range their test allows. */ +PURE_COLD static void range_code(PureModule *m, PureBlock block, int input) { + for (uint32_t pc = block.start; pc < block.start + block.length; ++pc) { + PureOp *op = &m->code[pc]; + PureRange range; + if (op->op == PURE_OP_FOLD) { + if (op->plan && block_range(m, (PureBlock){pc + 1, op->arg >> 16}, (int)(op->arg >> 8 & 0xFF), false, &range)) + op->range = keep_range(m, &range); + pc += op->arg >> 16; + continue; + } + if (op->op != PURE_OP_EACH) continue; + uint32_t end = pc + 1 + (op->arg >> 16); + int height = 0; + for (uint32_t at = pc + 1; at < end; ++at) { + const PureOp *next = &m->code[at]; + if (next->op == PURE_OP_JZ) { + PureBlock where = {pc + 1, at - pc - 1}; + if (height == 1 && next->arg == end && block_has_lanes(m, where, input) && + block_range(m, where, (int)(op->arg >> 8 & 0xFF), false, &range) && + bounds_fixed(m, &range, (PureBlock){at + 1, end - at - 1})) + op->range = keep_range(m, &range); + break; + } + if (!has_lanes(m, next, input) || next->op == PURE_OP_PICK || next->op == PURE_OP_SLIDE) break; + height += 1 - (int)arity(next->op); + } + } +} + 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; @@ -916,6 +1051,10 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule plan_folds(out, out->view_code[i], given); out->view_plan[i] = out->view_type[i] == PURE_VIEW_TABLE && out->view_code[i].length && block_has_lanes(out, out->view_code[i], given); + PureRange range; + if (out->view_plan[i] && block_range(out, out->view_code[i], out->view_list[i], true, &range)) + out->view_range[i] = keep_range(out, &range); + if (out->view_type[i] != PURE_VIEW_TABLE) range_code(out, out->view_code[i], given); /* 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) { @@ -925,7 +1064,9 @@ PURE_COLD PureResult pure_module_load(const void *bytes, size_t size, PureModule } for (uint32_t i = 0; i < out->handlers; ++i) { int input = event_input(out->handler[i].event); - plan_folds(out, out->handler[i].code, input >= 0 && out->input[input].kind == PURE_KIND_MESSAGE ? input : -1); + input = input >= 0 && out->input[input].kind == PURE_KIND_MESSAGE ? input : -1; + plan_folds(out, out->handler[i].code, input); + range_code(out, out->handler[i].code, input); } return PURE_OK; refused: @@ -975,8 +1116,9 @@ static double minimum(double a, double b) { return a < b || (a == b && signbit(a static double maximum(double a, double b) { return a > b || (a == b && !signbit(a)) ? a : b; } typedef struct { double num; PureBytes bytes; } Slot; -/* A walk (EACH): the list, the item it runs for, and whether that item left. */ -typedef struct { uint32_t list; size_t at; bool removed; } Walk; +/* A walk (EACH): the list, the item it runs for, where it ends (items the + * walk removes move it), and whether that item left. */ +typedef struct { uint32_t list; size_t at, end; bool removed; } Walk; typedef struct { PureApp *app; const PureInputShape *shape; /* message, or NULL */ @@ -1458,21 +1600,66 @@ static bool run_lanes(const PureModule *m, const double *values, PureBlock block return true; } -/* A fold's running value: what count, sum, max, min or first has so far. */ -typedef struct { unsigned how; double total; bool any, done; } Fold; +/* The values a distinct fold has seen: open addressing over their bits (-0 + * as 0), UINT64_MAX free (no finite number's bits). */ +typedef struct { uint64_t *keys; size_t capacity, count; } Seen; + +/* A fold's running value: what count, sum, max, min, first or distinct has so far. */ +typedef struct { unsigned how; double total; bool any, done; Seen seen; } Fold; -/* One item: its value, and whether it counts (for count and first, the value is). */ -static void fold_in(Fold *fold, size_t row, double value, double counts) { - if (counts == 0) return; +static size_t seen_slot(uint64_t bits, size_t capacity) { + bits ^= bits >> 33, bits *= 0xff51afd7ed558ccdu, bits ^= bits >> 33; + return (size_t)bits & (capacity - 1); +} + +/* Whether value is new to the set (added); false without memory too (*full). */ +static bool seen_new(Seen *seen, double value, bool *full) { + uint64_t bits; + value = value == 0 ? 0 : value; + memcpy(&bits, &value, sizeof(bits)); + if (2 * (seen->count + 1) > seen->capacity) { /* half the slots free */ + size_t capacity = seen->capacity ? 2 * seen->capacity : 64; + uint64_t *keys = malloc(capacity * sizeof(*keys)); + if (!keys) { *full = true; return false; } + memset(keys, 0xFF, capacity * sizeof(*keys)); + for (size_t i = 0; i < seen->capacity; ++i) { + if (seen->keys[i] == UINT64_MAX) continue; + size_t at = seen_slot(seen->keys[i], capacity); + while (keys[at] != UINT64_MAX) at = (at + 1) & (capacity - 1); + keys[at] = seen->keys[i]; + } + free(seen->keys); + seen->keys = keys; + seen->capacity = capacity; + } + size_t at = seen_slot(bits, seen->capacity); + for (; seen->keys[at] != UINT64_MAX; at = (at + 1) & (seen->capacity - 1)) + if (seen->keys[at] == bits) return false; + seen->keys[at] = bits; + ++seen->count; + return true; +} + +/* One item: its value, and whether it counts (for count and first, the + * value is). false without memory (a distinct fold's set). */ +static bool fold_in(Fold *fold, size_t row, double value, double counts) { + if (counts == 0) return true; unsigned how = fold->how; if (how == PURE_FOLD_FIRST) { fold->total = (double)row; fold->any = fold->done = true; - return; + return true; + } + if (how == PURE_FOLD_DISTINCT) { + bool full = false; + fold->total += seen_new(&fold->seen, value, &full); + fold->any = true; + return !full; } fold->total = how == PURE_FOLD_COUNT ? fold->total + 1 : how == PURE_FOLD_SUM ? fold->total + value : !fold->any ? value : how == PURE_FOLD_MAX ? maximum(fold->total, value) : minimum(fold->total, value); fold->any = true; + return true; } /* The fold's block for its items from first to last, one at a time. */ @@ -1486,7 +1673,7 @@ static PureResult fold_rows(const PureModule *m, double *values, PureBlock inner PureResult status = run(m, values, inner, per, out); per->arena->used = mark; if (status != PURE_OK) return status; - fold_in(fold, row, out[0].num, one ? out[0].num : out[1].num); + if (!fold_in(fold, row, out[0].num, one ? out[0].num : out[1].num)) return PURE_ALLOCATION; } return PURE_OK; } @@ -1495,13 +1682,15 @@ static PureResult fold_rows(const PureModule *m, double *values, PureBlock inner * fault goes item by item. The items join the fold in order, so a sum adds * as it would one at a time. */ static PureResult fold_runs(const PureModule *m, double *values, PureBlock inner, const PureList *list, Event *per, - Fold *fold) { + Fold *fold, size_t from, size_t to) { double store[PURE_MODULE_STACK][PURE_LIST_RUN], scratch[2][PURE_LIST_RUN]; bool one = fold->how == PURE_FOLD_COUNT || fold->how == PURE_FOLD_FIRST; PureRun items; Memo memo; memo.known = 0; - for (bool more = pure_list_run(list, 0, &items); more && !fold->done; more = pure_list_next(list, &items)) { + /* Items from and to (a range's): whole leaves, whose items out of range the test leaves out. */ + for (bool more = from < to && pure_list_run(list, from, &items); more && !fold->done && items.start < to; + more = pure_list_next(list, &items)) { Lanes out[2]; 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); @@ -1524,11 +1713,42 @@ static PureResult fold_runs(const PureModule *m, double *values, PureBlock inner fold->total = total; fold->any = any; } else - for (unsigned i = 0; i < items.count && !fold->done; ++i) fold_in(fold, items.start + i, v[i], c[i]); + for (unsigned i = 0; i < items.count && !fold->done; ++i) + if (!fold_in(fold, items.start + i, v[i], c[i])) return PURE_ALLOCATION; } return PURE_OK; } +/* The positions of the items whose keys a range allows: its bounds run + * alone, then found by key. false when a bound faults: the test then runs + * for every item, as written (and faults as it would). */ +static bool range_bounds(const PureModule *m, double *values, const PureRange *range, const PureList *list, unsigned key, + Event *ev, size_t *from, size_t *to) { + PureApp *app = ev->app; + PureFault fault = app->fault; + uint32_t fault_pc = app->fault_pc; + bool ok = true; + *from = 0; + *to = pure_list_count(list); + for (unsigned side = 0; side < 2 && ok; ++side) { + uint8_t relation = side ? range->upper_relation : range->lower_relation; + if (!relation) continue; + Slot bound; + size_t mark = ev->arena->used; + ok = run(m, values, side ? range->upper : range->lower, ev, &bound) == PURE_OK; + ev->arena->used = mark; + if (!ok) break; + bool found; + size_t at = pure_list_seek(list, key, (PureCell){bound.num, NULL, 0}, &found); + at += (relation == PURE_OP_GT || relation == PURE_OP_LE) && found; /* past the key itself */ + if (side) *to = at < *to ? at : *to; + else *from = at; + } + app->fault = fault, app->fault_pc = fault_pc; + if (*from > *to) *from = *to; + return 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, @@ -1640,7 +1860,7 @@ static Group *group_of(struct PurePass *pass, double key, unsigned how, bool add } 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->groups[at] = (Group){key, {how, 0, false, false, {0}}, true}; ++pass->count; return &pass->groups[at]; } @@ -1664,7 +1884,7 @@ static bool group_pass(const PureModule *m, double *values, PureBlock inner, con 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]); + if (!group->fold.done && !fold_in(&group->fold, items.start + i, v[i], c[i])) return false; } } return true; @@ -1973,6 +2193,7 @@ static PureResult run(const PureModule *m, double *values, PureBlock block, Even if (ev->walk && ev->walk->list == arg) { /* the walk goes on with the item after */ if (at < ev->walk->at) --ev->walk->at; else if (at == ev->walk->at) ev->walk->removed = true; + if (at < ev->walk->end && ev->walk->end != SIZE_MAX) --ev->walk->end; } continue; } @@ -2015,15 +2236,22 @@ static PureResult run(const PureModule *m, double *values, PureBlock block, Even PureBlock inner = {pc, arg >> 16}; PureList *list = app->lists[arg >> 8 & 0xFF]; pc += inner.length; - Fold fold = {arg & 0xFF, 0, false, false}; + Fold fold = {arg & 0xFF, 0, false, false, {0}}; Event per = *ev; per.outer = ev->row; if (!op->shape || !fold_shaped(m, values, op, list, &per, &fold)) { - status = op->plan ? fold_runs(m, values, inner, list, &per, &fold) + size_t from = 0, to = pure_list_count(list); + if (op->range) /* only the items a range of keys allows can count */ + range_bounds(m, values, &m->range[op->range - 1], list, m->state_key[arg >> 8 & 0xFF] - 1u, &per, &from, &to); + status = op->plan ? fold_runs(m, values, inner, list, &per, &fold, from, to) : fold_rows(m, values, inner, &per, 0, pure_list_count(list), &fold); + free(fold.seen.keys); if (status != PURE_OK) return status; } - if (!fold.any && fold.how >= PURE_FOLD_MAX) { kind = PURE_FAULT_NOT_FOUND; goto failed; } + if (!fold.any && (fold.how == PURE_FOLD_MAX || fold.how == PURE_FOLD_MIN || fold.how == PURE_FOLD_FIRST)) { + kind = PURE_FAULT_NOT_FOUND; + goto failed; + } if (!isfinite(fold.total)) { kind = PURE_FAULT_NON_FINITE; goto failed; } stack[sp].num = fold.total; stack[sp++].bytes.owner = (uint16_t)(arg >> 8 & 0xFF); /* first: the list the index is in */ @@ -2033,11 +2261,14 @@ static PureResult run(const PureModule *m, double *values, PureBlock block, Even /* The block that follows runs for each item the list has, as the * handler's code with ROW the item; the list only shrinks meanwhile. */ PureBlock inner = {pc, arg >> 16}; - Walk walk = {arg >> 8 & 0xFF, 0, false}; + Walk walk = {arg >> 8 & 0xFF, 0, SIZE_MAX, false}; pc += inner.length; Event per = *ev; per.walk = &walk; - while (walk.at < pure_list_count(app->lists[walk.list])) { + if (op->range) /* a where that bounds the key: only the items in bounds */ + range_bounds(m, values, &m->range[op->range - 1], app->lists[walk.list], m->state_key[walk.list] - 1u, &per, + &walk.at, &walk.end); + while (walk.at < walk.end && walk.at < pure_list_count(app->lists[walk.list])) { size_t mark = arena->used; per.row = walk.at; walk.removed = false; @@ -2734,7 +2965,11 @@ static struct PureRows *filtered(PureApp *app, uint32_t table) { Memo memo; memo.known = 0; if (!argue(app, table, &ev)) return NULL; - for (bool more = pure_list_run(list, 0, &run); more; more = pure_list_next(list, &run)) { + size_t from = 0, to = pure_list_count(list); + if (m->view_range[table]) /* items out of the range fail the filter: they are not read */ + range_bounds(m, (double *)app->values, &m->range[m->view_range[table] - 1], list, + m->state_key[m->view_list[table]] - 1u, &ev, &from, &to); + for (bool more = from < to && pure_list_run(list, from, &run); more && run.start < to; more = pure_list_next(list, &run)) { Lanes keep; if (run_lanes(m, app->values, m->view_code[table], &ev, &run, &keep, store, &memo, NULL)) { const double *kept = lanes_of(&keep, scratch, &run); |
