From e920666d7206963ca2d16600f3776e514513f88f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Wed, 14 Oct 2026 18:23:00 +0200 Subject: [PATCH] ARM32 IR JIT: Pass float helper arguments the softfp way where needed Android's armeabi-v7a uses the softfp calling convention, where floats travel in R0/R1, not S0/S1. The math helper calls (vfpu_sin and the rest) only did the hard float version, so on Android they got garbage and returned it (broken 3D in Virtua Tennis: World Tour). Like the old ArmJit, check __ARM_PCS_VFP. With an armel softfp build under qemu, the fuzzer found 41 of 3000 cases where the JIT differed from the IR interpreter before this, and none after. Co-Authored-By: Claude Opus 5.5 (1M context) --- Core/MIPS/ARM/ArmIRCompFPU.cpp | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/Core/MIPS/ARM/ArmIRCompFPU.cpp b/Core/MIPS/ARM/ArmIRCompFPU.cpp index 271b5db8e0..4841f5485a 100644 --- a/Core/MIPS/ARM/ArmIRCompFPU.cpp +++ b/Core/MIPS/ARM/ArmIRCompFPU.cpp @@ -492,7 +492,8 @@ void ArmJitBackend::CompIR_FSat(IRInst inst) { void ArmJitBackend::CompIR_FSpecial(IRInst inst) { CONDITIONAL_DISABLE; - // The argument goes in S0 and the result comes back there (hard float ABI.) + // The argument goes in S0 and the result comes back there with the hard float ABI. With + // softfp (Android's armeabi-v7a), floats are passed and returned in R0 (and R1.) auto loadArg = [&]() { regs_.FlushBeforeCall(); WriteDebugProfilerStatus(IRProfilerStatus::MATH_HELPER); @@ -502,10 +503,22 @@ void ArmJitBackend::CompIR_FSpecial(IRInst inst) { } else { VLDR(S0, CTXREG, offsetof(MIPSState, f) + inst.src1 * 4); } +#if !defined(__ARM_PCS_VFP) + VMOV(R0, S0); +#endif + }; + // Before anything else can use R0 and R1. + auto resultToS0 = [&](bool twoFloats) { +#if !defined(__ARM_PCS_VFP) + VMOV(S0, R0); + if (twoFloats) + VMOV(S1, R1); +#endif }; auto callFuncF_F = [&](float (*func)(float)) { loadArg(); QuickCallFunction(R12, func); + resultToS0(false); regs_.MapFPR(inst.dest, MIPSMap::NOINIT); VMOV(regs_.F(inst.dest), S0); WriteDebugProfilerStatus(IRProfilerStatus::IN_JIT); @@ -549,9 +562,10 @@ void ArmJitBackend::CompIR_FSpecial(IRInst inst) { break; case IROp::FSinCos: - // The helper returns the sine and cosine packed into D0, which is S0 and S1. + // The helper returns the sine and cosine packed into a double, which is S0 and S1. loadArg(); QuickCallFunction(R12, &vfpu_sincos_packed); + resultToS0(true); regs_.Map(inst); VMOV(regs_.F(inst.dest), S0); VMOV(regs_.F(inst.dest + 1), S1);