27 KiB
From ck + cl + cm
ADDENDUM — §NNN — sharpens §55a / §164-37 / §165-28 (switch-vs-tree cluster): A SHAPE THAT LOOKS LIKE A SWITCH DISPATCH CAN BE PLAIN NESTED ifs WHOSE SHARED BODY WAS TRIPLICATED BY THE SOURCE AND THEN CROSS-JUMP-MERGED BACK DOWN
Function. func_801F06B0 (md_SC03_076, 33/33 ins). Reported independently in wave ck (worked out
from first principles) and wave cm (confirmed by finding the actual cross-binary twin,
md_SC04_027:func_801E8D70, already banked).
Symptom. The Ghidra seed reads as switch (a1) { case 0: ...; case 5: ...; default: ...; } — a
plausible 2-case dispatch. §55a/§163c/§165-28 already state that gcc-2.7.2 compiles anything below
CASE_VALUES_THRESHOLD (5) as a flat equality-test tree, not a jump table — but a naive C switch
with exactly this case set still compiles to a SHORTER, flatter tree (measured 26 ins: beqz/li 5/beq) than the target's 33 ins. The extra length is not a scheduling residual; it is a shape
error.
Mechanism. The target's 33 ins decode as three copies of ONE shared compare-and-load body, spread
across a slti/bgtz root and an inner a1 != 0 test, with gcc's cross-jump pass (§5a family)
merging two of the three copies back into shared code. The real source is closer to if ((a1 > 0 && a1 < 5) || (a1 != 0 && a1 != 5)) { ...shared body... } than to any switch/case construct — the
"switch" reading was a plausible-looking misdirection from the seed, not evidence of a dispatch at
all. The cm instance found the actual mechanism by locating the real twin: md_SC04_027's already-
banked func_801E8D70 at the same shape (33 ins, same D_80115158 table, same caller pattern),
findable only by grepping asm/ for the shared symbol + call shape (D_80115158[ + * 2), not by
name — which is exactly §238's documented "positive use" (grep asm/ by encoding/shape, not src/
by name) applied cross-binary to a same-family md_* module rather than an overlay.
Why ADDENDUM, not NEW. The cross-binary-twin-discovery half is already §238's positive-use case,
just with an md_* module standing in for an overlay — not a new mechanism. What is worth adding as a
sharpening of the §55a/§164-37/§165-28 cluster is the negative caution the cluster doesn't currently
carry: a case-labeled shape under CASE_VALUES_THRESHOLD that is LONGER than the flat tree its own
apparent case set would produce is a structural tell (wrong C construct entirely, not a missing
transform) — check the instruction count against the naive flat-tree prediction before trusting a
switch reading at all, the same discipline §162a3/§164-37 already apply to jump-table dispatch
shapes but do not currently state for the below-threshold tree route.
ADDENDUM — §NNN — sharpens §167-13's boundary: CHAINING TWO IDENTICAL SIDE-BY-SIDE STORES INTO ONE C ASSIGNMENT STATEMENT IS A MID-BLOCK SCHEDULING-PRIORITY DIAL, NOT ONLY A STORE-ORDER SPELLING
Function. func_80181BE0 (ov_SC03_112, 51/51 ins). Reproduced across all three waves (ck 8 oracle
calls, cl 2, cm 1) with a consistent finding each time; cl/cm's later passes re-confirm rather than
add.
Symptom. A mid-block argument-setup instruction (move $a0,$s1 feeding a call to
func_8012AD80) schedules one sra/addiu pair earlier than the target places it — a
SCHEDULE-REORDER residual that plain statement reordering, named temps, an asm barrier on the
parameter, and a decl-order swap all failed to fix (measured and rejected in the ck note).
Mechanism / fix, byte-verified against the target's own relocation lines (7 sites): the target
stores two adjacent halfwords with the SAME value (*(s16*)(b+0x18) and *(s16*)(b+0x1C)).
Writing them as two separate C statements leaves the mid-block arg-setup instruction scheduled where
it was; writing them as ONE chained assignment — *(s16*)(b+0x18) = *(s16*)(b+0x1C) = expr; — shifts
that store pair's priority/LUID enough that the arg-setup instruction sinks below it, matching the
target exactly.
Why ADDENDUM, not NEW. §167-13 already documents a related but distinct fact — "a leading p1 = a1; terminates rather than rides the bb0 parameter pin, and the direction/pass/dose were previously
misstated" — but its own scope is explicitly the FUNCTION-HEAD parameter-pin weave, not an
ARBITRARY mid-block pair of stores. This candidate's own self-citation ("not in cookbook under
'head-crack'; §167-13 covers prologue-pair weaving, not mid-block") is accurate: the corpus has no
entry for chained-assignment-as-a-mid-block-priority-dial specifically. Recorded as ADDENDUM rather
than NEW because it is best read as extending §167-13's family (LUID/priority manipulation via
statement chaining) into a region §167-13 explicitly disclaims covering, and because the underlying
compiler mechanism (why chaining changes LUID assignment) was not independently re-derived from
source here — only the C-level lever and its effect were confirmed, three times, against the bytes.
ADDENDUM — §NNN — sharpens §162a3/§60b: A sll $v0,16 / sltiu $v0,1 ZERO-TEST OF A JUST-DECREMENTED HALFWORD IS A SIGNED-TEMP WIDTH TELL OUTSIDE ANY SWITCH/RANGE-TEST CONTEXT
Function. func_80186C3C (ov_SC06_029, 19/19 ins). Reproduced identically in waves cl and cm.
Byte evidence (asm/ov_SC06_029/nonmatchings/ov_SC06_029_jr_8017C954/func_80186C3C.s):
lhu $v0, 0x10A($s0)
addiu $v0, $v0, -1
sh $v0, 0x10A($s0)
sll $v0, $v0, 16 ; NO following sra
sltiu $v0, $v0, 1
The field is loaded/stored u16 (lhu/sh — it must stay unsigned for the store), but the
post-decrement zero-test uses sll $v0,16 immediately followed by sltiu $v0,1 (true only when the
shifted 32-bit value is exactly 0, i.e. when the low 16 bits were 0) rather than the more obvious
andi $v0,0xffff / sltu $zero,$v0 masking idiom. This spelling is produced by declaring the
DECREMENT TEMPORARY s16 (signed) even though the memory access itself must stay u16/lhu/sh to
match — an u16 temp instead emits the andi-based zero test.
Why ADDENDUM, not COVERED. §162a3/§164-37/§165-28's sll 16 / sra 16 family is specifically about
a SWITCH DISPATCH index (a value about to feed a sltiu-bounded jump table or beq chain against
table edges) — none of their text or examples cover a plain post-decrement ==0 test with no
dispatch anywhere nearby, and — noted as a discriminator, not a coincidence — this instance has NO
trailing sra, unlike the switch-index family's sll 16 ; sra 16 pair (this is a one-sided shift used
purely as a cheap "are the low 16 bits zero" test via sltiu, not a sign-extension). §60b's
"sltiu==!x" observation is the nearest neighbor but does not connect it to the signed/unsigned
temp-declaration choice. Grepped the corpus for this exact combination outside switch-dispatch
contexts — no hits.
ADDENDUM — §NNN — sharpens §37/§124 (unspecified-parameter-list family): DERIVE A CALLEE'S ARITY LOWER BOUND FROM ITS OWN .s STACK-ARGUMENT READS, NOT FROM ANY SINGLE CALL SITE
Function. func_801A8DCC (md_SC07_004, 26/26 ins). Reproduced in waves cl and cm; the callee in
question, func_801A8E34, is itself still INCLUDE_ASM (no C source or documented signature exists
anywhere in the tree).
Mechanism, byte-verified against asm/md_SC07_004/nonmatchings/md_SC07_004/func_801A8E34.s: the
callee's own prologue subtracts 0x68 from $sp (addiu $sp,$sp,-0x68), and its body later executes
lw $s6, 0x78($sp) — a read at an offset ABOVE its own frame (0x78 > 0x68), which can only resolve
to a slot the CALLER set up before the call (an incoming stack argument, per §217/§217-CROSS-
CONFIRMED's "the incoming home slot is the mirror" pattern, already documented for the symmetric case
of reading a caller-relative offset before sw $ra). Since func_801A8DCC itself only ever passes 5
arguments to this callee, and the true callee reads at least one argument beyond the four
register-passed slots, the only C spelling that is safe against every caller (this one included, which
under-supplies the true arity) is the K&R unspecified parameter list — extern void func_801A8E34();
— never a guessed fixed-arity prototype.
Why ADDENDUM, not NEW. The unspecified-parameter-list rule itself ("? is compatible with any
prototype and never a conflict") is already well established and repeatedly cited by name across this
corpus (§37, §124, and the drafting notes' own "card decl law" references) — this is not new. What
was previously undocumented is the SPECIFIC TECHNIQUE for deciding, from evidence, that a plain guess
is unsafe: read the callee's OWN .s for a stack-argument reference beyond its incoming-register
budget, using its declared frame size as the zero point. Could not independently verify the exact
argument COUNT the note claims (it says "sixth argument"; by the standard MIPS o32-style layout used
elsewhere in this corpus — where the first stack argument lands at the caller's $sp+0x10 — the
offset measured here reads as the callee's fifth argument, not sixth). The count is not load-bearing
for the law (unspecified-list is safe regardless of the exact number), so this is flagged in the
"could not verify" section rather than asserted either way.
ADDENDUM to §5a (func_80181F74, ov_SC03_112, wave cn)
A CROSS-JUMP TWIN BLOCK GUARDED BY ITS OWN if NEEDS THE BARRIER IN THE ELSE-CONTINUATION, NOT AT THE BLOCK HEAD AND NOT INSIDE THE GUARD'S THEN-ARM
§5a's own recipe places the zero-byte volatile-asm barrier "after the last store, before the return" —
written for a flat twin block (a sequence of stores ending in return c;). func_80181F74
(ov_SC03_112, MATCH 196/196, banked at src/ov_SC03_112/ov_SC03_112_jr_80181F74.c) shows a twin block
that is itself an if-guarded counter (*(s32*)(p+0x1C) += 1; if (counter >= 0x10) goto C78;), and
§5a's placement instruction does not say where inside a guarded twin the barrier goes. Three
placements were probed and only one works:
- Barrier above the shared suffix (top of the block):
find_cross_jump's backward scan starts at the fall-through edge below the barrier and merges the whole suffix anyway — the barrier never gets compared because the scan never walks up into it. - Barrier inside the THEN-arm of
if (>= 0x10): kills the merge, butjump.c's block layout then re-lays the branch asbnez-into-the-guarded-arm, flipping the target'sbeqz-into-C78 polarity — a BRANCH-POLARITY residual traded for the LENGTH-DRIFT it fixed. - Barrier in the ELSE-continuation, immediately after the guard, before the shared tail goto — the only spelling that reproduces the target exactly:
case 3:
*(s32 *)(param_1 + 0x1c) += 1;
if (*(s32 *)(param_1 + 0x1c) >= 0x10) {
goto C78;
}
__asm__ __volatile__("");
goto TAIL;
The sibling twin block (case 1) has the byte-identical 7-instruction shape but carries no
barrier — adding one to only ONE of a pair of would-be-identical tails is what defeats
rtx_renumbered_equal_p's suffix comparison; both blocks do not need the barrier, only one.
Evidence. asm/ov_SC03_112/nonmatchings/ov_SC03_112_jr_80181F74/func_80181F74.s; banked source
src/ov_SC03_112/ov_SC03_112_jr_80181F74.c:2748-2864, case 3: at line ~2825 carries the
__asm__ __volatile__(""); exactly as shown above; case 1: (line ~2789) has the same
+= 1; if (>= 0x10) goto C78; goto TAIL; shape with no barrier.
When it applies. Two candidate cross-jump twins are each an if-guarded tail (not a flat
store-sequence) sharing the SAME guard shape and SAME post-guard fall-through. Place the barrier as
the LAST statement of the fall-through continuation on exactly one of the two candidates, after the
guard's own branch and before the tail jump — not at the block's head (the scan walks past it) and not
inside the guard's taken-arm (it changes which arm is fall-through and flips branch polarity instead of
just blocking the merge).
ADDENDUM to §265 (func_8017E26C, ov_SC04_016, wave cn)
THE void f() { __asm__(...); } WRAPPER FORM MUST END THE STRING AT .set reorder — A TRANSCRIBED jr $ra/nop DUPLICATES cc1's OWN AUTO-GENERATED FUNCTION EPILOGUE
§265's C-definition-wrapper recipe (form 2) already documents transcribing a handwritten .s verbatim
inside a void f() { __asm__ __volatile__(...); } body. What it does not say: for THIS wrapper form,
cc1 still owns the function's own epilogue (the jr $ra/nop pair that closes out the C function),
because from cc1's point of view the function body is a single asm statement followed by an implicit
fall-off-the-end return. If the transcribed string ALSO includes its own trailing jr $ra/nop
(copied straight from the target .s's tail, which is exactly what "transcribe verbatim" invites), the
two epilogues stack — the target's return sequence appears twice. The fix is to transcribe everything
EXCEPT the final jr $ra/nop, ending the string at .set reorder and letting cc1 emit its own
closing return.
Byte evidence. func_8017E26C (ov_SC04_016, banked verbatim at
src/ov_SC04_016/ov_SC04_016_jr_8017BEBC.c:3534-3813). The asm string's own hand-transcribed
prologue/epilogue supplies sw $ra,80($sp) / addiu $sp,$sp,-88 at the head and
lw $ra,80($sp) / addiu $sp,$sp,88 at the tail (both present, both load-bearing — this function
saves/restores its own frame inside the string), but the string ends at ".set\treorder\n" with no
explicit jr $ra/nop — those two instructions are left for cc1's own function-closing codegen to
emit. Confirmed by reading the source directly (lines 3808-3813).
Toolchain facts confirmed alongside this (§265 already states the first; the rest are additions):
- MASPSX rejects hex immediates inside the string — decimal only (already in §265).
%hi/%louse a single%; a doubled%%hiis rejected by the assembler.- A GTE control-register operand must be written by its numeric form (
$31), not the pseudo-name form ($12) —cfc2 $12,31is rejected,cfc2 $12,$31(destination GPR, numeric CP2 control reg) is accepted.
When it applies. Any §265 lane-2 (void f() { asm(...); }) transcription. Read the target .s's
own trailing jr $ra/nop as belonging to cc1's auto-generated epilogue, not to the payload to
transcribe — leave it out and end the string at .set reorder, exactly as func_800CBA44's existing
precedent already does (cited by §265, but the WHY was not stated).
ADDENDUM to §42b (func_8018247C, ov_SC07_002, waves cu + cw)
A "DID THE SPLICE ADD A NEW COMPILER DIAGNOSTIC" DIFF IS A CHEAP PRE-GATE SANITY CHECK FOR A DECLARATION-LAYER CONFLICT — DOCUMENTED ONLY AS ONE FUNCTION'S OWN BANKED COMMENT, NOWHERE IN THE COOKBOOK OR INDEX
§42b's mandatory gate shape (git checkout → splice → rm the split .o → make build with an
asserted exit code → sha1sum vs config/check.<binary>.sha) is the real, sole arbiter — that does not
change. What is missing is a pre-gate diagnostic for narrowing down WHY a byte-exact, match_one-MATCH
draft keeps failing that real gate: diff the whole-TU compiler stderr with and without the splice. If
the splice adds zero new diagnostic lines over the untouched-TU baseline, the declaration-layer classes
§236/§61a/§138 name are very likely closed (necessary evidence, not by itself sufficient — the gate is
still the sha1 check) — narrower framing than the candidate note's own "diagnostic parity IS the bank
test," which overstates a diagnostic heuristic as if it were the bank criterion itself.
Evidence this technique is real and already practiced in-tree (not merely proposed):
src/ov_SC07_002/ov_SC07_002_jr_8017C8D0.c:6737-6845, the banked comment on func_801849AC, states
verbatim: "Verified by splicing this body over the INCLUDE_ASM at TU L3917 and compiling the whole TU:
same 7 stderr lines as the untouched TU, and func_801849AC is byte-identical." This is a real, on-disk
precedent of a prior session doing exactly this diff before trusting a splice — confirmed by reading the
comment directly, not inferred.
When it applies. A card that is match_one-MATCH and byte-stable across many resubmissions but
keeps failing the whole-binary gate, where every checkable §236/§61a/§138 declaration-layer class comes
back clean by static reading. Before spending another session re-deriving the C: build the untouched TU
once, build the spliced TU once, and diff cc1's stderr line-for-line. New lines pinpoint the exact
conflicting declaration (or its absence rules out the declaration layer as the cause, redirecting
suspicion to sibling-card / batch-gating mechanics per §176b).
ADDENDUM to §199-F family (func_8017EFB0, ov_SC02_021, wave cu)
RESTRUCTURING THE ENCLOSING GUARD FROM "WRAP THE BODY IN if" TO "EARLY RETURN" CAN FILL AN UNRELATED, DOWNSTREAM CONDITIONAL BRANCH'S DELAY SLOT — A CFG-SHAPE LEVER, NOT A FENCE
§199-F's target-head fence (a bare __asm__ __volatile__("") at the head of one arm) is the documented
way to steer which thread's instruction fills a conditional branch's delay slot. func_8017EFB0
(ov_SC02_021, MATCH 25/25) shows a DIFFERENT lever reaching the same class of outcome: the outer guard
of the whole function was written as an early return (if (s0 == 0) return 1;) rather than a wrapping
if (s0 != 0) { …body… }. Both spellings are semantically identical and both pass match_one on their
own local shape, but only the early-return form reproduces the SECOND, unrelated branch's delay-slot
fill deeper in the function (beqz $v0,.L8017EFF8 picking up lui $v0,(0x80520>>16) — a load literal
belonging to the fall-through arm, not the branch's own condition).
Mechanism (as far as observable from the outside). fill_eager_delay_slots' scan of a branch's
candidate threads is gated by own_thread_p/LABEL_NUSES reachability analysis over the whole
function's CFG, not just the branch's immediate arms. Wrapping the guarded body in an if block
creates a different block/edge structure around the SAME later branch than an early-return does (the
early-return form removes one CFG edge — the "guard false" path never reaches a block that also flows
into the later branch's fall-through — changing which insns downstream own_fallthrough analysis
considers eligible). The practical result matches §199-F's own consequence (a delay slot gets filled
from a specific thread) without touching that branch's own arms or adding a fence anywhere near it.
Byte evidence. src/ov_SC02_021/ov_SC02_021_jr_8017C294.c:3818-3828:
s32 func_8017EFB0(void) {
s32 s0 = func_80029204(0);
s32 v1;
if (s0 == 0)
return 1;
func_80162760();
v1 = *(s32 *)D_80193528;
if (v1 < s0)
v1 += 0x80520;
return (v1 - s0) < 0x2D0;
}
asm/ov_SC02_021/nonmatchings/ov_SC02_021_jr_8017C294/func_8017EFB0.s: beqz $s0,.L8017F000 /
addiu $v0,$zero,0x1 (outer guard's OWN delay slot, unremarkable — the early-return constant lands
there per ordinary jump-inversion, §3-T4) — but ALSO, further down, beqz $v0,.L8017EFF8 /
lui $v0,(0x80520>>16) for the SECOND, independent test, which the note reports ~19 if/else-spelled
drafts of the SAME body left as a bare nop.
When it applies. A near-miss whose only diff is an unfilled (nop) conditional-branch delay slot
DOWNSTREAM of an unrelated guard, where every fence placement at that branch itself (§199-F) has already
been tried and failed or is inapplicable. Try restructuring the EARLIER, unrelated guard from a wrapping
if to an early return before concluding the slot is unfillable — the CFG shape of a guard several
statements earlier can gate a later branch's own eligibility.
(Declaration-layer note, not promoted: the function's sole data symbol D_80193528 is typed u8[]
per its access width, matching the card's own atlas row — ordinary practice, not a new fact.)
ADDENDUM to §195-E (func_800CFC1C, md_MAIN_003, wave cv)
A GUARD'S OWN 0/1 TRUTH VALUE CAN BE THE FUNCTION'S RETURN VALUE, NOT JUST THE BRANCH'S CONDITION — SPELL IT AS return (T)gate; USING THE SAME NAMED BOOLEAN THE if TESTS
§195-E already establishes that a 0/1 value materialised at a join immediately before a controlling
beqz/bnez proves the source named the condition. func_800CFC1C (md_MAIN_003, MATCH 121/121,
banked at src/md_MAIN_003/md_MAIN_003.c:241) sharpens this for the specific case where the guard's
failure path RETURNS EXACTLY THE GUARD'S OWN TRUTH VALUE: writing gate = cond; if (gate) return (T)gate; — reusing the SAME named boolean both as the branch's condition and, unmodified, as the
return expression — keeps the compare's own destination register ($v0, from slti) as the return
register, with no separate li 0/li 1 materialisation on that path. A semantically equivalent but
differently-spelled version (if (cond) return 1;, or a fresh return cond ? 1 : 0;) would introduce a
second definition and is NOT proven byte-neutral here — the target's exact byte pattern requires the
compare's result itself, untouched, to reach $v0.
Byte evidence. src/md_MAIN_003/md_MAIN_003.c:265-270:
D_800EC69C = D_800EC69C + 1;
gate = D_800EC69C < 2;
if (gate) {
return (u16 *)gate;
}
asm/md_MAIN_003/nonmatchings/md_MAIN_003/func_800CFC1C.s:E4C-E5C:
addiu $v0, $v0, 0x1
lui $at, %hi(D_800EC69C)
sw $v0, %lo(D_800EC69C)($at)
slti $v0, $v0, 0x2
bnez $v0, .L800CFDDC
addu $s3, $zero, $zero
slti writes its 0/1 result directly into $v0; the bnez on that same register branches to the
shared epilogue with $v0 already holding the function's return value — no intervening li/move.
When it applies. A guard whose ENTIRE failure-path body is return <the guard's own boolean> (not
some other constant, and not a value derived from the guard by more than a bare cast). Name the compare
result once, test the SAME named local in the if, and return that same local (cast to the return
type) — do not re-derive the return value as a fresh literal or a second expression, even one that is
mathematically identical.
ADDENDUM to §215 addendum (func_800CB2C8, md_MAIN_033, waves cv + cw)
A TIED WRITEBACK BETWEEN TWO ALREADY-PINNED VARIABLES FORCES A GENUINE CROSS-REGISTER MOVE FOR A MULTI-STEP COPY CHAIN — x = y; asm("":"=r"(x):"0"(x)); y = x; — WHERE PLAIN REASSIGNMENT WOULD COALESCE OR GET VALUE-FORWARDED AWAY
§215 addendum item 5 already establishes that two pins of the SAME hard register coexist when their
live ranges are disjoint BLOCKS. func_800CB2C8 (md_MAIN_033, banked at
src/md_MAIN_033/md_MAIN_033.c:146) shows a related but distinct case: TWO DIFFERENT pinned variables
(register s32 raw __asm__("$3") and register s32 t __asm__("$2")), where the target's asm shows a
genuine register-to-register MOVE from one physical register to the other partway through the function
(a move/addu d,s,$zero encoding — confirmed NOT a zero-register add). A plain C reassignment between
two pinned locals (t = raw;) is not guaranteed to survive as a real move — cse/value-forwarding can
fold it away, especially when both sides are already hard-reg pinned and the "assignment" looks like a
no-op to the optimizer. The working idiom names the SAME variable pair through an explicit tied
input/output asm with no operands changed, forcing gcc to treat it as a live use that must be
materialised:
raw = t;
__asm__("" : "=r"(raw) : "0"(raw));
t = raw;
Because raw and t are each pinned to exactly one hard register apiece (no duplicate-pin conflict,
§257 item — pins on the same register in the same scope are refused by this cc1), and the tied asm
gives raw a real def/use pair that cannot be coalesced with t's, the subsequent t = raw; is forced
to emit as an actual cross-register move rather than being value-forwarded or dropped.
Byte evidence. src/md_MAIN_033/md_MAIN_033.c:146-165:
register void *p __asm__("$4");
register s32 raw __asm__("$3");
register s32 t __asm__("$2");
...
raw = *(u8 *)((u8 *)s1 + 0x24);
t = raw - 0x10;
if (t < 0) { ... }
raw = t;
__asm__("" : "=r"(raw) : "0"(raw));
t = raw;
*(u8 *)((u8 *)s1 + 0x24) = *(u8 *)((u8 *)s1 + 0x25) = *(u8 *)((u8 *)s1 + 0x26) = t;
The note's own residual history (near-33 → near-7 (pins) → near-4 (zero-reg-add misreading) → near-2 → MATCH) records introducing FRESH variables for the same chain as the failed intermediate step —
confirming the specific requirement is REUSING the two already-pinned variables, not adding new pinned
locals.
When it applies. A target shows a plain register-to-register move/addu d,s,$zero mid-function
between two hard registers that are both independently already forced by pins elsewhere in the same
function, and a naive reassignment between the two pinned C variables does not reproduce it. Route the
value through BOTH pinned variables with a tied, unmodified "0"-constrained asm between the two
assignments, rather than adding a third, freshly pinned local.
ADDENDUM to §236 item 4 (func_8017D268, ov_SC04_006, wave cw)
A SHARED Blk8/Rec8/S8-STYLE STRUCT TYPEDEF'S NAME IS KEYED TO (THE GLOBAL'S ADDRESS, THE DEFINING FUNCTION'S OWN ADDRESS) — GREP FOR YOUR FUNCTION'S ADDRESS, NOT A TWIN'S OR A NEIGHBOUR'S
§236 item 4 lists Blk8_80126940_8017D6D0_8017F6D4 among the shared-header names a draft must not
re-typedef, but does not state the naming convention itself. src/shared/engine_types.h disambiguates
per-function VIEWS of one shared raw buffer by compounding the global's own address with the address(es)
of the function(s) that use that particular field layout — because the same 8-byte (or other small)
global is legitimately interpreted with DIFFERENT field layouts by different functions, one bare
Blk8_<addr> name is not always enough. func_8017D268 (ov_SC04_006) was drafted against a donor
struct name borrowed from a NEIGHBOUR in the same TU (Blk8_80126940_8017D344) and separately from a
banked TWIN in another overlay (Blk8_80126940_8017D6D0) — both wrong; the correct spelling,
Blk8_80126940_8017D268, is keyed to func_8017D268's OWN address and exists in the header
specifically for it.
Evidence. grep -n Blk8_80126940_8017D268 src/shared/engine_types.h → line 1465,
} Blk8_80126940_8017D268; — confirmed present, keyed exactly as described. The banked same-address
twin src/ov_SC04_003/ov_SC04_003_jr_8017BEBC.c:3342 independently uses this same spelling for the same
global, corroborating that the key is the (global, defining-function) pair, not a per-overlay or
per-neighbour choice.
When it applies. Any card whose seed or a plausible twin/neighbour offers a compound-named shared
struct typedef (Blk8_, Blk20_, Rec8_, S8_, …) for a global your OWN function also touches. Before
adopting a borrowed spelling, grep src/shared/engine_types.h for a name that ends in YOUR function's
own address; a hit there is the authoritative donor, not a twin's or neighbour's compound name (which
encodes a DIFFERENT function's field-layout view of the same raw bytes and may not match yours).