diff --git a/config/regions.tsv b/config/regions.tsv index bee8a79..6f7ffbd 100644 --- a/config/regions.tsv +++ b/config/regions.tsv @@ -145,6 +145,7 @@ 0x80031FC4 0x800320D8 src/func_80031FC4.c 0x800320D8 0x800321EC src/func_800320D8.c 0x800321EC 0x800321F8 src/func_800321EC.c +0x80033DC8 0x80033F30 src/func_80033DC8.c 0x80034A80 0x80034ABC src/func_80034A80.c 0x800354F8 0x80035548 src/func_800354F8.c 0x80036134 0x8003622C src/func_80036134.c @@ -254,6 +255,7 @@ 0x80069580 0x800695D8 src/func_80069580.c 0x800697A4 0x800697C4 src/func_800697A4.c 0x800697C4 0x800697FC src/func_800697C4.c +0x8006A98C 0x8006AA10 src/func_8006A98C.c 0x8006AA10 0x8006AA88 src/func_8006AA10.c 0x8006ADB0 0x8006AE04 src/func_8006ADB0.c 0x8006B184 0x8006B1CC src/func_8006B184.c diff --git a/config/symbols.tsv b/config/symbols.tsv index bec6d5d..8a9f05f 100644 --- a/config/symbols.tsv +++ b/config/symbols.tsv @@ -381,6 +381,8 @@ D_80122718 0x80122718 gp D_8012271A 0x8012271A gp D_8012271C 0x8012271C gp D_80122720 0x80122720 gp +D_80122724 0x80122724 gp +D_80122728 0x80122728 gp D_8012272C 0x8012272C gp D_80122734 0x80122734 gp D_80122738 0x80122738 gp diff --git a/docs/MATCHING_COOKBOOK.md b/docs/MATCHING_COOKBOOK.md index a447331..97bad3e 100644 --- a/docs/MATCHING_COOKBOOK.md +++ b/docs/MATCHING_COOKBOOK.md @@ -1556,7 +1556,39 @@ block as the `then`** — `if (a0 != D1) { if (a0 == D2) k = 1; else k = -1; } e `x = a1[0] - a0[0]` matches; `x = -x` emits `negu` and **costs 8 bytes**. Also on that row: `y = ...` must be **assigned after** the x branch — an initialiser gives 124 B instead of 136. -### 102. Vector copies: FIVE independent confirmations +### 102. Vector copies: FIVE independent confirmations — but the rule STOPS before 128 bytes + +Struct-assignment vector copies (4 loads then 4 stores) are now confirmed on five independent rows: +`0x8005584C`, `0x8009F798`, `0x80036DA4`, `0x80036B14`, `0x800556E8`. + +**But worker B found the ceiling.** `0x800689DC` is a **fully unrolled 8x16-byte copy** (128 bytes, +`a0+64` -> `a0+0`, returns 1). This compiler will **not** produce that shape: + +| spelling | bytes | +|---|---| +| 128-byte struct assignment | 64-72 (a 4-word **loop**) | +| 8-iteration loop | 72 | +| 8 explicit statements | 264 (overshoots) | + +**So a 128-byte block move is LOOPED by this compiler — the original's unrolled form is not a +struct assignment.** The struct-assignment rule holds at 16/32 bytes but **not at 128**. If a +128-byte copy in the corpus has a matched source, its shape would settle the question. + +Companions, re-confirmed: **OR-chain `||` codegen** — every `bnez` jumps to the SAME forward target +and the last term inverts to skip the body; `t.v[0]=t.v[1]=t.v[2]=*a3` reads one address three times +with no CSE; and **byte offsets, not `int *` arithmetic** (`244(v0)` vs `p+244` = +976). + +### 103. Finding 42's "written order decides" has a COUNTEREXAMPLE (worker B) + +Worker B's `0x800321F8` (2 bytes): the operand order of two `addu`s is `addu v0,a0,v0` in the +original and `addu v0,v0,a0` from cc1. **Re-spelling the sum as `i*60 + a0` (writing `a0` second) +did NOT flip the encoding.** + +**Qualification for finding 42: when the base is a POINTER PARAMETER combined with an +already-scaled index, the operand order is decided elsewhere — by pseudo creation order — not by +the source.** Finding 42's rule applies to the arithmetic-operand case it was derived from, not +universally. + Struct-assignment vector copies (4 loads then 4 stores) are now confirmed on five independent rows: `0x8005584C`, `0x8009F798`, `0x80036DA4`, `0x80036B14`, `0x800556E8`. Treat it as a rule. @@ -1564,3 +1596,28 @@ Struct-assignment vector copies (4 loads then 4 stores) are now confirmed on fiv Companions, re-confirmed: **OR-chain `||` codegen** — every `bnez` jumps to the SAME forward target and the last term inverts to skip the body; `t.v[0]=t.v[1]=t.v[2]=*a3` reads one address three times with no CSE; and **byte offsets, not `int *` arithmetic** (`244(v0)` vs `p+244` = +976). + +### 104. The adjacency rule CROSSES partitions — the in-flight ledger (process) + +Finding 99 (take the row adjacent to one you just matched) is 4-for-4 across two workers and +beats every ranker, so it is now the primary dispatch rule. **But it has a structural cost:** +the worker partitions are disjoint, while adjacency sends a worker to a neighbour that is +frequently in **another** worker's partition. + +On `0x800320D8` **worker C and worker D both matched the same region independently**, and D +overwrote C's source file. Nothing was corrupted — both spellings are valid and the region +still reports `276 bytes / 0 differing / MATCH` — but a worker's whole effort was duplicated. + +**Root cause was mine, not the workers':** the partitions were verified disjoint (273/279/278/271, +union 1101 = sum), so no assignment error existed. The gap was that **no worker had any way to +know another had started a row.** The registry only knows about *merged* claims, and both +workers started before either was merged. + +**Fix: `.run/p11/inflight.tsv`, an append-only in-flight ledger.** Before starting a row, check +`config/regions.tsv` **and** the ledger; append your address before you begin. This is the +distributed-systems answer — the merge registry is the commit log, and this is the write-ahead +log. + +**Generalised lesson for the next orchestrator: a heuristic that makes workers more effective +can invalidate the assumption your work assignment rests on.** Adjacency optimised *which* row +to take without anyone revisiting *who owns it*. diff --git a/docs/ORCHESTRATOR_WORKFLOW.md b/docs/ORCHESTRATOR_WORKFLOW.md index 655c596..42ec4ee 100644 --- a/docs/ORCHESTRATOR_WORKFLOW.md +++ b/docs/ORCHESTRATOR_WORKFLOW.md @@ -383,3 +383,31 @@ Each of these was earned by a real incident. Quote them in charters; they are no - Do not commit ROM-derived material, build outputs, or session archives. - Do not close a herdr pane it did not create. - Do not estimate context, and do not accept an estimated context figure from a worker. + +## In-flight claim ledger (Phase 11 addition) + +**The problem.** The worker partitions are disjoint, but the **adjacency rule** — take the row +next to one you just matched — is the strongest dispatch heuristic found (4-for-4 across two +workers, beating the redundancy ranker and the size ranker). Adjacency **crosses partition +boundaries**, so a worker regularly reaches a row that belongs to another worker's partition. + +On `0x800320D8` worker C and worker D both matched the same region independently, and D +overwrote C's source file. Nothing was corrupted — both spellings match — but a worker's whole +effort was duplicated. **The partitions were verified disjoint** (273/279/278/271, union 1101 = +sum), so there was no assignment error. The gap was that **the registry only knows about MERGED +claims**, and both workers started before either was merged. + +**The protocol.** + +1. **Before starting a row**, check `config/regions.tsv` **and** `.run/p11/inflight.tsv`. + If the address is in either, pick a different row. +2. **Append your address to `.run/p11/inflight.tsv` before you begin** — `addressworkersize`. + Append only; never edit or remove a row. + +The registry is the **commit log**; the ledger is the **write-ahead log**. `.run/` is gitignored, +so the ledger is session-scoped by design — it coordinates live workers, not history. + +**The generalisable lesson: a heuristic that makes workers more effective can invalidate the +assumption the work assignment rests on.** Adjacency optimised *which* row to take without anyone +revisiting *who owns it*. When you adopt a worker-discovered heuristic, re-check what it assumes +about your own process. diff --git a/src/func_800320D8.c b/src/func_800320D8.c index 1016deb..ee95adb 100644 --- a/src/func_800320D8.c +++ b/src/func_800320D8.c @@ -1,97 +1,76 @@ /* * 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). + * Goal B, Phase 11. **Found by the ADJACENCY rule, not by the size ranker**: it sits 1048 bytes from + * this worker's 0x80031BBC match, and it is the same author's shape. Three spellings. * - * 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. + * What the body is: a 3-vector difference with two conditional 4-word loads. * - * 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 + * func_80031F78(a0, t); <- t is a 4-int local at sp+0x10 + * obj = *(int **)(a0 + 164); + * if (*(int *)((char *)obj + 976) == 0) + * v.a = *(int *)((char *)obj + 1032); + * else + * v = *(V4 *)((char *)obj + 1032); <- a 16-byte struct ASSIGNMENT + * obj = *(int **)(a0 + 164); <- re-read, per block + * if (*(int *)((char *)obj + 2272) == 0) + * w.a = *(int *)((char *)obj + 2328); + * else + * w = *(V4 *)((char *)obj + 2328); + * a1[0] = v.a - t[0]; + * a1[1] = v.b - t[1]; + * a1[2] = v.c - t[2]; + * a2[0] = w.c; * - * 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 LEVER — a struct ASSIGNMENT, not four element stores. Four indexed stores + * (`v[0] = ...; v[1] = ...; v[2] = ...; v[3] = ...`) compile to **load/store PAIRS interleaved with + * load-delay nops**; the original batches **four loads then four stores**. Writing + * `v = *(V4 *)((char *)obj + 1032);` reproduces the batch exactly. This is cookbook 28's + * "struct assignment, not element stores, is what puts a fixed base in a register" family, applied + * to a *copy out of* an object rather than into one — and the tell is the batch shape. * - * 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. + * The object pointer is re-read once per block (not once per statement, and not once per function): + * a macro-style spelling re-reads it for EVERY statement and comes out **80 bytes long**, and a + * single read for the whole function does not re-read at all. **One assignment per block is the + * shape.** The two `if` blocks leave `v.b`/`v.c` uninitialised on the single-word path, and the + * original then subtracts them — an uninitialised read that is a real property of the original, not a + * reconstruction error. * - * 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. + * LIMITS: the function name, the callee, the meaning of the two field blocks and of the 164/976/1032/ + * 2272/2328 offsets are hypotheses reconstructed from the disassembly; only the compiled bytes are + * evidence. `V4` is a 16-byte struct with only `.a`/`.b`/`.c` read; the 4-word copy is byte-required, + * the member names are not. The block offsets are recovered values. */ -struct V4 { int v[4]; }; +extern void func_80031F78(int a0, int *a1); -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; -}; +typedef struct { int a, b, c, d; } V4; -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) +void func_800320D8(int a0, int *a1, int *a2) { - int m[3][4]; + int t[4]; + V4 v; + V4 w; + int *obj; - 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]; + func_80031F78(a0, t); + + obj = *(int **)(a0 + 164); + if (*(int *)((char *)obj + 976) == 0) { + v.a = *(int *)((char *)obj + 1032); + } else { + v = *(V4 *)((char *)obj + 1032); + } + + obj = *(int **)(a0 + 164); + if (*(int *)((char *)obj + 2272) == 0) { + w.a = *(int *)((char *)obj + 2328); + } else { + w = *(V4 *)((char *)obj + 2328); + } + + a1[0] = v.a - t[0]; + a1[1] = v.b - t[1]; + a1[2] = v.c - t[2]; + a2[0] = w.c; } diff --git a/src/func_80033DC8.c b/src/func_80033DC8.c new file mode 100644 index 0000000..6c2dc8a --- /dev/null +++ b/src/func_80033DC8.c @@ -0,0 +1,100 @@ +/* + * func_80033DC8 — 360 bytes at 0x80033DC8..0x80033F30 + * + * Hypothesis, not a claim about meaning: builds two 3-component positions, offsetting each + * by the object's translation, and stores their difference into the caller's vector. The + * second position comes either from a helper call or from a fixed sub-object, depending on + * three flags. Completely repetitive (six identical add-into-frame groups), which is why it + * matched on the SECOND spelling. + * + * Shape (a3 = a0, s0 = a1): + * 27BDFFC8 addiu sp,sp,-56 + * 00803821 move a3,a0 + * AFBF0034 sw ra,52(sp) + * AFB00030 sw s0,48(sp) + * 8CE0009C lw v0,156(a3) ; p = *(int **)(a0 + 156) + * 00A08021 move s0,a1 + * 8C430090 lw v1,144(v0) ; v = *(V *)(p + 144) -- 4 loads then 4 stores + * 8C440094 lw a0,148(v0) + * 8C450098 lw a1,152(v0) + * 8C46009C lw a2,156(v0) + * AFA30010 sw v1,16(sp) + * ... AFA6001C sw a2,28(sp) + * 8FA20010 lw v0,16(sp) ; v[0] += a0[1191] + * 8CE1129C lw v1,4764(a3) + * 00431021 addu v0,v0,v1 + * AFA20010 sw v0,16(sp) + * ... ; v[1] += a0[1192], v[2] += a0[1193] + * 9080129A lbu v0,4762(a3) ; if (*(u8 *)(a0+4762)) -> the else + * 1440002E bnez v0,0x80033E7C + * 24020006 li v0,6 + * 8CE10134 lw v1,308(a3) ; a0[77] + * 14220027 bne v1,v0,0x80033E6C ; if (a0[77] != 6) -> THE CALL + * 00E02021 move a0,a3 ; (delay) + * 90800138 lbu v0,312(a3) ; if (*(u8 *)(a0+312)) -> the else + * 1440002B bnez v0,0x80033E7C + * 00000000 nop + * 0C00C7DE jal 0x80031F78 ; 0x80033E6C: func_80031F78(a0, w) + * 27A50020 addiu a1,sp,32 ; (delay) + * 0800CFB8 j 0x80033EE0 + * 00000000 nop + * 8CE0009C lw v0,156(a3) ; 0x80033E7C: w = *(V *)(p + 4) + * ... AFA6002C sw a2,44(sp) + * ... ; w[0..2] += a0[1191..1193] + * 8FA20010 lw v0,16(sp) ; a1[0] = v[0] - w[0] + * 8FA10020 lw v1,32(sp) + * 00431023 subu v0,v0,v1 + * AE020000 sw v0,0(s0) + * ... ; a1[1], a1[2] + * 8FBF0034 lw ra,52(sp) ; (END) + * + * **THE CONDITION IS `x == 0 && (y != 6 || z == 0)`, NOT `x == 0 && y == 6 && z == 0`.** + * The natural AND-of-three spelling gives the CORRECT LENGTH (360) and **7 differing + * bytes** — one branch displacement and the branch-delay placement — because it sends the + * `y == 6` failure to the else block, whereas the original sends it to the CALL. Reading + * the original's control flow literally: `bnez x, else; bne y,6, CALL; bnez z, else; CALL`. + * So the call runs when `x == 0 && (y != 6 || z == 0)`. **A correct-length candidate whose + * residual is a branch direction and a delay-slot instruction means the boolean STRUCTURE + * is wrong — check for a `||` hiding inside an `&&` (or vice versa) before touching + * anything else.** + * + * Also byte-required: both 4-word copies are STRUCT ASSIGNMENTS (seventh independent + * confirmation); the six add-into-frame groups are three separate `+=` statements per + * vector (each reloading both operands), not a loop. + * + * LIMITS: the function name, the callee, the object layout (the pointer at +156, the source + * vectors at +144 and +4, the translation at 4764/4768/4772, the flags at 4762/312 and the + * word at 308) and the whole "two positions and a difference" reading are hypotheses taken + * from the instruction shape; only the bytes are evidence. The `V` struct exists purely to + * obtain the 4-word block moves. func_80031F78 is not registered and is referenced by its + * address-named spelling. + */ + +struct V { int v[4]; }; + +void func_80031F78(int *, int *); + +void func_80033DC8(int *a0, int *a1) +{ + struct V v; + struct V w; + + v = *(struct V *)(*(char **)((char *)a0 + 156) + 144); + v.v[0] += a0[1191]; + v.v[1] += a0[1192]; + v.v[2] += a0[1193]; + + if (*(unsigned char *)((char *)a0 + 4762) == 0 && + (a0[77] != 6 || *(unsigned char *)((char *)a0 + 312) == 0)) { + func_80031F78(a0, (int *)&w); + } else { + w = *(struct V *)(*(char **)((char *)a0 + 156) + 4); + w.v[0] += a0[1191]; + w.v[1] += a0[1192]; + w.v[2] += a0[1193]; + } + + a1[0] = v.v[0] - w.v[0]; + a1[1] = v.v[1] - w.v[1]; + a1[2] = v.v[2] - w.v[2]; +} diff --git a/src/func_8006A98C.c b/src/func_8006A98C.c new file mode 100644 index 0000000..d35d335 --- /dev/null +++ b/src/func_8006A98C.c @@ -0,0 +1,77 @@ +/* + * func_8006A98C — 132 bytes at 0x8006A98C..0x8006AA10 + * + * Hypothesis, not a claim about meaning: adds one 3-component vector into another, twice, + * each guarded by a flag byte. A leaf with a completely repetitive body — which is why it + * matched on the FIRST spelling. + * + * Original words (int-index form; byte offsets are 4x): + * 8C83000C lw a3,12(a0) ; p = *(int **)(a0 + 12) + * 30A100FF andi a1,a1,0xff + * 10A0000E beqz a1,0x8006A9D0 ; if ((a1 & 0xff) == 0) skip + * 304200FF andi v0,a2,0xff ; (delay) the SECOND guard's test, hoisted + * 8C620050 lw v0,80(a3) ; p[20] += p[24] + * 8C630060 lw v1,96(a3) + * 8C640064 lw a0,100(a3) + * 8C650068 lw a1,104(a3) + * 00431021 addu v0,v0,v1 + * AC620050 sw v0,80(a3) + * 8C620054 lw v0,84(a3) ; p[21] += p[25] + * 8C630058 lw v1,88(a3) + * 00861021 addu v0,v0,a0 + * 00650821 addu v1,v1,a1 + * AC620054 sw v0,84(a3) + * AC630058 sw v1,88(a3) ; p[22] += p[26] + * 304200FF andi v0,a2,0xff ; 0x8006A9D0: + * 1040000B beqz v0,0x8006AA08 ; if ((a2 & 0xff) == 0) done + * 00000000 nop + * 8C6200D0 lw v0,208(a3) ; p[52] += p[56] + * 8C6300E0 lw v1,224(a3) + * 8C6400E4 lw a0,228(a3) + * 8C6500E8 lw a1,232(a3) + * 00431021 addu v0,v0,v1 + * AC6200D0 sw v0,208(a3) + * 8C6200D4 lw v0,212(a3) ; p[53] += p[57] + * 8C6300D8 lw v1,216(a3) + * 00861021 addu v0,v0,a0 + * 00650821 addu v1,v1,a1 + * AC6200D4 sw v0,212(a3) + * AC6300D8 sw v1,216(a3) ; p[54] += p[58] + * 03E00008 jr ra + * 00000000 nop + * + * BYTE-REQUIRED SHAPES: + * + * 1. **The guards are the raw `a1 & 0xff` / `a2 & 0xff` expressions**, not `(x & 0xff) != 0` + * — cc1 emits `andi` + `beqz` either way, but the first guard's DELAY SLOT holds the + * second guard's `andi v0,a2,0xff`, which only appears if the source re-evaluates the + * mask in the second `if`. + * 2. **The two vectors are 16 bytes apart in the stride sense** — the addends are at + * +80/+84/+88 and +96/+100/+104, i.e. one 4-int vector at int offset 20 and another at + * 24. That is the project's 4-int vector layout again (the same shape as 0x80068910 and + * the `struct V` rows). + * 3. **Three separate `+=` statements per vector**, each reloading both operands. The + * middle pair shares its loads (`addu v0,v0,a0` / `addu v1,v1,a1` before two stores), + * which is exactly what three independent statements produce. + * + * LIMITS: the function name, the object layout (the pointer at +12, the vectors at int + * offsets 20/24 and 52/56) and the meaning of the two flag bytes are hypotheses read from + * the instruction shape; only the bytes are evidence. No callee: this is a leaf and the + * harness emitted the common epilogue itself. + */ + +void func_8006A98C(int a0, int a1, int a2) +{ + int *p = *(int **)(a0 + 12); + + if (a1 & 0xff) { + p[20] += p[24]; + p[21] += p[25]; + p[22] += p[26]; + } + if (a2 & 0xff) { + p[52] += p[56]; + p[53] += p[57]; + p[54] += p[58]; + } +}