aboutsummaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/module-batch.c237
1 files changed, 232 insertions, 5 deletions
diff --git a/core/module-batch.c b/core/module-batch.c
index 69b538c..cfab5b7 100644
--- a/core/module-batch.c
+++ b/core/module-batch.c
@@ -93,16 +93,23 @@ typedef struct {
* and at a LOCAL_LOAD beginning x = x op e how far on op is (by; 0: none).
* A nested loop's instructions have no role (sums and recurrences are the
* loop's own block's) but may begin x = x op e. Its recurrences (NULL:
- * none). failed: more sums or recurrences than a batch holds. */
-enum { AT_PLAIN, AT_AGAIN, AT_ADD, AT_SET, AT_UPDATE, AT_RECORD };
+ * none). failed: more sums or recurrences than a batch holds. A segment
+ * (role AT_SEGMENT at its first instruction, docs/design/025's stage 2):
+ * instructions to its end (to) that make one uniform or affine value from
+ * uniform and affine values alone, then its LOCAL_STORE if store, run at
+ * once in scalars when the batch runs plainly (the pass's own way else). */
+enum { AT_PLAIN, AT_AGAIN, AT_ADD, AT_SET, AT_UPDATE, AT_RECORD, AT_SEGMENT };
typedef struct { uint8_t role, index; uint16_t by; } PlanAt;
+typedef struct { uint32_t to; bool store; } Segment;
+enum { SEGMENTS = 255 };
typedef struct PureBatchPlan {
uint32_t loop, start, end;
uint64_t sets[LOCALS / 64];
bool items, failed;
- unsigned sum_count;
+ unsigned sum_count, segment_count;
Sum sums[SUMS];
Recurring *recur;
+ Segment *segments;
PlanAt at[];
} PureBatchPlan;
/* An item a LIST_APPEND at pc appends in the lanes of mask (NULL: all): its fields. */
@@ -1669,6 +1676,115 @@ static bool plan_sums(const PureModule *m, PureBatchPlan *p) {
return true;
}
+/* What the load can tell of a value a segment makes: uniform, affine (or
+ * uniform, as its parts come out), or neither (no segment holds it). */
+enum { K_NONE, K_UNIFORM, K_AFFINE };
+
+/* The plan's segments (docs/design/025, stage 2): each run of instructions
+ * with no other part in the plan (no role, no x = x op e, no value the same
+ * code makes again or keeps), no jump landing inside, two or more, that
+ * makes one value from values it makes itself, each uniform or affine as
+ * far as the load can tell: a constant, LOAD, field, INDEX_LOAD, a walk's
+ * item (not the loop's own walk's, nor one walked in the block), a local
+ * the block does not set, the loop's value, a nested count loop's value in
+ * its block, a local set once in the block by a segment before here; an
+ * item's field at a uniform row (not a FIND's joined read); NEG, NOT (not
+ * before a JZ), FLOOR, arithmetic and comparisons by the pass's rules. Its
+ * LOCAL_STORE too when the statement ends there. Each is checked again as
+ * it runs (segment). slot: the loop's local (LOCALS: a walk's); walk: the
+ * list a walk walks (-1: a count's loop). */
+static void plan_segments(const PureModule *m, PureBatchPlan *p, unsigned slot, int walk) {
+ uint32_t start = p->start, end = p->end;
+ uint8_t *landed = calloc(end - start + 1, 1), stores[LOCALS] = {0}, local[LOCALS] = {0}, kinds[PURE_MODULE_STACK + 1];
+ Segment found[SEGMENTS];
+ unsigned count = 0;
+ if (!landed) return;
+ for (uint32_t pc = start; pc < end; ++pc) { /* where jumps land, where nested blocks begin and end; how often each local is set */
+ const PureOp *op = &m->code[pc];
+ if ((op->op == PURE_OP_JZ || op->op == PURE_OP_JMP) && op->arg >= start && op->arg <= end) landed[op->arg - start] = 1;
+ if (op->op == PURE_OP_LOOP || op->op == PURE_OP_EACH) landed[pc + 1 - start] = landed[pc + 1 + (op->arg >> 16) - start] = 1;
+ if ((op->op == PURE_OP_LOCAL_STORE || op->op == PURE_OP_LOOP) && stores[op->arg & 0xFF] < 2) ++stores[op->arg & 0xFF];
+ }
+ typedef struct { uint32_t end; unsigned slot; bool each; } Around;
+ Around nest[8];
+ unsigned depth = 0, walks = 0; /* walks: the nested walks around pc */
+ for (uint32_t pc = start; pc < end;) {
+ while (depth && pc >= nest[depth - 1].end) {
+ --depth;
+ if (nest[depth].each) --walks;
+ else local[nest[depth].slot] = K_NONE; /* its last value, whatever it was */
+ }
+ const PureOp *op = &m->code[pc];
+ if (op->op == PURE_OP_LOOP || op->op == PURE_OP_EACH) {
+ bool each = op->op == PURE_OP_EACH;
+ if (depth == 8) break; /* (deeper: no more segments) */
+ nest[depth++] = (Around){pc + 1 + (op->arg >> 16), op->arg & 0xFF, each};
+ if (each) ++walks;
+ else local[op->arg & 0xFF] = K_UNIFORM; /* the same round in every lane, as a rule */
+ ++pc;
+ continue;
+ }
+ uint32_t to = 0;
+ unsigned h0 = m->height[pc];
+ for (uint32_t q = pc; q < end && (q == pc || !landed[q - start]); ++q) {
+ const PureOp *o = &m->code[q];
+ const PlanAt *at = &p->at[q - start];
+ if (at->role || at->by || m->reuse_from[q] || m->reuse_length[q]) break;
+ unsigned h = m->height[q], pops = 0, k = K_NONE, l = o->arg & 0xFF;
+ switch (o->op) {
+ case PURE_OP_CONST: case PURE_OP_LOAD: case PURE_OP_FIELD: case PURE_OP_INDEX_LOAD: k = K_UNIFORM; break;
+ case PURE_OP_ROW: k = walks || (int)o->arg == walk ? K_NONE : K_UNIFORM; break;
+ case PURE_OP_LOCAL_LOAD: k = l == slot ? K_AFFINE : !(p->sets[l / 64] >> (l % 64) & 1) ? K_UNIFORM : local[l]; break;
+ case PURE_OP_ITEM_GET:
+ pops = 1;
+ if (!(q > start && m->code[q - 1].op == PURE_OP_LIST_FIND && m->code[q - 1].plan & PLAN_JOINED) && h >= h0 + 1 &&
+ kinds[h - 1] == K_UNIFORM)
+ k = K_UNIFORM;
+ break;
+ case PURE_OP_NOT:
+ if (q + 1 < end && m->code[q + 1].op == PURE_OP_JZ) break; /* a branch */
+ /* fall through */
+ case PURE_OP_NEG: case PURE_OP_FLOOR:
+ pops = 1;
+ if (h >= h0 + 1) k = kinds[h - 1] == K_UNIFORM ? K_UNIFORM : o->op == PURE_OP_FLOOR ? kinds[h - 1] : K_NONE;
+ break;
+ case PURE_OP_ADD: case PURE_OP_SUB: case PURE_OP_MUL: case PURE_OP_DIV: case PURE_OP_MOD: case PURE_OP_MIN: case PURE_OP_MAX:
+ 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:
+ pops = 2;
+ if (h >= h0 + 2 && kinds[h - 2] && kinds[h - 1]) {
+ unsigned x = kinds[h - 2], y = kinds[h - 1];
+ if (x == K_UNIFORM && y == K_UNIFORM) k = K_UNIFORM;
+ else if (o->op == PURE_OP_ADD || o->op == PURE_OP_SUB) k = K_AFFINE;
+ else if (o->op == PURE_OP_MUL && (x == K_UNIFORM || y == K_UNIFORM)) k = K_AFFINE;
+ }
+ break;
+ default: break;
+ }
+ if (k == K_NONE || h < h0 + pops) break; /* not one, or its operands made before the run */
+ kinds[h - pops] = (uint8_t)k;
+ if (m->height[q + 1] == h0 + 1) to = q + 1; /* one value made so far */
+ }
+ if (to && to - pc >= 2 && count < SEGMENTS) {
+ const PureOp *set = &m->code[to];
+ bool store = to < end && !landed[to - start] && set->op == PURE_OP_LOCAL_STORE && m->height[to + 1] == h0 && !p->at[to - start].role &&
+ !m->reuse_from[to] && !m->reuse_length[to];
+ if (store && stores[set->arg & 0xFF] == 1) local[set->arg & 0xFF] = kinds[h0];
+ else if (store) local[set->arg & 0xFF] = K_NONE;
+ p->at[pc - start].role = AT_SEGMENT, p->at[pc - start].index = (uint8_t)count;
+ found[count++] = (Segment){to, store};
+ pc = store ? to + 1 : to;
+ continue;
+ }
+ if (op->op == PURE_OP_LOCAL_STORE) local[op->arg & 0xFF] = K_NONE; /* set otherwise: not known here on */
+ ++pc;
+ }
+ free(landed);
+ if (count && (p->segments = malloc(count * sizeof(*p->segments)))) memcpy(p->segments, found, count * sizeof(*p->segments)), p->segment_count = count;
+ else /* (no memory: the pass's way throughout) */
+ for (uint32_t pc = start; pc < end; ++pc)
+ if (p->at[pc - start].role == AT_SEGMENT) p->at[pc - start].role = AT_PLAIN;
+}
+
/* The plan of the batch loop at pc, or NULL (no memory). */
static PureBatchPlan *plan_loop(const PureModule *m, uint32_t pc) {
uint32_t start = pc + 1, end = start + (m->code[pc].arg >> 16);
@@ -1692,6 +1808,8 @@ static PureBatchPlan *plan_loop(const PureModule *m, uint32_t pc) {
p->at[sum->again - start].role = AT_AGAIN, p->at[sum->add - start].role = AT_ADD, p->at[sum->set - start].role = AT_SET;
p->at[sum->again - start].index = p->at[sum->add - start].index = p->at[sum->set - start].index = (uint8_t)k;
}
+ if (!p->failed) plan_segments(m, p, m->code[pc].op == PURE_OP_EACH ? LOCALS : m->code[pc].arg & 0xFF,
+ m->code[pc].op == PURE_OP_EACH ? (int)(m->code[pc].arg >> 8 & 0xFF) : -1);
return p;
}
@@ -1711,7 +1829,7 @@ bool pure_batch_plans(PureModule *m) {
}
void pure_batch_plans_free(PureModule *m) {
- for (uint32_t i = 0; i < m->batch_plan_count; ++i) free(m->batch_plans[i]->recur), free(m->batch_plans[i]);
+ for (uint32_t i = 0; i < m->batch_plan_count; ++i) free(m->batch_plans[i]->recur), free(m->batch_plans[i]->segments), free(m->batch_plans[i]);
free(m->batch_plans);
m->batch_plans = NULL, m->batch_plan_count = 0;
}
@@ -1727,6 +1845,79 @@ static const PureBatchPlan *plan_of(const PureModule *m, uint32_t pc) {
return low < m->batch_plan_count && m->batch_plans[low]->loop == pc ? m->batch_plans[low] : NULL;
}
+/* The segment at from (docs/design/025, stage 2) as the pass would run
+ * it, each value a scalar (uniform, or affine: its first lane's and its
+ * step) by the pass's own rules: its value pushed (and its LOCAL_STORE run)
+ * unless dry (then only *out). false: a value is not as the load planned
+ * (lanes, not whole, a fault, past a list's end ...): nothing done, the
+ * pass's way then. */
+static bool segment(Batch *b, uint32_t from, const Segment *sg, bool dry, Val *out) {
+ const PureModule *m = b->m;
+ Val r[PURE_MODULE_STACK], reads[4];
+ uint32_t read_list[4], read_field[4];
+ unsigned sp = 0, read_count = 0;
+ for (uint32_t pc = from; pc < sg->to; ++pc) {
+ const PureOp *op = &m->code[pc];
+ uint32_t arg = op->arg;
+ switch (op->op) {
+ case PURE_OP_CONST: r[sp++] = uniform(op->value, 0); break;
+ case PURE_OP_LOAD: r[sp++] = uniform(b->frame->values[arg].num, 0); break;
+ case PURE_OP_FIELD: r[sp++] = uniform(field_value(b, arg), 0); break;
+ case PURE_OP_INDEX_LOAD: r[sp++] = uniform(b->frame->values[arg].num, m->state_ref[arg]); break;
+ case PURE_OP_ROW:
+ if (b->nest.rows || (int)arg == b->walk) return false;
+ r[sp++] = uniform((double)b->ev->row, (uint16_t)arg);
+ break;
+ case PURE_OP_LOCAL_LOAD: { /* as local_load, in plain lanes */
+ unsigned at = arg & 0xFF;
+ Val *v = &r[sp++];
+ if (at == b->slot) {
+ if (!affine_of(b, b->reverse ? b->first + (b->n - 1) : b->first, b->reverse ? -1 : 1, v)) return false;
+ } else if (!(b->sets[at / 64] >> (at % 64) & 1)) *v = uniform(b->ev->locals[at].num, 0);
+ else if (b->state[at] == 1 && (b->local[at].kind == UNIFORM || b->local[at].kind == AFFINE)) *v = b->local[at];
+ else return false;
+ v->owner = (uint16_t)(arg >> 16);
+ break;
+ }
+ case PURE_OP_ITEM_GET: { /* as item_get, at a uniform row */
+ Val *x = &r[sp - 1];
+ uint32_t list = list_of(arg), field = item_field(arg);
+ PureList *items = b->app->lists[list];
+ if (x->kind != UNIFORM || stored(b, list, field) || !(x->value >= 0 && x->value < (double)pure_list_count(items)) ||
+ (b->items && read_count == 4))
+ return false;
+ if (b->items) reads[read_count] = *x, read_list[read_count] = list, read_field[read_count++] = field;
+ *x = uniform(pure_list_get(items, (size_t)x->value, field).num, 0);
+ break;
+ }
+ case PURE_OP_NEG: case PURE_OP_NOT: case PURE_OP_FLOOR: { /* as unary */
+ Val *x = &r[sp - 1];
+ if (x->kind == UNIFORM) *x = uniform(op->op == PURE_OP_NEG ? -x->value : op->op == PURE_OP_NOT ? x->value == 0 : floor(x->value), 0);
+ else if (op->op == PURE_OP_FLOOR && x->kind == AFFINE) x->owner = 0;
+ else return false;
+ break;
+ }
+ default: { /* an operation of two, as binary: uniform, or affine (affine_op) */
+ Val y = r[--sp], *x = &r[sp - 1];
+ if (x->kind == UNIFORM && y.kind == UNIFORM) {
+ double value;
+ if (!number_op(op->op, x->value, y.value, &value)) return false;
+ *x = uniform(value, 0);
+ } else if (!affine_op(b, op->op, x, &y))
+ return false;
+ }
+ }
+ }
+ if (dry) {
+ *out = r[0];
+ return true;
+ }
+ for (unsigned i = 0; i < read_count; ++i) read_of(b, read_list[i], read_field[i], true, &reads[i]);
+ b->stack[b->sp++] = r[0];
+ if (sg->store) local_store(b, m->code[sg->to].arg);
+ return true;
+}
+
/* ---- The pass */
static void nested(Batch *b, const PureOp *op, uint32_t pc);
@@ -1828,11 +2019,33 @@ static bool keep_filtered(Batch *b) {
}
/* The block from start to end once for the batch's lanes: false where it failed. */
+#ifdef PURE_BATCH_CHECK
+#include <stdio.h>
+/* A segment's value (made dry) against the pass's own at its end (while docs/design/025's stage 2 is made). */
+static void check_segment(const Batch *b, uint32_t from, uint32_t to, const Val *dry) {
+ const Val *v = b->failed ? NULL : &b->stack[b->sp - 1];
+ if (v && v->kind == dry->kind && !memcmp(&v->value, &dry->value, sizeof(v->value)) && v->owner == dry->owner && v->u32 == dry->u32 &&
+ (v->kind != AFFINE || !memcmp(&v->step, &dry->step, sizeof(v->step))))
+ return;
+ fprintf(stderr, "batch segment %u..%u of the loop at %u differs: %s (kind %u value %.17g step %.17g owner %u) against (kind %u value %.17g step %.17g owner %u)\n",
+ from, to, b->plan->loop, b->failed ? "the pass failed" : "values", v ? v->kind : 0, v ? v->value : 0, v ? v->step : 0,
+ v ? v->owner : 0, dry->kind, dry->value, dry->step, dry->owner);
+ abort();
+}
+#endif
+
static bool pass(Batch *b, uint32_t start, uint32_t end) {
const PureModule *m = b->m;
Event *ev = b->ev;
PureApp *app = b->app;
+#ifdef PURE_BATCH_CHECK
+ uint32_t check_from = 0, check_to = UINT32_MAX;
+ Val check_v;
+#endif
for (uint32_t pc = start; pc < end && !b->failed;) {
+#ifdef PURE_BATCH_CHECK
+ if (pc == check_to) check_segment(b, check_from, check_to, &check_v), check_to = UINT32_MAX;
+#endif
if (b->sel && pc == b->selection.end) deselect(b);
arrive(b, pc);
if (b->none) { /* no lane here: on to where one goes */
@@ -1843,11 +2056,22 @@ static bool pass(Batch *b, uint32_t start, uint32_t end) {
continue;
}
const PlanAt *at_pc = &b->plan->at[pc - b->plan->start];
+ if (at_pc->role == AT_SEGMENT && !b->sel && !b->parent) { /* uniform and affine values at once, in scalars */
+ const Segment *sg = &b->plan->segments[at_pc->index];
+#ifdef PURE_BATCH_CHECK
+ if (segment(b, pc, sg, true, &check_v)) check_from = pc, check_to = sg->to; /* and on the pass's own way, to compare */
+#else
+ if (segment(b, pc, sg, false, NULL)) {
+ pc = sg->store ? sg->to + 1 : sg->to;
+ continue;
+ }
+#endif
+ }
if (at_pc->role == AT_UPDATE) { /* a recurrence's x = e: made ahead, round by round */
pc = b->plan->recur->updates[at_pc->index].set + 1;
continue;
}
- if (at_pc->role && at_pc->role != AT_RECORD) { /* a sum into a cell: its row and e, kept for the end */
+ if (at_pc->role >= AT_AGAIN && at_pc->role <= AT_SET) { /* a sum into a cell: its row and e, kept for the end */
const Sum *sum = &b->plan->sums[at_pc->index];
if (at_pc->role == AT_AGAIN) pc = sum->resume; /* the key again, its find and the cell's read: not needed */
else if (at_pc->role == AT_ADD) pc = sum->set;
@@ -2029,6 +2253,9 @@ static bool pass(Batch *b, uint32_t start, uint32_t end) {
if (op->op == PURE_OP_LIST_FIND && op->plan & PLAN_JOINED) ++at; /* its value its ITEM_GET's */
if (m->reuses && m->reuse_from[at] == PURE_REUSE_KEPT && !b->failed && b->sp > was) keep_known(b, at);
}
+#ifdef PURE_BATCH_CHECK
+ if (check_to != UINT32_MAX) check_segment(b, check_from, check_to, &check_v);
+#endif
if (b->sel) { /* (failed within a selection, or its end the block's) */
if (b->failed) b->sel = NULL, b->n = b->selection.n, b->active = b->selection.active;
else deselect(b);