From 4f95b988131fc08e9a367c89b810d08bfa97de7f Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Thu, 27 Aug 2026 06:12:41 +0000 Subject: [PATCH] re: the per-phase clear conditions, by dominance over the ISL CFG Closes the backlog's "which condition guards each END_PHASE". With the CFG from the previous commit this is a graph query, not new machinery. The obvious query is WRONG for this language, and I implemented it first: "one successor reaches END_PHASE and the other does not" finds 1/62/1 guards across Stage 02's three phases, and the 1s are both the same read_freg(0) < 1200 timeout -- every objective test missed. The cause is the dominant idiom: a POLL LOOP's loop-back branch also reaches the exit, one iteration later, so neither successor discriminates. The asymmetric 1/62/1 is what exposed it; a uniform number would have read as plausible. Dominance has no such blind spot: a condition dominates an exit when every path from an entry passes through it, so it is NECESSARY for the phase to end that way, and a poll loop's test dominates its own exit by construction. Iterative dominators converge in 3 passes over 15670/18739 instructions (83.6%). Result for Stage 02 -- every exit in all three phases is dominated by unit_hp_pct(TCN001, Character_Player_Test) != 0, the player's ship being alive, which falls out rather than being assumed. Beyond that, phase 1's objective exit requires hp_pct_test on ADT102, ADT107 and ADT113; phase 3's requires ADT301 and ADT302; read_freg(0) gates at 210 / 300 and times out at 1200; random(3) and random(5) dominate only the exits that pick one of several closing lines. Two of the 15 exits are reachable from NO static entry, both FORCE_END_PHASE. That agrees with the independently measured 389 unreachable routines: they are started from the trigger queue at phase+272, by data rather than code. Practical note recorded: the first dominator run was OOM-killed -- 6743 nodes each holding a Python set of up to 6743 elements. Integer bitmasks run in seconds. Not settled, and said so: dominance gives necessary, not sufficient, conditions; only Stage 02's artefact is committed; one listed condition is still an unresolved ; read_freg's units are inferred from the gate values, not read. calls, phase-ends and conditions all regenerate byte-identical. --- docs/re/BACKLOG.md | 15 +++- docs/re/data/isl-stage02-phase-guards.txt | 102 ++++++++++++++++++++++ docs/re/structures/isl-phase-guards.md | 88 +++++++++++++++++++ tools/re-capture/guard.py | 58 ++++++++++++ tools/re-capture/isl_cfg.py | 91 +++++++++++++++++++ tools/re-capture/isl_report.py | 36 +++++++- 6 files changed, 387 insertions(+), 3 deletions(-) create mode 100644 docs/re/data/isl-stage02-phase-guards.txt create mode 100644 docs/re/structures/isl-phase-guards.md create mode 100644 tools/re-capture/guard.py diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index 9d771406..23592232 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -1472,7 +1472,20 @@ premise was wrong.** + the unit's live object, pushes it on a queue via the `push.i` helper, and returns 1 (or 0 if the unit is absent). Finding that **exposed a much bigger bug in my own condition tracker** — see the entry below. - which condition guards each `END_PHASE` (needs the control flow between them); + ~~which condition guards each `END_PHASE` (needs the control flow between them);~~ + ✅ **(2026-08-27) DONE — [structures/isl-phase-guards](structures/isl-phase-guards.md)** + (`isl_report.py phase-guards`, `data/isl-stage02-phase-guards.txt`). Uses + DOMINANCE over the CFG, not reachability. 🔴 The obvious query — "one branch + reaches `END_PHASE`, the other doesn't" — is **wrong for this language** and was + tried first: poll loops have BOTH successors reaching the exit, so it found + 1/62/1 guards in Stage 02's three phases, the 1s being a `read_freg(0) < 1200` + timeout, missing every objective test. Dominators converge in 3 passes over + 15670/18739 instructions. Result: **every exit in all 3 phases requires + `unit_hp_pct(TCN001, Character_Player_Test) != 0`** (the player alive); phase 1's + objective exit additionally requires `hp_pct_test(ADT102/ADT107/ADT113, 0) != 1`, + phase 3's requires `ADT301`/`ADT302`, and `read_freg(0)` gates at 210/300/1200. + 🟡 Dominance gives NECESSARY not sufficient conditions. 🟡 2 of 15 exits are + reachable from NO static entry — consistent with the trigger queue at `phase+272`. the vtable's length. The condition lives in the `op10`/`op13` poll loop upstream of the outro — e.g. phase 3 polls `unit_state(ADT308)` and branches back to `0xFEB4` until it passes. Artefact: `data/isl-stage02-phase-ends.txt`. diff --git a/docs/re/data/isl-stage02-phase-guards.txt b/docs/re/data/isl-stage02-phase-guards.txt new file mode 100644 index 00000000..b62922cf --- /dev/null +++ b/docs/re/data/isl-stage02-phase-guards.txt @@ -0,0 +1,102 @@ +# Stage02.ssb — what each phase exit requires + +Generated by `tools/re-capture/isl_report.py phase-guards`. + +A condition is listed when it DOMINATES the exit: every path from an +entry to that `END_PHASE` passes through it, so it is NECESSARY for the +phase to end that way. Reachability alone is the wrong query here — +in a poll loop both successors reach the exit. + +CFG reached 15670 of 18739 instructions (83.6%). + +## phase 1 — END_PHASE at 0x0051E4: 2 necessary condition(s) + 0x004D70 unit_hp_pct(TCN001, Character_Player_Test) != 0 + 0x005110 random(3) == 0 + +## phase 1 — END_PHASE at 0x005828: 3 necessary condition(s) + 0x004D70 unit_hp_pct(TCN001, Character_Player_Test) != 0 + 0x00523C hp_pct_test(TCN004, 0) != 1 + 0x00572C random(5) == 0 + +## phase 1 — END_PHASE at 0x006010: 7 necessary condition(s) + 0x004D70 unit_hp_pct(TCN001, Character_Player_Test) != 0 + 0x00523C hp_pct_test(TCN004, 0) != 1 + 0x005888 read_freg(0) <= 210 + 0x0058DC hp_pct_test(ADT102, 0) != 1 + 0x005930 hp_pct_test(ADT107, 0) != 1 + 0x005984 hp_pct_test(ADT113, 0) != 1 + 0x005B3C hp_pct_test(ADN101, 0) != 1 + +## phase 1 — END_PHASE at 0x006260: 4 necessary condition(s) + 0x004D70 unit_hp_pct(TCN001, Character_Player_Test) != 0 + 0x00523C hp_pct_test(TCN004, 0) != 1 + 0x005888 read_freg(0) <= 210 + 0x006070 read_freg(0) < 1200 + +## phase 1 — FORCE_END_PHASE at 0x01482C: UNREACHABLE from any static entry + (started from the trigger queue at `phase+272`, by data not code) + +## phase 2 — END_PHASE at 0x019640: 2 necessary condition(s) + 0x0191CC unit_hp_pct(TCN001, Character_Player_Test) != 0 + 0x01956C random(3) == 0 + +## phase 2 — END_PHASE at 0x019934: 2 necessary condition(s) + 0x0191CC unit_hp_pct(TCN001, Character_Player_Test) != 0 + 0x019698 hp_pct_test(TCT206, 0) != 1 + +## phase 2 — END_PHASE at 0x01AC44: 9 necessary condition(s) + 0x0191CC unit_hp_pct(TCN001, Character_Player_Test) != 0 + 0x019698 hp_pct_test(TCT206, 0) != 1 + 0x0199C8 builtin7(TCT206, 1, Route_TCT206_p2S, 4294967295, 500) == 1 + 0x019A8C <= 240 + 0x01A1E4 global[4] == 0 + 0x01A2B0 global[4] != 1 + 0x01A924 hp_pct_test(TCT206, 89.9) != 0 + 0x01AA38 global[104] != 0 + 0x01AAF8 global[4] == 0 + +## phase 2 — FORCE_END_PHASE at 0x0249F0: 2 necessary condition(s) + 0x0247E4 hp_pct_test(ADN202, 0) != 1 + 0x024848 unit_state(TCT206) != 1 + +## phase 3 — END_PHASE at 0x02B96C: 2 necessary condition(s) + 0x02B4F8 unit_hp_pct(TCN001, Character_Player_Test) != 0 + 0x02B898 random(3) == 0 + +## phase 3 — END_PHASE at 0x02BFB0: 3 necessary condition(s) + 0x02B4F8 unit_hp_pct(TCN001, Character_Player_Test) != 0 + 0x02B9C4 hp_pct_test(TCN004, 0) != 1 + 0x02BEB4 random(5) == 0 + +## phase 3 — END_PHASE at 0x02C1E0: 3 necessary condition(s) + 0x02B4F8 unit_hp_pct(TCN001, Character_Player_Test) != 0 + 0x02B9C4 hp_pct_test(TCN004, 0) != 1 + 0x02BFC8 global[112] < 4 + +## phase 3 — END_PHASE at 0x02CF74: 16 necessary condition(s) + 0x02B4F8 unit_hp_pct(TCN001, Character_Player_Test) != 0 + 0x02B9C4 hp_pct_test(TCN004, 0) != 1 + 0x02BFC8 global[112] < 4 + 0x02C240 read_freg(0) <= 300 + 0x02C294 hp_pct_test(ADT301, 0) != 1 + 0x02C2E8 hp_pct_test(ADT302, 0) != 1 + 0x02C33C hp_pct_test(ADT303, 0) != 1 + 0x02C390 hp_pct_test(ADT304, 0) != 1 + 0x02C3E4 hp_pct_test(ADT305, 0) != 1 + 0x02C438 hp_pct_test(ADT306, 0) != 1 + 0x02C48C hp_pct_test(ADT307, 0) != 1 + 0x02C4E0 hp_pct_test(ADT308, 0) != 1 + 0x02C5C4 global[112] != 0 + 0x02CC0C global[112] != 0 + 0x02CDC8 global[4] == 0 + 0x02CE9C global[112] != 0 + +## phase 3 — END_PHASE at 0x02D1DC: 5 necessary condition(s) + 0x02B4F8 unit_hp_pct(TCN001, Character_Player_Test) != 0 + 0x02B9C4 hp_pct_test(TCN004, 0) != 1 + 0x02BFC8 global[112] < 4 + 0x02C240 read_freg(0) <= 300 + 0x02CFD4 read_freg(0) < 1200 + +## phase 3 — FORCE_END_PHASE at 0x034A10: UNREACHABLE from any static entry + (started from the trigger queue at `phase+272`, by data not code) diff --git a/docs/re/structures/isl-phase-guards.md b/docs/re/structures/isl-phase-guards.md new file mode 100644 index 00000000..30267819 --- /dev/null +++ b/docs/re/structures/isl-phase-guards.md @@ -0,0 +1,88 @@ +# ✅ What each phase exit requires — the per-phase clear conditions + +This is what the ISL thread was for. `BACKLOG.md` framed it as *"which condition +guards each `END_PHASE`"*, and with the CFG from +[isl-conditions](isl-conditions.md) it is a graph query rather than new +machinery. Artefact: +[`../data/isl-stage02-phase-guards.txt`](../data/isl-stage02-phase-guards.txt), +generator `isl_report.py phase-guards`. + +## 🔴 The obvious query is WRONG here — tried first, and it fails quietly + +The natural definition of a guard is *one successor reaches `END_PHASE` and the +other does not*. I implemented that first. It reports, for Stage 02: + +| phase | 1 | 2 | 3 | +|---|---|---|---| +| "guards" found | **1** | 62 | **1** | + +and the single condition it finds in phases 1 and 3 is the same one — +`read_freg(0) < 1200`, a timeout. Every objective test is missed. + +**The reason is the shape of the language.** The dominant idiom here is a **poll +loop**: `if still-alive: jump back`. The loop-back branch reaches the exit too — +one iteration later — so *neither* successor discriminates and the clear +condition is invisible to a reachability test. The asymmetric 1 / 62 / 1 is what +exposed it; a uniform number would have looked plausible and been wrong. + +## ✅ Dominance has no such blind spot + +A condition **dominates** an exit when *every* path from an entry to that +`END_PHASE` passes through it — so it is a **necessary** condition for the phase +to end that way. A poll loop's test dominates its own exit, so the idiom that +defeats reachability is handled by construction. + +Iterative dominators over the CFG, converging in **3 passes**, reaching +**15 670 of 18 739** instructions (83.6 %). + +> Practical note: the first run was **OOM-killed**. 6 743 reachable nodes each +> carrying a Python `set` of up to 6 743 elements is ~45 M objects. Integer +> bitmasks fit in seconds. + +## ✅ What Stage 02 actually requires + +**Every exit in all three phases** is dominated by + +``` +unit_hp_pct(TCN001, Character_Player_Test) != 0 +``` + +— the player's own ship being alive. That is the universal precondition, and it +falls out of the analysis rather than being assumed. + +Then, per phase: + +| phase | exit at | necessary conditions beyond the player being alive | +|---|---|---| +| 1 | `0x0051E4` | `random(3) == 0` | +| 1 | `0x005828` | `hp_pct_test(TCN004, 0) != 1`, `random(5) == 0` | +| 1 | `0x006010` | `hp_pct_test(TCN004…)`, `read_freg(0) <= 210`, **`hp_pct_test(ADT102, 0) != 1`**, **`ADT107`**, **`ADT113`** | +| 1 | `0x006260` | `hp_pct_test(TCN004…)`, `read_freg(0) <= 210`, `read_freg(0) < 1200` | +| 2 | `0x019934` | `hp_pct_test(TCT206, 0) != 1` | +| 2 | `0x01AC44` | `TCT206`, `builtin7(TCT206, 1, Route_TCT206_p2S, …, 500) == 1`, `global[4] == 0`, `global[4] != 1` | +| 2 | `0x0249F0` | `hp_pct_test(ADN202, 0) != 1`, `unit_state(TCT206) != 1` | +| 3 | `0x02C1E0` | `hp_pct_test(TCN004…)`, `global[112] < 4` | +| 3 | `0x02CF74` | + `read_freg(0) <= 300`, **`hp_pct_test(ADT301, 0) != 1`**, **`ADT302`** | +| 3 | `0x02D1DC` | + `read_freg(0) < 1200` | + +The `hp_pct_test(ADTnnn, 0) != 1` chains are the objective kills; `read_freg(0)` +is a phase clock (`<= 210`, `<= 300` gates, `< 1200` the timeout); `random(3)` +and `random(5)` dominate only the exits that pick one of several closing lines. + +## 🟡 Two exits are unreachable, and that is informative + +`0x01482C` and `0x034A10` — both `FORCE_END_PHASE` — are reachable from **no +static entry**. That agrees with the independently measured 389 unreachable +routines: they are started from the **trigger queue at `phase+272`**, by data +rather than code. So a purely static reading cannot say what forces those exits. + +## 🟡 Not settled + +* **Only Stage 02 is committed.** The other 27 generate from the same command. +* **Dominance gives necessary, not sufficient, conditions.** A dominator set does + not say the phase ends *when* they all hold, only that it cannot end unless + they do. Turning these into a simulator's exit test needs the sufficient side. +* One condition in the phase-2 list still prints ` <= 240` — one of the + 402 sites the CFG cannot resolve. +* `builtin7` and `read_freg`'s units are unread; `read_freg(0)` behaves like + seconds against the 210 / 300 / 1200 gates but that is not established. diff --git a/tools/re-capture/guard.py b/tools/re-capture/guard.py new file mode 100644 index 00000000..78283171 --- /dev/null +++ b/tools/re-capture/guard.py @@ -0,0 +1,58 @@ +import struct, collections, isl, isl_cfg + +def edges(b, spawn=False): + """off -> successors. `spawn` includes the coroutine a start_coroutine creates.""" + offs = isl.linear_offsets(b) + nxt = {offs[i]: offs[i+1] for i in range(len(offs)-1)} + bases = isl.phase_bases(b) + E = collections.defaultdict(list) + loc, sp = {}, {} + for off in offs: + w = struct.unpack_from('>I', b, off)[0] + op, ln = w & 0xFF, (w >> 8) & 0xFF + sk, dk = (w >> 24) & 0xFF, (w >> 16) & 0xFF + words = [struct.unpack_from('>I', b, off+i)[0] + for i in range(4, max(ln,4), 4) if off+i+4 <= len(b)] + ph = sum(1 for x in bases if x <= off) + base = bases[ph-1] if ph else bases[0] + fall = nxt.get(off) + if op == 0 and len(words) >= 2: + v = words[1] if sk == 1 else sp.get(words[1]) if sk == 2 else None + d = sp if dk == 2 else loc + if v is None: d.pop(words[0], None) + else: d[words[0]] = v + elif op == 19 and words: + if words[0] == 11: fall = None # end_coroutine: thread dies + if spawn and words[0] == 1 and 0 in loc: + t = base + loc[0] + if t in nxt or t in offs: E[off].append(t) + loc = {} + elif op == 12 and words: + E[off].append(base + words[0]); fall = None + elif op in isl.REL and words: + E[off].append(base + words[0]) + if fall: E[off].append(fall) + return E, nxt + +def guards(b, spawn=False): + offs = isl.linear_offsets(b) + E, nxt = edges(b, spawn) + rev = collections.defaultdict(list) + for a, ss in E.items(): + for s in ss: rev[s].append(a) + ends = {o for o, bid, _ in isl.call_sites(b) if bid in (6, 62)} + R, q = set(ends), collections.deque(ends) + while q: + n = q.popleft() + for p in rev.get(n, ()): + if p not in R: R.add(p); q.append(p) + out = [] + s1, s2 = isl.symbols(b, 1), isl.symbols(b, 2) + for c in isl_cfg.conditions(b, s1, s2): + br = nxt.get(c['off']) + if br is None: continue + taken, fallth = c['target'], nxt.get(br) + t, f = taken in R, (fallth in R if fallth else False) + if t != f: + out.append({**c, 'ends_when': 'taken' if t else 'not-taken'}) + return out, len(R), len(offs), len(ends) diff --git a/tools/re-capture/isl_cfg.py b/tools/re-capture/isl_cfg.py index cd6733be..b9a2b576 100644 --- a/tools/re-capture/isl_cfg.py +++ b/tools/re-capture/isl_cfg.py @@ -54,6 +54,97 @@ def _join(a, bst): st = a[2] if a[2] == bst[2] else () return (m(a[0], bst[0]), m(a[1], bst[1]), st) +def edges(b, spawn=False): + """off -> successors. `spawn` includes the coroutine a start_coroutine creates.""" + offs = isl.linear_offsets(b) + nxt = {offs[i]: offs[i+1] for i in range(len(offs)-1)} + bases = isl.phase_bases(b) + E = collections.defaultdict(list) + loc, sp = {}, {} + for off in offs: + w = struct.unpack_from('>I', b, off)[0] + op, ln = w & 0xFF, (w >> 8) & 0xFF + sk, dk = (w >> 24) & 0xFF, (w >> 16) & 0xFF + words = [struct.unpack_from('>I', b, off+i)[0] + for i in range(4, max(ln,4), 4) if off+i+4 <= len(b)] + ph = sum(1 for x in bases if x <= off) + base = bases[ph-1] if ph else bases[0] + fall = nxt.get(off) + if op == 0 and len(words) >= 2: + v = words[1] if sk == 1 else sp.get(words[1]) if sk == 2 else None + d = sp if dk == 2 else loc + if v is None: d.pop(words[0], None) + else: d[words[0]] = v + elif op == 19 and words: + if words[0] == 11: fall = None # end_coroutine: thread dies + if spawn and words[0] == 1 and 0 in loc: + t = base + loc[0] + if t in nxt or t in offs: E[off].append(t) + loc = {} + elif op == 12 and words: + E[off].append(base + words[0]); fall = None + elif op in isl.REL and words: + E[off].append(base + words[0]) + if fall: E[off].append(fall) + return E, nxt + + +def dominating_conditions(b): + """For each END_PHASE / FORCE_END_PHASE site, the conditions that DOMINATE it. + + A condition dominates an exit when EVERY path from an entry to that exit + passes through it -- so it is a NECESSARY condition for the phase to end. + That is what the port needs. + + ⚠️ The obvious query, "one branch reaches END_PHASE and the other does not", + is WRONG for this language and was tried first. The dominant shape here is a + POLL LOOP, where the loop-back branch also reaches the exit -- one iteration + later -- so neither successor discriminates. It found exactly ONE guard in + each of Stage 02's phases 1 and 3 (a `read_freg(0) < 1200` timeout) while + missing every objective test. Dominance has no such blind spot. + """ + import collections as _c + E, nxt = edges(b, spawn=True) + offs = isl.linear_offsets(b) + entries = {e for e in set(isl.phase_bases(b)) | set(coroutine_entries(b)) if e in nxt} + preds = _c.defaultdict(list) + for a, ss in E.items(): + for s in ss: preds[s].append(a) + R, q = set(entries), _c.deque(entries) + while q: + n = q.popleft() + for s in E.get(n, ()): + if s not in R: R.add(s); q.append(s) + order = [o for o in offs if o in R] + idx = {o: i for i, o in enumerate(order)} + N = len(order); FULL = (1 << N) - 1 + DOM = [(1 << i) if o in entries else FULL for i, o in enumerate(order)] + for _ in range(50): + changed = False + for i, o in enumerate(order): + if o in entries: continue + ps = [idx[p] for p in preds.get(o, ()) if p in idx] + if not ps: new = 1 << i + else: + acc = DOM[ps[0]] + for p in ps[1:]: acc &= DOM[p] + new = acc | (1 << i) + if new != DOM[i]: DOM[i] = new; changed = True + if not changed: break + conds = {c['off']: c for c in conditions(b, isl.symbols(b, 1), isl.symbols(b, 2))} + bases = isl.phase_bases(b) + out = [] + for e, bid, _x in [(o, bid, x) for o, bid, x in isl.call_sites(b) if bid in (6, 62)]: + ph = sum(1 for x in bases if x <= e) + if e not in idx: + out.append({'end': e, 'phase': ph, 'builtin': bid, 'conds': None}); continue + m = DOM[idx[e]] + dc = [conds[order[i]] for i in range(N) if (m >> i) & 1 and order[i] in conds] + out.append({'end': e, 'phase': ph, 'builtin': bid, + 'conds': sorted(dc, key=lambda c: c['off'])}) + return out, len(R), len(offs) + + def coroutine_entries(b): """Every `start_coroutine` target, found by a linear pre-pass.""" offs = isl.linear_offsets(b) diff --git a/tools/re-capture/isl_report.py b/tools/re-capture/isl_report.py index fc405fe1..db66b722 100644 --- a/tools/re-capture/isl_report.py +++ b/tools/re-capture/isl_report.py @@ -9,7 +9,8 @@ arguments) and once when three built-in names were corrected. Keeping the generator in the tree is the point of this file. isl_report.py phase-ends -> every END_PHASE with its context - isl_report.py conditions -> every condition site, comparand resolved + isl_report.py conditions -> every condition site, comparand resolved + isl_report.py phase-guards -> the NECESSARY conditions for each exit The "needs the coroutine entry points" blocker recorded here is REFUTED: the instruction stream is FLAT and `isl.linear_offsets` reaches 25705/25705 call @@ -130,12 +131,43 @@ def emit_conditions(b, path): % (c['off'], lhs, c['rel'], rhs, c['target'])) +def emit_phase_guards(b, path): + """The conditions that DOMINATE each phase exit — the per-phase clear condition.""" + rows, reached, total = isl_cfg.dominating_conditions(b) + print('# %s — what each phase exit requires' % path) + print() + print('Generated by `tools/re-capture/isl_report.py phase-guards`.') + print() + print('A condition is listed when it DOMINATES the exit: every path from an') + print('entry to that `END_PHASE` passes through it, so it is NECESSARY for the') + print('phase to end that way. Reachability alone is the wrong query here —') + print('in a poll loop both successors reach the exit.') + print() + print('CFG reached %d of %d instructions (%.1f%%).' % (reached, total, 100.0 * reached / total)) + for r in rows: + nm = isl.BUILTIN.get(r['builtin'], 'builtin%d' % r['builtin']) + if r['conds'] is None: + print() + print('## phase %d — %s at 0x%06X: UNREACHABLE from any static entry' + % (r['phase'], nm, r['end'])) + print(' (started from the trigger queue at `phase+272`, by data not code)') + continue + print() + print('## phase %d — %s at 0x%06X: %d necessary condition(s)' + % (r['phase'], nm, r['end'], len(r['conds']))) + for c in r['conds']: + lhs = c['lhs'] if c['lhs'] is not None else '' + rhs = c['rhs'] if c['rhs'] is not None else '' + print(' 0x%06X %s %s %s' % (c['off'], lhs, c['rel'], rhs)) + + def main(): path = sys.argv[1] b = isl.load(path) name = path.replace('\\', '/').split('/')[-1] {'calls': emit_calls, 'phase-ends': emit_phase_ends, - 'conditions': emit_conditions}[sys.argv[2]](b, name) + 'conditions': emit_conditions, + 'phase-guards': emit_phase_guards}[sys.argv[2]](b, name) if __name__ == '__main__':