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/structures/isl-branches.md
Sylpheed RE agent bad96eb54a re: read the ISL branch handlers -- it is a condition-code machine
Closes the backlog item that was the last thing between the flat decode and a
per-phase clear condition, and closes isl-builtins.md's standing "op10 + op13 look
like a switch -- NOT confirmed".

op10 resolves two operands, issues a SIGNED cmp, and writes three condition bits to
a bitset at phase+24: bit 0 = EQ, bit 1 = GT, bit 2 = LT.  op11 is the same machine
for floats via fcmpu.  op13-op18 branch on those bits to [phase+232] + word@+4 --
the same phase-relative target form as the unconditional op12:

  13 bit0 set    beq      16 bits 2 then 0   ble
  14 bit0 clear  bne      18 bits 1 then 0   bge
  15 bit2 set    blt      17 bit1 set        bgt

13/14/15/17 are byte-identical apart from the bit index and the polarity.  All six
relations are present and each appears exactly once; that completeness is the check
that the reading is right, rather than the usage pattern -- which the item
explicitly warned against.

Operand order recorded because it is easy to reverse: LHS = (kind byte[1], word@+4),
RHS = (kind byte[0], word@+8).

Method note in the doc: the jump table at 0x822635FC holds THUNKS, and the handler
is the bl target inside each.  My first pass guessed handler addresses at a fixed
stride, landed mid-function, and produced a 20-line "difference" that was pure
misalignment.

isl.py names the ops; data/isl-stage02.txt is regenerated and every diff line pairs
exactly, only the op-name column changing (op10->cmp.i x5, op13->beq x4,
op14->bne x1).  data/isl-stage02-phase-ends.txt now shows the phase-3 poll loop
reading as one: unit_state(ADT308) -> op23 -> cmp.i -> beq back to 0xFEB4.

Left unnamed on purpose: op23 (0x82271C30) and op21 (0x82175C20).
2026-08-27 05:10:50 +00:00

