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) <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-14 18:23:00 +02:00
1 parent 74703b031e
commit e920666d72
1 file changed
+16 -2
+16 -2
View File
@@ -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);