phase12: merge 26 (674 bodies) + the epilogue token is a per-row property invisible in a length check
This commit is contained in:
@@ -12,8 +12,10 @@
|
||||
# Matched so far:
|
||||
0x80010810 0x8001084C src/func_80010810.c
|
||||
0x80012780 0x8001278C src/func_80012780.c
|
||||
0x8001278C 0x800127F0 src/func_8001278C.c maspsx=epilogue
|
||||
0x800127F0 0x8001281C src/func_800127F0.c
|
||||
0x8001281C 0x80012834 src/func_8001281C.c
|
||||
0x80012834 0x8001289C src/func_80012834.c
|
||||
0x8001289C 0x800128E4 src/func_8001289C.c
|
||||
0x800128E4 0x80012918 src/func_800128E4.c
|
||||
0x80012918 0x80012960 src/func_80012918.c
|
||||
|
||||
|
@@ -1299,3 +1299,53 @@ Not rewriting history for a number, but recording it, because **a wrong body cou
|
||||
exactly the kind of figure a later session trusts without re-deriving**, and the phase's whole
|
||||
projection rests on this number being the one the gate produced. The authority is
|
||||
`config/regions.tsv` counted at the merge, and that said 681 regions / 672 bodies.
|
||||
|
||||
### The `maspsx=epilogue` TOKEN is a PER-ROW property and it is INVISIBLE in a length check (worker D)
|
||||
|
||||
D's `0x8001278C` (100 B) **requires** `--fill-epilogue`, and `0x80012834` (104 B) — **ten rows away in
|
||||
the same band** — epilogues PLAIN and must **not** get the flag. This makes the token the second entry
|
||||
on the list of things that may not be carried across rows, and it is worse than the first:
|
||||
|
||||
* the indexed-vs-pointer **loop form** at least shows up as a length difference;
|
||||
* the **epilogue token** does not. Both shapes have the same instruction COUNT —
|
||||
the original's `lw ra,16(sp) / move v0,zero / jr ra / addiu sp,sp,24` against cc1's
|
||||
`lw ra,16(sp) / addiu sp,sp,24 / jr ra / nop` — so a missing token presents as **differing bytes at
|
||||
the correct length**, which is precisely the presentation every worker on this roster has learned to
|
||||
read as an *allocator or scheduler floor*. **A worker who trusts the shape will mis-classify a row
|
||||
that a one-token claim would close.**
|
||||
|
||||
**D's procedure, which is the right one and is now the roster's:** *read the original's last three
|
||||
instructions BEFORE choosing the flag, and carry the token only when the `addiu sp,sp,N` sits inside the
|
||||
`jr ra` slot.* **15 registered regions carry `maspsx=epilogue` today**, each individually verified
|
||||
byte-exact, so none is at risk — but the habit is.
|
||||
|
||||
### A measurable invariant for extents, and it answers D's delay-slot worry
|
||||
|
||||
D reported losing a reading because its own `--end` **excluded the `jr ra` delay slot**, and said the
|
||||
failure "looked exactly like a one-instruction-short row" — which is the same symptom as my guessed-
|
||||
extent error earlier the same hour.
|
||||
|
||||
I checked all 681 registered regions against the invariant and it holds **uniformly**:
|
||||
|
||||
> **a region's extent ends exactly 8 bytes past its LAST `jr $31`** — 681 of 681, zero exceptions.
|
||||
|
||||
That is worth having as a procedure because it is a **self-check a worker can run on an extent it
|
||||
computed itself**: find the last `jr $31` in the extent, confirm `end - that_address == 8`, and if it is
|
||||
not, the extent is wrong rather than the candidate. It is also the right rule for the multi-exit rows,
|
||||
where "the last `jr $31`" and "the only `jr $31`" are different addresses.
|
||||
|
||||
### Worker D's third instance of "the counter survives unless the source has no counter"
|
||||
|
||||
`0x8001278C`'s loop variable is the **byte offset**, not an index: the original walks
|
||||
`v0 = 180; do { store; v0 -= 20; } while (v0 >= 0)` with the address formed
|
||||
`lui at,0x8012 / addu at,at,v0 / sw zero,12912(at)`. The natural indexed spelling
|
||||
(`for (i = 9; i >= 0; i--) D_80123270[i].f0 = 0;`) makes cc1 keep **both** the counter and the derived
|
||||
IV (`li v1,9`, `addiu v1,v1,-1`, `bgez v1`, and the delay slot becomes `addiu v0,v0,-20` instead of a
|
||||
`nop`) — four instructions more. Spelling it as an explicit byte offset leaves cc1 with ONE variable,
|
||||
which is the original. **Third instance this phase: the counter survives unless the source has no
|
||||
counter to survive.**
|
||||
|
||||
Also from that row, D's **second** instance of the symbol-arithmetic procedure paying off: D first
|
||||
mis-added 2300 and asked for a `0x801221E4` the binary never touches; `gp+12` is `0x80121944` and
|
||||
`gp+2300` is **`0x80122234`**, and both were already registered. D's own rule now: *read the
|
||||
displacement and compute the symbol, do not infer it from an adjacent symbol's name.*
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* func_8001278C — 100 bytes at 0x8001278C..0x800127F0
|
||||
*
|
||||
* Calls four helpers, zeroes the halfword at gp+12 (0x80121944), clears the first
|
||||
* word of each of ten 20-byte records at 0x80123270 (walking a BYTE OFFSET down from
|
||||
* 180), zeroes a SECOND halfword at gp+2300 (0x80122234), and returns 0.
|
||||
*
|
||||
* THREE levers, two of them reusable:
|
||||
*
|
||||
* 1. **`--fill-epilogue` (region token `maspsx=epilogue`).** The original's tail is
|
||||
* `lw ra,16(sp) / move v0,zero / jr ra / addiu sp,sp,24` -- the frame release
|
||||
* INSIDE the jump's delay slot. Without the flag cc1 emits
|
||||
* `lw ra,16(sp) / addiu sp,sp,24 / jr ra / nop`, which is the same instruction
|
||||
* COUNT and a different shape, so it shows up as differing bytes rather than a
|
||||
* length mismatch. Any framed row whose `addiu sp,sp,N` sits in the `jr ra` delay
|
||||
* slot needs this flag; the claim must carry the token.
|
||||
* 2. **THE LOOP VARIABLE IS THE BYTE OFFSET, NOT AN INDEX.** The original iterates
|
||||
* `v0 = 180; do { store; v0 -= 20; } while (v0 >= 0)` with the address formed as
|
||||
* `lui at,0x8012 / addu at,at,v0 / sw zero,12912(at)`. Writing the natural
|
||||
* indexed form -- `for (i = 9; i >= 0; i--) D_80123270[i].f0 = 0;` -- makes cc1
|
||||
* KEEP BOTH the counter and the derived induction variable (`li v1,9`,
|
||||
* `addiu v1,v1,-1`, `bgez v1`, and the delay slot filled with `addiu v0,v0,-20`
|
||||
* instead of a `nop`), 4 instructions more. Spelling the walk as an explicit byte
|
||||
* offset (`*(int *)((char *)D_80123270 + i)`, 180 down to 0 by 20) leaves cc1 with
|
||||
* a single variable, which is what the original has.
|
||||
* 3. **THE TWO HALFWORDS ARE DIFFERENT GLOBALS.** Decode the displacement rather than
|
||||
* reusing the first symbol: gp+12 is 0x80121944 and gp+2300 (0x8FC) is 0x80122234.
|
||||
* (Both are already registered WITH gp markers, so no symbol request was needed;
|
||||
* my own first attempt mis-added 2300 and asked for a 0x801221E4 that the binary
|
||||
* does not touch -- the assembler's displacement is the authority.)
|
||||
*
|
||||
* LIMITS: the ten-record/20-byte stride is inferred from the walk (`li v0,180`,
|
||||
* `addiu v0,v0,-20`), not from a recovered struct; only the first word of each record
|
||||
* is written, so the record size is a lower bound. The two halfwords are two
|
||||
* separate symbols and nothing here relates them. The four callees are named for
|
||||
* their addresses and their contracts are unestablished; two of them are called
|
||||
* twice in a row and are NOT merged, which is only evidence that they are calls.
|
||||
*/
|
||||
typedef struct {
|
||||
int f0;
|
||||
char pad[16];
|
||||
} Rec_80123270;
|
||||
|
||||
extern Rec_80123270 D_80123270[10];
|
||||
extern short D_80121944;
|
||||
extern short D_80122234;
|
||||
|
||||
void func_80026304(void);
|
||||
void func_80016174(void);
|
||||
void func_80012F24(void);
|
||||
void func_80021BA8(void);
|
||||
|
||||
int func_8001278C(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
func_80026304();
|
||||
func_80016174();
|
||||
D_80121944 = 0;
|
||||
for (i = 180; i >= 0; i -= 20) {
|
||||
*(int *)((char *)D_80123270 + i) = 0;
|
||||
}
|
||||
func_80012F24();
|
||||
func_80021BA8();
|
||||
func_80021BA8();
|
||||
D_80122234 = 0;
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* func_80012834 — 104 bytes at 0x80012834..0x8001289C
|
||||
*
|
||||
* If `*q` is non-zero, calls a helper with `p[37]` and `*q`; if the helper's low
|
||||
* byte is non-zero that byte is returned. Otherwise stores a second helper's result
|
||||
* back through `q` and returns 0. Matched on the FIRST spelling.
|
||||
*
|
||||
* WHAT THE BYTES PIN DOWN: the value of `*q` is loaded ONCE at the top, before the
|
||||
* branch, and stays live in `a1` across it (the first call passes it without
|
||||
* reloading), while the first call's FIRST argument is the VALUE at `p+148`
|
||||
* (`lw a0,148(s1)`) and the second call's first argument is the ADDRESS `p+148`
|
||||
* (`addiu a0,s1,148`) -- a value/address pair on the same field, which is the shape
|
||||
* that makes the two calls a get/set pair rather than a repeated call. The second
|
||||
* call's second argument is the POINTER `q` itself (`move a1,s0`), not `*q`. The
|
||||
* `0xff` mask is computed once and reused for both the test and the return value
|
||||
* (`andi v0,v0,0xff` then `bnez v0`), so `v & 0xff` must appear in BOTH the condition
|
||||
* and the returned expression -- writing it once and returning `v` changes the
|
||||
* emitted code.
|
||||
*
|
||||
* CONTRAST WORTH KEEPING: this function's epilogue is the PLAIN form
|
||||
* (`addiu sp,sp,32` BEFORE `jr ra`), so it must NOT be built with `--fill-epilogue`.
|
||||
* 0x8001278C, ten rows away, needs that flag. **The epilogue mode is a per-row
|
||||
* property, like the loop form -- check which shape the original has before choosing
|
||||
* the flag, and carry the token only when the frame release is inside the delay
|
||||
* slot.**
|
||||
*
|
||||
* LIMITS: `p` is treated as an `int *` because the field is reached as `p[37]`
|
||||
* (offset 148) and by `&p[37]`; the field's real type, the meaning of the two
|
||||
* helpers, the role of `q` (an out-parameter that receives the second helper's
|
||||
* result, and whose incoming value gates the first call), and what a non-zero low
|
||||
* byte signifies are NOT recovered. The callees are named for their addresses. The
|
||||
* frame is 32 bytes with `s0`/`s1`/`ra`, which is what two saved registers plus a
|
||||
* call costs; nothing here identifies what `s0`/`s1` stand for beyond `q` and `p`.
|
||||
*/
|
||||
extern int func_800263E8(int a, int b);
|
||||
extern int func_80026460(int *a, int *b);
|
||||
|
||||
int func_80012834(int *p, int *q)
|
||||
{
|
||||
if (*q != 0) {
|
||||
int v = func_800263E8(p[37], *q);
|
||||
|
||||
if (v & 0xff)
|
||||
return v & 0xff;
|
||||
}
|
||||
*q = func_80026460(&p[37], q);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user