diff --git a/README.md b/README.md index da13e2b3..c4dd368d 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ Your GPU needs to support Vulkan 1.1+, because the RDP is implemented via [ParaL This list will probably grow with time! ## Special thanks: - +- [ares](https://github.com/ares-emulator/ares) for being the cleanest and most accurate Nintendo 64 emulator out there. It served as a reference time and time again. Especially regarding FPU accuracy. - [Dillonb](https://github.com/Dillonb) and [KieronJ](https://github.com/KieronJ) for bearing with me and my recurring brainfarts, and for the support :heart: - [WhoBrokeTheBuild](https://github.com/WhoBrokeTheBuild) for the shader that allows letterboxing :rocket: - [Kelpsy](https://github.com/kelpsyberry), [fleroviux](https://github.com/fleroviux), [Kim-Dewelski](https://github.com/Kim-Dewelski), [Peach](https://github.com/wheremyfoodat/), diff --git a/src/backend/core/registers/Cop1.hpp b/src/backend/core/registers/Cop1.hpp index 57e5c3a5..e9d90ab5 100644 --- a/src/backend/core/registers/Cop1.hpp +++ b/src/backend/core/registers/Cop1.hpp @@ -89,35 +89,45 @@ struct JIT; struct Registers; struct Cop1 { -#define CheckFPUUsable_PreserveCause() do { if(!regs.cop0.status.cu1) { regs.cop0.FireException(ExceptionCode::CoprocessorUnusable, 1, regs.oldPC); return; } } while(0) -#define CheckFPUUsable() do { CheckFPUUsable_PreserveCause(); fcr31.cause = 0; } while(0) - Cop1(Registers&); + explicit Cop1(Registers&); u32 fcr0{}; FCR31 fcr31{}; FloatingPointReg fgr[32]{}; + void Reset(); template // either JIT or Interpreter void decode(T&, u32); friend struct Interpreter; + bool CheckFPUException(); bool FireException(); + template + bool CheckFPUUsable(); + template + bool CheckResult(T&); + template + bool CheckArg(T&); + template + bool CheckArgs(T&, T&); + + template + bool XORDERED(T fs, T ft); template - void SetCauseOnResult(T& d); + bool CheckCVTArg(float &f); + template + bool CheckCVTArg(double &f); + template void SetCauseByArg(T f); - template - void SetCauseByArgLCVT(T f); - template - void SetCauseByArgWCVT(T f); - void SetCauseRaised(int); - void SetCauseRaisedCVT(int); - void SetCauseUnimplemented(); - void SetCauseUnderflow(); - void SetCauseInexact(); - void SetCauseDivisionByZero(); - void SetCauseOverflow(); - void SetCauseInvalid(); + template + bool TestExceptions(); + bool SetCauseUnimplemented(); + bool SetCauseUnderflow(); + bool SetCauseInexact(); + bool SetCauseDivisionByZero(); + bool SetCauseOverflow(); + bool SetCauseInvalid(); private: template auto FGR_T(Cop0Status&, u32) -> T&; @@ -137,7 +147,7 @@ private: void ceilws(u32 instr); void ceilld(u32 instr); void ceilwd(u32 instr); - void cfc1(u32 instr) const; + void cfc1(u32 instr); void ctc1(u32 instr); void unimplemented(); void roundls(u32 instr); diff --git a/src/backend/core/registers/cop/cop1instructions.cpp b/src/backend/core/registers/cop/cop1instructions.cpp index f2f5600d..2313d063 100644 --- a/src/backend/core/registers/cop/cop1instructions.cpp +++ b/src/backend/core/registers/cop/cop1instructions.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include #include @@ -20,7 +19,7 @@ template<> auto Cop1::FGR_T(Cop0Status& status, u32 index) -> u32& { return (u32&)FGR_T(status, index); } -template<> auto Cop1::FGR_T(Cop0Status& status, u32 index) -> float& { +template<> auto Cop1::FGR_T(Cop0Status&, u32 index) -> float& { return fgr[index].float32; } @@ -36,7 +35,7 @@ template<> auto Cop1::FGR_T(Cop0Status& status, u32 index) -> u64& { return (u64&)FGR_T(status, index); } -template<> auto Cop1::FGR_T(Cop0Status& status, u32 index) -> double& { +template<> auto Cop1::FGR_T(Cop0Status&, u32 index) -> double& { return fgr[index].float64; } @@ -76,7 +75,7 @@ template<> auto Cop1::FGR_S(Cop0Status& status, u32 index) -> double& { } } -template<> auto Cop1::FGR_D(Cop0Status& status, u32 index) -> s32& { +template<> auto Cop1::FGR_D(Cop0Status&, u32 index) -> s32& { fgr[index].int32h = 0; return fgr[index].int32; } @@ -85,12 +84,12 @@ template<> auto Cop1::FGR_D(Cop0Status& status, u32 index) -> u32& { return (u32&)FGR_D(status, index); } -template<> auto Cop1::FGR_D(Cop0Status& status, u32 index) -> float& { +template<> auto Cop1::FGR_D(Cop0Status&, u32 index) -> float& { fgr[index].float32h = 0; return fgr[index].float32; } -template<> auto Cop1::FGR_D(Cop0Status& status, u32 index) -> s64& { +template<> auto Cop1::FGR_D(Cop0Status&, u32 index) -> s64& { return fgr[index].int64; } @@ -102,9 +101,159 @@ template<> auto Cop1::FGR_D(Cop0Status& status, u32 index) -> double& { return FGR_T(status, index); } + +template bool isqnan(T); + +u32 floatIntegerCast(float f) { + u32 v; + memcpy(&v, &f, 4); + return v; +} + +u64 doubleIntegerCast(double f) { + u64 v; + memcpy(&v, &f, 8); + return v; +} + +template <> bool isqnan(float f) { + return (floatIntegerCast(f) >> 22) & 1; +} + +template <> bool isqnan(double f) { + return (doubleIntegerCast(f) >> 51) & 1; +} + +template <> bool Cop1::CheckCVTArg(float& f) { + switch (fpclassify(f)) { + case FP_SUBNORMAL: case FP_INFINITE: case FP_NAN: + SetCauseUnimplemented(); + if(FireException()) return false; + break; + } + + if((f >= 0x1p+31f || f < -0x1p+31f) && SetCauseUnimplemented()) { + FireException(); + return false; + } + + return true; +} + +template <> bool Cop1::CheckCVTArg(double& f) { + switch (fpclassify(f)) { + case FP_SUBNORMAL: case FP_INFINITE: case FP_NAN: + if(SetCauseUnimplemented()) { + FireException(); + return false; + } + } + + if((f >= 0x1p+31 || f < -0x1p+31) && SetCauseUnimplemented()) { + FireException(); + return false; + } + + return true; +} + +template <> bool Cop1::CheckCVTArg(float& f) { + switch (fpclassify(f)) { + case FP_SUBNORMAL: case FP_INFINITE: case FP_NAN: + SetCauseUnimplemented(); + if(FireException()) return false; + break; + } + + if((f >= 0x1p+53f || f < -0x1p+53f) && SetCauseUnimplemented()) { + FireException(); + return false; + } + + return true; +} + +template <> bool Cop1::CheckCVTArg(double& f) { + switch (fpclassify(f)) { + case FP_SUBNORMAL: case FP_INFINITE: case FP_NAN: + if(SetCauseUnimplemented()) { + FireException(); + return false; + } + } + + if((f >= 0x1p+53 || f < -0x1p+53) && SetCauseUnimplemented()) { + FireException(); + return false; + } + + return true; +} + +template +bool Cop1::CheckArg(T& f) { + switch(fpclassify(f)) { + case FP_SUBNORMAL: + if(SetCauseUnimplemented()) { + FireException(); + return false; + } + case FP_NAN: + if(isqnan(f) ? SetCauseInvalid() : SetCauseUnimplemented()) { + FireException(); + return false; + } + } + return true; +} + +template +bool Cop1::CheckArgs(T& f1, T& f2) { + auto class1 = fpclassify(f1), class2 = fpclassify(f2); + if((class1 == FP_NAN && !isqnan(f1)) || (class2 == FP_NAN && !isqnan(f2))) { + if(SetCauseUnimplemented()) { + regs.cop0.FireException(ExceptionCode::FloatingPointError, 0, regs.oldPC); + return false; + } + } + + if(class1 == FP_SUBNORMAL || class2 == FP_SUBNORMAL) { + if(SetCauseUnimplemented()) { + regs.cop0.FireException(ExceptionCode::FloatingPointError, 0, regs.oldPC); + return false; + } + } + + if((class1 == FP_NAN && isqnan(f1)) || (class2 == FP_NAN && isqnan(f2))) { + if(SetCauseInvalid()) { + regs.cop0.FireException(ExceptionCode::FloatingPointError, 0, regs.oldPC); + return false; + } + } + + return true; +} + +template +bool Cop1::CheckFPUUsable() { + if constexpr (preserveCause) { + if(!regs.cop0.status.cu1) { + regs.cop0.FireException(ExceptionCode::CoprocessorUnusable, 1, regs.oldPC); + return false; + } + } else { + if(!CheckFPUUsable()) return false; + fcr31.cause = 0; + } + + return true; +} + +template bool Cop1::CheckFPUUsable(); +template bool Cop1::CheckFPUUsable(); + bool Cop1::FireException() { - u32 enable = fcr31.enable | (1 << 5); - if(fcr31.cause & enable) { + if(CheckFPUException()) { regs.cop0.FireException(ExceptionCode::FloatingPointError, 0, regs.oldPC); return true; } @@ -112,184 +261,147 @@ bool Cop1::FireException() { return false; } -#define CheckFPUException() do { if(FireException()) { return; } } while(0) - -FORCE_INLINE int PushRoundingMode(const FCR31& fcr31) { - int og = fegetround(); - switch (fcr31.rounding_mode) { - case 0: fesetround(FE_TONEAREST); break; - case 1: fesetround(FE_TOWARDZERO); break; - case 2: fesetround(FE_UPWARD); break; - case 3: fesetround(FE_DOWNWARD); break; +bool Cop1::CheckFPUException() { + u32 enable = fcr31.enable | (1 << 5); + if(fcr31.cause & enable) { + return true; } - return og; -} - -void Cop1::SetCauseUnimplemented() { - fcr31.cause_unimplemented_operation = true; -} - -void Cop1::SetCauseUnderflow() { - fcr31.cause_underflow = true; - if(!fcr31.enable_underflow) { - fcr31.flag_underflow = true; - } -} - -void Cop1::SetCauseInexact() { - fcr31.cause_inexact_operation = true; - if(!fcr31.enable_inexact_operation) { - fcr31.flag_inexact_operation = true; - } -} - -void Cop1::SetCauseDivisionByZero() { - fcr31.cause_division_by_zero = true; - if(!fcr31.enable_division_by_zero) { - fcr31.flag_division_by_zero = true; - } -} - -void Cop1::SetCauseOverflow() { - fcr31.cause_overflow = true; - if(!fcr31.enable_overflow) { - fcr31.flag_overflow = true; - } -} - -void Cop1::SetCauseInvalid() { - fcr31.cause_invalid_operation = true; - if(!fcr31.enable_invalid_operation) { - fcr31.flag_invalid_operation = true; - } -} - -#define PUSHROUNDING int orig_round = PushRoundingMode(fcr31) -#define POPROUNDING fesetround(orig_round) -#define OP_CheckExcept(op) do { feclearexcept(FE_ALL_EXCEPT); PUSHROUNDING; op; SetCauseRaised(fetestexcept(FE_ALL_EXCEPT)); POPROUNDING; } while(0) -#define CVT_OP_CheckExcept(op) do { feclearexcept(FE_ALL_EXCEPT); op; SetCauseRaisedCVT(fetestexcept(FE_ALL_EXCEPT)); CheckFPUException(); } while(0) - -#define OP(T, op) do { \ - CheckFPUUsable(); \ - auto fs = FGR_S(regs.cop0.status, FS(instr)); \ - auto ft = FGR_T(regs.cop0.status, FT(instr)); \ - CheckArg(fs); \ - CheckArg(ft); \ - T result; \ - OP_CheckExcept({result = (op);}); \ - CheckResult(result); \ - FGR_D(regs.cop0.status, FD(instr)) = result; \ -} while(0) - -template -void Cop1::SetCauseByArgWCVT(T f) { - switch (std::fpclassify(f)) { - case FP_NAN: - case FP_INFINITE: - case FP_SUBNORMAL: - SetCauseUnimplemented(); - //CheckFPUException(); - break; - - case FP_NORMAL: - // Check overflow - if (f >= 2147483648.0f || f < -2147483648.0f) { - SetCauseUnimplemented(); - //CheckFPUException(); - } - break; - - case FP_ZERO: - break; // Fine - } + return false; } template -void Cop1::SetCauseByArgLCVT(T f) { - switch (std::fpclassify(f)) { - case FP_NAN: - case FP_INFINITE: - case FP_SUBNORMAL: - SetCauseUnimplemented(); - //CheckFPUException(); - break; - - case FP_NORMAL: - // Check overflow - if (f >= 9007199254740992.000000 || f <= -9007199254740992.000000) { - SetCauseUnimplemented(); - //CheckFPUException(); - } - break; - - case FP_ZERO: - break; // Fine +FORCE_INLINE T FlushResult(T f, u32 round) { + switch (round) { + case 0: case 1: return std::copysign(T(), f); + case 2: return std::signbit(f) ? -T() : std::numeric_limits::min(); + case 3: return std::signbit(f) ? -std::numeric_limits::min() : T(); } } -#define CheckWCVTArg(f) do { SetCauseByArgWCVT(f); CheckFPUException(); } while(0) -#define CheckLCVTArg(f) do { SetCauseByArgLCVT(f); CheckFPUException(); } while(0) - -void Cop1::SetCauseRaised(int raised) { - if (raised == 0) { - return; - } - - if (raised & FE_UNDERFLOW) { - if (!fcr31.fs || fcr31.enable_underflow || fcr31.enable_inexact_operation) { - SetCauseUnimplemented(); - CheckFPUException(); - return; - } else { +template <> bool Cop1::CheckResult(float& f) { + switch (fpclassify(f)) { + case FP_SUBNORMAL: + if(!fcr31.fs || fcr31.enable_underflow || fcr31.enable_inexact_operation) { + if(SetCauseUnimplemented()) FireException(); + return false; + } SetCauseUnderflow(); + SetCauseInexact(); + f = FlushResult(f, fegetround()); + return true; + case FP_NAN: { + uint32_t v = 0x7fbf'ffff; + memcpy(&f, &v, 4); + return true; + } + } + return true; +} + +template <> bool Cop1::CheckResult(double& f) { + switch (fpclassify(f)) { + case FP_SUBNORMAL: + if(!fcr31.fs || fcr31.enable_underflow || fcr31.enable_inexact_operation) { + if(SetCauseUnimplemented()) FireException(); + return false; + } + SetCauseUnderflow(); + SetCauseInexact(); + f = FlushResult(f, fegetround()); + return true; + case FP_NAN: { + uint64_t v = 0x7ff7'ffff'ffff'ffff; + memcpy(&f, &v, 8); + return true; + } + } + return true; +} + +template +bool Cop1::TestExceptions() { + u32 exc = fetestexcept(FE_ALL_EXCEPT); + + if(!exc) return false; + + if constexpr (cvt) { + if(exc & FE_INVALID) { + if(SetCauseUnimplemented()) regs.cop0.FireException(ExceptionCode::FloatingPointError, 0, regs.oldPC); + return true; } } - if (raised & FE_INEXACT) { - SetCauseInexact(); + if(exc & FE_UNDERFLOW) { + if(!fcr31.fs || fcr31.enable_underflow || fcr31.enable_inexact_operation) { + if(SetCauseUnimplemented()) regs.cop0.FireException(ExceptionCode::FloatingPointError, 0, regs.oldPC); + return true; + } } - if (raised & FE_DIVBYZERO) { - SetCauseDivisionByZero(); - } - - if (raised & FE_OVERFLOW) { - SetCauseOverflow(); - } - - if (raised & FE_INVALID) { - SetCauseInvalid(); - } - - CheckFPUException(); + bool raise = false; + if(exc & FE_DIVBYZERO) raise |= SetCauseDivisionByZero(); + if(exc & FE_INEXACT) raise |= SetCauseInexact(); + if(exc & FE_UNDERFLOW) raise |= SetCauseUnderflow(); + if(exc & FE_OVERFLOW) raise |= SetCauseOverflow(); + if(exc & FE_INVALID) raise |= SetCauseInvalid(); + if(raise) regs.cop0.FireException(ExceptionCode::FloatingPointError, 0, regs.oldPC); + return raise; } -void Cop1::SetCauseRaisedCVT(int raised) { - if(raised & FE_INVALID) { - SetCauseUnimplemented(); - return; - } - - SetCauseRaised(raised); +bool Cop1::SetCauseUnimplemented() { + fcr31.cause_unimplemented_operation = true; + return true; } -#define F_TO_U32(f) (*((u32*)(&(f)))) -#define D_TO_U64(d) (*((u64*)(&(d)))) -#define U64_TO_D(d) (*((double*)(&(d)))) -#define U32_TO_F(f) (*((float*)(&(f)))) - -template -FORCE_INLINE bool isqnan(T f) { - if constexpr(std::is_same_v) { - u32 v = F_TO_U32(f); - return (v & 0x7FC00000) == 0x7FC00000; - } else if constexpr(std::is_same_v) { - u64 v = D_TO_U64(f); - return (v & 0x7FF8000000000000) == 0x7FF8000000000000; - } else { - Util::panic("Invalid float type in isqnan"); +bool Cop1::SetCauseUnderflow() { + fcr31.cause_underflow = true; + if(!fcr31.enable_underflow) { + fcr31.flag_underflow = true; + return false; } + + return true; +} + +bool Cop1::SetCauseInexact() { + fcr31.cause_inexact_operation = true; + if(!fcr31.enable_inexact_operation) { + fcr31.flag_inexact_operation = true; + return false; + } + + return true; +} + +bool Cop1::SetCauseDivisionByZero() { + fcr31.cause_division_by_zero = true; + if(!fcr31.enable_division_by_zero) { + fcr31.flag_division_by_zero = true; + return false; + } + + return true; +} + +bool Cop1::SetCauseOverflow() { + fcr31.cause_overflow = true; + if(!fcr31.enable_overflow) { + fcr31.flag_overflow = true; + return false; + } + + return true; +} + +bool Cop1::SetCauseInvalid() { + fcr31.cause_invalid_operation = true; + if(!fcr31.enable_invalid_operation) { + fcr31.flag_invalid_operation = true; + return false; + } + + return true; } template @@ -318,154 +430,87 @@ void Cop1::SetCauseByArg(T f) { } } -#define CheckArg(f) do { SetCauseByArg(f); CheckFPUException(); } while(0) +#define CHECK_FPE_IMPL(type, res, operation, convert) \ + feclearexcept(FE_ALL_EXCEPT); \ + volatile type v##res = [&]() __attribute__((noinline)) -> type { return (operation); }(); \ + if (TestExceptions()) return; \ + type res = v##res; -template -void Cop1::SetCauseOnResult(T& d) { - auto fp_class = std::fpclassify(d); - T magic, min; - if constexpr(std::is_same_v) { - u32 c = 0x7FBFFFFF; - magic = U32_TO_F(c); - min = std::numeric_limits::min(); - } else if constexpr(std::is_same_v) { - u64 c = 0x7FF7FFFFFFFFFFFF; - magic = U64_TO_D(c); - min = std::numeric_limits::min(); - } - switch (fp_class) { - case FP_NAN: - d = magic; // set result to sNAN - break; - case FP_SUBNORMAL: - if (!fcr31.fs || fcr31.enable_underflow || fcr31.enable_inexact_operation) { - SetCauseUnimplemented(); - //CheckFPUException(); - } else { - // Since the if statement checks for the corresponding enable bits, it's safe to turn these cause bits on here. - SetCauseUnderflow(); - SetCauseInexact(); - switch (fcr31.rounding_mode) { - case 0: - case 1: - d = std::copysign(0, d); - break; - case 2: - if (std::signbit(d)) { - d = -(T)0; - } else { - d = min; - } - break; - case 3: - if (std::signbit(d)) { - d = -min; - } else { - d = 0; - } - break; - } - } - break; - case FP_INFINITE: - case FP_ZERO: - case FP_NORMAL: - break; // No-op, these are fine. - default: - Util::panic("Unknown FP classification: {}", fp_class); - } -} - -#define CheckResult(f) do { SetCauseOnResult((f)); CheckFPUException(); } while(0) - -#define any_unordered(fs, ft) (std::isnan(fs) || std::isnan(ft)) -#define CheckRound(a, b) do { if ((a) != (b)) { SetCauseInexact(); } CheckFPUException(); } while(0) - -template -FORCE_INLINE bool is_nan(T f) { - if constexpr(std::is_same_v) { - u32 v = F_TO_U32(f); - return ((v & 0x7F800000) == 0x7F800000) && ((v & 0x7FFFFF) != 0); - } else if constexpr(std::is_same_v) { - u64 v = D_TO_U64(f); - return ((v & 0x7FF0000000000000) == 0x7FF0000000000000) && ((v & 0xFFFFFFFFFFFFF) != 0); - } else { - Util::panic("Invalid float type in is_nan"); - } -} - -#define checknanregs(fs, ft) do { \ - if(is_nan(fs) || is_nan(ft)) { \ - SetCauseInvalid(); \ - CheckFPUException(); \ - } \ -} while(0) - -#define checkqnanregs(fs, ft) do { \ - if(isqnan(fs) || isqnan(ft)) { \ - SetCauseInvalid(); \ - CheckFPUException(); \ - } \ -} while(0) +#define CHECK_FPE(type, res, operation) CHECK_FPE_IMPL(type, res, operation, false) +#define CHECK_FPE_CONV(type, res, operation) CHECK_FPE_IMPL(type, res, operation, true) void Cop1::absd(u32 instr) { - OP(double, std::fabs(fs)); + if(!CheckFPUUsable()) return; + auto fs = FGR_S(regs.cop0.status, FS(instr)); + if(!CheckArg(fs)) return; + auto fd = std::abs(fs); + if(!CheckResult(fd)) return; + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::abss(u32 instr) { - OP(float, std::fabs(fs)); + if(!CheckFPUUsable()) return; + auto fs = FGR_S(regs.cop0.status, FS(instr)); + if(!CheckArg(fs)) return; + auto fd = std::abs(fs); + if(!CheckResult(fd)) return; + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::adds(u32 instr) { - OP(float, fs + ft); + if(!CheckFPUUsable()) return; + auto fs = FGR_S(regs.cop0.status, FS(instr)); + auto ft = FGR_T(regs.cop0.status, FT(instr)); + if(!CheckArgs(fs, ft)) return; + CHECK_FPE(float, fd, fs + ft) + if(!CheckResult(fd)) return; + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::addd(u32 instr) { - OP(double, fs + ft); + if(!CheckFPUUsable()) return; + auto fs = FGR_S(regs.cop0.status, FS(instr)); + auto ft = FGR_T(regs.cop0.status, FT(instr)); + if(!CheckArgs(fs, ft)) return; + CHECK_FPE(double, fd, fs + ft) + if(!CheckResult(fd)) return; + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::ceills(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckLCVTArg(fs); - s64 result; - CVT_OP_CheckExcept({ result = std::ceil(fs); }); - CheckRound(fs, result); - FGR_D(regs.cop0.status, FD(instr)) = result; -} - -void Cop1::ceilws(u32 instr) { - CheckFPUUsable(); - auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckWCVTArg(fs); - s32 result; - CVT_OP_CheckExcept({ result = std::ceil(fs); }); - CheckRound(fs, result); - FGR_D(regs.cop0.status, FD(instr)) = result; + if(!CheckCVTArg(fs)) return; + CHECK_FPE(s64, fd, std::ceil(fs)) + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::ceilld(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckLCVTArg(fs); - s64 result; - CVT_OP_CheckExcept({ result = std::ceil(fs); }); - CheckRound(fs, result); - FGR_D(regs.cop0.status, FD(instr)) = result; + if(!CheckCVTArg(fs)) return; + CHECK_FPE(s64, fd, std::ceil(fs)) + FGR_D(regs.cop0.status, FD(instr)) = fd; +} + +void Cop1::ceilws(u32 instr) { + if(!CheckFPUUsable()) return; + auto fs = FGR_S(regs.cop0.status, FS(instr)); + if(!CheckCVTArg(fs)) return; + CHECK_FPE_CONV(s32, fd, std::ceil(fs)) + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::ceilwd(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckWCVTArg(fs); - s32 result; - CVT_OP_CheckExcept({ result = std::ceil(fs); }); - CheckRound(fs, result); - FGR_D(regs.cop0.status, FD(instr)) = result; + if(!CheckCVTArg(fs)) return; + CHECK_FPE_CONV(s32, fd, std::ceil(fs)) + FGR_D(regs.cop0.status, FD(instr)) = fd; } -void Cop1::cfc1(u32 instr) const { - CheckFPUUsable_PreserveCause(); +void Cop1::cfc1(u32 instr) { + if(!CheckFPUUsable()) return; u8 fd = RD(instr); s32 val = 0; switch(fd) { @@ -479,7 +524,7 @@ void Cop1::cfc1(u32 instr) const { } void Cop1::ctc1(u32 instr) { - CheckFPUUsable_PreserveCause(); + if(!CheckFPUUsable()) return; u8 fs = RD(instr); u32 val = regs.gpr[RT(instr)]; switch(fs) { @@ -495,267 +540,266 @@ void Cop1::ctc1(u32 instr) { case 3: fesetround(FE_DOWNWARD); break; } } - CheckFPUException(); + FireException(); } break; default: Util::panic("Undefined CTC1 with rd != 0 or 31"); } } void Cop1::cvtds(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckArg(fs); - double result; - OP_CheckExcept({ result = double(fs); }); - CheckResult(result); - FGR_D(regs.cop0.status, FD(instr)) = result; + if(!CheckArg(fs)) return; + CHECK_FPE(double, fd, fs) + if(!CheckResult(fd)) return; + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::cvtsd(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckArg(fs); - float result; - OP_CheckExcept({ result = float(fs); }); - CheckResult(result); - FGR_D(regs.cop0.status, FD(instr)) = result; + if(!CheckArg(fs)) return; + CHECK_FPE(float, fd, fs) + if(!CheckResult(fd)) return; + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::cvtsw(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - float result; - OP_CheckExcept({ result = float(fs); }); - CheckResult(result); - FGR_D(regs.cop0.status, FD(instr)) = result; + CHECK_FPE(float, fd, fs) + if(!CheckResult(fd)) return; + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::cvtsl(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); if (fs >= s64(0x0080000000000000) || fs < s64(0xff80000000000000)) { SetCauseUnimplemented(); - CheckFPUException(); + if(FireException()) return; } - float result; - OP_CheckExcept({ result = float(fs); }); - CheckResult(result); - FGR_D(regs.cop0.status, FD(instr)) = result; + CHECK_FPE(float, fd, fs) + if(!CheckResult(fd)) return; + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::cvtwd(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckWCVTArg(fs); - s32 result; - PUSHROUNDING; - CVT_OP_CheckExcept({ result = std::rint(fs); }); - POPROUNDING; - CheckRound(fs, result); - FGR_D(regs.cop0.status, FD(instr)) = result; + if(!CheckCVTArg(fs)) return; + CHECK_FPE_CONV(s32, fd, std::rint(fs)) + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::cvtws(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckWCVTArg(fs); - s32 result; - PUSHROUNDING; - CVT_OP_CheckExcept({ result = std::rint(fs); }); - POPROUNDING; - CheckRound(fs, result); - FGR_D(regs.cop0.status, FD(instr)) = result; + if(!CheckCVTArg(fs)) return; + CHECK_FPE_CONV(s32, fd, std::rint(fs)) + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::cvtls(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckLCVTArg(fs); - s64 result; - PUSHROUNDING; - CVT_OP_CheckExcept({ result = std::rint(fs); }); - POPROUNDING; - CheckRound(fs, result); - FGR_D(regs.cop0.status, FD(instr)) = result; + if(!CheckCVTArg(fs)) return; + CHECK_FPE(s64, fd, std::rint(fs)) + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::cvtdw(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - double result; - OP_CheckExcept({ result = double(fs); }); - CheckResult(result); - FGR_D(regs.cop0.status, FD(instr)) = result; + CHECK_FPE(double, fd, fs) + if(!CheckResult(fd)) return; + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::cvtdl(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); if (fs >= s64(0x0080000000000000) || fs < s64(0xff80000000000000)) { SetCauseUnimplemented(); - CheckFPUException(); + if(FireException()) return; } - double result; - OP_CheckExcept({ result = double(fs); }); - CheckResult(result); - FGR_D(regs.cop0.status, FD(instr)) = result; + CHECK_FPE(double, fd, fs) + if(!CheckResult(fd)) return; + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::cvtld(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckLCVTArg(fs); - s64 result; - PUSHROUNDING; - CVT_OP_CheckExcept({ result = std::rint(fs); }); - POPROUNDING; - CheckRound(fs, result); - FGR_D(regs.cop0.status, FD(instr)) = result; + if(!CheckCVTArg(fs)) return; + CHECK_FPE(s64, fd, std::rint(fs)) + FGR_D(regs.cop0.status, FD(instr)) = fd; } +template +bool Cop1::XORDERED(T fs, T ft) { + if(std::isnan(fs) || std::isnan(ft)) { + if(std::isnan(fs) && (!quiet || isqnan(fs)) && SetCauseInvalid()) { + FireException(); + return false; + } + if(std::isnan(ft) && (!quiet || isqnan(ft)) && SetCauseInvalid()) { + FireException(); + return false; + } + fcr31.compare = cf; + return false; + } + + return true; +} + +#define ORDERED(type, cf) XORDERED +#define UNORDERED(type, cf) XORDERED + template void Cop1::cf(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; T fs = FGR_S(regs.cop0.status, FS(instr)); T ft = FGR_T(regs.cop0.status, FT(instr)); - checkqnanregs(fs, ft); - fcr31.compare = false; + if(!UNORDERED(T, 0)(fs, ft)) return; + fcr31.compare = 0; } template void Cop1::cun(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; T fs = FGR_S(regs.cop0.status, FS(instr)); T ft = FGR_T(regs.cop0.status, FT(instr)); - checkqnanregs(fs, ft); - fcr31.compare = any_unordered(fs, ft); + if(!UNORDERED(T, 1)(fs, ft)) return; + fcr31.compare = 0; } template void Cop1::ceq(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; T fs = FGR_S(regs.cop0.status, FS(instr)); T ft = FGR_T(regs.cop0.status, FT(instr)); - checkqnanregs(fs, ft); + if(!UNORDERED(T, 0)(fs, ft)) return; fcr31.compare = fs == ft; } template void Cop1::cueq(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; T fs = FGR_S(regs.cop0.status, FS(instr)); T ft = FGR_T(regs.cop0.status, FT(instr)); - checkqnanregs(fs, ft); - fcr31.compare = fs == ft || any_unordered(fs, ft); + if(!UNORDERED(T, 1)(fs, ft)) return; + fcr31.compare = fs == ft; } template void Cop1::colt(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; T fs = FGR_S(regs.cop0.status, FS(instr)); T ft = FGR_T(regs.cop0.status, FT(instr)); - checkqnanregs(fs, ft); + if(!UNORDERED(T, 0)(fs, ft)) return; fcr31.compare = fs < ft; } template void Cop1::cult(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; T fs = FGR_S(regs.cop0.status, FS(instr)); T ft = FGR_T(regs.cop0.status, FT(instr)); - checkqnanregs(fs, ft); - fcr31.compare = fs < ft || any_unordered(fs, ft); + if(!UNORDERED(T, 1)(fs, ft)) return; + fcr31.compare = fs < ft; } template void Cop1::cole(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; T fs = FGR_S(regs.cop0.status, FS(instr)); T ft = FGR_T(regs.cop0.status, FT(instr)); - checkqnanregs(fs, ft); + if(!UNORDERED(T, 0)(fs, ft)) return; fcr31.compare = fs <= ft; } template void Cop1::cule(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; T fs = FGR_S(regs.cop0.status, FS(instr)); T ft = FGR_T(regs.cop0.status, FT(instr)); - checkqnanregs(fs, ft); - fcr31.compare = fs <= ft || any_unordered(fs, ft); + if(!UNORDERED(T, 1)(fs, ft)) return; + fcr31.compare = fs <= ft; } template void Cop1::csf(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; T fs = FGR_S(regs.cop0.status, FS(instr)); T ft = FGR_T(regs.cop0.status, FT(instr)); - checknanregs(fs, ft); - fcr31.compare = false; + if(!ORDERED(T, 0)(fs, ft)) return; + fcr31.compare = 0; } template void Cop1::cngle(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; T fs = FGR_S(regs.cop0.status, FS(instr)); T ft = FGR_T(regs.cop0.status, FT(instr)); - checknanregs(fs, ft); - fcr31.compare = any_unordered(fs, ft); + if(!ORDERED(T, 1)(fs, ft)) return; + fcr31.compare = 0; } template void Cop1::cseq(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; T fs = FGR_S(regs.cop0.status, FS(instr)); T ft = FGR_T(regs.cop0.status, FT(instr)); - checknanregs(fs, ft); + if(!ORDERED(T, 0)(fs, ft)) return; fcr31.compare = fs == ft; } template void Cop1::cngl(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; T fs = FGR_S(regs.cop0.status, FS(instr)); T ft = FGR_T(regs.cop0.status, FT(instr)); - checknanregs(fs, ft); - fcr31.compare = fs == ft || any_unordered(fs, ft); + if(!ORDERED(T, 1)(fs, ft)) return; + fcr31.compare = fs == ft; } template void Cop1::clt(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; T fs = FGR_S(regs.cop0.status, FS(instr)); T ft = FGR_T(regs.cop0.status, FT(instr)); - checknanregs(fs, ft); + if(!ORDERED(T, 0)(fs, ft)) return; fcr31.compare = fs < ft; } template void Cop1::cnge(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; T fs = FGR_S(regs.cop0.status, FS(instr)); T ft = FGR_T(regs.cop0.status, FT(instr)); - checknanregs(fs, ft); - fcr31.compare = fs < ft || any_unordered(fs, ft); + if(!ORDERED(T, 1)(fs, ft)) return; + fcr31.compare = fs < ft; } template void Cop1::cle(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; T fs = FGR_S(regs.cop0.status, FS(instr)); T ft = FGR_T(regs.cop0.status, FT(instr)); - checknanregs(fs, ft); + if(!ORDERED(T, 0)(fs, ft)) return; fcr31.compare = fs <= ft; } template void Cop1::cngt(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; T fs = FGR_S(regs.cop0.status, FS(instr)); T ft = FGR_T(regs.cop0.status, FT(instr)); - checknanregs(fs, ft); - fcr31.compare = fs <= ft || any_unordered(fs, ft); + if(!ORDERED(T, 1)(fs, ft)) return; + fcr31.compare = fs <= ft; } template void Cop1::cf(u32 instr); @@ -792,181 +836,218 @@ template void Cop1::cle(u32 instr); template void Cop1::cngt(u32 instr); void Cop1::divs(u32 instr) { - OP(float, fs / ft); + if(!CheckFPUUsable()) return; + auto fs = FGR_S(regs.cop0.status, FS(instr)); + auto ft = FGR_T(regs.cop0.status, FT(instr)); + if(!CheckArgs(fs, ft)) return; + CHECK_FPE(float, fd, fs / ft) + if(!CheckResult(fd)) return; + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::divd(u32 instr) { - OP(double, fs / ft); + if(!CheckFPUUsable()) return; + auto fs = FGR_S(regs.cop0.status, FS(instr)); + auto ft = FGR_T(regs.cop0.status, FT(instr)); + if(!CheckArgs(fs, ft)) return; + CHECK_FPE(double, fd, fs / ft) + if(!CheckResult(fd)) return; + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::muls(u32 instr) { - OP(float, fs * ft); + if(!CheckFPUUsable()) return; + auto fs = FGR_S(regs.cop0.status, FS(instr)); + auto ft = FGR_T(regs.cop0.status, FT(instr)); + if(!CheckArgs(fs, ft)) return; + CHECK_FPE(float, fd, fs * ft) + if(!CheckResult(fd)) return; + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::muld(u32 instr) { - OP(double, fs * ft); + if(!CheckFPUUsable()) return; + auto fs = FGR_S(regs.cop0.status, FS(instr)); + auto ft = FGR_T(regs.cop0.status, FT(instr)); + if(!CheckArgs(fs, ft)) return; + CHECK_FPE(double, fd, fs * ft) + if(!CheckResult(fd)) return; + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::subs(u32 instr) { - OP(float, fs - ft); + if(!CheckFPUUsable()) return; + auto fs = FGR_S(regs.cop0.status, FS(instr)); + auto ft = FGR_T(regs.cop0.status, FT(instr)); + if(!CheckArgs(fs, ft)) return; + CHECK_FPE(float, fd, fs - ft) + if(!CheckResult(fd)) return; + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::subd(u32 instr) { - OP(double, fs - ft); + if(!CheckFPUUsable()) return; + auto fs = FGR_S(regs.cop0.status, FS(instr)); + auto ft = FGR_T(regs.cop0.status, FT(instr)); + if(!CheckArgs(fs, ft)) return; + CHECK_FPE(double, fd, fs - ft) + if(!CheckResult(fd)) return; + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::movs(u32 instr) { - CheckFPUUsable_PreserveCause(); - auto val = FGR_S(regs.cop0.status, FS(instr)); - FGR_D(regs.cop0.status, FD(instr)) = val; + movd(instr); } void Cop1::movd(u32 instr) { - CheckFPUUsable_PreserveCause(); - auto val = FGR_S(regs.cop0.status, FS(instr)); - FGR_D(regs.cop0.status, FD(instr)) = val; + if(!CheckFPUUsable()) return; + FGR_D(regs.cop0.status, FD(instr)) = FGR_S(regs.cop0.status, FS(instr)); } void Cop1::negs(u32 instr) { - OP(float, -fs); + if(!CheckFPUUsable()) return; + auto fs = FGR_S(regs.cop0.status, FS(instr)); + if(!CheckArg(fs)) return; + CHECK_FPE(float, fd, -fs) + if(!CheckResult(fd)) return; + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::negd(u32 instr) { - OP(double, -fs); + if(!CheckFPUUsable()) return; + auto fs = FGR_S(regs.cop0.status, FS(instr)); + if(!CheckArg(fs)) return; + CHECK_FPE(double, fd, -fs) + if(!CheckResult(fd)) return; + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::sqrts(u32 instr) { - OP(float, std::sqrt(fs)); + if(!CheckFPUUsable()) return; + auto fs = FGR_S(regs.cop0.status, FS(instr)); + if(!CheckArg(fs)) return; + CHECK_FPE(float, fd, std::sqrt(fs)) + if(!CheckResult(fd)) return; + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::sqrtd(u32 instr) { - OP(double, std::sqrt(fs)); + if(!CheckFPUUsable()) return; + auto fs = FGR_S(regs.cop0.status, FS(instr)); + if(!CheckArg(fs)) return; + CHECK_FPE(double, fd, std::sqrt(fs)) + if(!CheckResult(fd)) return; + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::roundls(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckLCVTArg(fs); - s64 result; - CVT_OP_CheckExcept({ result = std::nearbyint(fs); }); - CheckRound(fs, result); - FGR_D(regs.cop0.status, FD(instr)) = result; + if(!CheckCVTArg(fs)) return; + CHECK_FPE(s64, fd, std::round(fs)) + if((float)fd != fs && SetCauseInexact()) { FireException(); return; } + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::roundld(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckLCVTArg(fs); - s64 result; - CVT_OP_CheckExcept({ result = std::nearbyint(fs); }); - CheckRound(fs, result); - FGR_D(regs.cop0.status, FD(instr)) = result; + if(!CheckCVTArg(fs)) return; + CHECK_FPE(s64, fd, std::round(fs)) + if((double)fd != fs && SetCauseInexact()) { FireException(); return; } + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::roundws(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckWCVTArg(fs); - s32 result; - CVT_OP_CheckExcept({ result = std::nearbyint(fs); }); - CheckRound(fs, result); - FGR_D(regs.cop0.status, FD(instr)) = result; + if(!CheckCVTArg(fs)) return; + CHECK_FPE_CONV(s32, fd, std::round(fs)) + if(fd != fs && SetCauseInexact()) { FireException(); return; } + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::roundwd(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckWCVTArg(fs); - s32 result; - CVT_OP_CheckExcept({ result = std::nearbyint(fs); }); - CheckRound(fs, result); - FGR_D(regs.cop0.status, FD(instr)) = result; + if(!CheckCVTArg(fs)) return; + CHECK_FPE_CONV(s32, fd, std::round(fs)) + if(fd != fs && SetCauseInexact()) { FireException(); return; } + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::floorls(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckLCVTArg(fs); - s64 result; - CVT_OP_CheckExcept({ result = std::floor(fs); }); - CheckRound(fs, result); - FGR_D(regs.cop0.status, FD(instr)) = result; + if(!CheckCVTArg(fs)) return; + CHECK_FPE(s64, fd, std::floor(fs)) + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::floorld(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckLCVTArg(fs); - s64 result; - CVT_OP_CheckExcept({ result = std::floor(fs); }); - CheckRound(fs, result); - FGR_D(regs.cop0.status, FD(instr)) = result; + if(!CheckCVTArg(fs)) return; + CHECK_FPE(s64, fd, std::floor(fs)) + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::floorws(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckWCVTArg(fs); - s32 result; - CVT_OP_CheckExcept({ result = std::floor(fs); }); - CheckRound(fs, result); - FGR_D(regs.cop0.status, FD(instr)) = result; + if(!CheckCVTArg(fs)) return; + CHECK_FPE_CONV(s32, fd, std::floor(fs)) + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::floorwd(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckWCVTArg(fs); - s32 result; - CVT_OP_CheckExcept({ result = std::floor(fs); }); - CheckRound(fs, result); - FGR_D(regs.cop0.status, FD(instr)) = result; + if(!CheckCVTArg(fs)) return; + CHECK_FPE_CONV(s32, fd, std::floor(fs)) + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::truncws(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckWCVTArg(fs); - s32 result; - CVT_OP_CheckExcept({ result = std::trunc(fs); }); - CheckRound(fs, result); - FGR_D(regs.cop0.status, FD(instr)) = result; + if(!CheckCVTArg(fs)) return; + CHECK_FPE_CONV(s32, fd, std::trunc(fs)) + if((float)fd != fs && SetCauseInexact()) { FireException(); return; } + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::truncwd(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckWCVTArg(fs); - s32 result; - CVT_OP_CheckExcept({ result = std::trunc(fs); }); - CheckRound(fs, result); - FGR_D(regs.cop0.status, FD(instr)) = result; + if(!CheckCVTArg(fs)) return; + CHECK_FPE_CONV(s32, fd, std::trunc(fs)) + if((double)fd != fs && SetCauseInexact()) { FireException(); return; } + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::truncls(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckLCVTArg(fs); - s64 result; - CVT_OP_CheckExcept({ result = std::trunc(fs); }); - CheckRound(fs, result); - FGR_D(regs.cop0.status, FD(instr)) = result; + if(!CheckCVTArg(fs)) return; + CHECK_FPE(s64, fd, std::trunc(fs)) + if((float)fd != fs && SetCauseInexact()) { FireException(); return; } + FGR_D(regs.cop0.status, FD(instr)) = fd; } void Cop1::truncld(u32 instr) { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; auto fs = FGR_S(regs.cop0.status, FS(instr)); - CheckLCVTArg(fs); - s64 result; - CVT_OP_CheckExcept({ result = std::trunc(fs); }); - CheckRound(fs, result); - FGR_D(regs.cop0.status, FD(instr)) = result; + if(!CheckCVTArg(fs)) return; + CHECK_FPE(s64, fd, std::trunc(fs)) + if((double)fd != fs && SetCauseInexact()) { FireException(); return; } + FGR_D(regs.cop0.status, FD(instr)) = fd; } template void Cop1::lwc1(T &cpu, Mem &mem, u32 instr) { if constexpr(std::is_same_v) { - CheckFPUUsable_PreserveCause(); + if(!CheckFPUUsable()) return; lwc1Interp(mem, instr); } else if constexpr (std::is_same_v) { lwc1JIT(cpu, mem, instr); @@ -981,7 +1062,7 @@ template void Cop1::lwc1(JIT&, Mem&, u32); template void Cop1::swc1(T &cpu, Mem &mem, u32 instr) { if constexpr(std::is_same_v) { - CheckFPUUsable_PreserveCause(); + if(!CheckFPUUsable()) return; swc1Interp(mem, instr); } else if constexpr (std::is_same_v) { swc1JIT(cpu, mem, instr); @@ -996,7 +1077,7 @@ template void Cop1::swc1(JIT&, Mem&, u32); template void Cop1::ldc1(T &cpu, Mem &mem, u32 instr) { if constexpr(std::is_same_v) { - CheckFPUUsable_PreserveCause(); + if(!CheckFPUUsable()) return; ldc1Interp(mem, instr); } else if constexpr (std::is_same_v) { ldc1JIT(cpu, mem, instr); @@ -1011,7 +1092,7 @@ template void Cop1::ldc1(JIT&, Mem&, u32); template void Cop1::sdc1(T &cpu, Mem &mem, u32 instr) { if constexpr(std::is_same_v) { - CheckFPUUsable_PreserveCause(); + if(!CheckFPUUsable()) return; sdc1Interp(mem, instr); } else if constexpr (std::is_same_v) { sdc1JIT(cpu, mem, instr); @@ -1049,7 +1130,7 @@ void Cop1::swc1Interp(Mem& mem, u32 instr) { } void Cop1::unimplemented() { - CheckFPUUsable(); + if(!CheckFPUUsable()) return; fcr31.cause_unimplemented_operation = true; FireException(); } @@ -1080,22 +1161,22 @@ void Cop1::sdc1Interp(Mem& mem, u32 instr) { } void Cop1::mfc1(u32 instr) { - CheckFPUUsable_PreserveCause(); + if(!CheckFPUUsable()) return; regs.gpr[RT(instr)] = FGR_T(regs.cop0.status, FS(instr)); } void Cop1::dmfc1(u32 instr) { - CheckFPUUsable_PreserveCause(); + if(!CheckFPUUsable()) return; regs.gpr[RT(instr)] = FGR_S(regs.cop0.status, FS(instr)); } void Cop1::mtc1(u32 instr) { - CheckFPUUsable_PreserveCause(); + if(!CheckFPUUsable()) return; FGR_T(regs.cop0.status, FS(instr)) = regs.gpr[RT(instr)]; } void Cop1::dmtc1(u32 instr) { - CheckFPUUsable_PreserveCause(); + if(!CheckFPUUsable()) return; FGR_S(regs.cop0.status, FS(instr)) = regs.gpr[RT(instr)]; }