From cfa421eef26f2c853a812b4f5224666f99ea92c6 Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Fri, 24 Jul 2026 16:42:20 -0600 Subject: [PATCH] =?UTF-8?q?docs(cookbook):=20=C2=A766d=20=E2=80=94=20the?= =?UTF-8?q?=20permuter/reader=20loop,=20and=20what=20transfers=20between?= =?UTF-8?q?=20giants?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - The two halves of a 'not steerable from source order' note belong to different tools: the permuter closes INSN_LUID scheduler ties a hand sweep provably cannot enumerate; the reader closes semantic fixes the mutation set cannot invent. residual_class's bucket ([permuter] vs [structural]), already printed by match_one, is the free handoff signal; its profile feeds p16_permute --klass verbatim. - Diagnose from a byte-verified SIBLING, and test the naive reading first: deleting the redundant mask alone COLLAPSED the copy (100 vs 101 ins), which is what proved a distinct PINNED register was needed. - §66d-1: the LOOP transfers between giants, the PIN does not (the same move cost func_8014D820 285 mismatched). §66d-2: setup() wipes the scratch dir; the cleanup pkill was global. --- docs/matching-cookbook.md | 47 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/docs/matching-cookbook.md b/docs/matching-cookbook.md index ea39a9644..596c2cba2 100644 --- a/docs/matching-cookbook.md +++ b/docs/matching-cookbook.md @@ -5220,3 +5220,50 @@ functions at live 1–4. **The structural point:** wave fuel at high reach is **created by a per-overlay Ghidra-C prefetch, not found** — the cached pool is a consumable, and once a wave drains it the next wave's cost/benefit is computed against a pool that no longer exists. Re-measure the fuel before each wave, not the leverage. + +## §66d — The permuter⇄reader loop: alternate a random search with a byte-verified idiom, and let `residual_class` decide whose turn it is (Phase 29 SESSION-17) + +`func_80177940` (101 ins, reach-138) was preserved as close=5 with the note *"equal-priority +`birthing_insn_p` ties broken by INSN_LUID, **not steerable from source order** — swept all 6 assign +orders/pin combos"*. Both halves of that note were right, and it still banked, because the two halves +belong to different tools: + +| residual | who closes it | why the other cannot | +|---|---|---| +| 4-ins scheduler tie (INSN_LUID order) | **the permuter** (5 → 1, 900 s) | a hand sweep enumerates *source orders*; the tie is broken by internal LUIDs, which randomized decl/statement churn moves and a human cannot address | +| `andi $a2,$v0,0xf` vs `addu $a2,$v0,$zero` | **the reader** | the permuter's mutation set adds masks/casts; it does not invent "delete this mask AND hard-pin the destination" | +| the register/schedule fallout of that fix (6 left) | **the permuter** (6 → 0, 1800 s) | ditto the first row | + +**The handoff signal is free and already computed.** `tools/match_one.py` prints `residual_class`'s +bucket on every run: `[permuter]` → hand it back to the search; `[structural]` → stop burning CPU and +read it. Here it went `structural(OPCODE-MIXED)` → after the idiom fix `ADDRESSING [permuter] profile=cse` +→ MATCH. Feed that profile straight to `p16_permute --klass` (`permuter_weights.classify` accepts a +profile name verbatim). + +**Diagnose from a byte-verified SIBLING, never from first principles.** The fix came from +`func_801778A8` — same family, same nibble walk, already banked byte-identical — which writes +`nib = uVar1;` (a plain copy) between two hard-pinned variables after the identical `(x << 16) >> 28` +shift pair. **And test the naive reading first:** deleting the redundant `& 0xf` *alone* collapsed the +copy (100 vs 101 ins, 52 mismatched), because gcc then reused one register. That failure is what proved +the target needs a *distinct pinned* register — the mask was never the point. A residual of one +instruction can still be a two-part fix. + +### §66d-1 — What transfers between giants is the LOOP, not the PIN + +Applied to `func_8014D820` (304 ins, close=33), whose residual is the mirror image (target holds the +`lhu` results in `$v1`, keeps `$a0` live to fill the load-delay slot), the *same* move — pin the +reusable temp to the register the target uses — went **303 vs 304 ins, 285 mismatched**: `$v1` is needed +elsewhere in that function. §44's "each giant is its own class" holds at the level of the specific pin. +Its permuter run improved 33 → 27 and plateaued (seed kept for an ILS warm restart). Budget the loop +per giant; do not budget a pin. + +### §66d-2 — Two operational sharp edges + +* **`p16_permute.setup` wipes `.run/permuter//`.** Re-running a target destroys the previous run's + `output--*` dirs, best score included. Copy the best `source.c` out *before* re-running — the + score-1 candidate here survived only because it had been copied to a run-local path first. +* **`run_permuter`'s cleanup used a global `pkill -f permuter/run_masked.py`**, which matches every + concurrent run — so two permuters on a multi-core box silently killed each other the moment the first + timed out, with no error anywhere. Now scoped to the run's own scratch dir (it is in argv). This is + what makes grinding several giants at once safe, and it is likely why the giant queue had only ever + been run one at a time.