Decodes the table found at the end of every phase region. Layout:
int N
N x [ int offset ; float t ; int kind ] -- 8-byte typed records,
tag 0x19 int, 0x1A float
1 + 3N matches the record count in every phase measured (Stage 02: 76/40/55
records for N = 25/13/18).
Checks, all independent of each other:
schedule entries disc-wide 675
0x1A float records disc-wide 675 (counted by a different route)
offsets landing on the instruction stream 675/675 = 100.0%
control, random 4-aligned offsets 33.3%
The floats are seconds -- 0, 0.5, 1, 4, 5, 30, 50, 60, 90, 120, 150, 170, 180, 210,
240, 270, 300, 330, 360, 420, 570, 1020, 1080, 1140, 1170 -- and the targets are small
one-shot coroutines that set arguments, call one built-in and end_coroutine. kind is
0 (556) or 5 (119) and is not identified.
Runtime cross-check, recorded as consistency rather than confirmation: the closed
REMAINING OB work measured Stage 02's squadron arrivals at t = 0, 120 and 210 s over
n=5 emulator runs, and all three appear in phase 1's static schedule, with 120 and 210
each appearing TWICE. These are round numbers and phase 1 has ~22 distinct times over
0-1170, so presence alone is not unlikely; the doubling is the sharper detail and was
not predicted in advance.
New artefacts data/isl-stage02-schedule.txt and data/isl-schedule-all.txt with a
committed generator (isl_report.py schedule). calls, phase-ends, conditions and
phase-guards all regenerate byte-identical.
Not settled and said so: kind is unread; the consumer is unread, so the decode rests
on the structural checks above; whether the clock is per-phase or per-mission is an
inference from the layout; and this is NOT what starts the unreachable code -- 0 of
the 675 targets are unreached run-starts, so that ~15% gap stands.
221 lines
9.5 KiB
Python
221 lines
9.5 KiB
Python
#!/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.
|
|
|
|
isl_report.py <StageNN.ssb> phase-ends -> every END_PHASE with its context
|
|
isl_report.py <StageNN.ssb> conditions -> every condition site, comparand resolved
|
|
isl_report.py <StageNN.ssb> phase-guards -> the NECESSARY conditions for each exit
|
|
isl_report.py <dir> phase-guards -> the same for every Stage*.ssb it holds
|
|
isl_report.py <StageNN.ssb> schedule -> the phase timeline tables
|
|
|
|
The "needs the coroutine entry points" blocker recorded here is REFUTED: the
|
|
instruction stream is FLAT and `isl.linear_offsets` reaches 25705/25705 call
|
|
sites across all 28 stages. What broke the naive linear decode was `isl.dis`
|
|
stopping at op 20 (`ret`), which in a coroutine VM is a yield, not an end of
|
|
code -- see `docs/re/isl-stream-is-flat.md`.
|
|
"""
|
|
import collections
|
|
import os
|
|
import sys
|
|
|
|
import isl
|
|
import isl_cfg
|
|
|
|
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)
|
|
s1, s2 = isl.symbols(b, 1), 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, sym1=s1):
|
|
print(line)
|
|
|
|
|
|
def emit_phase_ends(b, path):
|
|
"""Every phase-ending call with the instructions that lead into it."""
|
|
offs = isl.linear_offsets(b)
|
|
idx = {o: k for k, o in enumerate(offs)}
|
|
bases = isl.phase_bases(b)
|
|
s1, s2 = isl.symbols(b, 1), isl.symbols(b, 2)
|
|
ends = [(o, bid) for o, bid, _ in isl.call_sites(b) if bid in (6, 62)]
|
|
print('# %s — where each phase ends' % path)
|
|
print()
|
|
print('Generated by `tools/re-capture/isl_report.py phase-ends`.')
|
|
print('Decoded with `stop_at_ret=False`; a `ret` is a coroutine YIELD, so')
|
|
print('the listing continues past it.')
|
|
print()
|
|
print('phase code bases: ' + ' '.join('0x%x' % x for x in bases))
|
|
print('%d phase-ending call(s): %d END_PHASE, %d FORCE_END_PHASE'
|
|
% (len(ends), sum(1 for _, b2 in ends if b2 == 6),
|
|
sum(1 for _, b2 in ends if b2 == 62)))
|
|
for o, bid in ends:
|
|
ph = sum(1 for x in bases if x <= o)
|
|
k = idx.get(o)
|
|
if k is None:
|
|
print('\n## 0x%X builtin %d — NOT on the linear stream' % (o, bid))
|
|
continue
|
|
print('\n## phase %d — builtin %d (%s) at 0x%X'
|
|
% (ph, bid, isl.BUILTIN.get(bid, '?'), o))
|
|
for line in isl.dis(b, offs[max(0, k - 16)], 22,
|
|
code_base=bases[ph - 1], sym2=s2, sym1=s1,
|
|
stop_at_ret=False):
|
|
print(' ' + line)
|
|
|
|
|
|
def emit_conditions(b, path):
|
|
"""Every condition site with its comparand — what `data/isl-stage02-conditions.txt`
|
|
never had a generator for."""
|
|
import collections
|
|
cs = isl_cfg.conditions(b, isl.symbols(b, 1), isl.symbols(b, 2))
|
|
bases = isl.phase_bases(b)
|
|
print('# %s — condition sites, comparands resolved' % path)
|
|
print()
|
|
print('Generated by `tools/re-capture/isl_report.py conditions`.')
|
|
print()
|
|
print('The deque ops are an expression stack: `push.i` saves the comparand,')
|
|
print('the right-hand side is evaluated (its result lands in `special[0]`),')
|
|
print('`pop.i` restores the comparand into `special[1]`, then `cmp.i` compares.')
|
|
print()
|
|
print('Operands come from a CFG dataflow fixpoint (`isl_cfg.py`), joining each')
|
|
print('block over its ACTUAL predecessors — not from a linear walk.')
|
|
print()
|
|
print('%d condition sites; phase bases %s'
|
|
% (len(cs), ' '.join('0x%x' % x for x in bases)))
|
|
h = collections.Counter(c['lhs'].split('(')[0] for c in cs if c['lhs'] and '(' in c['lhs'])
|
|
print()
|
|
print('## most-tested predicates')
|
|
for k, n in h.most_common(20):
|
|
print(' %-28s %4d' % (k, n))
|
|
for ph in range(1, len(bases) + 1):
|
|
rows = [c for c in cs if c['phase'] == ph]
|
|
print()
|
|
print('## phase %d — %d sites' % (ph, len(rows)))
|
|
for c in rows:
|
|
lhs = c['lhs'] if c['lhs'] is not None else '<unknown: no static entry>'
|
|
rhs = c['rhs'] if c['rhs'] is not None else '<unknown>'
|
|
print(' 0x%06X if %s %s %s -> 0x%X'
|
|
% (c['off'], lhs, c['rel'], rhs, c['target']))
|
|
|
|
|
|
def emit_phase_guards(b, path):
|
|
"""The conditions that DOMINATE each phase exit — the per-phase clear condition."""
|
|
rows, reached, total = isl_cfg.dominating_conditions(b)
|
|
print('# %s — what each phase exit requires' % path)
|
|
print()
|
|
print('Generated by `tools/re-capture/isl_report.py phase-guards`.')
|
|
print()
|
|
print('A condition is listed when it DOMINATES the exit: every path from an')
|
|
print('entry to that `END_PHASE` passes through it, so it is NECESSARY for the')
|
|
print('phase to end that way. Reachability alone is the wrong query here —')
|
|
print('in a poll loop both successors reach the exit.')
|
|
print()
|
|
print('CFG reached %d of %d instructions (%.1f%%).' % (reached, total, 100.0 * reached / total))
|
|
print()
|
|
print('`precond` = necessary: the exit cannot happen unless it holds.')
|
|
print('`TRIGGER` = also SUFFICIENT: once it holds the exit is unavoidable.')
|
|
print('An exit with no TRIGGER is not fired by a branch — a timeout exit is')
|
|
print('fired by time passing, which is not a property of the graph.')
|
|
for r in rows:
|
|
nm = isl.BUILTIN.get(r['builtin'], 'builtin%d' % r['builtin'])
|
|
if r['conds'] is None:
|
|
print()
|
|
print('## phase %d — %s at 0x%06X: UNREACHABLE from any static entry'
|
|
% (r['phase'], nm, r['end']))
|
|
print(' (started from the trigger queue at `phase+272`, by data not code)')
|
|
continue
|
|
print()
|
|
print('## phase %d — %s at 0x%06X: %d necessary condition(s)'
|
|
% (r['phase'], nm, r['end'], len(r['conds'])))
|
|
for c in r['conds']:
|
|
lhs = c['lhs'] if c['lhs'] is not None else '<unknown>'
|
|
rhs = c['rhs'] if c['rhs'] is not None else '<unknown>'
|
|
tag = 'TRIGGER ' if c.get('sufficient') else 'precond '
|
|
print(' %s0x%06X %s %s %s' % (tag, c['off'], lhs, c['rel'], rhs))
|
|
|
|
|
|
def emit_schedule(b, path):
|
|
"""The per-phase timeline: what the script launches, and when."""
|
|
sch = isl.schedule(b)
|
|
print('# %s — phase timelines' % path)
|
|
print()
|
|
print('Generated by `tools/re-capture/isl_report.py schedule`.')
|
|
print()
|
|
print('Each phase ends with a table of `(routine offset, time in seconds, kind)`.')
|
|
print('%d entries.' % len(sch))
|
|
for ph in sorted({p for p, _o, _t, _k in sch}):
|
|
rows = [r for r in sch if r[0] == ph]
|
|
print()
|
|
print('## phase %d — %d scheduled routines' % (ph, len(rows)))
|
|
for _p, off, t, k in sorted(rows, key=lambda r: (r[2], r[1])):
|
|
print(' t=%-8g kind=%d -> 0x%06X' % (t, k, off))
|
|
|
|
|
|
def main():
|
|
# The dominator pass over a large stage is memory-hungry and once OOM-KILLED
|
|
# a run on this 15 GB box. Cap the address space so a bad input fails this
|
|
# process instead of the machine.
|
|
try:
|
|
import resource
|
|
resource.setrlimit(resource.RLIMIT_AS, (10 * 1024 ** 3,) * 2)
|
|
except Exception:
|
|
pass
|
|
path = sys.argv[1]
|
|
if os.path.isdir(path):
|
|
import glob
|
|
files = sorted(glob.glob(os.path.join(path, 'Stage*.ssb')))
|
|
for i, f in enumerate(files):
|
|
if i: print(); print('-' * 72); print()
|
|
{'calls': emit_calls, 'phase-ends': emit_phase_ends,
|
|
'conditions': emit_conditions,
|
|
'phase-guards': emit_phase_guards,
|
|
'schedule': emit_schedule}[sys.argv[2]](
|
|
isl.load(f), os.path.basename(f))
|
|
return
|
|
b = isl.load(path)
|
|
name = path.replace('\\', '/').split('/')[-1]
|
|
{'calls': emit_calls, 'phase-ends': emit_phase_ends,
|
|
'conditions': emit_conditions,
|
|
'phase-guards': emit_phase_guards,
|
|
'schedule': emit_schedule}[sys.argv[2]](b, name)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|