Skip to content

codegen: PS2-accurate divide-by-zero semantics for COP1 and VU0 FP + RSQRT operand fix#113

Closed
jlsandri wants to merge 1 commit intoran-j:mainfrom
jlsandri:pr/codegen-cop1-vu-divbyzero
Closed

codegen: PS2-accurate divide-by-zero semantics for COP1 and VU0 FP + RSQRT operand fix#113
jlsandri wants to merge 1 commit intoran-j:mainfrom
jlsandri:pr/codegen-cop1-vu-divbyzero

Conversation

@jlsandri
Copy link
Copy Markdown

@jlsandri jlsandri commented Apr 6, 2026

Problem

Five related floating-point codegen corrections for EE COP1 and VU0. All five are mis-emulations of documented PS2 hardware semantics.

1. COP1_S_RSQRT was using the wrong source operand

The previous emitter produced

ctx->f[fd] = 1.0f / sqrtf(ctx->f[fs]);

which is the formula for reciprocal-sqrt of fs. The actual PS2 rsqrt.s operation is

fd = fs / sqrt(ft)

The emitter ignored ft entirely and used fs as the radicand. Fixed to use ft as the radicand and fs as the numerator. (This operand fix is also a prerequisite for the div-by-zero handling below, which rewrites the same line.)

2. COP1_S_DIV divide-by-zero

The PS2 EE does not produce IEEE infinity on divide-by-zero. It returns 0x7F7FFFFF (the largest finite float) with the sign bit set to sign(fs) XOR sign(ft). Previously the codegen emitted copysignf(INFINITY, fs * 0.0f) which produces IEEE ±∞.

3. COP1_S_RSQRT divide-by-zero

Same 0x7F7FFFFF + sign semantics, with sign taken from fs. Also guards the sqrt input with std::max(0.0f, ft) to match the EE behaviour of ignoring the sign bit on the radicand instead of producing a NaN.

4. VU0 vdiv divide-by-zero

Same 0x7F7FFFFF + sign semantics, writing to vu0_q. Previously returned 0.0f on div-by-zero, which is visibly wrong for game code that relies on the max-float fallback.

5. VU0 vrsqrt divide-by-zero

Same 0x7F7FFFFF semantics (unsigned, per the VU0 ISA). Previously returned 0.0f.

Implementation

All five use std::bit_cast<float>(uint32_t) for the bit-level construction of the max-float sentinel, and each site has an inline comment documenting the 0x7F7FFFFF magic constant so it is self-explanatory.

Scope

  • One file: ps2xRecomp/src/lib/code_generator.cpp
  • +10 / -5

Rationale

All five changes bring the generated code into line with documented PS2 floating-point semantics. The div-by-zero behaviour in particular is well-known to differ from IEEE and is frequently relied upon by EE code that intentionally divides by zero as a cheap max-float construction.

… plus RSQRT operand fix

Five related floating-point codegen corrections for EE COP1 and VU0:

1. COP1_S_RSQRT was emitting `fd = 1.0f / sqrtf(fs)`, which is the
   formula for reciprocal-sqrt of fs. The actual PS2 rsqrt.s operation
   is `fd = fs / sqrt(ft)` — the old codegen ignored ft entirely and
   used the wrong source operand. This operand fix is a prerequisite
   for the div-by-zero handling below.

2. COP1_S_DIV on the PS2 EE does not produce IEEE infinity on divide
   by zero. It returns 0x7F7FFFFF (max finite float) with the sign
   bit set to (sign(fs) XOR sign(ft)). Replace the prior
   `copysignf(INFINITY, ...)` path with the correct max-float form.

3. COP1_S_RSQRT div-by-zero: returns 0x7F7FFFFF with sign preserved
   from fs. Also guards the sqrt input with std::max(0.0f, ft) to
   avoid producing NaN for negative operands (matches EE behaviour
   of ignoring the sign bit when reading the radicand).

4. VU0 vdiv div-by-zero: same 0x7F7FFFFF + sign semantics, writing
   to vu0_q.

5. VU0 vrsqrt div-by-zero: same 0x7F7FFFFF semantics (unsigned).

All five use std::bit_cast<float>(uint32_t) for the bit-level
construction. Commented in-line so the magic constant 0x7F7FFFFF
is self-documenting.
@jlsandri jlsandri force-pushed the pr/codegen-cop1-vu-divbyzero branch from 732f316 to 51b3b95 Compare April 6, 2026 08:56
@jlsandri
Copy link
Copy Markdown
Author

jlsandri commented Apr 6, 2026

Closing as part of a batch cleanup after #107 landed. The runtime ecosystem refactor in #107 substantially reworked the files this PR touched, and I would like to re-audit the underlying fix against the new code structure before putting it back in front of you. If the fix is still needed after that re-audit, I will re-open as a focused PR rebased onto current main. Thanks for your patience.

@jlsandri jlsandri closed this Apr 6, 2026
@jlsandri jlsandri deleted the pr/codegen-cop1-vu-divbyzero branch April 6, 2026 09:17
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