Files
Sylpheed/docs/re/structures/isl-unit-args.md
Sylpheed RE agent b42cd7183c re: builtin80 is a command -- and finding that exposed an 11.75% bug in my tracker
Reading builtin80's body (0x82268460) to name it: it is NOT a predicate.  It
allocates a 20-byte object, stamps vtable 0x820A8CB0, magic 0xAB0311BA and the
unit's live object into it, pushes it onto a queue via the same helper push.i uses,
and returns 1 -- or 0 when the unit is absent.  A command.

That made the conditions listing impossible: it showed a six-way switch
`if builtin80(TCT206) == 0 … == 5` on a function returning 1 or 0.  Disassembling the
site shows two unconditional `jmp`s between the call and the compare, so 0x1B6C0 is
reached ONLY by a branch and its special[0] has nothing to do with builtin80.

op12 is unconditional -- the next instruction is never reached by fall-through -- and
the tracker walked through it exactly as it had walked through end_coroutine.  Last
iteration I fixed the instance and not the class, leaving 22x more bad sites in place
than the fix removed.

A/B over all 28 stages, 7563 sites, resetting at jmp as well:
  sites whose operands change              889  (11.75%)
  LHS unresolved, before -> after     34 (0.45%) -> 756 (10.00%)

So the previous commit's headline "0.0% unresolved" was a MISSING CHECK, not a strong
result: the linear walk always had some value to report, and reporting it was the bug.
10% is the honest figure and the other 90% is trustworthy for a reason.

Also corrected: isl-unit-args.md illustrated its diff with 0x1B6C0, which is one of
the bogus sites.  The UNIT_ARG result itself stands -- it came from reading
implementations, not from this listing -- but the example was picked from bad output.

Not done, and said so: recovering the 756 needs a dataflow join over each block's
actual predecessors, a CFG fixpoint rather than a linear pass.  The branch targets are
all known so the CFG is available; the analysis is not written.

calls and phase-ends regenerate byte-identical; conditions changes on 187 lines.
2026-08-27 05:53:20 +00:00

101 lines
4.3 KiB
Markdown
Raw Permalink 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.
# ✅ Which built-ins take a unit — read from the implementations, and the old set was short
`isl.py`'s `UNIT_ARG` decides whether a built-in's slot-4 operand is printed as
a **unit name** or as a raw number. It was built **statistically**, from operand
ranges, and its own comment says slots were listed *"only when the ratio stayed
below 1.0"* — i.e. only when every observed value resolved. That is
conservative, and it was.
## The direct method
[`isl-builtin-dispatch`](isl-builtin-dispatch.md) makes this a lookup: a
built-in's stub tail-calls a fixed slot of the `ScriptPhase` vtable at
`0x820A84BC`, so the implementation can simply be read. Every unit-taking
built-in opens the same way — this is `unit_state`, `builtin80`, `builtin117`
and `builtin136`, character for character:
```
lwz r10, 324(r31) ; the unit array
lwz r11, 4(r30) ; r30 = the local[] base, so this is local[4]
rlwinm r11, r11, 2, 0, 29 ; x4
lwzx r11, r11, r10 ; -> the record
lwz r11, 4(r11) ; the handle
cmpli cr6, 0, r11, 0x0 ; "is this squadron gone?"
```
## ✅ Result: 31 → 55
| | |
|---|---|
| built-ins whose implementation indexes `[phase+324]` by an argument | **55** |
| of the statistical set's 31, confirmed | **31 — all of them** |
| **UNIT_ARG claims a unit, the implementation does not** | **0** |
| **implementation says unit, UNIT_ARG missed it** | **24** |
The 24: `21, 22, 23, 32, 42, 44, 46 (squadron_trace), 49, 50, 51, 55, 60, 61,
72 (group_ratio_pct), 80, 83, 94 (is_engaged), 101, 109 (set_unit_flags), 117,
136, 137 (wait_units_ready), 141, 142 (deploy_and_wait)`.
Zero false positives is worth stating on its own: the statistical method was
**right about everything it claimed** and only too cautious about what it
omitted.
## ⚠️ The first control I chose was worthless — recorded because it nearly passed
I first checked whether the additions' slot-4 operands resolve to a symbol-table-2
index. They did, **100.0 %** — and it means nothing:
| set | operands resolving to a symtab-2 unit |
|---|---|
| the 31 baseline | 100.0 % |
| the 24 additions | 100.0 % |
| **the 92 built-ins in neither set** | **99.3 %** |
Symbol table 2 is dense enough that almost any small integer lands in it, so the
test does not discriminate. A control that the negative class also passes is not
evidence, and this one was one careless glance from being written up as proof.
## ✅ The control that does discriminate
`isl-builtins.md` documents that a symbol operand is a **two-word pair** — a tag
holding the constant 1, then the index. So slot 0 should be 1 exactly when
slot 4 is a unit:
| set | calls with `slot0 == 1` |
|---|---|
| the 31 baseline | **100.0 %** (13 677 calls) |
| the 24 additions | **100.0 %** (140 calls) |
| the 92 in neither set | **2.5 %** (2 903 calls) |
A 40× separation, and the additions sit exactly on the positive class.
## Effect on the artefacts
`data/isl-stage02.txt` and `-phase-ends.txt` regenerate **byte-identical**;
`-conditions.txt` changes on 28 sites, every diff line pairing, all of them a
raw number becoming a unit name:
```
- 0x01B6C0 if builtin80(1, 117) == 0 -> + 0x01B6C0 if builtin80(TCT206) == 0
```
⚠️ **That example was itself a bogus site** — see
[isl-conditions](isl-conditions.md#-the-correction--i-fixed-the-instance-not-the-class).
`0x1B6C0` is reached only by a branch, so attributing its comparison to
`builtin80` was wrong, and `builtin80` is a command rather than a predicate. The
UNIT_ARG result is unaffected — it was derived by reading implementations, not
from this listing — but the illustration was picked from bad output.
## 🟡 Not settled
* **The 24 are still unnamed.** Knowing an argument is a unit is not knowing what
the built-in does. `builtin80` (69 operands disc-wide) is tested against
0, 1, 2, 3, 4 in a switch chain, so it returns a small enumeration — but I am
not naming it from that, and its body past the liveness check is unread.
* `builtin103` (`0x8226BFA8`) is a predicate over **`[phase+10152]` and
`[phase+10156]`**, neighbours of a value read out at `+10160`; it takes no unit.
The fields have 9, 7 and 1 writers respectively, none of them read.
* `builtin105` (`0x8226BFF0`) tests a unit record's **`+16` against 4**. What
`rec+16` holds is not established — `isl-builtins.md` only rules out its being
what `unit_state` reads.