phase-36: S105 harvest f7 — R22 tries every same-base sibling (not only the literal base) and no longer reads a cast store as a set; known-true 37 → 0 on func_8017FD14; selftest OK

This commit is contained in:
Drew T
2026-09-11 14:07:56 -06:00
parent d62cb1170c
commit bb2012f78d
2 changed files with 70 additions and 46 deletions
+1 -1
View File
@@ -1841,7 +1841,7 @@ CLAIM, not a fact — two were refuted on bytes on 2026-09-10.**
R28 …` feeds R28 the real TU like R27. **Then (S104, from d22/d24/d25):** **R23 accepts a `case K:` / `default:` label as a
statement boundary** (it had refused d22's `t` after `case 2:`; known-true `split t into 2` → 0); **R35 `drop_param_copies`**
(`T x = argN;` never reassigned → `argN` used; d24's start 36 → 23 — its close was joint); **R36 `merge_set_chains`** (`x = A;
[≤2 unrelated lines] x += B;` → one assignment; d25's frame-only residual; known-true 0). **R31 widened** (d39: an all-shifts `(s16)` candidate — its two sites closed only together; known-true 0), **R35 widened** (d38: a CAST copy `T *p = (T *)a1;` becomes `((T *)a1)` at each use; known-true 0), `named_definitions` indexes `func_X_body(` asm-label definitions (two bank lists failed to resolve them); **R38 `shift_to_division`** (e7: `if (v < 0) v += 2^k-1; v = v >> k;` — gcc's own expansion — written `v = v / 2^k;` or merged into the preceding `v = (E) / 2^k;`; on e7's start text alone 6 → 6/7: e7's close was joint with a width move, so R38 is a composition move; 7 residue bodies carry the shape), **R39 `duplicate_join_statement`** (e12/e14: the simple statement after an if/else join copied into both arms to split an integer-truncated `allocno_compare` tie — cross-jump re-merges the copies; known-true 0 on e14's func_8017BEBC and func_8017CAD4; needs the `} else {` line shape — e12's func_8017E35C produced no candidate; widened S104: the first 1–3 simple statements after the join, blank lines skipped — e24's func_80180E24 store pair closes at ×2), **R40 `return_preincrement`** (e2/e16: `return i + 1;` → `return ++i;`, zero bytes, more refs; known-true 0 on both), **R41 `swap_if_else_arms`** (e16: `if (C) {A} else {B}` → `if (!C) {B} else {A}`, one site at a time — the arm order decides reorg's delay-slot steal; known-true 0), **R42 `move_statement_far`** (e19: one simple statement moved 2–6 simple statements down within its block — R9 only swaps neighbours; on e19's func_8018230C start text alone best 8: its close also inlined a temp and dropped an inner block, so R42 is a composition move), **R43 `sign_test_to_mask`** (e24/e26: `if (E < 0)` whose arms set/clear bit 31 → `if ((u32)(E) & 0x80000000)`, plus the variant with never-used pad arrays dropped; alone 9 / 4 on the two start texts — both closes also deleted temps: a composition move), **R37 `return_constants`** (d27: a result local `r = 0; if (A) r = (B); return r;` → `if (A && B) return 1; return 0;` or the nested form — jump1's store-flag on the hard `$v0`; known-true 0 both spellings). **(S105) R44 `counter_derived_pointer`** (e21/f2: a pointer initialised before a counted loop and stepped `p += K` inside it — its own biv, never eliminated while a bare `p[0]` is read (`loop.c:4196`, `:6022`) — re-derived as `p = (T *)(BASE) + i * K;` at the loop top for each up-counter `i` of that block (`(i - C)` when it starts at C; a second `&SYM[c + i * K]` spelling for an uncast symbol base); refuses a down-counter, a second assignment, `&p`; known-true: alone 0 on e21's func_80037EA0 start text (from 33) and f2's measured 18 on func_80038838's loop 2; composed with R22 it reaches f2's 0). **R22 extended + a blind spot fixed (S105):** two pointers derived from ONE base expression at two offsets (`a1 = (u8 *)arg0 + 0x1B` beside `a3 = … + 0x1A`) merge like `q = p + K` (f2's loop 1, alone 3); and R22/R44's `&p` refusal had matched the `&&` operator (`… != 0 && p[0]`) since S103 — every body testing its pointer with `&&` was silently never offered R22 (fixed to `(?<!&)&(?!&)`; the S105 regen re-runs R22). **(S105 f5) R45 `derived_pointer_store`** (`s0[K] = E;` with `s0 = &SYM` known to cse → `lui $at; sw E,K($at)` because cse pass 1 folds `(plus s0 K)` to the constant first, `find_best_addr` `cse.c:2622-2740`/`:2653`; the pointer the function already passes to a call, `&s0[K]`, is declared, born BEFORE the store, stored through and passed — per slot and ALL; known-true: alone 0 on f5's func_8018F944 (from 4), ALL 0 on func_8018FEA0 (from 16)) and **R46 `set_once_chain`** (1–3 consecutive `v OP= E;` lines followed by `DST = v;` → `DST = ((v OP1 E1) OP2 E2);` — a multi-set temp loses sched1's birthing boost `sched.c:2468-2546` and its chain is emitted at the block top; refused when `v` is read after the store before its next plain assignment; per chain and ALL; known-true: ALL 0 on func_8018FA34 (from 6), the singles 2 / 4 = the agent's numbers; the INVERSE of METHOD step 18's multi-set trick — the `.sched` priority column decides the direction).
[≤2 unrelated lines] x += B;` → one assignment; d25's frame-only residual; known-true 0). **R31 widened** (d39: an all-shifts `(s16)` candidate — its two sites closed only together; known-true 0), **R35 widened** (d38: a CAST copy `T *p = (T *)a1;` becomes `((T *)a1)` at each use; known-true 0), `named_definitions` indexes `func_X_body(` asm-label definitions (two bank lists failed to resolve them); **R38 `shift_to_division`** (e7: `if (v < 0) v += 2^k-1; v = v >> k;` — gcc's own expansion — written `v = v / 2^k;` or merged into the preceding `v = (E) / 2^k;`; on e7's start text alone 6 → 6/7: e7's close was joint with a width move, so R38 is a composition move; 7 residue bodies carry the shape), **R39 `duplicate_join_statement`** (e12/e14: the simple statement after an if/else join copied into both arms to split an integer-truncated `allocno_compare` tie — cross-jump re-merges the copies; known-true 0 on e14's func_8017BEBC and func_8017CAD4; needs the `} else {` line shape — e12's func_8017E35C produced no candidate; widened S104: the first 1–3 simple statements after the join, blank lines skipped — e24's func_80180E24 store pair closes at ×2), **R40 `return_preincrement`** (e2/e16: `return i + 1;` → `return ++i;`, zero bytes, more refs; known-true 0 on both), **R41 `swap_if_else_arms`** (e16: `if (C) {A} else {B}` → `if (!C) {B} else {A}`, one site at a time — the arm order decides reorg's delay-slot steal; known-true 0), **R42 `move_statement_far`** (e19: one simple statement moved 2–6 simple statements down within its block — R9 only swaps neighbours; on e19's func_8018230C start text alone best 8: its close also inlined a temp and dropped an inner block, so R42 is a composition move), **R43 `sign_test_to_mask`** (e24/e26: `if (E < 0)` whose arms set/clear bit 31 → `if ((u32)(E) & 0x80000000)`, plus the variant with never-used pad arrays dropped; alone 9 / 4 on the two start texts — both closes also deleted temps: a composition move), **R37 `return_constants`** (d27: a result local `r = 0; if (A) r = (B); return r;` → `if (A && B) return 1; return 0;` or the nested form — jump1's store-flag on the hard `$v0`; known-true 0 both spellings). **(S105) R44 `counter_derived_pointer`** (e21/f2: a pointer initialised before a counted loop and stepped `p += K` inside it — its own biv, never eliminated while a bare `p[0]` is read (`loop.c:4196`, `:6022`) — re-derived as `p = (T *)(BASE) + i * K;` at the loop top for each up-counter `i` of that block (`(i - C)` when it starts at C; a second `&SYM[c + i * K]` spelling for an uncast symbol base); refuses a down-counter, a second assignment, `&p`; known-true: alone 0 on e21's func_80037EA0 start text (from 33) and f2's measured 18 on func_80038838's loop 2; composed with R22 it reaches f2's 0). **R22 extended + a blind spot fixed (S105):** two pointers derived from ONE base expression at two offsets (`a1 = (u8 *)arg0 + 0x1B` beside `a3 = … + 0x1A`) merge like `q = p + K` (f2's loop 1, alone 3); and R22/R44's `&p` refusal had matched the `&&` operator (`… != 0 && p[0]`) since S103 — every body testing its pointer with `&&` was silently never offered R22 (fixed to `(?<!&)&(?!&)`; the S105 regen re-runs R22). **(S105 f5) R45 `derived_pointer_store`** (`s0[K] = E;` with `s0 = &SYM` known to cse → `lui $at; sw E,K($at)` because cse pass 1 folds `(plus s0 K)` to the constant first, `find_best_addr` `cse.c:2622-2740`/`:2653`; the pointer the function already passes to a call, `&s0[K]`, is declared, born BEFORE the store, stored through and passed — per slot and ALL; known-true: alone 0 on f5's func_8018F944 (from 4), ALL 0 on func_8018FEA0 (from 16)) and **R46 `set_once_chain`** (1–3 consecutive `v OP= E;` lines followed by `DST = v;` → `DST = ((v OP1 E1) OP2 E2);` — a multi-set temp loses sched1's birthing boost `sched.c:2468-2546` and its chain is emitted at the block top; refused when `v` is read after the store before its next plain assignment; per chain and ALL; known-true: ALL 0 on func_8018FA34 (from 6), the singles 2 / 4 = the agent's numbers; the INVERSE of METHOD step 18's multi-set trick — the `.sched` priority column decides the direction). **R22, two more blind spots fixed (S105 f7):** the merge tried only the LITERAL base of `q = E + c1` (`prim`, which never steps) and stopped — it now tries every pointer local initialised from the same base at another offset and takes the first that steps in lockstep (`fp = prim + 0x2E` onto `pp = prim + 0xC`); and `*(u16 *)fp = v;` was read as an ASSIGNMENT to `fp` (the `)` before the name), refusing the merge — the assigns scan of R22/R44 excludes a `)` before the name. Known-true: R22 alone reproduces f7's close of func_8017FD14 (37 → 0). Every R22 refusal before S105 is suspect: the `s105_r22b` regen re-judged the residue.
- **Generator R27 `named_ports` (`tools/delever.py`, S104; `delever_regen --families R27`)** — the SAME function already
lever-free in another binary, ported. Donors: every definition of the name in `src/` (`named_definitions()`, one `git
grep`, cached) with no `register`/`__asm__`/`!FAKE`, nearest line count first, ≤ 6 distinct texts. Symbol renaming by
+69 -45
View File
@@ -2726,7 +2726,7 @@ def merge_walked_pointers(text, tu, fn, d_):
def assigns(name):
n = re.escape(name)
a = re.compile(r"(?<![\w.>])%s\s*(?:=(?!=)|\+=|-=|\+\+|--)|(?:\+\+|--)\s*%s\b" % (n, n))
a = re.compile(r"(?<![\w.>)])%s\s*(?:=(?!=)|\+=|-=|\+\+|--)|(?:\+\+|--)\s*%s\b" % (n, n)) # `)`: a store through a cast pointer is not a set of it (S105 f7)
return [i for i in range(lo, hi) if a.search(masked[i])]
def fmt(k):
@@ -2737,60 +2737,60 @@ def merge_walked_pointers(text, tu, fn, d_):
qn = re.escape(q)
if re.search(r"(?<!&)&(?!&)\s*%s\b" % qn, "\n".join(masked[lo:hi])):
continue
init, steps = None, []
ok = True
init_line, steps, ok = None, [], True
for i in assigns(q):
st = _step_of(masked[i], q)
if st is not None:
steps.append((i, st))
continue
s = masked[i].strip()
m = (re.match(r"^%s\s*=\s*([A-Za-z_]\w*)\s*([+-])\s*%s\s*;$" % (qn, _INT), s)
or re.match(r"^%s\s*=\s*&\s*([A-Za-z_]\w*)\s*\[\s*(-?)\s*%s\s*\]\s*;$" % (qn, _INT), s)
or re.match(r"^%s\s*=\s*([A-Za-z_]\w*)\s*;$()()" % qn, s))
if init is None and (not m or m.group(1) not in ptrs or m.group(1) == q):
# S105 f2 (func_80038838): `q = (T *)arg0 + 0x1B;` beside `p = (T *)arg0 + 0x1A;` — the same BASE
# expression at two offsets is `q = p + (c1 - c2)` in all but spelling; the lockstep test still applies
m2 = re.match(r"^%s\s*=\s*(.+?)\s*(?:([+-])\s*%s)?\s*;$" % (qn, _INT), s)
if m2:
E, sign, k = " ".join(m2.group(1).split()), m2.group(2), m2.group(3)
c1 = (int(k, 0) if k else 0) * (-1 if sign == "-" else 1)
for p2, (p2i, p2t) in ptrs.items():
if p2 == q or p2t != qt:
continue
for j in assigns(p2):
m3 = re.match(r"^%s\s*=\s*(.+?)\s*(?:([+-])\s*%s)?\s*;$" % (re.escape(p2), _INT), masked[j].strip())
if m3 and " ".join(m3.group(1).split()) == E and j < i and _step_of(masked[j], p2) is None:
c2 = (int(m3.group(3), 0) if m3.group(3) else 0) * (-1 if m3.group(2) == "-" else 1)
m, p_base, K_ = True, p2, c1 - c2
break
if m is True:
break
if m is not True:
ok = False
break
init = (i, p_base, K_)
continue
if init is not None or not m:
if init_line is not None:
ok = False
break
p, sign, k = m.group(1), m.group(2), m.group(3)
init = (i, p, (int(k, 0) if k else 0) * (-1 if sign == "-" else 1))
if not ok or init is None or not steps:
init_line = i
if not ok or init_line is None or not steps:
continue
ii, p, K = init
if ptrs[p][1] != qt:
s = masked[init_line].strip()
m2 = re.match(r"^%s\s*=\s*(.+?)\s*(?:([+-])\s*%s)?\s*;$" % (qn, _INT), s)
if not m2:
continue
psteps = []
for i in assigns(p):
st = _step_of(masked[i], p)
if st is not None:
psteps.append(st)
elif i > ii:
ok = False # p re-seated after q was derived from it: not lockstep
E, sign, k = " ".join(m2.group(1).split()), m2.group(2), m2.group(3)
c1 = (int(k, 0) if k else 0) * (-1 if sign == "-" else 1)
# the bases q may merge into, in order: the pointer local E itself (`q = p + K`, `q = &p[K]`, `q = p`), then every
# pointer local initialised from the SAME base expression at another offset (S105 f2/f7: `a1 = arg0 + 0x1B` beside
# `a3 = arg0 + 0x1A`; `fp = prim + 0x2E` beside `pp = prim + 0xC` — R22 had taken `prim` as the base and stopped)
cands = []
mb = re.match(r"^&\s*([A-Za-z_]\w*)\s*\[\s*(-?)\s*(%s)\s*\]$" % _INT, E)
if mb and not k:
E, c1 = mb.group(1), int(mb.group(3), 0) * (-1 if mb.group(2) else 1)
if E in ptrs and E != q:
cands.append((E, c1, init_line))
for p2, (p2i, p2t) in ptrs.items():
if p2 == q or p2 == E:
continue
for j in assigns(p2):
m3 = re.match(r"^%s\s*=\s*(.+?)\s*(?:([+-])\s*%s)?\s*;$" % (re.escape(p2), _INT), masked[j].strip())
if m3 and " ".join(m3.group(1).split()) == E and j < init_line and _step_of(masked[j], p2) is None:
c2 = (int(m3.group(3), 0) if m3.group(3) else 0) * (-1 if m3.group(2) == "-" else 1)
cands.append((p2, c1 - c2, init_line))
break
chosen = None
for p, K, ii in cands:
if ptrs[p][1] != qt:
continue
psteps, ok2 = [], True
for i in assigns(p):
st = _step_of(masked[i], p)
if st is not None:
psteps.append(st)
elif i > ii:
ok2 = False # p re-seated after q was derived from it: not lockstep
break
if ok2 and sorted(psteps) == sorted(st for _, st in steps):
chosen = (p, K, ii)
break
if not ok or sorted(psteps) != sorted(st for _, st in steps):
if chosen is None:
continue
p, K, ii = chosen
drop = {qi, ii} | {i for i, _ in steps}
base = p if K == 0 else f"({p} + {fmt(K)})"
cand = list(lines)
@@ -3700,7 +3700,7 @@ def counter_derived_pointer(text, tu, fn, d_):
def assigns(name):
n = re.escape(name)
a = re.compile(r"(?<![\w.>])%s\s*(?:=(?!=)|\+=|-=|\+\+|--)|(?:\+\+|--)\s*%s\b" % (n, n))
a = re.compile(r"(?<![\w.>)])%s\s*(?:=(?!=)|\+=|-=|\+\+|--)|(?:\+\+|--)\s*%s\b" % (n, n)) # `)`: a store through a cast pointer is not a set of it (S105 f7)
return [i for i in range(lo, hi) if a.search(masked[i])]
def block_of(si):
@@ -5619,6 +5619,30 @@ def selftest():
fail(f"R25 must re-issue the call at the real arity: {t25!r}")
_defs.pop("func_8FFFFFF0", None)
# R22, the base choice + the cast-store blind spot (S105 f7, func_8017FD14; known-true: alone it reproduces the agent's
# close, 37 -> 0): `fp = prim + 0x2E` beside `pp = prim + 0xC` — the literal base `prim` does not step, the same-base sibling
# `pp` does; and `*(u16 *)fp = v;` had been read as an ASSIGNMENT to fp (the `)` before the name) and refused the merge.
PFIX = ("void func_80100000(void) {\n"
" u8 *pp;\n"
" u8 *prim;\n"
" u8 *fp;\n"
" s32 i;\n"
" prim = (u8 *)func_80010A08(0x9C);\n"
" pp = prim + 0xC;\n"
" fp = prim + 0x2E;\n"
" for (i = 0; i < 4; i++) {\n"
" fp[-0x1F] = 8;\n"
" *(u16 *)fp = *(u16 *)(pp + 2);\n"
" pp += 0x24;\n"
" fp += 0x24;\n"
" }\n"
"}")
p22 = dict(merge_walked_pointers(PFIX, "src/fx/p.c", "func_80100000",
next(r for r in sc.scan_text(PFIX, "src/fx/p.c", shared_defs=None) if r["form"] == "def")))
if sorted(p22) != ["merge-ptr fp into pp+34"] or "pp[3] = 8;" not in p22["merge-ptr fp into pp+34"] \
or "*(u16 *)(pp + 34) = *(u16 *)(pp + 2);" not in p22["merge-ptr fp into pp+34"]:
fail(f"R22 must merge onto the SAME-BASE sibling that steps in lockstep and read a cast store as a use, got {p22!r}")
# R44, the counter-derived pointer (T7 agents e21 S104 + f2 S105; known-true: alone it reproduces e21's close of
# func_80037EA0 at 0 from 33, and on f2's func_80038838 start text scores the agent's "loop 2 alone" 18; composed with
# the extended R22 it reaches f2's 0). The fixture: a do-while with a counter and a walked pointer, an `&&` test on the