This repository has been archived on 2026-09-16. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
Syplheed-Reborn/docs/re/isl-stream-is-flat.md
Sylpheed RE agent f41847701c 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.
2026-08-27 05:00:49 +00:00

4.4 KiB

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:

"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 retwhat 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 (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 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.