diff --git a/docs/SETUP.md b/docs/SETUP.md index 43f2395cb..60ade1476 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -1840,7 +1840,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), **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), **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). + [≤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), **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). - **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 diff --git a/tools/delever.py b/tools/delever.py index f06ad6216..9f8352141 100644 --- a/tools/delever.py +++ b/tools/delever.py @@ -3492,11 +3492,16 @@ def duplicate_join_statement(text, tu, fn, d_, max_sites=12): masked = sc.mask_text(text).split("\n") lo, hi = d_["line"], d_["end"] - 1 out = [] - for k in range(lo + 1, hi): - if not re.match(r"^\s*\}\s*$", masked[k - 1]) or not simple_stmt(masked[k]) or is_decl_line(masked[k].strip()): + for c in range(lo, hi - 1): + if not re.match(r"^\s*\}\s*$", masked[c]): continue - # find the matching `if (…) {` … `} else {` … `}` that closes at k-1 - depth, j, else_at = 0, k - 1, None + k = c + 1 + while k < hi and not masked[k].strip(): # blank lines between the join and the statement (S104 e24) + k += 1 + if k >= hi or not simple_stmt(masked[k]) or is_decl_line(masked[k].strip()): + continue + # find the matching `if (…) {` … `} else {` … `}` that closes at c + depth, j, else_at = 0, c, None while j >= lo: depth += masked[j].count("}") - masked[j].count("{") if re.match(r"^\s*\}\s*else\s*\{\s*$", masked[j]) and depth == 1: @@ -3507,9 +3512,15 @@ def duplicate_join_statement(text, tu, fn, d_, max_sites=12): if else_at is None or j < lo or not re.match(r"^\s*if\s*\(", masked[j]): continue ind = lines[else_at][:len(lines[else_at]) - len(lines[else_at].lstrip())] + " " - stmt = lines[k].strip() - cand = lines[:else_at] + [ind + stmt] + [lines[else_at]] + lines[else_at + 1:k - 1] + [ind + stmt, lines[k - 1]] + lines[k + 1:] - out.append((f"dup-join {stmt[:24]} @{k + 1}", "\n".join(cand))) + # the first 1..3 simple statements after the join (S104 e24 func_80180E24: a STORE PAIR had to move together) + n = 0 + while n < 3 and k + n < hi and simple_stmt(masked[k + n]) and not is_decl_line(masked[k + n].strip()) \ + and not re.match(r"^\s*(?:return|goto|break|continue)\b", masked[k + n]): + n += 1 + stmts = [lines[x].strip() for x in range(k, k + n)] + cand = (lines[:else_at] + [ind + t for t in stmts] + [lines[else_at]] + lines[else_at + 1:c] + + [ind + t for t in stmts] + [lines[c]] + lines[c + 1:k] + lines[k + n:]) + out.append((f"dup-join ×{n} {stmts[0][:24]} @{k + 1}", "\n".join(cand))) if len(out) >= max_sites: break return out