diff options
| -rw-r--r-- | cli/pure-run.c | 8 | ||||
| -rw-r--r-- | core/module-jit.c | 218 | ||||
| -rw-r--r-- | tests/check.py | 91 |
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} |
