re: resolve every ISL condition's comparand -- the clear conditions are readable
The deque ops are an EXPRESSION STACK: push the left operand, evaluate the right (a built-in call, whose result lands in special[0]), pop the comparand back into special[1], compare. Tracking that through the linear decode is enough to recover what each site tests. Evidence the model is right, not just plausible: push vs pop across all 28 stages 1877 vs 1877 files that underflow or end unbalanced 0 of 28 Stage 02 pop.i sites followed by cmp.i 319 / 319 ops immediately before a pop.i call x313, cmp.a x6 isl.conditions() recovers 7563 condition sites disc-wide with 0.0% left as an unresolved special[N]; 83.2% have a built-in call as the LHS and 99.7% compare against a plain number. Most-tested: hp_pct_test 1955, unit_state 1257, unit_relation 796, dist_lt 450, unit_alive 413. They read as conditions now: if unit_alive(TCN105) != 1 if hp_pct_test(ADT308, 0) != 1 if dist_lt(ADT308, TCN000, 15000) != 1 (world unit = 1 m, so 15 km) if unit_state(ADT308) == 1 data/isl-stage02-conditions.txt was a stale artefact with NO generator -- the thing isl_report.py's docstring complained about. It has one now (isl_report.py conditions). The calls and phase-ends artefacts both regenerate byte-identical, so the change is additive. Recorded rather than glossed: 15 of Stage 02's 965 sites (1.6%) attribute the LHS to end_coroutine, which returns no value -- the tracker sets special[0] on EVERY call, so those show a stale value and are wrong, not imprecise. The fix is to set it only for built-ins that write [phase+164], which the vtable work makes checkable.
This commit is contained in:
@@ -354,6 +354,82 @@ def dis(b, off, count=40, code_base=0x24, args=True, sym2=None, sym1=None,
|
||||
return out
|
||||
|
||||
|
||||
REL = {13: '==', 14: '!=', 15: '<', 16: '<=', 17: '>', 18: '>='}
|
||||
|
||||
|
||||
def conditions(b, sym1=None, sym2=None):
|
||||
"""Every condition site, with its comparand resolved.
|
||||
|
||||
The deque ops are an EXPRESSION STACK, which is what makes this possible:
|
||||
`push.i` saves `special[1]`, the right-hand side is evaluated (usually a
|
||||
built-in call, whose result lands in `special[0]`), `pop.i` restores the
|
||||
saved comparand into `special[1]`, and `cmp.i` compares the two.
|
||||
|
||||
MEASURED, and this is why the model is trusted: across all 28 stages
|
||||
push and pop balance at 1877 each with ZERO underflows, and in Stage 02
|
||||
all 319 `pop.i` sites are immediately followed by `cmp.i`.
|
||||
"""
|
||||
bases = phase_bases(b)
|
||||
sp, loc, stack = {}, {}, []
|
||||
out, pend = [], None
|
||||
for off in linear_offsets(b):
|
||||
w = struct.unpack_from('>I', b, off)[0]
|
||||
op, ln = w & 0xFF, (w >> 8) & 0xFF
|
||||
src_kind, dst_kind = (w >> 24) & 0xFF, (w >> 16) & 0xFF
|
||||
words = [struct.unpack_from('>I', b, off + i)[0]
|
||||
for i in range(4, max(ln, 4), 4) if off + i + 4 <= len(b)]
|
||||
|
||||
def val(kind, operand, lo=0):
|
||||
if kind == 1:
|
||||
if op == 1:
|
||||
return '%.6g' % struct.unpack('>d',
|
||||
struct.pack('>II', operand, lo))[0]
|
||||
return str(operand)
|
||||
if kind == 2:
|
||||
return sp.get(operand, 'special[%d]' % operand)
|
||||
if kind == 3:
|
||||
return loc.get(operand, 'local[%d]' % operand)
|
||||
return 'global[%d]' % operand
|
||||
|
||||
if op in (0, 1) and len(words) >= 2:
|
||||
v = val(src_kind, words[1], words[2] if len(words) > 2 else 0)
|
||||
(sp if dst_kind == 2 else loc)[words[0]] = v
|
||||
elif op == 19 and words:
|
||||
nm = BUILTIN.get(words[0], 'builtin%d' % words[0])
|
||||
args = []
|
||||
tags = {sl - 4 for sl, ids in UNIT_SLOTS.items()
|
||||
if words[0] in ids and sl in loc}
|
||||
for sl in sorted(loc):
|
||||
v = loc[sl]
|
||||
if sl in tags and v == '1':
|
||||
continue
|
||||
if v.isdigit():
|
||||
i = int(v)
|
||||
if sym2 and words[0] in UNIT_SLOTS.get(sl, ()) and i in sym2:
|
||||
v = sym2[i][1]
|
||||
elif sym1 and words[0] in SYM1_SLOTS.get(sl, ()) and i in sym1:
|
||||
v = sym1[i][1]
|
||||
args.append(v)
|
||||
sp[0] = '%s(%s)' % (nm, ', '.join(args))
|
||||
loc = {}
|
||||
elif op == 21:
|
||||
stack.append(sp.get(1))
|
||||
elif op == 22:
|
||||
stack.append(sp.get(1))
|
||||
elif op in (23, 24):
|
||||
sp[1] = stack.pop() if stack else None
|
||||
elif op in (10, 11):
|
||||
pend = (off, val(dst_kind, words[0]), val(src_kind, words[1]))
|
||||
elif op in REL and pend:
|
||||
ph = sum(1 for x in bases if x <= off)
|
||||
tgt = bases[ph - 1] + words[0] if words else None
|
||||
out.append({'off': pend[0], 'phase': ph, 'lhs': pend[1],
|
||||
'rel': REL[op], 'rhs': pend[2], 'branch': off,
|
||||
'target': tgt})
|
||||
pend = None
|
||||
return out
|
||||
|
||||
|
||||
def call_sites(b):
|
||||
"""Every `call` in the code region. Scans on the encoding, not by decoding,
|
||||
so a bad length somewhere cannot hide the rest of the file."""
|
||||
|
||||
@@ -9,6 +9,7 @@ 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
|
||||
|
||||
The "needs the coroutine entry points" blocker recorded here is REFUTED: the
|
||||
instruction stream is FLAT and `isl.linear_offsets` reaches 25705/25705 call
|
||||
@@ -93,11 +94,42 @@ def emit_phase_ends(b, path):
|
||||
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.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('%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 '(' 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:
|
||||
print(' 0x%06X if %s %s %s -> 0x%X'
|
||||
% (c['off'], c['lhs'], c['rel'], c['rhs'], c['target']))
|
||||
|
||||
|
||||
def main():
|
||||
path = sys.argv[1]
|
||||
b = isl.load(path)
|
||||
name = path.replace('\\', '/').split('/')[-1]
|
||||
{'calls': emit_calls, 'phase-ends': emit_phase_ends}[sys.argv[2]](b, name)
|
||||
{'calls': emit_calls, 'phase-ends': emit_phase_ends,
|
||||
'conditions': emit_conditions}[sys.argv[2]](b, name)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
Reference in New Issue
Block a user