re: ISL opcodes decoded; the branch base is PER PHASE and isl.py was wrong

All 25 opcodes now have meanings. Ops 2/4/6/8 are integer compound assignment
(+= -= *= /=) and 3/5/7/9 the float versions; 10 and 11 are integer and float
compare writing three condition bits; 13-18 are je/jne/jl/jle/jg/jge; 21-24 are
push.i/push.f/pop.i/pop.f over deques at phase+44 and phase+64.

The shared-handler question is answered: the dispatcher leaves the opcode in r4
and the shared thunks never overwrite it, so those helpers take an extra opcode
argument and index a secondary table (0x82271448, 0x8227152C).

CORRECTION to my own tool and note: the branch/jump base is [phase+232], which
the phase initialiser sets to 0x24 + the phase's entry from the mission-level
stream -- 0xE4 / 0x14AA8 / 0x24B4C for Stage 02's three phases, not the file's
0x24. Measured on phase 1: base 0xE4 puts 525 of 525 branch targets on an
instruction boundary; base 0x24 manages 188. isl.py had been using 0x24 for
every phase, so its jump targets were wrong throughout. Fixed via
isl.phase_bases().

That also settles two things mission-script-ssb.md left open: offsets ARE
code-base-relative, and 0x1883's operand IS a code pointer -- the earlier worry
that some 'land on IEEE floats' was an artefact of adding the wrong base.
This commit is contained in:
Sylpheed RE agent
2026-08-25 18:52:09 +00:00
parent 415dd75a8b
commit 28a4b1ead1
4 changed files with 158 additions and 19 deletions

View File

@@ -37,27 +37,44 @@ Operand kinds go through resolvers with their own 4-entry table
|---|---|---|
| 0 | `82263660` | integer assign — resolve rvalue (`82271D40`, kind byte[0], word@+8), resolve lvalue (`82272030`, kind byte[1], word@+4), `stw` |
| 1 | `8226369C` | float assign — same shape with `82271F10`/`82272120` and `stfd` |
| 2,4,6,8 | `822636D0` | → `822713E8` (a compare/branch family; four opcodes share one handler) |
| 3,5,7,9 | `822636E4` | → `822714D0` (the sibling family) |
| 10 | `822636F8` | → `82271598` |
| 11 | `8226370C` | → `822716E0` |
| **2,4,6,8** | `822636D0` | → `822713E8` **integer compound assign**: `+= -= *= /=` |
| **3,5,7,9** | `822636E4` | → `822714D0` **float compound assign**: `fadd fsub fmul fdiv` |
| **10** | `822636F8` | → `82271598`**integer compare**, sets 3 condition bits |
| **11** | `8226370C` | → `822716E0`**float compare** (`fcmpu`; NaN clears all three) |
| **12** | `82263720` | **JUMP**`r31 = [phase+232] + word@+4` |
| 1318 | `82263738`… | `82271830`, `822718C8`, `82271960`, `822719F8`, `82271AC8`, `82271B60` |
| **1318** | `82263738`… | **conditional branches**`je`, `jne`, `jl`, `jle`, `jg`, `jge` |
| **19** | `822637B0` | **CALL BUILT-IN**`sub_82272220` |
| 20 | `82263874` | `li r29,1` then the suspend path — **yield / return** |
| 21 | `822637C4` | `sub_82175C20(phase+44, phase+168)` |
| 22 | `822637E4` | `sub_82274BA0(phase+64, phase+184)` |
| 23,24 | `82263804`… | `82271C30`, `82271CB8` |
| **21** | `822637C4` | **`push.i`** — `phase+44` deque ← `[phase+168]` (special int 1) |
| **22** | `822637E4` | **`push.f`** — `phase+64` deque ← `[phase+184]` (special float 1) |
| **23,24** | `82263804`… | **`pop.i` / `pop.f`** — back into `[+168]` / `[+184]` |
Handler return codes drive the outer loop at `0x82263828`: **0** continue,
**1** suspend, **2**/**3** other exits.
### ✅ Jump operands are code-base-relative
### 🔴 CORRECTED: the branch base is PER PHASE, not the file's `0x24`
Op 12 adds its operand to `[phase+232]`, the code base — i.e. the `.ssb`
header's code offset (`0x24` in every file). That settles, for this opcode, the
question `mission-script-ssb.md` left open about whether offsets are file- or
code-base-relative.
Op 12 adds its operand to `[phase+232]` — and **that is not `0x24`**. The phase
initialiser `sub_82270DF8` writes it as `0x24 + the phase's entry from the
mission-level stream`, whose three `0x1883` records carry `0xC0`, `0x14A84`,
`0x24B28` for Stage 02 → bases **`0xE4`, `0x14AA8`, `0x24B4C`**, one per phase.
Measured on Stage 02's phase-1 segment:
| base | branch targets landing on an instruction boundary |
|---|---|
| `0xE4` | **525 / 525** |
| `0x24` | 188 / 525 |
So the earlier "the code base is the header's `0x24`" was wrong, and
`tools/re-capture/isl.py` printed wrong jump targets for every phase — badly for
phases 2 and 3, and mostly wrong even in phase 1. Fixed: `isl.phase_bases()`
returns the three bases, and branch ops are annotated with the base in use.
This also settles two things `mission-script-ssb.md` left open: offsets **are**
code-base-relative, and `0x1883`'s operand **is** a code pointer (the earlier
worry that two of them "land on IEEE floats" was an artefact of adding the wrong
base).
### ✅ The call form, and a statement counter
@@ -149,7 +166,10 @@ read.
* Opcodes 211 and 1318 are named only by handler address. The four-way sharing
(2/4/6/8 and 3/5/7/9) suggests the handler re-reads the opcode to pick a
comparison or a type, but that is not yet read.
* The four-way opcode sharing (2/4/6/8 and 3/5/7/9) suggests the handler
re-reads the opcode to pick a comparison or a type; not yet read.
***How the shared handlers disambiguate — answered.** The dispatcher leaves
the opcode in `r4`, and the two shared thunks never overwrite it, so the
helpers are `f(phase, opcode, frame, &pc)` where every non-shared helper is
`f(phase, frame, &pc)`. Each helper then subtracts its base opcode and indexes
a **secondary** table (`0x82271448` for ops 28, `0x8227152C` for 39).
* The mission-level stream at `+0x24` of a `.ssb` — as opposed to this ISL
stream — is still only partly read.