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:
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