Skip to content

Pad the code after a label for what the branches to it leave in flight. - #10

Merged
fjtrujy merged 1 commit into
ps2dev:masterfrom
glampert:fix/cross-block-latency
Oct 26, 2026
Merged

fjtrujy merged 1 commit into
ps2dev:masterfrom
glampert:fix/cross-block-latency

Conversation

@glampert

Copy link
Copy Markdown

The emitter pads each basic block for the latencies still in flight from the code before it in program order. It pads for nothing that a branch into the block leaves in flight. Its padding for the code that falls into a label is also one cycle short.

Three of the resulting hazards are not interlocked, so they silently read old values:

  • Clip flags: an fcand after a join read the flags 3 cycles after a clipw on the other path; they land after 4.
  • MAC flags: an fmand after a join had the same problem.
  • Branch conditions: a VI loaded before a join was tested too early on one path.

VF registers and VI loads are interlocked. For them, the missing padding only turns a nop into a stall at the same cycle, so the timing doesn't change. The fix pads them too, because the emitter's tracker models them like everything else.

Fix

  • Each branch records what its target inherits: the latencies still pending when it is taken, measured from the target's first issue cycle, keeping the largest over all branches to that label. VuLatencyTracker::merge combines the records.
  • At each label the emitter merges the record into its own tracker, and pads the scheduled slots for anything the schedule did not see.
  • The program is re-emitted until the records stop growing. Padding only moves a branch further from the writes before it, so they converge. The schedule itself is computed once.

Effect

On a local corpus of 46 programs, against current master:

  • 16 programs change, all by added nop pairs: 11 of the 12 ps2gl fixtures, ps2gl's scei, and 4 small repros.
  • The ps2gl changes are 1 to 6 lines each, in the light and transform loops.
  • quake2-ps2 and Tyra are byte-identical.
  • vu-checker's latency findings on the clip and MAC join repros are fixed.

Tests

test/integration/test_cross_block_latency.cpp, 6 cases; the first 5 fail on master without this change:

  • the clip flags are read 4 cycles after the clipw on every path into a join;
  • the MAC flags are read 4 cycles after the operation on every path into a join;
  • a register loaded before a label or a branch is read 4 cycles later on every path;
  • a VI loaded before a join is tested 5 cycles later on every path;
  • a vertex colour written lane by lane before a join is stored after the lanes land;
  • a join with nothing in flight on either path gets no padding.

The scheduler pads each basic block for the results still in flight from
the code before it in the program, and for nothing that a branch into the
block leaves in flight: the clip flags from a clipw on the other side of
a join were read three cycles after it, a register loaded just before a
loop's label or in a branch's delay slot one to three cycles after the
load, and a VI loaded before a join was tested four cycles after. The
padding for the code that falls into a label runs one cycle short as
well, so the same reads came one cycle early on that path too: a vertex
colour's w lane moved in right before a join was stored a cycle before
the move landed, and the store got the lane's old value.

Each branch now records what its target inherits: the latencies still
pending when it is taken, relative to the cycle the target's first
instruction issues, as the most any branch to the label leaves. The
program is emitted again with those merged into the emitter's own
tracker at the labels, and the emitter pads the scheduled slots for
whatever the schedule did not see, until the records stop growing;
padding only moves a branch further from the writes before it, so they
do. The schedule itself is computed once.

Tests: test/integration/test_cross_block_latency.cpp (6 cases, 5 fail on
master). Corpus: 16 of the 47 programs that compile change, all by added
nop pairs: 11 of the 12 ps2gl fixtures, ps2gl's scei and 4 repros; the
rest, quake2 and Tyra among them, are byte-identical.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@fjtrujy fjtrujy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@fjtrujy
fjtrujy merged commit 17cbadb into ps2dev:master Oct 26, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants