playbook §2b has called neighbor_ref the biggest measured cost lever in the
wave since S68 (~20x token swing) and documented it as a MANUAL per-card
command wired into nothing — so it ran for approximately zero cards. Packs now
carry an ALREADY-MATCHED NEIGHBOURS block, same additive never-fail contract as
the past-attempt notes. First run: 30/30 targets had a matched neighbour.
It also shipped with a defect that would have silently un-done it:
neighbor_ref reports the SYMBOL-TABLE name, and for an unnamed function that is
Ghidra's FUN_8003a0e4 — which appears nowhere in src/*.c, where the function is
func_8003A0E4. An agent sent to read FUN_8003a0e4 finds nothing and concludes
there is no neighbour. _src_name resolves against the destination TU's own text,
falls back to the address, and shows the symbol-table spelling in parentheses.
Measured: 150 of 150 neighbour names needed resolving; 0 primary names remain
Ghidra-style. Checked against known-true cases first (resolves FUN_8003a0e4,
leaves func_8003A0E4 alone, leaves an unknown name untouched).
R61(b): the pack was asserting a name true of the symbol table and false of the
world the agent works in.
wall_sweep --emit-exclude lists 9 fleet-wide; the list carried 5 of them.
func_8005D734, func_8005D8B4, func_8005ED4C and func_8005F450 each have a
%lo in a branch/jal delay slot — the second half of an assembler macro gcc
emits as ONE atomic insn, so no C can place it there. An agent handed one
returns a NEAR with an unexplainable tail, which is indistinguishable from a
hard function; the playbook measured a main wave spending 4 of 7 slots that way.
Drew ratified in-session after the S77 census: eight instrument defects, all one
shape — a tool asserting about a DRAFT what was true only of the HARNESS.
harvest_verify: verified 1 / failed 0, ef86fe1e403998a82ead42f4466ac4bc80f2c8d1
BYTE-IDENTICAL. The static probe had called this a `local_type' Blk16 conflict;
the real gate strips TU-provided typedefs and then named the true blocker.
harvest_verify named the step-2 signature exactly: `too few arguments to
function func_80182A00' at ov_SC01_084_jr_80182A00.c:534. 4 call sites cast.
Baseline green with NO draft substituted: ef86fe1e403998a82ead42f4466ac4bc80f2c8d1.
The residual was a two-instruction adjacent swap in the target's favour:
idx 49 MINE lh $a1, 0($sp) TARGET sra $a2, $v1, 16
idx 50 MINE sra $a2, $v1, 16 TARGET lh $a1, 0($sp)
Hand lever tried first and REFUTED by bytes: hoisting `a0 = a0 >> 16` above
the load is semantics-preserving (a0 is untouched in between) but scores
23 mismatched at 67/68 ins — it lets gcc fold an instruction away entirely.
permuter_ils --klass SCHEDULE reached score 0 on cycle 1. Its winning edit is
a clean C-level one: drop the `a1 = *(s16 *)sp;` temporary and inline the load
into both comparisons, which is what moves the sign-extend ahead of it.
gate_main: BANKED 1, 143dbb89f34491258bbc27810d0a12ec8b43a8dd BYTE-IDENTICAL.
T11 4/7, T12 13 banked of a 34-draft pool, T13 R22 213/213 twice (a green
baseline before the overlay banks and again after all 17).
main REAL 895 -> 899, stubs 46 -> 42. Fleet stubs 82 -> 65, distinct-code
99.3% -> 99.4%, MAIN game-code 57.1% -> 57.3%.
A §265 verbatim draft — the target's own asm in a file-scope __asm__ —
assembles to the bytes it was copied from, so BOTH of this tool's oracles emit
the strongest possible signal: static `none`, real cc1 `MATCH`. The routing
then reads "byte-correct body, nothing blocking it", the byte gate refuses it
for free, and progress.py moves by exactly zero.
Measured: of the 13 MATCH rows in the S77 overlay pool, SIX were verbatim
(md_MAIN_003 x3, md_MAIN_020, ov_SC05_005, ov_SC06_010). The whole 13-draft
cohort gated 0, and the probe had scoped it as the highest-value work
available.
S76 closed exactly this hole in gate_main, harvest_verify and
api_agent.prior_draft. This is the fourth consumer — and the one that SCOPES
the work, so it is the one whose blindness costs a session's plan. Uses the
gate's own detector (DP.is_verbatim_asm_draft) so the two cannot drift (R33),
and a VERBATIM row is excluded from the agreement arithmetic rather than
counted as a match.
NEGATIVE CONTROL (R39): md_MAIN_020's verbatim draft now reports
`verbatim_asm / VERBATIM (not a decompile)`; ov_SC04_018's two real-C drafts
still report `none / MATCH` exactly as before.
The same class that produced four banks in main, applied across the overlay
fleet. `blocker_probe` over all 26 binaries holding a stranded S76 draft found
11 whose blocker is `self_decl_tu`; harvest_verify named a twelfth
(ov_SC03_111:func_80181344, `conflicting types for func_80181344').
ov_SC01_005 func_8017FBCC ov_SC04_005 func_80185CEC
ov_SC01_006 func_8017FBCC ov_SC04_007 func_80182358
ov_SC02_041 func_801832F8 ov_SC04_011 func_8018985C
ov_SC03_105 func_8017F018 ov_SC05_003 func_80181720
ov_SC03_111 func_80181344 ov_SC05_018 func_80181294
ov_SC03_124 func_8018095C ov_SC04_002 func_80182CBC
35 edits, 0 refusals. cast_self_callers is binary-generic — only sync_tu_decls
is main-only — so the checkpoint's "extend it or drive recover_integration per
binary" needed neither.
Every one of the 12 binaries was baseline-checked with NO draft substituted and
all 12 build their locked SHA, so the casts move zero bytes fleet-wide, exactly
as they did in main.
Verified in-tree by harvest_verify, final SHA af117efbe4c0142d204bd243e41fd53e6ea5e350
BYTE-IDENTICAL.
Notable because parallel_gate had just reported `banked 0` for this exact
binary and this exact draft dir, in a 106s worker run — see the follow-up
investigation. The in-tree gate is the one that agrees with the bytes.
`cast_self_callers --undo-journal .run/S77_selfcast.json --keep
func_80013154,func_8005EAC8,func_8005E3AC,func_8005E79C` — reverted 6 edits
across 2 files, kept the 4 that banked.
The three reverted are func_80015608, func_80015760 and func_80039DEC, all
proven NEARs (closeness 3, closeness 9, and 8 differing bytes at 0x80039ded
respectively) — body residuals for the DIFF lane, not plumbing. Their §378
chain is one command to regenerate when a corrected body arrives.
main rebuilds 143dbb89f34491258bbc27810d0a12ec8b43a8dd after the revert.
round 1: D_80072978 -> extern s32 (*D_80072978)(void);
round 2: func_8005E804 -> extern void func_8005E804(u8 *arg0);
BANKED func_8005E79C after 2 declaration syncs
Round 1 is the ordinary extern path; round 2 is the definition path added in
commit:3807, and the draft could not have been reached without it. The same draft
had previously reported "no declaration conflict named" — that was the gate
refusing on a dirty tree, which is the misattribution the second fix removes.
The TU spells D_80072978 as a pointer-to-function; the draft had guessed `s32`
and cast at the use site. The TU's spelling is authoritative and the cast still
folds, so the bytes are unchanged.
Two defects, both found by driving the last two self_decl_tu drafts to a bank.
1. tu_decl looked only for an `extern … sym …;` line, so when the clashing
symbol is a function the TU DEFINES it stopped with
stopping: func_8005E480 clashes with the TU itself but src/800c3.c has
no `extern` line to copy.
though the authoritative spelling was in the definition's own header at
src/800c3.c:916. This was the terminal blocker of BOTH remaining drafts
(func_8005E3AC on func_8005E480, func_8005E79C on func_8005E804). The
definition is now preferred over an extern when both exist — it is the one
cc1 checks every other declaration against. Banked func_8005E3AC in one
round. Checked against known-true cases before being trusted: definition
path on func_8005E480/func_8005E804, extern path still verbatim on
func_8005D734, absent symbol still None.
2. gate_main refuses outright on a dirty src/ or a red baseline and never
reaches a per-draft opinion. The round loop matched neither DROP_RE nor
COMPILE_RE in that output and fell through to "no declaration conflict
named; stopping after 0 sync(s)" — reporting a HARNESS refusal as a property
of the DRAFT (R40). Measured on func_8005E79C, whose gate was refused
because the bank one command earlier had left src/ uncommitted. The refusal
is now surfaced and exits 3.
sync_tu_decls looked only for an `extern … sym …;` line, so when the clashing
symbol is a function the TU DEFINES it reported
stopping: func_8005E480 clashes with the TU itself but src/800c3.c has no
`extern` line to copy.
and gave up, though the authoritative spelling was sitting in the definition's
own header at src/800c3.c:916. That was the terminal blocker of BOTH remaining
self_decl_tu drafts. tu_decl now reads a definition header and renders it as an
extern, preferring it over an `extern` line when both exist — it is the one cc1
checks every other declaration against.
round 1: func_8005E480 -> extern void func_8005E480(void *arg0);
BANKED func_8005E3AC after 1 declaration sync
Checked against cases whose answer was already known before trusting it:
definition path OK on func_8005E480 and func_8005E804, extern path still
verbatim on func_8005D734, absent symbol still None.
progress.py main: REAL 897 -> 898, INCLUDE_ASM stubs 44 -> 43.
The first two banks off the `self_decl_tu` class: the TU declared the very
function the draft defines, with a different signature, so the draft could not
compile no matter how correct its body was.
func_80013154 src/800.c tu s32 (s32,s32,s32) | def s32 (s16,s16,s16)
func_8005EAC8 src/800c3.c tu void (void) | def void (void*)
func_80013154 was a §265 VERBATIM-ASM bank — it is now real decompiled C.
gate_main: 3-draft slate, bisected in 5 rebuilds, 2 banked,
143dbb89f34491258bbc27810d0a12ec8b43a8dd BYTE-IDENTICAL.
progress.py main: REAL 895 -> 897, INCLUDE_ASM stubs 46 -> 44.
Rejected by the byte gate, correctly, and handed to the DIFF lane:
func_80039DEC 8 differing bytes at 0x80039ded (a NEAR, not a plumbing miss)
func_80015608 sync_tu_decls refused up front: NEAR at closeness 3
`blocker_probe --binary main` over the 36 stranded S76 drafts classifies 7
whose blocker is `self_decl_tu` — the TU declares the very function the draft
defines, with a different signature:
func_80013154 src/800.c tu s32 (s32,s32,s32) | def s32 (s16,s16,s16)
func_80015608 src/800.c tu void (s32,s32) | def void (void*,u32*)
func_80015760 src/800.c tu void (s32,s32) | def void (Obj*,s32*)
func_80039DEC src/800_c.c tu void (void*,s16,u8) | def void (void*,s16,s16)
func_8005E3AC src/800c3.c tu void () | def s32 (Ctx*,s32)
func_8005E79C src/800c3.c tu void () | def s32 (void*,void*)
func_8005EAC8 src/800c3.c tu void (void) | def void (void*)
14 edits: each call site cast to a no-proto function pointer (§20 — gcc-2.7.2
folds the cast of a known function symbol back to a direct `jal`, so the
caller's bytes do not move), then the forward declaration synced.
Verified byte-neutral BEFORE any draft is substituted: main builds
143dbb89f34491258bbc27810d0a12ec8b43a8dd with these edits alone.
Committed ahead of the gate because gate_main `git checkout`s main's TUs
before substituting and would otherwise destroy these edits. Journal at
.run/S77_selfcast.json — `--undo-journal --keep <banked>` follows the gate.
`--sync-decls` copied the draft's parameter list verbatim into the TU. A draft
names types that are not in scope where the declaration sits, and both forms
of that broke the COMMITTED baseline build in one apply:
src/800.c:2631 extern void func_80015760(Obj_80015760 *obj, s32 *ot);
-> the type is draft-local; the TU has never heard of it
src/800c3.c:866 s32 func_8005E3AC(Ctx *s, s32 size);
-> `Ctx' is typedef'd at line 941, 75 lines BELOW the decl
src/800c3.c:866: parse error before `*'
src/800.c:2631: parse error before `*'
Caught by gate_main's BASELINE RED check with no draft substituted, so the
failure was attributed to the plumbing and not to seven innocent drafts.
THE FALLBACK FOLLOWS THE TOOL'S OWN DOCTRINE. Once the call sites are cast, the
declaration emits no code; it only has to be COMPATIBLE with the definition and
PARSE. `<ret> fn();` satisfies both without naming a type, and C89 6.5.4.3
makes it compatible with a prototyped definition exactly when no parameter is
affected by the default argument promotions. So the draft's own spelling is
still preferred — it is the byte-proven behaviour and it keeps the declaration
informative — and the no-proto form is used ONLY where that spelling cannot
parse at that line. Where it cannot parse AND a narrow parameter forbids
no-proto, the tool refuses loudly and names the type (R43).
NEGATIVE CONTROL (R39) over all 40 main recovery drafts: 68 edits before and
after, 65 byte-identical. The three that changed are exactly the declarations
naming an out-of-scope type — Obj_80015760, Ctx, and Slot54/Rec14 — and no
already-correct declaration is churned.
BASELINE PROOF: with all 14 plumbing edits applied and NO draft substituted,
main builds 143dbb89f34491258bbc27810d0a12ec8b43a8dd — byte-identical. The
casts move zero bytes, as the §20 fold predicts.
`blocker_probe --binary main` over the 36 stranded S76 drafts classifies 7
whose blocker is `self_decl_tu` — the TU declares the very function the draft
defines, with a different signature:
func_80013154 src/800.c tu s32 (s32,s32,s32) | def s32 (s16,s16,s16)
func_80015608 src/800.c tu void (s32,s32) | def void (void*,u32*)
func_80015760 src/800.c tu void (s32,s32) | def void (Obj*,s32*)
func_80039DEC src/800_c.c tu void (void*,s16,u8)| def void (void*,s16,s16)
func_8005E3AC src/800c3.c tu void () | def s32 (Ctx*,s32)
func_8005E79C src/800c3.c tu void () | def s32 (void*,void*)
func_8005EAC8 src/800c3.c tu void (void) | def void (void*)
14 edits: each call site cast to a no-proto function pointer (§20 — gcc-2.7.2
folds the cast of a known function symbol back to a direct `jal`, so the
caller's bytes do not move), then the forward declaration synced to the
draft's own spelling, which emits no code once the sites are cast.
Committed ahead of the gate because gate_main `git checkout`s main's TUs
before substituting and would otherwise destroy these edits. Journal at
.run/S77_selfcast.json — `--undo-journal --keep <banked>` follows the gate.
`return func_X(a0, a1);` has the exact shape of a forward declaration —
leading identifier, name, parenthesised argument list, `;` — so every
permissive "<type> <fn>(...);" regex in this tool read that CALL as a
DECLARATION. One misclassification, three consumers, two opposite failures:
* `is_declaration` -> `cast_sites` SKIPPED the call site, leaving the
caller's bytes exposed to the synced (narrowed) prototype.
* `sync_decls` -> REWROTE the whole statement into a declaration,
silently deleting the function's `return`.
* `DEF_RE` -> read the same line as "the definition itself", and
in `draft_signature` could have handed back ret="return".
Witnessed on a dry run before anything touched src/:
src/800.c:713
- return func_80013154(a0, a1, a2);
+ s32 func_80013154(s16 x, s16 y, s16 step);
One shared `_kw_prefixed()` guard, called from all three sites (R33 — the
guard lives in one place, never duplicated into three regexes).
BLAST RADIUS: 524 `return func_X(...);` lines across 482 files fleet-wide.
AUDIT: no past journal records a keyword-prefixed `before`, so no committed
source was corrupted by this.
NEGATIVE CONTROL (R39), old vs new over all 40 main recovery drafts:
68 edits each, 67 byte-identical, zero false positives. The single
difference is the defect itself — the corrupting declaration-rewrite
replaced by the correct cast:
- return func_80013154(a0, a1, a2);
+ return ((s32 (*)())func_80013154)(a0, a1, a2);
Written for a fresh session: what banked, what did not and WHY (classified by
compiling each stranded draft in its real TU, not guessed), the five tool
defects and their fixes, the four refuted walls and the one proved, and the
five things I got wrong so the next session does not inherit them.
Headline state: REAL 895 (was 882), main game-code 57.1% instruction-weighted
(was 55.8%), 46 open stubs in main and 82 fleet-wide, 143dbb89 byte-identical.
R22 clean-fleet NOT run since the banks.
Next four tasks are logged with their evidence and their traps.
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.
Two gaps found by running it over all 16 candidates.
It only parsed the slate-load 'DROP … clashes with …' path, so five drafts
whose conflict surfaced AFTER the build as 'COMPILE conflict on `SYM'' looked
unrecoverable when they were the same class one symbol deeper. Both forms are
read now.
And a CC1-FAIL classification says the declaration blocked COMPILATION, never
that the body underneath is right: five candidates compiled once synced and
then failed the byte gate because they were NEARs (closeness 12-89) all along.
It now scores the body first and refuses a NEAR, so a gate is not spent
learning what match_one already knows (R37).
That check had the §238 bug it exists to prevent — I called match_one without
--asm-subdir, so it defaulted to asm/resident/nonmatchings/resident, judged a
DIFFERENT function, returned no verdict, and let the NEAR through. The subdir
now comes from the stub oracle. Caught only by controlling the guard against
a case whose answer I already knew.
Controls: closeness-12 draft REFUSED as a NEAR; func_80013154 still refused as
self_decl_tu with the correct redirect.
The dominant reason a byte-correct draft does not bank is not codegen: the
draft and its destination TU spell a shared symbol differently and gcc-2.7.2
rejects the redeclaration. gate_main's pre-check already NAMES the symbol and
which side it kept, and the TU holds the authoritative spelling — so the fix
needs no judgement. Copy the TU's extern line verbatim into the draft,
re-gate, repeat.
Done by hand this session it banked func_8005EB28 in one round and
func_8005EC00 in two, both stuck across multiple slates, both byte-identical
after. The conflicts are typically a CASCADE: banking one function gives the
TU a real definition that then contradicts the stale extern every later draft
in that TU still carries.
Refuses the self_decl_tu class loudly (the TU declares the function being
banked, so the call SITES must change too — that is cast_self_callers
--sync-decls), and refuses any binary but main, whose gate is the one that
names the symbol (R43).
Controls: on an already-banked function it reports no conflict rather than
claiming a bank; on func_80013154 it refuses with the right reason. The byte
gate remains the sole arbiter — every round ends in a real gate run.
Same §378 class as func_8005EB28, two symbols deep: the draft declared
D_800729DC as void* where the TU says u32, and D_80072974 as void(*)()
where the TU says void(*)(void*). Copying the TU's own extern verbatim for
each, in the order the gate named them, banked it in two rounds.
143dbb89 BYTE-IDENTICAL.
The loop is the §378 chain's essence: ask the gate which symbol conflicts,
copy the TU's declaration into the draft, re-gate, repeat. No judgement
required — the gate names the symbol and the TU holds the authoritative
spelling.
A CASCADE, not a new defect: banking func_8005DE78 earlier this session gave
src/800c3.c a real definition at :690 with signature s32 (s32, s32). The
func_8005EB28 draft still carried extern void func_8005DE78(void *, s32) from
when the callee was a stub, so the TU and the draft now contradicted each
other and the gate refused with a compile conflict.
Fix is the §378 shape by hand: drop the redundant extern (the TU's own
definition is above the splice point) and cast the two call sites to the
banked signature. 143dbb89 BYTE-IDENTICAL.
The general point: every bank CHANGES the declaration environment for every
later draft in the same TU. A draft that was compatible before a bank can be
incompatible after it — the same shape as the rescan-twins-after-every-bank
rule, applied to declarations instead of the twin graph.
The recover_integration probe compiles each stranded draft in its ACTUAL TU
and reported 6 of main's 40 blocked drafts as MATCH there — i.e. not
integration problems at all, just casualties of batch-internal conflicts in
the earlier slates. Gating those 6 alone banked 2 (143dbb89 byte-identical).
The remaining 4 are a finding in their own right: gate_main's resolve_conflicts
pre-check dropped them while real cc1 accepts them. Gating them individually
next.
BANKED 9 of 28 after bisection, 143dbb89 BYTE-IDENTICAL. Verified from the
SOURCE (every stub gone), not from the gate's own count.
main 509 ins the game's entry point
func_800226C0 670 ins the largest function in the project
func_800215F4 465
func_800623A4 36 · func_80062434 36 · func_8005D410 42
func_8005D4B8 14 · func_8005D4F0 18 · StopRCnt 13
Reached by iterating the gate and dropping the compile-conflict culprit it
named each round: func_8005E79C, func_8005E3AC, func_8005EAE8, func_8001FC08.
Each of those is a §376/§378 declaration conflict, not a bad body — they go to
the recovery chain, not the bin.
Several were only reachable because of this session's oracle fixes: the 800c3
functions had been recorded as §182/§188 epilogue walls by an oracle modelling
maspsx + as -O1 for a TU the Makefile builds through reorder_passthrough +
as -O2. func_800226C0 came from the §476 finding that a hard-register pin
strips nonzero_bits and reg_n_sets==1.
From the S76 Fable agent on func_800226C0 — 670 instructions, the largest
function in the project, matched at closeness 0.
Explains WHY pins so often hurt, completing the arc of §461/§462/§471:
(a) A pinned hard register carries no nonzero_bits, so combine cannot fold
sext(HImode t) into a copy — which is exactly what the target's 228E4
addu/beqz/addu chain is, with cse2 reusing it as the loop multiplier.
The $18 pin that looked obvious was what prevented the fold; one plain
uninitialised s16 t (mul left an unpinned pseudo) unlocked it.
(b) A pin makes reg_n_sets != 1, so birthing_insn_p refuses the §199-A
boost and the value is placed first — a whole-block schedule shift.
Unpinning o/col/sh23/abr fixed the prologue order and two ties.
Rule: if a residual involves a sign/zero-extend fold or a first-in-block
placement, REMOVE pins before adding them.
From the S76 func_8002FF0C agent (166 ins -> MATCH, verified in-TU with a
spliced src/800_b.c compiling rc=0 and all 63 relocs matching).
__asm__ __volatile__("" ::: "memory") is a CSE MEMORY-TABLE invalidator, not
only a scheduling fence, and the colon-less __asm__("") does NOT substitute:
it forces D_800A46D2 to be re-read rather than folded to sign_extend(r), and
without it the function is exactly two instructions short. Pairs with §464
lever 4 — same two spellings, register half there, memory half here.
Write (b*3)<<3, not b*24: expand_mult never honours its target, so b*24
leaves a move copy that survives into the join block and costs a sixth
callee-saved register plus a 0x30 frame. A top-level LSHIFT_EXPR expands into
the variable's own pseudo. General for any constant multiply factoring as
odd<<n.
Independently confirms §470's 'two distinct locals for the same b*24' on a
different function via a different agent — treat as established.
And the house array spelling can be the defect: D_800A46D2 must be scalar at
block scope; extern s16 D_800A46D2[] forces la for both accesses and costs 12
mismatches. A fleet-consensus declaration is a prior, not a law.
From the S76 func_80011380 agent, which upgraded an empirical closeness-6
plateau to a floor proved from the gcc sources in tools/reference/.
The target needs MULT(MULT(i,2),2) unmerged, but fold-const.c:882 split_tree
decomposes any MULT whose op1 is TREE_CONSTANT — all 20 spellings measured
collapse to one sll 2, and STRIP_NOPS eats NON_LVALUE_EXPR so the usual |0
+0 *1 &~0 ^0 >>0 shields cannot protect it.
Both escapes cost an instruction, each for a named reason: a stmt-expr gives
the exact 5-insn RTL but its BLOCK_END note breaks the adjacency that
stupid.c:497-508 needs for a copy to conflict with its source, so the copy
self-coalesces and final.c deletes it; and (t = i*2)*2 with register s32 t
reaches exact length and shape but expand_decl's zero-byte (use) brackets
make t the longest interval, seizing $v0 and rotating the register ring.
Clinching fact that the target has no variable there: its 4th insn
sll $v1,$a0,1 reads $a0, not insn 2's dest.
Bonus: expand_binop allocates the PLUS dest before force_reg'ing the symbol,
so the symbol pseudo loses stupid_reg_compare's tie-break — that is the
la-on-$a0 colour.
Recorded as the TEMPLATE for a wall claim: name the pass, cite file and line,
measure each escape, and give the byte fact ruling out the alternative. A
wall asserted without that is a belief (§473).
From the S76 agent: 324 -> 89, from a 20-attempt LENGTH-DRIFT/-33 wall to -2.
The interleaved sw/def prologue this file cited as proof of hand-written
assembly is ordinary gcc-2.7.2 MIPS RTL.
Moved by §30's /s-dep lattice (plain scalar sxy stack locals + COMPONENT_REF
packet stores through a POLY_G4/LINE_G2 struct pointer), un-cached
*(s32*)(c+0xB) reloads, and a recomputed OT pointer.
Fourth wall refuted this session, after the §182/§188 reorder oracle, §41b's
prologue hoist (§463) and the S75 nine — three of the four were recorded as
properties of the CODE and were properties of an instrument or a model.
Manifest consequence: this function's UNCERTAIN row resolves toward
decompilable, not PERMANENT-VERBATIM; converting it to a stub was correct and
it belongs in the drawable pool.
From the S76 func_8001EA14 agent (371 ins, 349/303 -> 89, length exact),
cracked with cc1 -dL.
The loop.c hoist threshold is call-dependent: 29 when the loop contains a
call, not the 58 this file has been quoting. And the inputs are not what
their names suggest — savings is the COUNT of matched movables, lifetime is
their SUM. Anyone applying §148-A to a loop with a call has had the wrong
constant.
MEM_IN_STRUCT_P runs both ways: §469 set it to unblock hoisting, here it must
stay CLEAR (plain casts, not a struct) to reproduce the target's alias-blocked
schedule. Decide which direction the target needs first.
The COND_EXPR 'X ? A op B : A' singleton fold is escaped only by making the
arms structurally different TREES, not merely different values.
Spill slots follow DECLARATION order — completing the frame model with §463
(8-byte rounding), §469 (layouts only a declared local can give) and §471
(the §172 USE-orphan): a slot nothing reads is a spill or an orphan, never
padding.
From the S76 func_80032A74 agent (422 ins, 408 -> 12).
Refines §153: the launder was necessary but created an allocno outranking the
value it was protecting; the cure was pinning the launder itself to $10 — and
NOT $8, which evicts reload's $t0 parameter reloads. So '§461: the launder is
the defect' has a third resolution beyond remove-it or move-it: pin it, and
choose the register with reload's own needs in mind.
New general fact: $t0 is unreachable from C because reload owns it — the
target's table bases are reload rematerialisations of a reg_equiv_constant
there. A residual of the form 'the target uses $t0 and I cannot' is a reload
artifact, not an unfound spelling.
Also pairs with §463/§469: a frame slot nothing reads is either an 8-byte
rounded spill or a §172 combine USE-orphan — both reproducible, neither
padding.
The counterintuitive one: use TWO distinct locals for the same b*24, because
cse resets at the if-join and the original recomputes the product into a
second register — one shared local cannot reproduce it, and writing it inline
is worse still (cse hoists the %hi/%lo address into a pseudo and changes the
addressing mode). Duplicating a subexpression can be the correct decompile.
Plus: a store-then-read-back turns a redundant load into the target's
register copy; a zero-byte fence stops sched1 hoisting two '= 0' stores into
the load-delay slot; and writing three repeated tails out separately lets
cross_jump merge them, where funnelling them through one variable emits the
arms inverted.
Residual is three allocation facts, incl. a $17 pin that is REQUIRED (else k2
splits across two callee-saved regs and costs a fourth) but drags the shift
chain into $s1.
From the S76 func_80039308 agent (518 ins, 402 -> 154, length exact).
Writing a varying-address load as a struct member (((VMask*)q)->w rather than
*(u32*)q) sets MEM_IN_STRUCT_P, which lets true_dependence prove the load
cannot alias a scalar-global store. Both loads hoist above both stores and
three load-delay nops vanish — semantically identical C, different alias
info.
It also needed a 16-byte s16 sav[8] memory local because reload rounds every
spill slot to BIGGEST_ALIGNMENT=8 — the same law §463 derived from alter_reg
on a different function via a different agent that had not seen it. Two
independent derivations, and a second use for the law: it tells you when a
stack layout can only come from a declared local, never from spilling.
main:func_80040DE8 went 86 -> 2 when §76 variable-reuse pushed o1 off $a3
onto $t0, which made the §3-C pin unnecessary — the pin had been tying ~30
instructions into $t0.
That completes a trio: a volatile launder (§461), a temporary (§462 lever 3)
and now a hard-register pin can each be the thing holding a match back.
Before adding a lever, check whether an existing one is what you are
fighting.
From the S76 func_80181E04 agent (269 ins -> MATCH):
1. §18's %lo-fold applies to STORES only when the symbol is declared
extern Struct SYM[] (stride 0x50, field at +0). On a plain s32[] it
folds for read-only symbols only — worth 13 ins here, and a real
extension of the Phase-20 entry, which only exercised the read side.
2. Relocation masking can HIDE a wrong operand order: the reversed
comparison scores identically under match_one because §1c masks
HI16/LO16 and both symbol refs mask to the same bytes. When a compare's
operands are two different symbols the byte oracle cannot tell them
apart — read the relocations.
3. No biased q pointer (write off p so combine_givs picks p+0x12, else it
mints a second anchor, +2), and keep the counted i<0x100 loop (spelling
the bound via D_801F2A44 costs 12 ins for the same resolved address).
From the S76 func_8001EFE0 agent (468 ins, 172 -> 89):
1. When equal-priority pseudos tie in global-alloc, DECLARATION order
breaks the tie, not assignment order — worth 36 ins here, and it changed
control flow too (a spilled base made an arm's reload break the tail
jump2 had been cross-jumping), so re-check branch shape after using it.
2. A clobber list copied from a neighbour is a liability: a phantom "$2"
clobber evicted abr from $v0 and cost 14 ins, where the real macros
clobber only $12/$13/$14. Verify the list, not just the body.
3. convert_to_integer shortens a narrow-looking sum to QImode and drops its
andi; an explicit s32 temp for the sum restores it.
The §464 append was lost to a git index-lock race: the commit landed with a
message documenting four levers from func_8005DE78 while the file held only
§465 and §466. Caught by grepping the file for each section instead of
trusting the commit I had just written.
Content unchanged from the agent's report: a volatile QI/HI load preserves
the zero-extend as its own andi; ||-vs-&& selects do_jump's drop-through arm;
a volatile STORE can never be stolen into a delay slot (resource_conflicts_p
returns 1 on any volatil resource), which is how to force a target nop after
a j; and a "memory" clobber vs a volatile read are not interchangeable
CSE-breakers — both reload the index, only the clobber leaves the addu
operand order intact.
§464, from func_8005DE78 (141 ins -> MATCH): a volatile QI/HI load stops
combine folding the u8->s32 promotion into the lbu; ||-vs-&& selects
do_jump's drop-through arm; a VOLATILE STORE can never be stolen into a delay
slot (resource_conflicts_p returns 1 on any volatil resource) which is how to
force a target nop after a j; and a "memory" clobber vs a volatile read are
NOT interchangeable CSE-breakers — both reload, but only the clobber leaves
the addu operand order alone.
§465, from func_8005F830 (152/153 byte-exact): the target hops the head insn
of the branch's own target block into the delay slot. Ten controlled probes
show cc1's fill_slots_from_thread refuses a thread insn writing the register
the branch TESTS, and a negative control shows GNU as -O2 only swaps with the
PRECEDING insn. So it is the original ASPSX reorder doing what our
REORDER_TUS substitute structurally cannot — an assembler gap, §182/§188 one
level deeper. Also records that this function's old 'epilogue unreachable'
verdicts are stale.
§466, from matching main itself (509 ins, -O0): inside a MEMORY ADDRESS,
base + i*K expands to a (mult reg K) that force_operand emits INDEX-first;
rewriting as base + ((i*(K>>n))<<n) gives the target's BASE-first addu. Value
context is unaffected, which is why it hides. Plus five supporting -O0 idioms
(COMPONENT_REF for strided stores, pad[6] for the 0x38 frame, a dead register
var to keep $s0 live, (*(u16*)x)++ vs +=1, and MEM-operand-0 argument order).
From the S76 func_8001FC08 agent (400 ins, 33 -> 0 MATCH). Three laws.
A 4-byte gap in an otherwise 4-packed frame is a SPILL SLOT, not a pad:
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 occupies
eight bytes. Worth 11 ins, and modelling them as spills is what evicts both
from local-alloc so reload picks $t0.
§41b's 'a global load cannot float above the RTL prologue' is NOT a wall — it
is an $a0 anti-dependence, because the param copy addu $s0,$a0,$zero reads
$a0. Get the value out of $a0 AND make the load first and it floats to idx 0.
Either move alone is worthless (statement-first alone measured 33 -> 50);
together 22 -> 4.
Argument POSITION decides a guard value's hard register: passing it as arg 1
gives the pseudo a qty_phys_copy_sugg toward $a1, unreachable by local-alloc's
scan-from-$v0. The siblings that don't pass it stay $v0 — the control.
Also records the bank-time typedef hoist this function needs in src/800.c.
From the S76 agent, none previously recorded:
1. array[var-K] folds K into the symbol LO16/lhu displacement, and naming
an intermediate idx does NOT stop it (the fold is front-end/combine,
before any steerable register choice). A zero-byte opacity barrier on
idx, one per use site, is what defeats it.
2. The fused sll 16 / sra 15 sign-extend-scale needs the index declared
s16 — confirms §241's recipe reproduces on a fresh case.
3. A mask-then-compare LOCAL cross-jump-merged two case tails and flipped
branch polarity to bne; switching on the expression directly fixed both
and matched the target's forward-beq. The temporary was the defect —
§461 from the other direction.
4. A pointer parameter's SIGNEDNESS decides how -1 is materialized:
s16* gives addiu -1, u16* gives ori 0xffff, because gcc-2.7.2
canonicalizes the RHS constant against the lvalue's signedness when
picking the load-immediate opcode. Invisible in the C, one instruction
in the asm.
Residual is one permuter-class DELAY-SLOT diff two prior attempts also hit.