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

5.6 KiB
Raw Blame History

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 floats0x822716E0 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 2k=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@+4the 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 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.