diff --git a/docs/cookbook-index.md b/docs/cookbook-index.md index d63dabd83..745598a60 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 / 1087 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 / 1088 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. @@ -812,7 +812,7 @@ - **§362** — TWO TRAPS WHEN A CARVE MOVES A STUB INTO THE `-O0` TU (P31 S68; byte-proven, 6 fns / 2,547 ins across ov_MAIN_012 / ov_SC02_037 / ov_SC03_107) L31682 - **§388** — ★★★ — THE **-O0 COLOURING ORACLE**: simulate `stupid.c` instead of grinding spellings (P31 S69; main/func_80011380 proved a C-level WALL at 6) L32384 -### family propagation & sweeps (120) +### family propagation & sweeps (121) - **§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) L502 - **§11** — Cross-binary dedup & code-sharing (Phase 11 — "one match unlocks many") L927 @@ -934,6 +934,7 @@ - **§398** — ★★★ — `family_remap` CARRIES THE **SOURCE** TU's DECL ENVIRONMENT INTO A DESTINATION THAT ALREADY OWNS THOSE NAMES (P31 S69; measured 3 banked of 22) L32835 - **§3-B.** — THE SCHEDULER DIALS (the dominant residual family this wave) L33108 - **§408** — ★★★ — §406 REFUTED AS A SWEEP: THE SHAPE IS THE FAMILY, THE DISAGREEMENT IS THE DEFECT (P31 S71; 0 MATCH / 14 applied, 0 / 210) L33209 +- **§420** — ★★★ — A MULTI-CLUSTER SYMBOL REBASE, AND THE BARE-NAME DEDUP THAT HID THREE QUARTERS OF IT (P31 S71; 4 banked in 57 s) L33692 ### integration / TU plumbing (76) @@ -2762,6 +2763,7 @@ - **§417** — ★★★ — A REGISTER PIN CAN BLOCK `jump.c`'s SELECT COLLAPSE, AND UNPINNING THEN EXPOSES A `cse` SKIP-BLOCKS MERGE (P31 S71; byte-proven `ov_SC03_013/func_8017E6F4`, 182 ins) L33605 - **§418** — ★★★ — TWO LOOP-STRUCTURE LEVERS: MAKE THE SECOND INDEX A GIV, AND KEEP A TABLE ADDRESS UNFOLDED (P31 S71; byte-proven `ov_SC04_016/func_8017DF8C`, 184 ins, 32 → 0 in seven compiles) L33632 - **§419** — ★★★ — WHEN A PIN IS IMPOSSIBLE, WIN THE local-alloc DENSITY CONTEST INSTEAD (P31 S71; byte-proven `ov_SC01_000/func_8017DD04`, 297 ins) L33658 +- **§420** — ★★★ — A MULTI-CLUSTER SYMBOL REBASE, AND THE BARE-NAME DEDUP THAT HID THREE QUARTERS OF IT (P31 S71; 4 banked in 57 s) L33692 --- @@ -3861,3 +3863,4 @@ Notes routinely quote that as a section id. This table resolves it. Grep bait: ` | L33605 | §417 | ★★★ — A REGISTER PIN CAN BLOCK `jump.c`'s SELECT COLLAPSE, AND UNPINNING THEN EXPOSES A `c | | L33632 | §418 | ★★★ — TWO LOOP-STRUCTURE LEVERS: MAKE THE SECOND INDEX A GIV, AND KEEP A TABLE ADDRESS UNF | | L33658 | §419 | ★★★ — WHEN A PIN IS IMPOSSIBLE, WIN THE local-alloc DENSITY CONTEST INSTEAD (P31 S71; byte | +| L33692 | §420 | ★★★ — A MULTI-CLUSTER SYMBOL REBASE, AND THE BARE-NAME DEDUP THAT HID THREE QUARTERS OF IT | diff --git a/docs/matching-cookbook.md b/docs/matching-cookbook.md index 6784fba30..5a3e3cbdb 100644 --- a/docs/matching-cookbook.md +++ b/docs/matching-cookbook.md @@ -33688,3 +33688,40 @@ density ranking rather than the register.* Reference count is a dial (zero-byte length is a dial (where you place the barrier); local-alloc's ordering is arithmetic on the two, so compute it from the `-df`/`-dl` dumps and aim, instead of trying pins that a call-argument register structurally forbids. + +## §420 ★★★ — A MULTI-CLUSTER SYMBOL REBASE, AND THE BARE-NAME DEDUP THAT HID THREE QUARTERS OF IT (P31 S71; 4 banked in 57 s) + +**The shape.** One shared body (`func_8016AB6C`) lives in four overlays — `ov_SC03_107`, +`ov_SC07_007`, `ov_SC07_010`, `ov_SC07_011`. Every copy scored `match_one` closeness 0 and every copy +failed the gate with `undefined reference to D_8018D538`: the classic §171 stale-seed-symbol class, the +draft carrying the symbols of the overlay it was written for. + +**Defect 1 — `aprop_symfix` deduped the slate by BARE FUNCTION NAME (R48/§238).** A four-row slate +reported *"1 drafts audited"*. Three rows were silently dropped, and they were not redundant: **each +overlay needs a DIFFERENT rebase**, because each has its own target symbols. Keyed by `(binary, fn)`, +all four audit. This is the same defect class as `reloc_filter`'s `binof` (fixed earlier the same +session) and `gate_lane`'s homonym staging — three tools, one root. + +**Defect 2 — the delta rule allowed exactly ONE uniform cluster.** `STALE-DELTA` requires the sorted +zip to have a single `target − draft` delta, so it refused all four as AMBIGUOUS. The real structure +is **two clusters of two**, and it is identical in shape across all four overlays: + +| overlay | run 1 | run 2 | +|---|---|---| +| ov_SC03_107 | −0x7F64 ×2 | −0x5A650 ×2 | +| ov_SC07_007 | −0x74DC ×2 | −0x2E898 ×2 | +| ov_SC07_010 | −0x844C ×2 | −0x4E3A0 ×2 | +| ov_SC07_011 | −0xBD8C ×2 | −0x65D08 ×2 | + +Two data clusters, each moving as a block — exactly what one seed body copied into four overlays +produces. The single-delta rule is the special case of one cluster. + +**The safe generalisation.** Sort both symbol lists, zip positionally, split into runs of constant +delta, and **require every run to have ≥ 2 members** — a single-member run is a free guess, not a +cluster, and that is where a wrong symbol would come from. Filter to `D_8xxxxxxx` first: the draft's +`Ent_`/`Obj_`/`SVec_` typedef names and the target's `rand`/`jtbl_` carry the same vram-looking suffix +and are not data symbols. + +**Result: 4 banked in 57 seconds of gate time, zero drafting** — and all four had been sitting at +closeness 0 behind a symbol rename. **Verify after rewriting** (`match_one` again, all four still +MATCH) before gating; a rebase that changes the body is a rebase you got wrong. diff --git a/tools/aprop_symfix.py b/tools/aprop_symfix.py index d48ee9625..219f231a9 100644 --- a/tools/aprop_symfix.py +++ b/tools/aprop_symfix.py @@ -188,12 +188,19 @@ def main(): recs = [r for r in json.load(open(a.slate)) if isinstance(r, dict) and (r.get('fn') or r.get('name'))] skip = set(json.load(open(a.skip))) if a.skip else set() + # KEY BY (binary, fn), NEVER BY NAME (R48/§238). A slate legitimately carries the SAME function + # name for several binaries — one shared body copied into four overlays is the common case — and + # a name-keyed `seen` silently audited ONE of them. Measured S71: a 4-row slate for + # func_8016AB6C across ov_SC03_107/ov_SC07_007/010/011 reported "1 drafts audited", and the + # three unexamined rows each needed a DIFFERENT rebase because each overlay's target symbols + # differ. seen, rows = set(), [] for r in recs: fn = r.get('fn') or r['name'] - if fn in skip or fn in seen: + key = (r['binary'], fn) + if fn in skip or key in seen: continue - seen.add(fn) + seen.add(key) draft = r.get('draft') or f".run/aprop1/{fn}/{fn}.c" st, stale, asm_only = audit_one(fn, r['binary'], draft) rows.append(dict(fn=fn, binary=r['binary'], draft=draft, status=st,