aboutsummaryrefslogtreecommitdiffstats
path: root/core/module.c
diff options
context:
space:
mode:
Diffstat (limited to 'core/module.c')
-rw-r--r--core/module.c374
1 files changed, 368 insertions, 6 deletions
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;