mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-10-05 17:03:23 -04:00
docs: restore §462/§463, record S76 tooling in SETUP, add the gate-triage step to the playbook
Three gaps found by auditing instead of asserting. §462 and §463 were MISSING from the cookbook although their commits are ancestors of HEAD and added 37 and 34 lines. Same silent loss as §464, which I caught only because I happened to re-check the three sections I had just written. Both restored from their own commits; all of §460-§476 now verified present one by one. SETUP.md had no record of either new tool (R21). Added gate_main_parallel and sync_tu_decls, plus the oracle corrections a reader needs in order to re-judge older verdicts: the REORDER_TUS routing in match_one/rtu_match, the draw_waves --main no-op, the verbatim-draft refusals at three points, and the §179-C conversion guard. The playbook had nothing on what to do when a gate banks far less than it staged — which is exactly what happened this session. Added the triage step: probe first (CC1-FAIL 16 / DIFF 18 / MATCH 6 on main's 40), sync declarations for the plumbing class, hand self_decl_tu to cast_self_callers, and expect a cascade because every bank changes the declaration environment for the drafts that follow it.
This commit is contained in:
@@ -973,6 +973,30 @@ fills fast). Nothing is leaking — but the host does not get the memory back on
|
||||
| `tools/restage_matching.py` | rebuilds a gate plan from `recover_integration --probe-only` verdicts, keeping only drafts that compile-and-MATCH in their REAL TU | when a binary banks 0 and you suspect one bad draft is failing its siblings' shared build. **Caveat measured S71:** that probe compiles but never LINKS or CARVES, so its MATCH is not a bank prediction |
|
||||
| `tools/weave_sweep.py` | the §406/§408 derived-selector sweep: scores each open stub's stored drafts, classifies the `sw $ra` disagreement from the residual, applies the clobber only to WEAVE-SUNK, `--lever-all` is the ablation control | as the template for "price a class by its RESIDUAL, not its SHAPE" — the sweep itself is a measured null (§408) |
|
||||
|
||||
### Tools added / changed 2026-09-03 (S76) — R21 record
|
||||
|
||||
| tool | what it does | when you need it |
|
||||
|---|---|---|
|
||||
| `tools/gate_main_parallel.py` **(NEW)** | runs the REAL `gate_main` inside N git worktrees to discover which drafts pass, then hands the union to ONE authoritative `gate_main` in the real tree. Workers discover; only the final serial pass banks | a main slate large enough that serial bisection hurts. **Measured: one gate cycle is 16 s**, so MAX_STEPS=24 is ~6.4 min serial and ~90 s across four workers. Run `--negative-control` once per environment first |
|
||||
| `tools/sync_tu_decls.py` **(NEW)** | banks a draft the gate refuses by copying the TU's OWN `extern` line for whatever symbol the gate names, re-gating, and repeating | a draft that is byte-correct but rejected on a declaration conflict. Refuses `self_decl_tu` (use `cast_self_callers --sync-decls`) and refuses a NEAR up front, since syncing declarations makes a body COMPILE, never MATCH |
|
||||
|
||||
**Oracle corrections — re-read any verdict recorded before these:**
|
||||
* `match_one` and `rtu_match` now route the Makefile's `REORDER_TUS` (`800c2 800c2_2 800c2_3 800c3`)
|
||||
through `reorder_passthrough.py + as -O2`, the real build path. They previously modelled
|
||||
`maspsx + as -O1` and manufactured a phantom §182/§188 epilogue wall for every function in those
|
||||
four TUs. Same draft, `func_8005ECC0`: closeness **5 / 36 ins** before, **2 / 35** after.
|
||||
* `oracle_reorder.py`'s docstring no longer says the shape is unreachable — for those four TUs the
|
||||
second cell of its diagnostic IS the build path, so a 0 there means the draft will bank.
|
||||
* `draw_waves --main` **drew ZERO main functions** until this session (`bins` came from `src/*`
|
||||
DIRECTORIES and main has no `src/main/`), while printing a reassuring "refusing 49 LINKED subsegs".
|
||||
Coverage is now asserted: `--main` yielding no main stubs exits 4. Also `--redraw-open`, because
|
||||
the ledger records what was ATTEMPTED and a still-open stub is still work.
|
||||
* A **verbatim-asm draft** is refused at three points — `gate_main`, `harvest_verify`, and
|
||||
`api_agent.prior_draft` (which was OFFERING them as warm starts). 1,099 of the 704,375 `.c` files
|
||||
in the draft store are §265 bodies under ordinary `<fn>.c` names.
|
||||
* `verbatim_to_stub` refuses a **§179-C epilogue-less fragment** (no `jr $ra` of its own) — a stub
|
||||
there is an unbankable target. Census: of main's verbatim blocks, 37 have `jr $ra` and 100 do not.
|
||||
|
||||
**Two gating rules that are now enforced in code, not remembered:**
|
||||
* `parallel_gate` **REFUSES `main`** — main's extract rewrites the linker script, so an incremental
|
||||
gate is a false PASS (§414). Use `tools/gate_main.py`: baseline assert → one clean rebuild per
|
||||
|
||||
@@ -35636,3 +35636,74 @@ as a whole-block schedule difference. Unpinning `o`/`col`/`sh23`/`abr` fixed the
|
||||
*(Also: the 850 single-prim block written as an inline `addPrim` with block-local temps, and the
|
||||
function defined with the TU's typed prototype `(Obj_80021D38*, u8*, DVec_80021D38*, u8*)` to avoid
|
||||
the §41 declaration wall.)*
|
||||
|
||||
#### §462 — FOUR LEVERS FROM `main:func_80024054` (91 ins, 74/53/32 → 4)
|
||||
|
||||
**Source: the S76 agent on `func_80024054`.** Four independent unlocks, none previously recorded;
|
||||
the last one is the kind of rule that silently costs a whole attempt.
|
||||
|
||||
**1. `array[var - K]` folds K into the symbol's LO16 / `lhu` displacement.** Naming an intermediate
|
||||
`idx = var - K` does NOT stop it — the fold happens at the front end / in combine, before any
|
||||
register assignment you could steer. The only thing that defeated it was a zero-byte opacity barrier
|
||||
immediately after computing the index, one per use site:
|
||||
`__asm__ __volatile__("" : "=r"(idx) : "0"(idx));`
|
||||
Reach for this whenever the target loads from `sym+0` with a computed index and your build folds the
|
||||
constant into the displacement instead.
|
||||
|
||||
**2. The fused `sll 16; sra 15` sign-extend-and-scale wants the index declared `s16`, not `s32`.**
|
||||
This confirms §241's recipe on a fresh case — worth knowing it reproduces rather than being a
|
||||
one-off of that function.
|
||||
|
||||
**3. A mask-then-compare LOCAL causes a cross-jump merge AND flips branch polarity.** Writing
|
||||
`bits = val & 0xC000;` then `if (bits == …) else if …` merged two case tails into one shared block
|
||||
and emitted `bne`-polarity branches where the target has `beq`. Dropping the local and switching on
|
||||
the expression directly — `switch (val & 0xC000) { case 0x8000: … case 0xC000: … default: … }` —
|
||||
fixed both at once and reproduced the target's forward-`beq` shape. **The temporary was the defect**;
|
||||
this is the same family as §461's "laundering can be the defect", from the opposite direction.
|
||||
|
||||
**4. 🔴 A POINTER PARAMETER'S SIGNEDNESS DECIDES HOW `-1` IS MATERIALIZED.** Declaring `arg1` as
|
||||
`s16 *` rather than `u16 *` flips the fail-path constant from `ori $x, 0xffff` to `addiu $x, -1`,
|
||||
matching the target — because gcc-2.7.2 canonicalizes the RHS constant against the **lvalue's**
|
||||
signedness before choosing the load-immediate opcode. Nothing about the store's *value* changes, so
|
||||
this is invisible in the C and shows up only as a one-instruction opcode difference. If a residual is
|
||||
a lone `ori 0xffff` vs `addiu -1`, check the signedness of the pointer being written through before
|
||||
touching anything else.
|
||||
|
||||
**Left open:** one `DELAY-SLOT` residual — `addu $a3,$zero,$zero` is insn #0 in the target and lands
|
||||
in the branch delay slot in every C variant. Two independent prior attempts hit the same wall;
|
||||
five further variants (statement reorder, register pin, barriers either side of the load) each left
|
||||
it unchanged or traded it for an equal residual elsewhere. Permuter-class, Law 3.
|
||||
|
||||
#### §463 — 🔴 SPILL SLOTS ARE 8 BYTES, AND THE §41b "LOAD ABOVE THE PROLOGUE" WALL IS REFUTED
|
||||
|
||||
**Source: the S76 agent on `main:func_8001FC08` (400 ins, 33 → 0 MATCH).** Three laws, and the
|
||||
second one deletes a wall this file has been asserting.
|
||||
|
||||
**1. A 4-BYTE GAP IN AN OTHERWISE 4-PACKED FRAME IS A SPILL SLOT, NOT A PAD.** `sp+0xC8` / `sp+0xD0`
|
||||
in this target are not struct members — they are spilled pseudos. reload's `alter_reg` calls
|
||||
`assign_stack_local(mode, size, -1)`, and `align == -1` means `BIGGEST_ALIGNMENT` (8) with
|
||||
`CEIL_ROUND`, so **every 4-byte spill slot occupies EIGHT bytes**. That is exactly why the target's
|
||||
two slots sit 8 apart with `0xCC`/`0xD4` untouched. Reading those gaps as padding — or as fields of a
|
||||
struct you then invent — is a wrong model of the frame. Worth 11 instructions here, and modelling
|
||||
them as spills is also what evicts both values from local-alloc so reload picks `$t0`.
|
||||
|
||||
**2. §41b's "a global load cannot float above the RTL prologue" IS NOT A WALL — it is an `$a0`
|
||||
ANTI-DEPENDENCE.** The parameter copy `addu $s0, $a0, $zero` *reads* `$a0`, which pins the load
|
||||
below it. Get the value out of `$a0` and make the load the first statement, and it floats to idx 0
|
||||
on its own. **Both moves are required and either alone is worthless** — statement-first by itself
|
||||
measured 33 → **50** (worse); combined with law 1 (which is what frees the register) it went
|
||||
22 → 4. Before treating a "load above the prologue" residual as unreachable, check what reads the
|
||||
argument register.
|
||||
|
||||
**3. Which ARGUMENT POSITION a guard value is passed in decides its hard register.** Passing it as
|
||||
arg 1 — `func_80021120(&L.cnt, L.lp)` — gives that pseudo a `qty_phys_copy_sugg` toward `$a1`, which
|
||||
local-alloc's scan-from-`$v0` can never reach on its own. The sibling guards that do *not* pass it
|
||||
stay in `$v0`, which is the control proving the mechanism rather than a coincidence.
|
||||
|
||||
**Banker caveat for this function:** its `INCLUDE_ASM` is at `src/800.c:11168`, but the TU's own
|
||||
`MTX_80020248` typedef (`:11180`) and the `D_80074818`/`D_80075018` externs (`:11191-2`) are twelve
|
||||
lines BELOW it. Hoist that block above `:11168` or drop the draft's copy, or the duplicate typedef is
|
||||
a hard C89 error at bank time.
|
||||
|
||||
**Verified by hand (law 1c):** 26 `jal` targets and 16 HI16/LO16 relocs identical in name and order;
|
||||
the four `D_1F800020` words are the scratchpad literal, byte-identical (`3c111f80` / `26310020`).
|
||||
|
||||
@@ -433,6 +433,25 @@ said; a sibling `scratch_<fn>/` with candidates but no final draft is worth scor
|
||||
tier is exhausted, re-run its targets on the next tier ALONGSIDE the dying agents — never kill a
|
||||
running workflow to relaunch it differently.
|
||||
|
||||
### 6-S76. WHAT TO DO WHEN A GATE BANKS FAR LESS THAN IT STAGED
|
||||
|
||||
Measured S76: the overlay gate banked **1 of 38** and main's first pass **0 of 49** — and none of it
|
||||
was drafting quality. Classify before re-drafting anything:
|
||||
|
||||
1. **Run `recover_integration --probe-only --no-propagate`.** It compiles each stranded draft in its
|
||||
ACTUAL TU and splits them three ways. On main's 40: **CC1-FAIL 16 / DIFF 18 / MATCH 6**. Only the
|
||||
CC1-FAIL group is plumbing; the DIFF group is real residual and no declaration work will save it.
|
||||
**A `CC1-FAIL` says the declaration blocked COMPILATION — never that the body underneath is right.**
|
||||
2. **For a CC1-FAIL, run `tools/sync_tu_decls.py --binary main --fn F --draft D --apply`.** It copies
|
||||
the TU's own `extern` for whichever symbol the gate names, re-gates, repeats. Banked
|
||||
`func_8005EB28` in one round and `func_8005EC00` in two.
|
||||
3. **If it refuses with `self_decl_tu`**, the TU declares the function being banked, so the call
|
||||
SITES must change too — that is `cast_self_callers --sync-decls`, and the `--undo-journal --keep`
|
||||
afterwards is mandatory.
|
||||
4. **Expect a CASCADE.** Every bank gives its TU a real definition, which then contradicts the stale
|
||||
`extern` that every later draft in that TU still carries. A draft that was compatible before a
|
||||
bank can be incompatible after it — re-run the sync rather than concluding the draft went bad.
|
||||
|
||||
## 6. Gate — EVERYTHING PARALLEL. There is no serial lane.
|
||||
|
||||
> **P31 S74 — after a gate that CARVED, the binary's `asm/` is stale until a re-extract.** The
|
||||
|
||||
Reference in New Issue
Block a user