diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index e3884e24..9d771406 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -1442,9 +1442,18 @@ premise was wrong.** on `builtin80`, which returns only 1 or 0. A/B over 28 stages: **889 of 7563 sites (11.75 %) change**, and unresolved goes **34 (0.45 %) → 756 (10.00 %)**. The earlier "0.0 % unresolved" was a MISSING CHECK, not a strong result. - 🟡 Recovering the 756 needs a real dataflow **join over each block's actual - predecessors** (a CFG fixpoint) — the branch targets are all known, the analysis - is not written. ▶️ **Still open:** the 35 unnamed built-ins (`builtin16` 132 + ~~🟡 Recovering the 756 needs a real dataflow join over each block's actual + predecessors (a CFG fixpoint) — the analysis is not written.~~ + ✅ **(2026-08-27) WRITTEN — `tools/re-capture/isl_cfg.py`.** Worklist fixpoint, + join over actual predecessors. **85.0 %** of instructions reached; unknown LHS + **756 (10.00 %) → 402 (5.32 %)**, of which **389 are never reached** and only + **13** are genuine join-aways. It also found **161 more sites where the linear + walk gave a confident WRONG answer**. ⚠️ Two of my own zero-results on the way: + the phase bases reach only 36 % (most routines are coroutines with no static + predecessor, so every `start_coroutine` target must be seeded), and that seeding + first found **0 entries in a file with 216** because the target is staged in TWO + steps (`special[0]=imm` then `local[0]=special[0]`). 🟡 The 389 are a real limit: + they are started from the **trigger queue at `phase+272`** — by data, not code. ▶️ **Still open:** the 35 unnamed built-ins (`builtin16` 132 sites, `builtin105` 117, `builtin103` 115 — each now a vtable-slot lookup); ✅ **(2026-08-27) partial — [structures/isl-unit-args](structures/isl-unit-args.md):** reading the implementations shows **55** built-ins take a unit at `local[4]`, diff --git a/docs/re/data/isl-stage02-conditions.txt b/docs/re/data/isl-stage02-conditions.txt index 26bfeab7..ea642240 100644 --- a/docs/re/data/isl-stage02-conditions.txt +++ b/docs/re/data/isl-stage02-conditions.txt @@ -6,37 +6,41 @@ The deque ops are an expression stack: `push.i` saves the comparand, the right-hand side is evaluated (its result lands in `special[0]`), `pop.i` restores the comparand into `special[1]`, then `cmp.i` compares. +Operands come from a CFG dataflow fixpoint (`isl_cfg.py`), joining each +block over its ACTUAL predecessors — not from a linear walk. + 965 condition sites; phase bases 0xe4 0x14aa8 0x24b4c ## most-tested predicates - unit_state 255 - hp_pct_test 167 + unit_state 248 + hp_pct_test 165 dist_lt 92 - unit_alive 71 + unit_alive 62 unit_relation 52 - builtin103 39 builtin105 38 - read_freg 10 + builtin103 35 + random 26 unit_hp_pct 9 + read_freg 9 builtin7 9 builtin99 1 ## phase 1 — 435 sites - 0x002E48 if builtin103() != 1 -> 0x322C - 0x0032F4 if unit_alive(TCN105) != 1 -> 0x3388 - 0x0044A4 if unit_alive(TCN105) != 1 -> 0x4538 + 0x002E48 if != -> 0x322C + 0x0032F4 if != -> 0x3388 + 0x0044A4 if != -> 0x4538 0x004D70 if unit_hp_pct(TCN001, Character_Player_Test) != 0 -> 0x51FC - 0x005110 if == 0 -> 0x4E68 - 0x005124 if == 1 -> 0x4F10 - 0x005138 if == 2 -> 0x4FB8 - 0x00514C if == 3 -> 0x5060 + 0x005110 if random(3) == 0 -> 0x4E68 + 0x005124 if random(3) == 1 -> 0x4F10 + 0x005138 if random(3) == 2 -> 0x4FB8 + 0x00514C if random(3) == 3 -> 0x5060 0x00523C if hp_pct_test(TCN004, 0) != 1 -> 0x5840 - 0x00572C if == 0 -> 0x5334 - 0x005740 if == 1 -> 0x53DC - 0x005754 if == 2 -> 0x5484 - 0x005768 if == 3 -> 0x552C - 0x00577C if == 4 -> 0x55D4 - 0x005790 if == 5 -> 0x567C + 0x00572C if random(5) == 0 -> 0x5334 + 0x005740 if random(5) == 1 -> 0x53DC + 0x005754 if random(5) == 2 -> 0x5484 + 0x005768 if random(5) == 3 -> 0x552C + 0x00577C if random(5) == 4 -> 0x55D4 + 0x005790 if random(5) == 5 -> 0x567C 0x005888 if read_freg(0) <= 210 -> 0x6028 0x0058DC if hp_pct_test(ADT102, 0) != 1 -> 0x6028 0x005930 if hp_pct_test(ADT107, 0) != 1 -> 0x6028 @@ -270,21 +274,21 @@ the right-hand side is evaluated (its result lands in `special[0]`), 0x0103D0 if unit_state(ADT113) == 1 -> 0x10438 0x010484 if unit_state(ADN114) == 1 -> 0x104EC 0x010538 if unit_state(ADN115) == 1 -> 0x105A0 - 0x0105C4 if == 28 -> 0xFB1C - 0x0105D8 if == 29 -> 0xFBD0 - 0x0105EC if == 30 -> 0xFC84 - 0x010600 if == 31 -> 0xFD38 - 0x010614 if == 32 -> 0xFDEC - 0x010628 if == 33 -> 0xFEA0 - 0x01063C if == 34 -> 0xFF54 - 0x010650 if == 35 -> 0x10008 - 0x010664 if == 36 -> 0x100BC - 0x010678 if == 37 -> 0x10170 - 0x01068C if == 38 -> 0x10224 - 0x0106A0 if == 39 -> 0x102D8 - 0x0106B4 if == 40 -> 0x1038C - 0x0106C8 if == 41 -> 0x10440 - 0x0106DC if == 42 -> 0x104F4 + 0x0105C4 if global[20] == 28 -> 0xFB1C + 0x0105D8 if global[20] == 29 -> 0xFBD0 + 0x0105EC if global[20] == 30 -> 0xFC84 + 0x010600 if global[20] == 31 -> 0xFD38 + 0x010614 if global[20] == 32 -> 0xFDEC + 0x010628 if global[20] == 33 -> 0xFEA0 + 0x01063C if global[20] == 34 -> 0xFF54 + 0x010650 if global[20] == 35 -> 0x10008 + 0x010664 if global[20] == 36 -> 0x100BC + 0x010678 if global[20] == 37 -> 0x10170 + 0x01068C if global[20] == 38 -> 0x10224 + 0x0106A0 if global[20] == 39 -> 0x102D8 + 0x0106B4 if global[20] == 40 -> 0x1038C + 0x0106C8 if global[20] == 41 -> 0x10440 + 0x0106DC if global[20] == 42 -> 0x104F4 0x010728 if builtin103() != 0 -> 0x10748 0x010788 if hp_pct_test(TCN004, 30) != 1 -> 0x108D4 0x010814 if dist_lt(TCN004, TCN001, 10000) == 1 -> 0x108D4 @@ -404,31 +408,31 @@ the right-hand side is evaluated (its result lands in `special[0]`), 0x012F1C if unit_state(ADN131) != 2 -> 0x12FAC 0x012F30 if global[36] != 0 -> 0x12FAC 0x012F68 if builtin105(ADN131) != 1 -> 0x12FAC - 0x012FF0 if unit_state(ADN132, global[12], 1) != 2 -> 0x13080 + 0x012FF0 if unit_state(ADN132) != 2 -> 0x13080 0x013004 if global[40] != 0 -> 0x13080 0x01303C if builtin105(ADN132) != 1 -> 0x13080 - 0x0130C4 if unit_state(ADN133, global[12], 1) != 2 -> 0x13154 + 0x0130C4 if unit_state(ADN133) != 2 -> 0x13154 0x0130D8 if global[44] != 0 -> 0x13154 0x013110 if builtin105(ADN133) != 1 -> 0x13154 - 0x013198 if unit_state(ADN134, global[12], 1) != 2 -> 0x13228 + 0x013198 if unit_state(ADN134) != 2 -> 0x13228 0x0131AC if global[48] != 0 -> 0x13228 0x0131E4 if builtin105(ADN134) != 1 -> 0x13228 - 0x01326C if unit_state(ADN135, global[12], 1) != 2 -> 0x132FC + 0x01326C if unit_state(ADN135) != 2 -> 0x132FC 0x013280 if global[52] != 0 -> 0x132FC 0x0132B8 if builtin105(ADN135) != 1 -> 0x132FC - 0x013340 if unit_state(ADN136, global[12], 1) != 2 -> 0x133D0 + 0x013340 if unit_state(ADN136) != 2 -> 0x133D0 0x013354 if global[56] != 0 -> 0x133D0 0x01338C if builtin105(ADN136) != 1 -> 0x133D0 - 0x013414 if unit_state(ADN137, global[12], 1) != 2 -> 0x134A4 + 0x013414 if unit_state(ADN137) != 2 -> 0x134A4 0x013428 if global[60] != 0 -> 0x134A4 0x013460 if builtin105(ADN137) != 1 -> 0x134A4 - 0x0134E8 if unit_state(ADN138, global[12], 1) != 2 -> 0x13578 + 0x0134E8 if unit_state(ADN138) != 2 -> 0x13578 0x0134FC if global[64] != 0 -> 0x13578 0x013534 if builtin105(ADN138) != 1 -> 0x13578 - 0x0135BC if unit_state(ADN139, global[12], 1) != 2 -> 0x1364C + 0x0135BC if unit_state(ADN139) != 2 -> 0x1364C 0x0135D0 if global[68] != 0 -> 0x1364C 0x013608 if builtin105(ADN139) != 1 -> 0x1364C - 0x013690 if unit_state(ADN140, global[12], 1) != 2 -> 0x13720 + 0x013690 if unit_state(ADN140) != 2 -> 0x13720 0x0136A4 if global[72] != 0 -> 0x13720 0x0136DC if builtin105(ADN140) != 1 -> 0x13720 0x013724 if global[12] < 6 -> 0x14264 @@ -452,28 +456,28 @@ the right-hand side is evaluated (its result lands in `special[0]`), 0x014340 if global[16] >= 4 -> 0x143F4 0x0143F4 if global[12] < 3 -> 0x144BC 0x014408 if global[16] >= 3 -> 0x144BC - 0x014504 if builtin103(global[12]) != 0 -> 0x14524 + 0x014504 if builtin103() != 0 -> 0x14524 0x014524 if global[28] != 1 -> 0x145E4 0x014628 if unit_state(ADS151) != 1 -> 0x14694 0x014680 if unit_state(TCS151) == 1 -> 0x146A0 0x014744 if dist_lt(TCN001, ADS151, 8000) == 1 -> 0x1481C ## phase 2 — 302 sites - 0x018580 if builtin103() != 1 -> 0x186E8 - 0x018C30 if unit_alive(TCN208) != 1 -> 0x18C98 - 0x018CBC if unit_alive(TCN207) != 1 -> 0x18D24 - 0x018DB8 if hp_pct_test(ADN204, 0) != 1 -> 0x18E4C - 0x018FFC if hp_pct_test(ADN208, 0) != 1 -> 0x19090 - 0x019124 if builtin103() != 1 -> 0x1915C + 0x018580 if != -> 0x186E8 + 0x018C30 if != -> 0x18C98 + 0x018CBC if != -> 0x18D24 + 0x018DB8 if != -> 0x18E4C + 0x018FFC if != -> 0x19090 + 0x019124 if != -> 0x1915C 0x0191CC if unit_hp_pct(TCN001, Character_Player_Test) != 0 -> 0x19658 - 0x01956C if == 0 -> 0x192C4 - 0x019580 if == 1 -> 0x1936C - 0x019594 if == 2 -> 0x19414 - 0x0195A8 if == 3 -> 0x194BC + 0x01956C if random(3) == 0 -> 0x192C4 + 0x019580 if random(3) == 1 -> 0x1936C + 0x019594 if random(3) == 2 -> 0x19414 + 0x0195A8 if random(3) == 3 -> 0x194BC 0x019698 if hp_pct_test(TCT206, 0) != 1 -> 0x1994C 0x0199C8 if builtin7(TCT206, 1, Route_TCT206_p2S, 4294967295, 500) == 1 -> 0x19A38 0x019A24 if read_freg(0) <= 600 -> 0x19A44 - 0x019A8C if read_freg(0, 1) <= 240 -> 0x1A1E4 + 0x019A8C if <= 240 -> 0x1A1E4 0x019AE0 if hp_pct_test(ADN201, 0) != 1 -> 0x1A1E4 0x019B34 if hp_pct_test(ADN202, 0) != 1 -> 0x1A1E4 0x019B88 if hp_pct_test(ADN203, 0) != 1 -> 0x1A1E4 @@ -499,18 +503,18 @@ the right-hand side is evaluated (its result lands in `special[0]`), 0x01A1E4 if global[4] == 0 -> 0x1AC5C 0x01A2B0 if global[4] != 1 -> 0x1A864 0x01A2F4 if unit_alive(ADN201, 0) != 1 -> 0x1A32C - 0x01A350 if unit_alive(ADN202, global[104]) != 1 -> 0x1A388 - 0x01A3AC if unit_alive(ADN207, global[104]) != 1 -> 0x1A3E4 - 0x01A408 if unit_alive(ADN223, global[104]) != 1 -> 0x1A440 - 0x01A464 if unit_alive(ADN224, global[104]) != 1 -> 0x1A49C - 0x01A4C0 if unit_alive(ADN225, global[104]) != 1 -> 0x1A4F8 - 0x01A51C if unit_alive(ADN226, global[104]) != 1 -> 0x1A554 - 0x01A578 if unit_alive(ADN227, global[104]) != 1 -> 0x1A5B0 - 0x01A5D4 if unit_alive(ADN228, global[104]) != 1 -> 0x1A60C - 0x01A630 if unit_alive(ADN229, global[104]) != 1 -> 0x1A668 - 0x01A68C if unit_alive(ADN230, global[104]) != 1 -> 0x1A6C4 - 0x01A6E8 if unit_alive(ADN231, global[104]) != 1 -> 0x1A720 - 0x01A744 if unit_alive(ADN232, global[104]) != 1 -> 0x1A77C + 0x01A350 if unit_alive(ADN202) != 1 -> 0x1A388 + 0x01A3AC if unit_alive(ADN207) != 1 -> 0x1A3E4 + 0x01A408 if unit_alive(ADN223) != 1 -> 0x1A440 + 0x01A464 if unit_alive(ADN224) != 1 -> 0x1A49C + 0x01A4C0 if unit_alive(ADN225) != 1 -> 0x1A4F8 + 0x01A51C if unit_alive(ADN226) != 1 -> 0x1A554 + 0x01A578 if unit_alive(ADN227) != 1 -> 0x1A5B0 + 0x01A5D4 if unit_alive(ADN228) != 1 -> 0x1A60C + 0x01A630 if unit_alive(ADN229) != 1 -> 0x1A668 + 0x01A68C if unit_alive(ADN230) != 1 -> 0x1A6C4 + 0x01A6E8 if unit_alive(ADN231) != 1 -> 0x1A720 + 0x01A744 if unit_alive(ADN232) != 1 -> 0x1A77C 0x01A77C if global[104] <= 1 -> 0x1A844 0x01A924 if hp_pct_test(TCT206, 89.9) != 0 -> 0x1AA38 0x01AA38 if global[104] != 0 -> 0x1AAEC @@ -536,12 +540,12 @@ the right-hand side is evaluated (its result lands in `special[0]`), 0x01B554 if dist_lt(TCT206, ADN231, 20000) == 1 -> 0x1B598 0x01B5E0 if hp_pct_test(ADN232, 0) == 1 -> 0x1B680 0x01B66C if dist_lt(TCT206, ADN232, 20000) == 1 -> 0x1B6B0 - 0x01B6C0 if == 0 -> 0x1AC74 - 0x01B6D4 if == 1 -> 0x1B140 - 0x01B6E8 if == 2 -> 0x1B258 - 0x01B6FC if == 3 -> 0x1B370 - 0x01B710 if == 4 -> 0x1B488 - 0x01B724 if == 5 -> 0x1B5A0 + 0x01B6C0 if global[76] == 0 -> 0x1AC74 + 0x01B6D4 if global[76] == 1 -> 0x1B140 + 0x01B6E8 if global[76] == 2 -> 0x1B258 + 0x01B6FC if global[76] == 3 -> 0x1B370 + 0x01B710 if global[76] == 4 -> 0x1B488 + 0x01B724 if global[76] == 5 -> 0x1B5A0 0x01B77C if hp_pct_test(TCN207, 0) != 1 -> 0x1B79C 0x01B7D4 if unit_alive(ADN201) != 1 -> 0x1B8A4 0x01B840 if dist_lt(TCN207, ADN201, 15000) != 1 -> 0x1B8A4 @@ -563,12 +567,12 @@ the right-hand side is evaluated (its result lands in `special[0]`), 0x01C090 if dist_lt(TCN207, ADN231, 20000) == 1 -> 0x1C0D4 0x01C11C if hp_pct_test(ADN232, 0) == 1 -> 0x1C1BC 0x01C1A8 if dist_lt(TCN207, ADN232, 20000) == 1 -> 0x1C1EC - 0x01C1FC if == 0 -> 0x1B7B0 - 0x01C210 if == 1 -> 0x1BC7C - 0x01C224 if == 2 -> 0x1BD94 - 0x01C238 if == 3 -> 0x1BEAC - 0x01C24C if == 4 -> 0x1BFC4 - 0x01C260 if == 5 -> 0x1C0DC + 0x01C1FC if global[80] == 0 -> 0x1B7B0 + 0x01C210 if global[80] == 1 -> 0x1BC7C + 0x01C224 if global[80] == 2 -> 0x1BD94 + 0x01C238 if global[80] == 3 -> 0x1BEAC + 0x01C24C if global[80] == 4 -> 0x1BFC4 + 0x01C260 if global[80] == 5 -> 0x1C0DC 0x01C2B8 if hp_pct_test(TCN208, 0) != 1 -> 0x1C2D8 0x01C310 if unit_alive(ADN201) != 1 -> 0x1C3E0 0x01C37C if dist_lt(TCN208, ADN201, 15000) != 1 -> 0x1C3E0 @@ -590,12 +594,12 @@ the right-hand side is evaluated (its result lands in `special[0]`), 0x01CBCC if dist_lt(TCN208, ADN231, 20000) == 1 -> 0x1CC10 0x01CC58 if hp_pct_test(ADN232, 0) == 1 -> 0x1CCF8 0x01CCE4 if dist_lt(TCN208, ADN232, 20000) == 1 -> 0x1CD28 - 0x01CD38 if == 0 -> 0x1C2EC - 0x01CD4C if == 1 -> 0x1C7B8 - 0x01CD60 if == 2 -> 0x1C8D0 - 0x01CD74 if == 3 -> 0x1C9E8 - 0x01CD88 if == 4 -> 0x1CB00 - 0x01CD9C if == 5 -> 0x1CC18 + 0x01CD38 if global[84] == 0 -> 0x1C2EC + 0x01CD4C if global[84] == 1 -> 0x1C7B8 + 0x01CD60 if global[84] == 2 -> 0x1C8D0 + 0x01CD74 if global[84] == 3 -> 0x1C9E8 + 0x01CD88 if global[84] == 4 -> 0x1CB00 + 0x01CD9C if global[84] == 5 -> 0x1CC18 0x01D200 if hp_pct_test(ADN201, 0) != 1 -> 0x1D244 0x01D27C if unit_alive(TCN207) != 1 -> 0x1D34C 0x01D2E8 if dist_lt(ADN201, TCN207, 15000) != 1 -> 0x1D34C @@ -611,11 +615,11 @@ the right-hand side is evaluated (its result lands in `special[0]`), 0x01D9EC if dist_lt(ADN201, TCN208, 20000) == 1 -> 0x1DA30 0x01DAA8 if builtin7(ADN201, 1, Route_ADN201_p2S, 4294967295, 500) != 1 -> 0x1DB48 0x01DBC0 if dist_lt(ADN201, TCT206, 20000) == 1 -> 0x1DC04 - 0x01DC1C if == 0 -> 0x1D258 - 0x01DC30 if == 1 -> 0x1D5E8 - 0x01DC44 if == 2 -> 0x1D810 - 0x01DC58 if == 3 -> 0x1DA38 - 0x01DC6C if == 4 -> 0x1DC0C + 0x01DC1C if global[88] == 0 -> 0x1D258 + 0x01DC30 if global[88] == 1 -> 0x1D5E8 + 0x01DC44 if global[88] == 2 -> 0x1D810 + 0x01DC58 if global[88] == 3 -> 0x1DA38 + 0x01DC6C if global[88] == 4 -> 0x1DC0C 0x01DCC4 if hp_pct_test(ADN202, 0) != 1 -> 0x1DD08 0x01DD4C if hp_pct_test(ADN207, 0) != 1 -> 0x1DD6C 0x01DDA4 if unit_alive(TCN207) != 1 -> 0x1DE74 @@ -636,11 +640,11 @@ the right-hand side is evaluated (its result lands in `special[0]`), 0x01E7D4 if builtin7(ADN207, 1, Route_ADN207_p2S, 3, 55) != 1 -> 0x1E914 0x01E80C if unit_alive(TCN208) != 1 -> 0x1E8AC 0x01E98C if dist_lt(ADN207, TCT206, 20000) == 1 -> 0x1E9D0 - 0x01E9E8 if == 0 -> 0x1DD80 - 0x01E9FC if == 1 -> 0x1E1D4 - 0x01EA10 if == 2 -> 0x1E49C - 0x01EA24 if == 3 -> 0x1E764 - 0x01EA38 if == 4 -> 0x1E9D8 + 0x01E9E8 if global[96] == 0 -> 0x1DD80 + 0x01E9FC if global[96] == 1 -> 0x1E1D4 + 0x01EA10 if global[96] == 2 -> 0x1E49C + 0x01EA24 if global[96] == 3 -> 0x1E764 + 0x01EA38 if global[96] == 4 -> 0x1E9D8 0x01F78C if unit_state(ADN201) != 1 -> 0x1F7F8 0x01F7E4 if unit_state(TCT206) == 1 -> 0x1F804 0x01F85C if dist_lt(ADN201, TCT206, 10000) != 1 -> 0x1FA7C @@ -763,24 +767,24 @@ the right-hand side is evaluated (its result lands in `special[0]`), 0x02492C if dist_lt(TCT206, ADN202, 10000) != 1 -> 0x249EC ## phase 3 — 228 sites - 0x028B98 if builtin103() != 1 -> 0x28E20 - 0x029C78 if unit_alive(TCN002) != 1 -> 0x29D04 - 0x02A4D8 if unit_alive(TCN002) != 1 -> 0x2A60C - 0x02A630 if unit_alive(TCN003) != 1 -> 0x2A764 - 0x02B0D8 if unit_alive(TCN002) != 1 -> 0x2B20C - 0x02B230 if unit_alive(TCN003) != 1 -> 0x2B310 + 0x028B98 if != -> 0x28E20 + 0x029C78 if != -> 0x29D04 + 0x02A4D8 if != -> 0x2A60C + 0x02A630 if != -> 0x2A764 + 0x02B0D8 if != -> 0x2B20C + 0x02B230 if != -> 0x2B310 0x02B4F8 if unit_hp_pct(TCN001, Character_Player_Test) != 0 -> 0x2B984 - 0x02B898 if == 0 -> 0x2B5F0 - 0x02B8AC if == 1 -> 0x2B698 - 0x02B8C0 if == 2 -> 0x2B740 - 0x02B8D4 if == 3 -> 0x2B7E8 + 0x02B898 if random(3) == 0 -> 0x2B5F0 + 0x02B8AC if random(3) == 1 -> 0x2B698 + 0x02B8C0 if random(3) == 2 -> 0x2B740 + 0x02B8D4 if random(3) == 3 -> 0x2B7E8 0x02B9C4 if hp_pct_test(TCN004, 0) != 1 -> 0x2BFC8 - 0x02BEB4 if == 0 -> 0x2BABC - 0x02BEC8 if == 1 -> 0x2BB64 - 0x02BEDC if == 2 -> 0x2BC0C - 0x02BEF0 if == 3 -> 0x2BCB4 - 0x02BF04 if == 4 -> 0x2BD5C - 0x02BF18 if == 5 -> 0x2BE04 + 0x02BEB4 if random(5) == 0 -> 0x2BABC + 0x02BEC8 if random(5) == 1 -> 0x2BB64 + 0x02BEDC if random(5) == 2 -> 0x2BC0C + 0x02BEF0 if random(5) == 3 -> 0x2BCB4 + 0x02BF04 if random(5) == 4 -> 0x2BD5C + 0x02BF18 if random(5) == 5 -> 0x2BE04 0x02BFC8 if global[112] < 4 -> 0x2C1F8 0x02C240 if read_freg(0) <= 300 -> 0x2CF8C 0x02C294 if hp_pct_test(ADT301, 0) != 1 -> 0x2CF8C @@ -839,12 +843,12 @@ the right-hand side is evaluated (its result lands in `special[0]`), 0x02F5BC if global[124] != 0 -> 0x2F664 0x02F5F4 if unit_alive(ADT306) != 1 -> 0x2F664 0x02F644 if unit_relation(TCN003, ADT306) != 1 -> 0x2F664 - 0x02F6A4 if hp_pct_test(ADT306, 0, 1) != 1 -> 0x2F82C + 0x02F6A4 if hp_pct_test(ADT306, 0) != 1 -> 0x2F82C 0x02F6B8 if global[120] != 0 -> 0x2F780 0x02F6CC if global[124] != 1 -> 0x2F780 0x02F830 if global[108] != 1 -> 0x2F9FC - 0x02F9C8 if == 0 -> 0x2F870 - 0x02F9DC if == 1 -> 0x2F918 + 0x02F9C8 if random(1) == 0 -> 0x2F870 + 0x02F9DC if random(1) == 1 -> 0x2F918 0x02FA44 if 8 < 1 -> 0x2FBC8 0x02FC10 if 8 < 3 -> 0x2FCF4 0x02FD3C if 8 < 4 -> 0x2FE20 @@ -964,7 +968,7 @@ the right-hand side is evaluated (its result lands in `special[0]`), 0x034110 if global[16] >= 2 -> 0x341C4 0x0341C4 if global[12] < 1 -> 0x3428C 0x0341D8 if global[16] >= 1 -> 0x3428C - 0x0342EC if unit_state(ADT301, global[12]) == 1 -> 0x34358 + 0x0342EC if unit_state(ADT301) == 1 -> 0x34358 0x034344 if unit_state(ADT302) != 1 -> 0x345FC 0x03439C if unit_state(TCN000) == 1 -> 0x343BC 0x034400 if unit_state(ADT301) == 1 -> 0x34478 @@ -974,20 +978,20 @@ the right-hand side is evaluated (its result lands in `special[0]`), 0x03460C if global[20] == 8 -> 0x34634 0x034620 if global[20] != 9 -> 0x34640 0x034640 if global[20] != 0 -> 0x346B8 - 0x034698 if unit_state(ADT301) == 1 -> 0x346B8 + 0x034698 if unit_state(ADT301, 1) == 1 -> 0x346B8 0x0346B8 if global[20] != 1 -> 0x34730 - 0x034710 if unit_state(ADT302) == 1 -> 0x34730 + 0x034710 if == 1 -> 0x34730 0x034730 if global[20] != 2 -> 0x347A8 - 0x034788 if unit_state(ADT303) == 1 -> 0x347A8 + 0x034788 if == 1 -> 0x347A8 0x0347A8 if global[20] != 3 -> 0x34820 - 0x034800 if unit_state(ADT304) == 1 -> 0x34820 + 0x034800 if == 1 -> 0x34820 0x034820 if global[20] != 4 -> 0x34898 - 0x034878 if unit_state(ADT305) == 1 -> 0x34898 + 0x034878 if == 1 -> 0x34898 0x034898 if global[20] != 5 -> 0x34910 - 0x0348F0 if unit_state(ADT306) == 1 -> 0x34910 + 0x0348F0 if == 1 -> 0x34910 0x034910 if global[20] != 6 -> 0x34988 - 0x034968 if unit_state(ADT307) == 1 -> 0x34988 + 0x034968 if == 1 -> 0x34988 0x034988 if global[20] != 7 -> 0x34A00 - 0x0349E0 if unit_state(ADT308) == 1 -> 0x34A00 - 0x034A90 if == 1 -> 0x34A30 - 0x034AA4 if == 2 -> 0x34A5C + 0x0349E0 if == 1 -> 0x34A00 + 0x034A90 if == -> 0x34A30 + 0x034AA4 if == -> 0x34A5C diff --git a/docs/re/structures/isl-conditions.md b/docs/re/structures/isl-conditions.md index aafd4b2e..02d10f4e 100644 --- a/docs/re/structures/isl-conditions.md +++ b/docs/re/structures/isl-conditions.md @@ -47,7 +47,7 @@ the stack, then reports each compare with its branch: | | all 28 stages | |---|---| | condition sites | **7 563** | -| **LHS honestly unresolved** | **756 — 10.0 %** | +| **LHS honestly unresolved** | **402 — 5.3 %** (CFG dataflow; the linear walk left 756) | | RHS is a plain number | 7 544 — 99.7 % | 🔴 **The first version of this table claimed 0.0 % unresolved. That was wrong** — @@ -182,4 +182,47 @@ word@+4`), so the CFG is available; the analysis is not written. **Method note worth keeping:** when the same defect appears twice, fix the class. Patching `end_coroutine` alone left 22× more bad sites in place than it removed, -and only another impossible-looking output exposed them. \ No newline at end of file +and only another impossible-looking output exposed them. + +## ✅ Replaced by a CFG dataflow fixpoint — `isl_cfg.py` + +The linear walk's honest 10 % unknown was a floor imposed by the method, not by +the data: a block entered only by a branch has *a* well-defined state, just not +one a straight-line pass can see. So the walk is gone, replaced by a worklist +fixpoint that joins each block's state over its **actual predecessors** — a value +survives only if every predecessor agrees. + +| over all 28 stages | linear walk | CFG dataflow | +|---|---|---| +| instructions reached | (all, but with stale state) | **85.0 %** | +| condition sites with an unknown LHS | 756 — 10.00 % | **402 — 5.32 %** | +| of those, never reached at all | — | 389 | +| joined away (predecessors genuinely disagree) | — | **13** | +| **sites where both resolve but DISAGREE** | — | **161** | + +Those 161 are 161 more places the linear walk reported a confident wrong answer, +on top of the 889 the `jmp` fix caught. + +### ⚠️ Two zero-results that were both my bug + +The first CFG run reached only **36 %** of instructions and made things *worse* +— 35 % unknown against the walk's 10 %. Two causes, and each surfaced as a +suspiciously round zero: + +1. **The phase bases reach almost nothing.** Most routines are **coroutines** the + engine starts from its trigger queue, so they have no static predecessor at + all. They must be seeded from every `start_coroutine` target. +2. **The seeding found ZERO entries in a file with 216 `start_coroutine` calls.** + The target is staged in *two* steps — `special[0] = imm`, then + `local[0] = special[0]` — and I matched only the direct-immediate form. + +Reachability went 36 % → 64 % → **85 %** as each was fixed. The lesson is the one +this corpus keeps re-teaching: *a count that does not move when it should is the +bug reporting itself.* Both times I checked because the number looked wrong, not +because a test failed. + +### 🟡 The 389 that remain are a real limit, not a gap + +Nothing in the bytecode starts them. They are entered from the **trigger queue at +`phase+272`** — by data, not by code — so no purely static analysis reaches them. +Resolving those needs the trigger table's contents, which is a separate question. diff --git a/tools/re-capture/isl_cfg.py b/tools/re-capture/isl_cfg.py new file mode 100644 index 00000000..cd6733be --- /dev/null +++ b/tools/re-capture/isl_cfg.py @@ -0,0 +1,177 @@ +"""ISL condition recovery by CFG DATAFLOW, replacing `isl.conditions`'s linear walk. + +`isl.conditions` walks the flat stream and resets its tracker at every +control-flow boundary, so a block entered only by a branch reports an unknown. +This module instead builds the CFG and runs a worklist fixpoint, joining each +block's state over its ACTUAL predecessors (a value survives the join only if +every predecessor agrees). + +Measured over all 28 stages, against the linear walk: + + instructions reached by the CFG 85.0% + condition sites with an unknown LHS 402 (5.32%) linear walk: 756 (10.00%) + of those, still never reached 389 + sites where both resolve but DISAGREE 161 <- the linear walk was wrong + +Two things the entry-point search had to get right, both of which read as zero +results first: + + * The phase bases reach only ~36% of the code. Most routines are COROUTINES + the engine starts from its trigger queue, so they have no static predecessor + and must be seeded from every `start_coroutine` target. + * That target is staged in TWO steps -- `special[0] = imm` then + `local[0] = special[0]`. Matching only the direct-immediate form found ZERO + entries in a file with 216 of them. + +The 389 that remain unreached are the honest limit: nothing in the bytecode +starts them, so they are entered by data (the trigger queue at `phase+272`), +not by code. +""" +import struct, collections +import isl + +REL = isl.REL + +def _val(op, kind, operand, lo, sp, loc): + if kind == 1: + if op == 1: + return '%.6g' % struct.unpack('>d', struct.pack('>II', operand, lo))[0] + return str(operand) + if kind == 2: return sp.get(operand) + if kind == 3: return loc.get(operand) + return 'global[%d]' % operand + +def _pack(sp, loc, stack): + return (tuple(sorted(sp.items())), tuple(sorted(loc.items())), tuple(stack)) + +def _join(a, bst): + if a is None: return bst + if bst is None: return a + if a == bst: return a + def m(x, y): + dx, dy = dict(x), dict(y) + return tuple(sorted((k, v) for k, v in dx.items() if dy.get(k) == v)) + st = a[2] if a[2] == bst[2] else () + return (m(a[0], bst[0]), m(a[1], bst[1]), st) + +def coroutine_entries(b): + """Every `start_coroutine` target, found by a linear pre-pass.""" + offs = isl.linear_offsets(b) + bases = isl.phase_bases(b) + out, 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)] + if op == 0 and len(words) >= 2: + # staging is TWO-step: `special[0] = imm` then `local[0] = special[0]`. + # Matching only the direct-immediate form found ZERO entries in a file + # with 216 start_coroutine sites. + v = words[1] if sk == 1 else sp.get(words[1]) if sk == 2 else None + if v is None: (sp if dk == 2 else loc).pop(words[0], None) + else: (sp if dk == 2 else loc)[words[0]] = v + elif op == 19 and words: + if words[0] == 1 and 0 in loc: + ph = sum(1 for x in bases if x <= off) + out.append(bases[ph - 1] + loc[0]) + loc = {} + return out + + +def conditions(b, s1=None, s2=None): + offs = isl.linear_offsets(b) + nxt = {offs[i]: offs[i + 1] for i in range(len(offs) - 1)} + bases = isl.phase_bases(b) + EMPTY = ((), (), ()) + IN = {} + work = collections.deque() + # Entry points. The phase bases alone reach only ~36% of the code: most + # routines are COROUTINES the engine starts from its trigger queue, so they + # have no static predecessor. Seed every `start_coroutine` target as well -- + # a linear pre-pass finds them because the target is staged into local[0] + # immediately before the call, which no control-flow boundary intervenes in. + entries = list(bases) + coroutine_entries(b) + for e in entries: + if e in nxt or e == offs[-1]: + IN[e] = EMPTY; work.append(e) + conds = {} + pend_at = {} + seen_entry = set(bases) + rounds = 0 + while work: + rounds += 1 + if rounds > 400000: break + off = work.popleft() + state = IN[off] + sp, loc, stack = dict(state[0]), dict(state[1]), list(state[2]) + 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)] + succ, fall = [], nxt.get(off) + if op in (0, 1) and len(words) >= 2: + v = _val(op, sk, words[1], words[2] if len(words) > 2 else 0, sp, loc) + 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: + bid = words[0] + nm = isl.BUILTIN.get(bid, 'builtin%d' % bid) + args = [] + tags = {sl - 4 for sl, ids in isl.UNIT_SLOTS.items() if bid in ids and sl in loc} + for sl in sorted(loc): + v = loc[sl] + if sl in tags and v == '1': continue + if v.isdigit(): + i = int(v) + if s2 and bid in isl.UNIT_SLOTS.get(sl, ()) and i in s2: v = s2[i][1] + elif s1 and bid in isl.SYM1_SLOTS.get(sl, ()) and i in s1: v = s1[i][1] + args.append(v) + sp[0] = '%s(%s)' % (nm, ', '.join(args)) + loc = {} + if bid == 1: # start_coroutine: a FRESH thread + t = state[1] and dict(state[1]).get(0) + if t and t.isdigit(): + e = bases[max(0, sum(1 for x in bases if x <= off) - 1)] + int(t) + if e in nxt or e in IN: + if e not in seen_entry: + seen_entry.add(e); IN[e] = EMPTY; work.append(e) + if bid == 11: # end_coroutine: thread destroyed + fall = None + elif op in (21, 22): + stack.append(sp.get(1)) + elif op in (23, 24): + if stack: sp[1] = stack.pop() + else: sp.pop(1, None) + elif op == 12 and words: + ph = sum(1 for x in bases if x <= off) + succ.append(bases[ph - 1] + words[0]); fall = None + elif op in (10, 11): + pend_at[off] = (_val(op, dk, words[0], 0, sp, loc), + _val(op, sk, words[1], words[2] if len(words) > 2 else 0, sp, loc)) + elif op in REL and words: + ph = sum(1 for x in bases if x <= off) + succ.append(bases[ph - 1] + words[0]) + out = _pack(sp, loc, stack) + for s in ([fall] if fall else []) + succ: + if s is None or s not in nxt and s not in IN and s != offs[-1]: continue + j = _join(IN.get(s), out) + if IN.get(s) != j: + IN[s] = j; work.append(s) + # read conditions off the fixpoint + out = [] + prev = None + for off in offs: + w = struct.unpack_from('>I', b, off)[0]; op = w & 0xFF + if op in (10, 11): prev = off + elif op in REL and prev is not None: + lhs, rhs = pend_at.get(prev, (None, None)) + ph = sum(1 for x in bases if x <= off) + words = [struct.unpack_from('>I', b, off + 4)[0]] + out.append({'off': prev, 'phase': ph, 'lhs': lhs, 'rel': REL[op], + 'rhs': rhs, 'target': bases[ph - 1] + words[0]}) + prev = None + return out diff --git a/tools/re-capture/isl_report.py b/tools/re-capture/isl_report.py index 17ccbda7..fc405fe1 100644 --- a/tools/re-capture/isl_report.py +++ b/tools/re-capture/isl_report.py @@ -21,6 +21,7 @@ import collections import sys import isl +import isl_cfg def census(b): cs = isl.call_sites(b) @@ -98,7 +99,7 @@ def emit_conditions(b, path): """Every condition site with its comparand — what `data/isl-stage02-conditions.txt` never had a generator for.""" import collections - cs = isl.conditions(b, isl.symbols(b, 1), isl.symbols(b, 2)) + cs = isl_cfg.conditions(b, isl.symbols(b, 1), isl.symbols(b, 2)) bases = isl.phase_bases(b) print('# %s — condition sites, comparands resolved' % path) print() @@ -108,9 +109,12 @@ def emit_conditions(b, path): print('the right-hand side is evaluated (its result lands in `special[0]`),') print('`pop.i` restores the comparand into `special[1]`, then `cmp.i` compares.') print() + print('Operands come from a CFG dataflow fixpoint (`isl_cfg.py`), joining each') + print('block over its ACTUAL predecessors — not from a linear walk.') + print() print('%d condition sites; phase bases %s' % (len(cs), ' '.join('0x%x' % x for x in bases))) - h = collections.Counter(c['lhs'].split('(')[0] for c in cs if '(' in c['lhs']) + h = collections.Counter(c['lhs'].split('(')[0] for c in cs if c['lhs'] and '(' in c['lhs']) print() print('## most-tested predicates') for k, n in h.most_common(20): @@ -120,8 +124,10 @@ def emit_conditions(b, path): print() print('## phase %d — %d sites' % (ph, len(rows))) for c in rows: + lhs = c['lhs'] if c['lhs'] is not None else '' + rhs = c['rhs'] if c['rhs'] is not None else '' print(' 0x%06X if %s %s %s -> 0x%X' - % (c['off'], c['lhs'], c['rel'], c['rhs'], c['target'])) + % (c['off'], lhs, c['rel'], rhs, c['target'])) def main():