diff --git a/ps2xRuntime/include/ps2_runtime_macros.h b/ps2xRuntime/include/ps2_runtime_macros.h index f7b3316e4..24ecd412f 100644 --- a/ps2xRuntime/include/ps2_runtime_macros.h +++ b/ps2xRuntime/include/ps2_runtime_macros.h @@ -623,7 +623,17 @@ inline __m128i ps2_u64_to_epi64_pair(uint64_t value) #define FPU_FLOOR_W_S(a) ((int32_t)floorf((float)(a))) #define FPU_CVT_S_W(a) ((float)(int32_t)(a)) #define FPU_CVT_S_L(a) ((float)(int64_t)(a)) -#define FPU_CVT_W_S(a) ((int32_t)nearbyintf((float)(a))) +// CVT.W.S: truncate toward zero regardless of the rounding mode. +// Clamp to either INT32_MIN/INT32_MAX on over/underflow. +static inline int32_t ps2_fpu_cvt_w_s(float a) +{ + if (a >= 2147483648.0f) + return INT32_MAX; + if (a <= -2147483648.0f) + return INT32_MIN; + return (int32_t)a; +} +#define FPU_CVT_W_S(a) (ps2_fpu_cvt_w_s((float)(a))) #define FPU_CVT_L_S(a) ((int64_t)(float)(a)) #define FPU_C_F_S(a, b) (0) #define FPU_C_UN_S(a, b) (isnan((float)(a)) || isnan((float)(b))) diff --git a/ps2xTest/CMakeLists.txt b/ps2xTest/CMakeLists.txt index 39dbc65ef..c97315425 100644 --- a/ps2xTest/CMakeLists.txt +++ b/ps2xTest/CMakeLists.txt @@ -45,6 +45,7 @@ target_include_directories(ps2_test_function_table PRIVATE add_library(ps2_test_lib STATIC src/code_generator_tests.cpp src/r5900_decoder_tests.cpp + src/ps2_fpu_macros_tests.cpp src/elf_analyzer_tests.cpp src/pad_input_tests.cpp src/ps2_runtime_io_tests.cpp diff --git a/ps2xTest/src/main.cpp b/ps2xTest/src/main.cpp index 40a68b16b..7fef9cb4f 100644 --- a/ps2xTest/src/main.cpp +++ b/ps2xTest/src/main.cpp @@ -18,6 +18,7 @@ void register_ps2_sif_rpc_tests(); void register_ps2_sif_dma_tests(); void register_ps2_recompiler_tests(); void register_ps2_runtime_expansion_tests(); +void register_ps2_fpu_macros_tests(); void reset_ps2_test_function_table(); int main() @@ -40,6 +41,7 @@ int main() register_ps2_sif_dma_tests(); register_ps2_recompiler_tests(); register_ps2_runtime_expansion_tests(); + register_ps2_fpu_macros_tests(); int res = MiniTest::Run(); std::cout.flush(); std::cerr.flush(); diff --git a/ps2xTest/src/ps2_fpu_macros_tests.cpp b/ps2xTest/src/ps2_fpu_macros_tests.cpp new file mode 100644 index 000000000..a73217b4e --- /dev/null +++ b/ps2xTest/src/ps2_fpu_macros_tests.cpp @@ -0,0 +1,41 @@ +#include "MiniTest.h" + +#include +#include +#include + +#include "ps2_runtime_macros.h" + +// R5900 CVT.W.S reference behavior (matches the PCSX2 interpreter's CVT_W): +// truncate toward zero, saturate to 0x7FFFFFFF / 0x80000000 on overflow. +// The EE FPU ignores the host/guest rounding mode for this conversion. + +void register_ps2_fpu_macros_tests() +{ + MiniTest::Case("FpuMacros", [](TestCase &tc) + { + tc.Run("CVT_W_S truncates toward zero", [](TestCase &t) { + t.Equals((uint32_t)FPU_CVT_W_S(2.7f), 2u, "2.7 must truncate to 2"); + t.Equals((uint32_t)FPU_CVT_W_S(3.5f), 3u, "3.5 must truncate to 3 (not round to 4)"); + t.Equals((uint32_t)FPU_CVT_W_S(0.99f), 0u, "0.99 must truncate to 0"); + t.Equals((uint32_t)FPU_CVT_W_S(-2.7f), 0xFFFFFFFEu, "-2.7 must truncate to -2"); + }); + + tc.Run("CVT_W_S ignores host rounding mode", [](TestCase &t) { + const int previous = std::fegetround(); + std::fesetround(FE_TONEAREST); + t.Equals((uint32_t)FPU_CVT_W_S(2.7f), 2u, "must truncate even under FE_TONEAREST"); + std::fesetround(FE_UPWARD); + t.Equals((uint32_t)FPU_CVT_W_S(2.1f), 2u, "must truncate even under FE_UPWARD"); + std::fesetround(previous); + }); + + tc.Run("CVT_W_S saturates on overflow", [](TestCase &t) { + t.Equals((uint32_t)FPU_CVT_W_S(3e9f), 0x7FFFFFFFu, "positive overflow -> 0x7FFFFFFF"); + t.Equals((uint32_t)FPU_CVT_W_S(-3e9f), 0x80000000u, "negative overflow -> 0x80000000"); + t.Equals((uint32_t)FPU_CVT_W_S(2147483648.0f), 0x7FFFFFFFu, "2^31 -> 0x7FFFFFFF"); + t.Equals((uint32_t)FPU_CVT_W_S(-2147483648.0f), 0x80000000u, "-2^31 -> 0x80000000"); + t.Equals((uint32_t)FPU_CVT_W_S(2147483520.0f), 0x7FFFFF80u, + "largest float below 2^31 converts exactly"); + }); }); +}