7.2 KiB
§454 — Rung D and rung R: taking a lever off a body that still has to compile to the same bytes (Phase 36 T6)
The residue after the mechanical de-lever campaign (rungs A–C) is 12,970 bodies in 1,804 text classes, each with at least one site the oracle called NEEDED. Two instruments work on it: rung D, decomp-permuter seeded with the LEVER-FREE body, and rung R, the cookbook's byte-neutral shape recipes applied mechanically. What follows is what S99 measured, including the part where the measurement was wrong.
A DISASSEMBLY LISTING IS NOT A TARGET. Two campaigns returned "0 of 16 exemplars" with a straight face. Both were the harness. The
permuter's target.o had been assembled from verbatim_target_s.py --gas, a listing regenerated from the ROM image — and assembling a
disassembly is a second toolchain with its own answers:
- objdump prints the PSEUDO-instruction
move rX,rYforaddu rX,rY,$zero(0x…21). GAS assemblesmoveasor(0x…25). In one 234-instruction function that is 24 wrong words, and nothing said so. - a listing's
%hi/%lopairs come back RESOLVED —lui $a1,0x801f/addiu $a1,$a1,-11600, no relocation — while every compiled candidate carriesR_MIPS_HI16/LO16against a symbol, and the masked scorer compares reloc operands.
Result: the scorer reported 28 for a body that is byte-identical, so score 0 was unreachable and every NO-MATCH was its own.
The control that names this in one line: seed the search with the body the tree already matches and read the base score. It must be
0. (delever_permute.py --positive-control.) Run it before believing any campaign's yield — a 0-yield run cannot otherwise be told from
a broken scorer (R40).
The fix is to stop assembling a listing: the target is the tree's own body compiled by the build's own tail into a one-function
object, so it carries the candidates' relocations by construction. The ROM listing stays the INDEPENDENT oracle that proves it —
match_one must call that body a MATCH before the search starts — which is what keeps the arrangement from being circular (R34/R56).
verbatim_target_s.py --gas now verifies itself too (assemble → disassemble → compare word by word → .word-patch or REFUSE); it is
the file pasted into decomp.me, and it was wrong for every function containing a move.
THE PROFILE FOLLOWS THE REGISTER, NOT THE SITE KIND. A needed pin on a CALLEE-SAVED register ($16–$23) is an allocation-order
residual and wants the regalloc profile. A pin on a CALLER-SAVED one ($2/$3/$4–$7) is not. func_80163EC8 pins $2, and its residual
is lw v1,68(s1); li v0,-33; and v0,v1,v0 against lw v0,68(s1); li v1,-33; and v0,v0,v1 — the operand ORDER of one &. The regalloc
profile weights perm_commutative 2.0; the cse profile weights it 40.0. Steering by "it was a pin, so regalloc" spends the whole budget
20× away from the only lever that closes it.
WHAT RUNG D ACTUALLY FINDS, AND WHY RUNG R THEN GETS IT FOR FREE. Every win is a small, nameable source shape:
| function | start (mismatched ins) | time | the whole semantic change |
|---|---|---|---|
func_80135D20 |
12 | 24 s | flag = 0; → do { flag = 0; } while (0); (perm_ins_block, RC-5 scope) |
func_80163EC8 |
8 | 314 s | uVar5 = *(s32 *)(p+0x44); … uVar5 & ~0x20 → the temp inlined at its use (perm_expand_expr, §501-R's S2 kill) |
The permuter's winner is machine-reprinted by pycparser (extra parens, ; statements, its own brace style), so banking it verbatim
trades a lever for a readability regression — which is exactly what this phase exists to prevent. Each win is therefore turned into a
mechanical recipe that produces the SAME bytes from a one-line diff in the real source. Rung R's generators, in trial order (targeted
first, blanket last), each one compile:
- R2 the formerly-pinned declarations permuted among their own lines · R4 one of them moved through the whole declaration run (§76/§501-R: the allocation order is the bank) · R3 an initializer split off its declaration, the assignment placed after the WHOLE run (C89 forbids a declaration after a statement — placing it after the last PINNED declaration compiles as K&R parameters and fails in a way that reads like a compiler bug)
- R5 the operand order of one commutative operator (the caller-saved lever above; needs no pinned declaration)
- R6 a local assigned once and read once, inlined at its use with its dead declaration removed
- R7 one statement wrapped in a block —
{ stmt; }tried beforedo { stmt; } while (0);, because the readable spelling should win when both hold
THE CLASS, NOT THE BODY. A win on an exemplar is worth its whole text class (the head of the draw is 80 classes of 134 copies), but a
ledger REPLAY cannot deliver it: the ledger replays a SITE SET, and a reshaped body is not one. delever.py --propagate TU FN does it by
address remap — the two old bodies are identical modulo func_/D_ tokens (that IS the class hash), so their tokens correspond one for
one and the map they define is applied to the new body, each sibling judged on its own objects. --apply-body records the before/after
body text in its ledger row because after the write it exists nowhere else.
THE SHAPE OF THE POPULATION (first 6 of 16 exemplars, all ov_SC04_011, 134-copy classes). Two closed, four did not, and the split is
the starting distance, not the site count: the closers started 8 and 12 mismatched instructions from the target; the four that did not
started at 70, 99, 105 and 131 — every one a body where removing a hand-placed instruction lever changed the instruction COUNT and
shifted everything after it (mine=103 ins, target=106). They still improved a long way (131→35, 105→18, 99→37, 70→24), so they are
seeds for a longer run or for T7's agents, not walls. --max-start N triages by that number instead of spending a search on it.
§454a — the recipe rung's real shape: replication, not discovery (S99, measured)
delever --recipes closed 134 of 134 bodies of the class rung D had just cracked, in 6.0 minutes, every one by the same
R7 do-while — and 0 of 300 residue bodies drawn by fewest-NEEDED-sites, at both --cap 40 (12,110 compiles, 4.6 min)
and --cap 400 (17,140 compiles, 6.0 min). The zero is not a budget: 17,140 compiles over 300 bodies is ~57 candidates per
body, so the cap was never binding — ten times the depth added 42% more candidates and closed nothing — and a by-hand check of
one body (src/800.c CdReadSectorReadyCB, one $18 pin) showed every candidate COMPILING and returning DIFFERS, not
COMPILE-ERROR. Nor was it the ordering: candidates are sorted by distance to the NEEDED site (the lever says where to look)
and the result was identical before and after that change.
The lesson for any mechanical-rewrite rung: it multiplies a shape you already have across the population that shares it — which is worth an enormous amount when a class has 134 copies — and it discovers nothing. Budget it as replication. The discovery budget is the search rung and the agents, and the flywheel is that every shape either of them finds becomes a recipe, after which the population that shares it is free forever.