Two files (isl_report.py's docstring and structures/isl-builtins.md) recorded the same blocker on a faithful per-phase condition listing: that it needs the coroutine entry points from start_coroutine's operand. Measured against isl.call_sites(), which enumerates by scanning the encoding rather than by decoding and so is an independent denominator: linear + jumps, stopping at ret (what the tool did) 133 / 2846 = 4.7% linear + jumps, continuing past ret 2275 / 2846 = 79.9% ... + following start_coroutine (the recorded fix) 2355 / 2846 = 82.7% plain linear decode, no control flow at all 2846 / 2846 = 100.0% Following the coroutine entries buys 2.8 points. Disc-wide, a plain linear decode from the first phase base reaches 25705/25705 call sites over all 28 stages, and 28/28 decode clean to code_end with no desync. The real bug was isl.dis ending on `if op == 20: break`. Op 20 is `ret`, but this is a coroutine VM -- the thread suspends and resumes at the FOLLOWING instruction, so code continues past it. dis() now takes stop_at_ret (default True, preserving the old output: data/isl-stage02.txt regenerates byte-identical) and isl.linear_offsets() is the correct walk. By-product, kept with its control: start_coroutine's target is staged slot 0 -- 73/83 phase-1 sites land on a valid instruction, against a 38.7% chance rate for an arbitrary 4-aligned offset. New artefact data/isl-stage02-phase-ends.txt with a committed generator (isl_report.py phase-ends). It shows END_PHASE's call site is the WRONG place to read a clear condition: all 12 Stage-02 sites sit in one stereotyped outro. Not settled, and stated as such: op10/op13/op14/op21/op23 are unread handlers, so the condition in the poll loop upstream cannot be named yet.
107 lines
4.4 KiB
Markdown
107 lines
4.4 KiB
Markdown
# ✅ The ISL instruction stream is FLAT — the recorded blocker was my own decoder
|
|
|
|
Two files recorded the same blocker on producing a faithful per-phase condition
|
|
listing — `tools/re-capture/isl_report.py`'s docstring and
|
|
[`structures/isl-builtins.md`](structures/isl-builtins.md):
|
|
|
|
> *"neither `isl.resync` … nor a naive linear decode from the phase base reaches
|
|
> every call site, so producing that listing faithfully needs the **coroutine
|
|
> entry points**, which `start_coroutine`'s operand carries and this tool does
|
|
> not yet follow."*
|
|
|
|
**Refuted.** The coroutine entry points were never the problem.
|
|
|
|
## The measurement
|
|
|
|
`isl.call_sites()` enumerates calls by **scanning the encoding**, not by
|
|
decoding, so it is an independent denominator — exactly the control this needed.
|
|
Against it, for `Stage02.ssb` (2 846 call sites):
|
|
|
|
| decode strategy | call sites reached |
|
|
|---|---|
|
|
| linear + jumps, stopping at `ret` — *what the tool did* | 133 — **4.7 %** |
|
|
| linear + jumps, continuing past `ret` | 2 275 — 79.9 % |
|
|
| … + following `start_coroutine`'s target — *the recorded fix* | 2 355 — **82.7 %** |
|
|
| **plain linear decode, no control flow at all** | **2 846 — 100.0 %** |
|
|
|
|
Following the coroutine entries — the thing both files said was required — buys
|
|
**2.8 points**. Ignoring control flow entirely gets everything.
|
|
|
|
### And it holds across the disc
|
|
|
|
Decoding linearly from the first phase base to `code_end`:
|
|
|
|
**28 files, 25 705 / 25 705 call sites = 100.00 %, and 28 / 28 decode clean to
|
|
the end with no desync.** Not one stage has a gap or a bad length.
|
|
|
|
So the stream is a flat, self-consistent instruction sequence. Reaching a call
|
|
site needs no control-flow reconstruction — only a correct length walk.
|
|
|
|
## The actual bug: `ret` is a YIELD
|
|
|
|
`isl.dis` ended its loop on `if op == 20: break`. Op 20 *is* `ret` — but this is
|
|
a **coroutine VM**: the handler "sets `r29=1` and takes the suspend path", the
|
|
thread parks, and it later **resumes at the following instruction**. Code
|
|
continues after a `ret`, so stopping there abandons the file at the first one.
|
|
For Stage 02 that is offset `0x1F0` of a `0x34C80` code region — 271 of 18 739
|
|
instructions.
|
|
|
|
This also silently truncated my own reading: the listing for the phase-3
|
|
`FORCE_END_PHASE` at `0x34A10` stopped four instructions short of the site,
|
|
at the `ret` at `0x34A0C`.
|
|
|
|
`dis` now takes `stop_at_ret` (default `True`, preserving the old output — the
|
|
committed `data/isl-stage02.txt` regenerates **byte-identical**), and
|
|
`isl.linear_offsets()` is the correct walk.
|
|
|
|
## ✅ By-product: `start_coroutine`'s target is staged slot 0
|
|
|
|
Worth keeping even though it was not the blocker. Of 83 `start_coroutine` sites
|
|
in phase 1, the only staged slot is **0**, and `phase_base + slot0` lands on a
|
|
valid instruction in **73/83 = 88 %**.
|
|
|
|
**Control:** an arbitrary 4-aligned offset in the code region looks like a valid
|
|
instruction **38.7 %** of the time (20 887 / 53 990). So 88 % is discriminating,
|
|
not the null result — the operand is a code offset relative to the phase base.
|
|
|
|
## 🟡 Where a phase actually ends — and where the condition is NOT
|
|
|
|
New artefact [`data/isl-stage02-phase-ends.txt`](data/isl-stage02-phase-ends.txt)
|
|
(generator: `isl_report.py phase-ends`). Stage 02 has **15** phase-ending calls:
|
|
12 `END_PHASE` (6) and 3 `FORCE_END_PHASE` (62).
|
|
|
|
**Every one of the 12 `END_PHASE` sites sits in the same stereotyped outro:**
|
|
|
|
```
|
|
call wait_cmds_drained
|
|
call fade_sound(3)
|
|
call builtin85(3)
|
|
call wait_s(3)
|
|
call END_PHASE
|
|
call end_coroutine
|
|
```
|
|
|
|
That is the phase *teardown*, not its condition — so **`END_PHASE`'s call site
|
|
is the wrong place to look for the clear condition.** The decision is upstream,
|
|
in the `op10`/`op13` pair that precedes the outro, which
|
|
[`structures/isl-builtins.md`](structures/isl-builtins.md) already flags as an
|
|
unread compare/branch (handlers `0x82271598` and `0x82271830`).
|
|
|
|
The phase-3 `FORCE_END_PHASE` region shows the shape a real condition takes —
|
|
a poll loop, not a straight line:
|
|
|
|
```
|
|
op10 global[20], 7
|
|
op14 -> 0xFEB4
|
|
call unit_state(ADT308)
|
|
op23
|
|
op10 special, 1
|
|
op13 -> 0xFEB4 ; branch back — poll until the test passes
|
|
call end_coroutine
|
|
```
|
|
|
|
🟡 **Not settled:** `op10`/`op13`/`op14`/`op21`/`op23` are still unread
|
|
handlers, so which way each branch goes is a guess, and I am not naming them
|
|
from a pattern — the corpus has paid for that twice already. Reading those five
|
|
handlers is what turns this listing into the per-phase clear condition.
|