phase10: merge 27 + negatives extraction — 484 distinct bodies / 493 regions

+2 bodies (worker C2 claims 3-4). Candidate gate MATCH before promotion.

NEGATIVES EXTRACTION (the closing-checklist step that protects worker findings
from ignored staging being lost): imported 28 new negatives from all five workers'
staging into the tracked index, and dropped 28 rows that had since been REGISTERED
(the reconcile step working as designed). Index 166 -> 194 rows, address-ordered,
0 duplicates, 0 registered. Worklist 1193 rows; excluded_recorded_negative=170;
0 unregistered negatives survive into the worklist.

make check green: regions=493 AGREE, differing_bytes=0 MATCH, 237 tests OK.

Worker C2's cross-cutting finding recorded: three of its four negatives are the -O2
SCHEDULER, not source shape. For 0x800A6C34 (16 differing) and 0x80016F80 (45
differing), both at correct length, the identical source with
`cc1 -quiet -O2 -G0 -fno-schedule-insns` produces the original's instruction ORDER
byte-for-byte -- and both then show the same two-sided signature: sched OFF gives the
original's order but the wrong allocation (or the wrong delay slot), sched ON gives
the right allocation and the wrong order. Neither alone matches. C2 correctly did NOT
take the scheduler override (out of scope); both stay recorded as negatives with the
lever named. Diagnostic broadcast: correct length + a residual that is a permutation
of a few instructions + the unscheduled build matching the original order => the
residual is sched.

