From c22ef879e88fd5ed8b6f647ded0c31be2f8eec13 Mon Sep 17 00:00:00 2001 From: Christopher Williams Date: Thu, 24 Sep 2026 09:39:25 -0400 Subject: [PATCH] =?UTF-8?q?phase11:=20merge=2015=20+=20amend=20cookbook=20?= =?UTF-8?q?67,=20add=2082=20=E2=80=94=20524=20bodies=20/=20533=20regions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Worker D's 0x800910BC (280 B), its 8th match. TWO CORRECTIONS TO THE COORDINATOR'S OWN COOKBOOK ENTRY, both from measurement: - 67 was INCOMPLETE and cost worker D a spelling. The magic alone is AMBIGUOUS: D = 2^(32+s)/M where s is the shift of the sra after the mfhi. 0x2AAAAAAB is /6 at s=0, /12 at s=1, /24 at s=2, and worker D read it as /6 when the shift was 1. The corollary is worth having too: the same magic twice in one function is not a contradiction (0x66666667 serves both /10 at s=2 and /5 at s=1, materialised once into a callee-saved register). - The named-local rule is PER-SITE within one function. Naming a result the original consumes immediately costs 2 words; naming one the original reuses is free. Apply the decision once per VALUE, not once per function. That is now the fourth correction to coordinator work this phase, and every one came from a worker measuring something the coordinator had asserted. --- config/regions.tsv | 1 + docs/MATCHING_COOKBOOK.md | 26 +++++++++++++++++-- src/func_80069580.c | 54 +++++++++++++++++++++++++++++++++++++++ src/func_8007E7FC.c | 54 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 2 deletions(-) create mode 100644 src/func_80069580.c create mode 100644 src/func_8007E7FC.c diff --git a/config/regions.tsv b/config/regions.tsv index 19ad12d..0799abc 100644 --- a/config/regions.tsv +++ b/config/regions.tsv @@ -331,6 +331,7 @@ 0x80090C8C 0x80090CAC src/func_80090C8C.c 0x80090CAC 0x80090D00 src/func_80090CAC.c 0x8009107C 0x800910B0 src/func_8009107C.c +0x800910BC 0x800911D4 src/func_800910BC.c 0x800912D4 0x800912FC src/func_800912D4.c 0x800912FC 0x8009132C src/func_800912FC.c 0x8009132C 0x80091370 src/func_8009132C.c diff --git a/docs/MATCHING_COOKBOOK.md b/docs/MATCHING_COOKBOOK.md index 0d72ed3..f1d92c5 100644 --- a/docs/MATCHING_COOKBOOK.md +++ b/docs/MATCHING_COOKBOOK.md @@ -1138,8 +1138,16 @@ magic-number expansion of `(x << 12) / 27648`, where `0x4BDA12F7 = ceil(2^45 / 2 `mflo` for a genuine 64-bit multiply — probed on open `gcc-2.7.2-psx` and the real Sony `CC1PSX` 4.0 and 4.6. So the tell is checkable in one glance. -**Recover the divisor from the magic, never from the constant's face value:** -`D = ceil(2^(32+s) / M)`. Worker D burned four spellings writing the multiply before +**Recover the divisor from the magic AND the `sra` shift together — never from the magic alone** +(worker D's amendment, after it cost a spelling on `0x800910BC`): + + D = 2^(32+s) / M where s is the shift of the `sra` that follows `mfhi` + +The magic is **ambiguous on its own**: `0x2AAAAAAB` is `/6` at `s=0`, `/12` at `s=1`, `/24` +at `s=2`. Worker D read it as `/6` and was wrong — the `sra` after the `mfhi` is `1`, so the +divisor is 12. **Corollary: the same magic appearing twice in one function is not a +contradiction** — on that row `0x66666667` serves both `/10` at `s=2` and `/5` at `s=1`, and +cc1 materialises it **once** into a callee-saved register and reuses it for both. Worker D burned four spellings writing the multiply before solving for `D` and getting 27648 — which is also the second argument of all six `func_80010654` calls in the same function. Probe `int f(int x) { return (x << 12) / 27648; }` reproduces the triple exactly. @@ -1309,3 +1317,17 @@ negations then land immediately after their own loads. Same property as worker A's `buf[1]` finding in `0x80027CA0` (`buf[1] = a0[1]-a1[1];` then `buf[1] = 0;`), now confirmed on a much larger body. **Not a scheduler quirk — a second statement.** + +### 82. The named-local rule is PER-SITE within one function (worker D) + +The third distinct instance this session of the named-locals family deciding a row, and the first +where the rule applies **per-site inside a single function**. + +Worker D's `0x800910BC`: naming the second division's result (`r = func_800F6F60() % 10; if (r >= 6) ...`) +costs **exactly 2 words** — the original keeps the remainder in **v0** because it is consumed directly +by the `slti`, while the named local forces **a0**. But the **first** division's result *may* be named, +because it is used twice (`s1 = r + 6`), and there it lands in a0 in both compilations. + +**Rule: name a result only where the original REUSES it. Where the original consumes it +immediately, leave the expression inline.** Do not apply the decision once per function — apply it +once per value. diff --git a/src/func_80069580.c b/src/func_80069580.c new file mode 100644 index 0000000..a1beaa2 --- /dev/null +++ b/src/func_80069580.c @@ -0,0 +1,54 @@ +/* + * func_80069580 — 88 bytes at 0x80069580..0x800695D8 + * + * Byte-identical reconstruction of a framed teardown helper: it detaches an + * inner object through one callee, then — only when a second field is set — + * logs the owner through a second callee with a fixed format argument. + * + * The observed instructions are: + * addiu sp,sp,-24 + * sw s0,16(sp) + * move s0,a0 the argument is needed after the first call + * sw ra,20(sp) + * lw v0,8(s0) \ func_8002497C(*(int *)(*(int *)(a0 + 8) + 12)); + * nop | + * lw a0,12(v0) | + * jal 0x8002497C | + * nop / + * lw v0,12(s0) \ if (*(int *)(a0 + 12)) + * nop | + * beqz v0,0x800695C4 | + * move a1,s0 | (delay slot) the second argument + * lui a0,0x8007 | func_8006D1C4(&D_80068A64, a0, 0); + * addiu a0,a0,-30108 | + * jal 0x8006D1C4 | + * move a2,zero / (delay slot) + * lw ra,20(sp) + * lw s0,16(sp) + * addiu sp,sp,24 + * jr ra + * nop + * + * The first callee receives a **doubly indirect** word: a pointer loaded from + * `a0 + 8` is dereferenced again at +12, and only that value is passed. The + * second field is re-read from `a0` (not from the register) after the call, + * because the call may have rewritten the object — the reload is load-bearing. + * `0x80068A64` is reached with the symbol `la` form (`lui` + `addiu`), so it is + * an address-named symbol. + * + * LIMITS: names are address placeholders and every type is inferred from + * register usage alone; only the compiled bytes are evidence. The argument is + * modelled as `int *` because the body does word loads at +8 and +12 through it; + * the nested object's layout is not observable beyond the +12 field. The three + * callees' signatures are hypotheses from the registers each call site sets. + */ + +extern char D_80068A64; +extern void func_8002497C(int); +extern void func_8006D1C4(void *, int *, int); + +void func_80069580(int *a0) { + func_8002497C(*(int *)(*(int *)((char *)a0 + 8) + 12)); + if (*(int *)((char *)a0 + 12)) + func_8006D1C4(&D_80068A64, a0, 0); +} diff --git a/src/func_8007E7FC.c b/src/func_8007E7FC.c new file mode 100644 index 0000000..cdece0f --- /dev/null +++ b/src/func_8007E7FC.c @@ -0,0 +1,54 @@ +/* + * func_8007E7FC — 96 bytes at 0x8007E7FC..0x8007E85C + * + * Byte-identical reconstruction of a framed retry helper: it asks one callee to + * process an object with a fixed selector pair, and when that reports a negative + * result it retries the same call with a zeroed selector pair. + * + * The observed instructions are: + * addiu sp,sp,-32 + * sw s0,16(sp) \ s0 = a0; s1 = a1; + * move s0,a0 | + * sw s1,20(sp) | + * move s1,a1 / + * sw ra,24(sp) + * lw v0,28(s1) \ if (func_8007C664(a0, + * li a2,2 | *(unsigned char *)(*(int *)(a1 + 28) + 18), + * lbu a1,18(v0) | 2, 2) < 0) + * jal 0x8007C664 | + * li a3,2 / (delay slot) + * bgez v0,0x8007E844 \ func_8007C664(a0, + * move a0,s0 | *(unsigned char *)(*(int *)(a1 + 28) + 18), + * lw v0,28(s1) | 0, 0); + * move a2,zero | + * lbu a1,18(v0) | + * jal 0x8007C664 | + * move a3,zero / (delay slot) + * lw ra,24(sp) + * lw s1,20(sp) + * lw s0,16(sp) + * addiu sp,sp,32 + * jr ra + * nop + * + * The object is **re-read** for the retry (`lw v0,28(s1)` again, not the value + * kept in a register): the first call may rewrite it, so the second call site + * recomputes the same address expression — the "re-reads it → cc1 re-reads it" + * direction of cookbook finding 45. Both call sites pass the same `a0`, which is + * why only the callee-saved copy is used to restore a0 in the second call's + * branch delay slot. + * + * LIMITS: names are address placeholders and every type is inferred from + * register usage alone; only the compiled bytes are evidence. The second + * parameter is modelled as `char *` with raw offsets (+28 then +18) because only + * those two displacements are observable; the nested objects' real layouts are + * unknown. The first callee's result is only tested with `bgez`, so its width + * beyond the sign is unproven. + */ + +extern int func_8007C664(int, int, int, int); + +void func_8007E7FC(int a0, char *a1) { + if (func_8007C664(a0, *(unsigned char *)(*(int *)(a1 + 28) + 18), 2, 2) < 0) + func_8007C664(a0, *(unsigned char *)(*(int *)(a1 + 28) + 18), 0, 0); +}