re: recover ISL conditions by CFG dataflow instead of a linear walk

The linear walk's 10% unknown was a floor imposed by the method: a block entered only
by a branch has a well-defined state, just not one a straight-line pass can see.
tools/re-capture/isl_cfg.py replaces it with 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:
  instructions reached by the CFG          85.0%
  condition sites, unknown LHS             756 (10.00%) -> 402 (5.32%)
  of those, never reached at all           389
  joined away (predecessors disagree)       13
  both resolve but DISAGREE                161   <- linear walk was wrong here

Those 161 are on top of the 889 the previous jmp fix caught.

Two zero-results on the way, both my own bug, both caught because the number looked
wrong rather than because a test failed:

  * The first CFG run reached only 36% of instructions and made things WORSE (35%
    unknown).  Cause: the phase bases reach almost nothing.  Most routines are
    COROUTINES the engine starts from its trigger queue, with no static predecessor,
    so every start_coroutine target has to be seeded as an entry.
  * That seeding then found ZERO entries in a file with 216 start_coroutine calls,
    because 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 389 still unreached are an honest limit rather than a gap: nothing in the bytecode
starts them; they are entered from the trigger queue at phase+272, by data rather than
code, so no purely static analysis reaches them.

isl_report.py conditions now uses isl_cfg; calls and phase-ends regenerate
byte-identical.  Stage 02 unknowns drop from 71 to 25.
This commit is contained in:
Sylpheed RE agent
2026-08-27 06:02:37 +00:00
parent 58b404aef4
commit 5ea9e38b35
5 changed files with 371 additions and 132 deletions

View File

@@ -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.
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.