aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-10-05 16:44:35 +0100
committereunhhu <[email protected]>2026-10-05 16:44:35 +0100
commit0baba8f8d3aa164c93ed72abced3c397ef10178c (patch)
treef4012c586edf405b7145c63f17845bada4e55641
parent5cf9dcb47cd0037461782335ba812481363970da (diff)
downloadpure-0baba8f8d3aa164c93ed72abced3c397ef10178c.tar.gz
pure-0baba8f8d3aa164c93ed72abced3c397ef10178c.zip
The tier's inner loops: a count's work paid for every round as it opens (the rounds it does not run given back where it breaks or gives up), a comparison only a branch reads its flags, a full list's keys read without its bits (a key's position found: its key's register), an add's or sub's register its operand's where that is read last. knapsack 16.5 to 8.5 ms (1.37x C), matmul 25.7 to 22.9
Two bugs the work checks found, there since 6cd7f5a: the outermost loop's round past the work budget gave up at the loop's instruction, with nothing on the stack (the executor opened it again: LIMIT where WORK), now at its iteration; and a phi where an if's ways meet took its merge's loop (-1), so a value made of it was taken as made outside its loop and made once for all rounds. pure-run's budget and work commands; module_tier compares the work and runs four small budgets; module_assumptions keeps both programs
-rw-r--r--cli/pure-run.c8
-rw-r--r--core/module-jit.c218
-rw-r--r--tests/check.py91
3 files changed, 253 insertions, 64 deletions
diff --git a/cli/pure-run.c b/cli/pure-run.c
index c7a252c..8ef91d9 100644
--- a/cli/pure-run.c
+++ b/cli/pure-run.c
@@ -196,7 +196,13 @@ int main(int argc, char **argv) {
else active = !active;
} else if (!strcmp(command, "save")) {
if (pure_app_save(&app) != PURE_OK) failure = "save failed";
- } else if (strcmp(command, "show")) failure = "unknown command";
+ } else if (!strcmp(command, "budget")) { /* the work each event may do (0: the default) */
+ char *end;
+ unsigned long long budget = strtoull(argument, &end, 10);
+ if (!*argument || *end) failure = "budget";
+ else pure_app_work(&app, budget);
+ } else if (!strcmp(command, "work")) printf("{\"work\":%llu}\n", (unsigned long long)app.work); /* the last event's */
+ else if (strcmp(command, "show")) failure = "unknown command";
if (batch && strcmp(command, "show") && ++unsaved == batch) {
unsaved = 0;
if (pure_app_save(&app) != PURE_OK) failure = "save failed";
diff --git a/core/module-jit.c b/core/module-jit.c
index a1e6061..1c6de64 100644
--- a/core/module-jit.c
+++ b/core/module-jit.c
@@ -2913,6 +2913,8 @@ typedef struct {
bool negzero; /* T_MUL of integers: 0 times a number below 0 is -0 (checked: else it gives up) */
bool bounded; /* an integer its loop's opening bounds (its ends checked there: no check of its own) */
bool proven; /* a list access whose key or position its loop's opening found in range */
+ bool fused; /* a comparison only the branch right after it reads: its flags, no register */
+ bool aliased; /* a full list's position found: its key's register (they are equal) */
uint16_t list, field;
int a, b, c, d; /* operands: values, or -1 */
uint32_t pc; /* its instruction */
@@ -3338,7 +3340,7 @@ static void tier_arrive(Tier *t, int g) {
}
first = m->ways[0][var];
int phi = tval(t, T_PHI, R_NUM, -1, -1, -1);
- t->vals[phi].var = var, t->vals[phi].label = g, t->vals[phi].loop = m->loop;
+ t->vals[phi].var = var, t->vals[phi].label = g, t->vals[phi].loop = t->cur_loop; /* the loop the merge is in (a loop's exit: around it) */
t->vals[phi].kind = first >= 0 ? t->vals[first].kind : K_NUM, t->vals[phi].list = first >= 0 ? t->vals[first].list : 0;
for (unsigned w = 0; w < m->way_count; ++w) tphi_in(t, phi, m->ways[w][var]);
m->phis[var] = phi;
@@ -3449,7 +3451,7 @@ static void tier_round(Tier *t, int n, uint32_t at) {
t->vals[next].pc = at;
t->state[V_STACK + PURE_MODULE_STACK] = next;
titem(t, I_LATCH, next, l->head, n);
- int latch = (int)t->item_count - 1, snap = tsnap(t, at); /* a count's work for its next round, where it gives up */
+ int latch = (int)t->item_count - 1, snap = tsnap(t, bl->end); /* a count's work for its next round: where it gives up, the generic code's iteration */
tier_back(t, l->head);
int out = tway(t, l->exit); /* done: past it */
if (!t->failed) t->items[latch].snap = snap, t->items[latch].way = out;
@@ -3808,6 +3810,7 @@ typedef struct TList {
int part[L_PARTS]; /* the values, or -1 */
bool position; /* read or set by position: FLAT, or DENSE full */
bool key; /* found by key: DENSE */
+ bool full; /* found by key and by position, or a key's position found: DENSE and full (every key below its count there) */
} TList;
static bool tfinite(const TVal *v) { return v->lo > -INFINITY && v->hi < INFINITY; }
@@ -3989,7 +3992,7 @@ static int tlist(Tier *t, TList *lists, unsigned list, unsigned part) {
static void tier_lists(Tier *t, TList *lists) {
for (unsigned i = 0; i < PURE_MODULE_STATES; ++i) {
for (unsigned p = 0; p < L_PARTS; ++p) lists[i].part[p] = -1;
- lists[i].position = lists[i].key = false;
+ lists[i].position = lists[i].key = lists[i].full = false;
}
size_t made = t->val_count;
for (size_t i = 0; i < made && !t->failed; ++i) {
@@ -4021,8 +4024,12 @@ static void tier_lists(Tier *t, TList *lists) {
default: break;
}
}
- for (unsigned i = 0; i < PURE_MODULE_STATES; ++i) /* found by key and by position: DENSE and full, its count its keys' reach */
- if (lists[i].key && lists[i].position) (void)tlist(t, lists, i, L_COUNT);
+ for (size_t i = 0; i < made; ++i)
+ if (t->vals[i].op == T_FIND) lists[t->vals[i].list].full = true;
+ for (unsigned i = 0; i < PURE_MODULE_STATES; ++i) { /* found by key and by position: DENSE and full, its count its keys' reach */
+ if (lists[i].key && lists[i].position) lists[i].full = true;
+ if (lists[i].full) (void)tlist(t, lists, i, L_COUNT);
+ }
}
/* ---- The tier: where each value lives, and its register */
@@ -4080,6 +4087,12 @@ static void tuse_way(Tier *t, int g, int way, int at) {
if (m->phis[var] >= 0 && (unsigned)way < t->vals[m->phis[var]].ins) tuse(t, t->vals[m->phis[var]].in[way], at);
}
+/* Whether loop l's latch pays for its next round: the outermost count's (the generic code opened it); the others paid for all their rounds as they opened. */
+static bool tlatch_pays(const Tier *t, int l) { return l == 0 && !t->loops[l].walk; }
+
+/* Whether item it leaves its loop early (a break): to its exit. */
+static bool tbreaks(const Tier *t, const TItem *it) { return it->kind == I_JUMP && it->loop > 0 && it->merge == t->loops[it->loop].exit; }
+
static void tneed_affine(Tier *t, bool *need, int *work, size_t *top, int x, int l) {
const TVal *v = &t->vals[x];
if (x == t->loops[l].k) return;
@@ -4118,7 +4131,11 @@ static void tier_needed(Tier *t, bool *need) {
tneed_affine(t, need, work, &top, t->loops[it->loop].bounds[b][0], it->loop);
NEED(t->loops[it->loop].bounds[b][1]);
}
- if (it->kind == I_OPEN || it->kind == I_LATCH || it->kind == I_BREAK || it->kind == I_DONE) NEED_SNAP(it->snap);
+ if (it->kind == I_OPEN || it->kind == I_BREAK || it->kind == I_DONE || (it->kind == I_LATCH && tlatch_pays(t, it->loop))) NEED_SNAP(it->snap);
+ if (tbreaks(t, it)) { /* the rounds it will not run given back */
+ NEED(t->loops[it->loop].count);
+ NEED(t->loops[it->loop].k);
+ }
}
for (size_t i = 0; i < t->val_count; ++i)
if (t->vals[i].snap >= 0 || t->vals[i].op == T_SET) NEED((int)i);
@@ -4176,7 +4193,10 @@ static bool tier_registers(Tier *t) {
break;
}
case I_UNLESS: tuse(t, it->value, p); /* fall through */
- case I_JUMP: tuse_way(t, it->merge, it->way, p); break;
+ case I_JUMP:
+ tuse_way(t, it->merge, it->way, p);
+ if (tbreaks(t, it)) tuse(t, t->loops[it->loop].count, p), tuse(t, t->loops[it->loop].k, p);
+ break;
case I_OPEN: {
const TLoop *l = &t->loops[it->loop];
tuse(t, l->count, p);
@@ -4190,16 +4210,29 @@ static bool tier_registers(Tier *t) {
tuse(t, it->value, p), tuse(t, l->count, p);
tuse_way(t, l->head, (int)t->merges[l->head].way_count - 1, p);
tuse_way(t, it->merge == l->head ? l->exit : it->merge, it->way, p);
- tuse_snap(t, it->snap, p);
+ if (tlatch_pays(t, it->loop)) tuse_snap(t, it->snap, p);
break;
}
case I_BREAK: case I_DONE: tuse(t, it->value, p), tuse_snap(t, it->snap, p); break;
default: break;
}
}
- /* A value read in a loop it was made before lives to the loop's iteration (its back edge reads it again). */
+ for (size_t v = 0; v < t->val_count; ++v) { /* a full list's position found: its key, in its key's register */
+ TVal *x = &t->vals[v];
+ x->aliased = x->op == T_FIND && t->lists[x->list].full && need[v] && x->at >= 0 && need[x->a] && t->vals[x->a].at >= 0 &&
+ t->vals[x->a].rep == R_INT && t->vals[x->a].op != T_SET;
+ }
+ for (size_t i = 1; i < t->item_count; ++i) { /* a comparison only the branch right after it reads: its flags */
+ const TItem *it = &t->items[i];
+ if (it->kind != I_UNLESS || t->items[i - 1].kind != I_VALUE || t->items[i - 1].value != it->value) continue;
+ TVal *x = &t->vals[it->value];
+ x->fused = x->op >= T_LT && x->op <= T_NE && x->uses == 0 && x->last <= base + (int)i;
+ }
+ /* A value read in a loop it was made before lives to the loop's iteration (its back edge reads it again); a key its alias's reads too. */
for (bool changed = true; changed;) {
changed = false;
+ for (size_t v = 0; v < t->val_count; ++v)
+ if (t->vals[v].aliased && t->vals[t->vals[v].a].last < t->vals[v].last) t->vals[t->vals[v].a].last = t->vals[v].last, changed = true;
for (unsigned l = 0; l < t->loop_count; ++l) {
int head = -1, latch = -1;
for (size_t i = 0; i < t->item_count; ++i) {
@@ -4219,7 +4252,7 @@ static bool tier_registers(Tier *t) {
if (!order) return false;
size_t n = 0;
for (size_t v = 0; v < t->val_count; ++v)
- if (t->vals[v].at >= 0 && t->vals[v].op != T_SET && need[v]) order[n++] = (int)v;
+ if (t->vals[v].at >= 0 && t->vals[v].op != T_SET && need[v] && !t->vals[v].fused) order[n++] = (int)v;
free(need);
for (size_t i = 1; i < n; ++i) { /* by at (stable) */
int v = order[i];
@@ -4237,6 +4270,15 @@ static bool tier_registers(Tier *t) {
unsigned size = d ? sizeof(tier_d) : sizeof(tier_x);
int *until = d ? d_until : x_until;
int pick = -1;
+ if (v->aliased) {
+ v->reg = t->vals[v->a].reg;
+ continue;
+ }
+ if (v->rep == R_INT && (v->op == T_ADD || v->op == T_SUB)) /* an operand's register where it is read last (where it may give up the code computes in scratch) */
+ for (int o = 0; o < 2 && pick < 0; ++o) {
+ const TVal *x = &t->vals[o ? v->b : v->a];
+ if (x->reg >= 0 && x->rep != R_NUM && x->last == v->at && until[x->reg] == v->at) pick = x->reg;
+ }
if (v->op == T_PHI) /* a phi: an input's register when it is free (no move on that way) */
for (unsigned w = 0; w < v->ins && pick < 0; ++w) {
const TVal *in = &t->vals[v->in[w]];
@@ -4426,6 +4468,20 @@ static void tier_moves(Tier *t, int g, int way) {
}
}
+/* Whether a way into merge g moves anything (its phis' registers their inputs'). */
+static bool tier_has_moves(const Tier *t, int g, int way) {
+ const TMerge *m = &t->merges[g];
+ if (!m->phis || way < 0) return false;
+ for (int var = 0; var < VARS; ++var) {
+ int phi = m->phis[var];
+ if (phi < 0 || t->vals[phi].reg < 0 || (unsigned)way >= t->vals[phi].ins) continue;
+ const TVal *p = &t->vals[phi], *in = &t->vals[p->in[way]];
+ if (in->reg < 0 || ((in->rep == R_NUM) == (p->rep == R_NUM) && in->reg == p->reg)) continue;
+ return true;
+ }
+ return false;
+}
+
/* What the tier changed, back where the executor keeps it: the variables
* whose values differ from memory's (in vars: a snapshot's or the end's),
* the work, the lists it set (their versions). */
@@ -4531,6 +4587,20 @@ static void tier_records(Tier *t, int n) {
store_w(e, X10, X19, offsetof(Frame, depth));
}
+/* A count opened in the tier paid for its every round: where it leaves in
+ * round k (a break, or giving up), the rounds after k given back (the
+ * generic code pays for each it runs). */
+static void tier_refund(Tier *t, int l) {
+ Emit *e = t->k->e;
+ const TLoop *lp = &t->loops[l];
+ if (l == 0 || lp->walk) return;
+ sub_reg(e, X16, treg(t, lp->count), treg(t, lp->k));
+ sub_imm(e, X16, X16, 1);
+ mov_imm(e, X17, (uint64_t)lp->length + 1);
+ mul_reg(e, X16, X16, X17);
+ add_reg(e, TIER_WORK, TIER_WORK, X16);
+}
+
/* Where snapshot s gives up: what changed written back, the stack and the
* loops as the executor keeps them at its instruction, the rest of the
* round the executor's, the generic code going on. */
@@ -4540,6 +4610,7 @@ static void tier_stub(Tier *t, int s) {
const TSnap *snap = &t->snaps[s];
const LoopAt *l0 = &k->loops[t->nest];
bind(e, snap->label);
+ for (int l = snap->loop; l > 0; l = t->loops[l].parent) tier_refund(t, l);
tier_write(t, snap->vars);
tbase(t, B_STACK, X17);
for (unsigned h = l0->base; h < snap->height; ++h) {
@@ -4571,6 +4642,19 @@ static void tier_stub(Tier *t, int s) {
b(e, l->after[0]);
}
+/* A comparison's flags; the condition its true sets. */
+static unsigned tcompare(Tier *t, const TVal *v) {
+ Emit *e = t->k->e;
+ bool ints = t->vals[v->a].rep != R_NUM && t->vals[v->b].rep != R_NUM;
+ if (ints && timm(t, v->b)) cmp_imm(e, treg(t, v->a), (uint32_t)t->vals[v->b].num);
+ else if (ints) cmp_reg(e, treg(t, v->a), treg(t, v->b));
+ else {
+ fcmp(e, td(t, v->a, D0), td(t, v->b, D1));
+ return v->op == T_LT ? MI : v->op == T_LE ? LS : v->op == T_GT ? GT : v->op == T_GE ? GE : v->op == T_EQ ? EQ : NE; /* false unordered (NE true) */
+ }
+ return v->op == T_LT ? LT : v->op == T_LE ? LE : v->op == T_GT ? GT : v->op == T_GE ? GE : v->op == T_EQ ? EQ : NE;
+}
+
/* A value's code. */
static void tier_value(Tier *t, int vi) {
Block *k = t->k;
@@ -4580,7 +4664,6 @@ static void tier_value(Tier *t, int vi) {
unsigned r = v->reg >= 0 ? (unsigned)v->reg : X16;
int fail = v->snap >= 0 ? tfail(t, v->snap) : -1;
const TVal *a = v->a >= 0 ? &t->vals[v->a] : NULL, *b2 = v->b >= 0 ? &t->vals[v->b] : NULL;
- bool ints = a && b2 && a->rep != R_NUM && b2->rep != R_NUM;
switch (v->op) {
case T_CONST:
if (v->rep == R_INT) mov_imm(e, r, (uint64_t)(int64_t)v->num);
@@ -4774,21 +4857,9 @@ static void tier_value(Tier *t, int vi) {
} else
fp2(e, FRINTM, r, treg(t, v->a));
return;
- case T_LT: case T_LE: case T_GT: case T_GE: case T_EQ: case T_NE: {
- unsigned cond;
- if (ints && timm(t, v->b)) {
- cmp_imm(e, treg(t, v->a), (uint32_t)t->vals[v->b].num);
- cond = v->op == T_LT ? LT : v->op == T_LE ? LE : v->op == T_GT ? GT : v->op == T_GE ? GE : v->op == T_EQ ? EQ : NE;
- } else if (ints) {
- cmp_reg(e, treg(t, v->a), treg(t, v->b));
- cond = v->op == T_LT ? LT : v->op == T_LE ? LE : v->op == T_GT ? GT : v->op == T_GE ? GE : v->op == T_EQ ? EQ : NE;
- } else {
- fcmp(e, td(t, v->a, D0), td(t, v->b, D1));
- cond = v->op == T_LT ? MI : v->op == T_LE ? LS : v->op == T_GT ? GT : v->op == T_GE ? GE : v->op == T_EQ ? EQ : NE;
- }
- cset(e, r, cond);
+ case T_LT: case T_LE: case T_GT: case T_GE: case T_EQ: case T_NE:
+ cset(e, r, tcompare(t, v));
return;
- }
case T_NOT: eor_w_one(e, r, treg(t, v->a)); return;
case T_AND: and_w(e, r, treg(t, v->a), treg(t, v->b)); return;
case T_OR: orr_w(e, r, treg(t, v->a), treg(t, v->b)); return;
@@ -4818,6 +4889,15 @@ static void tier_value(Tier *t, int vi) {
case T_FIND_GET: case T_FIND: case T_HAS: { /* by key: in the bits (below their reach), its cell at its key */
unsigned key = tx(t, v->a, X0, fail), bits = treg(t, v->b);
unsigned limit = treg(t, v->op == T_FIND_GET ? v->d : v->c);
+ bool full = t->lists[v->list].full; /* every key below its count there (pure_list_suits): the bits not read */
+ if (v->op == T_HAS && full) {
+ if (v->proven) mov_w(e, r, 1);
+ else {
+ cmp_reg(e, key, limit);
+ cset(e, r, LO);
+ }
+ return;
+ }
if (v->op == T_HAS) {
int no = new_label(e), done = new_label(e), bit = new_label(e);
if (!v->proven) {
@@ -4842,13 +4922,15 @@ static void tier_value(Tier *t, int vi) {
cmp_reg(e, key, limit);
b_cond(e, HS, fail);
}
- int there = new_label(e);
- tbnz(e, bits, 0, there); /* full: every key below its count there */
- lsr_imm(e, X16, key, 6);
- load64_index(e, X16, bits, X16);
- lsr_reg(e, X16, X16, key);
- tbz(e, X16, 0, fail);
- bind(e, there);
+ if (!full) {
+ int there = new_label(e);
+ tbnz(e, bits, 0, there); /* full as it began: every key below its count there */
+ lsr_imm(e, X16, key, 6);
+ load64_index(e, X16, bits, X16);
+ lsr_reg(e, X16, X16, key);
+ tbz(e, X16, 0, fail);
+ bind(e, there);
+ }
if (v->op == T_FIND) { /* a full list's position: its key */
if (r != key) mov_reg(e, r, key);
return;
@@ -4883,11 +4965,9 @@ static void tier_emit(Tier *t, int entry, int fallback) {
const TList *l = &t->lists[list];
unsigned needs = 0, narrow = 0;
for (unsigned f = 0; f < 16; ++f) narrow |= l->part[f] >= 0 ? 1u << f : 0;
- bool found = false;
- for (size_t i = 0; i < t->val_count; ++i) found = found || (t->vals[i].op == T_FIND && t->vals[i].list == list);
if (l->key) needs |= NEEDS_KEY;
if (l->position) needs |= NEEDS_POSITION;
- if ((l->key && l->position) || found) needs |= NEEDS_FULL;
+ if (l->full) needs |= NEEDS_FULL;
if (!needs && !narrow) continue;
load64(e, X0, X24, list * sizeof(PureList *));
mov_w(e, X1, needs);
@@ -4934,23 +5014,34 @@ static void tier_emit(Tier *t, int entry, int fallback) {
bind(e, budgeted);
load64(e, X9, X17, offsetof(PureApp, work));
sub_reg(e, TIER_WORK, TIER_WORK, X9);
+ unsigned flags = EQ; /* a fused comparison's condition */
for (size_t i = 0; i < t->item_count && !e->failed; ++i) {
const TItem *it = &t->items[i];
switch (it->kind) {
case I_VALUE:
- if (t->vals[it->value].reg >= 0 || t->vals[it->value].op == T_SET) tier_value(t, it->value);
+ if (t->vals[it->value].fused) flags = tcompare(t, &t->vals[it->value]);
+ else if (t->vals[it->value].reg >= 0 || t->vals[it->value].op == T_SET) tier_value(t, it->value);
break;
case I_MERGE: bind(e, t->merges[it->merge].label); break;
case I_JUMP:
+ if (tbreaks(t, it)) tier_refund(t, it->loop);
tier_moves(t, it->merge, it->way);
if (!(i + 1 < t->item_count && t->items[i + 1].kind == I_MERGE && t->items[i + 1].merge == it->merge))
b(e, t->merges[it->merge].label);
break;
- case I_UNLESS: { /* its bool 0: to the merge */
+ case I_UNLESS: { /* its bool 0 (a fused comparison's condition false): to the merge */
+ bool fused = t->vals[it->value].fused;
+ int to = t->merges[it->merge].label;
+ if (!tier_has_moves(t, it->merge, it->way)) {
+ if (fused) b_cond(e, flags ^ 1, to);
+ else cbz_w(e, treg(t, it->value), to);
+ break;
+ }
int go_on = new_label(e);
- cbnz_w(e, treg(t, it->value), go_on);
+ if (fused) b_cond(e, flags, go_on);
+ else cbnz_w(e, treg(t, it->value), go_on);
tier_moves(t, it->merge, it->way);
- b(e, t->merges[it->merge].label);
+ b(e, to);
bind(e, go_on);
break;
}
@@ -4958,10 +5049,13 @@ static void tier_emit(Tier *t, int entry, int fallback) {
const TLoop *l = &t->loops[it->loop];
unsigned count = treg(t, l->count);
int go_on = new_label(e), fail = tfail(t, it->snap);
- cbnz_x(e, count, go_on); /* none (a walk's work then none too) */
- tier_moves(t, it->merge, it->way);
- b(e, t->merges[it->merge].label);
- bind(e, go_on);
+ if (!tier_has_moves(t, it->merge, it->way)) cbz_x(e, count, t->merges[it->merge].label); /* none (its work then none too) */
+ else {
+ cbnz_x(e, count, go_on);
+ tier_moves(t, it->merge, it->way);
+ b(e, t->merges[it->merge].label);
+ bind(e, go_on);
+ }
for (unsigned bi = 0; bi < l->bound_count; ++bi) { /* each key or position in range at both ends of its rounds */
static const unsigned first[] = {X1, X17}, last[] = {X17, X9};
unsigned limit = treg(t, l->bounds[bi][1]);
@@ -4973,37 +5067,41 @@ static void tier_emit(Tier *t, int entry, int fallback) {
cmp_reg(e, X1, limit);
b_cond(e, HS, fail);
}
- if (l->walk) { /* its block's length times its items, at once (after what may give up there: the executor's EACH charges it) */
- mov_imm(e, X16, l->length);
- mul_reg(e, X16, X16, count);
- cmp_reg(e, TIER_WORK, X16);
- b_cond(e, LO, fail);
- sub_reg(e, TIER_WORK, TIER_WORK, X16);
- }
- if (!l->walk) { /* a count's first round */
- mov_imm(e, X16, (uint64_t)l->length + 1);
- cmp_reg(e, TIER_WORK, X16);
- b_cond(e, LO, fail);
- sub_reg(e, TIER_WORK, TIER_WORK, X16);
+ /* its work at once (after what may give up there): a walk's block's length times its items (the executor's
+ * EACH charges it so), a count's length and iteration times its rounds (given back for those it does not run, tier_refund) */
+ mov_imm(e, X16, l->walk ? l->length : (uint64_t)l->length + 1);
+ if (!l->walk) { /* past 64 bits: more than it may do */
+ put(e, 0x9BC07C00 | count << 16 | X16 << 5 | X17); /* UMULH */
+ cbnz_x(e, X17, fail);
}
+ mul_reg(e, X16, X16, count);
+ cmp_reg(e, TIER_WORK, X16);
+ b_cond(e, LO, fail);
+ sub_reg(e, TIER_WORK, TIER_WORK, X16);
break;
}
- case I_LATCH: { /* again while below its rounds (a count's work each), else past it */
+ case I_LATCH: { /* again while below its rounds (the outermost count's work each), else past it */
const TLoop *l = &t->loops[it->loop];
- int out = new_label(e);
+ int out = new_label(e), back = (int)t->merges[l->head].way_count - 1;
cmp_reg(e, treg(t, it->value), treg(t, l->count));
+ if (!tlatch_pays(t, it->loop) && !tier_has_moves(t, l->head, back)) {
+ b_cond(e, LO, t->merges[l->head].label);
+ tier_moves(t, l->exit, it->way);
+ break;
+ }
b_cond(e, HS, out);
- if (!l->walk && l->length + 1 < 4096) {
+ if (!tlatch_pays(t, it->loop)) {
+ } else if (l->length + 1 < 4096) {
cmp_imm(e, TIER_WORK, l->length + 1);
b_cond(e, LO, tfail(t, it->snap));
sub_imm(e, TIER_WORK, TIER_WORK, l->length + 1);
- } else if (!l->walk) {
+ } else {
mov_imm(e, X16, (uint64_t)l->length + 1);
cmp_reg(e, TIER_WORK, X16);
b_cond(e, LO, tfail(t, it->snap));
sub_reg(e, TIER_WORK, TIER_WORK, X16);
}
- tier_moves(t, l->head, (int)t->merges[l->head].way_count - 1);
+ tier_moves(t, l->head, back);
b(e, t->merges[l->head].label);
bind(e, out);
tier_moves(t, l->exit, it->way);
diff --git a/tests/check.py b/tests/check.py
index 64e5021..c7a6c79 100644
--- a/tests/check.py
+++ b/tests/check.py
@@ -1079,6 +1079,80 @@ on go {
"""
+# The tier where its first checks missed (module_tier's seeds 17 and 23): a
+# value in a loop made of a phi where an if's ways meet in it (MERGES_SOURCE:
+# taken as made outside the loop, it was made once for every round), and the
+# outermost loop's round past the work budget (BUDGET_SOURCE: given up at the
+# loop's instruction, with nothing on the stack, rather than its iteration).
+MERGES_SOURCE = """record Cell {
+ at = 0
+ v = 0
+}
+state out = 0
+state acc = 0
+input go = message {
+ n: u32
+ m: u32
+ q: u32
+}
+on go {
+ let t: list of Cell by at
+ let f: list of Cell
+ for i in 0 .. go.n {
+ t.add(Cell { at: i, v: i })
+ f.append(Cell { at: i * 2, v: i })
+ }
+ let s = 0
+ let u = 1
+ for i in 0 .. go.m {
+ for j in 0 .. go.n {
+ if t.has(go.q) {
+ acc = acc + u
+ }
+ for c in f {
+ if go.m != u {
+ s = s + (floor(go.n / 7) - max(u, go.m))
+ }
+ t[3].v = 1000
+ }
+ acc = acc + (go.n + go.m)
+ }
+ t[3].v = ((255 - s) + (1000 - 1000))
+ }
+ out = s
+ acc = acc + u
+}
+"""
+
+BUDGET_SOURCE = """record Cell {
+ at = 0
+ v = 0
+}
+state out = 0
+state acc = 0
+input go = message {
+ n: u32
+ m: u32
+ q: u32
+}
+on go {
+ let t: list of Cell by at
+ let f: list of Cell
+ for i in 0 .. go.n {
+ t.add(Cell { at: i, v: i })
+ f.append(Cell { at: i * 2, v: i })
+ }
+ let s = 0
+ let u = 1
+ for i in 0 .. go.m {
+ if !(go.q != s) { break }
+ t[3].v = (t[floor(go.n / 2)].v + (go.m + go.n))
+ }
+ out = s
+ acc = acc + u
+}
+"""
+
def module_assumptions(aup, pure, work):
"""The compiled code's integers (Pure docs/design/022) where they stop
holding: a loop whose columns hold small whole numbers until, halfway,
@@ -1107,6 +1181,15 @@ def module_assumptions(aup, pure, work):
outputs[jit] = done.stdout
assert outputs['all'] == outputs['0'], next((a, b) for a, b in zip(outputs['all'].splitlines(), outputs['0'].splitlines()) if a != b)
assert all(json.loads(line)['fault'] == 'none' for line in outputs['0'].splitlines())
+ messages = ''.join(f'message go {struct.pack("<III", n, m, q).hex()}\nwork\n' for n, m, q in ((70, 40, 3), (130, 25, 2)))
+ for name, text in (('merges', MERGES_SOURCE), ('budget', BUDGET_SOURCE)): # the tier's, as the executor: states, faults, work
+ source, program = work / f'{name}.aup', work / f'{name}.pmod'
+ source.write_text(text)
+ compile_module(aup, source, program)
+ outputs = {jit: subprocess.run([str(pure / 'build/pure-run'), str(program)], input=messages + 'budget 1201\n' + messages,
+ capture_output=True, text=True, timeout=120, env=dict(os.environ, PURE_JIT=jit)).stdout
+ for jit in ('all', '0')}
+ assert outputs['all'] == outputs['0'], (name, next((a, b) for a, b in zip(outputs['all'].splitlines(), outputs['0'].splitlines()) if a != b))
return len(runs)
@@ -1200,7 +1283,8 @@ def module_tier(aup, pure, work, programs=150):
(tier_program) whose loops the tier runs where it can, each run on a few
messages by the executor alone (PURE_JIT=0), with the compiled code
(PURE_JIT=1, all) and without the tier (PURE_TIER=0): the same states,
- faults and errors, byte for byte. Their failures (keys not there, not
+ faults, errors and work, byte for byte, with the default work budget
+ and a few small ones (where each runs out). Their failures (keys not there, not
whole, 0 divided by, cells past their columns' widths, a list not yet
DENSE) are where the tier gives up to the executor."""
import os
@@ -1208,7 +1292,8 @@ def module_tier(aup, pure, work, programs=150):
import struct
rng = random.Random(23)
runs = [(0, 0, 0), (5, 3, 1), (70, 40, 3), (130, 25, 2), (300, 7, 299), (70, 70, 64)]
- commands = ''.join(f'message go {struct.pack("<III", n, m, q).hex()}\n' for n, m, q in runs)
+ messages = ''.join(f'message go {struct.pack("<III", n, m, q).hex()}\nwork\n' for n, m, q in runs)
+ commands = messages + ''.join(f'budget {b}\n' + messages for b in (61, 389, 1201, 4099)) + 'budget 0\n' + messages
checked = failed = 0
for i in range(programs):
source, module = work / f'tier-{i}.aup', work / f'tier-{i}.pmod'
@@ -1228,7 +1313,7 @@ def module_tier(aup, pure, work, programs=150):
assert outputs[name] == outputs['0'], (i, name, source.read_text(),
next((a, b) for a, b in zip(outputs[name].splitlines(), outputs['0'].splitlines()) if a != b))
checked += 1
- failed += sum(json.loads(line)['fault'] != 'none' for line in outputs['0'].splitlines() if line.startswith('{'))
+ failed += sum(json.loads(line).get('fault', 'none') != 'none' for line in outputs['0'].splitlines() if line.startswith('{'))
assert checked >= programs // 2, checked
return {'programs': checked, 'failed_events': failed}