Gate exit 0 (693 regions, MATCH), 344 tests OK, make check exit 0, tracked patch regenerated so
pristine + patch reproduces the working tree.
+4 this commit (worker D): 0x80100334, 0x800FBDC0, 0x800F88F0 (all maspsx=epilogue), 0x80042DD4.
Phase: 602 -> 684 = +82. Milestone 750 was not reached; +66 remains.
TWO TOOL INCIDENTS OF MINE, both reported by workers before I saw them, both repaired:
1. I edited tools/sf3_match in place while four workers verified against it. The new add_argument
block landed between `--fill-epilogue` and its help=, raising IndentationError at line 1129 --
EVERY worker's verification failed for the duration. A verified with a probe copy and re-verified
with the official tool once it parsed; D reported it independently.
2. Reverting an untested transform, my scripted edit DELETED `_LOAD_RA`, the load-delay regex
fill_epilogue needs, breaking maspsx=epilogue for all 21 regions carrying the token. The
whole-binary gate caught it immediately (exit 2, no result line) and it is repaired at the point
of damage.
Guard adopted for every future tool edit: ast.parse immediately after saving, and one atomic write
rather than a scripted multi-step edit. A worker cannot tell a transient tool break from a broken row,
and the cost lands in their context.
ONE DEFERRED ITEM COSTS A BODY: `gp=-` is SINGLE-VALUED per region. D's 0x80102F58 (60 B) needs
D_801221A8/AA/AC registered with gp markers (proven: 60/0 with, 72 without), but A's merged 0x800FFF60
needs those same three ABSENT from the registry because it uses them as SYMBOLS with per-site macro
expansion. Measured: adding the rows breaks A's 140 -> 124; adding them AND giving A three gp=- overrides
restores A to 140/0 -- but the parser refuses with `duplicate override key 'gp'`. The resolution is
proven; only the option key's multiplicity blocks it. Cheapest remaining body in the phase.
The la SYM+N harness transform the developer directed was implemented and REVERTED, because it did not
close its target and an untested transform in the shipped tool is worse than a documented design. Its
mechanism, its guard, where it belongs in the pipeline, and the open question about WHICH STAGE's text
it must rewrite are all in the ledger so it can be resumed without re-deriving.
Also recorded: the gp-rewrite bug worker A found and I fixed (a stale %hi from an unrecorded register
redefinition); the compiler-boundary class (3 rows no vendored cc1 can build); the per-region codegen
flag policy; D's measured tier finding (4/4 with a call or a frame, 0/4 on leaves); and the reading
rules discovered this session, each with at least two instances.
**670 bodies / 680 regions. Phase: 602 -> 670 = +68.**
**+3 bodies, all verified by me from fresh --work dirs at their REAL extents:**
0x80107CCC (76 B, cc1bin=gcc-2.8.1-psx) -- A's row, unlocked by the tool fix below
0x8002D060 (72 B, D) 0x8006FAEC (88 B, D)
And +2 earlier this round: 0x8004D7C8 (220 B, B), 0x8003A9C8 (240 B, B).
**WORKER A FOUND A REAL BUG IN tools/sf3_match AND I FIXED IT.** `rewrite_gp_accesses`'s `_GP_LO`
branch rewrote `lw $2,%lo(SYM)($R)` to `lw $2,%gp_rel(SYM)($gp)` and then `continue`d WITHOUT recording
that the rewrite redefines its destination register. `carrying` kept the stale `%hi`, the register's
next use was scored as an unrewritten use, and the `%hi` was deliberately kept -- so the region came
out exactly 4 bytes long, one instruction.
I reproduced the evidence independently before touching the code, in 0x80107CCC's pre-fix output:
lui $2,%hi(D_801221CC) # high
lw $2,%gp_rel(D_801221CC)($gp)
The same failure the docstring says was fixed for the adjacent-lines case, still live for the
load-whose-result-is-used-later case. One-line fix, A's reasoning and name in the comment, plus why it
stayed invisible: nothing breaks until a gp-marked LOAD feeds something.
Measured: 80/LENGTH-MISMATCH -> 76/0/MATCH. **The tool change was GATED, not trusted**: the pass
touches every gp region, so the full gate was re-run -- c_regions=678, differing_bytes=0, MATCH. That
also answers A, which asked whether the four merged cc1bin rows needed re-verification; the gate is
what proves it and it was green.
**AND WORKER D FOUND THE SAME PASS FROM THE OPPOSITE DIRECTION** on 0x800FFBBC: cc1 2.8.1 puts a safe
`lui $2,%hi(...)` in a branch delay slot, the rewrite folds the hi/lo pair to gp-relative, the lui
DISAPPEARS and the store slides into the delay slot (48 -> 44). So A's case is the rewrite failing to
fire and D's is the rewrite firing and changing a schedule -- two faces of one pass, on two rows, found
by two workers who could not see each other's row. D's framing is the open item: a maspsx mode that
re-derives the delay slot after the gp-relative rewrite would likely close a class.
**cc1bin's measured boundary, and a correction to my own guidance.** B swept the compilers: 2.5.7 /
2.6.0 / 2.6.3 / 2.7.2 / 2.7.2-cdk / 2.7.2-psx give a MERGED epilogue; 2.8.0-psx / 2.8.1-psx give
SEPARATE jr $31 per return. So return merging is a 2.7.2-family behaviour. BUT 2.8.x also changes the
GLOBAL ACCESS FORM (explicit lui %hi / lw %lo pair instead of the symbolic load maspsx turns into
lw v0,2080(gp)). **The lever moves two axes at once and is not free** -- which corrects what I told A,
B and D when routing the multi-exit rows.
**0x80107D7C sits BETWEEN the compilers and is a genuine open question.** B: default 112, 2.8.1 104,
target 108. I swept all TEN vendored builds because B's table listed eight: only 2.7.2-cdk produces 108
bytes, and it is 30 bytes off in content. 2.91.66 and 2.95.2 give 100. So the length is reachable and
the content is not, on any vendored compiler.
**Two corrections of record, both mine:** I guessed extents instead of reading
config/function_extents.tsv (testing A's 76-byte row against an 80-byte extent and reporting
LENGTH-MISMATCH from a correct candidate); and I told three workers cc1bin was a clean lever for the
multi-exit rows when B's sweep shows it moves two axes.
**Ruling on A's per-region codegen flag request (0x800AB504): (b) class-bound, with the diagnosis
kept** -- because `-fno-strength-reduce` gives 152 against a 148 target. A flag that closes a row is a
lever; one that moves a row 16 bytes closer to a different number is a diagnosis. But the PRINCIPLE is
recorded for the next such row: a codegen flag is admissible on the same terms as cc1bin, i.e. a named
mechanism in the bytes, and A has one (address normalisation creating a second loop-carried pointer).
**D: the indexed and pointer loop forms COEXIST and neither carries across rows.** 0x8002D060 matches
only as an INDEXED loop; 0x80107B40 wants the pointer form. A direct refutation of pattern-carrying
from the worker with the most reason to trust a pattern. Also: the both-sides-volatile rule TRANSFERS
(0x8006FAEC), with the narrowing measured (only interleaved accesses need it); and READ the displacement
and compute the symbol rather than inferring it from an adjacent symbol's name (gp+0xBA4 -> 0x801224DC).
**Charter note: `--cc1-flag=X` REPLACES the default `-quiet -O2 -G0` set**, so a diagnosis run must
pass all four explicitly. A's first -fthread-jumps probe read 200 B purely because -O2 was dropped.
MERGE 23: +2 bodies from worker D's un-attempted band, both re-verified from fresh --work dirs:
0x800582AC (64 B) and 0x800A82D0 (64 B). Gate c_regions=676, differing_bytes=0, MATCH.
DISPATCH CORRECTION, and it came from checking a claim rather than forwarding it. D reported three
multi-exit rows as unclosable because 'my multi-return probes share ONE exit under all ten cc1 builds'.
Measured from the binary: ALL SEVEN unregistered multi-exit rows are >=2 jr $31, which is the gate's
precondition for naming an alternative cc1 -- so the restriction PERMITS the lever on every one, with
four merged rows as precedent.
And C's measured row refutes the probe conclusion directly: 0x801008DC is 3 exits and the default gives
the correct 88 bytes with 58 DIFFERING bytes because it merges all three returns, while
cc1bin=gcc-2.8.1-psx gives 88/0/MATCH. The merge is SHAPE-dependent, and a synthetic probe speaks only
for the shape it probes. I made exactly this error earlier in the phase with a probe-dependent
mechanism written into tools/sf3_match.
Rows routed: A gets 0x80107CCC (3 exits, A has the diagnosis), B gets 0x800FF6DC / 0x80100998 /
0x80107D7C, D gets 0x800FFBBC.
Also recorded: D's self-caught harness hazard -- its run helper leaves the LAST variant on disk, so its
first verification of 0x800A82D0 ran against the wrong variant and report.tsv's md5 described it. D
re-verified from a fresh directory and made the re-write explicit in its procedure. This is the failure
mode that would produce a FALSE MATCH CLAIM rather than a missed one, and it is invisible from outside
because the report's own md5 agrees with the wrong file.
MERGE 22: +2 bodies, both from worker D's un-attempted band. 0x801092C0 (52 B) and 0x80085B44 (60 B),
both re-verified by me from fresh --work dirs first. Gate: c_regions=674, differing_bytes=0, MATCH.
POOL FILTER, SECOND LEAK, MY ERROR CORRECTED. D found 0x80012D54 -- the charter-blocked 0x80012xxx
primitive-init family, with class , so the address-parsing rule I installed after the FIRST leak
(0x80012A98) had no text to match on. D read its bytes and confirmed the family.
I had explicitly DECLINED band-blocking the first time, arguing it 'would silently discard
unclassified rows on an inference the charter does not make'. That was backwards: the charter names
the family as 'the 0x80012xxx primitive-init family', i.e. BY ADDRESS BAND, and earlier phases already
acted on it -- 0x80012A10/AE0/B20/CFC, all class '-', are excluded BY NAME in the Makefile. Two rows
leaked by label phrasing and the second had no label at all. The band is the predicate. Blocked 8 -> 15,
pools 70 -> 54.
Also applying my own corrected commit idiom: this commit names the two claim sources rather than
using 'git add -A src/', which swept three of D's in-flight rows into an earlier commit.
New levers recorded: LOAD/STORE INTERLEAVING NEEDS BOTH SIDES VOLATILE (a volatile load can still
hoist above a plain store and vice versa; cc1 orders volatile only against volatile) -- and from
0x801092C0, unsigned-parameter signedness read from the instruction AFTER the test, volatile on a
table pointer for an alias reason, cookbook 19 generalising to a forward loop, and
`p += i + start; p->p0 = 0;` over the subscript form.
My per-merge command used `git add -A $(git status --porcelain src/)`, which adds EVERY modified or
untracked file under src/ -- and four workers write there concurrently. One merge commit therefore
carried three files I had not verified: an edit to src/func_80013114.c, a new src/func_80085B44.c, and
a 20-line DELETION from src/func_800FBF5C.c.
Measured, not assumed: all three are UNREGISTERED, so no region references them and the full gate
re-run on the committed tree is c_regions=672 / differing_bytes=0 / MATCH. Also committed here: the
deletion of src/func_80013114.c, which a worker made after I had committed the file, so that HEAD and
the working tree agree again.
The dangerous variant is one edit away and did not happen: if a worker edits a REGISTERED region's
source mid-merge, that edit is committed and the gate verdict from moments earlier no longer describes
the committed tree. Rule from here: a merge adds the named claim sources plus config/docs/tools, never
src/ wholesale.
**+4 bodies: 0x80102A80 (132, B, maspsx=epilogue), 0x800FFF60 (140, A, maspsx=epilogue),
0x80102A00 (128, B's source + the NEW maspsx=regread predicate), and the positioning is now
663 bodies / 672 regions. Phase: 602 -> 663 = +61.** Gate byte-exact at every merge, SHA-1 unchanged,
make check exit 0, 344 tests green.
**THE HARNESS PATCH IS THE HEADLINE, because it turns a class into a predicate.** Worker B reported
`0x80102A00` as an UNCLOSABLE row with the mechanism pinned to the harness's own `--work`
intermediates, and refused to touch `tools/` -- correct, since it could not know the default path's
invariants. Its diagnosis: finding 27's gap was fixed by `line_jumps_via_reg` (a load feeding a
REGISTER JUMP), and **this row is the complementary case in the opposite direction -- the jump
ignores the register, but the instruction in its DELAY SLOT reads it.**
lw $2, D_8011FD2C <- loads $2
jal func_80103FCC
sb $0, 0($2) <- the SLOT FILLER reads $2
cc1 emits that with no `#nop` and no marker, correctly by the documented load-to-use rule (gap of 1).
**ASPSX was conservative ACROSS a jump** -- the slot filler is part of the jump -- and maspsx's own
trace says so: `#nop # DEBUG: 'jal func_80103FCC' does not load from $2`. It declined the row for
exactly the reason the original accepted it.
Implementation: `_jump_slot_filler_reads_reg`, on the **existing opt-in** `--nop-on-reg-read` /
`maspsx=regread` token, so the default path is byte-identical and the green gate cannot move. Confirmed
safe rather than assumed: **no registered region named that token before this commit.** Restricted to
`j`/`jal` -- `jr`/`jalr` slot fillers are the same shape but UNMEASURED, and an opt-in predicate whose
whole purpose is default-identity has no place for an unmeasured widening.
Measured, on the source unchanged: **default 124 LENGTH-MISMATCH -> `maspsx=regread` 128 / 0 differing
/ MATCH.** So B's "unclosable" row is a body, and B's candidate source is now `src/func_80102A00.c`
with a header recording that the token is REQUIRED and must not be "cleaned up".
**THE PATCH WAS REGENERATED IN THE SAME COMMIT, which is the whole point of the discipline.**
`tools/patches/maspsx-phase10-r1r2.patch` went 229 -> 289 lines, and all 7 `test_maspsx_patch` tests
pass -- so pristine + patch reconstructs the working tree byte-for-byte and a fresh clone can still
rebuild the gate. Phase 11's `ea51ac9` did exactly this correction WITHOUT regenerating the patch and
left a fresh clone broken; that is why the test exists and it is why this was done in one commit.
**Cookbook, two new entries:**
* **61c -- THE REGISTRY DECIDES THE SPELLING.** A closed two rows today in OPPOSITE directions and the
deciding fact is the registry entry, not the code's shape: `0x800FB758`'s address IS `gp`-marked and
the original writes it absolutely, so the source must use the LITERAL (a symbol lets the harness
rewrite to `%gp_rel`); `0x800FFF60`'s three addresses are NOT registered, so the source must use
SYMBOLS (as literals the address becomes a value and cc1 CSEs it -- the index's `cc1=-G4` attempt was
chasing the wrong thing). Rule: check the registry first. This is the source-side companion to
finding 46.
* **61d -- the `nop_on_reg_read` second gap**, above, with the trace line as the receipt.
Also recorded: worker A's `0x80011484` is no longer the index's "UNRESOLVED" row -- A derived it from
the bytes as an octagonal distance approximation (`s=|dx|+|dz|`, `d=||dx|-|dz||`, then `d<h -> s-q`,
`h+q<d -> s`, else `s-(s>>3)`) with both absolutes as SWAPPED SUBTRACTIONS, and the build is 120 with
a scheduler residual. And A's `0x8002FB54` gave the phase a new lever type: **a DUPLICATED MASK is
evidence of the variable's WIDTH** (`unsigned char r` vs `int r &= 0xFF`), because a byte-typed object
masks at every read and no placement can fake that.
Three workers converged independently on the same split within one hour, which is normally the
signal to act on. This time acting on it would have been wrong, and the reason is measurable.
D, from its scoreboard: what matters is whether the row has been attempted WITH THE CURRENT LEVER
SET, not which pool it is in.
C, from 11 classifications: prio-1 does not distinguish "a name that implies a SOURCE SHAPE" from
"a name that implies a COMPILER BEHAVIOUR".
A, from 14 rows touched: what predicts closability is the WORDING of the row's own note, not its
prio -- and a grep would separate the two groups in one pass.
**The insight is right and is kept. The proposed discriminator is not predictive, and I measured it
rather than arguing about it.** I classified every row A and C named, by outcome, against the pool's
own record:
MATCHED (8): 0x8010036C 0x800690E4 0x80018284 0x80094370 0x80065494 0x800C3514 0x80022E44 0x8002DF1C
-> source-shape 2 | unknown 2 | **residual-class 4**
RESISTED (7): 0x8010400C 0x800A6C34 0x80107CCC 0x80050674 0x80023D40 0x80024630 0x80101E50
-> source-shape 1 | **unknown 3** | residual-class 3
Precision of "residual-class => will resist": **3/7, worse than a coin flip**, and the rule would have
**deprioritised four of the eight actual successes.** The decisive examples are the proposers' own:
C's `0x800C3514` is classed `alloc+layout (priority selector; ...)` and MATCHED -- the lever was
ARITY; B's `0x8002DF1C` is classed `candidate_bytes=104 = CORRECT length, 58 differing bytes` and
MATCHED -- the lever was a `goto` into the body. Both would have gone to the back of the queue.
So `kind` is DISARMED as an ordering rule and retained only as a LABEL written into every pool row,
with the failure and its numbers in the header so no worker trusts it. A test asserts it is not in
the sort key and cannot drop a row, so it cannot be quietly re-enabled.
**This is defect 5's shape exactly** -- a text heuristic over free prose, on this same field, which
inverted 62 rows. The difference is only that this one was tested before being believed. The real
discriminator is whether a residual has been LOCALISED to an allocation or scheduling decision, and
that requires reading the bytes rather than the note. **Free prose is not evidence about closability.**
Also in this commit: `negatives-d-unattempted.tsv`, the 74-row UN-PUSHED band that worker D asked for
and that D's own numbers justify (2-for-7 on rows a previous session pushed to 1-6 bytes, against
one-spelling closes on rows nobody had pushed). Rows with no mechanism named, not registered, not
excluded, not charter-blocked, not held in the ledger. This is D's criterion applied as a band rather
than as a preference.
Pools after the re-cut: 70 rows (a 16 / b 19 / c 21 / d 14).
MERGES. +13 bodies across rounds 12-14, each re-verified by me from a fresh --work dir first:
0x80042CE0 (80, D) 0x80018284 (80, A) 0x80094370 (84, A) 0x8002DF1C (104, B)
0x80065494 (80, C) 0x800C3514 (88, C) 0x801097A0 (128, D)
Gate every time: differing_bytes=0 result=MATCH, SHA-1 unchanged. make check exit 0.
**652 bodies / 662 regions. Phase: 602 -> 652 = +50.**
**A DEPENDENT ROW WAS RE-VERIFIED WHEN ITS DEPENDENCY LANDED, and this is a new rule.** Worker C's
`0x80091490` was merged as a DEPENDENT claim whose callee `0x80018284` was unreconstructed, with a
3-argument prototype INFERRED from the call site. Worker A then reconstructed `0x80018284`. Merging it
changes `config/symbols.tsv`, which is the environment that row was verified IN -- so I re-ran
`0x80091490` after the merge: still `differing_bytes=0 result=MATCH`. The arity check I promised:
A's `int func_80018284(int *a0, int a1, struct V8 *a2)` vs C's inferred `(int, int, int *)` --
**same arity (3)**, differing only in pointee types on args 1 and 3, which are **byte-invisible** at a
pass-through and a literal-0 site. So the inference held. Rule recorded: **a dependent claim must be
re-verified whenever its dependency is merged**, because the symbol set is part of its input.
**`0x801097A0` -- THE PHASE'S FIRST GENUINE INLINE-ASM ROW IS IN.** Worker D needed seven spellings and
each of the four asm properties is a *measurement with its counter-shape*, which is what makes this an
authorised use rather than a shortcut:
1. `$31` cannot be WRITTEN from C. `register int ra __asm__("$31"); ra = ...;` makes cc1 keep a frame
(132 B) and DELETES the assignment -- reading `$31` is a documented binding, writing it is not
expressible. That is the "provably cannot express" clause demonstrated rather than asserted.
2. The call must NOT be a C call: as a C call cc1 preserves `$31` itself and adds a prologue and an
epilogue (140 B), while **the original has NO FRAME AT ALL**. Written as an asm `jal`, cc1 never
sees a call. This single fact is what makes the row expressible.
3. Zero operands must be `$0` LITERALLY: `gte_ldOFX(0)` passes `"r"(0)`, cc1 materialises a register,
and the original's `ctc2 zero,$24` becomes `move` + `ctc2`.
4. The constant register must be pinned to `$8` (cookbook 160); otherwise the seven constants land in
`a0` (10 differing bytes = five `li` plus five `ctc2` register fields).
Also measured: maspsx supplies the jump-slot `nop` after the asm `jal` (writing one explicitly gives
132 B -- two nops), and the CP0 constant is `0x40000000`, not `0x4000`: bit 30 is the COP2-enable bit,
and `0x4000` compiles to `ori` and does not match.
**I wrote `include/gtemac.h` myself, because worker D refused to and was RIGHT.** D's charter section 4
forbids it from writing under `include/`, and my authorisation conflicted with the charter. D held its
write scope and handed me the macro text with the note that "I followed the charter because it is a
file and it is unambiguous". **That is the correct resolution of a coordinator error and it is the
behaviour I want**: the charter is a file, it was unambiguous, and D did not quietly exceed it. The two
macros (`gte_ldZSF3`/`gte_ldZSF4`, $29/$30) are written in exactly the form of the existing entries,
with the evidence in the comment -- the NUMBERS are the evidence, not the register names. The open
question D raises is recorded for the next charter: whether `include/` should be a worker write scope
or whether worker-authored macro text passed to the coordinator is the right division of labour.
**CHARTER SECTION 7'S NOT-COUNTED ROW IS NOW EXCLUDED BY NAME, and this one is my miss.** Worker C found
`0x80107C5C` (112 B) in its pool and skipped it because section 7 says it "matches but has no
documented source -- it is NOT counted; do not claim it". **I had READ that sentence earlier in this
session and did not act on it.** A worker who did not know the sentence would have produced a body the
phase does not count. It is now in `NAMED_EXCLUSIONS` -- the one place an exclusion lives -- with a
comment explaining that its reason differs from the rest of the list.
**`sf3_free` CRASHED, worker B caught it, and the fix is in.** My `--claim` addition used `free` and
`failed` without initialising them, so **every FREE address raised NameError and exited 1**. Worker B
reported the reproduction and, critically, the asymmetry that makes it matter: **the crash happened
AFTER the `FREE` line was printed, so a worker reading stdout saw the right answer while `$?` said 1**
-- a false BLOCKED for anyone checking the exit code or running under `set -e`. The harmless direction
of the two, but it costs a row per occurrence and it would have looked like a ledger problem rather
than a tool problem. Fixed (`free: list[int] = []`, `failed = False`), B's exact reproduction now
exits 0, and the 25-test suite passes. **The test suite caught this on the very change that introduced
it** -- recorded because that is the tests earning their keep, and because the tool whose whole purpose
is to be trusted unread is the one that must never be wrong about its exit code.
**`--claim` added so check-and-claim is ONE step.** The race is real and worker B hit it: B appended its
`wip` row in the same shell line as the check, *before* reading the output, so the ledger's last row for
`0x80042CE0` said B while D held it. B caught it, did not touch the row, and restored D's hold by
appending the row back (last-row-wins). **In the safe direction** -- B's own row masked the true holder
as TAKEN -- but the inverse ordering, appending on a stale read, is how two workers end up on one
address. `--claim WORKER` now appends only if every address is free, and a refusal writes nothing.
**Worker C found the FOURTH row where the class name pointed at the right place and the mechanism was
not what the note said.** `0x800C3514`'s note described "all stack loads hoisted, beqz+nop+li groups";
the lever was **ARITY** -- the function takes SIX `unsigned char` parameters, the fifth and sixth
arriving on the stack (`lbu 16(sp)`/`lbu 20(sp)`), which the note read as hoisted loads. Measured: the
four-argument spelling gives 56 bytes, twelve short, with neither stack load present. The tally is now
`alloc-tiebreak` -> a pass-through argument (finding 164) | `constant-materialisation-order` -> source
statement order | `return-merge` -> a literal twin and a compiler revision | `alloc+layout` -> arity.