phase12: merge 28 (679 bodies) + the per-region codegen-flag policy, and A's semantics-not-codegen correction
This commit is contained in:
@@ -495,6 +495,7 @@
|
||||
0x800AA2B4 0x800AA2F8 src/func_800AA2B4.c
|
||||
0x800AA56C 0x800AA59C src/func_800AA56C.c
|
||||
0x800AB078 0x800AB0EC src/func_800AB078.c
|
||||
0x800AB504 0x800AB598 src/func_800AB504.c cc1=-fno-strength-reduce
|
||||
0x800AC818 0x800AC85C src/func_800AC818.c
|
||||
0x800AC85C 0x800AC884 src/func_800AC85C.c
|
||||
0x800AC98C 0x800AC9D8 src/func_800AC98C.c
|
||||
@@ -630,6 +631,7 @@
|
||||
0x80101838 0x80101878 src/func_80101838.c
|
||||
0x80101C2C 0x80101C54 src/func_80101C2C.c
|
||||
0x80101C5C 0x80101C7C src/func_80101C5C.c
|
||||
0x80101C7C 0x80101CAC src/func_80101C7C.c cc1=-fno-delayed-branch
|
||||
0x80101CAC 0x80101CDC src/func_80101CAC.c
|
||||
0x801027CC 0x801027F8 src/func_801027CC.c
|
||||
0x80102A00 0x80102A80 src/func_80102A00.c maspsx=regread
|
||||
|
||||
|
@@ -1145,6 +1145,37 @@ place for an unmeasured widening. Results on the row: default 124 (LENGTH-MISMAT
|
||||
The tracked patch `tools/patches/maspsx-phase10-r1r2.patch` was **regenerated in the same commit** —
|
||||
the exact step Phase 11's `ea51ac9` forgot, which left a fresh clone unable to rebuild the gate.
|
||||
|
||||
### 61e. WHERE THE ARGUMENT REGISTER IS RE-ARMED tells you the statement order (read, do not spell)
|
||||
|
||||
Phase 12, worker A, `0x800AB504` (148 B). A loop that both **advances a pointer** and **passes it to a
|
||||
call** has exactly two candidate orders, and the bytes decide it before any spelling is tried:
|
||||
|
||||
> **Find where the argument register is written. If it is armed TWICE — once before the loop and once at
|
||||
> the BACK EDGE, after the pointer increment — then the call is made with the CURRENT record and the
|
||||
> pointer is advanced AFTERWARDS.**
|
||||
|
||||
```c
|
||||
p[6] = p[5] = p[4] = 0; /* three statements, in that order */
|
||||
func_8001289C(D_80122720, p); /* called with the current record */
|
||||
p += 16; /* advanced afterwards */
|
||||
```
|
||||
|
||||
A's first version had `p += 16;` **before** the call. That is not a different schedule — **it is a
|
||||
different pointer on every iteration**, a semantic error — but it looked merely *close* (168 vs 148)
|
||||
**because cc1 reorders the independent increment above the call anyway.** The lesson is A's own:
|
||||
***"I read the bytes for the codegen and not for the semantics."***
|
||||
|
||||
**The general form, and it is why this is a reading technique rather than a row:** a copy, a reload, or a
|
||||
duplicated register write in the ORIGINAL is evidence about LIVENESS and ORDER, not noise to be
|
||||
explained away by the allocator. Worker D found the same shape three times in one band
|
||||
(`0x80011084`'s `move t0,a0`, `0x80019700`'s `move v1,v0`, `0x80016E24`'s double load) and recorded the
|
||||
rule as: **a copy in the original means the value must survive something the source does not obviously
|
||||
say.**
|
||||
|
||||
**And a consequence for rulings:** `0x800AB504` was ruled class-bound on the grounds that its flag got
|
||||
"152 against 148". The 152 was A's bug. **A ruling of the form "the lever does not reach the target" must
|
||||
be re-checked whenever the candidate it rested on is corrected.**
|
||||
|
||||
### 62. Fail fast on an invalid region row
|
||||
|
||||
A worker placed the source md5 in a claim row's 4th column. `sf3_merge` passed it through as a region
|
||||
|
||||
@@ -1451,3 +1451,79 @@ the comment early and produced `parse error before 'the'`. D's guard is now: **c
|
||||
any header, before believing anything**, and D corrected `report.tsv`'s md5 to the fixed file. Recorded
|
||||
for the roster because the failure surfaces as a one-line parse error in `candidate.i` and looks nothing
|
||||
like a comment problem.
|
||||
|
||||
---
|
||||
|
||||
## ORCHESTRATOR RULING: per-region CODEGEN FLAGS (asked for once, by worker A, rather than row by row)
|
||||
|
||||
Worker A closed two rows with per-region cc1 flags and then **asked me to set the phase's policy once
|
||||
rather than decide row by row, and explicitly declined to argue for either option itself**:
|
||||
|
||||
| row | flag | default | with flag | counter-measurement on A's merged rows |
|
||||
|---|---|---|---|---|
|
||||
| `0x80101C7C` (48 B) | `cc1=-fno-delayed-branch` | 44, LENGTH-MISMATCH | **48 / 0 / MATCH** | **breaks 6 of 6** tested (140→152, 108→124, 204→212, 68→72, 84→92, 80→84) |
|
||||
| `0x800AB504` (148 B) | `cc1=-fno-strength-reduce` | 168, LENGTH-MISMATCH | **148 / 0 / MATCH** | neutral on 5, **changes 2** (`0x800FFF60`, `0x8010036C`) |
|
||||
|
||||
Both are merged (the project's standard is byte-for-byte and both are byte-exact, gate green). **The
|
||||
policy, set once:**
|
||||
|
||||
> **A per-region CODEGEN flag is admissible on the `cc1bin` principle, under four conditions:**
|
||||
> **C1** it must close the row — byte-exact, gate green;
|
||||
> **C2** the bytes at the difference must show the mechanism the flag addresses, **named in the file
|
||||
> header**;
|
||||
> **C3** the counter-measurement must be recorded in the file header — that the flag is neutral or
|
||||
> harmful elsewhere — because that is what makes it a statement about the REGION rather than a claim
|
||||
> about the build;
|
||||
> **C4** source spelling must be exhausted first (A tried three spellings on `0x80101C7C`, four plus a
|
||||
> semantics error on `0x800AB504`).
|
||||
|
||||
**And the honest limitation, which must be stated wherever the rule is quoted: unlike `cc1bin`'s ≥2-`jr
|
||||
$31` test, there is NO mechanical byte predicate for these flags.** I tried to build one — the natural
|
||||
candidate was "the original's `jr ra` delay slot is empty" — and it **fails**: two of the six rows that
|
||||
`-fno-delayed-branch` BREAKS (`0x800297F4`, `0x800690E4`) also have empty slots, so the predicate neither
|
||||
distinguishes the rows nor predicts the flag's effect. The reason is structural: **`cc1bin` swaps a
|
||||
coherent compiler binary and its effect here is localisable (return merging), while
|
||||
`-fno-delayed-branch` disables a whole-function scheduling pass**, so its effects are global and cannot
|
||||
be read off one instruction.
|
||||
|
||||
**Consequence, stated plainly: for these two rows the discipline is DOCUMENTARY, not mechanical. The
|
||||
gate cannot distinguish a right flag from a wrong one, so C2+C3 are the entire safeguard, and a worker
|
||||
who lies to itself in a header comment would produce a byte-exact row with no mechanism behind it.**
|
||||
That is a genuine weakening of the phase's evidence standard relative to every other lever in use, and
|
||||
**it is raised to the developer as a scope question** — the developer restricted `cc1bin` precisely
|
||||
because the gate cannot detect a wrong compiler choice, and that rationale applies here *more* strongly,
|
||||
not less. Two rows of 688 are affected.
|
||||
|
||||
## Worker A's best finding of the session, and it is a correction of A's own method
|
||||
|
||||
`0x800AB504` was ruled class-bound by me at 152-vs-148. **It closed at 148 because part of the obstacle
|
||||
was A's own SEMANTIC error, not the toolchain.**
|
||||
|
||||
The call's argument register is armed **twice** — once in the `blez`'s delay slot before the first
|
||||
iteration, and once at the loop's **back edge**, after `p += 16`. So the callee sees `p`, then `p+16`,
|
||||
then `p+32`: **the call is made with the CURRENT record and the pointer is advanced afterwards.** A's v1
|
||||
had `p += 16;` *before* the call, which is not a different schedule — **it is a different pointer on
|
||||
every iteration.** It looked close (168 vs 148) only because cc1 reorders the independent increment above
|
||||
the call anyway.
|
||||
|
||||
> **A's transferable check: a loop that both ADVANCES and PASSES a pointer can be ordered correctly by
|
||||
> reading WHERE THE ARGUMENT REGISTER IS RE-ARMED, before any trial-and-error spelling.**
|
||||
|
||||
And A's own sentence about why it was missed is the part worth keeping: ***"I read the bytes for the
|
||||
codegen and not for the semantics."*** That is the same error I made earlier in this phase when I wrote a
|
||||
probe-shape-dependent mechanism into `tools/sf3_match` as a finding — reading bytes for a mechanism
|
||||
without asking what the code MEANS. It cost A a class-bound ruling and me a false finding, and it is now
|
||||
recorded as its own failure mode rather than as a footnote to either.
|
||||
|
||||
**Consequence recorded: my class-bound ruling on `0x800AB504` was WRONG, and it was wrong because it
|
||||
rested on A's 152-byte candidate being the row's floor.** The floor was A's bug. Rulings on "the flag
|
||||
does not reach the target" have to be re-checked when the candidate is corrected, which is a rule I did
|
||||
not have and now do.
|
||||
|
||||
## The un-attempted band is the phase's best band, measured
|
||||
|
||||
Worker D's band (index rows with NO mechanism named — attempted in phase 8/9 but never pushed to a floor)
|
||||
is now **15 rows attempted, 11 matched, 4 classified, 0 wrong-function failures** — a **73% hit rate
|
||||
against roughly 50% on prio-1**. Fresh bands generated from the same criterion for A (25 rows) and B (36
|
||||
rows), so all three workers are on the criterion that measurably works rather than on the one that was
|
||||
merely convenient.
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* func_800AB504 — 148 bytes at 0x800AB504..0x800AB598
|
||||
*
|
||||
* Recursive release of a counted record list: recurse into the child list first,
|
||||
* then walk `count` 16-byte records clearing three bytes at +4/+5/+6 of each, and
|
||||
* finally clear the parent's own fields.
|
||||
*
|
||||
* The observed instructions are:
|
||||
* addiu sp,sp,-32
|
||||
* sw s2,24(sp) \
|
||||
* addu s2,a0,zero | n = argument
|
||||
* sw ra,28(sp) |
|
||||
* sw s1,20(sp) |
|
||||
* beq s2,zero,epilogue |
|
||||
* _sw s0,16(sp) / (the fourth save fills the branch's delay slot)
|
||||
* lhu s1,12(s2) i = *(unsigned short *)(n + 12) SIGNED compare later
|
||||
* lw a0,24(s2) \
|
||||
* lw s0,0(s2) | p = *(char **)(n + 0)
|
||||
* beq a0,zero,L2 | if (*(char **)(n + 24))
|
||||
* _nop |
|
||||
* jal func_800AB504 | func_800AB504(*(char **)(n + 24));
|
||||
* _nop /
|
||||
* L2:
|
||||
* blez s1,L4 \
|
||||
* _addu a1,s0,zero / the FIRST call's argument is the UN-incremented p
|
||||
* L3:
|
||||
* sb zero,6(s0) \
|
||||
* sb zero,5(s0) | p[6] = p[5] = p[4] = 0, written in that order
|
||||
* sb zero,4(s0) |
|
||||
* addiu s0,s0,16 / p += 16 (scheduled BEFORE the call)
|
||||
* lw a0,0xde8(gp) \
|
||||
* jal func_8001289C / func_8001289C(D_80122720, p);
|
||||
* _addiu s1,s1,-1 (the decrement fills the jump's delay slot)
|
||||
* bgtz s1,L3 \
|
||||
* _addu a1,s0,zero / the back edge re-arms the argument for the NEXT turn
|
||||
* L4:
|
||||
* sw zero,0(s2) \
|
||||
* sb zero,20(s2) |
|
||||
* sw zero,24(s2) | the parent's fields, in this exact order
|
||||
* sh zero,12(s2) |
|
||||
* sw zero,8(s2) /
|
||||
* lw ra / lw s2 / lw s1 / lw s0 / addiu sp,sp,32 / jr ra / nop
|
||||
*
|
||||
* THE STATEMENT ORDER IS PROVED BY THE ARGUMENT SEQUENCE, AND MY FIRST TWO
|
||||
* SPELLINGS HAD IT BACKWARDS. The argument register is armed TWICE: once in the
|
||||
* `blez`'s delay slot (before the first iteration) and once at the loop's back
|
||||
* edge (after `p += 16`). So the call sees `p`, then `p+16`, then `p+32` …: **the
|
||||
* call is made with the CURRENT record and the pointer is advanced afterwards**,
|
||||
* i.e. the source is
|
||||
* p[6] = p[5] = p[4] = 0; (three separate statements, in that order)
|
||||
* func_8001289C(D_80122720, p);
|
||||
* p += 16;
|
||||
* Writing `p += 16;` BEFORE the call (my v1) is not merely a different schedule —
|
||||
* it passes a different pointer on every iteration. That is a semantic error, not a
|
||||
* codegen difference, and the only reason it was visible is that cc1 reorders the
|
||||
* INDEPENDENT increment above the call, so the wrong spelling still looked close:
|
||||
* 168 bytes against 148. THE ARGUMENT'S ARMING POINTS ARE THE EVIDENCE, and they are
|
||||
* a cheap check for any loop with a pointer that is advanced and also passed.
|
||||
*
|
||||
* REGION TOKEN: `cc1=-fno-strength-reduce`, and with the corrected statement order it
|
||||
* CLOSES the row (148 / 0 / MATCH). Mechanism in the bytes: with the default
|
||||
* strength reduction cc1 turns the `p[4..6]` addressing into a SECOND loop-carried
|
||||
* pointer (`addu $16,$17,4` with offsets 0/1/2) while keeping `p` itself for the call
|
||||
* argument, so the frame grows to 40 and five registers are saved where the original
|
||||
* saves four and keeps a single pointer with constant displacements. The flag removes
|
||||
* that induction variable and the frame and prologue become the original's exactly.
|
||||
* **Measured counter-evidence, same test I applied to `-fno-delayed-branch`: the flag
|
||||
* is neutral on five of my merged rows (0x8002FB54, 0x800297F4, 0x800690E4,
|
||||
* 0x80094370, 0x80018284 all still MATCH) but changes two (0x800FFF60 at
|
||||
* 0x800FFFE0, 0x8010036C at 0x80100398). So it is a per-region escape hatch by
|
||||
* measurement, not a global build setting, and admitting it is the coordinator's call.**
|
||||
* It is a CODEGEN flag, not a scheduler flag, which is the line cookbook 50 draws.
|
||||
*
|
||||
* LIMITS: the record layout beyond the bytes touched (+12 unsigned short, +0, +24 as
|
||||
* pointers, stride 16, +20 and +8), the meaning of `D_80122720`, the callee's contract,
|
||||
* and whether the recursion is a sibling or child list are hypotheses read off the
|
||||
* instruction shape. Only the compiled bytes are evidence.
|
||||
*/
|
||||
|
||||
extern int D_80122720;
|
||||
|
||||
void func_8001289C(int a0, char *a1);
|
||||
|
||||
void func_800AB504(char *n)
|
||||
{
|
||||
int i;
|
||||
char *p;
|
||||
|
||||
if (n == 0)
|
||||
return;
|
||||
|
||||
i = *(unsigned short *)(n + 12);
|
||||
p = *(char **)(n + 0);
|
||||
|
||||
if (*(char **)(n + 24) != 0)
|
||||
func_800AB504(*(char **)(n + 24));
|
||||
|
||||
while (i > 0) {
|
||||
p[6] = 0;
|
||||
p[5] = 0;
|
||||
p[4] = 0;
|
||||
func_8001289C(D_80122720, p);
|
||||
p += 16;
|
||||
i--;
|
||||
}
|
||||
|
||||
*(int *)(n + 0) = 0;
|
||||
n[20] = 0;
|
||||
*(int *)(n + 24) = 0;
|
||||
*(short *)(n + 12) = 0;
|
||||
*(int *)(n + 8) = 0;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* func_80101C7C — 48 bytes at 0x80101C7C..0x80101CAC
|
||||
*
|
||||
* Loads two words from a source structure into the GTE's VXY0/VZ0 data registers,
|
||||
* runs a matrix/vector command, and writes the three IR results straight back to
|
||||
* memory at the destination pointer, which it also returns.
|
||||
*
|
||||
* The observed instructions are:
|
||||
* 0x8C880000 lw t0,0(a0) ; src[0]
|
||||
* 0x8C890004 lw t1,4(a0) ; src[1]
|
||||
* 0x48880000 mtc2 t0,$0 ; VXY0 -- DATA register, rs=4 (mtc2), NOT ctc2
|
||||
* 0x48890800 mtc2 t1,$1 ; VZ0
|
||||
* 0x00000000 nop ; GTE latency padding (the call site writes it)
|
||||
* 0x4A486012 cop2 0x486012 ; MVMVA-family command, field read off the word
|
||||
* 0xE8A90000 swc2 $9,0(a1) ; IR1 -> dst[0]
|
||||
* 0xE8AA0004 swc2 $10,4(a1) ; IR2 -> dst[1]
|
||||
* 0xE8AB0008 swc2 $11,8(a1) ; IR3 -> dst[2]
|
||||
* 0x00A01021 addu v0,a1,zero ; return dst
|
||||
* 0x03E00008 jr ra
|
||||
* 0x00000000 nop ; the delay slot is LEFT EMPTY -- see below
|
||||
*
|
||||
* THE FIRST DRAFT OF THIS ROW WAS WRONG IN A WAY WORTH RECORDING: I reached for
|
||||
* `gte_ldRT1RT2` / `gte_ldRT3RT21` because the SIBLING at 0x80101CAC loads exactly
|
||||
* those two control registers, and got `48C80000` / `48C90800` where the original has
|
||||
* `48880000` / `48890800`. The two differ only in the COP2 `rs` field: **rs=4 is
|
||||
* `mtc2` (a DATA register, $0/$1 = VXY0/VZ0) and rs=6 is `ctc2` (a CONTROL register,
|
||||
* $0/$1 = RT1RT2/RT3RT21).** Two words of a 48-byte body, and the only way to see it
|
||||
* is the raw word (cookbook 25: Ghidra's PSX loader collapses COP2 sequences, so a
|
||||
* disassembly listing cannot settle this). The adjacency lever got me to the right
|
||||
* header and the wrong macro family; the bytes corrected it.
|
||||
*
|
||||
* REGION TOKEN: `cc1=-fno-delayed-branch`, and it is the ONE thing that closes this
|
||||
* row — **but it is per-region for a reason, and the reason is evidence, not taste.**
|
||||
* Mechanism, read off the bytes: with the default cc1 the return copy
|
||||
* (`addu $2,$5,zero`) is moved INTO the `jr ra` delay slot, so the body is 44 bytes
|
||||
* where the original has 48; the original leaves that slot EMPTY and lets the
|
||||
* assembler place a `nop`. `-fno-delayed-branch` stops cc1's reorg from doing the
|
||||
* move and the harness's maspsx stage then fills the slot with the nop, giving
|
||||
* 48 / 0 / MATCH. **Measured counter-evidence, which is why I am flagging this rather
|
||||
* than claiming it quietly: the same flag changes six of my already-merged rows** —
|
||||
* 0x800FFF60 140->152, 0x8002FB54 108->124, 0x800297F4 204->212, 0x800690E4 68->72,
|
||||
* 0x80094370 84->92, 0x80018284 80->84. So the flag CANNOT be the original build's
|
||||
* global setting; it is a per-region escape hatch on the same terms as `cc1bin`, and
|
||||
* the decision to admit it is the coordinator's. I have also tried three source-side
|
||||
* routes to the same bytes without the flag (dropping the destination register
|
||||
* binding, binding the return value to $2, and binding the pointer but returning
|
||||
* through it) and none of them leaves the slot empty.
|
||||
*
|
||||
* The three stores need `swc2 $9/$10/$11` — IR1/IR2/IR3 straight to memory — and
|
||||
* include/gtemac.h has `lwc2` and `swc2` macros for the MAC registers and the IR
|
||||
* registers only in the `lwc2` direction, so the stores are written as the same
|
||||
* inline-asm form the header uses. **The header needs `gte_swc2IR1/2/3`**, and per the
|
||||
* project's write scope that addition is the coordinator's, not mine.
|
||||
*
|
||||
* The register bindings are load-bearing (cookbook 24): without them cc1 chooses its
|
||||
* own destinations for the loads and its own register for the address, and the operand
|
||||
* registers are what a GTE row's residual usually is.
|
||||
*
|
||||
* LIMITS: the structure types, the meaning of the command field (it is named by FIELD
|
||||
* VALUE, not semantically), and which fields of the GTE are actually being consumed are
|
||||
* hypotheses read off the instruction shape. Only the compiled bytes are evidence.
|
||||
*/
|
||||
|
||||
#include "../include/gtemac.h"
|
||||
|
||||
int *func_80101C7C(int *src, int *dst)
|
||||
{
|
||||
register int v0 __asm__("$8");
|
||||
register int v1 __asm__("$9");
|
||||
register int *p __asm__("$5");
|
||||
|
||||
v0 = src[0];
|
||||
v1 = src[1];
|
||||
|
||||
gte_ldVXY0(v0);
|
||||
gte_ldVZ0(v1);
|
||||
|
||||
__asm__ volatile ("nop");
|
||||
|
||||
gte_cmd(0x486012);
|
||||
|
||||
p = dst;
|
||||
__asm__ volatile ("swc2 $9, 0(%0)" : : "r"(p));
|
||||
__asm__ volatile ("swc2 $10, 4(%0)" : : "r"(p));
|
||||
__asm__ volatile ("swc2 $11, 8(%0)" : : "r"(p));
|
||||
|
||||
return dst;
|
||||
}
|
||||
Reference in New Issue
Block a user