Skip to content

Keep a block's outputs in the registers its --exit names until the exit. - #12

Open
glampert wants to merge 1 commit into
ps2dev:masterfrom
glampert:fix/exit-outputs
Open

glampert wants to merge 1 commit into
ps2dev:masterfrom
glampert:fix/exit-outputs

Conversation

@glampert

@glampert glampert commented Nov 2, 2026

Copy link
Copy Markdown

Follow-up to #11.

Problem

A code block's out_vf/out_vi outputs could leave in the wrong register, or be overwritten before the exit, in two ways:

  1. Paths through a taken branch ignored the outputs. The register allocator walks the program path by path; the path that takes a conditional branch is a copy of the state before it, and BranchState's copy constructor copied the inputs but not the outputs. A value assigned before the branch and left alone on the taken path never got its output register:
       move fOut, fA
       ibeq iFlag, vi00, lDone
       add  fOut, fA, fA
   lDone:
       --exit
       out_vf fOut(vf05)

compiled the move as move VF02, VF01, so the taken path left with VF05 never written.

  1. An output's live range ended at its last access, not at the exit. A temporary needed after that point could take the output's register before the program ended (add fOut, fA, fA then add fT, fA, fA / sq fT, 0(vi00) gave fT VF05, the register out_vf fOut(vf05) names). An input passed straight through to an output had no range at all.

Fix

  • Copy the outputs into the branch states.
  • In completeLiveRanges, the exit reads every output: each read joins the writes that reach it, is live from them to the end of the block, and takes the register its directive names. When the value it sees is an input in another register, that is reported: error: Register conflict: fA has to be in both VF01 and VF05.

Tests

test/integration/test_exit_outputs.cpp, 4 new cases:

  • an output assigned before a taken branch gets its register;
  • an output keeps its register until the exit (VF and VI);
  • an input passed through to an output keeps its register;
  • an output that wants an input's value in another register is an error.

Effect on generated code

None of the ps2gl fixtures, Tyra's 15 programs or quake2-ps2's programs use outputs: all compile byte-identically to master (a5e9dbe). The test suite and an ASan/UBSan build of the suite and those programs are clean.

The register allocator walks the program path by path. The path that takes
a conditional branch is a copy of the state before it, and the copy was
made without the block's outputs, so a value assigned before the branch
and left alone on that path never got its out_vf or out_vi register. And
an output's live range ended at its last access instead of at the exit,
so a temporary needed after that could take its register before the
program ended; an input passed through to an output untouched had no
range at all.

Copy the outputs into the branch states, and let the exit read the
outputs in the control-flow pass: each read joins the writes that reach
it, is live from them to the end of the block, and takes the register its
directive names, which reports a conflict when the value it sees is an
input in another register.

Tests: test/integration/test_exit_outputs.cpp (4 cases, all fail on
master).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.

1 participant