re: the ISL stream is flat -- refute the "needs coroutine entry points" blocker
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.
This commit is contained in:
106
docs/re/isl-stream-is-flat.md
Normal file
106
docs/re/isl-stream-is-flat.md
Normal file
@@ -0,0 +1,106 @@
|
||||
# ✅ 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.
|
||||
Reference in New Issue
Block a user