aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/module-jit.c75
-rw-r--r--docs/design/023-optimizing-tier.md7
-rw-r--r--tests/check.py25
3 files changed, 92 insertions, 15 deletions
diff --git a/core/module-jit.c b/core/module-jit.c
index 2732b6a..e8858fb 100644
--- a/core/module-jit.c
+++ b/core/module-jit.c
@@ -1354,6 +1354,8 @@ static void store_tagged(Block *k, unsigned h, unsigned col, unsigned at, int sl
* its room and its bits' reach), the count or the items added counted in
* their register, the event's headroom less the item's bytes; nothing else
* written until a call or the nest's end (books_back). */
+static void append(Block *k, const PureOp *op, uint32_t pc, unsigned height);
+
static void append_books(Block *k, const PureOp *op, uint32_t pc, unsigned height) {
Emit *e = k->e;
const PureModule *m = k->m;
@@ -1374,7 +1376,8 @@ static void append_books(Block *k, const PureOp *op, uint32_t pc, unsigned heigh
}
unsigned some = key == 1 ? columns[1] : columns[0]; /* a column kept: 0 when the list is not in that form */
size_t bytes = row_bytes(m, list);
- cbz_x(e, some, slow);
+ int other = new_label(e), done = new_label(e); /* a keyed list not DENSE (HASHED): as append does it, the headroom kept */
+ cbz_x(e, some, key ? other : slow);
if (!key) {
load64(e, X16, X24, list * sizeof(PureList *));
load64(e, X17, X16, pure_list_layout[LIST_ROOM_AT]);
@@ -1408,6 +1411,12 @@ static void append_books(Block *k, const PureOp *op, uint32_t pc, unsigned heigh
add_imm(e, pending, pending, 1);
}
slow_back(k, s, V_MEM, 0);
+ if (key) {
+ b(e, done);
+ bind(e, other);
+ append(k, op, pc, height);
+ }
+ bind(e, done);
}
/* A key's bits in a HASHED list's table (module-list.c's key_bits: -0 as +0) into x17. */
@@ -2972,6 +2981,8 @@ typedef struct {
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 deferred; /* a number's arithmetic whose finiteness its one reader, right after it, checks (tier_defer) */
+ int retry; /* where its finiteness check gives up, the first of such a run's snapshot; or -1: its own */
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 */
@@ -3080,7 +3091,7 @@ static int tval(Tier *t, unsigned op, unsigned rep, int a, int b, int c) {
TVal *v = &t->vals[t->val_count];
memset(v, 0, sizeof(*v));
v->op = (uint8_t)op, v->rep = (uint8_t)rep, v->a = a, v->b = b, v->c = c;
- v->snap = -1, v->loop = t->cur_loop, v->var = -1, v->label = -1, v->reg = -1, v->d = -1;
+ v->snap = -1, v->loop = t->cur_loop, v->var = -1, v->label = -1, v->reg = -1, v->d = -1, v->retry = -1;
v->lo = -INFINITY, v->hi = INFINITY;
int made = (int)t->val_count++;
if (op == T_PHI) return made; /* bound at its merge */
@@ -4008,6 +4019,29 @@ static void tier_facts(Tier *t) {
if (t->vals[i].lo > t->vals[i].hi && t->vals[i].rep != R_BOOL) t->vals[i].lo = -INFINITY, t->vals[i].hi = INFINITY, t->vals[i].whole = false, t->vals[i].zero = true;
}
+/* A run of numbers' arithmetic each read by the next alone, right after it
+ * (no other code between but constants): only the last checks it is finite. A number not
+ * finite stays not finite through an add, a sub, a mul, or as what a
+ * division divides; where it is not, the code gives up at the run's first
+ * snapshot, so the executor makes the run again and fails where the
+ * executor does. */
+static void tier_defer(Tier *t) {
+ for (size_t i = 0; i + 1 < t->item_count; ++i) {
+ size_t next = i + 1; /* the next value but constants (made as they are: no check, nothing changed) */
+ while (next < t->item_count && t->items[next].kind == I_VALUE && t->vals[t->items[next].value].op == T_CONST) ++next;
+ if (t->items[i].kind != I_VALUE || next == t->item_count || t->items[next].kind != I_VALUE) continue;
+ TVal *v = &t->vals[t->items[i].value], *w = &t->vals[t->items[next].value];
+ int vi = t->items[i].value;
+ bool arith = v->rep == R_NUM && v->op >= T_ADD && v->op <= T_DIV && v->snap >= 0 && v->uses == 1;
+ bool reads = w->rep == R_NUM && w->snap >= 0 && w->loop == v->loop &&
+ (((w->op == T_ADD || w->op == T_SUB || w->op == T_MUL) && (w->a == vi || w->b == vi)) ||
+ (w->op == T_DIV && w->a == vi && w->b != vi && (t->vals[w->b].lo > 0 || t->vals[w->b].hi < 0)));
+ if (!arith || !reads || tfinite(v) || tfinite(w)) continue;
+ v->deferred = true;
+ w->retry = v->retry >= 0 ? v->retry : v->snap;
+ }
+}
+
/* Each value's register: a whole number an integer, else a number (a bool's as made). Uses counted. */
static void tier_reps(Tier *t) {
for (size_t i = 0; i < t->val_count; ++i) {
@@ -4169,8 +4203,9 @@ static void tuse_affine(Tier *t, int x, int l, int at) {
static void tuse_snap(Tier *t, int s, int at) {
if (s < 0) return;
const TSnap *snap = &t->snaps[s];
- for (int var = 0; var < VARS; ++var)
- if (var < V_STACK ? tchanged(t, var, snap->vars[var]) : snap->vars[var] >= 0) tuse(t, snap->vars[var], at);
+ for (int var = 0; var < VARS; ++var) /* (a constant written as the constant it is) */
+ if ((var < V_STACK ? tchanged(t, var, snap->vars[var]) : snap->vars[var] >= 0) && t->vals[snap->vars[var]].op != T_CONST)
+ tuse(t, snap->vars[var], at);
for (int l = snap->loop; l >= 0; l = t->loops[l].parent) { /* the open loops: their rounds, counts and lows */
tuse(t, t->loops[l].k, at), tuse(t, t->loops[l].count, at);
if (t->loops[l].low >= 0 && t->vals[t->loops[l].low].op != T_CONST) tuse(t, t->loops[l].low, at); /* a constant written as one */
@@ -4228,7 +4263,8 @@ static void tier_needed(Tier *t, bool *need) {
}
#define NEED(v) do { int v_ = (v); if (v_ >= 0 && !need[v_]) need[v_] = true, work[top++] = v_; } while (0)
#define NEED_SNAP(s) do { if ((s) >= 0) { const TSnap *sn = &t->snaps[s]; \
- for (int var = 0; var < VARS; ++var) if (var < V_STACK ? tchanged(t, var, sn->vars[var]) : sn->vars[var] >= 0) NEED(sn->vars[var]); \
+ for (int var = 0; var < VARS; ++var) if ((var < V_STACK ? tchanged(t, var, sn->vars[var]) : sn->vars[var] >= 0) && \
+ t->vals[sn->vars[var]].op != T_CONST) NEED(sn->vars[var]); \
for (int l = sn->loop; l >= 0; l = t->loops[l].parent) { NEED(t->loops[l].k); NEED(t->loops[l].count); \
if (t->loops[l].low >= 0 && t->vals[t->loops[l].low].op != T_CONST) NEED(t->loops[l].low); } } } while (0)
for (size_t i = 0; i < t->item_count; ++i) {
@@ -4272,6 +4308,7 @@ static void tier_needed(Tier *t, bool *need) {
NEED(v->c);
NEED(v->d);
NEED_SNAP(v->snap);
+ NEED_SNAP(v->retry);
for (unsigned w = 0; w < v->ins; ++w) NEED(v->in[w]);
}
#undef NEED
@@ -4304,6 +4341,7 @@ static bool tier_registers(Tier *t) {
TVal *v = &t->vals[it->value];
tuse(t, v->a, p), tuse(t, v->b, p), tuse(t, v->c, p), tuse(t, v->d, p);
tuse_snap(t, v->snap, p);
+ tuse_snap(t, v->retry, p); /* the run's first snapshot: given up to from here */
break;
}
case I_UNLESS: tuse(t, it->value, p); /* fall through */
@@ -4605,6 +4643,19 @@ static bool tier_has_moves(const Tier *t, int g, int way) {
return false;
}
+/* A value a snapshot holds as a number in d0 (or its own d register): a
+ * constant made there, as the constant it is (its register, if it has one,
+ * not kept for the snapshot: another value's by then). */
+static unsigned tsnap_d(Tier *t, int v) {
+ const TVal *x = &t->vals[v];
+ if (x->op != T_CONST) return td(t, v, D0);
+ uint64_t bits;
+ memcpy(&bits, &x->num, sizeof(bits));
+ mov_imm(t->k->e, X9, bits);
+ fp2(t->k->e, FMOV_DX, D0, X9);
+ return D0;
+}
+
/* 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). */
@@ -4615,8 +4666,8 @@ static void tier_write(Tier *t, const int *vars) {
tbase(t, B_VALUES, X16);
for (int var = 0; var < V_STACK; ++var) {
int v = vars[var];
- if (!tchanged(t, var, v) || t->vals[v].reg < 0) continue;
- unsigned d = td(t, v, D0);
+ if (!tchanged(t, var, v) || (t->vals[v].reg < 0 && t->vals[v].op != T_CONST)) continue;
+ unsigned d = tsnap_d(t, v);
if (var < V_STATE) store_d(e, d, X17, (size_t)var * sizeof(PureValue));
else store_d(e, d, X16, (size_t)(var - V_STATE) * sizeof(PureValue));
}
@@ -4738,8 +4789,8 @@ static void tier_stub(Tier *t, int s) {
tbase(t, B_STACK, X17);
for (unsigned h = l0->base; h < snap->height; ++h) {
int v = snap->vars[V_STACK + h];
- if (v < 0 || t->vals[v].reg < 0) continue;
- store_d(e, td(t, v, D0), X17, AT(h));
+ if (v < 0 || (t->vals[v].reg < 0 && t->vals[v].op != T_CONST)) continue;
+ store_d(e, tsnap_d(t, v), X17, AT(h));
if (t->vals[v].kind == K_INDEX) {
mov_w(e, X9, (uint32_t)t->vals[v].list & 0xFFFF);
store_h(e, X9, X17, AT(h) + OWNER);
@@ -5000,7 +5051,8 @@ static void tier_value(Tier *t, int vi) {
unsigned x = td(t, v->a, D0), y = td(t, v->b, D1);
uint32_t fop = v->op == T_ADD ? FADD : v->op == T_SUB ? FSUB : v->op == T_MUL ? FMUL : v->op == T_MIN ? FMIN : FMAX;
fp3(e, fop, r, x, y);
- if (fail >= 0 && (v->op == T_ADD || v->op == T_SUB || v->op == T_MUL) && !tfinite(v)) tfinite_check(t, r, fail);
+ if (fail >= 0 && (v->op == T_ADD || v->op == T_SUB || v->op == T_MUL) && !tfinite(v) && !v->deferred)
+ tfinite_check(t, r, v->retry >= 0 ? tfail(t, v->retry) : fail);
return;
}
case T_DIV:
@@ -5015,7 +5067,7 @@ static void tier_value(Tier *t, int vi) {
b_cond(e, EQ, fail);
}
fp3(e, FDIV, r, x, y);
- if (!tfinite(v)) tfinite_check(t, r, fail);
+ if (!tfinite(v) && !v->deferred) tfinite_check(t, r, v->retry >= 0 ? tfail(t, v->retry) : fail);
return;
}
case T_NEG:
@@ -5403,6 +5455,7 @@ static Tier *tier_plan(Block *k, unsigned li) {
const char *hoist = getenv("PURE_TIER_HOIST");
if (!t->failed && !(hoist && !strcmp(hoist, "0"))) tier_hoist(t);
if (!t->failed) tier_reps(t);
+ if (!t->failed) tier_defer(t);
const char *bounds = getenv("PURE_TIER_BOUNDS");
if (!t->failed && !(bounds && !strcmp(bounds, "0"))) tier_bounds(t);
if (t->failed || !tier_registers(t)) {
diff --git a/docs/design/023-optimizing-tier.md b/docs/design/023-optimizing-tier.md
index 2003af5..78a4591 100644
--- a/docs/design/023-optimizing-tier.md
+++ b/docs/design/023-optimizing-tier.md
@@ -125,6 +125,9 @@ roots와 BFS만 기계어로 돈다. 나머지는 JIT의 값 모형이 거르거
키의 수와 같으면) 그 리스트의 키 비트 검사는 키가 [0, 닿는 범위) 안인지로 바뀌고, 그것도 귀납 변수로 묶이면 루프 앞에서 한 번.
- 루프 안의 덧붙이기나 고치기가 리스트를 바꿀 수 있으면, 그 리스트에 대한 사실은 그 명령 뒤에서 다시 확인한다(022의 규칙).
- 끌어낸 검사가 실패하면 그 루프를 검사가 제자리에 있는 코드(지금의 JIT, 또는 실행기)로 돈다. 결함은 위의 원칙대로 실행기가 낸다.
+- 수의 산수가 줄을 이어 하나씩 다음 것만 읽으면(사이에 상수 말고 다른 코드 없이) 유한 검사는 마지막 것만 한다(했다). 유한하지 않은
+ 수는 더하기, 빼기, 곱하기, 나누는 수로서 그대로 유한하지 않으므로, 마지막이 유한하지 않으면 줄의 첫 스냅숏으로 물러나 실행기가 줄을
+ 다시 하고 실행기가 내는 자리에서 결함을 낸다. 스냅숏의 상수는 레지스터에 두지 않고 그 상수로 쓴다. roots 12.2 ms에서 10.9.
### 4. 작업량과 메모리 몫을 루프마다
@@ -142,7 +145,7 @@ roots와 BFS만 기계어로 돈다. 나머지는 JIT의 값 모형이 거르거
### 6. 표현 (바닥이 가정한 것)
- **희소한 키로 둔 이벤트의 리스트**(HASHED, 했다): 수 키가 차례를 어기고 작은 정수가 아니면, 항목의 셀을 키 집합(열린 주소,
- 키의 비트, 칸의 4/5까지: 표가 L3에 들게; 절반까지면 조인 112 ms, 4/5면 84)의 그 키의 칸에 둔다. DENSE가 키의 자리에 두듯 해서, 키로 찾기는 칸의 키와 셀을 같은 번호로 읽는다(C의 해시
+ 키의 비트, 칸의 4/5까지: 표가 L3에 들게; 절반까지면 조인 112 ms, 4/5면 84; 일반 코드의 장부 루프에서도 HASHED에 바로 넣으면 74)의 그 키의 칸에 둔다. DENSE가 키의 자리에 두듯 해서, 키로 찾기는 칸의 키와 셀을 같은 번호로 읽는다(C의 해시
표처럼 두 미스가 겹친다; 넣은 차례로 두고 칸에 위치를 두면 미스가 줄을 선다: 조인 129 ms에서 108). 차례가 필요할 때(걷기, 위치,
찾은 위치, 되돌리기, 공유, 저장) 칸들을 키의 비트 순서로 여덟 비트씩 기수 정렬해 FLAT이 된다. 지우기까지 두 번이고, 그다음은
FLAT이나 트리다(오가며 정렬하지 않게). 일반 코드의 APPEND도 표가 자라지 않는 한 칸을 바로 찾아 넣는다(sort 58 ms에서 37). 계층은 키로만 찾는 리스트의 비트 열 1번 비트로 HASHED를 알고(한계는 모두 1: 키의 경계 검사가
@@ -153,7 +156,7 @@ roots와 BFS만 기계어로 돈다. 나머지는 JIT의 값 모형이 거르거
키 집합이 아니므로 버린다.
- **찾은 자리에 쓰기**(PLAN_ROW, 했다): `counts[w].n = counts[w].n + 1`처럼 FIND가 낸 위치를 ITEM_SET만 받고 그 사이가 한 줄(뛰어
들거나 나가지 않음)이며 리스트를 키로 찾기(합친 FIND, HAS) 말고 건드리지 않으면, 로더가 둘을 묶는다. HASHED 리스트는 위치 대신
- 칸에 쓴다(위치를 내면 정렬된다). words 453 ms에서 217.
+ 칸에 쓴다(위치를 내면 정렬된다). words 453 ms에서 211–219. 남은 것은 실행기다: 루프가 텍스트를 만들어(`"w{n}"`을 세 번, aup의 let은 텍스트를 담지 못한다) 일반 코드로 가지 못한다.
- 텍스트 보간의 정수는 정수의 자릿수로 바로 쓴다(지금의 수→텍스트가 정수에서 내는 것과 같은 바이트).
- 저장된 리스트는 022 그대로(트리, 많이 찾으면 편 배열).
diff --git a/tests/check.py b/tests/check.py
index 30ee5f0..bb78bc6 100644
--- a/tests/check.py
+++ b/tests/check.py
@@ -1153,6 +1153,26 @@ on go {
}
"""
+# Numbers' arithmetic in a run, each read by the next alone: only the last
+# checks it is finite, giving up at the run's first snapshot (023); the
+# product passes the largest number at the 56th round.
+CHAIN_SOURCE = """state out = 0
+state steps = 0
+input go = message {
+ n: u32
+}
+on go {
+ let s = 1
+ let c = 0
+ for i in 0 .. go.n {
+ s = (s * 1000000 + i) / 3 * 2 + 1
+ c = c + 1
+ }
+ out = s
+ steps = c
+}
+"""
+
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,
@@ -1182,11 +1202,12 @@ def module_assumptions(aup, pure, work):
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
+ for name, text in (('merges', MERGES_SOURCE), ('budget', BUDGET_SOURCE), ('chain', CHAIN_SOURCE)): # the tier's, as the executor
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,
+ given = ''.join(f'message go {struct.pack("<I", n).hex()}\nwork\n' for n in (3, 50, 60, 100)) if name == 'chain' else messages
+ outputs = {jit: subprocess.run([str(pure / 'build/pure-run'), str(program)], input=given + 'budget 1201\n' + given,
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))