phase12: record the five pool defects, the convergent worker findings, and D's counter-evidence

This commit is contained in:
Christopher Williams
2026-09-24 18:31:26 -04:00
parent 9053a4dc51
commit ce8e1221ff
+137
View File
@@ -711,3 +711,140 @@ Authorised, and the macros go into `gtemac.h` the way the existing ones were add
**Worth naming:** the GTE inline-asm hatch authorised earlier for `0x80010810`/`0x8009C69C` **was
never used** — worker C matched both in plain C. So this would be the phase's first real asm row,
which is a milestone and not a shortcut.
---
## THE NEGATIVES RE-DISPATCH, AND THE FIVE DEFECTS IN THE FILE I DISPATCHED
### Position after merge 11: **646 bodies / 655 regions. Phase: 602 → 646 = +44.**
| worker | bodies | what produced them |
|---|---|---|
| D | 14 | fresh band + Goal B + twins + GTE + `0x800A6B38` |
| C | **12** | pool prio-1, 3 of them from rows whose recorded mechanism was half wrong |
| B | **11** | **pool prio-1, all three of the last batch at 1-4 spellings** |
| A | 7 | adjacency 3-for-3, plus `goto ret0` and the `maspsx=epilogue` token |
### The five defects in my pool generator, and every one was found by a worker
I generated the dispatch pools with a one-off script. The tracked index documents its own schema on
line 2 — `# Columns: address<TAB>size<TAB>status<TAB>class` — and **my script never read `status`**,
and knew nothing about two collaborator lists. Five defects, all shipped, **all found by the workers**:
| # | defect | rows | who found it |
|---|---|---|---|
| 1 | `status=blocked` (charter §7: forbidden to attempt) dispatched **at prio 1** | 6 | **C**, refused three in its own pool |
| 2 | blocked FAMILY with `status=near-match` (`primitive-init`) also dispatched | 1 | **C** (listed `0x80012A48` as its next row) |
| 3 | Makefile `NAMED_EXCLUSIONS` invisible → leaked back as fresh work | 8 | **A** (found two, asked if they should be filtered) |
| 4 | workers' own `negatives.tsv` invisible → **94 of 115 classified rows re-served**, at the top of prio 1 | 94 | **C** ("treat them as done, not as fresh rows") |
| 5 | `named = class not in ('-','')` treats **`no-mechanism-yet`** as a named mechanism → **62 rows ordered backwards** | 62 | **A** in prose ("your pool led with rows I could not close") |
Plus a seventh rule added after defect 4 was understood: **a worker's OWN classification is dropped
from that worker's own pool**, while still being dispatched to the others with a `classified-by:XY`
note — because the classification is the *lead* for a different worker and a *floor* for its author.
Worker C's words: *"they are the first four rows in the file's own prio-1 order, so a worker starting
at the top spends its first hours re-deriving my floor."*
**This is a SCHEMA error, not a logic error**, and that is the transferable lesson: five separate bugs
all trace to generating a dispatch file from a table whose header I had not read.
The fix is `tools/sf3_negpool` (17 tests) plus a `NAMED_EXCLUSIONS` variable in the Makefile so the
list has **one source of truth** — given to both `worklist` and `negpool`, because the whole defect
was two lists that must agree living in different files. Pools: 176 → **93** (a 24 / b 23 / c 28 / d 18).
Fail-first is **demonstrated rather than asserted**: the two integration tests were run against the
pools the workers were holding at that moment and **failed, naming all 8 leaked rows.**
Two bugs in my own fix, found while making it: `names_a_mechanism` accepted the numeric dashboard
columns (`'1'` is not a mechanism), which made the rule vacuous and dropped 0 rows instead of 58; and
one of my tests asserted output that its own `--quiet` flag suppressed.
**A counter caught a redundant fix of mine, which belongs in the record.** I had already "fixed"
defect 3 by hand, adding `--exclude 0x8001DC20` to `worklist` — and `excluded_named_exclusion` did not
move, because the row was already excluded under another rule and had reached the worker via the
*negatives pool*. Fixing one row by hand while the generator leaked was treating a symptom, and the
tool said so.
**Process defect, reported by worker D and my error:** I re-cut the pools **twice mid-flight without
telling the workers**, the second time after saying the split was stable. D measured it (18 rows, two
of its worked rows gone) and asked that a re-cut say so explicitly. It now does, and the re-cut is
tool-driven rather than hand-done.
### Two workers independently converged on what these negatives are worth
* **B:** *"the recorded class named the SYMPTOM (a merged store, a folded shift, a strength-reduce)
while the fix was an ORDER or a claim-row option."*
* **C:** *"in every one the recorded class NAME was right and the recorded MECHANISM was half wrong."*
Two partitions, same conclusion, and it cuts both ways — **the strongest argument for dispatching
onto named negatives, AND a warning that the class is a signpost, not an instruction.** This is
cookbook 182's "a negative with a named mechanism is overturnable" given a measured boundary.
### The counter-evidence, recorded because it challenges my own decision
**Worker D is 1-for-4 on prio-1 rows, having been 4-for-4 on Goal B**, and argues: *"what matters is
whether the row has been attempted with the current lever set, not which pool it is in."* D's read is
that the prio-1 rows are negatives *with recorded residuals* — rows previous attempts already pushed
to 1-6 bytes — so the surviving cost is the allocator/rematerialisation wall, while the unclassified
rows were productive for the opposite reason: nobody had ever asked them a structural question.
**A (3 for 5) and B (5 overturns) contradict it in their partitions.** Not re-deciding on nine rows;
watching the aggregate, and D's own conclusion is the re-rank criterion: not "which pool" but
"attempted with the current lever set".
### Convergent findings, each reached independently by two workers
1. **`constant-base-in-register` is TOOLCHAIN BEHAVIOUR, not a source puzzle.** A (`0x8010400C`): *no
source shape can both keep the address in a register and keep the 11-instruction count, because
keeping the address a value requires a use of the value and the original has none.* C
(`0x80107874`): *cc1 materialises a symbol address into a GPR only when the address ESCAPES
(passed, stored, incremented, or indexed by a variable); a plain constant-base store never does.*
**Same rule, found without knowledge of each other.** A's row has **1** `jr $31`, so the gate
correctly refuses `cc1bin` — the restriction doing exactly its job.
2. **The registry's gp marker is per-SYMBOL; the access form is per-SITE.** B needed
`gp=-D_80121BFC`, C needed `gp=-D_801226E0`, in the same hour, on rows where the index marks the
symbol `gp` and the original reads it absolutely. **Cookbook 46's per-site override is not an
exotic fallback** — it is what two workers needed on one afternoon. Both rows came out one
instruction SHORT without it.
3. **cookbook 42's coupling is refuted on BOTH of its own rows**, and the named-binding lever that
cracked `0x801008DC` does **not** transfer to `0x80050674`. D corroborated C's floor from a second
direction (2 spellings) and added a detail: the loop counter's `move t0,zero` must come first.
### New mechanism-level results
* **C pins finding 8's missing trigger.** `0x800AC9D8` and `0x800A6658` are the **same source shape**
(two symbols, variable byte offset, fresh per-store base): in the first cc1 **re-materialised the
base per store and matched**; in the second it **hoisted** and the row was lost. The trigger is
**which form cc1 picks for the address** — *through `$at`, a scratch register that cannot stay live
across a loop, no hoist is possible and the original's shape comes out; through a free GPR, cc1
hoists and the row is lost.* On `0x800A6658` the four setup stores occupy `v0/a0/a1`, so cc1 reached
for GPRs. The row's untried lever is a spelling that raises register pressure at the loop head.
* **`maspsx=epilogue` decided from the CANDIDATE's tail, three independent confirmations** (A's
`0x8010036C`, C's `0x800FE970`). Shape A inserts a load-delay nop: candidate `lw ra,24(sp) / addiu
sp,sp,32 / jr ra / nop` vs original `lw ra,24(sp) / nop / jr ra / addiu sp,sp,32`.
* **A's `goto ret0` lever fixes two failures at once** (`0x800690E4`): CSE of the head test into the
back edge (→64 B), and a duplicated node test top-and-back-edge (→76 B). Both null exits sharing one
`ret0:` label makes the head test survive as a forward branch.
* **B's strength-reduction rule:** *"let the compiler do the strength reduction" is itself a
byte-required spelling* — the indexed `do/while` on `i != -1` gives the pointer walk with no counter
and the original's INDEX-FIRST `addu`; a hand-written pointer walk gives base-first and costs 3
bytes; `for (i = len; i >= 0; i--)` keeps a counter and adds a redundant `bltz`. **A pointer-order
residual can be a hint to write the INDEX loop instead.**
* **Source-side controls accumulated:** `volatile` on a store target suppresses the jump-delay-slot
fill · `volatile` on a local reproduces a stack-routed temp · a loop-invariant constant must be a
NAMED local assigned before the loop for the prologue order to come out right · a loop-invariant
base must go through `$at` (or be forced to) or cc1 hoists it.
### Two cross-reconstruction defects, both named rather than buried
* **`func_800F8F3C` arity disagreement** (C). The registered `src/func_800F8F3C.c` declares
`int func_800F8F3C(void)` while C's call site in `0x800FE970` materialises a buffer argument
(`addiu a0,sp,16`) and discards the result. Per finding 162 C used the non-prototype declaration and
the call-site bytes are identical either way, so the match does not depend on it — **but whoever
revisits `0x800F8F3C` should know at least one caller passes it a buffer it may not use.**
* **`0x80091490`'s inferred prototype** for the unreconstructed `0x80018284` (merged on my ruling).
### Two unclosable rows turned into decision inputs rather than losses
* **A's `0x8006FAA0`**: the C is SOLVED and verified — `-fno-schedule-insns` gives
`differing_bytes=0`. Per cookbook 50 the flag is diagnosis-only and never attaches to a claim row, so
it stays a negative, but the row is now the cleanest post-pass/scheduler candidate in A's pool.
* **A's `0x8010400C`** and **C's `0x80107874`/`0x800F6ED0`**: classified with mechanisms, no lever left.