Files
Sylpheed/docs/re/structures/isl-builtin-dispatch.md
Sylpheed RE agent 0287a78633 re: close the ISL operand chain -- built-in result is special[0], via a vtable
Answers what the previous commit left open: naming the branches did not give a
clear condition, because that needs the operand chain feeding each compare.

First, a correction to my own work.  isl-bytecode.md -- which OWNS the opcode table
-- already named ops 21-24 push.i/push.f/pop.i/pop.f.  isl-branches.md, which I
wrote last iteration, said op21 and op23 were unread.  The stale file was mine.
Verified from the thunks rather than accepted: 21 pushes [phase+168] onto the deque
at phase+44, 22 pushes [phase+184] onto phase+64, and the 23/24 handlers touch only
r3+168 and r3+184.  So pop.i lands in special[1].

New: the 147-entry built-in table is a thin DISPATCH LAYER, not implementations.
Each stub resolves the local[] argument base and tail-calls a fixed ScriptPhase
vtable slot.  112 of 147 dispatch that way; 17 write [phase+164] inline; 0 write
+184.  Every named predicate is in the vtable group -- unit_state 184, unit_alive
188, hp_pct_test 64, dist_lt 56, is_engaged 252, timer_elapsed 372 -- which is the
control that the split separates engine queries from script bookkeeping.

The vtable is 0x820A84BC, derived from a known implementation rather than a stride:
MARK_LAST_PHASE is documented as [phase+300]=2; the function 0x8226B498 is exactly
that stub; it appears as a data word at exactly one address, 0x820A8570; built-in
39 uses slot 180.  The check NOT used in the derivation: built-in 40 mark_not_last
uses slot 176, and slot 176 holds the [phase+300]=1 stub.  Predicted and confirmed.
The db's own vptr_writes independently lists 0x820A84BC, written at 0x82261B80.

unit_state = slot 184 = 0x8226ADF0, which indexes [phase+324] by local[4] and writes
its answer to [phase+164] = special[0] at both exits.  The phase-3 poll loop now
reads end to end: unit_state(ADT308) -> special[0]; pop.i -> special[1]; cmp.i; beq.

isl.py names ops 21-24; the calls artefact regenerates with NO diff.

Left open and said so: the other 111 vtable slots, which comparand each site pushes,
the 35 non-vtable built-ins, and the vtable's length.
2026-08-27 05:21:10 +00:00

105 lines
4.8 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.
# ✅ How a built-in's result reaches a condition — the operand chain
The previous iteration named the branches and stated plainly that this was still
missing: *"naming the branch does not by itself give the clear condition — that
needs the operand chain feeding each compare."* It is read now, and it closes.
## ⚠️ First: the corpus already had half of it, and I had written the stale file
[`isl-bytecode.md`](isl-bytecode.md) — which **owns** the opcode table — already
named ops 2124 (`push.i` / `push.f` / `pop.i` / `pop.f`) and ops 1318 as the
six branches. My own [`isl-branches.md`](isl-branches.md), written one iteration
earlier, said `op23` and `op21` were unread. **The stale file was mine.**
Verified rather than assumed, from the thunks and handlers:
| op | moves | verified by |
|---|---|---|
| 21 `push.i` | `[phase+168]` → deque at `phase+44` | thunk: `addi r4,r30,168` / `addi r3,r30,44` |
| 22 `push.f` | `[phase+184]` → deque at `phase+64` | thunk: `addi r4,r30,184` / `addi r3,r30,64` |
| 23 `pop.i` | → `[phase+168]` | handler `0x82271C30`, only r3-offset touched is **168** |
| 24 `pop.f` | → `[phase+184]` | handler `0x82271CB8`, only r3-offset touched is **184** |
With `isl-bytecode.md`'s operand-kind table (`special[0] = [phase+164]`,
`special[1] = [phase+168]`), `pop.i` lands in **`special[1]`**.
## ✅ The built-in table is a thin dispatch layer over a vtable
Each of the 147 entries is a **stub**, not an implementation. The stub resolves
the `local[]` argument base and tail-calls a fixed slot of the `ScriptPhase`
vtable at `[phase+0]`:
```
82272DFC addi r3, r31, 20 ; local[] base
82272E00 bl 0x82454A40 ; resolve
82272E04 lwz r11, 0(r31) ; the vptr
82272E10 lwz r11, 184(r11) ; <- fixed slot, one per built-in
82272E14 b 0x822724F0 ; mtspr CTR / bcctrl, then return 0
```
Measured over all 147:
| | count |
|---|---|
| dispatched through a `ScriptPhase` vtable slot | **112** |
| write `[phase+164]` (`special[0]`) inline in the stub | 17 |
| write `[phase+184]` inline | 0 |
**Every named predicate is in the vtable group**`unit_state` 184,
`unit_alive` 188, `hp_pct_test` 64, `dist_lt` 56, `unit_hp_pct` 256,
`is_engaged` 252, `group_ratio_pct` 196, `timer_elapsed` 372,
`player_gauge0/1_test` 396/400 — which is the control: the split is not
arbitrary, it separates engine queries from script-local bookkeeping.
## ✅ The vtable is at `0x820A84BC`, derived self-checkingly
Not guessed from a stride — the trap this corpus already paid for. Derived from
a **known implementation**:
1. `isl-bytecode.md` documents built-in **39** `MARK_LAST_PHASE` as `[phase+300] = 2`.
2. The function `0x8226B498` is exactly `addi r11,r0,2 ; stw r11,300(r3) ; blr`.
3. It appears as a data word at exactly **one** address: `0x820A8570`.
4. Built-in 39's stub uses slot **180** → base = `0x820A8570 180` = **`0x820A84BC`**.
**The check, which was not used in the derivation:** built-in **40**
`mark_not_last` (`[phase+300] = 1`) uses slot **176**, so the base predicts
`0x820A84A8`… and slot 176 holds `0x8226B4A8`, which is
`addi r11,r0,1 ; stw r11,300(r3) ; blr` — the `= 1` stub sitting immediately
after the `= 2` one. Predicted and confirmed.
**Third, independent:** the database's own `vptr_writes` lists
`0x820A84BC` as a vtable, written at `0x82261B80`.
## ✅ And the result lands in `special[0]`
`unit_state` is slot 184 → **`0x8226ADF0`**. It indexes `[phase+324]` — the unit
array `isl-builtins.md` already documents — by `local[4]`, reads the record, and
writes its answer to **`[phase+164]` = `special[0]`** at both its normal exit
(`0x8226AEC4`) and its early exit (`0x8226AF48`).
That completes the chain, and the phase-3 poll loop now reads end to end:
```
call unit_state(ADT308) ; result -> special[0]
pop.i ; special[1] <- the pushed comparand
cmp.i special[0], special[1]
beq -> 0xFEB4 ; loop back while they are equal
```
**A built-in's return value is `special[0]`; the comparand is popped into
`special[1]`; the compare and branch do the rest.** That is the shape of every
clear condition in the corpus.
## 🟡 Not settled
* **The other 111 vtable slots are not read.** The base is now known, so each is
a lookup rather than a search — but knowing where `hp_pct_test` lives is not
the same as having read it.
* **Which comparand each site pushes.** The loop above compares against whatever
`push.i` put on the deque; recovering that per site needs the push tracked
through the decode, which `isl.py` does not do.
* **The 35 non-vtable built-ins**, and `op21`/`op22`'s generic deque helpers
(`0x82175C20`, `0x82274BA0`), were not read — only their arguments.
* **The vtable's length.** `0x820A84BC` is the base and slot 404 is in use, so it
is at least 102 entries; I did not establish where it ends.