FPU: saturate float-to-int in the interpreters

The C cast is undefined past the int32 range, and x86 makes it INT_MIN, so
round/trunc/ceil/floor/cvt.w.s of anything from 2^31 up gave 0x80000000 on
x86 hosts while the PSP saturates to 0x7fffffff (cpu/fpu/roundmode). Route
all of them through SaturatedFloatToInt, which also covers NaN and inf, and
drop the special cases that did.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
Henrik RydgårdandClaude Fable 5.1 committed 2026-09-22 14:25:41 -06:00
1 parent dc983bb1c5
commit fc9dbf5ff5
3 files changed
+30 -83

No files matched your search

+13
View File
@@ -127,6 +127,19 @@ inline int is_even(float d) {
return 2.0f * int_part == d;
}
// Float to int the way the PSP's FPU does it: at or past the int32 range the result is the nearest
// limit, and a NaN gives INT_MAX whatever its sign (cpu/fpu/roundmode). The plain cast is undefined
// there, and x86 makes it INT_MIN. Takes a value that's already been rounded.
inline int32_t SaturatedFloatToInt(double d) {
if (d >= 2147483648.0)
return 0x7FFFFFFF;
if (d <= -2147483648.0)
return (int32_t)0x80000000;
if (d != d)
return 0x7FFFFFFF;
return (int32_t)d;
}
// Rounds *.5 to closest even number
inline double round_ieee_754(double d) {
float i = (float)floor(d);
+9 -54
View File
@@ -928,58 +928,17 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) {
mips->r[inst->dest] = mips->vfpuCtrl[inst->src1];
break;
case IROp::FRound:
{
float value = mips->f[inst->src1];
if (my_isnanorinf(value)) {
mips->fi[inst->dest] = my_isinf(value) && value < 0.0f ? -2147483648LL : 2147483647LL;
break;
} else {
mips->fs[inst->dest] = (int)round_ieee_754(value);
}
mips->fs[inst->dest] = SaturatedFloatToInt(round_ieee_754(mips->f[inst->src1]));
break;
}
case IROp::FTrunc:
{
float value = mips->f[inst->src1];
if (my_isnanorinf(value)) {
mips->fi[inst->dest] = my_isinf(value) && value < 0.0f ? -2147483648LL : 2147483647LL;
break;
} else {
if (value >= 0.0f) {
mips->fs[inst->dest] = (int)floorf(value);
// Overflow, but it was positive.
if (mips->fs[inst->dest] == -2147483648LL) {
mips->fs[inst->dest] = 2147483647LL;
}
} else {
// Overflow happens to be the right value anyway.
mips->fs[inst->dest] = (int)ceilf(value);
}
break;
}
}
mips->fs[inst->dest] = SaturatedFloatToInt(truncf(mips->f[inst->src1]));
break;
case IROp::FCeil:
{
float value = mips->f[inst->src1];
if (my_isnanorinf(value)) {
mips->fi[inst->dest] = my_isinf(value) && value < 0.0f ? -2147483648LL : 2147483647LL;
break;
} else {
mips->fs[inst->dest] = (int)ceilf(value);
}
mips->fs[inst->dest] = SaturatedFloatToInt(ceilf(mips->f[inst->src1]));
break;
}
case IROp::FFloor:
{
float value = mips->f[inst->src1];
if (my_isnanorinf(value)) {
mips->fi[inst->dest] = my_isinf(value) && value < 0.0f ? -2147483648LL : 2147483647LL;
break;
} else {
mips->fs[inst->dest] = (int)floorf(value);
}
mips->fs[inst->dest] = SaturatedFloatToInt(floorf(mips->f[inst->src1]));
break;
}
case IROp::FCmp:
switch (inst->dest) {
case IRFpCompareMode::False:
@@ -1019,16 +978,12 @@ u32 IRInterpret(MIPSState *mips, const IRInst *inst) {
case IROp::FCvtWS:
{
float src = mips->f[inst->src1];
if (my_isnanorinf(src)) {
mips->fs[inst->dest] = my_isinf(src) && src < 0.0f ? -2147483648LL : 2147483647LL;
break;
}
// TODO: Inline assembly to use here would be better.
switch (IRRoundMode(mips->fcr31 & 3)) {
case IRRoundMode::RINT_0: mips->fs[inst->dest] = (int)round_ieee_754(src); break;
case IRRoundMode::CAST_1: mips->fs[inst->dest] = (int)src; break;
case IRRoundMode::CEIL_2: mips->fs[inst->dest] = (int)ceilf(src); break;
case IRRoundMode::FLOOR_3: mips->fs[inst->dest] = (int)floorf(src); break;
case IRRoundMode::RINT_0: mips->fs[inst->dest] = SaturatedFloatToInt(round_ieee_754(src)); break;
case IRRoundMode::CAST_1: mips->fs[inst->dest] = SaturatedFloatToInt(truncf(src)); break;
case IRRoundMode::CEIL_2: mips->fs[inst->dest] = SaturatedFloatToInt(ceilf(src)); break;
case IRRoundMode::FLOOR_3: mips->fs[inst->dest] = SaturatedFloatToInt(floorf(src)); break;
}
break; //cvt.w.s
}
+8 -29
View File
@@ -1130,46 +1130,25 @@ namespace MIPSInt {
case 13:
case 14:
case 15:
if (my_isnanorinf(F(fs)))
{
FsI(fd) = my_isinf(F(fs)) && F(fs) < 0.0f ? -2147483648LL : 2147483647LL;
break;
}
switch (op & 0x3f)
{
// round.w.s is round-half-to-even, not half-away-from-zero - and its mode is fixed,
// so unlike cvt.w.s below it must not follow fcr31. round_ieee_754 is both.
case 12: FsI(fd) = (int)round_ieee_754(F(fs)); break; //round.w.s
case 13: //trunc.w.s
if (F(fs) >= 0.0f) {
FsI(fd) = (int)floorf(F(fs));
// Overflow, but it was positive.
if (FsI(fd) == -2147483648LL) {
FsI(fd) = 2147483647LL;
}
} else {
// Overflow happens to be the right value anyway.
FsI(fd) = (int)ceilf(F(fs));
}
break;
case 14: FsI(fd) = (int)ceilf (F(fs)); break; //ceil.w.s
case 15: FsI(fd) = (int)floorf(F(fs)); break; //floor.w.s
case 12: FsI(fd) = SaturatedFloatToInt(round_ieee_754(F(fs))); break; //round.w.s
case 13: FsI(fd) = SaturatedFloatToInt(truncf(F(fs))); break; //trunc.w.s
case 14: FsI(fd) = SaturatedFloatToInt(ceilf(F(fs))); break; //ceil.w.s
case 15: FsI(fd) = SaturatedFloatToInt(floorf(F(fs))); break; //floor.w.s
}
break;
case 32: F(fd) = (float)FsI(fs); break; //cvt.s.w
case 36:
if (my_isnanorinf(F(fs)))
{
FsI(fd) = my_isinf(F(fs)) && F(fs) < 0.0f ? -2147483648LL : 2147483647LL;
break;
}
switch (mips->fcr31 & 3)
{
case 0: FsI(fd) = (int)round_ieee_754(F(fs)); break; // RINT_0
case 1: FsI(fd) = (int)F(fs); break; // CAST_1
case 2: FsI(fd) = (int)ceilf(F(fs)); break; // CEIL_2
case 3: FsI(fd) = (int)floorf(F(fs)); break; // FLOOR_3
case 0: FsI(fd) = SaturatedFloatToInt(round_ieee_754(F(fs))); break; // RINT_0
case 1: FsI(fd) = SaturatedFloatToInt(truncf(F(fs))); break; // CAST_1
case 2: FsI(fd) = SaturatedFloatToInt(ceilf(F(fs))); break; // CEIL_2
case 3: FsI(fd) = SaturatedFloatToInt(floorf(F(fs))); break; // FLOOR_3
}
break; //cvt.w.s
default: