From ba98d26df8141ce8f06e6be47d234791d7d20d26 Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Thu, 10 Sep 2026 23:11:13 -0600 Subject: [PATCH] phase-36: R23 accepts a case/default label as a statement boundary (d22's split, known-true 0); generators R35 drop_param_copies (d24) and R36 merge_set_chains (d25, known-true 0) --- .run/P36/agents/ORDER.tsv | 6 +- .../ov_SC03_091__func_80188DF4/residual.txt | 1 + docs/SETUP.md | 5 +- tools/delever.py | 85 ++++++++++++++++++- 4 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 .run/P36/agents/ov_SC03_091__func_80188DF4/residual.txt diff --git a/.run/P36/agents/ORDER.tsv b/.run/P36/agents/ORDER.tsv index f4d7b8b4cd..f1947fc4ed 100644 --- a/.run/P36/agents/ORDER.tsv +++ b/.run/P36/agents/ORDER.tsv @@ -1,6 +1,2 @@ rank fn alias copies best needed kinds regs tu -61 func_801861FC ov_SC03_091 4 9 1 pin $2 src/ov_SC03_091/ov_SC03_091_jr_8018326C.c -36 func_8018003C ov_SC02_016 4 9 4 pin $0,$17,$5,$6 src/ov_SC02_016/ov_SC02_016_jr_8017DC70.c -57 func_80181864 ov_SC03_113 4 21 3 barrier,launder src/ov_SC03_113/ov_SC03_113_jr_8017C294.c -68 func_80180324 ov_SC04_007 7 43 8 cast,keepalive,pin $0 src/ov_SC04_007/ov_SC04_007_jr_8017BEBC.c -85 func_8018179C ov_SC06_010 4 63 2 pin $16,$4 src/ov_SC06_010/ov_SC06_010_jr_8017A4AC.c +1 func_80188DF4 ov_SC03_091 3 6 1 keepalive src/ov_SC03_091/ov_SC03_091_jr_8018326C.c diff --git a/.run/P36/agents/ov_SC03_091__func_80188DF4/residual.txt b/.run/P36/agents/ov_SC03_091__func_80188DF4/residual.txt new file mode 100644 index 0000000000..bebc0c68f5 --- /dev/null +++ b/.run/P36/agents/ov_SC03_091__func_80188DF4/residual.txt @@ -0,0 +1 @@ +UNSTRIPPABLE [('keepalive', 6768, 'token mismatch at src/ov_SC03_091/ov_SC03_091_jr_8018326C.c:6768: expected an asm statement')] diff --git a/docs/SETUP.md b/docs/SETUP.md index ec2e712667..0c5037863a 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -1835,7 +1835,10 @@ CLAIM, not a fact — two were refuted on bytes on 2026-09-10.** spans merged — a single pair never closed those three alone (13 / 5 / 63): it is a MOVE for the engine's composition, not a one-shot closer). The new generators mask the WHOLE text (`sc.mask_text(text).split("\n")`): a per-line mask leaves a block comment's inner lines visible (R32's first known-true run counted a mention inside a comment). `delever_regen --families - R28 …` feeds R28 the real TU like R27. + 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). - **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 1bf9d01e28..93bc125127 100644 --- a/tools/delever.py +++ b/tools/delever.py @@ -2860,8 +2860,10 @@ def split_reused_locals(text, tu, fn, d_): semi = mb.find(";", p) if semi < 0 or re.search(r"(?])%s\b" % re.escape(v), mb[p + len(v):semi]): return None - if before and not before.endswith((";", "{", "}")): + if before and not before.endswith((";", "{", "}")) and \ + not re.search(r"(?:\bcase\s+[^;:?]+|\bdefault\s*):$", before): return None # inside an expression, or a brace-less `if (c) v = E;` + # (a `case K:` / `default:` label IS a statement boundary — S104 d22: R23 never split func_801861FC's `t`) defs.append(p) if len(defs) < 2 or occ[0] != defs[0]: return None @@ -3348,6 +3350,79 @@ def _loop_between(masked, i, j): return any(re.match(r"^\s*(?:for|while|do)\b", masked[k]) for k in range(i, j + 1)) +def drop_param_copies(text, tu, fn, d_): + """[(description, candidate text)] — R35: a parameter copy `T x = argN;` (or `x = argN;` as the first use) deleted and + `argN` used everywhere instead. + + T7 agents d24 (func_8018003C ×4) and d17 (func_80181DAC ×4), P36 S104: a copy of a parameter that lives past the + parameter's last use becomes the canonical register in cse (`make_regs_eqv`, `cse.c:846-862`), which re-routes later + reads through it — two callee-saved registers where the target has one, and tails that cross-jump could have merged now + load `$a0` differently (`jump.c:2371`). Only a copy that is never reassigned, of a parameter never reassigned after it.""" + lines = text.split("\n") + masked = sc.mask_text(text).split("\n") + lo, hi = d_["line"], d_["end"] - 1 + head = " ".join(masked[d_["line"] - 1:lo + 2]) + pm = re.search(r"\b%s\s*\(([^)]*)\)" % re.escape(fn), head) + if not pm: + return [] + params = [re.findall(r"([A-Za-z_]\w*)\s*(?:\[[^\]]*\])?\s*$", p_.strip())[0] for p_ in pm.group(1).split(",") + if re.findall(r"([A-Za-z_]\w*)\s*$", p_.strip()) and p_.strip() not in ("void", "")] + out = [] + for i in range(lo, hi): + m = re.match(r"^\s*(?:(?:[A-Za-z_]\w*\s*\**\s+)+)?\**\s*([A-Za-z_]\w*)\s*=\s*(?:\([^()]*\)\s*)?([A-Za-z_]\w*)\s*;\s*$", + masked[i]) + if not m or m.group(2) not in params or m.group(1) in params: + continue + x, a = m.group(1), m.group(2) + body = "\n".join(masked[i + 1:hi]) + assign = r"(?])\b%s\s*(?:[-+*/%%&|^]|<<|>>)?=(?!=)|(?:\+\+|--)\s*%s\b|\b%s\s*(?:\+\+|--)" + if re.search(assign % ((re.escape(x),) * 3), body) or re.search(assign % ((re.escape(a),) * 3), body): + continue + cand = list(lines) + is_decl = bool(re.match(r"^\s*[A-Za-z_]\w*[\w\s]*\**\s*%s\s*=" % re.escape(x), masked[i])) and \ + not re.match(r"^\s*%s\s*=" % re.escape(x), masked[i]) + cand[i] = None + if not is_decl: + cm = [sc.mask_text(l) if l is not None else "" for l in cand] + _drop_single_decl(cand, cm, lo, hi, x) + cand = [re.sub(r"\b%s\b" % re.escape(x), a, l) if l is not None and k > i else l for k, l in enumerate(cand)] + out.append((f"drop-param-copy {x}->{a} @{i + 1}", "\n".join(l for l in cand if l is not None))) + return out + + +def merge_set_chains(text, tu, fn, d_): + """[(description, candidate text)] — R36: a local set twice in a row, `x = A; x += B;` / `x = A; x = x + B;`, written as + one assignment `x = A + B;` (the operator kept). + + T7 agent d25 (func_8017E060 ×3, P36 S104) and S103 c35: combine folds every use of such a pseudo into its consumers but + zeroes its ref count only when its set count reaches 0 (`combine.c:2305-2337`; `i2dest_in_i2src` skips the i2 update, + `:1394`), so the dead pseudo keeps refs, gets no register, and reload hands it a stack slot (`reload1.c:2327-2352`) — a + FRAME-ONLY residual: every instruction equal, the frame 8 bytes larger.""" + lines = text.split("\n") + masked = sc.mask_text(text).split("\n") + lo, hi = d_["line"], d_["end"] - 1 + out = [] + for i in range(lo, hi - 1): + a = re.match(r"^(\s*)([A-Za-z_]\w*)\s*=\s*(.+);\s*$", masked[i]) + if not a or not simple_stmt(masked[i]): + continue + x = a.group(2) + A = lines[i][lines[i].index("=") + 1:].rsplit(";", 1)[0].strip() + for j in range(i + 1, min(i + 4, hi)): # up to two lines between that do not mention x (d25: `new_var = r;`) + m1 = re.match(r"^\s*%s\s*([-+|&^])=\s*(.+);\s*$" % re.escape(x), masked[j]) + m2 = re.match(r"^\s*%s\s*=\s*%s\s*([-+|&^])\s*(.+);\s*$" % (re.escape(x), re.escape(x)), masked[j]) + m = m1 or m2 + if m or re.search(r"\b%s\b" % re.escape(x), masked[j]) or not simple_stmt(masked[j]): + break + if not m or re.search(r"\b%s\b" % re.escape(x), m.group(2)): + continue + B = lines[j][m.start(2):m.end(2)] + cand = list(lines) + cand[i], cand[j] = None, f"{a.group(1)}{x} = {A} {m.group(1)} {B};" + out.append((f"merge-set-chain {x} @{i + 1}", "\n".join(l for l in cand if l is not None))) + return out + + def merge_pinned_twins(tu, fn, free_text): """[(description, candidate text)] — R28: locals the TREE pins to the same hard register, merged into one variable. @@ -3565,7 +3640,7 @@ def named_ports(tu, fn, max_donors=6): return out -ALL_FAMILIES = ("R2", "R3", "R4", "R5", "R6", "R7", "R8", "R9", "R10", "R12", "R13", "R14", "R15", "R16", "R17", "R18", "R19", "R20", "R21", "R22", "R23", "R24", "R25", "R26", "R27", "R28", "R29", "R31", "R32", "R33", "R34") +ALL_FAMILIES = ("R2", "R3", "R4", "R5", "R6", "R7", "R8", "R9", "R10", "R12", "R13", "R14", "R15", "R16", "R17", "R18", "R19", "R20", "R21", "R22", "R23", "R24", "R25", "R26", "R27", "R28", "R29", "R31", "R32", "R33", "R34", "R35", "R36") RUNG_R_FAMILIES = ("R2", "R3", "R4", "R5", "R6", "R7") # the free sweep's set (R8/R9 are the search engine's until measured) @@ -3718,6 +3793,12 @@ def recipe_candidates(text, tu, fn, names, limit=24, rng=None, cap=40, blocks=Tr if "R33" in fam: for desc, cand in else_arm_assignments(text, tu, fn, d_): out.append(("R33", desc, cand)) + if "R35" in fam: + for desc, cand in drop_param_copies(text, tu, fn, d_): + out.append(("R35", desc, cand)) + if "R36" in fam: + for desc, cand in merge_set_chains(text, tu, fn, d_): + out.append(("R36", desc, cand)) if "R34" in fam: for desc, cand in merge_disjoint_locals(text, tu, fn, d_): out.append(("R34", desc, cand))