phase11: merge 13 + cookbook 73-78 — 522 bodies / 531 regions
Worker C's 0x80031FC4 (276 B). Cookbook gains six entries, the most important of which is worker C's correction of the COORDINATOR: a DEPENDENT row is one you cannot VERIFY, not one you have MATCHED. C's 0x800A613C and 0x800FD120 had symbol rows outstanding, but re-verifying against the tracked registry gave byte-identical results to the overlay runs -- both are still near-matches blocked on an ALLOCATION lever. Adding a symbol row unblocks the verification, not the match; conflating the two would have had a worker stop working a row it had not solved. Also recorded: the address-taken value may be a PARAMETER not a local (frame 8 too big with all offsets shifted by 8 is the tell); address-taken form forces a register; the struct assignment is what BATCHES the loads where element stores serialise behind maspsx nops; and the cop2 operand is the 25-bit field (0x486012 -> 0x4A486012).
This commit is contained in:
@@ -140,6 +140,7 @@
|
||||
0x80031BBC 0x80031CC0 src/func_80031BBC.c
|
||||
0x80031F2C 0x80031F78 src/func_80031F2C.c
|
||||
0x80031F78 0x80031FC4 src/func_80031F78.c
|
||||
0x80031FC4 0x800320D8 src/func_80031FC4.c
|
||||
0x800321EC 0x800321F8 src/func_800321EC.c
|
||||
0x80034A80 0x80034ABC src/func_80034A80.c
|
||||
0x800354F8 0x80035548 src/func_800354F8.c
|
||||
|
||||
|
@@ -1127,3 +1127,150 @@ size has not been the difficulty — the absence of a lever was.**
|
||||
|
||||
Recording the *direction* of a failure ("a source-shape lever exists and here it is") is more useful than
|
||||
recording it as a class, because the next worker knows whether to retry.
|
||||
|
||||
### 67. `mult` + `mfhi` + `sra` with NO `mflo` is a CONSTANT DIVISION, not a 64-bit multiply (worker D)
|
||||
|
||||
`lui v1,0x4bda / ori v1,v1,0x12f7 / sll v0,v0,0xc / mult v0,v1 / mfhi t0 / sra v1,t0,0xd`
|
||||
reads like a fixed-point `(x << 12) * 0x4BDA12F7 >> 45`. **It is not.** It is cc1's
|
||||
magic-number expansion of `(x << 12) / 27648`, where `0x4BDA12F7 = ceil(2^45 / 27648)`.
|
||||
|
||||
**The discriminator is the ABSENCE of `mflo`.** Every available cc1 emits `mfhi` **and**
|
||||
`mflo` for a genuine 64-bit multiply — probed on open `gcc-2.7.2-psx` and the real Sony
|
||||
`CC1PSX` 4.0 and 4.6. So the tell is checkable in one glance.
|
||||
|
||||
**Recover the divisor from the magic, never from the constant's face value:**
|
||||
`D = ceil(2^(32+s) / M)`. Worker D burned four spellings writing the multiply before
|
||||
solving for `D` and getting 27648 — which is also the second argument of all six
|
||||
`func_80010654` calls in the same function. Probe `int f(int x) { return (x << 12) / 27648; }`
|
||||
reproduces the triple exactly.
|
||||
|
||||
Sign side: a *signed* division of a provably non-negative value gives `mult`/`sra` with no
|
||||
sign correction — which is why an `unsigned short` load shifted left 12 needs no fixup.
|
||||
An `addu` + `subu` sign-correction pair instead indicates the **negative-magic** form.
|
||||
|
||||
**Why this matters for dispatch:** this class is nearly disjoint from the corpus (8 rows in
|
||||
worker D's partition, **0 of 522 registered**) — the third such class this phase, after
|
||||
trapping-arith and division-check. But it differs in kind: those two need a different
|
||||
compiler build; this one is unmatched **because it is misread**. Any row previously
|
||||
classified as a "64-bit multiply residual" is suspect and should be re-read.
|
||||
|
||||
### 68. The dispatch signal is REDUNDANCY, not size — measured 6-to-0 (worker C)
|
||||
|
||||
Across worker C's 15 above-ceiling rows, **"does the body contain a repeated block?" has
|
||||
out-performed "how big is it?" six to zero.**
|
||||
|
||||
- Every one of C's six matches is a **repetitive or mechanically-determined** body: a
|
||||
struct assignment, a repeated identical block, a record initialiser, a transposition,
|
||||
a repeated bounds check.
|
||||
- Every one of C's nine near-misses is **tie-break-dense**: a long straight-line call
|
||||
sequence, a pointer-walk loop, a table walk.
|
||||
|
||||
Worker D's rows say the same thing from the other side — its 700 B and 1232 B bodies cost
|
||||
**3 and 2** spellings while its 248 B body cost **4**. And the two largest bodies were the
|
||||
cheapest after the 264 B one.
|
||||
|
||||
**This supersedes size-band reasoning (finding 58).** The cost of a row is set by tie-break
|
||||
density; size is incidental. Median attempts to a *match* for C was 3.5; median attempts
|
||||
before classifying a *near-match* was 4 — so the near-misses are cheap to identify and the
|
||||
matches are cheap to close, and what the band actually costs is the 60% that never closes.
|
||||
|
||||
### 69. The `char[4]` struct-copy loop is the block-move idiom (worker C)
|
||||
|
||||
An explicit `for (i = 0; i < 40; i++) dst[i] = src[i];` over a **4-byte `char[4]` struct**
|
||||
yields `lwl`/`lwr` + `swl`/`swr` with **no runtime check**. A 160-byte struct assignment
|
||||
instead makes cc1 emit a runtime alignment test plus two loops (344 B, +96). `char[160]`
|
||||
and `short[80]` both do this.
|
||||
|
||||
### 70. Two cheap tells (worker C)
|
||||
|
||||
- **A `sb`/`lbu` round trip for a result flag means the flag is a STRUCT MEMBER** — a bare
|
||||
local would stay in a register.
|
||||
- **A local holding a constant is not free.** Naming `127` forced an `s1` save and grew the
|
||||
frame 48 → 56; the original keeps 48 with literal 127s. Frame arithmetic catches it
|
||||
immediately.
|
||||
|
||||
### 71. A guard whose test must branch INTO the body: spell it `do`/`while` (worker D)
|
||||
|
||||
Worker D's `0x800FF5A8`: the original's entry guard is `beqz v0` (count == 0) where the
|
||||
naive `for` gives `blez v0` — this build infers `0 <= n` from an `lbu` and folds `0 < n`
|
||||
to `n <= 0`. Spelling the loop as a `do`/`while` with an explicit
|
||||
`if (((unsigned char *)a0)[7] != 0)` guard produces `beqz` and matches.
|
||||
|
||||
**And the asymmetry is the point:** the guard must test `a0` while the loop bound tests a
|
||||
named local `q` — and *that same asymmetry* produces the second pointer register below.
|
||||
Two mechanisms, one source change.
|
||||
|
||||
### 72. Named-locals family: FOURTH mechanism, OPPOSITE polarity (worker D)
|
||||
|
||||
The three recorded cases are all **"name a local so cc1 does NOT reload."** This one is
|
||||
**"name a local so cc1 MUST keep a second copy."**
|
||||
|
||||
Worker D's `0x800FF5A8`: the original keeps `a0` in **two** callee-saved registers (`s2`
|
||||
for the magic check / entry count / unlink, `s3` for the in-loop count re-read). The lever:
|
||||
name **two separate pointer locals that both hold `a0` and are both live across the call**
|
||||
(`q` for the loop bound, `p` for the walk). The allocator cannot coalesce pseudos with
|
||||
overlapping live ranges, so the second register appears.
|
||||
|
||||
**Generalised diagnostic (worker D's 1232 B row):** a residual that is **ONE INSTRUCTION
|
||||
REPEATED AT EVERY ONE OF N SITES** ⇒ the original hoisted a sub-expression into a named
|
||||
local. At N=32 that is a 4-byte residual that a per-site diff misreads as noise.
|
||||
|
||||
Also from that row: the LAST block of a 32-way bit-flag dispatch needs a **signed** test —
|
||||
`flags & 0x80000000` (the literal, unsigned in C) gives `and`/`bnez`, while the original's
|
||||
`bgez a1` is what `flags < 0` produces. Same semantics, different encoding.
|
||||
|
||||
### 73. "DEPENDENT" means you cannot VERIFY it — not that you have MATCHED it (worker C)
|
||||
|
||||
Worker C's correction, and it is a discipline point worth keeping: a **dependent** row is
|
||||
one whose verification is blocked on a registry row. It is **not** a row that is waiting to
|
||||
be matched. Worker C's `0x800A613C` and `0x800FD120` had symbol requests outstanding, but
|
||||
re-verifying them against the tracked registry once the rows existed gave **byte-identical
|
||||
results to the overlay runs** — both are still near-matches, blocked on an *allocation*
|
||||
lever, not on a symbol row.
|
||||
|
||||
**So adding a symbol row unblocks the VERIFICATION, not the MATCH.** Conflating the two
|
||||
would have had a worker stop working a row it had not actually solved.
|
||||
|
||||
### 74. The address-taken value may be a PARAMETER, not a local (worker B)
|
||||
|
||||
Worker B's `0x80045F1C`: the frame is only **40 bytes** yet the body touches `sp+56`, and the
|
||||
address passed to the first callee is `&a4` — the **fifth parameter**, whose home is the
|
||||
caller's outgoing-argument area at `frame+16` — not a local. Modelling it as a local `short`
|
||||
reproduces the same instruction *shape* with a **48-byte frame and every offset +8** (22
|
||||
differing bytes).
|
||||
|
||||
**Diagnostic: "right shape, frame 8 too big, all offsets shifted by 8" ⇒ check whether the
|
||||
address-taken value is a parameter rather than a local.**
|
||||
|
||||
### 75. Address-taken form forces a register (worker B)
|
||||
|
||||
`int *p = &D_8013C330;` gives `la` into a saved register plus two `lw a0,0(s0)`; reading the
|
||||
symbol directly gives 64 bytes (8 short) with two macro pairs and **no saved register**. This
|
||||
is the *source-forced* counterpart of the `-G` route in finding 47.
|
||||
|
||||
### 76. The struct assignment is what BATCHES the loads (worker C)
|
||||
|
||||
Worker C's `0x80031FC4`: four element stores **serialise** behind four maspsx load-delay nops
|
||||
(+16 bytes), whereas `*(struct V4 *)m[1] = v1` gives the original's four **batched** loads.
|
||||
So where a copy appears as separate element stores and the original has batched loads, the
|
||||
source used a struct assignment.
|
||||
|
||||
Companion, same row: **an expression repeated in two blocks must NOT be bound to a local** if
|
||||
the stores in between can alias it — `a0->f164` is re-read for the second block, and a local
|
||||
`struct T *t` comes out 1 instruction short (268 vs 276).
|
||||
|
||||
### 77. Invert the condition and swap the arms to place the block (worker C)
|
||||
|
||||
Third instance this session of the mirrored-layout lever (findings 28/43): to get `bnez`
|
||||
branching **to** the four-word block, the condition is written **inverted with the arms
|
||||
swapped** — the single store inline, the copy out of line. Applied per guard.
|
||||
|
||||
### 78. `cop2` operand encoding (worker A)
|
||||
|
||||
`cop2 0x486012` assembles to exactly `0x4A486012` — the operand is the **25-bit field**, not
|
||||
26 — while `cop2 0x2486012` is an operand-range error. Also: `-fno-schedule-insns2` (reorg
|
||||
off) shows the RTL order is stores-then-copy for worker A's GTE row, so that residual is the
|
||||
**second** scheduler, and `-fno-schedule-insns` does **not** change it.
|
||||
|
||||
And a byte-required operand order: `a0 == D_8012204C` matches where `D_8012204C == a0` does
|
||||
not (worker A, `0x800B62C8`, 2 bytes).
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
* func_800320D8 — 276 bytes at 0x800320D8..0x800321EC
|
||||
*
|
||||
* Phase 11, band ABOVE the old 244-byte ceiling. First-attempt match, taken
|
||||
* directly from its sibling func_80031FC4 (same body, same local layout, same
|
||||
* callee shape — only the object offsets and the callee address differ).
|
||||
*
|
||||
* Fills one 4-word row from a helper, then fills two more rows from two
|
||||
* optional 4-word blocks of an object — each block either copied whole or as
|
||||
* its first word only, gated by a separate flag — and finally writes three
|
||||
* component differences plus one raw component.
|
||||
*
|
||||
* The observed instructions are:
|
||||
* addiu sp,sp,-80 / sw s0,64(sp) / move s0,a0 / sw s1,68(sp) / move s1,a1
|
||||
* sw s2,72(sp) / move s2,a2 / sw ra,76(sp)
|
||||
* jal func_80031F78 / addiu a1,sp,16
|
||||
* lw a2,164(s0)
|
||||
* lw v0,976(a2) / nop / bnez v0,full1 / nop
|
||||
* lw v0,1032(a2) / j join1 / sw v0,32(sp)
|
||||
* full1:
|
||||
* lw v0,1032(a2) / lw v1,1036(a2) / lw a0,1040(a2) / lw a1,1044(a2)
|
||||
* sw v0,32(sp) / sw v1,36(sp) / sw a0,40(sp) / sw a1,44(sp)
|
||||
* join1:
|
||||
* lw a2,164(s0)
|
||||
* lw v0,2272(a2) / nop / bnez v0,full2 / nop
|
||||
* lw v0,2328(a2) / j join2 / sw v0,48(sp)
|
||||
* full2:
|
||||
* lw v0,2328(a2) / lw v1,2332(a2) / lw a0,2336(a2) / lw a1,2340(a2)
|
||||
* sw v0,48(sp) / sw v1,52(sp) / sw a0,56(sp) / sw a1,60(sp)
|
||||
* join2:
|
||||
* lw v0,32(sp) / lw v1,16(sp) / nop / subu v0,v0,v1 / sw v0,0(s1)
|
||||
* lw v0,36(sp) / lw v1,20(sp) / nop / subu v0,v0,v1 / sw v0,4(s1)
|
||||
* lw v0,40(sp) / lw v1,24(sp) / nop / subu v0,v0,v1 / sw v0,8(s1)
|
||||
* lw v0,56(sp) / nop / sw v0,0(s2)
|
||||
* lw ra,76(sp) / lw s2,72(sp) / lw s1,68(sp) / lw s0,64(sp)
|
||||
* addiu sp,sp,80 / jr ra / nop
|
||||
*
|
||||
* The four byte-required facts are the same four as its sibling func_80031FC4,
|
||||
* and each was established there:
|
||||
* 1. `a0->f164` is RE-READ for the second block (a local pointer is one
|
||||
* instruction short) because the stores into the local matrix can alias it.
|
||||
* 2. The condition is INVERTED with the arms swapped, so the branch is `bnez`
|
||||
* TO the four-word block with the single store inline (mirrored-layout
|
||||
* lever, per guard).
|
||||
* 3. The four-word copy is a STRUCT assignment, which batches the loads; four
|
||||
* element stores serialise them behind four maspsx load-delay nops.
|
||||
* 4. The matrix is 3x4 with a 16-byte row stride; the fourth column is never
|
||||
* read.
|
||||
*
|
||||
* THE FAMILY LEVER: this row is the sibling of func_80031FC4 and shares its
|
||||
* callee family (func_80031F78 vs func_80031F2C). Only the object offsets
|
||||
* changed — the two flags stay at +976 and +2272, while the two 4-word blocks
|
||||
* move to +1032 and +2328 (48 bytes later than the sibling's +984/+2280) — so
|
||||
* the body, the local matrix, the branch polarities and the epilogue are
|
||||
* identical and the match came on the first spelling.
|
||||
*
|
||||
* LIMITS: offsets and widths are read off the disassembly; everything between
|
||||
* the named fields is padding reconstructed only to fix the offsets, and the
|
||||
* real structures are certainly larger. `struct S`'s `+164` is a pointer. The
|
||||
* callee's signature is inferred from its single call site.
|
||||
*/
|
||||
|
||||
struct V4 { int v[4]; };
|
||||
|
||||
struct T {
|
||||
char pad0[976];
|
||||
int f976;
|
||||
char pad_a[1032 - 980];
|
||||
struct V4 v1;
|
||||
char pad1[2272 - 1048];
|
||||
int f2272;
|
||||
char pad_b[2328 - 2276];
|
||||
struct V4 v2;
|
||||
};
|
||||
|
||||
struct S { char pad[164]; struct T *f164; };
|
||||
|
||||
extern void func_80031F78(struct S *a, int *b);
|
||||
|
||||
void func_800320D8(struct S *a0, int *a1, int *a2)
|
||||
{
|
||||
int m[3][4];
|
||||
|
||||
func_80031F78(a0, m[0]);
|
||||
if (a0->f164->f976 == 0)
|
||||
m[1][0] = a0->f164->v1.v[0];
|
||||
else
|
||||
*(struct V4 *)m[1] = a0->f164->v1;
|
||||
if (a0->f164->f2272 == 0)
|
||||
m[2][0] = a0->f164->v2.v[0];
|
||||
else
|
||||
*(struct V4 *)m[2] = a0->f164->v2;
|
||||
a1[0] = m[1][0] - m[0][0];
|
||||
a1[1] = m[1][1] - m[0][1];
|
||||
a1[2] = m[1][2] - m[0][2];
|
||||
a2[0] = m[2][2];
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* func_800910BC — 280 bytes at 0x800910BC..0x800911D4
|
||||
*
|
||||
* Goal B, Phase 11. Four calls, seven gp-relative globals, three constant divisions — and it is the
|
||||
* row that sharpened the constant-division diagnostic into something unambiguous.
|
||||
*
|
||||
* What the body is: a toggling spawn/state routine.
|
||||
*
|
||||
* r = func_800F6F60() % 12; s1 = r + 6; <- the default delay
|
||||
* if (D_80122674 == 1) { <- the toggle
|
||||
* func_8008FCD8(D_80122660, D_80122670);
|
||||
* if (func_800F6F60() % 10 >= 6)
|
||||
* s1 = func_800F6F60() % 5 + 1;
|
||||
* } else {
|
||||
* func_8008FA58(D_80122660, D_80122670, D_80121EEC, D_80121EE8);
|
||||
* }
|
||||
* D_80121EF4 = s1;
|
||||
* D_80121EF0 = *(int *)0x801223B8;
|
||||
* D_80122674 ^= 1;
|
||||
*
|
||||
* THE DIVISIONS AND THE AMBIGUITY THAT COST A SPELLING.
|
||||
*
|
||||
* /12 : magic 0x2AAAAAAB, mfhi then sra 1
|
||||
* /10 : magic 0x66666667, mfhi then sra 2
|
||||
* /5 : magic 0x66666667, mfhi then sra 1
|
||||
*
|
||||
* 0x2AAAAAAB is the well-known magic for a division by 6 at shift 0, and reading it as 6 was the
|
||||
* first spelling's error — worth one instruction of codegen difference at 0x800910E4. The identity is
|
||||
* D = 2^(32+s) / M, and THE MAGIC ALONE IS AMBIGUOUS: 0x2AAAAAAB gives 6 at s=0, 12 at s=1, 24 at
|
||||
* s=2. Always take the shift from the `sra` that follows `mfhi`; the magic is only half the identity.
|
||||
* The same magic appearing twice (0x66666667 for both 10 and 5) is not a contradiction for the same
|
||||
* reason, and cc1 materialises it ONCE into a callee-saved register and reuses it for both.
|
||||
*
|
||||
* THE NAMED-LOCAL DIRECTION AGAIN, AND THIS ROW IS DECIDED BY IT.
|
||||
* Naming the second division's result — `r = func_800F6F60() % 10; if (r >= 6) ...` — costs 2 words:
|
||||
* the original keeps the remainder in v0 because it is consumed directly by the `slti`, while the
|
||||
* named local forces a0. The FIRST division's result may be named, because it is used twice
|
||||
* (`s1 = r + 6`), and there it lands in a0 in both. The rule is per-site: name a result only where
|
||||
* the original reuses it; where the original consumes it immediately, leave the expression inline.
|
||||
* Inlining the second one is what closed the row.
|
||||
*
|
||||
* LIMITS: the function name, the callees, the meaning of the seven globals and the claim that the
|
||||
* routine is a spawn delay selector are hypotheses reconstructed from the disassembly; only the
|
||||
* compiled bytes are evidence. D_80122674 is byte-accessed (lbu/sb) and so is declared
|
||||
* `unsigned char`; 0x801223B8 is accessed ABSOLUTELY (lui + lw, not gp-relative) and is therefore
|
||||
* written as a literal address, which is what gives the original's absolute encoding (cookbook 46).
|
||||
* The three divisors are recovered from the magic-plus-shift pairs, not interpreted.
|
||||
*/
|
||||
|
||||
extern unsigned char D_80122674;
|
||||
extern int D_80122660;
|
||||
extern int D_80122670;
|
||||
extern int D_80121EF4;
|
||||
extern int D_80121EF0;
|
||||
extern int D_80121EEC;
|
||||
extern int D_80121EE8;
|
||||
extern int func_800F6F60(void);
|
||||
extern void func_8008FCD8(int a0, int a1);
|
||||
extern void func_8008FA58(int a0, int a1, int a2, int a3);
|
||||
|
||||
void func_800910BC(void)
|
||||
{
|
||||
int s1;
|
||||
int r;
|
||||
|
||||
r = func_800F6F60() % 12;
|
||||
s1 = r + 6;
|
||||
|
||||
if (D_80122674 == 1) {
|
||||
func_8008FCD8(D_80122660, D_80122670);
|
||||
if (func_800F6F60() % 10 >= 6) {
|
||||
s1 = func_800F6F60() % 5 + 1;
|
||||
}
|
||||
} else {
|
||||
func_8008FA58(D_80122660, D_80122670, D_80121EEC, D_80121EE8);
|
||||
}
|
||||
|
||||
D_80121EF4 = s1;
|
||||
D_80121EF0 = *(int *)0x801223B8;
|
||||
D_80122674 ^= 1;
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* func_8009F798 — 248 bytes at 0x8009F798..0x8009F890
|
||||
*
|
||||
* Hypothesis, not a claim about meaning: rewrites a 3x3 array of shorts in place. The
|
||||
* body is fully unrolled and completely repetitive — 9 loads, 6 negations, 9 stores,
|
||||
* then 9 stores back — which is why it matched in three spellings.
|
||||
*
|
||||
* Shape (all offsets in words; the temp array's layout is the whole point, see below):
|
||||
* 0000 lh v0,0(a0) ; phase 1: copy, column-major order
|
||||
* 0004 addiu sp,sp,-48
|
||||
* 0008 sw v0,0(sp) ; m[0][0] = a0[0]
|
||||
* 000c lh v0,6(a0)
|
||||
* 0010 nop
|
||||
* 0014 sw v0,4(sp) ; m[0][1] = a0[3]
|
||||
* 0018 lh v0,12(a0)
|
||||
* 001c nop
|
||||
* 0020 sw v0,8(sp) ; m[0][2] = a0[6]
|
||||
* 0024 lh v0,2(a0)
|
||||
* 0028 nop
|
||||
* 002c sw v0,16(sp) ; m[1][0] = a0[1]
|
||||
* ... (column-major: (r,c) = (0,0),(1,0),(2,0),(0,1),(1,1),(2,1),(0,2),(1,2),(2,2))
|
||||
* 0068 sw v0,16(sp) ; phase 2: negate the c != 0 columns and STORE AGAIN
|
||||
* ... (6 negated stores, same slots as their raw stores)
|
||||
* 0088 sh t0,0(a0) ; phase 3: write back, column-major again
|
||||
* 00f0 addiu sp,sp,48
|
||||
* 00f4 jr ra
|
||||
* 00f8 nop
|
||||
*
|
||||
* **THE TEMP ARRAY IS INDEXED TRANSPOSED.** The original's slot for matrix cell (r,c) is
|
||||
* `4*r + 16*c` — a 16-byte stride on the SECOND index. So `int m[3][4]` must be written
|
||||
* `m[c][r]` (the first index is the column), not `m[r][c]`. Natural `m[r][c]` indexing
|
||||
* gives the CORRECT LENGTH (248) and 15 differing bytes, all of them stack offsets: the
|
||||
* slot permutation is the only residual, which is the clean diagnostic for this class
|
||||
* (cookbook 59). With the declaration and the transposed use, the row is exact.
|
||||
*
|
||||
* **THE RAW STORES ARE DEAD AND SURVIVE.** Phase 1 stores the un-negated value and phase 2
|
||||
* negates the slot and stores it a second time; GCC 2.7.2 has no pass that removes the
|
||||
* first store, so both appear. Writing `m[c][r] = -a0[3*r + c];` directly (one statement
|
||||
* per cell) is 244 bytes LENGTH-MISMATCH — the negations then sit immediately after their
|
||||
* own loads instead of in a second pass. Same property as the `buf[1]` double store in
|
||||
* func_80027CA0; it is a general tell that a "redundant" store in the original is a real
|
||||
* second source statement, not a codegen artefact.
|
||||
*
|
||||
* LIMITS: the function name, the element type (short), the 3x3 extent, the in-place
|
||||
* rewrite and the sign pattern are hypotheses read from the instruction shape; only the
|
||||
* bytes are evidence. The `m[c][r]` form is a byte-required spelling, not a claim that the
|
||||
* original source was written that way — the same bytes come from any declaration whose
|
||||
* (r,c) slot is 4*r + 16*c. No callee: this is a leaf, and the harness emitted the common
|
||||
* epilogue itself.
|
||||
*/
|
||||
|
||||
void func_8009F798(short *a0)
|
||||
{
|
||||
int m[3][4];
|
||||
|
||||
m[0][0] = a0[0];
|
||||
m[0][1] = a0[3];
|
||||
m[0][2] = a0[6];
|
||||
m[1][0] = a0[1];
|
||||
m[1][1] = a0[4];
|
||||
m[1][2] = a0[7];
|
||||
m[2][0] = a0[2];
|
||||
m[2][1] = a0[5];
|
||||
m[2][2] = a0[8];
|
||||
|
||||
m[1][0] = -m[1][0];
|
||||
m[1][1] = -m[1][1];
|
||||
m[1][2] = -m[1][2];
|
||||
m[2][0] = -m[2][0];
|
||||
m[2][1] = -m[2][1];
|
||||
m[2][2] = -m[2][2];
|
||||
|
||||
a0[0] = m[2][0];
|
||||
a0[3] = m[2][1];
|
||||
a0[6] = m[2][2];
|
||||
a0[1] = m[0][0];
|
||||
a0[4] = m[0][1];
|
||||
a0[7] = m[0][2];
|
||||
a0[2] = m[1][0];
|
||||
a0[5] = m[1][1];
|
||||
a0[8] = m[1][2];
|
||||
}
|
||||
Reference in New Issue
Block a user