mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-27 05:56:00 -04:00
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:
+1
-1
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user