phase-36: T7 toolify a1 — generator R15, the sink (agent a1's crack made mechanical; reproduces it from the pre-bank text at score 0)

The harvest half of the one-at-a-time loop (R16): agent a1's reading of func_80156044 is now a move the engine can make
on any body, so the remaining head classes get it for free.

- R15 sinks the statement AFTER an if/else chain into every arm and deletes the variables it consumed:
  `if (c) { v = e1; } else { v = e2; } w = f(v);` -> `if (c) { w = f(e1); } else { w = f(e2); }`.
- it is a REGISTER move, not a scheduling one. A value set in every arm and read after the merge is a cross-block pseudo
  local-alloc never gives a quantity (local-alloc.c:472, next_qty reset at :517), so the arm holds two quantities and
  takes block_alloc's unrolled case 2 (:1499-1502, qty_compare :1578-1596). Sinking makes it a third block-local
  quantity, and case 3 (:1491-1496) falls through into case 2 and applies that comparison a second time, undoing its own
  exchange — the two caller-saved colours swap. It also takes the value out of global.c, where set_preference
  (global.c:1535+) had given it a copy preference through the merge result's argument copy.
- applicability is checked, never assumed: each consumed variable must be assigned exactly once in every arm by a simple
  statement, appear in the merge statement, and occur nowhere else in the function.
- if_chains() counts a line's CLOSING braces before its opening ones. On a `} else if (...) {` line the two net to zero
  and the first version's depth counter never closed the arm — the generator found 0 candidates on the very body it was
  written from. Caught by running it on that known-true case before believing it.
- ranked third in REG-caller / REG-mixed / COUNT; the engine selftest's "R5 in the first three" assertion widened to
  "R5 and R15 in the first four" rather than de-ranking the new move.
- verified: delever --selftest OK (3 new controls: a variable read after the merge, a variable one arm does not set, the
  brace walk's three arms); delever_search --selftest OK; and the known-true check — R15 run on func_80156044's
  pre-bank text emits the agent's crack and `--try` scores it
  `score 0 (OTHER; mine 74 ins, target 74) — MATCH`.
- SETUP row rewritten (R21), kit corpus regenerated, tool_census --check OK (371 copies + 30 pointers, 0 gaps).
- no src/ change in this commit; the sweep of the other 56 head classes follows.
This commit is contained in:
Drew T
2026-09-10 00:45:15 -06:00
parent fd2433b6d2
commit 861dd0651c
7 changed files with 609 additions and 197 deletions
+180 -180
View File
@@ -1,7 +1,7 @@
{
"head": "5a8bd1021",
"head": "fd2433b6d",
"stamp": "b44c5a8a3423",
"generated": "2026-09-10 00:04",
"generated": "2026-09-10 00:39",
"aliases": [
"main",
"ov_SC03_014",
@@ -21,207 +21,207 @@
"main": {
"objects": 85,
"identical": 85,
"seconds": 7.544000000000001,
"mean_s": 0.089
"seconds": 6.715,
"mean_s": 0.079
},
"ov_SC03_014": {
"objects": 32,
"identical": 32,
"seconds": 4.776,
"mean_s": 0.149
"seconds": 4.601000000000001,
"mean_s": 0.144
},
"ov_SC03_015": {
"objects": 32,
"identical": 32,
"seconds": 4.493000000000001,
"mean_s": 0.14
"seconds": 4.5489999999999995,
"mean_s": 0.142
},
"ov_SC04_011": {
"objects": 28,
"identical": 28,
"seconds": 3.912000000000001,
"mean_s": 0.14
"seconds": 3.94,
"mean_s": 0.141
}
},
"per_object_seconds": {
"build/src/800.o": 0.814,
"build/src/800_b.o": 0.134,
"build/src/800_b_2.o": 0.367,
"build/src/800_b_o0a.o": 0.11,
"build/src/800_c.o": 0.246,
"build/src/800b2.o": 0.107,
"build/src/apicard1.o": 0.093,
"build/src/apicard2.o": 0.095,
"build/src/apicard3.o": 0.09,
"build/src/apicard4.o": 0.09,
"build/src/apicard5.o": 0.091,
"build/src/apicard6.o": 0.097,
"build/src/apicard7.o": 0.095,
"build/src/boot.o": 0.109,
"build/src/gap.o": 0.089,
"build/src/libapi1.o": 0.094,
"build/src/libapi2.o": 0.058,
"build/src/libc2_1.o": 0.103,
"build/src/800.o": 0.754,
"build/src/800_b.o": 0.084,
"build/src/800_b_2.o": 0.287,
"build/src/800_b_o0a.o": 0.073,
"build/src/800_c.o": 0.189,
"build/src/800b2.o": 0.072,
"build/src/apicard1.o": 0.066,
"build/src/apicard2.o": 0.075,
"build/src/apicard3.o": 0.077,
"build/src/apicard4.o": 0.065,
"build/src/apicard5.o": 0.071,
"build/src/apicard6.o": 0.066,
"build/src/apicard7.o": 0.083,
"build/src/boot.o": 0.07,
"build/src/gap.o": 0.07,
"build/src/libapi1.o": 0.081,
"build/src/libapi2.o": 0.054,
"build/src/libc2_1.o": 0.064,
"build/src/libc2_2.o": 0.058,
"build/src/libcd1.o": 0.096,
"build/src/libcd2.o": 0.067,
"build/src/libetc.o": 0.071,
"build/src/libgpu.o": 0.079,
"build/src/libgpu2.o": 0.094,
"build/src/libgs1.o": 0.068,
"build/src/libgs2.o": 0.066,
"build/src/libgs3.o": 0.063,
"build/src/libgs4.o": 0.068,
"build/src/libgs5.o": 0.062,
"build/src/libgs6.o": 0.112,
"build/src/libgs7.o": 0.068,
"build/src/libcd1.o": 0.066,
"build/src/libcd2.o": 0.056,
"build/src/libetc.o": 0.063,
"build/src/libgpu.o": 0.065,
"build/src/libgpu2.o": 0.081,
"build/src/libgs1.o": 0.059,
"build/src/libgs2.o": 0.063,
"build/src/libgs3.o": 0.077,
"build/src/libgs4.o": 0.069,
"build/src/libgs5.o": 0.05,
"build/src/libgs6.o": 0.074,
"build/src/libgs7.o": 0.059,
"build/src/libgs8.o": 0.061,
"build/src/libgte1.o": 0.062,
"build/src/libgte10.o": 0.056,
"build/src/libgte11.o": 0.06,
"build/src/libgte12.o": 0.064,
"build/src/libgte13.o": 0.066,
"build/src/libgte14.o": 0.056,
"build/src/libgte15.o": 0.071,
"build/src/libgte16.o": 0.065,
"build/src/libgte17.o": 0.069,
"build/src/libgte18.o": 0.059,
"build/src/libgte19.o": 0.063,
"build/src/libgte2.o": 0.064,
"build/src/libgte20.o": 0.064,
"build/src/libgte21.o": 0.069,
"build/src/libgte22.o": 0.07,
"build/src/libgte1.o": 0.06,
"build/src/libgte10.o": 0.067,
"build/src/libgte11.o": 0.062,
"build/src/libgte12.o": 0.061,
"build/src/libgte13.o": 0.06,
"build/src/libgte14.o": 0.067,
"build/src/libgte15.o": 0.067,
"build/src/libgte16.o": 0.067,
"build/src/libgte17.o": 0.071,
"build/src/libgte18.o": 0.065,
"build/src/libgte19.o": 0.066,
"build/src/libgte2.o": 0.065,
"build/src/libgte20.o": 0.063,
"build/src/libgte21.o": 0.061,
"build/src/libgte22.o": 0.065,
"build/src/libgte23.o": 0.065,
"build/src/libgte24.o": 0.067,
"build/src/libgte25.o": 0.086,
"build/src/libgte26.o": 0.059,
"build/src/libgte27.o": 0.071,
"build/src/libgte28.o": 0.064,
"build/src/libgte29.o": 0.071,
"build/src/libgte3.o": 0.07,
"build/src/libgte30.o": 0.072,
"build/src/libgte4.o": 0.066,
"build/src/libgte24.o": 0.064,
"build/src/libgte25.o": 0.072,
"build/src/libgte26.o": 0.067,
"build/src/libgte27.o": 0.063,
"build/src/libgte28.o": 0.063,
"build/src/libgte29.o": 0.062,
"build/src/libgte3.o": 0.068,
"build/src/libgte30.o": 0.064,
"build/src/libgte4.o": 0.063,
"build/src/libgte5.o": 0.063,
"build/src/libgte6.o": 0.066,
"build/src/libgte7.o": 0.066,
"build/src/libgte8.o": 0.065,
"build/src/libgte9.o": 0.067,
"build/src/libmcrd1.o": 0.091,
"build/src/libmcrd2.o": 0.075,
"build/src/libpad1.o": 0.07,
"build/src/libpad2.o": 0.082,
"build/src/sgap.o": 0.063,
"build/src/sgap_2.o": 0.063,
"build/src/sgap_3.o": 0.062,
"build/src/sgap_4.o": 0.074,
"build/src/sgap_5.o": 0.071,
"build/src/sgap_6.o": 0.062,
"build/src/sgap_8.o": 0.073,
"build/src/snd1.o": 0.084,
"build/src/snd10.o": 0.071,
"build/src/snd11.o": 0.065,
"build/src/snd12.o": 0.065,
"build/src/snd2.o": 0.093,
"build/src/snd3.o": 0.065,
"build/src/snd4.o": 0.062,
"build/src/snd5.o": 0.061,
"build/src/snd6.o": 0.079,
"build/src/snd7.o": 0.068,
"build/src/snd8.o": 0.074,
"build/src/snd9.o": 0.08,
"build/src/ov_SC03_014/ov_SC03_014.o": 0.136,
"build/src/ov_SC03_014/ov_SC03_014_after.o": 0.505,
"build/src/ov_SC03_014/ov_SC03_014_jr_8012ACE0.o": 0.394,
"build/src/ov_SC03_014/ov_SC03_014_jr_80135888.o": 0.059,
"build/src/ov_SC03_014/ov_SC03_014_jr_80135A4C.o": 0.062,
"build/src/ov_SC03_014/ov_SC03_014_jr_80135D20.o": 0.127,
"build/src/ov_SC03_014/ov_SC03_014_jr_801380E0.o": 0.16,
"build/src/ov_SC03_014/ov_SC03_014_jr_8013C98C.o": 0.127,
"build/src/ov_SC03_014/ov_SC03_014_jr_8013F350.o": 0.08,
"build/src/ov_SC03_014/ov_SC03_014_jr_8013FFD8.o": 0.078,
"build/src/ov_SC03_014/ov_SC03_014_jr_80140608.o": 0.183,
"build/src/ov_SC03_014/ov_SC03_014_jr_8015444C.o": 0.07,
"build/src/ov_SC03_014/ov_SC03_014_jr_80154C24.o": 0.171,
"build/src/ov_SC03_014/ov_SC03_014_jr_801588CC.o": 0.1,
"build/src/ov_SC03_014/ov_SC03_014_jr_80159C84.o": 0.072,
"build/src/ov_SC03_014/ov_SC03_014_jr_8015A3C8.o": 0.072,
"build/src/ov_SC03_014/ov_SC03_014_jr_8015AE2C.o": 0.1,
"build/src/ov_SC03_014/ov_SC03_014_jr_8015C32C.o": 0.498,
"build/src/ov_SC03_014/ov_SC03_014_jr_8016AB6C.o": 0.275,
"build/src/ov_SC03_014/ov_SC03_014_jr_80171B4C.o": 0.101,
"build/src/ov_SC03_014/ov_SC03_014_jr_801734BC.o": 0.213,
"build/src/ov_SC03_014/ov_SC03_014_jr_801789AC.o": 0.061,
"build/src/ov_SC03_014/ov_SC03_014_jr_80178D40.o": 0.106,
"build/src/ov_SC03_014/ov_SC03_014_jr_8017A4AC.o": 0.074,
"build/src/ov_SC03_014/ov_SC03_014_jr_8017AE2C.o": 0.159,
"build/src/ov_SC03_014/ov_SC03_014_jr_8017EB7C.o": 0.212,
"build/src/ov_SC03_014/ov_SC03_014_jr_80184440.o": 0.054,
"build/src/ov_SC03_014/ov_SC03_014_jr_801848E4.o": 0.281,
"build/src/ov_SC03_014/ov_SC03_014_o0b.o": 0.061,
"build/src/libgte6.o": 0.068,
"build/src/libgte7.o": 0.064,
"build/src/libgte8.o": 0.067,
"build/src/libgte9.o": 0.066,
"build/src/libmcrd1.o": 0.075,
"build/src/libmcrd2.o": 0.068,
"build/src/libpad1.o": 0.069,
"build/src/libpad2.o": 0.069,
"build/src/sgap.o": 0.075,
"build/src/sgap_2.o": 0.088,
"build/src/sgap_3.o": 0.059,
"build/src/sgap_4.o": 0.075,
"build/src/sgap_5.o": 0.065,
"build/src/sgap_6.o": 0.06,
"build/src/sgap_8.o": 0.072,
"build/src/snd1.o": 0.07,
"build/src/snd10.o": 0.059,
"build/src/snd11.o": 0.069,
"build/src/snd12.o": 0.062,
"build/src/snd2.o": 0.072,
"build/src/snd3.o": 0.064,
"build/src/snd4.o": 0.066,
"build/src/snd5.o": 0.06,
"build/src/snd6.o": 0.071,
"build/src/snd7.o": 0.066,
"build/src/snd8.o": 0.069,
"build/src/snd9.o": 0.064,
"build/src/ov_SC03_014/ov_SC03_014.o": 0.134,
"build/src/ov_SC03_014/ov_SC03_014_after.o": 0.5,
"build/src/ov_SC03_014/ov_SC03_014_jr_8012ACE0.o": 0.379,
"build/src/ov_SC03_014/ov_SC03_014_jr_80135888.o": 0.06,
"build/src/ov_SC03_014/ov_SC03_014_jr_80135A4C.o": 0.057,
"build/src/ov_SC03_014/ov_SC03_014_jr_80135D20.o": 0.121,
"build/src/ov_SC03_014/ov_SC03_014_jr_801380E0.o": 0.165,
"build/src/ov_SC03_014/ov_SC03_014_jr_8013C98C.o": 0.12,
"build/src/ov_SC03_014/ov_SC03_014_jr_8013F350.o": 0.079,
"build/src/ov_SC03_014/ov_SC03_014_jr_8013FFD8.o": 0.07,
"build/src/ov_SC03_014/ov_SC03_014_jr_80140608.o": 0.181,
"build/src/ov_SC03_014/ov_SC03_014_jr_8015444C.o": 0.08,
"build/src/ov_SC03_014/ov_SC03_014_jr_80154C24.o": 0.176,
"build/src/ov_SC03_014/ov_SC03_014_jr_801588CC.o": 0.099,
"build/src/ov_SC03_014/ov_SC03_014_jr_80159C84.o": 0.069,
"build/src/ov_SC03_014/ov_SC03_014_jr_8015A3C8.o": 0.067,
"build/src/ov_SC03_014/ov_SC03_014_jr_8015AE2C.o": 0.101,
"build/src/ov_SC03_014/ov_SC03_014_jr_8015C32C.o": 0.449,
"build/src/ov_SC03_014/ov_SC03_014_jr_8016AB6C.o": 0.257,
"build/src/ov_SC03_014/ov_SC03_014_jr_80171B4C.o": 0.095,
"build/src/ov_SC03_014/ov_SC03_014_jr_801734BC.o": 0.197,
"build/src/ov_SC03_014/ov_SC03_014_jr_801789AC.o": 0.056,
"build/src/ov_SC03_014/ov_SC03_014_jr_80178D40.o": 0.097,
"build/src/ov_SC03_014/ov_SC03_014_jr_8017A4AC.o": 0.068,
"build/src/ov_SC03_014/ov_SC03_014_jr_8017AE2C.o": 0.155,
"build/src/ov_SC03_014/ov_SC03_014_jr_8017EB7C.o": 0.196,
"build/src/ov_SC03_014/ov_SC03_014_jr_80184440.o": 0.053,
"build/src/ov_SC03_014/ov_SC03_014_jr_801848E4.o": 0.276,
"build/src/ov_SC03_014/ov_SC03_014_o0b.o": 0.059,
"build/src/ov_SC03_014/ov_SC03_014_o0c.o": 0.061,
"build/src/ov_SC03_014/ov_SC03_014_o0d.o": 0.058,
"build/src/ov_SC03_014/ov_SC03_014_o0e.o": 0.066,
"build/src/ov_SC03_015/ov_SC03_015.o": 0.112,
"build/src/ov_SC03_015/ov_SC03_015_after.o": 0.469,
"build/src/ov_SC03_015/ov_SC03_015_jr_8012ACE0.o": 0.382,
"build/src/ov_SC03_015/ov_SC03_015_jr_80135888.o": 0.059,
"build/src/ov_SC03_015/ov_SC03_015_jr_80135A4C.o": 0.057,
"build/src/ov_SC03_015/ov_SC03_015_jr_80135D20.o": 0.112,
"build/src/ov_SC03_015/ov_SC03_015_jr_801380E0.o": 0.144,
"build/src/ov_SC03_015/ov_SC03_015_jr_8013C98C.o": 0.111,
"build/src/ov_SC03_015/ov_SC03_015_jr_8013F350.o": 0.067,
"build/src/ov_SC03_015/ov_SC03_015_jr_8013FFD8.o": 0.066,
"build/src/ov_SC03_015/ov_SC03_015_jr_80140608.o": 0.18,
"build/src/ov_SC03_015/ov_SC03_015_jr_8015444C.o": 0.071,
"build/src/ov_SC03_015/ov_SC03_015_jr_80154C24.o": 0.171,
"build/src/ov_SC03_014/ov_SC03_014_o0d.o": 0.057,
"build/src/ov_SC03_014/ov_SC03_014_o0e.o": 0.067,
"build/src/ov_SC03_015/ov_SC03_015.o": 0.123,
"build/src/ov_SC03_015/ov_SC03_015_after.o": 0.504,
"build/src/ov_SC03_015/ov_SC03_015_jr_8012ACE0.o": 0.393,
"build/src/ov_SC03_015/ov_SC03_015_jr_80135888.o": 0.049,
"build/src/ov_SC03_015/ov_SC03_015_jr_80135A4C.o": 0.054,
"build/src/ov_SC03_015/ov_SC03_015_jr_80135D20.o": 0.121,
"build/src/ov_SC03_015/ov_SC03_015_jr_801380E0.o": 0.148,
"build/src/ov_SC03_015/ov_SC03_015_jr_8013C98C.o": 0.104,
"build/src/ov_SC03_015/ov_SC03_015_jr_8013F350.o": 0.068,
"build/src/ov_SC03_015/ov_SC03_015_jr_8013FFD8.o": 0.064,
"build/src/ov_SC03_015/ov_SC03_015_jr_80140608.o": 0.168,
"build/src/ov_SC03_015/ov_SC03_015_jr_8015444C.o": 0.065,
"build/src/ov_SC03_015/ov_SC03_015_jr_80154C24.o": 0.167,
"build/src/ov_SC03_015/ov_SC03_015_jr_801588CC.o": 0.09,
"build/src/ov_SC03_015/ov_SC03_015_jr_80159C84.o": 0.068,
"build/src/ov_SC03_015/ov_SC03_015_jr_8015A3C8.o": 0.071,
"build/src/ov_SC03_015/ov_SC03_015_jr_8015AE2C.o": 0.093,
"build/src/ov_SC03_015/ov_SC03_015_jr_8015C32C.o": 0.455,
"build/src/ov_SC03_015/ov_SC03_015_jr_80159C84.o": 0.065,
"build/src/ov_SC03_015/ov_SC03_015_jr_8015A3C8.o": 0.067,
"build/src/ov_SC03_015/ov_SC03_015_jr_8015AE2C.o": 0.098,
"build/src/ov_SC03_015/ov_SC03_015_jr_8015C32C.o": 0.449,
"build/src/ov_SC03_015/ov_SC03_015_jr_8016AB6C.o": 0.27,
"build/src/ov_SC03_015/ov_SC03_015_jr_80171B4C.o": 0.1,
"build/src/ov_SC03_015/ov_SC03_015_jr_801734BC.o": 0.197,
"build/src/ov_SC03_015/ov_SC03_015_jr_801789AC.o": 0.057,
"build/src/ov_SC03_015/ov_SC03_015_jr_80178D40.o": 0.099,
"build/src/ov_SC03_015/ov_SC03_015_jr_8017A4AC.o": 0.059,
"build/src/ov_SC03_015/ov_SC03_015_jr_8017AE2C.o": 0.168,
"build/src/ov_SC03_015/ov_SC03_015_jr_8017EB7C.o": 0.195,
"build/src/ov_SC03_015/ov_SC03_015_jr_80184440.o": 0.06,
"build/src/ov_SC03_015/ov_SC03_015_jr_801848E4.o": 0.274,
"build/src/ov_SC03_015/ov_SC03_015_o0b.o": 0.062,
"build/src/ov_SC03_015/ov_SC03_015_o0c.o": 0.058,
"build/src/ov_SC03_015/ov_SC03_015_o0d.o": 0.054,
"build/src/ov_SC03_015/ov_SC03_015_o0e.o": 0.062,
"build/src/ov_SC04_011/ov_SC04_011.o": 0.126,
"build/src/ov_SC04_011/ov_SC04_011_after.o": 0.422,
"build/src/ov_SC04_011/ov_SC04_011_jr_8012ACE0.o": 0.338,
"build/src/ov_SC04_011/ov_SC04_011_jr_80135888.o": 0.05,
"build/src/ov_SC04_011/ov_SC04_011_jr_80135A4C.o": 0.055,
"build/src/ov_SC03_015/ov_SC03_015_jr_80171B4C.o": 0.101,
"build/src/ov_SC03_015/ov_SC03_015_jr_801734BC.o": 0.194,
"build/src/ov_SC03_015/ov_SC03_015_jr_801789AC.o": 0.061,
"build/src/ov_SC03_015/ov_SC03_015_jr_80178D40.o": 0.098,
"build/src/ov_SC03_015/ov_SC03_015_jr_8017A4AC.o": 0.063,
"build/src/ov_SC03_015/ov_SC03_015_jr_8017AE2C.o": 0.171,
"build/src/ov_SC03_015/ov_SC03_015_jr_8017EB7C.o": 0.198,
"build/src/ov_SC03_015/ov_SC03_015_jr_80184440.o": 0.053,
"build/src/ov_SC03_015/ov_SC03_015_jr_801848E4.o": 0.279,
"build/src/ov_SC03_015/ov_SC03_015_o0b.o": 0.067,
"build/src/ov_SC03_015/ov_SC03_015_o0c.o": 0.061,
"build/src/ov_SC03_015/ov_SC03_015_o0d.o": 0.058,
"build/src/ov_SC03_015/ov_SC03_015_o0e.o": 0.078,
"build/src/ov_SC04_011/ov_SC04_011.o": 0.121,
"build/src/ov_SC04_011/ov_SC04_011_after.o": 0.419,
"build/src/ov_SC04_011/ov_SC04_011_jr_8012ACE0.o": 0.355,
"build/src/ov_SC04_011/ov_SC04_011_jr_80135888.o": 0.051,
"build/src/ov_SC04_011/ov_SC04_011_jr_80135A4C.o": 0.053,
"build/src/ov_SC04_011/ov_SC04_011_jr_80135D20.o": 0.116,
"build/src/ov_SC04_011/ov_SC04_011_jr_801380E0.o": 0.155,
"build/src/ov_SC04_011/ov_SC04_011_jr_8013C98C.o": 0.121,
"build/src/ov_SC04_011/ov_SC04_011_jr_8013F350.o": 0.068,
"build/src/ov_SC04_011/ov_SC04_011_jr_8013FFD8.o": 0.062,
"build/src/ov_SC04_011/ov_SC04_011_jr_80140608.o": 0.175,
"build/src/ov_SC04_011/ov_SC04_011_jr_8015444C.o": 0.068,
"build/src/ov_SC04_011/ov_SC04_011_jr_80154C24.o": 0.158,
"build/src/ov_SC04_011/ov_SC04_011_jr_801588CC.o": 0.086,
"build/src/ov_SC04_011/ov_SC04_011_jr_80159C84.o": 0.063,
"build/src/ov_SC04_011/ov_SC04_011_jr_8015A3C8.o": 0.068,
"build/src/ov_SC04_011/ov_SC04_011_jr_8015AE2C.o": 0.094,
"build/src/ov_SC04_011/ov_SC04_011_jr_8015C32C.o": 0.358,
"build/src/ov_SC04_011/ov_SC04_011_jr_8016AB6C.o": 0.225,
"build/src/ov_SC04_011/ov_SC04_011_jr_80171B4C.o": 0.089,
"build/src/ov_SC04_011/ov_SC04_011_jr_801734BC.o": 0.172,
"build/src/ov_SC04_011/ov_SC04_011_jr_801789AC.o": 0.053,
"build/src/ov_SC04_011/ov_SC04_011_jr_80178D40.o": 0.087,
"build/src/ov_SC04_011/ov_SC04_011_jr_8017A4AC.o": 0.064,
"build/src/ov_SC04_011/ov_SC04_011_jr_8017AE2C.o": 0.101,
"build/src/ov_SC04_011/ov_SC04_011_jr_8017D494.o": 0.432,
"build/src/ov_SC04_011/ov_SC04_011_o0b.o": 0.046,
"build/src/ov_SC04_011/ov_SC04_011_o0c.o": 0.06
"build/src/ov_SC04_011/ov_SC04_011_jr_801380E0.o": 0.142,
"build/src/ov_SC04_011/ov_SC04_011_jr_8013C98C.o": 0.113,
"build/src/ov_SC04_011/ov_SC04_011_jr_8013F350.o": 0.075,
"build/src/ov_SC04_011/ov_SC04_011_jr_8013FFD8.o": 0.06,
"build/src/ov_SC04_011/ov_SC04_011_jr_80140608.o": 0.179,
"build/src/ov_SC04_011/ov_SC04_011_jr_8015444C.o": 0.07,
"build/src/ov_SC04_011/ov_SC04_011_jr_80154C24.o": 0.163,
"build/src/ov_SC04_011/ov_SC04_011_jr_801588CC.o": 0.093,
"build/src/ov_SC04_011/ov_SC04_011_jr_80159C84.o": 0.066,
"build/src/ov_SC04_011/ov_SC04_011_jr_8015A3C8.o": 0.067,
"build/src/ov_SC04_011/ov_SC04_011_jr_8015AE2C.o": 0.091,
"build/src/ov_SC04_011/ov_SC04_011_jr_8015C32C.o": 0.351,
"build/src/ov_SC04_011/ov_SC04_011_jr_8016AB6C.o": 0.224,
"build/src/ov_SC04_011/ov_SC04_011_jr_80171B4C.o": 0.09,
"build/src/ov_SC04_011/ov_SC04_011_jr_801734BC.o": 0.175,
"build/src/ov_SC04_011/ov_SC04_011_jr_801789AC.o": 0.056,
"build/src/ov_SC04_011/ov_SC04_011_jr_80178D40.o": 0.092,
"build/src/ov_SC04_011/ov_SC04_011_jr_8017A4AC.o": 0.065,
"build/src/ov_SC04_011/ov_SC04_011_jr_8017AE2C.o": 0.11,
"build/src/ov_SC04_011/ov_SC04_011_jr_8017D494.o": 0.428,
"build/src/ov_SC04_011/ov_SC04_011_o0b.o": 0.049,
"build/src/ov_SC04_011/ov_SC04_011_o0c.o": 0.066
},
"ok": true,
"seconds": 2.3
"seconds": 2.2
}
@@ -3714,3 +3714,37 @@ register lever (accelerators (16)), and treat "PROVED" as "proved against this l
35 minutes of state only in its transcript. (3) Build the disagreeing oracle before the batch runs, not after: the text oracle found
in one run what the byte join could never see. (4) A tool's restore path is part of the tool (R57): `git checkout` on an uncommitted
tree is a destroyer with a green exit code.
## P36 S101 (2026-09-09) — the levers phase pivots from search to reading: a scored engine, the head as the number, and one agent at a time
**Context and belief.** After T6 the residue was 33,427 sites in 12,048 bodies (1,755 text classes); the free recipe rung had measured
as a replication engine (134/134 on a known shape, 0/300 on unknown ones, cookbook §454a) and the permuter as a random walk that
reprints the source. Drew's plan for S101: build a compute-only guided engine ("a fancier permuter, focused on pin pulling and C shape
matching") while a Fable agent reads gcc 2.7.2's source for the residual → move map, and defer the exemplar triage.
**What was built and what it measured.** Rung G (`tools/delever_search.py`): the candidate is scored by the function's instructions in
the oracle's own scratch object against the fleet run's baseline object (an edit distance over the reloc-masked words — a positional
count read one inlined temp as 43), the residual classified from the diff blocks, the move families ordered by the class from lane
B's map and ranked round-robin, a beam composing two to five moves, every score-0 banked through the real recipe and propagated to the
class. Six runs, no drafting tokens: g1 1/16, g2 1/16, g3 13/64 (1,516 bodies), g4s 3/38, g5 5/70, g6+g6b 110/400 (the tail) —
33,427 → 30,358 sites, every bank gated 218/218. The generator registry grew from six moves to fourteen, each new one read off a
residual with `--explain` and, where lane B's map named it, verified on bytes (a constant-operand swap is byte-neutral; the do-while
lever is the ref weight in `.lreg`; the 16-bit copy does not reach a value whose known bits already fit).
**What failed, and why it looked right.** Wider search looked like the lever after g3 (nine of fifteen moved past g1's best when the
beam widened); g5 then spent 112,216 compiles at three times the beam and eight times the budget for 62 bodies in small classes and
closed none of the ~50 head classes. The instrument also lied twice before it was caught by its own controls: a per-tag scratch
object let two workers read each other's objects (a byte-identical body scored 14,871), and the ledger's rung-R miss rows carried no
text hash (301 bodies read as one class). Both were found by reading an artifact, not by a red check.
**The pivot.** Derived from the ledger at the end: 57 classes of ≥100 copies hold 7,318 of the 9,796 residue bodies, 17 within four
instructions. Each is a reading and a generator, not a search. Drew's decision: run T7 as ONE AGENT AT A TIME on the head (he had
approved a 57-agent wave and then reshaped it: "time clock isn't the issue, it's efficiency … hone our methodology each time one lands,
in the hopes of learning enough to be able to tool crack the rest"), starting in a fresh session. The agent's loop scores its
candidate without a tree write (`--try`), the coordinator banks and propagates, the move becomes a generator, the engine sweeps the
rest for free — the harvest→toolify gate at the granularity of one crack.
**Hindsight (the better path).** Read the residual first, always: every generator that closed a class this session came from
`--explain` on one body and a line of the compiler's source, and every hour of wider search bought less than the previous one. The
engine's real value was not the search but the SCORE — a distance that turns a stall into a named shape — and the instrument that
prints it. Build that on day one of a de-lever campaign, before any search.
+184 -1
View File
@@ -2079,7 +2079,140 @@ def param_copies(text, tu, fn, d_):
return out
ALL_FAMILIES = ("R2", "R3", "R4", "R5", "R6", "R7", "R8", "R9", "R10", "R12", "R13", "R14")
def if_chains(masked, lo, hi):
"""[(chain_lo, chain_hi, [(arm_lo, arm_hi)])] — every brace-form if / else-if / else chain whose lines lie in
masked[lo:hi] at the body's own depth. `chain_hi` is the line index just past the chain's last `}`; an arm span is the
lines strictly inside that arm's braces. Depth is counted on the MASKED text, so a brace in a string or a comment is
not a brace. Chains that do not open a brace on the `if` line (a one-statement `if` without braces) are skipped: the
sink rewrites arm bodies and needs somewhere to put the statement."""
out = []
i = lo
while i < hi:
s_ = masked[i].strip()
if not (s_.startswith("if") and re.match(r"^if\s*\(", s_) and s_.endswith("{")):
i += 1
continue
arms, depth, arm_start, j = [], 0, i + 1, i
while j < hi:
nxt = masked[j].strip()
closes = masked[j].count("}")
# the CLOSES are counted before the OPENS: on a `} else if (…) {` line the two net to zero, and a depth
# counter that adds both at once never comes back to zero — the chain then looks like one unterminated arm.
if j > i and depth - closes == 0: # this arm's `}` is on line j
arms.append((arm_start, j))
m = re.match(r"^\}\s*else\b(.*)$", nxt)
if m and m.group(1).strip().endswith("{"): # `} else {` or `} else if (…) {`
arm_start, depth, j = j + 1, 1, j + 1
continue
if nxt == "}" and j + 1 < hi and re.match(r"^else\b", masked[j + 1].strip()) \
and masked[j + 1].strip().endswith("{"):
arm_start, depth, j = j + 2, 1, j + 2 # the `else …{` on its own line
continue
break
depth += masked[j].count("{") - closes
j += 1
if len(arms) >= 2:
out.append((i, j + 1, arms))
i = j + 1
else:
i += 1
return out
def sink_merges(text, tu, fn, d_):
"""[(description, candidate text)] — R15: the statement AFTER an if/else chain sunk into every arm, and the variables
it consumed deleted. `if (c) { v = e1; } else { v = e2; } w = f(v);` -> `if (c) { w = f(e1); } else { w = f(e2); }`.
T7 agent a1's crack of func_80156044 (2026-09-10, 130 bodies, the rank-1 head class six rung-G runs left at best 1).
The mechanism is a REGISTER move, not a scheduling one: a variable set in every arm and read after the merge is a
CROSS-BLOCK pseudo, so (i) local-alloc never makes a quantity for it (`local-alloc.c:472`, `next_qty` reset per block
at `:517`) and each arm holds two quantities, which is `block_alloc`'s unrolled `case 2` at `local-alloc.c:1499-1502`
— one `qty_compare` (`:1578-1596`), higher density first; sinking makes it a third block-local quantity and
`case 3` at `:1491-1496` FALLS THROUGH into `case 2`, applying that comparison a second time and undoing its own
exchange, so the two caller-saved colours swap; and (ii) while it is a global allocno it can inherit a copy
preference from whatever the merge statement's result is passed to (`set_preference` `global.c:1535+`, merged by
`expand_preferences` `global.c:781-825`, overriding first-fit at `global.c:1034-1067`) — sinking removes it from
`global.c` entirely. Read the whole reading in `.run/P36/agents/ov_SC04_011__func_80156044/mechanism.md`.
Applicability is checked, not assumed: every consumed variable must be assigned exactly once in EVERY arm by a simple
statement, must appear in the merge statement, and must occur nowhere else in the function (declaration + one
assignment per arm + its uses in the merge statement is its whole census) — otherwise the rewrite would change what
the code reads. The bytes remain the correctness proof (a rewrite that changes behaviour simply DIFFERS)."""
lines = text.split("\n")
masked = [sc.mask_text(l) for l in lines]
lo, hi = d_["line"], d_["end"] - 1
whole = sc.mask_text("\n".join(lines[d_["line"] - 1:d_["end"]]))
out = []
for c_lo, c_hi, arms in if_chains(masked, lo, hi):
j = next((k for k in range(c_hi, hi) if masked[k].strip()), None)
if j is None or not simple_stmt(masked[j]):
continue
merge_masked = masked[j]
eq = re.search(r"(?<![=!<>+\-*/%&|^~])=(?!=)", merge_masked)
if not eq: # only an assignment merges arm values
continue
rhs = merge_masked[eq.end():]
# every variable assigned exactly once, by a simple statement, in EVERY arm
per_arm = []
for a_lo, a_hi in arms:
got = {}
for k in range(a_lo, a_hi):
if not simple_stmt(masked[k]):
continue
m = re.match(r"^\s*([A-Za-z_]\w*)\s*=\s*(.+);\s*$", masked[k])
if not m:
continue
if m.group(1) in got: # assigned twice in one arm: not a single value
got[m.group(1)] = None
else:
got[m.group(1)] = k
per_arm.append(got)
common = set(k for k, v in per_arm[0].items() if v is not None)
for g in per_arm[1:]:
common &= set(k for k, v in g.items() if v is not None)
names_ = sorted(v for v in common if re.search(r"(?<![\w.>])%s(?![\w])" % re.escape(v), rhs))
if not names_:
continue
decl = {}
for v in names_:
# the variable's whole census: one declaration, one assignment per arm, and its uses in the merge statement
uses = len(re.findall(r"(?<![\w.>])%s(?![\w])" % re.escape(v), whole))
in_merge = len(re.findall(r"(?<![\w.>])%s(?![\w])" % re.escape(v), merge_masked))
d_line = next((k for k in range(lo, hi)
if is_decl_line(masked[k].strip())
and re.search(r"(?<![\w.>])%s(?![\w])\s*(?:=|;|,)" % re.escape(v), masked[k])), None)
if d_line is None or MULTI_DECL.match(masked[d_line]) or "=" in masked[d_line]:
decl[v] = None # a shared or initialised declaration: leave it standing
else:
decl[v] = d_line
if uses != len(arms) + in_merge + (1 if d_line is not None else 0):
names_ = None
break
if not names_:
continue
drop = {decl[v] for v in names_ if decl[v] is not None} | {j}
after = {} # line index -> the sunk statement to emit just after it
for a_idx, (a_lo, a_hi) in enumerate(arms):
stmt = lines[j].strip()
for v in names_:
k = per_arm[a_idx][v]
expr = re.match(r"^\s*[A-Za-z_]\w*\s*=\s*(.+);\s*$", lines[k]).group(1).strip()
stmt = re.sub(r"(?<![\w.>])%s(?![\w])" % re.escape(v), "(" + expr + ")", stmt)
drop.add(k)
tail = a_hi - 1 # the arm's LAST BODY line (a_hi is its `}`)
ref = lines[tail] if lines[tail].strip() else lines[a_lo]
after[tail] = ref[:len(ref) - len(ref.lstrip())] + stmt
cand = []
for k, l in enumerate(lines): # a dropped line may still be the one we append after
if k not in drop:
cand.append(l)
if k in after:
cand.append(after[k])
out.append((f"sink @{j + 1} ({','.join(names_)})", "\n".join(cand)))
return out
ALL_FAMILIES = ("R2", "R3", "R4", "R5", "R6", "R7", "R8", "R9", "R10", "R12", "R13", "R14", "R15")
RUNG_R_FAMILIES = ("R2", "R3", "R4", "R5", "R6", "R7") # the free sweep's set (R8/R9 are the search engine's until measured)
@@ -2184,6 +2317,9 @@ def recipe_candidates(text, tu, fn, names, limit=24, rng=None, cap=40, blocks=Tr
if "R13" in fam:
for desc, cand in reassociations(text, tu, fn, d_):
out.append(("R13", desc, cand))
if "R15" in fam:
for desc, cand in sink_merges(text, tu, fn, d_):
out.append(("R15", desc, cand))
if blocks and "R7" in fam: # last: one candidate per statement, so the targeted recipes go first
for desc, cand in block_wraps(text, tu, fn, d_):
out.append(("R7", desc, cand))
@@ -2894,6 +3030,53 @@ def selftest():
fail(f"remap_body produced {got!r} ({why})")
if remap_body(exb, exa, "void func_80300000(void) { D_80400000 = 0; }")[0] is not None:
fail("remap_body must refuse a sibling with a different token count")
# R15, the sink (T7 agent a1's crack of func_80156044, 2026-09-10): the merge statement pushed into every arm and the
# variables it consumed deleted. The three controls are the ones the applicability test exists for.
SINKFIX = ("void func_80100000(int c) {\n"
" s32 base;\n"
" u8 *row;\n"
"\n"
" if (c) {\n"
" base = 1;\n"
" } else if (c == 2) {\n"
" base = 2;\n"
" } else {\n"
" base = 3;\n"
" }\n"
" row = (u8 *)(base + 4);\n"
" use(row);\n"
"}")
d15 = next(r for r in sc.scan_text(SINKFIX, "src/fx/s.c", shared_defs=None)
if r["form"] == "def" and r["name"] == "func_80100000")
got15 = sink_merges(SINKFIX, "src/fx/s.c", "func_80100000", d15)
if len(got15) != 1 or "sink" not in got15[0][0] or "base" not in got15[0][0]:
fail(f"R15 must find one sink in the three-arm fixture, got {[g[0] for g in got15]}")
else:
c15 = got15[0][1]
want15 = ["row = (u8 *)((1) + 4);", "row = (u8 *)((2) + 4);", "row = (u8 *)((3) + 4);"]
if not all(w in c15 for w in want15):
fail(f"R15 must sink the merge statement into every arm: {c15!r}")
if "s32 base;" in c15 or "base = 1;" in c15:
fail("R15 must delete the consumed variable's declaration and its per-arm assignments")
if c15.count("row = ") != 3 or "row = (u8 *)(base + 4);" in c15:
fail("R15 must remove the merge statement itself")
if "u8 *row;" not in c15:
fail("R15 must keep the declaration of the variable the merge statement ASSIGNS")
# control 1: a consumed variable read anywhere else is not sinkable (its census would not add up)
if sink_merges(SINKFIX.replace(" use(row);", " use(row + base);"), "src/fx/s.c", "func_80100000",
next(r for r in sc.scan_text(SINKFIX.replace(" use(row);", " use(row + base);"), "src/fx/s.c",
shared_defs=None) if r["form"] == "def")):
fail("R15 must refuse a variable that is read after the merge statement")
# control 2: a variable not set in EVERY arm is not sinkable
if sink_merges(SINKFIX.replace(" base = 2;\n", ""), "src/fx/s.c", "func_80100000",
next(r for r in sc.scan_text(SINKFIX.replace(" base = 2;\n", ""), "src/fx/s.c",
shared_defs=None) if r["form"] == "def")):
fail("R15 must refuse a variable one arm does not set")
# control 3: the brace walk itself — a `} else if (…) {` line nets to zero braces and must still close its arm
ch = if_chains([sc.mask_text(l) for l in SINKFIX.split("\n")], d15["line"], d15["end"] - 1)
if len(ch) != 1 or len(ch[0][2]) != 3:
fail(f"if_chains must see three arms in the fixture, got {ch}")
# the oracle's crash classification on its real message forms (R103)
if not oracle.SIGNAL_LINE.search("bash: line 1: 3845091 Done mipsel-linux-gnu-cpp ...\n 3845092 Aborted (core dumped) | tools/bin/gcc-2.7.2-psx/cc1 -quiet\n"):
fail("SIGNAL_LINE must match bash's job-status block")
@@ -82,15 +82,18 @@ FAMILIES = {
# lane B's map (`.run/P36/engine/residual_moves.md`, S101, gcc 2.7.2 source): the caller-saved swap is decided in
# local-alloc's block_alloc/combine_regs by which dying pseudo the operand ties to — the temp inlined/introduced first; a
# constant-operand commutative swap is undone by fold (fold-const.c) and only a var/var swap can reach the allocator.
"REG-caller": ("R6", "R8", "R5", "R10", "R12", "R14", "R13", "R3", "R7", "R9", "R2", "R4"),
# R15 (the sink) is the arm-scoped form of the same tie: a value set in every arm of an if/else chain is a CROSS-BLOCK
# pseudo local-alloc never gives a quantity, so the arm holds two quantities and takes block_alloc's unrolled case 2;
# sinking makes it three, and case 3 falls through into case 2 and undoes its own exchange (T7 agent a1, func_80156044).
"REG-caller": ("R6", "R8", "R15", "R5", "R10", "R12", "R14", "R13", "R3", "R7", "R9", "R2", "R4"),
# the s-bank order is global.c's allocno_compare (ref weight x live length), declaration order only on an exact tie
"REG-callee": ("R2", "R4", "R3", "R6", "R8", "R12", "R7", "R9", "R10", "R14", "R13", "R5"),
"REG-mixed": ("R6", "R2", "R5", "R10", "R4", "R3", "R8", "R12", "R13", "R14", "R7", "R9"),
"REG-callee": ("R2", "R4", "R3", "R6", "R8", "R15", "R12", "R7", "R9", "R10", "R14", "R13", "R5"),
"REG-mixed": ("R6", "R2", "R15", "R5", "R10", "R4", "R3", "R8", "R12", "R13", "R14", "R7", "R9"),
# a copy dies to cse's canon_reg or the local-alloc tie unless its destination changes MODE (the width); an address
# pseudo lives when a pointer local is used twice; a value named once is computed once; a short PARAMETER is extended in place
"COUNT": ("R12", "R14", "R6", "R8", "R3", "R7", "R5", "R13", "R9", "R10", "R2", "R4"),
"COUNT": ("R12", "R14", "R15", "R6", "R8", "R3", "R7", "R5", "R13", "R9", "R10", "R2", "R4"),
# statement order IS the schedule among equal-priority insns (rank_for_schedule's LUID tie-break); do-while is a barrier
"ORDER": ("R9", "R7", "R13", "R3", "R6", "R8", "R5", "R12", "R14", "R10", "R2", "R4"),
"ORDER": ("R9", "R7", "R13", "R3", "R6", "R8", "R5", "R12", "R14", "R10", "R15", "R2", "R4"),
"MIXED": dl.ALL_FAMILIES,
"OTHER": dl.ALL_FAMILIES,
}
@@ -839,8 +842,11 @@ def selftest():
c = classify(mine, tgt)
if c["kind"] != "REG" or c["bank"] != "caller" or c["score"] != 3:
fail(f"REG-caller classification wrong: {c}")
if family_key(c) != "REG-caller" or FAMILIES["REG-caller"][0] != "R6" or "R5" not in FAMILIES["REG-caller"][:3]:
fail(f"REG-caller family wrong: {family_key(c)} {FAMILIES['REG-caller'][:3]}")
# the temp move leads, and the two byte-proven caller-saved levers (R5 the commutative swap, R15 the sink) are drawn
# early — R15 joined the front at S102 when agent a1's crack showed the arm-scoped form of the same allocator tie.
if family_key(c) != "REG-caller" or FAMILIES["REG-caller"][0] != "R6" \
or not {"R5", "R15"} <= set(FAMILIES["REG-caller"][:4]):
fail(f"REG-caller family wrong: {family_key(c)} {FAMILIES['REG-caller'][:4]}")
# a callee-saved swap: addu s0,a0,zero vs addu s1,a0,zero
c = classify([_ins(0x00808021)], [_ins(0x00808821)])
if c["kind"] != "REG" or c["bank"] != "callee":
+1 -1
View File
File diff suppressed because one or more lines are too long
+184 -1
View File
@@ -2079,7 +2079,140 @@ def param_copies(text, tu, fn, d_):
return out
ALL_FAMILIES = ("R2", "R3", "R4", "R5", "R6", "R7", "R8", "R9", "R10", "R12", "R13", "R14")
def if_chains(masked, lo, hi):
"""[(chain_lo, chain_hi, [(arm_lo, arm_hi)])] — every brace-form if / else-if / else chain whose lines lie in
masked[lo:hi] at the body's own depth. `chain_hi` is the line index just past the chain's last `}`; an arm span is the
lines strictly inside that arm's braces. Depth is counted on the MASKED text, so a brace in a string or a comment is
not a brace. Chains that do not open a brace on the `if` line (a one-statement `if` without braces) are skipped: the
sink rewrites arm bodies and needs somewhere to put the statement."""
out = []
i = lo
while i < hi:
s_ = masked[i].strip()
if not (s_.startswith("if") and re.match(r"^if\s*\(", s_) and s_.endswith("{")):
i += 1
continue
arms, depth, arm_start, j = [], 0, i + 1, i
while j < hi:
nxt = masked[j].strip()
closes = masked[j].count("}")
# the CLOSES are counted before the OPENS: on a `} else if (…) {` line the two net to zero, and a depth
# counter that adds both at once never comes back to zero — the chain then looks like one unterminated arm.
if j > i and depth - closes == 0: # this arm's `}` is on line j
arms.append((arm_start, j))
m = re.match(r"^\}\s*else\b(.*)$", nxt)
if m and m.group(1).strip().endswith("{"): # `} else {` or `} else if (…) {`
arm_start, depth, j = j + 1, 1, j + 1
continue
if nxt == "}" and j + 1 < hi and re.match(r"^else\b", masked[j + 1].strip()) \
and masked[j + 1].strip().endswith("{"):
arm_start, depth, j = j + 2, 1, j + 2 # the `else …{` on its own line
continue
break
depth += masked[j].count("{") - closes
j += 1
if len(arms) >= 2:
out.append((i, j + 1, arms))
i = j + 1
else:
i += 1
return out
def sink_merges(text, tu, fn, d_):
"""[(description, candidate text)] — R15: the statement AFTER an if/else chain sunk into every arm, and the variables
it consumed deleted. `if (c) { v = e1; } else { v = e2; } w = f(v);` -> `if (c) { w = f(e1); } else { w = f(e2); }`.
T7 agent a1's crack of func_80156044 (2026-09-10, 130 bodies, the rank-1 head class six rung-G runs left at best 1).
The mechanism is a REGISTER move, not a scheduling one: a variable set in every arm and read after the merge is a
CROSS-BLOCK pseudo, so (i) local-alloc never makes a quantity for it (`local-alloc.c:472`, `next_qty` reset per block
at `:517`) and each arm holds two quantities, which is `block_alloc`'s unrolled `case 2` at `local-alloc.c:1499-1502`
— one `qty_compare` (`:1578-1596`), higher density first; sinking makes it a third block-local quantity and
`case 3` at `:1491-1496` FALLS THROUGH into `case 2`, applying that comparison a second time and undoing its own
exchange, so the two caller-saved colours swap; and (ii) while it is a global allocno it can inherit a copy
preference from whatever the merge statement's result is passed to (`set_preference` `global.c:1535+`, merged by
`expand_preferences` `global.c:781-825`, overriding first-fit at `global.c:1034-1067`) — sinking removes it from
`global.c` entirely. Read the whole reading in `.run/P36/agents/ov_SC04_011__func_80156044/mechanism.md`.
Applicability is checked, not assumed: every consumed variable must be assigned exactly once in EVERY arm by a simple
statement, must appear in the merge statement, and must occur nowhere else in the function (declaration + one
assignment per arm + its uses in the merge statement is its whole census) — otherwise the rewrite would change what
the code reads. The bytes remain the correctness proof (a rewrite that changes behaviour simply DIFFERS)."""
lines = text.split("\n")
masked = [sc.mask_text(l) for l in lines]
lo, hi = d_["line"], d_["end"] - 1
whole = sc.mask_text("\n".join(lines[d_["line"] - 1:d_["end"]]))
out = []
for c_lo, c_hi, arms in if_chains(masked, lo, hi):
j = next((k for k in range(c_hi, hi) if masked[k].strip()), None)
if j is None or not simple_stmt(masked[j]):
continue
merge_masked = masked[j]
eq = re.search(r"(?<![=!<>+\-*/%&|^~])=(?!=)", merge_masked)
if not eq: # only an assignment merges arm values
continue
rhs = merge_masked[eq.end():]
# every variable assigned exactly once, by a simple statement, in EVERY arm
per_arm = []
for a_lo, a_hi in arms:
got = {}
for k in range(a_lo, a_hi):
if not simple_stmt(masked[k]):
continue
m = re.match(r"^\s*([A-Za-z_]\w*)\s*=\s*(.+);\s*$", masked[k])
if not m:
continue
if m.group(1) in got: # assigned twice in one arm: not a single value
got[m.group(1)] = None
else:
got[m.group(1)] = k
per_arm.append(got)
common = set(k for k, v in per_arm[0].items() if v is not None)
for g in per_arm[1:]:
common &= set(k for k, v in g.items() if v is not None)
names_ = sorted(v for v in common if re.search(r"(?<![\w.>])%s(?![\w])" % re.escape(v), rhs))
if not names_:
continue
decl = {}
for v in names_:
# the variable's whole census: one declaration, one assignment per arm, and its uses in the merge statement
uses = len(re.findall(r"(?<![\w.>])%s(?![\w])" % re.escape(v), whole))
in_merge = len(re.findall(r"(?<![\w.>])%s(?![\w])" % re.escape(v), merge_masked))
d_line = next((k for k in range(lo, hi)
if is_decl_line(masked[k].strip())
and re.search(r"(?<![\w.>])%s(?![\w])\s*(?:=|;|,)" % re.escape(v), masked[k])), None)
if d_line is None or MULTI_DECL.match(masked[d_line]) or "=" in masked[d_line]:
decl[v] = None # a shared or initialised declaration: leave it standing
else:
decl[v] = d_line
if uses != len(arms) + in_merge + (1 if d_line is not None else 0):
names_ = None
break
if not names_:
continue
drop = {decl[v] for v in names_ if decl[v] is not None} | {j}
after = {} # line index -> the sunk statement to emit just after it
for a_idx, (a_lo, a_hi) in enumerate(arms):
stmt = lines[j].strip()
for v in names_:
k = per_arm[a_idx][v]
expr = re.match(r"^\s*[A-Za-z_]\w*\s*=\s*(.+);\s*$", lines[k]).group(1).strip()
stmt = re.sub(r"(?<![\w.>])%s(?![\w])" % re.escape(v), "(" + expr + ")", stmt)
drop.add(k)
tail = a_hi - 1 # the arm's LAST BODY line (a_hi is its `}`)
ref = lines[tail] if lines[tail].strip() else lines[a_lo]
after[tail] = ref[:len(ref) - len(ref.lstrip())] + stmt
cand = []
for k, l in enumerate(lines): # a dropped line may still be the one we append after
if k not in drop:
cand.append(l)
if k in after:
cand.append(after[k])
out.append((f"sink @{j + 1} ({','.join(names_)})", "\n".join(cand)))
return out
ALL_FAMILIES = ("R2", "R3", "R4", "R5", "R6", "R7", "R8", "R9", "R10", "R12", "R13", "R14", "R15")
RUNG_R_FAMILIES = ("R2", "R3", "R4", "R5", "R6", "R7") # the free sweep's set (R8/R9 are the search engine's until measured)
@@ -2184,6 +2317,9 @@ def recipe_candidates(text, tu, fn, names, limit=24, rng=None, cap=40, blocks=Tr
if "R13" in fam:
for desc, cand in reassociations(text, tu, fn, d_):
out.append(("R13", desc, cand))
if "R15" in fam:
for desc, cand in sink_merges(text, tu, fn, d_):
out.append(("R15", desc, cand))
if blocks and "R7" in fam: # last: one candidate per statement, so the targeted recipes go first
for desc, cand in block_wraps(text, tu, fn, d_):
out.append(("R7", desc, cand))
@@ -2894,6 +3030,53 @@ def selftest():
fail(f"remap_body produced {got!r} ({why})")
if remap_body(exb, exa, "void func_80300000(void) { D_80400000 = 0; }")[0] is not None:
fail("remap_body must refuse a sibling with a different token count")
# R15, the sink (T7 agent a1's crack of func_80156044, 2026-09-10): the merge statement pushed into every arm and the
# variables it consumed deleted. The three controls are the ones the applicability test exists for.
SINKFIX = ("void func_80100000(int c) {\n"
" s32 base;\n"
" u8 *row;\n"
"\n"
" if (c) {\n"
" base = 1;\n"
" } else if (c == 2) {\n"
" base = 2;\n"
" } else {\n"
" base = 3;\n"
" }\n"
" row = (u8 *)(base + 4);\n"
" use(row);\n"
"}")
d15 = next(r for r in sc.scan_text(SINKFIX, "src/fx/s.c", shared_defs=None)
if r["form"] == "def" and r["name"] == "func_80100000")
got15 = sink_merges(SINKFIX, "src/fx/s.c", "func_80100000", d15)
if len(got15) != 1 or "sink" not in got15[0][0] or "base" not in got15[0][0]:
fail(f"R15 must find one sink in the three-arm fixture, got {[g[0] for g in got15]}")
else:
c15 = got15[0][1]
want15 = ["row = (u8 *)((1) + 4);", "row = (u8 *)((2) + 4);", "row = (u8 *)((3) + 4);"]
if not all(w in c15 for w in want15):
fail(f"R15 must sink the merge statement into every arm: {c15!r}")
if "s32 base;" in c15 or "base = 1;" in c15:
fail("R15 must delete the consumed variable's declaration and its per-arm assignments")
if c15.count("row = ") != 3 or "row = (u8 *)(base + 4);" in c15:
fail("R15 must remove the merge statement itself")
if "u8 *row;" not in c15:
fail("R15 must keep the declaration of the variable the merge statement ASSIGNS")
# control 1: a consumed variable read anywhere else is not sinkable (its census would not add up)
if sink_merges(SINKFIX.replace(" use(row);", " use(row + base);"), "src/fx/s.c", "func_80100000",
next(r for r in sc.scan_text(SINKFIX.replace(" use(row);", " use(row + base);"), "src/fx/s.c",
shared_defs=None) if r["form"] == "def")):
fail("R15 must refuse a variable that is read after the merge statement")
# control 2: a variable not set in EVERY arm is not sinkable
if sink_merges(SINKFIX.replace(" base = 2;\n", ""), "src/fx/s.c", "func_80100000",
next(r for r in sc.scan_text(SINKFIX.replace(" base = 2;\n", ""), "src/fx/s.c",
shared_defs=None) if r["form"] == "def")):
fail("R15 must refuse a variable one arm does not set")
# control 3: the brace walk itself — a `} else if (…) {` line nets to zero braces and must still close its arm
ch = if_chains([sc.mask_text(l) for l in SINKFIX.split("\n")], d15["line"], d15["end"] - 1)
if len(ch) != 1 or len(ch[0][2]) != 3:
fail(f"if_chains must see three arms in the fixture, got {ch}")
# the oracle's crash classification on its real message forms (R103)
if not oracle.SIGNAL_LINE.search("bash: line 1: 3845091 Done mipsel-linux-gnu-cpp ...\n 3845092 Aborted (core dumped) | tools/bin/gcc-2.7.2-psx/cc1 -quiet\n"):
fail("SIGNAL_LINE must match bash's job-status block")
+13 -7
View File
@@ -82,15 +82,18 @@ FAMILIES = {
# lane B's map (`.run/P36/engine/residual_moves.md`, S101, gcc 2.7.2 source): the caller-saved swap is decided in
# local-alloc's block_alloc/combine_regs by which dying pseudo the operand ties to — the temp inlined/introduced first; a
# constant-operand commutative swap is undone by fold (fold-const.c) and only a var/var swap can reach the allocator.
"REG-caller": ("R6", "R8", "R5", "R10", "R12", "R14", "R13", "R3", "R7", "R9", "R2", "R4"),
# R15 (the sink) is the arm-scoped form of the same tie: a value set in every arm of an if/else chain is a CROSS-BLOCK
# pseudo local-alloc never gives a quantity, so the arm holds two quantities and takes block_alloc's unrolled case 2;
# sinking makes it three, and case 3 falls through into case 2 and undoes its own exchange (T7 agent a1, func_80156044).
"REG-caller": ("R6", "R8", "R15", "R5", "R10", "R12", "R14", "R13", "R3", "R7", "R9", "R2", "R4"),
# the s-bank order is global.c's allocno_compare (ref weight x live length), declaration order only on an exact tie
"REG-callee": ("R2", "R4", "R3", "R6", "R8", "R12", "R7", "R9", "R10", "R14", "R13", "R5"),
"REG-mixed": ("R6", "R2", "R5", "R10", "R4", "R3", "R8", "R12", "R13", "R14", "R7", "R9"),
"REG-callee": ("R2", "R4", "R3", "R6", "R8", "R15", "R12", "R7", "R9", "R10", "R14", "R13", "R5"),
"REG-mixed": ("R6", "R2", "R15", "R5", "R10", "R4", "R3", "R8", "R12", "R13", "R14", "R7", "R9"),
# a copy dies to cse's canon_reg or the local-alloc tie unless its destination changes MODE (the width); an address
# pseudo lives when a pointer local is used twice; a value named once is computed once; a short PARAMETER is extended in place
"COUNT": ("R12", "R14", "R6", "R8", "R3", "R7", "R5", "R13", "R9", "R10", "R2", "R4"),
"COUNT": ("R12", "R14", "R15", "R6", "R8", "R3", "R7", "R5", "R13", "R9", "R10", "R2", "R4"),
# statement order IS the schedule among equal-priority insns (rank_for_schedule's LUID tie-break); do-while is a barrier
"ORDER": ("R9", "R7", "R13", "R3", "R6", "R8", "R5", "R12", "R14", "R10", "R2", "R4"),
"ORDER": ("R9", "R7", "R13", "R3", "R6", "R8", "R5", "R12", "R14", "R10", "R15", "R2", "R4"),
"MIXED": dl.ALL_FAMILIES,
"OTHER": dl.ALL_FAMILIES,
}
@@ -839,8 +842,11 @@ def selftest():
c = classify(mine, tgt)
if c["kind"] != "REG" or c["bank"] != "caller" or c["score"] != 3:
fail(f"REG-caller classification wrong: {c}")
if family_key(c) != "REG-caller" or FAMILIES["REG-caller"][0] != "R6" or "R5" not in FAMILIES["REG-caller"][:3]:
fail(f"REG-caller family wrong: {family_key(c)} {FAMILIES['REG-caller'][:3]}")
# the temp move leads, and the two byte-proven caller-saved levers (R5 the commutative swap, R15 the sink) are drawn
# early — R15 joined the front at S102 when agent a1's crack showed the arm-scoped form of the same allocator tie.
if family_key(c) != "REG-caller" or FAMILIES["REG-caller"][0] != "R6" \
or not {"R5", "R15"} <= set(FAMILIES["REG-caller"][:4]):
fail(f"REG-caller family wrong: {family_key(c)} {FAMILIES['REG-caller'][:4]}")
# a callee-saved swap: addu s0,a0,zero vs addu s1,a0,zero
c = classify([_ins(0x00808021)], [_ins(0x00808821)])
if c["kind"] != "REG" or c["bank"] != "callee":