mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-10-07 09:27:55 -04:00
docs(phase-29): §74 — pin-safety audit; the 5-pin behemoth draft is SAFE (open action #3)
- AUDITED .run/giants/s18_func_8017D960_b2.c (pins $25 $17 $19 $20 $21) WITHOUT recompiling: the SESSION-18 match_one object survives and cmp proves its t.c is this draft. - VERDICT SAFE. The corrupting form of the §72 hazard is a CALLER-SAVED pin ($25=$t9) whose live range spans a jal — gcc-2.7.2 does not save/restore an explicit-register variable across a call. Byte-checked: exactly 3 jal, all at 0x2c-0x50; first pin write at 0x58 => NO call after the pins are established. Corroborated by a token census of the C (every call-shaped token after line 270 is a file-local macro: gte_*, BOXTEST, ATTEN, CLAMP80). - The observed excess writes (2/3/3/5/5 vs 2 assignments each + 1 epilogue lw) are the BENIGN §72 mode: gcc using the pinned reg as scratch before the pinned value lands (lui/lw/addiu on $20, with addu t9,s4,zero routing r1's value out through it). Nothing live was clobbered. - cookbook §74: the reusable audit (objdump the surviving object; compare jal addresses against the first pin write; expect writes == assignments + 1 epilogue restore) + the standing rule — prefer a callee-saved register for any pin outliving a call; a caller-saved pin across a jal is a real wall verdict, not a drafting slip.
This commit is contained in:
@@ -5797,3 +5797,47 @@ compile-time; the emitted code is unchanged; nothing outside the draft is touche
|
||||
5. **Pick the isolation probe deliberately:** an overlay that instantiates the *return-casting* macros
|
||||
(`((s32 (*)(...))func_X)(...)`, §17a-1) is the only place a decl's return type could plausibly touch
|
||||
codegen. `ov_SC01_000` served that role for `func_8014F3E8`.
|
||||
|
||||
## §74 — Auditing a pinned draft: the §72 hazard is CALLER-SAVED pins spanning a call, and only the disassembly can tell you (Phase 29 SESSION-19, `func_8017D960` b2, 5 pins)
|
||||
|
||||
§72 established that a `register __asm__("$N")` pin is a **preference, not a reservation** — gcc will
|
||||
happily put another value in `$N` while your variable is notionally live. The banked corpus is safe
|
||||
from this by construction (the whole-binary byte-gate rejects any miscompile), but **un-gated drafts
|
||||
are not**, and a behemoth draft can carry pins for days before it ever reaches a gate. This is the
|
||||
cheap audit.
|
||||
|
||||
**Two distinct failure modes, only one of which is real most of the time:**
|
||||
|
||||
1. **CALLER-SAVED PIN SPANNING A CALL — the one that silently corrupts.** `$25` (`$t9`) and the
|
||||
`$t0–$t9` range are call-clobbered. gcc-2.7.2 does **not** save/restore an explicit-register
|
||||
variable across a call, so if the pinned variable's live range crosses a `jal`, the value is
|
||||
destroyed with no diagnostic. **Callee-saved pins (`$16–$23` = `$s0–$s7`) are immune** — the
|
||||
prologue/epilogue save/restore covers them.
|
||||
2. **SCRATCH REUSE OF THE PINNED REGISTER — usually benign.** gcc will use `$N` as a temporary for an
|
||||
unrelated value *before* the pinned variable's own value lands there. Observed here: `lui s4,..;
|
||||
lw s4,0(s4); addiu s4,s4,-128` — `$20` (pinned `r1lo`) carried the raw global for two insns, and
|
||||
`addu t9,s4,zero` copied that value out to `$25` (pinned `r1`) on the way. Self-consistent; nothing
|
||||
live was clobbered.
|
||||
|
||||
**The audit (no recompile needed if a `match_one` object survives — `.run/match/<fn>.<pid>/<fn>/t.o`;
|
||||
confirm it is the draft you think it is with `cmp t.c <draft>`):**
|
||||
```
|
||||
mipsel-linux-gnu-objdump -d <t.o> > dis.txt
|
||||
# (a) does any call exist after the first pin write?
|
||||
grep -nE '\bjalr?\b' dis.txt # compare addresses against the pin-write addresses
|
||||
# (b) how many times is each pinned reg WRITTEN? (first operand, excluding sw/branch/jal/mult-class)
|
||||
```
|
||||
Expect `writes == (assignments in the C) + 1 epilogue `lw` for each callee-saved pin`. Anything above
|
||||
that is scratch reuse — read those sites before assuming they are benign.
|
||||
|
||||
**Worked verdict (`.run/giants/s18_func_8017D960_b2.c`, pins `$25 $17 $19 $20 $21`):** 3 `jal`s total,
|
||||
all at `0x2c–0x50`, and the **first pin write is at `0x58`** — *no call after the pins are
|
||||
established*, so the caller-saved `$25` pin never spans one and mode (1) does not arise. Modes (2)
|
||||
sightings on `$20`/`$21`/`$17` are the benign scratch pattern above. **Draft is safe to keep building
|
||||
on.** (The C corroborates: everything after the three prologue calls is a macro — `gte_*`, `BOXTEST`,
|
||||
`ATTEN`, `CLAMP80` — not a function call. Verify that with a token census, not by eye: a 636-line
|
||||
behemoth hides a `jal` easily.)
|
||||
|
||||
**Standing rule of thumb:** prefer a **callee-saved** register for any pin whose variable outlives a
|
||||
call; if the target's register really is caller-saved and the value really does span a `jal`, the pin
|
||||
cannot express it — that is a genuine wall verdict, not a drafting slip.
|
||||
|
||||
@@ -3524,6 +3524,29 @@ conditional) · main-EXE/B9 + GLM/B6 + resident's 14 walls (P30) · behemoths B7
|
||||
propagation is worth its own gated pass (32+84 ins × 138 = 16,008 ins ≈ **+0.12pp** if it lands).
|
||||
Drafts: `.run/drafts-s18-widen/` (F3E8) + `.run/drafts-s18-widen2/` (D4C0, the param-axis variant).
|
||||
|
||||
- **✅ 2026-07-25 (SESSION-19) — §72 PIN-SAFETY AUDIT (open action #3): the 5-pin behemoth draft is
|
||||
SAFE, and the audit produced the general test (§74).** `.run/giants/s18_func_8017D960_b2.c` carries
|
||||
`register __asm__` pins on `$25 $17 $19 $20 $21`. **`$25` is `$t9` — CALLER-SAVED**, which is the
|
||||
only genuinely corrupting form of the §72 hazard (gcc-2.7.2 does not save/restore an
|
||||
explicit-register variable across a call, so a live range spanning a `jal` is destroyed silently).
|
||||
**Audited without recompiling** — the SESSION-18 `match_one` object survives at
|
||||
`.run/match/func_8017D960.2216347/func_8017D960/t.o` and `cmp` proves its `t.c` IS this draft.
|
||||
**VERDICT — mode (1) does not arise:** the object contains **exactly 3 `jal`s, all at `0x2c–0x50`,
|
||||
and the first pin write is at `0x58`** ⇒ *no call after the pins are established*. Corroborated in
|
||||
the C: every call-shaped token after line 270 is a macro defined in the file (`gte_*`, `BOXTEST`,
|
||||
`ATTEN`, `CLAMP80`) — checked by token census, not by eye, because a 636-line behemoth hides a
|
||||
`jal` easily.
|
||||
**Mode (2) IS present and is benign:** write-counts per pinned reg are 2/3/3/5/5 against 2
|
||||
assignments each (+1 epilogue restore for the callee-saved four). The excess is gcc using the pinned
|
||||
register as a SCRATCH before the pinned variable's own value lands — `lui s4,..; lw s4,0(s4);
|
||||
addiu s4,s4,-128` (`$20` carrying the raw `D_801CBC90` for two insns) with `addu t9,s4,zero`
|
||||
routing `r1`'s value out through it. Self-consistent; nothing live was clobbered.
|
||||
**⇒ the draft is safe to keep building on**, and the reusable test is cookbook **§74** (objdump the
|
||||
surviving `match_one` object; compare `jal` addresses against the first pin write; expect
|
||||
`writes == assignments + 1 epilogue lw`). **Standing rule:** prefer a **callee-saved** register for
|
||||
any pin whose variable outlives a call; if the target genuinely wants a caller-saved reg across a
|
||||
`jal`, the pin cannot express it — that is a real wall verdict, not a drafting slip.
|
||||
|
||||
> **🛑 SESSION-18 CLOSING CHECKPOINT (2026-07-25, Opus 5 @ High) — SUPERSEDES the earlier SESSION-18
|
||||
> block, which was written mid-session and is STALE (it still says "two searches in flight").
|
||||
> Fresh session safe here.**
|
||||
|
||||
Reference in New Issue
Block a user