re: three ISL built-in names were wrong, including the most-used one
All re-read twice — the handler, and the thing it calls — because each had been named from its shape rather than its effect. * id 11 `yield` -> `end_coroutine`. 0x82272624 is li r11,1 ; li r3,3 ; stw r11,164(r31), and the dispatcher's r3==3 arm erases the thread from the active list and returns it to the free list. It destroys the thread. 2945 sites game-wide, 372 in Stage 02 — the most-used built-in there was. * id 5 `await_label` -> `kill_coroutine(label)`. sub_82273B08 kills the thread parked at the target pc, or itself if the target is its own pc. It waits for nothing. * id 100 `push_trigger` -> `reset_phase_threads`. It clears the trigger container and then frees every thread whose pc differs from the caller's — the opposite of pushing a trigger. Corroborated by usage: its 12 Stage 02 sites all sit in the phase terminator, next to timer_stop, clear_flag(-1) and MARK_LAST_PHASE. One name recovered from the game's own text: opcode 992 prints "RequestScriptMessage %s" at 0x820A5700, so id 64 is request_script_message (2683 sites). Return codes documented properly: 1 = restart the coroutine from its entry (previously not recorded at all), 3 = terminate. And the blocking set was wrong in two places — it is 102, 120, 137, 142, 143. Id 97 does NOT block; its handler ends `b 0x822724F8`, so it always returns 0. Unit-operand resolution settled from DATA over all 28 stages rather than by reading 147 handlers: a slot qualifies only if every value is a valid symtab-2 index, it takes >=15 distinct values, AND its maximum reaches most of the table — that last clause is what discriminates, since every small integer is trivially "in range". 31 built-ins at slot 4, 8 at slot 12, one at slot 20. It also refutes set_flag's slot 0, whose maximum overruns the table, and the resolver now declines rather than inventing a name. New and unexplained: symtab-2 holds two types, 2 and 8, and built-ins 95 and 128 take type 8 at slot 12 in 100% of their sites. A downstream inference is withdrawn with it: the note reading the live trigger counter attributed it to "the script arming watches as it goes" via built-in 100. The measurement stands; the attribution does not. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
# Stage02.ssb — ISL disassembly artefacts
|
||||
|
||||
Generated by `tools/re-capture/isl_report.py calls`.
|
||||
|
||||
2846 call sites, 73 distinct built-ins
|
||||
builtin 11 372 site(s)
|
||||
builtin 69 255 site(s)
|
||||
@@ -81,14 +83,16 @@
|
||||
39 MARK_LAST_PHASE 8: 0x4dfc 0x52c8 0x60fc 0x19258 0x19724 0x2b584 0x2ba50 0x2c054
|
||||
40 mark_not_last 4: 0x5a10 0x1a270 0x2c578 0x2d06c
|
||||
|
||||
## condition vocabulary actually used by Stage 02
|
||||
11 yield 372
|
||||
## named built-ins used, by traffic
|
||||
11 end_coroutine 372
|
||||
69 unit_state 255
|
||||
1 start_coroutine 216
|
||||
64 request_script_message 213
|
||||
30 objective_marker 179
|
||||
20 hp_pct_test 167
|
||||
47 squadron_attack 137
|
||||
18 dist_lt 92
|
||||
5 await_label 83
|
||||
5 kill_coroutine 83
|
||||
70 unit_alive 71
|
||||
56 unit_relation 52
|
||||
4 wait_s 34
|
||||
@@ -97,13 +101,14 @@
|
||||
124 timer_stop 15
|
||||
120 wait_cmds_drained 14
|
||||
8 set_flag 12
|
||||
100 push_trigger 12
|
||||
100 reset_phase_threads 12
|
||||
93 clear_flag 12
|
||||
59 fade_sound 12
|
||||
6 END_PHASE 12
|
||||
9 read_freg 12
|
||||
95 unit_hp_pct 9
|
||||
115 named_event 9
|
||||
48 squadron_escort 8
|
||||
39 MARK_LAST_PHASE 8
|
||||
90 camera_at_route 8
|
||||
17 wait_frames 6
|
||||
@@ -116,6 +121,10 @@
|
||||
62 FORCE_END_PHASE 3
|
||||
127 timer_set 1
|
||||
|
||||
## phase code bases
|
||||
Each phase has its OWN base; the file header offset is not it.
|
||||
0xe4 0x14aa8 0x24b4c
|
||||
|
||||
## disassembly into the first END_PHASE
|
||||
resync from 0x5058
|
||||
005058: 0000080C jmp len=8 k=00,00 0000507C -> code+0x507C (file 0x50A0)
|
||||
@@ -131,7 +140,7 @@ resync from 0x5058
|
||||
0050CC: 02030C00 set.i len=12 k=02,03 00000010 00000000 local[16] = special[0]
|
||||
0050D8: 01021001 set.f len=16 k=01,02 00000000 BFF00000 00000000 special[0] = imm -1
|
||||
0050E8: 02030C01 set.f len=12 k=02,03 00000018 00000000 local[24] = special[0]
|
||||
0050F4: 00000C13 call len=12 k=00,00 00000040 00000245 builtin64(0x42, 0x2, 0x1, 0x9, 0x1, -1)
|
||||
0050F4: 00000C13 call len=12 k=00,00 00000040 00000245 request_script_message(0x42, 0x2, 0x1, 0x9, 0x1, -1)
|
||||
005100: 0000080C jmp len=8 k=00,00 0000507C -> code+0x507C (file 0x50A0)
|
||||
005108: 0000080C jmp len=8 k=00,00 0000507C -> code+0x507C (file 0x50A0)
|
||||
005110: 01020C0A op10 len=12 k=01,02 00000000 00000000
|
||||
@@ -153,13 +162,32 @@ resync from 0x5058
|
||||
0051CC: 02030C01 set.f len=12 k=02,03 00000000 00000000 local[0] = special[0]
|
||||
0051D8: 00000C13 call len=12 k=00,00 00000004 0000024C wait_s(3)
|
||||
0051E4: 00000C13 call len=12 k=00,00 00000006 0000024D END_PHASE
|
||||
0051F0: 00000C13 call len=12 k=00,00 0000000B 0000024E yield
|
||||
0051F0: 00000C13 call len=12 k=00,00 0000000B 0000024E end_coroutine
|
||||
0051FC: 01030C00 set.i len=12 k=01,03 00000000 00000001 local[0] = imm 0x1
|
||||
005208: 01030C00 set.i len=12 k=01,03 00000004 00000056 local[4] = imm 0x56
|
||||
005214: 01021001 set.f len=16 k=01,02 00000000 00000000 00000000 special[0] = imm 0
|
||||
005224: 02030C01 set.f len=12 k=02,03 00000008 00000000 local[8] = special[0]
|
||||
005230: 00000C13 call len=12 k=00,00 00000014 00000252 hp_pct_test(0)
|
||||
005230: 00000C13 call len=12 k=00,00 00000014 00000252 hp_pct_test(0x1, TCN004, 0)
|
||||
00523C: 01020C0A op10 len=12 k=01,02 00000000 00000001
|
||||
005248: 0000080E op14 len=8 k=00,00 0000575C
|
||||
005250: 01020C00 set.i len=12 k=01,02 00000000 00000000 special[0] = imm 0x0
|
||||
00525C: 02030C00 set.i len=12 k=02,03 00000000 00000000 local[0] = special[0]
|
||||
005268: 00000C13 call len=12 k=00,00 00000074 00000253 builtin116(0x0)
|
||||
005274: 00000C13 call len=12 k=00,00 00000065 00000254 builtin101
|
||||
005280: 00000C13 call len=12 k=00,00 00000064 00000255 reset_phase_threads
|
||||
00528C: 00000C13 call len=12 k=00,00 0000007C 00000256 timer_stop
|
||||
005298: 01020C00 set.i len=12 k=01,02 00000000 FFFFFFFF special[0] = imm 0xFFFFFFFF
|
||||
0052A4: 02030C00 set.i len=12 k=02,03 00000000 00000000 local[0] = special[0]
|
||||
0052B0: 00000C13 call len=12 k=00,00 0000005D 00000257 clear_flag(0xFFFFFFFF)
|
||||
0052BC: 00000C13 call len=12 k=00,00 00000076 00000258 builtin118
|
||||
0052C8: 00000C13 call len=12 k=00,00 00000027 0000025A MARK_LAST_PHASE
|
||||
0052D4: 00000C13 call len=12 k=00,00 00000052 0000025B builtin82
|
||||
0052E0: 01021001 set.f len=16 k=01,02 00000000 40100000 00000000 special[0] = imm 4
|
||||
0052F0: 02030C01 set.f len=12 k=02,03 00000000 00000000 local[0] = special[0]
|
||||
0052FC: 00000C13 call len=12 k=00,00 00000004 0000025C wait_s(4)
|
||||
005308: 01020C00 set.i len=12 k=01,02 00000000 00000005 special[0] = imm 0x5
|
||||
005314: 02030C00 set.i len=12 k=02,03 00000000 00000000 local[0] = special[0]
|
||||
005320: 00000C13 call len=12 k=00,00 0000000A 0000025E random(0x5)
|
||||
00532C: 0000080C jmp len=8 k=00,00 00005648 -> code+0x5648 (file 0x566C)
|
||||
005334: 01020C00 set.i len=12 k=01,02 00000000 00000043 special[0] = imm 0x43
|
||||
005340: 02030C00 set.i len=12 k=02,03 00000000 00000000 local[0] = special[0]
|
||||
|
||||
@@ -75,7 +75,8 @@ squadron lost anybody at all?"* — an escort-casualty test. **The game has no
|
||||
| `is_engaged` | **S05 only**, 3 sites, all on `TCN004` |
|
||||
|
||||
**S18–S23, the tutorials, are the outliers by a distance.** No flag/trigger
|
||||
machinery at all — zero `read_freg`, `clear_flag`, `push_trigger`,
|
||||
machinery at all — zero `read_freg`, `clear_flag`, `reset_phase_threads`
|
||||
(built-in 100, formerly mis-named `push_trigger`),
|
||||
`named_event`, `MARK_LAST_PHASE`, `FORCE_END_PHASE`, and none of the timer
|
||||
family. One `set_flag` and one `END_PHASE` each. They are linear scripted
|
||||
lessons, not missions.
|
||||
|
||||
@@ -22,9 +22,28 @@ Arguments do **not** live in the instruction. Every handler starts
|
||||
**packed operand blob**, which is what the `local[]` staging in
|
||||
[isl-bytecode](isl-bytecode.md) fills.
|
||||
|
||||
Return codes: **0** continue, **2** yield (re-execute next frame), **3**
|
||||
coroutine control. Five built-ins skip the pc advance on 2 and so genuinely
|
||||
**block**: 97, 120, 137, 142, 143.
|
||||
### ✅ Return codes — read off `ScriptPhase::Update` (`0x82263830`)
|
||||
|
||||
The dispatcher switches on `r3` exactly four ways:
|
||||
|
||||
| r3 | address | effect |
|
||||
|---|---|---|
|
||||
| **0** | `0x82263850` | continue to the next thread in the same frame |
|
||||
| **1** | `0x82263878` | `[thread+4] = [thread+0]` — **restart this coroutine from its entry** |
|
||||
| **2** | `0x82263888` | `[thread+4] = saved pc` — resume next frame |
|
||||
| **3** | `0x82263894` | `sub_8226EA20` erases the thread from the active list `[+216]`, then `sub_8226EAB8` returns it to the free list `[+204]` — **terminate this coroutine** |
|
||||
|
||||
Two tails do the pc bookkeeping: `0x822724F8` is `li r3,0` then advance;
|
||||
`0x822724FC` advances only, preserving `r3`. Advance is `pc += [insn+2]`, the
|
||||
length byte — the same field [isl-bytecode](isl-bytecode.md) decodes. So **every
|
||||
handler that ends `b 0x822724F0` returns 0**, and its only output is
|
||||
`[phase+164]`/`[phase+176]`.
|
||||
|
||||
❌ **CORRECTED — the blocking set was wrong in two places.** The blocking form is
|
||||
`bctrl ; cmpwi r3,2 ; bne 0x822724FC`, and it appears at **102, 120, 137, 142,
|
||||
143**. This file previously listed **97**, which does not block: its handler
|
||||
`0x8227313C` ends `b 0x822724F8`, so it *always* returns 0. And **102** was
|
||||
missing. All six handlers re-read to confirm.
|
||||
|
||||
## ✅ `ScriptPhase` state layout
|
||||
|
||||
@@ -65,7 +84,7 @@ two tables already parsed in [mission-script-ssb](mission-script-ssb.md).
|
||||
| **132–134** | player gauges | speed/boost ratios and a player byte |
|
||||
| **73, 123–127** | timer family | start / resume / stop / reset / read elapsed / read limit |
|
||||
| **8 / 9 / 93** | `set_flag` / `read_freg` / `clear_flag` | latch a result into the 32-entry files |
|
||||
| **100 / 115** | `push_trigger` / `named_event` | the engine→script edge |
|
||||
| **100 / 115** | `reset_phase_threads` / `named_event` | ❌ 100 is **not** `push_trigger` — see below |
|
||||
|
||||
**The state machine is therefore:** a trigger fires a coroutine → the coroutine
|
||||
tests one of the predicates → it latches the answer with `set_flag` → some later
|
||||
@@ -80,9 +99,63 @@ Two spot-checks I ran against the disassembly rather than taking on trust:
|
||||
a NULL object and state 1, then calls `823011B0` (initial, packed
|
||||
`hi<<16|lo`) and `82301118` (current). Exactly as described.
|
||||
|
||||
## ❌ Three built-in names WITHDRAWN
|
||||
|
||||
Each re-read twice — the handler, and the thing it calls — because all three had
|
||||
been named from their shape rather than their effect.
|
||||
|
||||
| id | was | **is** | evidence |
|
||||
|---|---|---|---|
|
||||
| **11** | `yield` | **`end_coroutine`** | `0x82272624` is `li r11,1 ; li r3,3 ; stw r11,164(r31)`. Return 3 **destroys the thread**. It is the single most-used built-in in the game — 2945 sites, 372 in Stage 02 alone — so this was the most load-bearing wrong name in the file. |
|
||||
| **5** | `await_label` | **`kill_coroutine(label)`** | `sub_82273B08` computes `target = [phase+232] + blob[0]`; if that equals the **caller's own** pc it returns 3 (kill self), otherwise it finds the thread parked at `target` in `[phase+220]` and moves it to the free list. It does not wait for anything. |
|
||||
| **100** | `push_trigger` | **`reset_phase_threads`** | the handler calls vtable slot 2 (clears the trigger container at `[phase+272]`) and then `sub_82273BE8`, which walks `[phase+220]` and frees **every thread whose pc differs from the caller's**. It drops queued triggers and terminates every *other* coroutine — the opposite of pushing one. |
|
||||
|
||||
One name is newly **recovered**, from the game's own text: interpreter opcode 992
|
||||
prints `★RequestScriptMessage %s` (`0x820A5700`), so **id 64 is
|
||||
`request_script_message`** — 2683 sites, and the second most-used built-in.
|
||||
|
||||
## ✅ Which operands are unit indices — settled from the data
|
||||
|
||||
`tools/re-capture/isl.py` resolved a symbol-table-2 name only for 11 built-ins,
|
||||
at slot 4. The real set is much larger, and it was established by **measurement
|
||||
over all 28 stages** rather than by reading 147 handlers:
|
||||
|
||||
> a slot qualifies only if every observed value is a valid symtab-2 index, it
|
||||
> takes ≥15 distinct values, **and its maximum reaches most of the table**.
|
||||
|
||||
That last clause is what makes the test work. Symbol table 2 tops out at 122
|
||||
entries, so a slot carrying something else overruns it; plain range-checking
|
||||
cannot separate an index from a bool, because every small integer is in range.
|
||||
|
||||
* **unit index at slot 4** — 2, 3, 7, 12, 15, 16, 18, 19, 20, 24, 25, 26, 28,
|
||||
29, 30, 47, 48, 56, 57, 58, 63, 69, 70, 79, 91, 92, 95, 105, 108, 128, 143
|
||||
* **a second at slot 12** — 2, 18, 47, 48, 56, 79, 95, 128
|
||||
* **a third at slot 20** — 128
|
||||
|
||||
Every one of these is 100.0 % in range across its call sites (the largest, id 20,
|
||||
over 2930 of them).
|
||||
|
||||
The test also **refuted** a tempting entry: `set_flag`'s slot 0 passes the range
|
||||
and spread checks but its maximum *exceeds* the table — flag indices run 0..31
|
||||
against symbol tables as small as 40 — so it is excluded, and the disassembler
|
||||
now declines to resolve it rather than printing an invented name.
|
||||
|
||||
❔ **New, unexplained:** symbol table 2 holds **two types**, 2 (1160 entries
|
||||
disc-wide) and 8 (249), and they are not interchangeable. Built-ins **95** and
|
||||
**128** take a type-2 unit at slot 4 and, at slot 12, an operand that is type 8
|
||||
in **100 %** of its 90 and 152 call sites. What separates the two classes is not
|
||||
established.
|
||||
|
||||
## ✅ What Stage 02 actually uses — and it settles a standing question
|
||||
|
||||
Counting call sites in `Stage02.ssb` (`data/isl-stage02.txt`):
|
||||
Counting call sites in `Stage02.ssb` (`data/isl-stage02.txt`, regenerated by
|
||||
`tools/re-capture/isl_report.py calls`):
|
||||
|
||||
⚠️ The sibling artefact `data/isl-stage02-conditions.txt` **predates the name
|
||||
corrections above** — it still prints `yield`, `await_label` and `push_trigger`,
|
||||
and its operand rendering predates the staging fix. It has no committed
|
||||
generator; reproducing it needs the coroutine entry points, which
|
||||
`start_coroutine`'s operand carries and the tool does not yet follow.
|
||||
|
||||
| built-in | sites |
|
||||
|---|---|
|
||||
@@ -91,7 +164,7 @@ Counting call sites in `Stage02.ssb` (`data/isl-stage02.txt`):
|
||||
| `dist_lt` | **92** |
|
||||
| `unit_alive` | **71** |
|
||||
| `unit_relation` | **52** |
|
||||
| `set_flag` / `clear_flag` / `push_trigger` | 12 each |
|
||||
| `set_flag` / `clear_flag` / `reset_phase_threads` | 12 each |
|
||||
| `END_PHASE` / `MARK_LAST_PHASE` / `FORCE_END_PHASE` | 12 / 8 / 3 |
|
||||
|
||||
**Not used at all in Stage 02:** `squad_survival_pct`, `group_ratio_pct`,
|
||||
@@ -285,9 +358,16 @@ Read from a running Stage 02 mission (`ScriptPhase 0xBE14DD80`, container at
|
||||
```
|
||||
|
||||
**`+20` moves, 0 → 1 → 2**, while the phase ordinal stays 1. So it is a real
|
||||
counter of **currently registered triggers** — the script arming watches as it
|
||||
goes (Stage 02 has 12 `push_trigger` sites) — and it is readable live with no
|
||||
debugger. That is the first direct view of *what the script is waiting for*.
|
||||
counter of **currently registered triggers**, readable live with no debugger —
|
||||
the first direct view of *what the script is waiting for*.
|
||||
|
||||
⚠️ The *measurement* stands; its attribution did not. This paragraph used to add
|
||||
"the script arming watches as it goes (Stage 02 has 12 `push_trigger` sites)",
|
||||
pointing at built-in 100. Built-in 100 is `reset_phase_threads` — it **clears**
|
||||
the trigger container, it does not arm one. The 12 sites are real, but they are
|
||||
12 places where Stage 02 *tears the trigger set down*, which is close to the
|
||||
opposite reading. ❔ What actually arms a trigger is now open again; built-ins
|
||||
19 and 25 both queue into `[phase+272]` and are the first place to look.
|
||||
|
||||
🟡 **`+12` is not a list head after all**, or not only that: it reads
|
||||
`0x000A0009`, which is not a pointer. The `addi r31, r30, 12` in the push made
|
||||
|
||||
@@ -78,18 +78,18 @@ KIND = {0: 'global', 1: 'imm', 2: 'special', 3: 'local'}
|
||||
# guess. See docs/re/structures/isl-builtins.md.
|
||||
BUILTIN = {
|
||||
1: 'start_coroutine', 2: 'deploy_squadron', 3: 'move_order', 4: 'wait_s',
|
||||
5: 'await_label', 6: 'END_PHASE', 8: 'set_flag', 9: 'read_freg',
|
||||
10: 'random', 11: 'yield', 13: 'play_se', 14: 'play_bgm',
|
||||
5: 'kill_coroutine', 6: 'END_PHASE', 8: 'set_flag', 9: 'read_freg',
|
||||
10: 'random', 11: 'end_coroutine', 13: 'play_se', 14: 'play_bgm',
|
||||
17: 'wait_frames', 18: 'dist_lt', 20: 'hp_pct_test', 24: 'squad_survival_pct',
|
||||
26: 'damage_unit', 30: 'objective_marker', 31: 'objective_marker_at_route',
|
||||
33: 'global_counter0', 34: 'global_counter1', 36: 'screen_fade',
|
||||
39: 'MARK_LAST_PHASE', 40: 'mark_not_last', 43: 'play_voice',
|
||||
45: 'play_voice_vol', 46: 'squadron_trace', 47: 'squadron_attack',
|
||||
48: 'squadron_escort', 52: 'play_stream', 53: 'sound_busy', 54: 'stop_sound',
|
||||
56: 'unit_relation', 59: 'fade_sound', 62: 'FORCE_END_PHASE',
|
||||
56: 'unit_relation', 59: 'fade_sound', 62: 'FORCE_END_PHASE', 64: 'request_script_message',
|
||||
69: 'unit_state', 70: 'unit_alive', 72: 'group_ratio_pct', 73: 'timer_start',
|
||||
74: 'timer_limit', 88: 'camera_at', 90: 'camera_at_route',
|
||||
93: 'clear_flag', 94: 'is_engaged', 95: 'unit_hp_pct', 100: 'push_trigger',
|
||||
93: 'clear_flag', 94: 'is_engaged', 95: 'unit_hp_pct', 100: 'reset_phase_threads',
|
||||
102: 'prompt_yes_no', 109: 'set_unit_flags', 115: 'named_event',
|
||||
120: 'wait_cmds_drained', 123: 'timer_resume', 124: 'timer_stop',
|
||||
125: 'timer_reset', 126: 'timer_elapsed', 127: 'timer_set',
|
||||
@@ -135,10 +135,30 @@ def symbols(b, which):
|
||||
return out
|
||||
|
||||
|
||||
UNIT_ARG = {18, 20, 24, 26, 56, 69, 70, 94, 95, 105, 109} # unit idx at blob[4]
|
||||
# Built-ins 71 and 72 carry a SECOND unit index at blob[12] (handler 0x8226E568
|
||||
# indexes [phase+324] with both), so their second operand printed as a bare
|
||||
# integer until this was noticed. 56 may be the same shape -- unverified.
|
||||
# Built-ins whose operand blob carries a symbol-table-2 (unit) index, by slot.
|
||||
#
|
||||
# Derived from the DATA, not from reading 147 handlers: across all 28 stages a
|
||||
# slot qualifies only if every observed value is a valid symtab-2 index, it takes
|
||||
# >=15 distinct values, and its maximum reaches most of the table (symtab-2 tops
|
||||
# out at 122 entries, so a non-index slot overruns). That last clause is what
|
||||
# makes the test discriminating -- plain range-checking cannot separate an index
|
||||
# from a bool, because every small integer is "in range".
|
||||
#
|
||||
# It also refutes one tempting entry: `set_flag`'s slot 0 passes the range and
|
||||
# spread tests but its maximum EXCEEDS the table (flag indices run 0..31 against
|
||||
# tables as small as 40), so it is excluded. Slots are only listed here when the
|
||||
# ratio stayed below 1.0.
|
||||
UNIT_ARG = {2, 3, 7, 12, 15, 16, 18, 19, 20, 24, 25, 26, 28, 29, 30, 47, 48,
|
||||
56, 57, 58, 63, 69, 70, 79, 91, 92, 95, 105, 108, 128, 143}
|
||||
UNIT_ARG2 = {2, 18, 47, 48, 56, 79, 95, 128} # a SECOND unit index at blob[12]
|
||||
UNIT_ARG3 = {128} # and a third at blob[20]
|
||||
UNIT_SLOTS = {4: UNIT_ARG, 12: UNIT_ARG2, 20: UNIT_ARG3}
|
||||
|
||||
# Symbol table 2 holds TWO entity types: type 2 (1160 entries disc-wide) and
|
||||
# type 8 (249). They are not interchangeable -- built-ins 95 and 128 take a
|
||||
# type-2 unit at slot 4 and, at slot 12, an operand that is type 8 in 100% of
|
||||
# its 90 and 152 call sites respectively. What distinguishes the two classes is
|
||||
# not yet established.
|
||||
|
||||
|
||||
def dis(b, off, count=40, code_base=0x24, args=True, sym2=None):
|
||||
@@ -203,7 +223,11 @@ def dis(b, off, count=40, code_base=0x24, args=True, sym2=None):
|
||||
parts = []
|
||||
for slot, v in sorted(staged.items()):
|
||||
txt = ('0x%X' % v) if isinstance(v, int) else v
|
||||
if (sym2 and slot == 4 and words[0] in UNIT_ARG
|
||||
# Resolve only a slot that is declared a unit index AND
|
||||
# whose value really is one -- a resolver that invents a
|
||||
# name for a non-index is worse than one that prints the
|
||||
# raw number.
|
||||
if (sym2 and words[0] in UNIT_SLOTS.get(slot, ())
|
||||
and isinstance(v, int) and v in sym2):
|
||||
txt = sym2[v][1]
|
||||
parts.append(txt)
|
||||
|
||||
72
tools/re-capture/isl_report.py
Normal file
72
tools/re-capture/isl_report.py
Normal file
@@ -0,0 +1,72 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Regenerate the committed ISL artefacts under `docs/re/data/`.
|
||||
|
||||
isl_report.py <StageNN.ssb> calls -> the call-site census + a listing
|
||||
|
||||
The artefact was produced by an uncommitted one-off, so it drifted out of date
|
||||
twice: once when operand staging was fixed (calls printed with too few
|
||||
arguments) and once when three built-in names were corrected. Keeping the
|
||||
generator in the tree is the point of this file.
|
||||
|
||||
`data/isl-stage02-conditions.txt` still has no generator here. A first attempt
|
||||
is not committed because it printed most sites as bare `builtinN`: neither
|
||||
`isl.resync` (it gives up far from a valid start) nor a naive linear decode from
|
||||
the phase base reaches every call site, so producing that listing faithfully
|
||||
needs the coroutine entry points, which `start_coroutine`'s operand carries and
|
||||
this tool does not yet follow.
|
||||
"""
|
||||
import collections
|
||||
import sys
|
||||
|
||||
import isl
|
||||
|
||||
def census(b):
|
||||
cs = isl.call_sites(b)
|
||||
return cs, collections.Counter(bid for _, bid, _ in cs)
|
||||
|
||||
|
||||
def emit_calls(b, path):
|
||||
cs, h = census(b)
|
||||
s2 = isl.symbols(b, 2)
|
||||
print('# %s — ISL disassembly artefacts' % path)
|
||||
print()
|
||||
print('Generated by `tools/re-capture/isl_report.py calls`.')
|
||||
print()
|
||||
print('%d call sites, %d distinct built-ins' % (len(cs), len(h)))
|
||||
for bid, n in h.most_common():
|
||||
print(' builtin %-4d %5d site(s)' % (bid, n))
|
||||
print()
|
||||
print('## phase-control sites')
|
||||
for bid, nm in ((6, 'END_PHASE'), (62, 'FORCE_END_PHASE'),
|
||||
(39, 'MARK_LAST_PHASE'), (40, 'mark_not_last')):
|
||||
offs = [off for off, b2, _ in cs if b2 == bid]
|
||||
print('%-3d %-18s %2d: %s'
|
||||
% (bid, nm, len(offs), ' '.join('0x%x' % o for o in offs)))
|
||||
print()
|
||||
print('## named built-ins used, by traffic')
|
||||
for bid, n in h.most_common():
|
||||
nm = isl.BUILTIN.get(bid)
|
||||
if nm:
|
||||
print(' %-3d %-24s %4d' % (bid, nm, n))
|
||||
print()
|
||||
print('## phase code bases')
|
||||
print('Each phase has its OWN base; the file header offset is not it.')
|
||||
print(' ' + ' '.join('0x%x' % x for x in isl.phase_bases(b)))
|
||||
print()
|
||||
print('## disassembly into the first END_PHASE')
|
||||
target = [off for off, bid, _ in cs if bid == 6][0]
|
||||
start = isl.resync(b, target)
|
||||
print('resync from 0x%X' % start)
|
||||
for line in isl.dis(b, start, 64, code_base=0x24, sym2=s2):
|
||||
print(line)
|
||||
|
||||
|
||||
def main():
|
||||
path = sys.argv[1]
|
||||
b = isl.load(path)
|
||||
name = path.replace('\\', '/').split('/')[-1]
|
||||
{'calls': emit_calls}[sys.argv[2]](b, name)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user