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).
5.6 KiB
✅ The ISL branch ops — a condition-code machine, read off the handlers
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).
✅ op13–op18 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 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:
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 atr3+44and stores a word to[phase+168]; it is almost certainly how a built-in's return value reachesspecial[], 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 passesphase+168andphase+44, not the pc — and was not read.- The bitset container at
phase+24.op10reaches it with0x822749B0(by address,phase+24) andop13with0x82274CC0(through a word loaded fromphase+32). Both land on the same bits, but the exact container layout is not established, sophase+32is 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.