12 reader agents over 172 substantial agent findings from waves P and Q; 16 survived their novelty
filter; merging duplicates and dropping what §174-§178 already own left EIGHT levers. The
synthesizer re-read every gcc/maspsx/masked_diff citation and corrected three wrong line numbers
inline; I independently re-verified two of them (loop.c:3500 `src = SET_SRC (bl->init_set)`,
valid_initial_value_p at :3509, bl->initial_value at :3511; and maspsx's tab-literal `.set\t` test
at __init__.py:844-848) -- both exact.
A. A loop-walked pointer PARAMETER hands its argument register to the giv (9 byte-proofs).
record_initial records assign_parms' own incoming-argument copy as the biv's init_set, so
emit_iv_add_mult re-references the HARD register in the preheader, extending $aN's live range
and costing the parameter its hard-reg tie. Fix: body-local copy, or an identity re-tie whose
ASM_OPERANDS SET_SRC makes valid_initial_value_p refuse. Explicitly paired with §70, which
documents the SAME chain used in the opposite direction.
B. The hand-written-asm transcription checklist (10 byte-proofs): `.set` needs a literal TAB or
maspsx never updates is_reorder and re-nops every branch; `.ent` does not update it either;
displacements must be DECIMAL (bare int() before the range check); maspsx's LOAD-delay nop is
UNCONDITIONAL, so you manage branch-delay nops and must NOT manage load-delay ones; internal
labels need a `.L` prefix or masked_diff truncates the function at the stray symtab entry.
Also flags a real CONFLICT with the banked trampoline rules and gives the A/B to resolve it.
C-H. No-epilogue functions must be file-scope __asm__; gte_stflg must clobber $12; a
>2*MAX_MOVE_BYTES copy is a struct assignment; pinning a walked pointer disables strength
reduction entirely; a pin can CREATE a combine LOG_LINK (n=1, marked as such); a mid-body
.global pair slices a byte-comparable fragment.
Ends with a "considered and NOT banked" list -- including a softened claim where the reader's
derivation was not traced line-by-line, and a corrected symbol typo. Nothing silently dropped.
Cookbook 554 -> 564 sections.
Wave Q (90 cards / 6,249 ins) was stopped mid-flight, then a repair-only pass recovered 12 of its
39 unfinished drafts (+579 ins, 51 -> 64 verified MATCH). Banking took two new tools, because the
gate failed three times on causes no C-level check can see.
tools/fragment_check.py (NEW) — the enclosing-function trap, both halves, in milliseconds:
* a draft whose [addr, addr+4*nins) SUBSUMES another symbol. splat labels addresses that are not
functions (a bare epilogue tail, a jump-only branch target, an interior label); convert the
enclosing function and a neighbour's `jal .L80050F24` loses its target.
* a draft that DEFINES another stub's symbol in inline asm. MoveImage's repair agent correctly
worked out that a trailing epilogue belongs to MoveImage (splat attributes those bytes to the
NEXT symbol) and emitted `.globl SYS_OBJ_8F4` itself -- right bytes, match_one passes, and the
assembler then sees the symbol defined twice because the fragment still has its own .s.
My first hypothesis was the subsumption case; the tool REFUTED it (clean) and the redefinition
case was the real one. Recording that order, because I built the tool before verifying the
premise -- R35 inverted.
tools/bisect_slate.py (NEW) — gate_main's built-in bisect ran THREE HOURS on a 45-draft slate and
printed nothing. Three defects: it never checked whether the BASELINE builds (if no subset can
pass, the search is doomed from step 1), it logs only when it isolates a single draft, and its
re-split rule degrades toward one rebuild per draft. The replacement runs the NULL CONTROL first
(the same control that refuted this phase's "main is link-blocked" misdiagnosis), logs every step,
and does a true binary search: it found the culprit (SYS_OBJ_1790) in 7 steps and 176 SECONDS.
Result: 40 banked, main stubs 1,803 -> 1,763.
§178 — SIX LEVERS FROM THE WAVE-P JOURNALS, each byte-proven and source-cited. Four wave-P repair
agents REFUTED the first pass's own diagnosis by dumping cc1 -dS/-da and reading gcc-2.7.2. The
meta-finding leads the section: "REGALLOC-PERM" is this project's most over-diagnosed class -- in
four functions the symptom was a register swap and the cause was in cse.c or sched.c, decided
BEFORE allocation, which is exactly why pins and statement order all failed.
A. The $0-add OPAQUE COPY defeats cse.c:826 make_regs_eqv (a PLUS is not a (set reg reg)), so the
parm pseudo keeps its register. MATCH on first compile; 3 of 5 pins then became dead weight.
B. A `return <const>` is a priority-1 hard-reg set that the BACKWARD list scheduler places FIRST
in the block, making hard $v0 live across a temp's range. Lever: goto a shared return tail.
C. birthing_insn_p (sched.c:2469) boosts only single-set destinations; splitting a 3-set temp
boosts the insn and drags its feeder chain down.
D. NEW IDIOM: a NARROW destination type blocks copy elision (SI->HI cannot be coalesced), so the
copy survives at its source position -- one type change worth ~20 instructions.
E. The ZERO-OFFSET ALIAS HOLE: memrefs_conflict_p's find_symbolic_term path is only reachable for
offset-0 fields, so an offset-0 store silently loses its dependence and floats.
F. MEM_IN_STRUCT_P asymmetry in true_dependence (sched.c:817): struct-varying vs scalar-fixed do
not depend. Struct-vs-scalar externs are a scheduling decision, not cosmetics.
G. Two modelling traps: `sw $a1,SYM($a0)` is ONE cc1 insn (the lui/addu/store triple is gas -G0
macro expansion, not cc1 output); and __asm__ __volatile__ with a memory clobber is a FULL
barrier that also sinks address chains.
Plus the exhaustion result: 2,240- and 5,040-variant statement-order sweeps moved nothing, because
the schedule was DAG-determined. When order does not matter, look for an alias or set-count
property, not a permutation.
§176j-2 — THE REPAIR PASS, MEASURED: 12 of 39 recovered / 579 ins, taking wave Q from 51 matches
(3,631 ins) to 64 (4,245). Closeness must be COUNTED, not read off the first differing index (my
first measurement reported six "closeness 0" drafts that were actually truncated).
§176k — two silent selector bugs: ranking gate groups by MEMBER COUNT collapses a wide band to the
smallest functions when the gate cost is per-slate (60 cards/2,604 ins chosen where 46/4,829 were
available); and a selector that globs its own output counts the previous attempt as spent (pool
106 -> 46). Any derive-from-disk rule must exclude the artifact it is about to produce.
Eleven wave-Q functions in 800c/800c3 sat at closeness 1-3 with the same epilogue residual, and
every agent independently filed it as intrinsic ("epilogue-delay-slot-unfillable", "gcc/maspsx
structural wall"). It is neither intrinsic nor a scheduling problem.
Source-confirmed at gcc-2.7.2/config/mips/mips.c:5376 --
int mips_epilogue_delay_slots () {
if (current_frame_info.total_size == 0) return 1; /* no frame */
if (current_frame_info.mask == RA_MASK && current_frame_info.fmask == 0) return 1; /* only $ra */
return 0;
}
gcc offers the epilogue a delay slot ONLY when the function allocates no stack, or saves nothing
but $ra. Otherwise the slot is never offered to the scheduler and the emitter puts the stack
restore there instead (mips.c:5276, the tsize > 0 path).
So the lever is the CALLEE-SAVED SET, steerable from C: the first value whose live range spans a
jal costs an $s register and flips the switch. To gain a filled slot, hold nothing across a call
(recompute or re-read after it); to lose one, hoist a load above the call. Register pins are the
WRONG tool here -- §176-C already established a pin cannot schedule across a call.
~600 instructions were three instructions from banked and about to be written off. The meta-lesson
(R17): when N independent agents call one residual "structural", read the compiler -- the answer
was forty lines of mips.c already sitting in tools/reference/gcc-2.7.2/.
§176i — WHAT A STATIC PRE-GATE CHECK CAN AND CANNOT PROVE. pregate_check validated wave Q's slate
as clean in 0.7s (the first slate all session to reach the gate pre-validated) and the build then
failed twice, both times outside what any text-only check can see:
1. LINK-time undefined reference: `.L80050F24` lives INSIDE gfx2D_BG0_OBJ_698 and another
function's .s branches to it -- converting a function to C deletes the local labels its
neighbours jump to. Statically checkable, but from the .s files, not the .c: scan every other
.s for label references landing inside a candidate's address range. Worth building.
2. BYTE mismatch: with the link fixed the binary BUILT and the SHA differed -- the §174 law 1c
class, which reloc_identity had already named six suspects for.
The division of labour to rely on: text checker for SHAPE, reloc oracle for IDENTITY, gate for
TRUTH. A clean pre-gate is a licence to build, not a prediction of success -- and when the binary
builds but the hash differs, BISECT, because it costs wall-clock and zero tokens.
§176j — STOPPING A WAVE MID-FLIGHT COSTS THE IN-FLIGHT TAIL. Wave Q stopped early: 51/90 verified
(3,631 of 6,249 ins) versus the 96-97% the same pipeline yields when allowed to finish. The loss is
SUSPENDED, not destroyed -- every draft persists on disk, 15 of the 39 unfinished sit at closeness
<=10. Do NOT resume the workflow to recover it (resume re-runs unfinished agents from scratch at
full cost); use a REPAIR-ONLY pass over the <=30 band instead. Decision rule: before killing a long
agent run, price the tail -- stopping converts near-matches into "needs a cheaper second pass",
which is a deferral, not a saving.
The 68%->95% lever from §176h.C2, mechanized. Reconciliation belongs INSIDE the wave: a banked
draft's declarations become the TU's, so a sibling clash hardens into a file clash and post-bank
recovery is measurably worse (18 parked drafts still MATCH, only 1 survived after their wave banked
vs 5 before).
AUTO-FIXES, each re-verified with match_one and REVERTED if a byte moves (a declaration change is
a codegen change, §176f):
* COSMETIC-TYPEDEF two names for a structurally identical struct -> adopt the other. Compared by
BODY, never by name (OtBlk_80015498 == OtBlk_80016450; Elem12 != B12). This body comparison is
also the answer to §176h.C's spelled-name limit.
* SIGNEDNESS / ALIAS / ARRAY-VS-SCALAR -> adopt the TU's spelling, fixing the use site.
* DEFPARAMS (NEW LEVER) -> adopt the TU's parameter types on the DEFINITION and re-narrow with a
shadowing local: `void f(s32 a0_p) { s16 a0 = (s16)a0_p; <body unchanged> }`. One textual
insertion instead of rewriting every use site, and the cast emits the same sll/sra pair.
Byte-identical on both cases tried.
REFUSES, with named reasons, because these are decisions and not edits: DIFFERENT-STRUCT (two real
layouts for one symbol), IMMOVABLE-TU-DECL (gate_main reverts src/, so it needs its own commit +
rebuild + R22), DEF-SIDE-RETURN (adopting the TU's return type usually costs the match -- measured
on func_8001ABBC), and BROKE-MATCH for anything its own verification rejects.
Measured on wave P's leftover slate: 6 -> 9 compatible, 3 auto-reconciled, 2 repairs reverted by
the tool's own byte check, 7 named for a human.
build_wave_atlas: --rank mass (main's gate cost is per SLATE, so ranking groups by member count
silently collapses a wide band to the smallest functions -- measured 60 cards/2,604 ins where 46
cards/4,829 ins were available), and the selector no longer counts ITS OWN OUTPUT as already-waved
(re-running with identical filters had been shrinking the pool 60 -> 46).
MEASURED, and it inverts the obvious plan. "Bank the clean drafts now, recover the conflicted ones
later" is backwards: of 18 wave-O/P drafts parked and re-verified still MATCH, only 1 survived
resolve_conflicts once their wave had banked -- versus 5 before it.
The mechanism: a banked draft's declarations BECOME the TU's, so every parked draft that merely
disagreed with a SIBLING now disagrees with the FILE, which is the stricter arbiter (a sibling
clash can be settled by editing either side; a file clash only by editing the draft, and some
cannot be settled at all because gate_main reverts src/ before every build). Worse, the auto-rename
that reconciles a cosmetic clash pre-bank becomes a DUPLICATE TYPEDEF post-bank, because the name
it renames to is now defined in the file.
So iterate the dry run to `N -> N compatible, 0 dropped` BEFORE spending the first rebuild, and
budget reconciliation into the wave rather than after it.
pregate_check: DUPLICATE-TYPEDEF now fires on ANY redefinition, not only differing bodies. C89 has
no compatible-redefinition allowance for typedefs, and my first version missed exactly the case the
tool exists for -- two identical `OtBlk_80016450` definitions, which the compiler rejected on the
next rebuild. Measured, not reasoned.
Also banks the auto-reconciler's rule: tell a COSMETIC clash from a REAL one by comparing struct
BODIES, not names. OtBlk_80015498 vs OtBlk_80016450 are the same {s32 a; s32 b[4];} and rename
byte-identically (both re-verified MATCH); Elem12 vs B12 genuinely differ and were refused.
Wave P drafted at 97% and cost A DOZEN clean rebuilds to bank, and not one of those rebuilds
failed on a matching problem -- every one failed on a TEXTUAL property of the substituted file
that a grep could have reported instantly. This is that grep.
gate_main's resolve_conflicts cannot answer it, and not from carelessness: it inspects the DRAFTS
while the compiler sees the FILE THEY LAND IN -- after typedef stripping and renaming, at each
draft's own insertion offset, interleaved with declarations the file already had. Those
transformations run AFTER the conflict check passes. So substitute() gained write=False and this
tool checks the artifact itself.
Five checks, each earned by a rebuild lost this session (§176h): typedef used above its
definition; type never defined anywhere; duplicate typedef with different bodies; one symbol
declared two incompatible ways; definition contradicting a visible prototype.
CALIBRATED AGAINST THE COMPILER, NOT AGAINST C89 PEDANTRY -- and this mattered. The first version
reported 4 hard FAILUREs on the slate that had just built BYTE-IDENTICAL:
- it ignored SCOPE, but the project deliberately uses block-scope extern blocks, and a declaration
inside one function cannot conflict with a definition elsewhere. Now brace-depth aware.
- it split `void f()` from `void f(void)`, which gcc-2.7.2 accepts. Normalized.
- it called every def-vs-decl mismatch fatal, but gcc-2.7.2 accepted `void f(void*,s32)` against a
`void f(s8*,s32)` definition and even `G3P *f(...)` against `G4P *f(...)`. What it REJECTED was
a void/non-void RETURN split (func_8001ABBC). That split alone is FAIL; the rest are WARN.
Comments are masked via cdecl before any use-site scan (an unmasked scan reported 7 phantom hits).
R39 controls: the slate that banked is FAIL-free (exit 0, 3 informative warnings); four synthetic
defects each reported at FAIL; a clean text reports nothing; a block-scope extern does not
conflict; `short` vs `s16` does not conflict; array-vs-scalar does.
Wave P drafted at 97% and then cost a dozen clean rebuilds to bank, and not one of those rebuilds
failed on a matching problem. Banks the whole failure surface:
A. The SEVEN under-reporting holes in gate_main, all the same shape (R32): the checker never read
the destination TU, shared headers, a draft's own definition, lines with trailing comments,
typedef aliases, the build's own error text, or file order. Law: audit a batch-integration tool
for what it DOESN'T look at -- its verdicts can be correct on the inputs it reads and still be
worthless, because the compiler reads more.
B. Typedef handling, with the two wrong strategies that both look right: blanket STRIP (assumes
the surviving definition sits above the insertion point -- src/800.c defines Rec14 at 7336
while stubs wanting it sit at 7272), blanket RENAME (breaks drafts sharing an identical
typedef, because their externs stop agreeing -- my regression, three drafts at once), and the
rescan loop that deletes the definition it just renamed. The survivor is body-aware +
position-aware in a single pass over a snapshot.
C. The remaining limit: conflict detection compares spelled type NAMES, so three drafts each
defining their own Slot54 with different layouts all declare func_80032A74(Slot54*) and compare
equal. Comparing struct LAYOUTS is the real fix.
D. The measured cost shape -- drafting cheap and solved, integration expensive -- and therefore the
next lever: a STATIC pre-gate check over the substituted text, no make at all. Plus the R39
lesson that negative controls apply to the tool you are FIXING, not just the one you ship.
Wave P was the first full run of the 6k-ins doctrine: 60 cards / 6,589 ins in 2 gate groups,
59/60 claimed and 58/60 independently re-verified MATCH (6,372 ins), reloc_identity 58/58 AGREE
with ZERO symbol errors -- the second consecutive clean wave on symbol identity.
Banking cost a dozen rebuilds and exposed four more gate_main defects plus three regressions of
my own. The tool fixes, all NC'd:
- resolve_conflicts never read a draft's OWN DEFINITION, so the DEF-side wall (a draft defining
s32 func_X against a TU prototyping void func_X) reached the compiler. Now definition-aware:
it caught 13 conflicts up front where the build had been finding them one rebuild at a time.
- DECL and both typedef patterns anchored on end-of-line, so a TRAILING COMMENT hid a declaration
or typedef entirely -- and agents comment nearly everything they declare. Seventh instance of
one root cause: a scanner that looks green while reading less than it claims (R32).
- typedef handling is now BODY-AWARE and POSITION-AWARE, in a single pass:
* identical definition visible ABOVE the insertion point -> strip and reuse;
* same name, different shape -> rename (private to the draft);
* definition below the insertion point -> never reuse (it is not in scope there).
Three wrong strategies preceded this, each costing a rebuild: blanket strip (the file's copy can
sit BELOW the draft -> implicit-int, then a collision), blanket rename (breaks drafts that share
an IDENTICAL typedef, because their externs stop agreeing -- my regression, three drafts at
once), and a rescan loop that found the definition it had just renamed and stripped it as a
self-duplicate -> 'parse error before *'.
KNOWN LIMIT, recorded not fixed: the conflict check compares spelled type NAMES, so three drafts
each defining their own Slot54 with different layouts all declared func_80032A74(Slot54*) and
looked compatible. Comparing struct LAYOUTS for locally-defined types is the real fix.
13 + 4 verified-correct drafts are parked in two named buckets (competing local type models;
immovable TU declarations that gate_main reverts before every build).
A wave is now sized by INSTRUCTION MASS, not card count. The metric is instruction-weighted, so a
wave is worth what its instructions are worth: the 12-42-ins card lanes carried ~1,400 ins/wave
(~0.011pp, ~440 waves to finish) while wave O carried 6,266 ins at the same gate cost and the same
draft rate.
build_wave_atlas --target-ins draws cards until the instruction budget is met (still capped by n)
and refuses to under-fill silently. Standard recipe: --target-ins 6500 --min-ins 60 --max-ins 200
--max-bins 4, levers now including UNKNOWN.
THE MEASUREMENT BEHIND IT: draft rate barely decays with size -- wave M 98% at avg 51 ins, wave N
92% at avg 65, wave O 96% at avg 128. Mass is nearly free.
THE UNKNOWN UNLOCK: UNKNOWN is not a difficulty label, it means the atlas could not name a lever,
and it had been routed as needing its own bespoke lane. Wave O's 22-card R37 probe drafted it like
any other lane -- reclassifying ~138k ins (a quarter of everything open) as ordinary wave fuel.
With UNKNOWN in, 9,224 fns / 417,325 ins = 70% of all open instructions are agent-draftable; the
60-200-ins mass band alone is 164,357 ins ~= 27 waves, and is the band to work first.
Also banks the five-step PRE-GATE PROTOCOL (independent re-verify -> reloc_identity -> dry-run to
0-dropped -> reconcile declarations toward the form the match needs -> gate), cookbook §176g.
36 fresh wave-O cracks + 10 recovered wave-J/K/L drafts, verified in ONE clean rebuild:
143dbb89f34491258bbc27810d0a12ec8b43a8dd BYTE-IDENTICAL. main stubs 1881 -> 1835.
Wave O was a 3-arm 49-card wave (6,266 ins): main head-crack, main UNKNOWN, overlay UNKNOWN.
47/49 standalone MATCH, independently re-verified by me (R14) at 47/49 -- exact agreement --
and reloc_identity reported 46 AGREE / 0 MISMATCH, the first wave of the campaign with zero
symbol errors. THE UNKNOWN LEVER DRAFTS LIKE ANY OTHER LANE, which matters strategically: it is
~138k ins fleet-wide (a quarter of everything open) and was routed as "needs its own lane".
FOUR gate_main defects fixed here, each of which had been silently costing prior waves drafts:
- typedef stripping walked drafts in SLATE order while substitution happens at ADDRESS order, so
the surviving typedef could land BELOW a draft using it -> "syntax error before D_800A651C".
Verified the two orders genuinely diverge for both destination files in this slate.
- a BUILD failure (sha None) fell through to a silent bisect -- a full clean rebuild per step to
rediscover what the compiler had already printed and discarded. Now the error lines are shown
and the offending drafts named for undefined-reference/redefinition/conflicting-types.
(My first version of that printer TAILED a stderr+stdout concatenation and faithfully showed 25
lines of make progress chatter instead of the error -- selecting by position, not by content.)
- typesig treated "short" and "s16" as different types (R39 over-refusal). Aliases now normalize;
11/11 NC cases pass, with signedness, volatile and array-vs-scalar still conflicting correctly.
- conflict detection ignored shared headers: engine_core.h's DEFINE_ macros declare symbols in
their own bodies, so a draft's file-scope array decl of D_800A651C was illegal. Block-scoping
the draft's extern fixes it byte-identically.
DECLARATION RECONCILIATION took the slate from 5 dropped to 0, and three of the four conflicts
were load-bearing CODEGEN, not style: the array form of D_80078D88 blocks a sched1 hoist (scalar
users adopt [0] for free); "volatile" on D_800B9A02 is required by one draft and fatal to two
others (plain u16 loses 1 bank, volatile loses 2); D_800A651C needs block scope. Cookbook §176f.
Wave O: six drafts in one TU referenced D_80078D88, three declaring it scalar and three as an
array. One draft's own comment explained why the array form is load-bearing: with a scalar decl the
global load is a plain symbol_ref and sched1 HOISTS the lui/lw above a store; declaring it as an
array makes gcc-2.7.2 alias.c treat the access as possibly-aliasing and the hoist stops.
So §176b/§176d's 'pick one form and cast at the use site' is wrong for a scalar/array clash. Array
is the STRONGER form -- scalar users adopt it for free by indexing [0] (byte-verified on all three)
-- while forcing an array user to scalar can re-enable the hoist and break the match. Reconciling
toward the array form took the slate from 42 -> 37 compatible (5 dropped) to 42/42, every converted
draft re-verified MATCH. Waves J/K/L each lost 5-10 drafts to the greedy keep-first rule.
R37 probe: 20 shape-verified AND symbol-verified stored drafts, 5 gate groups -> 1 banked (5%),
statistically the same as the project's A10 stored-verdict law (~0-8%; T1 measured 0/23 on the same
kind of pile earlier this phase). The null is the finding: a stored draft's rejection is almost
never symbol identity, it is TU plumbing (§176d) or staleness. reloc_identity's real home is a
PRE-GATE check on FRESH drafts, not a backlog resurrection tool. The remaining 30 groups are not
worth 30 rebuilds -- lane closed rather than pursued because the tooling was new and interesting.
Also names the SYMBOL-COLLAPSE class in the fixer's refusal (one draft extern standing in for N
distinct globals; a textual rename moves every occurrence together, so it needs one extern per
site) and records the R38 self-note: the 0/23 prior was already in the phase log.