139 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ✅ The ISL branch ops — a condition-code machine, read off the handlers
[`BACKLOG.md`](../BACKLOG.md) had this as the last thing between the flat decode
and a per-phase clear condition: five unread handlers, and an explicit
instruction not to name a branch from a pattern. They are read now, from
`sylpheed.db`. Nothing below is inferred from usage.
## Getting the real handler addresses
The 25-entry jump table at `0x822635FC` holds **thunks** inside the dispatcher,
not the handlers. Each thunk is three register moves and a `bl`; the `bl` target
is the handler. Taking the addresses any other way gets them wrong — my first
attempt guessed them at a fixed stride and landed mid-function, which produced a
20-line "difference" that was pure misalignment.
| op | thunk | handler |
|---|---|---|
| 10 | `0x822636F8` | `0x82271598` |
| 11 | `0x8226370C` | `0x822716E0` |
| 12 | `0x82263720` | *inline in the thunk* |
| 13 | `0x82263738` | `0x82271830` |
| 14 | `0x8226374C` | `0x822718C8` |
| 15 | `0x82263760` | `0x82271960` |
| 16 | `0x82263774` | `0x822719F8` |
| 17 | `0x8226379C` | `0x82271AC8` |
| 18 | `0x82263788` | `0x82271B60` |
## ✅ op10 / op11 are COMPARE, and they write three condition bits
`0x82271598` resolves two operands and compares them:
```
822715B0 lwz r5, 8(r29) ; word@+8
822715B4 lbz r4, 0(r29) ; kind byte[0]
822715B8 bl 0x82271D40 ; integer operand resolver -> r28 = RHS
822715C0 lwz r5, 4(r29) ; word@+4
822715C8 lbz r4, 1(r29) ; kind byte[1]
822715CC bl 0x82271D40 -> r27 = LHS
822715D4 addi r31, r31, 24 ; the condition bitset lives at phase+24
822715D8 cmp cr6, 0, r27, r28 ; SIGNED
```
It then writes **three** bits, each set-or-cleared by its own `cmp`:
| bit | written at | condition | set / clear |
|---|---|---|---|
| **0** | `0x822715D8` | `EQ` | `or` if equal, `andc` if not |
| **1** | `0x8227162C` | `GT` | `or` / `andc` on `cr6+gt` |
| **2** | `0x82271678` | `LT` | `or` / `andc` on `cr6+lt` |
**`op11` is the same machine for floats** — `0x822716E0` resolves through the
float resolvers (`0x82271F10` / `0x82271E30`), issues `fcmpu cr6, f30, f31`, and
writes the same bitset at `phase+24`.
**Operand order matters and is easy to get backwards:** LHS is `(kind byte[1],
word@+4)` and RHS is `(kind byte[0], word@+8)`. So a listing line
```
cmp.i k=01,02 00000000 00000002
```
is *compare `special[0]` against immediate `2`*`k=01` is the RHS kind
(`imm`), `k=02` the LHS kind (`special`).
## ✅ op13op18 are the six relational branches
Every one of them tests bits in that same bitset and, when taken, sets the pc to
`[phase+232] + word@+4` — **the identical phase-relative target form as the
unconditional `op12`**.
| op | bits tested | taken when | name |
|---|---|---|---|
| 13 | 0 | set | **`beq`** |
| 14 | 0 | clear | **`bne`** |
| 15 | 2 | set | **`blt`** |
| 17 | 1 | set | **`bgt`** |
| 16 | 2, then 0 | either set | **`ble`** |
| 18 | 1, then 0 | either set | **`bge`** |
13, 14, 15 and 17 are **byte-identical to each other** apart from two
instructions — the bit index (`addi r5, r0, N`) and the polarity
(`cmpli r11, 0x1` vs `0x0`). 16 and 18 are longer because they test a second bit
after the first fails, which is exactly `<=` and `>=`.
🔑 **All six relations are present and each appears exactly once.** That
completeness is the check: a mis-read would not produce a closed, non-redundant
relational set.
## ✅ This closes the `op10`+`op13` "switch" question
[`isl-builtins.md`](isl-builtins.md) recorded it as 🟡 *"Consecutive small
immediates each paired with their own code offset is the shape of a case/branch
dispatch … **Not confirmed** — the handlers have not been read."* They are read
now, and the shape is what it looked like — a chain of compare-and-branch-if-equal:
```
cmp.i k=01,02 00000000 00000001
beq -> 0x4E2C
cmp.i k=01,02 00000000 00000002
beq -> 0x4ED4
cmp.i k=01,02 00000000 00000003
beq -> 0x4F7C
```
*"switch (special[0]) { case 1: … case 2: … case 3: … }"*, lowered to sequential
compares. Confirmed from the handlers, not from the pattern.
## ✅ And a phase-3 clear condition now reads as one
From [`../data/isl-stage02-phase-ends.txt`](../data/isl-stage02-phase-ends.txt):
```
0349D0 call unit_state(ADT308)
0349DC op23 ; takes the call result
0349E0 cmp.i k=02,02 special[0], special[1]
0349EC beq -> 0xFEB4 ; loop back while it holds
0349F4 call end_coroutine
```
A coroutine polling `unit_state` on unit `ADT308` and branching **back** while
the comparison is equal — the poll loop the previous iteration could only
describe by shape.
## 🟡 Not settled
* **`op23`** (`0x82271C30`) is left unnamed. It indexes a container at `r3+44`
and stores a word to `[phase+168]`; it is almost certainly how a built-in's
return value reaches `special[]`, but "almost certainly" is how this corpus
acquired two names it later had to withdraw. Characterised, not named.
* **`op21`** (`0x82175C20`) has a different shape from all of these — its thunk
passes `phase+168` and `phase+44`, not the pc — and was not read.
* **The bitset container at `phase+24`.** `op10` reaches it with `0x822749B0`
(by address, `phase+24`) and `op13` with `0x82274CC0` (through a word loaded
from `phase+32`). Both land on the same bits, but the exact container layout
is not established, so `phase+32` is *not* asserted to be its data pointer.
* **Which value `special[0]` holds at a given site.** Naming the branch does not
by itself give the clear condition for every stage — that needs the operand
chain feeding each compare, which is the next step.