diff --git a/docs/cookbook-index.md b/docs/cookbook-index.md index 9a3dfe2a7..849ef506c 100644 --- a/docs/cookbook-index.md +++ b/docs/cookbook-index.md @@ -2,7 +2,7 @@ > **Generated by `tools/cookbook_index.py` — do not hand-edit** (R33). Regenerate after adding a cookbook section. > -> `docs/matching-cookbook.md` is ~716 KB / 505 sections. Grepping it blind is how three P30 wave-1 agents each "discovered" an idiom that was already written down. **Start here, then read the section.** A section appears under every symptom it addresses. +> `docs/matching-cookbook.md` is ~716 KB / 506 sections. Grepping it blind is how three P30 wave-1 agents each "discovered" an idiom that was already written down. **Start here, then read the section.** A section appears under every symptom it addresses. **How to use:** name what you SEE in the diff (a stolen delay slot, an extra `la`, a swapped register pair, a `conflicting types` error), find that symptom below, read those sections first. If nothing fits, THEN grind — and add a section when you win. @@ -393,7 +393,7 @@ - **§163z** — THE UNVETTED REMAINDER (do not cite as law; each needs a dedupe pass) L11825 - **§3-The** — ADDRESS-CLASS TABLE: which load/store pairs even REACH the `/s` clause (P30 S48 wave 4, `func_80185B44`, ov_SC03_014) L14339 -### integration / TU plumbing (38) +### integration / TU plumbing (39) - **§8c** — Splitting a TU means rebuilding its DECLARATION ENVIRONMENT, not moving text (Phase 26 session 6) L437 - **§8d** — Templating a body INTO a TU must not CHANGE its declaration environment — demote the carried data externs (Phase 26 session 8, byte-proven on `func_8015AE2C` ×133) L483 @@ -433,8 +433,9 @@ - **§3-The** — same swallow, twice more, in the integration spine L9706 - **§3-A.** — Payload word0 is a global MODULE ID; code starts after the header L10524 - **§162** — THE LICM PAIR: what makes an address a movable AT ALL, and why the preheader order is the body order (P30 S47, `ov_MAIN_012`) L11140 +- **§166** — THE DESTINATION-TU ORACLE (P30 S48): the seven-attempt bug that was never codegen L14899 -### build graph, splat & the harness (107) +### build graph, splat & the harness (108) - **§4** — Flag/toolchain gotchas L190 - **Build** — mechanism — per-file opt override (splat resegmentation) L288 @@ -543,6 +544,7 @@ - **§162** — S48 WAVE-1 HARVEST (P30, 2026-08-11): the reach-ordered sibling campaign's first 12 targets L11011 - **§163** — S48 WAVES 2-3 HARVEST (P30, 2026-08-11/12): the five that were byte-probed and are actionable L11757 - **§165** — S48 WAVE-4 HARVEST (P30, 2026-08-12): banked the same day the wave landed L13686 +- **§166** — THE DESTINATION-TU ORACLE (P30 S48): the seven-attempt bug that was never codegen L14899 ### process, measurement & doctrine (71) @@ -1295,3 +1297,4 @@ - **§16Z** — SHARPENS *(sharpens §37 "the /s-DEP LATTICE", §136-13, §136-14, §16Xy, §162q, `gcc-2.7.2-map/sched.md` §64)* L14337 - **§3-The** — ADDRESS-CLASS TABLE: which load/store pairs even REACH the `/s` clause (P30 S48 wave 4, `func_80185B44`, ov_SC03_014) L14339 - **§165z** — REFUTED THIS WAVE: do NOT re-derive L14855 +- **§166** — THE DESTINATION-TU ORACLE (P30 S48): the seven-attempt bug that was never codegen L14899 diff --git a/docs/matching-cookbook.md b/docs/matching-cookbook.md index 7d668e2c2..4ef186064 100644 --- a/docs/matching-cookbook.md +++ b/docs/matching-cookbook.md @@ -14893,3 +14893,46 @@ Target shape — a subtract whose three registers are all specific: *Why:* Both halves fail. (1) NECESSITY, byte-refuted: replacing `if (t == 3) { goto sel_C94; }` with the duplicated body `if (t == 3) { sel = (u32)&D_801C3C94; goto set_sel; }` → MATCH 279. The 2-instruction `lui/addiu` + `j` tail clears §50-B's floor and jump.c merges it for you — this is precisely §164-69's 'a tail the compiler WILL merge must be duplic * **`func_8017F2D4`** — `s32 lt = mode < 0xA;` must be an explicit local because gcc-2.7.2 has no gcse and the single `slti` must precede the branch both arms need it after. *Why:* Byte-refuted by me: deleting the local and inlining the compare at both use sites — `if ((mode < 0xA) || (D_8011515A == 0x100))` and `if (!(mode < 0xA))` — gives MATCH 279. The C form is byte-INERT here, so it is not a lever and must not be taught as one. The mechanism phrasing is also imprecise: §164-52 byte-establishes that cse spans basic blocks + +--- + +## §166 — THE DESTINATION-TU ORACLE (P30 S48): the seven-attempt bug that was never codegen + +**§166a — THE SPLAT ASM SUBDIR *NAMES* THE DESTINATION TU. A PROSE CITATION THAT DISAGREES WITH IT IS +WRONG.** *(NEW. Nothing in §52b/§161c/§163a/§165-01 — the whole decl-conflict family — covers "you +are editing the wrong file". They all assume the TU is known.)* + + asm//nonmatchings//.s ⇒ the INCLUDE_ASM is in src//.c + +The third path component **is** the TU stem, derived from the split config. It is authoritative. + +**WHY THIS COSTS WAVES.** A `grep` for the function name also hits **callers** and **prototypes** in +OTHER TUs of the same overlay, and those hits read exactly like a destination hit. `func_8017F2D4` +was cited in five waves of notes as living in `ov_SC01_005_jr_8017C340.c` — that file holds only +`ret = func_8017F2D4(c, ret);` and a prototype. The real `INCLUDE_ASM` is in +`ov_SC01_005_jr_8017ED5C.c:3115`. Splicing into the wrong file is a **no-op**: the binary keeps its +`INCLUDE_ASM` bytes, the SHA differs, and the run is recorded as a gate refusal. + +**THE COMPOUNDING FAILURE — a guess printed as a finding.** `gate_stage` labelled every such refusal +`match_one MATCH but gate rejected (declaration/TU plumbing)`. That string is not a measurement; the +tool never checked for a declaration conflict. Seven attempts across five waves hunted codegen and +decl conflicts on a body that was **byte-correct from the first attempt**. Fixed: the message now +states only what is true (the two oracles disagree) and hands over this check first. + +**THE RULE.** For ANY "standalone MATCH / whole-binary DIFF" entry: **re-derive the TU path from the +asm subdir before hunting anything.** Then prove it in situ — splice into a private copy of the real +TU (one directory deep, with a `shared` symlink so `../shared/engine_core.h` resolves), run the +pinned triple end-to-end, and masked-diff your function out of the WHOLE-TU object. + +**TWO PROBE GOTCHAS** (both paid for in wave-5/6 agent time): +* the wrong `--aspsx-version` yields ~32 spurious mismatches **all** of the `ori`-vs-`addiu` li-form + shape — that uniformity is the fingerprint of a version mismatch, never a codegen residual. Use + `--aspsx-version=2.56 --expand-div`. +* a collateral-drift check must filter to symbols with a **real size** (`nm -S`): the + `*.NON_MATCHING` aliases are zero-size markers, so a masked diff falls back to the whole `.text` + and reports every one of them as drift purely because your function's bytes changed. + +*Byte evidence:* `func_8017F2D4` (ov_SC01_005 + ov_SC01_006, 279 ins). In-situ splice into BOTH real +TUs: 279/279, 0 masked diffs, cpp/cc1 clean; collateral check 71/71 other sized symbols identical. +The same agent corrected the family reach to **×2** (only two `.s` exist, byte-identical modulo the +overlay name) against a map that claimed ×5. diff --git a/tools/gate_stage.py b/tools/gate_stage.py index 0dc901e2b..0673f07ac 100644 --- a/tools/gate_stage.py +++ b/tools/gate_stage.py @@ -450,8 +450,19 @@ def _run_gate_locked(drafts, binary, src, asm, out, good_sha, propagate, source_ sm = re.search(r"//\s*@stuck:\s*(.+)", body) rclass = cm.group(1).strip() if cm else None # the worker's self-reported residual class note = sm.group(1).strip() if sm else None - if kind == "match": # match_one says MATCH but the whole-binary gate rejected -> plumbing/TU conflict - status, where = "near", note or "match_one MATCH but gate rejected (declaration/TU plumbing)" + if kind == "match": # match_one says MATCH but the whole-binary gate rejected + # ⚠ DO NOT NAME A CAUSE HERE. This used to read "(declaration/TU plumbing)" — a GUESS + # printed as a finding. `func_8017F2D4` carried that label through SEVEN attempts across + # five waves while every agent hunted codegen; the body was byte-correct the whole time + # and the real fault was that the notes named the WRONG DESTINATION TU (a file holding + # only a caller + prototype). No declaration conflict ever existed. A diagnosis the tool + # did not measure must not be stated as one — say what is TRUE (the two oracles disagree) + # and hand over the check that resolves it (P30 S48). + status, where = "near", note or ( + "match_one MATCH but the whole-binary gate rejected — CAUSE NOT DETERMINED. " + "FIRST re-derive the destination TU from the asm subdir: " + "asm//nonmatchings//.s => src//.c (the third path " + "component IS the TU). Only then look for a decl conflict or a codegen residual.") near += 1 # …and COUNT it. It was logged as `near` and counted as NOTHING, so a # run of 63 such drafts printed "banked 0, near 0, failed 0" — three # zeros that do not sum to 63, and nobody ever added them up (R32).