From 8042beb85e1c96037be1dd1ef78a57b71fd1c2cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Trung=20L=C3=AA?= <8@tle.id.au> Date: Tue, 30 Jun 2026 01:22:45 +1000 Subject: [PATCH] ppc64-QVM: honor IEEE unordered semantics for float <= and >= OP_LEF/OP_GEF mapped to a single CR0 test (branch if !GT / !LT). PPC FCMPU sets only CR0[SO] when an operand is NaN, leaving LT/GT/EQ clear, so those conditions wrongly took the branch on unordered operands - diverging from the interpreter and aarch64 (where NaN <= / >= is false). Fold the unordered bit into the tested bit via cror before the branch (GT |= SO for <=, LT |= SO for >=) so a NaN operand does not branch. Ordered comparisons and the other four float compares are unaffected; the integer LE/GE paths are untouched. Verified with a differential harness (JIT vs interpreter): NaN <= / >= 1.0 returned 1 on the JIT before and 0 after, matching the interpreter. --- code/qcommon/vm_powerpc.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/code/qcommon/vm_powerpc.c b/code/qcommon/vm_powerpc.c index b6760bef2e..cbb9284783 100644 --- a/code/qcommon/vm_powerpc.c +++ b/code/qcommon/vm_powerpc.c @@ -580,6 +580,11 @@ static inline uint32_t _ppc_chk_ui16( int32_t v, const char *opname ) // bns target (not SO) #define PPC_BNS(off) PPC_BC(BO_FALSE, BI_SO, off) +// cror bt, ba, bb (CR[bt] = CR[ba] | CR[bb]) XL-form, xo=449 +#define PPC_CROR(bt, ba, bb) \ + ( (19u<<26) | (((unsigned)(bt)&0x1F)<<21) | (((unsigned)(ba)&0x1F)<<16) | \ + (((unsigned)(bb)&0x1F)<<11) | (449u<<1) ) + // -- Branch to LR/CTR (XL-form) -- // blr (branch to LR) #define PPC_BLR() PPC_XL(19, BO_ALWAYS, 0, 16, 0) @@ -2116,7 +2121,16 @@ qboolean VM_Compile( vm_t *vm, vmHeader_t *header ) sx[0] = load_sx_opstack( F0 | RCONST ); dec_opstack(); // F0 = *opstack; opstack -= 4 flush_nonvolatile(); emit( PPC_FCMPU( 0, sx[0], sx[1] ) ); - // emit_branchConditional( vm, ci, ci->op ); + // IEEE unordered (NaN) handling: FCMPU sets only CR0[SO] when an + // operand is NaN, leaving LT/GT/EQ clear. For <= / >= the single-bit + // condition from get_branch_cond would then wrongly take the branch, + // so fold the unordered bit into the tested bit: a NaN operand must + // NOT branch (matching the interpreter and aarch64). The other float + // compares (eq,ne,lt,gt) are already correct under NaN. + if ( ci->op == OP_LEF ) + emit( PPC_CROR( BI_GT, BI_GT, BI_SO ) ); // GT |= unordered + else if ( ci->op == OP_GEF ) + emit( PPC_CROR( BI_LT, BI_LT, BI_SO ) ); // LT |= unordered emit_branchConditionalShort( vm, ci ); unmask_sx( sx[1] ); unmask_sx( sx[0] );