diff --git a/core/libs/prx/libc/CMakeLists.txt b/core/libs/prx/libc/CMakeLists.txt index 30aaf71ba88..a562f55e234 100644 --- a/core/libs/prx/libc/CMakeLists.txt +++ b/core/libs/prx/libc/CMakeLists.txt @@ -46,6 +46,15 @@ if(WIN32) endif() if(BUILD_TESTING) + add_test_executable(guest_math_exports_tests ${LIBS_INCLUDE_DIR}/tests/GuestMathExports.cpp) + target_include_directories(guest_math_exports_tests PRIVATE ${LIBS_INCLUDE_DIR}) + target_link_libraries(guest_math_exports_tests PRIVATE libc) + configure_windows_unwind(guest_math_exports_tests) + add_test(NAME guest_math_exports COMMAND guest_math_exports_tests) + if(WIN32) + set_tests_properties(guest_math_exports PROPERTIES ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_test_executable(guest_time_conversion_tests ${LIBS_INCLUDE_DIR}/tests/GuestTimeConversion.cpp) target_include_directories(guest_time_conversion_tests PRIVATE ${LIBS_INCLUDE_DIR}) target_link_libraries(guest_time_conversion_tests PRIVATE libc) diff --git a/core/libs/prx/libc/src/Math.cpp b/core/libs/prx/libc/src/Math.cpp index 1ae4fb350c7..870a521b574 100644 --- a/core/libs/prx/libc/src/Math.cpp +++ b/core/libs/prx/libc/src/Math.cpp @@ -15,6 +15,9 @@ std::lldiv_t APS5_VABI lldiv_nid_postfix(long long numerator, long long denomina } float APS5_VABI fmodf_nid_postfix(float x, float y) { return std::fmod(x, y); } +float APS5_VABI nextafterf_nid_postfix(float x, float y) { return std::nextafter(x, y); } +double APS5_VABI nextafter_nid_postfix(double x, double y) { return std::nextafter(x, y); } +float APS5_VABI tgammaf_nid_postfix(float x) { return std::tgamma(x); } float APS5_VABI asinf_nid_postfix(float x) { return std::asin(x); } float APS5_VABI acosf_nid_postfix(float x) { return std::acos(x); } float APS5_VABI atan2f_nid_postfix(float y, float x) { return std::atan2(y, x); } diff --git a/core/libs/tests/GuestMathExports.cpp b/core/libs/tests/GuestMathExports.cpp new file mode 100644 index 00000000000..4871df92a57 --- /dev/null +++ b/core/libs/tests/GuestMathExports.cpp @@ -0,0 +1,80 @@ +#include "prx/libc/include/general/VabiMacros.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +float APS5_VABI nextafterf_nid_postfix(float, float); +double APS5_VABI nextafter_nid_postfix(double, double); +float APS5_VABI tgammaf_nid_postfix(float); +int* APS5_VABI __error_nid_postfix(); +} + +static void Require(bool value, const char* message) { + if (!value) { + std::fprintf(stderr, "%s\n", message); + std::abort(); + } +} + +template +static void CheckNextafter(TFloat (APS5_VABI *next)(TFloat, TFloat)) { + const auto infinity = std::numeric_limits::infinity(); + const auto nan = std::numeric_limits::quiet_NaN(); + const auto one = std::bit_cast(TFloat{1}); + Require(std::bit_cast(next(TFloat{1}, TFloat{2})) == one + 1, "nextafter: upward neighbour"); + Require(std::bit_cast(next(TFloat{1}, TFloat{0})) == one - 1, "nextafter: downward neighbour"); + Require(next(TFloat{1}, TFloat{1}) == TFloat{1}, "nextafter: equal arguments"); + Require(std::signbit(next(TFloat{0}, -TFloat{0})), "nextafter: negative target zero"); + Require(!std::signbit(next(-TFloat{0}, TFloat{0})), "nextafter: positive target zero"); + Require(next(TFloat{0}, TFloat{1}) == std::numeric_limits::denorm_min(), "nextafter: positive subnormal"); + Require(next(TFloat{0}, -TFloat{1}) == -std::numeric_limits::denorm_min(), "nextafter: negative subnormal"); + Require(next(infinity, TFloat{0}) == std::numeric_limits::max(), "nextafter: infinity to finite"); + Require(std::isnan(next(nan, TFloat{1})) && std::isnan(next(TFloat{1}, nan)), "nextafter: NaN propagation"); + *__error_nid_postfix() = 13; + Require(next(TFloat{1}, TFloat{2}) > TFloat{1} && *__error_nid_postfix() == 13, "nextafter: preserve errno on success"); + std::feclearexcept(FE_ALL_EXCEPT); + *__error_nid_postfix() = 0; + Require(std::isinf(next(std::numeric_limits::max(), infinity)), "nextafter: overflow value"); + if (math_errhandling & MATH_ERREXCEPT) + Require(std::fetestexcept(FE_OVERFLOW) != 0, "nextafter: overflow exception"); + if (math_errhandling & MATH_ERRNO) + Require(*__error_nid_postfix() == 34, "nextafter: overflow errno"); + std::feclearexcept(FE_ALL_EXCEPT); + *__error_nid_postfix() = 0; + Require(next(std::numeric_limits::denorm_min(), TFloat{0}) == TFloat{0}, "nextafter: underflow value"); + if (math_errhandling & MATH_ERREXCEPT) + Require(std::fetestexcept(FE_UNDERFLOW) != 0, "nextafter: underflow exception"); + if (math_errhandling & MATH_ERRNO) + Require(*__error_nid_postfix() == 34, "nextafter: underflow errno"); +} + +int main() { + CheckNextafter(nextafterf_nid_postfix); + CheckNextafter(nextafter_nid_postfix); + Require(tgammaf_nid_postfix(1.f) == 1.f && tgammaf_nid_postfix(5.f) == 24.f, "tgammaf: integer values"); + Require(std::abs(tgammaf_nid_postfix(0.5f) - 1.77245385f) < 0.000001f, "tgammaf: half integer"); + Require(std::abs(tgammaf_nid_postfix(-0.5f) + 3.5449077f) < 0.000002f, "tgammaf: negative half integer"); + Require(std::isnan(tgammaf_nid_postfix(std::numeric_limits::quiet_NaN())), "tgammaf: NaN"); + Require(tgammaf_nid_postfix(std::numeric_limits::infinity()) == std::numeric_limits::infinity(), "tgammaf: positive infinity"); + for (const float input : {0.f, -0.f, -1.f, 36.f}) { + std::feclearexcept(FE_ALL_EXCEPT); + *__error_nid_postfix() = 0; + const float result = tgammaf_nid_postfix(input); + if (input == -1.f) { + Require(std::isnan(result), "tgammaf: negative integer domain"); + if (math_errhandling & MATH_ERRNO) Require(*__error_nid_postfix() == 33, "tgammaf: domain errno"); + if (math_errhandling & MATH_ERREXCEPT) Require(std::fetestexcept(FE_INVALID) != 0, "tgammaf: domain exception"); + } else { + Require(std::isinf(result) && std::signbit(result) == std::signbit(input), "tgammaf: pole or overflow value"); + if (math_errhandling & MATH_ERRNO) Require(*__error_nid_postfix() == 34, "tgammaf: range errno"); + if (math_errhandling & MATH_ERREXCEPT) Require(std::fetestexcept(input == 0.f ? FE_DIVBYZERO : FE_OVERFLOW) != 0, "tgammaf: pole or overflow exception"); + } + } +}