phase10: maspsx fix — honour cc1's explicit #nop marker (developer-authorised)

cc1 emits an explicit `#nop` marker when it wants a load-delay nop. maspsx used to
RE-DERIVE the need and could overrule cc1 for a BARE-SYMBOL STORE consumer:

    uses_at('sw\t$2,D_801221C4')  ->  True   (macro store, expands via $at)
    uses_at('sw\t$2,0($4)')       ->  False  (register+offset, no macro)
    nop_at_expansion is False for ASPSX >= 2.30

so neither test in _handle_nop_before_next_instruction fired, nop_required stayed
False, and an instruction cc1 had explicitly asked for was dropped (0x80107C5C at
108 vs 112; worker B's 0x8003A9C8). The fix honours the marker instead of
overruling it; only that path changes.

Worker B2 found the gap but mis-diagnosed it: its proposed fix was to extend the
`line_loads_from_reg` predicate, which ALREADY returns True for a store source, so
that patch would have been a no-op. The coordinator traced the actual call and found
the real mechanism in the uses_at / nop_at_expansion interaction. B2 then appended a
CORRECTION row to its own staged report superseding its paragraph — the right
response, and it records the general lesson: a named mechanism is a hypothesis until
it is traced, even when the observation is solid and reproducible.

REGRESSION VERIFICATION (the whole point of gating this):
  make check  exit 0
  regions=489 disagreements=0 AGREE
  c_regions=489 differing_bytes=0 MATCH
  237 tests OK
All 489 previously-matched regions are byte-identical with the fix in place.

Carried as a tracked patch (tools/maspsx/ is git-ignored, so an in-place edit would
not survive a fresh clone); patch verified to reproduce both modified files exactly
from the pristine pinned checkout. docs/SETUP.md records the fix and its provenance.

0x80107C5C NOW MATCHES (112 B, 0 differing, verified against worker B2's variant
X3.c) — but that is a BARE variant with no header, and the project convention requires
a documented source stating the observed instructions and limits. So the row is
UNBLOCKED and one documented source away, not claimed. Recorded as a carry-forward.
This commit is contained in:
Christopher Williams
2026-09-24 08:18:37 -04:00
parent c1dbf9d64e
commit a7b842faf5
2 changed files with 26 additions and 2 deletions
+8
View File
@@ -88,6 +88,14 @@ patch, and diffing the result against the working tree (byte-identical). It adds
after a branch/jump so GNU `as` can fill the slot.
- `--nop-on-reg-read` (region token `maspsx=regread`) — extend the load-delay predicate (cookbook
finding 27) so a following `jr`/`jalr` that *uses* the loaded register also gets the delay `nop`.
- **A default-on fix (no region token): maspsx now honours cc1's explicit `#nop` marker.** cc1 emits the
marker when it wants a load-delay nop; maspsx used to re-derive the need and could overrule cc1 for a
**bare-symbol store** consumer (`sw $2,D_801221C4`), because `uses_at` is True for a macro store while
`nop_at_expansion` is False for ASPSX >= 2.30, so neither test fired and the nop was dropped
(`0x80107C5C`, `0x8003A9C8`). The fix honours the marker instead of overruling it. **Verified
regression-free: `make check` green at 489 regions / 237 tests with all 489 regions byte-identical.**
Worker B2 found the gap; the coordinator traced the mechanism after B2's first diagnosis (extend the
predicate) proved to be a no-op — the predicate already returned True.
Both default **off**, and `make check` is green at 439 regions with them off, so the matched corpus is
byte-identical. **Status: implemented, but neither mode has been shown to close a region.** See
+18 -2
View File
@@ -64,7 +64,7 @@
self.sltu_at = sltu_at
self.addiu_at = addiu_at
self.div_uses_tge = div_uses_tge
@@ -696,7 +739,14 @@
@@ -696,9 +739,30 @@
) -> List[str]:
res: List[str] = []
@@ -79,8 +79,24 @@
+ if reuse:
nop_required = False
+ # Phase 10 (developer-authorised): cc1 emits an explicit `#nop` marker
+ # when it wants a load-delay nop. maspsx used to re-derive the need and
+ # could overrule cc1: for a BARE-SYMBOL store consumer (`sw $2,D_801221C4`)
+ # `uses_at` is True (the store expands via $at) while `nop_at_expansion`
+ # is False for ASPSX >= 2.30, so neither test below fired and the nop was
+ # dropped -- losing an instruction cc1 had asked for (0x80107C5C,
+ # 0x8003A9C8). Honour the explicit marker instead of overruling it.
+ # Only the marker changes behaviour; every other path is untouched.
+ if self.get_next_instruction(
+ skip=0, ignore_nop=False, ignore_set=True, ignore_label=True
+ ) == "#nop":
+ reason = "cc1 emitted an explicit '#nop' marker"
+ nop_required = True
+
if not uses_at(next_instruction):
@@ -1102,7 +1152,7 @@
reason = f"'{next_instruction}' does not use $at"
nop_required = True
@@ -1102,7 +1166,7 @@
elif op in branch_mnemonics or op in jump_mnemonics:
res.append(line)