Also recorded: the commutative-operand lever has a COUPLED ALLOCATION side-effect
(0x80027744 at 4 bytes, 0x80050674 at 1 byte) -- every spelling giving the original's
addu operand order also flips which value takes v0 vs v1.
This commit is contained in:
Christopher Williams
2026-09-24 08:22:55 -04:00
parent be2ee1874c
commit 9aae442e64
5 changed files with 1385 additions and 1224 deletions
+1194 -1224
View File
File diff suppressed because it is too large Load Diff
+28
View File
@@ -8,6 +8,7 @@
0x80010810 60 near-match register-tiebreak
0x8001084C 712 blocked trapping-arithmetic
0x80011084 - near-match -
0X80011484 132 near-match candidate_bytes=108 vs 132 (24 SHORT). My reconstruction is wrong and I am recording it as unresolved rather than guessing further. What the bytes DO show: a fr
0x8001278C - near-match -
0x80012834 - near-match -
0x80012A10 - near-match -
@@ -16,11 +17,13 @@
0x80012AE0 - near-match -
0x80012CFC - near-match -
0x80012D54 56 near-match -
0X80012E84 160 near-match LENGTH-MISMATCH 168 vs 160 (42 vs 40 words). Same family as 0x800A6B38. cc1 placed the shared `return 0` block in the MIDDLE of the function (branch target 0x80
0x80013114 64 near-match -
0x800161E0 - near-match -
0x80016224 - near-match -
0x80016268 - near-match -
0x80016E24 - near-match -
0X80016F80 156 near-match candidate_bytes=156 = CORRECT LENGTH, 45 differing bytes, first_difference=0x80016F90. Instruction MULTISET is identical to the original; the residual is purely
0x800179E0 - near-match -
0x80017A38 - near-match -
0x80018284 80 near-match struct-copy jr-slot shape (8 loads/8 stores + v0-zero in slot)
@@ -31,10 +34,12 @@
0x8001EAFC 12 deferred shared-block-not-a-function
0x80022E44 52 near-match adjacent-zero-store merge
0X800238BC 264 near-match candidate_bytes=264 (correct length) differing_bytes=5 first_difference=0x80023900. Structure solved: `if (arg == 0) { out->x=out->y=out->z=0; } else
0X80023D40 104 near-match candidate_bytes=104 = CORRECT length, 5 differing bytes, and the diff is ONLY the first two instructions: original `bgez a0,<after the negu>` + `nop`, candidate
0x800245D8 - near-match -
0x80024630 56 near-match stack-routed min (temp elided by optimizer)
0x8002515C - near-match -
0x800254B0 - near-match -
0X800256F0 104 near-match LENGTH-MISMATCH 108 vs 104 (27 vs 26 words). Right structure, one extra instruction: the original RELOADS the sub-object field after the call (`lw v1,32(s0)` th
0x80025758 - near-match -
0x800259DC - near-match -
0x80025C08 - near-match -
@@ -43,11 +48,17 @@
0x800266A8 68 near-match byte-replication alloc (first sll register; fresh-context re-spelling 3B; named-locals regresses)
0X800268F4 136 near-match differing_bytes=9 result=DIFF at 136 bytes with the shared-result spelling (r-268f4-shared): branch target 0x80026968 vs 0x80026964 and the delay slot
0X80026A04 152 near-match LENGTH-MISMATCH: `for (i=0;i<10;i++)` gives 136 bytes (34 words) vs 152; cc1 ROTATED the loop into a do-while with the test at the bottom, and dropped
0X80027744 104 near-match candidate_bytes=104 = CORRECT length, differing_bytes=4, first_difference=0x8002778D. Residual is ONE instruction's DESTINATION register: original emits `addu v
0x80027BE8 - near-match -
0X80028750 244 near-match candidate_bytes=236 vs 244 (8 short) with `int r[4]; int s;` and with `int r[3]; int s;`. Structure understood: s = f(p0,q0)+f(p1,q1)+f(p2,q2); r[i] =
0X80029ED8 224 near-match candidate_bytes=236 vs 224 (12 over). Two levers applied and kept: (a) declare the address local unassigned and assign it AFTER the first call (244 -> 240) -- c
0x8002D014 - near-match -
0x8002D060 72 near-match -
0X8002DE28 140 near-match candidate_bytes=136 vs 140 (4 short), 2 spellings tried, both 136. Structure solved and it is the SIBLING of 0x8002DF1C (same 76-byte record at D_80121BFC index
0X8002DF1C 104 near-match candidate_bytes=104 = CORRECT length, 58 differing bytes. Structure: if (a0 < D_801222B4 && a0 >= 0) { *a2 = *(int *)(D_80121BFC + a0*76 + 44); *a1 = *(short *)
0X8002E198 180 deferred 4 prologue callee-saved assignment: ORIGINAL copies a0->s2, a1->s3, a2->s1 and the func_800A8700 result->s0 (save order s2,s3,s1, then s0 in the jal d
0X8002FB54 108 near-match candidate_bytes=112 vs 108 (4 over). Structure: int r = 1; if (a2 == 0) a3[2] = a0[0]; else if ((unsigned)a2 < 3) { a3[2]=a0[0]; a3[3]=a0[1]; a3[4]=a0[2]; a3[5]
0X8002FD4C 124 deferred LENGTH-EXACT (124 B, 31 insns) with 9 differing bytes in 5 instructions, all in the s0-first-use group: 0x8002FD50/54 prologue save order (orig sw s0,16 then sw
0x80037984 - near-match -
0x800379E0 - near-match -
0x80039308 - near-match -
@@ -59,6 +70,7 @@
0x80042DD4 - near-match -
0x80042E10 - near-match -
0x80042E68 - near-match -
0X80043DC4 212 deferred 2 2 attempts. Body understood: table lookup via 0x80121C00, x = *(int *)(*(int *)(s0+16)+16) loaded at the TOP into a0 and used as func_80043D8C's only argument
0x80045540 56 near-match constant-materialisation-order
0x80045F00 - near-match -
0X80045FD8 120 deferred 4 register shift: original reloads a3->a0, a4->v0, and in the L1 block a4->a1; candidate gets a3->v0, a4->v0, a4->v1 (i.e. the allocator's choice star
@@ -67,12 +79,15 @@
0x8004820C - near-match -
0x800496CC - near-match -
0X8004D7C8 220 near-match candidate_bytes=220 = CORRECT length, differing_bytes=7 first_difference=0x8004D7F4. Structure: `v = *(char**)(p+32); s1 = *(int*)(*(char**)(v+244)+8)
0X80050604 112 near-match candidate_bytes=116 vs 112 (4 over). Structure solved: two 24-byte-stride array bases 384 bytes apart (D_8012FDD4 and D_8012FF54, both address-named, both takin
0X80050674 112 near-match candidate_bytes=112 = CORRECT length, differing_bytes=1, first_difference=0x80050696 -- the register FIELDS of the second `addu`, nothing else. Original: `addu
0x800518BC 88 near-match return-merge/sltiu (3 spellings: goto-shared 80B, combined cond 80B, two-exit if 80B; original keeps move-zero at separate target + j-to-shared-return; sltiu needs unsigned val which alone fixes the compare byte but not the 8-byte layout)
0x800582AC 64 near-match -
0X800589E8 192 near-match candidate_bytes=196 vs 192 with `V4 d; V4 r;` and with `int d[3]; int r[3];` (the array form gives the original's 64-byte... no: frame 64 with s1 also
0X80058AA8 248 near-match candidate_bytes=252 vs 248 (4 over, 121 differing bytes from one shifted instruction). Structure: `V4 d; V4 v; v = *(V4*)D_8010D138; func_80027ECC(&v,
0x8005DEF8 124 near-match register-tiebreak
0x80065494 80 near-match popcount scheduling (base in beqz delay slot)
0X800667DC 204 near-match candidate_bytes=188 vs 204 (16 short). cc1 merged the two call sites by selecting `a1` (`j 0x80066880` + `li a1,23`) where the original has a shared call reache
0x800690E4 68 near-match loop-rotation+head-match-early-return (3 spellings: do/while arg-test-top 84B, while+conditional 76B, while-test 64B; the j/li early path and bnez-back-to-bne rotation are the residual)
0x8006AD5C - near-match -
0x8006AE04 - near-match -
@@ -83,14 +98,17 @@
0x8007374C - near-match -
0x800751E8 - near-match -
0x8007A114 - near-match -
0X8007C408 160 near-match LENGTH-MISMATCH 164 vs 160 (41 vs 40 words), first difference 0x8007C418. SECOND INSTANCE of the same unexplained tie-break as 0x800A6B38: the original holds th
0x8007E85C - near-match -
0x800807E0 - near-match -
0x80085B44 60 near-match -
0x8008A198 56 near-match -
0x8008F478 - near-match -
0x80090990 - near-match -
0X80091490 84 near-match candidate_bytes=84 differing_bytes=2 result=DIFF at 0x800914B8 (or at 0x800914C8 with the statements swapped). The original emits load 20(s0), load 4(s0), subu,
0x80092104 - near-match -
0x80094370 84 near-match triple-deref copy loop (dest = *(*(a0+0xc)+0x160)+0x160; 1 coordinator attempt 100B)
0X80099D14 88 near-match candidate_bytes=88 differing_bytes=12 result=DIFF, one instruction POSITION. Everything matches except that the original emits `addiu v0,zero,2` (the constant f
0x80099E34 40 near-match alloc-tiebreak (symbol-form address in a1)
0x8009B218 - near-match -
0x8009C69C 60 near-match register-tiebreak
@@ -101,13 +119,18 @@
0x800A6C34 88 near-match hoist: cc1 lifts `li v0,-1; sw v0,0(s0)` above the load of 0x80121B88; the original loads first then stores, so the hoist is the WHOLE residual. The draft already uses a LITERAL address (`*(int *)0x80121B88`) deliberately, to avoid the name-keyed gp trap, and that literal correctly produces the original's absolute encoding (`lui v1,0x8012` / `lw v1,7048(v1)`). Note: the coordinator's `--no-gp D_80121B88` test on this draft is INERT by construction -- the harness gp rewrite only acts on SYMBOL accesses, so a literal gives identical output and that is not evidence about the encoding. The untried lever is a source order that stops the hoist. Recovered by worker C's cleanup audit after the row fell out of its staging.
0x800A82D0 64 near-match -
0x800A86B4 - near-match -
0X800A8920 100 near-match LENGTH-MISMATCH 104 vs 100 (26 vs 25 words). Everything matches except the `return 0` path: the original puts `addu v0,zero,zero` in the `beq s0,zero` DELAY SLO
0x800A8AEC - near-match -
0x800AAC44 - near-match -
0X800AB504 148 near-match candidate_bytes=172 vs 148 (24 over). Structure solved: recursive free of a linked record (`if (n) func_800AB504(n);`), then a counted loop of `count
0X800AC7A0 120 near-match candidate_bytes=120 = CORRECT length (with the 2D-array spelling), 70 differing; the 1D spelling `D_8013C078[a3 * 4 + a4]` gives 116 (4 short) and 28 differing.
0x800AC9D8 56 near-match constant-materialisation-order
0X800ACA10 184 near-match candidate_bytes=188 vs 184 (one extra instruction). Structure solved: `v = 0; for (i=0;i<9;i++) if (D_80116E3C[i] == 1) { v = D_80116E84[i]; break; }`
0x800B0B88 - near-match -
0x800B34A4 88 near-match alloc-tiebreak (bit-index/base register pair a0/a1 vs cc1 a0/v1; 2 spellings both 80B; original keeps base in a1 via direct +0x10 load)
0X800B5C5C 88 near-match candidate_bytes=88 = CORRECT length, 61 differing bytes, first_difference=0x800B5C5C. Structure solved: int idx = g_80122068; int prev = g_8012277C; unsigned sh
0X800B6F64 232 near-match four variants, all named. (1) `int n = D_80122774;` as an INITIALISER before the call: 240 bytes -- cc1 hoists the global load ABOVE the call. (2) assigning `n
0X800BC658 148 near-match LENGTH-MISMATCH 140 vs 148 (35 vs 37 words). THE INLINE-ASM IDIOM IS PROVEN CORRECT: all eight stack-switch instructions (4 in the slow path, 4 in the join bloc
0X800BC6EC 236 deferred 0 NOT attempted beyond disassembly. The body switches the STACK POINTER to the PSX scratchpad and back: `lui at,0x1f80` + `sw sp,1020(at)` (saves sp t
0x800C1E54 - near-match -
0x800C3514 88 near-match alloc+layout (priority selector; original: all stack loads hoisted, beqz+nop+li groups, sltu first test; cc1 interleaves with bnez — 2 coordinator spellings 84B)
@@ -126,6 +149,7 @@
0x800F9134 - near-match -
0X800FB6D8 128 near-match differing_bytes=56 result=DIFF, 128 bytes both sides. Instruction MULTISET and register allocation are IDENTICAL (handler in s0, previous in v0, buffe
0X800FB758 112 near-match rare-epilogue-order-35b; residual 6B at 0x800FB7BC; tried default;--no-maspsx;--cc1 gcc-2.91.66-psx;--cc1 gcc-2.8.1-ps
0X800FBB20 128 near-match HARNESS ROW (finding-40 class). Structure fully solved; candidate_bytes=132 vs 128, only 3 differing instructions, and one of them is a HARNESS limitation. Sour
0x800FBD80 - near-match -
0x800FBDC0 - near-match -
0x800FBF5C 56 near-match -
@@ -144,6 +168,8 @@
0x80100334 - near-match -
0x8010036C 56 near-match rare-epilogue-order (F21)
0X801003A4 148 near-match candidate_bytes=180 vs 148 (32 over). Structure read off the original: two calls, then a 24-iteration loop over 72-byte records at 0x80145AC0 where th
0X80100438 208 deferred 2 2 attempts. Body fully understood: `func_800FFBBC(0)`, then a 24-iteration loop over 72-byte records at 0x80145AC0 with fields +12/+14 (short), +16 (short), +
0X801008DC 88 near-match candidate_bytes=88 = CORRECT length, 58 differing bytes, first_difference=0x801008E4. Structure solved: int *p = *(int **)(a0 + 28); if (p == 0) return 0; if (*
0x80100998 80 near-match -
0X80101764 212 near-match correct LENGTH (212) with `int one = 1;` used for `one << mask` and both loop comparisons, but 139 differing bytes. WITHOUT the `one` local: 216 bytes
0x80101C5C 32 blocked -
@@ -159,7 +185,9 @@
0x80107338 48 near-match no-frame-call (t0-t2 temps across call, ra via a3 — library asm family; cc1 builds a frame 88B)
0x80107874 40 near-match constant-base-in-register
0x80107B40 68 near-match -
0X80107C5C 112 near-match HARNESS ROW, ROOT CAUSE PINNED TO A SINGLE MASPSX PREDICATE - and this one looks like a one-line fix worth taking to the developer. candidate_bytes=108 vs 112,
0x80107CCC 76 near-match -
0X80107D7C 108 near-match candidate_bytes=112 vs 108 (4 over). Structure solved: int *p = (int *)D_801221C4; while (p != 0) { if (p[2] == a0 && p[3] == a1 && *(short *)(p + 5) == a2) { D
0x80108034 24 blocked gp-thunk
0x8010804C 16 blocked gp-thunk
0x80108578 56 near-match two-epilogue
1 # Syphon Filter 3 (USA) open negatives index.
8 0x80010810
9 0x8001084C
10 0x80011084
11 0X80011484
12 0x8001278C
13 0x80012834
14 0x80012A10
17 0x80012AE0
18 0x80012CFC
19 0x80012D54
20 0X80012E84
21 0x80013114
22 0x800161E0
23 0x80016224
24 0x80016268
25 0x80016E24
26 0X80016F80
27 0x800179E0
28 0x80017A38
29 0x80018284
34 0x8001EAFC
35 0x80022E44
36 0X800238BC
37 0X80023D40
38 0x800245D8
39 0x80024630
40 0x8002515C
41 0x800254B0
42 0X800256F0
43 0x80025758
44 0x800259DC
45 0x80025C08
48 0x800266A8
49 0X800268F4
50 0X80026A04
51 0X80027744
52 0x80027BE8
53 0X80028750
54 0X80029ED8
55 0x8002D014
56 0x8002D060
57 0X8002DE28
58 0X8002DF1C
59 0X8002E198
60 0X8002FB54
61 0X8002FD4C
62 0x80037984
63 0x800379E0
64 0x80039308
70 0x80042DD4
71 0x80042E10
72 0x80042E68
73 0X80043DC4
74 0x80045540
75 0x80045F00
76 0X80045FD8
79 0x8004820C
80 0x800496CC
81 0X8004D7C8
82 0X80050604
83 0X80050674
84 0x800518BC
85 0x800582AC
86 0X800589E8
87 0X80058AA8
88 0x8005DEF8
89 0x80065494
90 0X800667DC
91 0x800690E4
92 0x8006AD5C
93 0x8006AE04
98 0x8007374C
99 0x800751E8
100 0x8007A114
101 0X8007C408
102 0x8007E85C
103 0x800807E0
104 0x80085B44
105 0x8008A198
106 0x8008F478
107 0x80090990
108 0X80091490
109 0x80092104
110 0x80094370
111 0X80099D14
112 0x80099E34
113 0x8009B218
114 0x8009C69C
119 0x800A6C34
120 0x800A82D0
121 0x800A86B4
122 0X800A8920
123 0x800A8AEC
124 0x800AAC44
125 0X800AB504
126 0X800AC7A0
127 0x800AC9D8
128 0X800ACA10
129 0x800B0B88
130 0x800B34A4
131 0X800B5C5C
132 0X800B6F64
133 0X800BC658
134 0X800BC6EC
135 0x800C1E54
136 0x800C3514
149 0x800F9134
150 0X800FB6D8
151 0X800FB758
152 0X800FBB20
153 0x800FBD80
154 0x800FBDC0
155 0x800FBF5C
168 0x80100334
169 0x8010036C
170 0X801003A4
171 0X80100438
172 0X801008DC
173 0x80100998
174 0X80101764
175 0x80101C5C
185 0x80107338
186 0x80107874
187 0x80107B40
188 0X80107C5C
189 0x80107CCC
190 0X80107D7C
191 0x80108034
192 0x8010804C
193 0x80108578
+2
View File
@@ -314,6 +314,7 @@
0x800920DC 0x80092104 src/func_800920DC.c
0x80092130 0x8009214C src/func_80092130.c
0x8009214C 0x800921C0 src/func_8009214C.c
0x800923E8 0x80092460 src/func_800923E8.c
0x80093A08 0x80093A64 src/func_80093A08.c
0x80093A64 0x80093A84 src/func_80093A64.c
0x800943C4 0x800943E0 src/func_800943C4.c
@@ -352,6 +353,7 @@
0x800AA01C 0x800AA0A0 src/func_800AA01C.c
0x800AA2B4 0x800AA2F8 src/func_800AA2B4.c
0x800AA56C 0x800AA59C src/func_800AA56C.c
0x800AB078 0x800AB0EC src/func_800AB078.c
0x800AC818 0x800AC85C src/func_800AC818.c
0x800AC85C 0x800AC884 src/func_800AC85C.c
0x800AC98C 0x800AC9D8 src/func_800AC98C.c
1 # Code-region registry: one C region per matched function.
314 0x800920DC
315 0x80092130
316 0x8009214C
317 0x800923E8
318 0x80093A08
319 0x80093A64
320 0x800943C4
353 0x800AA01C
354 0x800AA2B4
355 0x800AA56C
356 0x800AB078
357 0x800AC818
358 0x800AC85C
359 0x800AC98C
+83
View File
@@ -0,0 +1,83 @@
/*
* func_800923E8 — 120 bytes at 0x800923E8..0x80092460
*
* Hypothesis, not a claim about meaning: a leaf initialiser. It dereferences a
* handle, rejects a null handle and a null payload, stamps a type byte and a
* halfword parameter into the payload, clamps four incoming values at zero
* (two of them to 4096 instead), writes them as four halfwords, clears a word
* and returns 1.
*
* The observed instructions are:
* lw v1,16(sp) v1 = 5th argument
* lw t0,20(sp) t0 = 6th argument
* beqz a0,0x80092458 if (a0 == 0) return 0;
* move v0,zero (delay slot)
* lw a0,12(a0) a0 = *(a0 + 12)
* nop (load delay)
* bnez a0,0x80092410 if (a0 != 0) goto body;
* li v0,2 (delay slot) the type byte
* j 0x80092458 return 0;
* move v0,zero (delay slot)
* sb v0,258(a0) body: *(char *)(a0 + 258) = 2
* bgez a2,0x80092420 if (a2 < 0) a2 = 0;
* sh a1,262(a0) (delay slot) *(short *)(a0 + 262) = a1
* move a2,zero
* bgez a3,0x8009242C if (a3 < 0) a3 = 4096;
* nop
* li a3,4096
* bgez v1,0x80092438 if (v1 < 0) v1 = 0;
* nop
* move v1,zero
* bgez t0,0x80092444 if (t0 < 0) t0 = 4096;
* li v0,1 (delay slot) the return value
* li t0,4096
* sh a2,280(a0) *(short *)(a0 + 280) = a2
* sh a3,282(a0) *(short *)(a0 + 282) = a3
* sh v1,284(a0) *(short *)(a0 + 284) = v1
* sh t0,286(a0) *(short *)(a0 + 286) = t0
* sw zero,340(a0) *(int *)(a0 + 340) = 0
* jr ra
* nop
*
* The signature has six parameters: the 5th and 6th arrive at 16(sp) and
* 20(sp) and are loaded into `v1` and `t0` once, at the top. Every clamp is a
* `bgez` (signed `>= 0`), so all four values are plain signed `int`s, narrowed
* only at the `sh` stores. The `li v0,2` in the `bnez` delay slot and the
* `li v0,1` in the last `bgez` delay slot are the type byte and the return
* value materialised early by reorg, not separate statements.
*
* LIMITS. Every offset (12, 258, 262, 280, 282, 284, 286, 340) is a raw
* disassembly reading with no evidence for a struct layout, so the payload is
* written as explicit pointer arithmetic. The handle's type, the meaning of the
* two 4096 clamps and the return codes are unknown.
*/
int func_800923E8(int a0, short a1, int a2, int a3, int a4, int a5)
{
int v1;
int t0;
v1 = a4;
t0 = a5;
if (a0 == 0)
return 0;
a0 = *(int *)(a0 + 12);
if (a0 == 0)
return 0;
*(char *)(a0 + 258) = 2;
*(short *)(a0 + 262) = a1;
if (a2 < 0)
a2 = 0;
if (a3 < 0)
a3 = 4096;
if (v1 < 0)
v1 = 0;
if (t0 < 0)
t0 = 4096;
*(short *)(a0 + 280) = a2;
*(short *)(a0 + 282) = a3;
*(short *)(a0 + 284) = v1;
*(short *)(a0 + 286) = t0;
*(int *)(a0 + 340) = 0;
return 1;
}
+78
View File
@@ -0,0 +1,78 @@
/*
* func_800AB078 — 116 bytes at 0x800AB078..0x800AB0EC
*
* Hypothesis, not a claim about meaning: a leaf that walks a singly-linked
* list of nodes (next at +24) and, for each node, scales down three bytes of a
* per-element record by one quarter (`b -= b >> 2`) for every element the
* node's halfword count at +12 describes. The record base comes from the
* node's first word, plus 6, and the stride is 16.
*
* The observed instructions are:
* move a3,a0 node = a0
* beqz a3,0x800AB0E4 while (node != 0) {
* nop
* lhu a2,12(a3) n = *(unsigned short *)((char *)node + 12)
* lw v1,0(a3) base = *(int *)node
* blez a2,0x800AB0D4 if (n > 0) {
* nop
* addiu a1,v1,6 p = (unsigned char *)(base + 6)
* addiu a2,a2,-1 i = n - 1
* lbu v0,-2(a1) p[-2] -= p[-2] >> 2
* lbu a0,-1(a1)
* srl v1,v0,0x2
* subu v0,v0,v1
* sb v0,-2(a1)
* srl v0,a0,0x2 p[-1] -= p[-1] >> 2
* lbu v1,0(a1)
* subu a0,a0,v0
* sb a0,-1(a1)
* srl v0,v1,0x2 p[0] -= p[0] >> 2
* subu v1,v1,v0
* sb v1,0(a1)
* bgtz a2,0x800AB098
* addiu a1,a1,16 (delay slot) p += 16
* lw a3,24(a3) } node = *(int *)((char *)node + 24)
* nop
* bnez a3,0x800AB084 }
* nop
* jr ra
* nop
*
* Every byte update is a read-modify-write of the form `b - (b >> 2)`, which
* is what `b -= b >> 2` emits: `lbu`, `srl 2`, `subu`, `sb`. The count is a
* halfword (`lhu`) and the list link is a word at byte offset 24, so the node
* is written with explicit pointer arithmetic rather than an invented struct.
*
* THE RECORD POINTER IS THE NODE'S BASE, NOT AN OFFSET ONE. Writing
* `p = (unsigned char *)(node[0] + 6)` with the accesses `p[-2]`, `p[-1]`,
* `p[0]` makes cc1's combine pass split it into TWO pointers (`addiu` to
* base+6 and base+5) and the region comes out 124 bytes, LENGTH-MISMATCH.
* Writing `p = (unsigned char *)node[0]` with `p[4]`, `p[5]`, `p[6]` reproduces
* the original's single `addiu a1,v1,6` base with -2/-1/0 offsets. The
* off-by-one in the base choice is what decides whether combine can split.
*
* LIMITS. Every offset (12, 24, and the record's -2/-1/0 with stride 16) is a
* raw disassembly reading; the node's other fields are unevidenced. The
* function returns nothing observable, so it is declared `void`.
*/
void func_800AB078(int a0)
{
int *node;
unsigned char *p;
int i;
int n;
node = (int *)a0;
while (node != 0) {
n = *(unsigned short *)((char *)node + 12);
p = (unsigned char *)node[0];
for (i = n; i > 0; i--) {
p[4] -= p[4] >> 2;
p[5] -= p[5] >> 2;
p[6] -= p[6] >> 2;
p += 16;
}
node = *(int *)((char *)node + 24);
}
}