From 7b501ef8b0b611dd00bc10d22ef186913bd8b36f Mon Sep 17 00:00:00 2001 From: Christopher Williams Date: Thu, 24 Sep 2026 08:56:45 -0400 Subject: [PATCH] =?UTF-8?q?cookbook:=20findings=2058-62=20=E2=80=94=20the?= =?UTF-8?q?=20size-band=20amendment,=20the=20ceiling=20break,=20division?= =?UTF-8?q?=5Fcheck,=20the=20ASPSX=20oracle=20rule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Finding 58 AMENDS finding 41, which was Phase 10's headline result and drove every dispatch decision. The "1-in-12 for 200-800 B" comparison was taken on a queue that had never been attempted (5 of 427 rows above 244 B ever tried, 1.2%; three in an excluded class; both non-excluded attempts near-matched). The first row attempted above the ceiling matched. The band's measured yield is a function of the lever set at the time of measurement, and the lever set grows -- RE-MEASURE a band before concluding it is exhausted, and never treat a band as closed at ~1% attempt coverage. Findings 59-62: the local-aggregate row-stride/element-size lever that broke the ceiling (int t[3][4] not int t[9]; frame 48 vs 40; residual concentrated on the frame adjustment); the division_check trapped class; the localisation of the maspsx/GNU-as mutual exclusion with the developer's ASPSX-as-oracle-only rule; and the fail-fast validation of region override keys in sf3_merge. --- docs/MATCHING_COOKBOOK.md | 100 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/docs/MATCHING_COOKBOOK.md b/docs/MATCHING_COOKBOOK.md index 7e9a5a3..5c74f4f 100644 --- a/docs/MATCHING_COOKBOOK.md +++ b/docs/MATCHING_COOKBOOK.md @@ -919,3 +919,103 @@ that merely read one, so the count is a lower bound. - **One attempt on a named lever, then classify.** A harness classification backed by a proof is worth more than an assumption; the model case is `0x80023D40`, where cc1's duplicated `slti` in the `bgez` slot is **correct code**, so no source shape can prevent it. + +## Phase 11 — amendments and new findings (2026-09-24) + +### 58. AMENDMENT TO FINDING 41 (the size-band law) — the "1-in-12" figure was measured against a queue that had never been attempted + +Finding 41's headline measurement was "≤200 B yields at 1–2 attempts per row against 1-in-12 for +200–800 B", and it drove every dispatch decision in Phase 10. **Phase 11's Goal B census showed the +comparison was not what it appeared to be.** + +| | rows | ever attempted | +|---|---|---| +| >244 B | 427 | **5 (1.2%)** | + +Three of those five are the already-excluded trapping class. **Both** attempted rows that are not in an +excluded class reached **near-match** (4 and 5 differing bytes). The corpus's size profile — 456 of 493 +registered regions at ≤120 B, 0 above 244 B — is simply **a queue consumed from the small end**. + +**The first row worker D attempted above the ceiling matched** (`0x8009F6A0`, 248 B), setting a new +corpus maximum. So: + +- **The 244-byte "ceiling" was a dispatch artefact, not a measured wall.** Nothing about 248 B was + special. +- **The 1-in-12 figure was taken on rows attempted mostly *before* the length-class levers existed** — + cookbook 54's row-stride lever, the named-locals family, the two-arm branch family and the rest. A + band's measured yield is a function of the lever set *at the time of measurement*, and the lever set + grows. **Re-measure a band before concluding it is exhausted.** +- The "soft" verdict is a claim about **dispatch**, not a promise of yield: the two near-misses above the + ceiling still failed at 4–5 bytes, so per-row cost there is real. + +*What survives from finding 41:* small bodies are still cheaper per attempt, and a worker should still +start there. *What does not:* treating a size band as *closed* because nobody has matched in it. **A +band with ~1% attempt coverage has not been measured at all.** + +### 59. A local aggregate's row stride and element size are byte-load-bearing (extends finding 54) + +`0x8009F6A0` (248 B) needed **`int t[3][4]`, only columns 0..2 used** — not `int t[9]`. The 4-word row +stride moves the 2nd and 3rd triples to `0x10` and `0x20`, makes the frame **48 B instead of 40 B**, and +leaves the unused `0x0C`/`0x1C` slots the original shows. Four spellings: + +| spelling | result | +|---|---| +| nine `short` locals | 100 B LENGTH-MISMATCH — cc1 drops the sign extension (`lhu`+`subu`), no frame | +| nine `int` locals | 100 B LENGTH-MISMATCH — right signedness (`lh`), still no frame | +| `int t[9]` | 248 B, **19 differing bytes**, first difference **at the frame adjustment itself** | +| `int t[3][4]` | **MATCH** | + +**Diagnostic: correct length + right instruction multiset and order + residual concentrated on the FRAME +ADJUSTMENT and every sp-relative offset ⇒ suspect a local aggregate's row stride / element size, not the +control flow.** The element type is the other half of the lever — `short` locals let cc1 drop the sign +extension, `int` locals keep it. + +### 60. A new trapped class: compiler-generated division checks (`div` + `break`) + +`break` **never** appears without `div` and `div` **never** appears without `break` — 75 worklist rows, +0 exceptions. The shape is GCC's divmodsi4 with `MASK_CHECK_ZERO_DIV|MASK_CHECK_RANGE_DIV`: `div` / +`bnez`+`break 7` (zero check) / `li at,-1` + `bne` + `lui at,0x8000` + `bne quotient,at` + `break 6` +(range check) / `mflo`. **`break` cannot be produced from C.** + +**Measured disjoint from the matched corpus: 0 of 493 registered regions contains a div, a rem or a +break.** Same signature as finding 26's trapping class. + +Compiler matrix: every available cc1 either emits a **bare** div with no check (open 2.5.7–2.91.66-psx, +and Sony CC1PSX 4.0–4.5, which reject `-mcheck-zero-division`), or emits a check with a **different +shape** (open 2.95.2-psx and CC1PSX 4.6: `mflo` *before* the check, scratch `$3`/`$4`, comparing the +**divisor** against `0x80000000`; the original checks before `mflo`, uses `$at`, and compares the +**quotient**). Not reproducible with this toolchain — the route is a cc1 build with those masks in +`TARGET_DEFAULT`, a developer-owned toolchain decision. Implemented as the counted exclusion +`division_check` (81 rows). + +### 61. The maspsx / GNU-as mutual exclusion, localised — and the ASPSX oracle rule + +**ASPSX 2.56 does BOTH the `move`→`addu` conversion and the macro-expansion delay-slot fill.** maspsx +does the first only; GNU `as` reorder mode does the second only; and **the two cannot be combined** — +maspsx must be in noreorder to supply its own nops, while `as` needs reorder *from the function start* +and then orphans cc1's own slots. Measured on `0x800FA5D8` across five spellings: `maspsx=off` gives the +correct length with all four fills and breaks all four `move` copies (`or` instead of `addu`, one byte +each); `maspsx=noreordernop` gives the right length with 30 differing bytes; `.set reorder` from the +function start over-fills to 144 B (reproducing finding 40); a per-site `.set reorder` window stays at +148 B (confirming that a mid-function `.set reorder` does not re-enable the fill). + +Worker B measured **42 rows (14%) in one partition** with this signature, so roughly **~170 across the +worklist**. It is the *same* mutual exclusion that blocks the rare-epilogue class — **two phases of +evidence converging on one root cause**, and the strongest argument that one post-pass closes both. + +**THE ORACLE RULE (developer decision).** `tools/psyq/psyq4.0/psyq4.0/ASPSX.EXE` under `wibo` is the +*exact* assembler that built the original, and it is tempting to use it as the build stage. **Do not.** +It is a proprietary, git-ignored binary, so a build depending on it could not be reproduced by anyone +else — and reproducibility is the point of a matching decompilation. Phase 10 hit the same trap with +maspsx, and that fix worked *only* because maspsx is open source and patchable; ASPSX cannot be +redistributed. **Use ASPSX as a read-only diagnostic ORACLE to characterise the behaviour, then carry +that behaviour as tracked code.** + +### 62. Fail fast on an invalid region row + +A worker placed the source md5 in a claim row's 4th column. `sf3_merge` passed it through as a region +override, so the row **merged** and only `sf3_match gate` failed later with `unknown override key +'md5'`. **`sf3_merge` now validates override keys at merge time** and rejects the row with a message +naming the valid keys and pointing at `report.tsv` for per-claim metadata. The lesson generalises: +**validate at the earliest stage that can see the error**, because a late failure is diagnosed as a +gate problem rather than a claim problem.