re: the 0x1883 record carries entry points -- every phase exit is now reachable
data/isl-phase-guards-all.txt goes from 5 of 177 unreachable exits to 0.
The cheap first step failed, usefully. An unreached routine's entry offset does NOT
appear as a word anywhere in the file, in any encoding: phase-relative 6.6% against an
11.5% control on reached offsets, absolute 1.6% vs 3.3%, and the /4 forms 0-1.6% vs
6.6-8.2%. Every variant is at or below its control, which rules out the whole family
of "some instruction operand points at them". It also rules out dead code: Stage 02's
3069 unreached instructions contain 485 calls, including start_coroutine x75,
squadron_attack x59, set_group_speed x42 and objective_marker x13.
The answer is the mission-level stream that isl-bytecode.md already partly read. Each
0x1883 record is
0x1883, base_delta, size, 0, entry_a, entry_b ; entries PHASE-RELATIVE
Measured over all 28 stages, 82 of those 88 values land on a valid instruction --
93.2%, against a 38.6% chance rate for a random 4-aligned offset. In Stage 02
entry_b is the phase's force-end handler: 0x1482C, 0x249F0 and 0x34A10, two of which
were exactly the unreachable exits, and the third being already reachable is the
consistency check.
Seeding them: exits unreachable 5 -> 0. Those exits now report 0 necessary
conditions, which is what an engine-entered abort handler should look like.
Recorded because it is the same mistake twice: the first seeding attempt moved NOTHING
(reach 85.0% -> 85.0%, exits 5 -> 5). dominating_conditions() builds its own entry set
and did not use the one I had patched -- fix-the-instance-not-the-class again, caught
only because an unchanged count is by now a standing signal.
Not settled and stated: reach went only 85.0% -> 85.2%, so what starts the other ~15%
of code is still unknown, and the negative above says it is not an operand in the file;
entry_a is unidentified; 6 of the 88 values do not land on an instruction.
This commit is contained in:
@@ -132,7 +132,8 @@ def dominating_conditions(b):
|
||||
import collections as _c
|
||||
E, nxt = edges(b, spawn=True)
|
||||
offs = isl.linear_offsets(b)
|
||||
entries = {e for e in set(isl.phase_bases(b)) | set(coroutine_entries(b)) if e in nxt}
|
||||
entries = {e for e in set(isl.phase_bases(b)) | set(coroutine_entries(b))
|
||||
| set(stream_entries(b)) if e in nxt}
|
||||
preds = _c.defaultdict(list)
|
||||
for a, ss in E.items():
|
||||
for s in ss: preds[s].append(a)
|
||||
@@ -174,6 +175,31 @@ def dominating_conditions(b):
|
||||
return out, len(R), len(offs)
|
||||
|
||||
|
||||
def stream_entries(b):
|
||||
"""The pair of entry points each mission-level `0x1883` record carries.
|
||||
|
||||
Layout, per phase: `0x1883, base_delta, size, 0, entry_a, entry_b`
|
||||
with both entries PHASE-RELATIVE. Measured over all 28 stages: 82 of 88
|
||||
land on a valid instruction (93.2 %) against a 38.6 % chance rate for a
|
||||
random 4-aligned offset.
|
||||
|
||||
These are why some routines have no static predecessor at all: nothing in
|
||||
the bytecode names them. In Stage 02 the SECOND entry of phases 1 and 3 is
|
||||
exactly the `FORCE_END_PHASE` site that the CFG could not otherwise reach.
|
||||
"""
|
||||
code = struct.unpack_from('>I', b, 0x08)[0]
|
||||
end = struct.unpack_from('>I', b, 0x0C)[0]
|
||||
out = []
|
||||
for o in range(0x24, 0x400, 4):
|
||||
if o + 24 > len(b): break
|
||||
if struct.unpack_from('>I', b, o)[0] == 0x1883:
|
||||
base = code + struct.unpack_from('>I', b, o + 4)[0]
|
||||
for k in (16, 20):
|
||||
t = base + struct.unpack_from('>I', b, o + k)[0]
|
||||
if 0 <= t < end: out.append(t)
|
||||
return out
|
||||
|
||||
|
||||
def coroutine_entries(b):
|
||||
"""Every `start_coroutine` target, found by a linear pre-pass."""
|
||||
offs = isl.linear_offsets(b)
|
||||
@@ -212,7 +238,7 @@ def conditions(b, s1=None, s2=None):
|
||||
# have no static predecessor. Seed every `start_coroutine` target as well --
|
||||
# a linear pre-pass finds them because the target is staged into local[0]
|
||||
# immediately before the call, which no control-flow boundary intervenes in.
|
||||
entries = list(bases) + coroutine_entries(b)
|
||||
entries = list(bases) + coroutine_entries(b) + stream_entries(b)
|
||||
for e in entries:
|
||||
if e in nxt or e == offs[-1]:
|
||||
IN[e] = EMPTY; work.append(e)
|
||||
|
||||
Reference in New Issue
Block a user