1184 lines
32 KiB
C++
1184 lines
32 KiB
C++
#include <core/registers/Cop1.hpp>
|
|
#include <core/registers/Registers.hpp>
|
|
#include <core/Interpreter.hpp>
|
|
#include <cmath>
|
|
#include <cfenv>
|
|
|
|
namespace n64 {
|
|
template<> auto Cop1::FGR_T<s32>(Cop0Status& status, u32 index) -> s32& {
|
|
if (status.fr) {
|
|
return fgr[index].int32;
|
|
} else if (index & 1) {
|
|
return fgr[index & ~1].int32h;
|
|
} else {
|
|
return fgr[index & ~1].int32;
|
|
}
|
|
}
|
|
|
|
template<> auto Cop1::FGR_T<u32>(Cop0Status& status, u32 index) -> u32& {
|
|
return (u32&)FGR_T<s32>(status, index);
|
|
}
|
|
|
|
template<> auto Cop1::FGR_T<float>(Cop0Status&, u32 index) -> float& {
|
|
return fgr[index].float32;
|
|
}
|
|
|
|
template<> auto Cop1::FGR_T<s64>(Cop0Status& status, u32 index) -> s64& {
|
|
if (status.fr) {
|
|
return fgr[index].int64;
|
|
} else {
|
|
return fgr[index & ~1].int64;
|
|
}
|
|
}
|
|
|
|
template<> auto Cop1::FGR_T<u64>(Cop0Status& status, u32 index) -> u64& {
|
|
return (u64&)FGR_T<s64>(status, index);
|
|
}
|
|
|
|
template<> auto Cop1::FGR_T<double>(Cop0Status&, u32 index) -> double& {
|
|
return fgr[index].float64;
|
|
}
|
|
|
|
template<> auto Cop1::FGR_S<s32>(Cop0Status& status, u32 index) -> s32& {
|
|
if (status.fr) {
|
|
return fgr[index].int32;
|
|
} else {
|
|
return fgr[index & ~1].int32;
|
|
}
|
|
}
|
|
|
|
template<> auto Cop1::FGR_S<u32>(Cop0Status& status, u32 index) -> u32& {
|
|
return (u32&)FGR_S<s32>(status, index);
|
|
}
|
|
|
|
template<> auto Cop1::FGR_S<float>(Cop0Status& status, u32 index) -> float& {
|
|
if (status.fr) {
|
|
return fgr[index].float32;
|
|
} else {
|
|
return fgr[index & ~1].float32;
|
|
}
|
|
}
|
|
|
|
template<> auto Cop1::FGR_S<s64>(Cop0Status& status, u32 index) -> s64& {
|
|
return FGR_T<s64>(status, index);
|
|
}
|
|
|
|
template<> auto Cop1::FGR_S<u64>(Cop0Status& status, u32 index) -> u64& {
|
|
return (u64&)FGR_S<s64>(status, index);
|
|
}
|
|
|
|
template<> auto Cop1::FGR_S<double>(Cop0Status& status, u32 index) -> double& {
|
|
if (status.fr) {
|
|
return fgr[index].float64;
|
|
} else {
|
|
return fgr[index & ~1].float64;
|
|
}
|
|
}
|
|
|
|
template<> auto Cop1::FGR_D<s32>(Cop0Status&, u32 index) -> s32& {
|
|
fgr[index].int32h = 0;
|
|
return fgr[index].int32;
|
|
}
|
|
|
|
template<> auto Cop1::FGR_D<u32>(Cop0Status& status, u32 index) -> u32& {
|
|
return (u32&)FGR_D<s32>(status, index);
|
|
}
|
|
|
|
template<> auto Cop1::FGR_D<float>(Cop0Status&, u32 index) -> float& {
|
|
fgr[index].float32h = 0;
|
|
return fgr[index].float32;
|
|
}
|
|
|
|
template<> auto Cop1::FGR_D<s64>(Cop0Status&, u32 index) -> s64& {
|
|
return fgr[index].int64;
|
|
}
|
|
|
|
template<> auto Cop1::FGR_D<u64>(Cop0Status& status, u32 index) -> u64& {
|
|
return (u64&)FGR_D<s64>(status, index);
|
|
}
|
|
|
|
template<> auto Cop1::FGR_D<double>(Cop0Status& status, u32 index) -> double& {
|
|
return FGR_T<double>(status, index);
|
|
}
|
|
|
|
|
|
template <typename T> 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<s32>(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<s32>(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<s64>(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<s64>(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 <typename T>
|
|
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 <typename T>
|
|
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 preserveCause>
|
|
bool Cop1::CheckFPUUsable() {
|
|
if constexpr (preserveCause) {
|
|
if(!regs.cop0.status.cu1) {
|
|
regs.cop0.FireException(ExceptionCode::CoprocessorUnusable, 1, regs.oldPC);
|
|
return false;
|
|
}
|
|
} else {
|
|
if(!CheckFPUUsable<true>()) return false;
|
|
fcr31.cause = 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
template bool Cop1::CheckFPUUsable<true>();
|
|
template bool Cop1::CheckFPUUsable<false>();
|
|
|
|
bool Cop1::FireException() {
|
|
if(CheckFPUException()) {
|
|
regs.cop0.FireException(ExceptionCode::FloatingPointError, 0, regs.oldPC);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Cop1::CheckFPUException() {
|
|
u32 enable = fcr31.enable | (1 << 5);
|
|
if(fcr31.cause & enable) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template <typename T>
|
|
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<T>::min();
|
|
case 3: return std::signbit(f) ? -std::numeric_limits<T>::min() : T();
|
|
}
|
|
}
|
|
|
|
template <> bool Cop1::CheckResult<float>(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>(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 cvt>
|
|
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(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;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool Cop1::SetCauseUnimplemented() {
|
|
fcr31.cause_unimplemented_operation = true;
|
|
return true;
|
|
}
|
|
|
|
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 <typename T>
|
|
void Cop1::SetCauseByArg(T f) {
|
|
auto fp_class = std::fpclassify(f);
|
|
switch(fp_class) {
|
|
case FP_NAN:
|
|
if(isqnan(f)) {
|
|
SetCauseInvalid();
|
|
//CheckFPUException();
|
|
} else {
|
|
SetCauseUnimplemented();
|
|
//CheckFPUException();
|
|
}
|
|
break;
|
|
case FP_SUBNORMAL:
|
|
SetCauseUnimplemented();
|
|
//CheckFPUException();
|
|
break;
|
|
case FP_INFINITE:
|
|
case FP_ZERO:
|
|
case FP_NORMAL:
|
|
break; // No-op, these are fine.
|
|
default:
|
|
Util::panic("Unknown floating point classification: {}", fp_class);
|
|
}
|
|
}
|
|
|
|
#define CHECK_FPE_IMPL(type, res, operation, convert) \
|
|
feclearexcept(FE_ALL_EXCEPT); \
|
|
volatile type v##res = [&]() __attribute__((noinline)) -> type { return (operation); }(); \
|
|
if (TestExceptions<convert>()) return; \
|
|
type res = v##res;
|
|
|
|
#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) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<double>(regs.cop0.status, FS(instr));
|
|
if(!CheckArg(fs)) return;
|
|
auto fd = std::abs(fs);
|
|
if(!CheckResult(fd)) return;
|
|
FGR_D<double>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::abss(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<float>(regs.cop0.status, FS(instr));
|
|
if(!CheckArg(fs)) return;
|
|
auto fd = std::abs(fs);
|
|
if(!CheckResult(fd)) return;
|
|
FGR_D<float>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::adds(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<float>(regs.cop0.status, FS(instr));
|
|
auto ft = FGR_T<float>(regs.cop0.status, FT(instr));
|
|
if(!CheckArgs(fs, ft)) return;
|
|
CHECK_FPE(float, fd, fs + ft)
|
|
if(!CheckResult(fd)) return;
|
|
FGR_D<float>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::addd(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<double>(regs.cop0.status, FS(instr));
|
|
auto ft = FGR_T<double>(regs.cop0.status, FT(instr));
|
|
if(!CheckArgs(fs, ft)) return;
|
|
CHECK_FPE(double, fd, fs + ft)
|
|
if(!CheckResult(fd)) return;
|
|
FGR_D<double>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::ceills(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<float>(regs.cop0.status, FS(instr));
|
|
if(!CheckCVTArg<s64>(fs)) return;
|
|
CHECK_FPE(s64, fd, std::ceil(fs))
|
|
FGR_D<s64>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::ceilld(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<double>(regs.cop0.status, FS(instr));
|
|
if(!CheckCVTArg<s64>(fs)) return;
|
|
CHECK_FPE(s64, fd, std::ceil(fs))
|
|
FGR_D<s64>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::ceilws(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<float>(regs.cop0.status, FS(instr));
|
|
if(!CheckCVTArg<s32>(fs)) return;
|
|
CHECK_FPE_CONV(s32, fd, std::ceil(fs))
|
|
FGR_D<s32>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::ceilwd(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<double>(regs.cop0.status, FS(instr));
|
|
if(!CheckCVTArg<s32>(fs)) return;
|
|
CHECK_FPE_CONV(s32, fd, std::ceil(fs))
|
|
FGR_D<s32>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::cfc1(u32 instr) {
|
|
if(!CheckFPUUsable<true>()) return;
|
|
u8 fd = RD(instr);
|
|
s32 val = 0;
|
|
switch(fd) {
|
|
case 0: val = fcr0; break;
|
|
case 31:
|
|
val = fcr31.read();
|
|
break;
|
|
default: Util::panic("Undefined CFC1 with rd != 0 or 31");
|
|
}
|
|
regs.gpr[RT(instr)] = val;
|
|
}
|
|
|
|
void Cop1::ctc1(u32 instr) {
|
|
if(!CheckFPUUsable<true>()) return;
|
|
u8 fs = RD(instr);
|
|
u32 val = regs.gpr[RT(instr)];
|
|
switch(fs) {
|
|
case 0: break;
|
|
case 31: {
|
|
u32 prevRound = fcr31.rounding_mode;
|
|
fcr31.write(val);
|
|
if (prevRound != fcr31.rounding_mode) {
|
|
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;
|
|
}
|
|
}
|
|
FireException();
|
|
} break;
|
|
default: Util::panic("Undefined CTC1 with rd != 0 or 31");
|
|
}
|
|
}
|
|
|
|
void Cop1::cvtds(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<float>(regs.cop0.status, FS(instr));
|
|
if(!CheckArg(fs)) return;
|
|
CHECK_FPE(double, fd, fs)
|
|
if(!CheckResult(fd)) return;
|
|
FGR_D<double>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::cvtsd(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<double>(regs.cop0.status, FS(instr));
|
|
if(!CheckArg(fs)) return;
|
|
CHECK_FPE(float, fd, fs)
|
|
if(!CheckResult(fd)) return;
|
|
FGR_D<float>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::cvtsw(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<s32>(regs.cop0.status, FS(instr));
|
|
CHECK_FPE(float, fd, fs)
|
|
if(!CheckResult(fd)) return;
|
|
FGR_D<float>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::cvtsl(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<s64>(regs.cop0.status, FS(instr));
|
|
if (fs >= s64(0x0080000000000000) || fs < s64(0xff80000000000000)) {
|
|
SetCauseUnimplemented();
|
|
if(FireException()) return;
|
|
}
|
|
CHECK_FPE(float, fd, fs)
|
|
if(!CheckResult(fd)) return;
|
|
FGR_D<float>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::cvtwd(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<double>(regs.cop0.status, FS(instr));
|
|
if(!CheckCVTArg<s32>(fs)) return;
|
|
CHECK_FPE_CONV(s32, fd, std::rint(fs))
|
|
FGR_D<s32>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::cvtws(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<float>(regs.cop0.status, FS(instr));
|
|
if(!CheckCVTArg<s32>(fs)) return;
|
|
CHECK_FPE_CONV(s32, fd, std::rint(fs))
|
|
FGR_D<s32>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::cvtls(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<float>(regs.cop0.status, FS(instr));
|
|
if(!CheckCVTArg<s64>(fs)) return;
|
|
CHECK_FPE(s64, fd, std::rint(fs))
|
|
FGR_D<s64>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::cvtdw(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<s32>(regs.cop0.status, FS(instr));
|
|
CHECK_FPE(double, fd, fs)
|
|
if(!CheckResult(fd)) return;
|
|
FGR_D<double>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::cvtdl(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<s64>(regs.cop0.status, FS(instr));
|
|
|
|
if (fs >= s64(0x0080000000000000) || fs < s64(0xff80000000000000)) {
|
|
SetCauseUnimplemented();
|
|
if(FireException()) return;
|
|
}
|
|
CHECK_FPE(double, fd, fs)
|
|
if(!CheckResult(fd)) return;
|
|
FGR_D<double>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::cvtld(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<double>(regs.cop0.status, FS(instr));
|
|
if(!CheckCVTArg<s64>(fs)) return;
|
|
CHECK_FPE(s64, fd, std::rint(fs))
|
|
FGR_D<s64>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
template<typename T, bool quiet, bool cf>
|
|
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<type, 0, cf>
|
|
#define UNORDERED(type, cf) XORDERED<type, 1, cf>
|
|
|
|
template <typename T>
|
|
void Cop1::cf(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
T fs = FGR_S<T>(regs.cop0.status, FS(instr));
|
|
T ft = FGR_T<T>(regs.cop0.status, FT(instr));
|
|
if(!UNORDERED(T, 0)(fs, ft)) return;
|
|
fcr31.compare = 0;
|
|
}
|
|
|
|
template <typename T>
|
|
void Cop1::cun(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
T fs = FGR_S<T>(regs.cop0.status, FS(instr));
|
|
T ft = FGR_T<T>(regs.cop0.status, FT(instr));
|
|
if(!UNORDERED(T, 1)(fs, ft)) return;
|
|
fcr31.compare = 0;
|
|
}
|
|
|
|
template <typename T>
|
|
void Cop1::ceq(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
T fs = FGR_S<T>(regs.cop0.status, FS(instr));
|
|
T ft = FGR_T<T>(regs.cop0.status, FT(instr));
|
|
if(!UNORDERED(T, 0)(fs, ft)) return;
|
|
fcr31.compare = fs == ft;
|
|
}
|
|
|
|
template <typename T>
|
|
void Cop1::cueq(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
T fs = FGR_S<T>(regs.cop0.status, FS(instr));
|
|
T ft = FGR_T<T>(regs.cop0.status, FT(instr));
|
|
if(!UNORDERED(T, 1)(fs, ft)) return;
|
|
fcr31.compare = fs == ft;
|
|
}
|
|
|
|
template <typename T>
|
|
void Cop1::colt(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
T fs = FGR_S<T>(regs.cop0.status, FS(instr));
|
|
T ft = FGR_T<T>(regs.cop0.status, FT(instr));
|
|
if(!UNORDERED(T, 0)(fs, ft)) return;
|
|
fcr31.compare = fs < ft;
|
|
}
|
|
|
|
template <typename T>
|
|
void Cop1::cult(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
T fs = FGR_S<T>(regs.cop0.status, FS(instr));
|
|
T ft = FGR_T<T>(regs.cop0.status, FT(instr));
|
|
if(!UNORDERED(T, 1)(fs, ft)) return;
|
|
fcr31.compare = fs < ft;
|
|
}
|
|
|
|
template <typename T>
|
|
void Cop1::cole(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
T fs = FGR_S<T>(regs.cop0.status, FS(instr));
|
|
T ft = FGR_T<T>(regs.cop0.status, FT(instr));
|
|
if(!UNORDERED(T, 0)(fs, ft)) return;
|
|
fcr31.compare = fs <= ft;
|
|
}
|
|
|
|
template <typename T>
|
|
void Cop1::cule(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
T fs = FGR_S<T>(regs.cop0.status, FS(instr));
|
|
T ft = FGR_T<T>(regs.cop0.status, FT(instr));
|
|
if(!UNORDERED(T, 1)(fs, ft)) return;
|
|
fcr31.compare = fs <= ft;
|
|
}
|
|
|
|
template <typename T>
|
|
void Cop1::csf(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
T fs = FGR_S<T>(regs.cop0.status, FS(instr));
|
|
T ft = FGR_T<T>(regs.cop0.status, FT(instr));
|
|
if(!ORDERED(T, 0)(fs, ft)) return;
|
|
fcr31.compare = 0;
|
|
}
|
|
|
|
template <typename T>
|
|
void Cop1::cngle(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
T fs = FGR_S<T>(regs.cop0.status, FS(instr));
|
|
T ft = FGR_T<T>(regs.cop0.status, FT(instr));
|
|
if(!ORDERED(T, 1)(fs, ft)) return;
|
|
fcr31.compare = 0;
|
|
}
|
|
|
|
template <typename T>
|
|
void Cop1::cseq(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
T fs = FGR_S<T>(regs.cop0.status, FS(instr));
|
|
T ft = FGR_T<T>(regs.cop0.status, FT(instr));
|
|
if(!ORDERED(T, 0)(fs, ft)) return;
|
|
fcr31.compare = fs == ft;
|
|
}
|
|
|
|
template <typename T>
|
|
void Cop1::cngl(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
T fs = FGR_S<T>(regs.cop0.status, FS(instr));
|
|
T ft = FGR_T<T>(regs.cop0.status, FT(instr));
|
|
if(!ORDERED(T, 1)(fs, ft)) return;
|
|
fcr31.compare = fs == ft;
|
|
}
|
|
|
|
template <typename T>
|
|
void Cop1::clt(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
T fs = FGR_S<T>(regs.cop0.status, FS(instr));
|
|
T ft = FGR_T<T>(regs.cop0.status, FT(instr));
|
|
if(!ORDERED(T, 0)(fs, ft)) return;
|
|
fcr31.compare = fs < ft;
|
|
}
|
|
|
|
template <typename T>
|
|
void Cop1::cnge(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
T fs = FGR_S<T>(regs.cop0.status, FS(instr));
|
|
T ft = FGR_T<T>(regs.cop0.status, FT(instr));
|
|
if(!ORDERED(T, 1)(fs, ft)) return;
|
|
fcr31.compare = fs < ft;
|
|
}
|
|
|
|
template <typename T>
|
|
void Cop1::cle(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
T fs = FGR_S<T>(regs.cop0.status, FS(instr));
|
|
T ft = FGR_T<T>(regs.cop0.status, FT(instr));
|
|
if(!ORDERED(T, 0)(fs, ft)) return;
|
|
fcr31.compare = fs <= ft;
|
|
}
|
|
|
|
template <typename T>
|
|
void Cop1::cngt(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
T fs = FGR_S<T>(regs.cop0.status, FS(instr));
|
|
T ft = FGR_T<T>(regs.cop0.status, FT(instr));
|
|
if(!ORDERED(T, 1)(fs, ft)) return;
|
|
fcr31.compare = fs <= ft;
|
|
}
|
|
|
|
template void Cop1::cf<float>(u32 instr);
|
|
template void Cop1::cun<float>(u32 instr);
|
|
template void Cop1::ceq<float>(u32 instr);
|
|
template void Cop1::cueq<float>(u32 instr);
|
|
template void Cop1::colt<float>(u32 instr);
|
|
template void Cop1::cult<float>(u32 instr);
|
|
template void Cop1::cole<float>(u32 instr);
|
|
template void Cop1::cule<float>(u32 instr);
|
|
template void Cop1::csf<float>(u32 instr);
|
|
template void Cop1::cngle<float>(u32 instr);
|
|
template void Cop1::cseq<float>(u32 instr);
|
|
template void Cop1::cngl<float>(u32 instr);
|
|
template void Cop1::clt<float>(u32 instr);
|
|
template void Cop1::cnge<float>(u32 instr);
|
|
template void Cop1::cle<float>(u32 instr);
|
|
template void Cop1::cngt<float>(u32 instr);
|
|
template void Cop1::cf<double>(u32 instr);
|
|
template void Cop1::cun<double>(u32 instr);
|
|
template void Cop1::ceq<double>(u32 instr);
|
|
template void Cop1::cueq<double>(u32 instr);
|
|
template void Cop1::colt<double>(u32 instr);
|
|
template void Cop1::cult<double>(u32 instr);
|
|
template void Cop1::cole<double>(u32 instr);
|
|
template void Cop1::cule<double>(u32 instr);
|
|
template void Cop1::csf<double>(u32 instr);
|
|
template void Cop1::cngle<double>(u32 instr);
|
|
template void Cop1::cseq<double>(u32 instr);
|
|
template void Cop1::cngl<double>(u32 instr);
|
|
template void Cop1::clt<double>(u32 instr);
|
|
template void Cop1::cnge<double>(u32 instr);
|
|
template void Cop1::cle<double>(u32 instr);
|
|
template void Cop1::cngt<double>(u32 instr);
|
|
|
|
void Cop1::divs(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<float>(regs.cop0.status, FS(instr));
|
|
auto ft = FGR_T<float>(regs.cop0.status, FT(instr));
|
|
if(!CheckArgs(fs, ft)) return;
|
|
CHECK_FPE(float, fd, fs / ft)
|
|
if(!CheckResult(fd)) return;
|
|
FGR_D<float>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::divd(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<double>(regs.cop0.status, FS(instr));
|
|
auto ft = FGR_T<double>(regs.cop0.status, FT(instr));
|
|
if(!CheckArgs(fs, ft)) return;
|
|
CHECK_FPE(double, fd, fs / ft)
|
|
if(!CheckResult(fd)) return;
|
|
FGR_D<double>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::muls(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<float>(regs.cop0.status, FS(instr));
|
|
auto ft = FGR_T<float>(regs.cop0.status, FT(instr));
|
|
if(!CheckArgs(fs, ft)) return;
|
|
CHECK_FPE(float, fd, fs * ft)
|
|
if(!CheckResult(fd)) return;
|
|
FGR_D<float>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::muld(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<double>(regs.cop0.status, FS(instr));
|
|
auto ft = FGR_T<double>(regs.cop0.status, FT(instr));
|
|
if(!CheckArgs(fs, ft)) return;
|
|
CHECK_FPE(double, fd, fs * ft)
|
|
if(!CheckResult(fd)) return;
|
|
FGR_D<double>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::subs(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<float>(regs.cop0.status, FS(instr));
|
|
auto ft = FGR_T<float>(regs.cop0.status, FT(instr));
|
|
if(!CheckArgs(fs, ft)) return;
|
|
CHECK_FPE(float, fd, fs - ft)
|
|
if(!CheckResult(fd)) return;
|
|
FGR_D<float>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::subd(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<double>(regs.cop0.status, FS(instr));
|
|
auto ft = FGR_T<double>(regs.cop0.status, FT(instr));
|
|
if(!CheckArgs(fs, ft)) return;
|
|
CHECK_FPE(double, fd, fs - ft)
|
|
if(!CheckResult(fd)) return;
|
|
FGR_D<double>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::movs(u32 instr) {
|
|
movd(instr);
|
|
}
|
|
|
|
void Cop1::movd(u32 instr) {
|
|
if(!CheckFPUUsable<true>()) return;
|
|
FGR_D<double>(regs.cop0.status, FD(instr)) = FGR_S<double>(regs.cop0.status, FS(instr));
|
|
}
|
|
|
|
void Cop1::negs(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<float>(regs.cop0.status, FS(instr));
|
|
if(!CheckArg(fs)) return;
|
|
CHECK_FPE(float, fd, -fs)
|
|
if(!CheckResult(fd)) return;
|
|
FGR_D<float>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::negd(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<double>(regs.cop0.status, FS(instr));
|
|
if(!CheckArg(fs)) return;
|
|
CHECK_FPE(double, fd, -fs)
|
|
if(!CheckResult(fd)) return;
|
|
FGR_D<double>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::sqrts(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<float>(regs.cop0.status, FS(instr));
|
|
if(!CheckArg(fs)) return;
|
|
CHECK_FPE(float, fd, std::sqrt(fs))
|
|
if(!CheckResult(fd)) return;
|
|
FGR_D<float>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::sqrtd(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<double>(regs.cop0.status, FS(instr));
|
|
if(!CheckArg(fs)) return;
|
|
CHECK_FPE(double, fd, std::sqrt(fs))
|
|
if(!CheckResult(fd)) return;
|
|
FGR_D<double>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::roundls(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<float>(regs.cop0.status, FS(instr));
|
|
if(!CheckCVTArg<s64>(fs)) return;
|
|
CHECK_FPE(s64, fd, std::round(fs))
|
|
if((float)fd != fs && SetCauseInexact()) { FireException(); return; }
|
|
FGR_D<s64>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::roundld(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<double>(regs.cop0.status, FS(instr));
|
|
if(!CheckCVTArg<s64>(fs)) return;
|
|
CHECK_FPE(s64, fd, std::round(fs))
|
|
if((double)fd != fs && SetCauseInexact()) { FireException(); return; }
|
|
FGR_D<s64>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::roundws(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<float>(regs.cop0.status, FS(instr));
|
|
if(!CheckCVTArg<s32>(fs)) return;
|
|
CHECK_FPE_CONV(s32, fd, std::round(fs))
|
|
if(fd != fs && SetCauseInexact()) { FireException(); return; }
|
|
FGR_D<s32>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::roundwd(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<double>(regs.cop0.status, FS(instr));
|
|
if(!CheckCVTArg<s32>(fs)) return;
|
|
CHECK_FPE_CONV(s32, fd, std::round(fs))
|
|
if(fd != fs && SetCauseInexact()) { FireException(); return; }
|
|
FGR_D<s32>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::floorls(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<float>(regs.cop0.status, FS(instr));
|
|
if(!CheckCVTArg<s64>(fs)) return;
|
|
CHECK_FPE(s64, fd, std::floor(fs))
|
|
FGR_D<s64>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::floorld(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<double>(regs.cop0.status, FS(instr));
|
|
if(!CheckCVTArg<s64>(fs)) return;
|
|
CHECK_FPE(s64, fd, std::floor(fs))
|
|
FGR_D<s64>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::floorws(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<float>(regs.cop0.status, FS(instr));
|
|
if(!CheckCVTArg<s32>(fs)) return;
|
|
CHECK_FPE_CONV(s32, fd, std::floor(fs))
|
|
FGR_D<s32>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::floorwd(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<double>(regs.cop0.status, FS(instr));
|
|
if(!CheckCVTArg<s32>(fs)) return;
|
|
CHECK_FPE_CONV(s32, fd, std::floor(fs))
|
|
FGR_D<s32>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::truncws(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<float>(regs.cop0.status, FS(instr));
|
|
if(!CheckCVTArg<s32>(fs)) return;
|
|
CHECK_FPE_CONV(s32, fd, std::trunc(fs))
|
|
if((float)fd != fs && SetCauseInexact()) { FireException(); return; }
|
|
FGR_D<s32>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::truncwd(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<double>(regs.cop0.status, FS(instr));
|
|
if(!CheckCVTArg<s32>(fs)) return;
|
|
CHECK_FPE_CONV(s32, fd, std::trunc(fs))
|
|
if((double)fd != fs && SetCauseInexact()) { FireException(); return; }
|
|
FGR_D<s32>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::truncls(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<float>(regs.cop0.status, FS(instr));
|
|
if(!CheckCVTArg<s64>(fs)) return;
|
|
CHECK_FPE(s64, fd, std::trunc(fs))
|
|
if((float)fd != fs && SetCauseInexact()) { FireException(); return; }
|
|
FGR_D<s64>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
void Cop1::truncld(u32 instr) {
|
|
if(!CheckFPUUsable()) return;
|
|
auto fs = FGR_S<double>(regs.cop0.status, FS(instr));
|
|
if(!CheckCVTArg<s64>(fs)) return;
|
|
CHECK_FPE(s64, fd, std::trunc(fs))
|
|
if((double)fd != fs && SetCauseInexact()) { FireException(); return; }
|
|
FGR_D<s64>(regs.cop0.status, FD(instr)) = fd;
|
|
}
|
|
|
|
template<class T>
|
|
void Cop1::lwc1(T &cpu, Mem &mem, u32 instr) {
|
|
if constexpr(std::is_same_v<decltype(cpu), Interpreter&>) {
|
|
if(!CheckFPUUsable<true>()) return;
|
|
lwc1Interp(mem, instr);
|
|
} else if constexpr (std::is_same_v<decltype(cpu), JIT&>) {
|
|
lwc1JIT(cpu, mem, instr);
|
|
} else {
|
|
Util::panic("What the fuck did you just give me?!!");
|
|
}
|
|
}
|
|
|
|
template void Cop1::lwc1<Interpreter>(Interpreter&, Mem&, u32);
|
|
template void Cop1::lwc1<JIT>(JIT&, Mem&, u32);
|
|
|
|
template<class T>
|
|
void Cop1::swc1(T &cpu, Mem &mem, u32 instr) {
|
|
if constexpr(std::is_same_v<decltype(cpu), Interpreter&>) {
|
|
if(!CheckFPUUsable<true>()) return;
|
|
swc1Interp(mem, instr);
|
|
} else if constexpr (std::is_same_v<decltype(cpu), JIT&>) {
|
|
swc1JIT(cpu, mem, instr);
|
|
} else {
|
|
Util::panic("What the fuck did you just give me?!!");
|
|
}
|
|
}
|
|
|
|
template void Cop1::swc1<Interpreter>(Interpreter&, Mem&, u32);
|
|
template void Cop1::swc1<JIT>(JIT&, Mem&, u32);
|
|
|
|
template<class T>
|
|
void Cop1::ldc1(T &cpu, Mem &mem, u32 instr) {
|
|
if constexpr(std::is_same_v<decltype(cpu), Interpreter&>) {
|
|
if(!CheckFPUUsable<true>()) return;
|
|
ldc1Interp(mem, instr);
|
|
} else if constexpr (std::is_same_v<decltype(cpu), JIT&>) {
|
|
ldc1JIT(cpu, mem, instr);
|
|
} else {
|
|
Util::panic("What the fuck did you just give me?!!");
|
|
}
|
|
}
|
|
|
|
template void Cop1::ldc1<Interpreter>(Interpreter&, Mem&, u32);
|
|
template void Cop1::ldc1<JIT>(JIT&, Mem&, u32);
|
|
|
|
template<class T>
|
|
void Cop1::sdc1(T &cpu, Mem &mem, u32 instr) {
|
|
if constexpr(std::is_same_v<decltype(cpu), Interpreter&>) {
|
|
if(!CheckFPUUsable<true>()) return;
|
|
sdc1Interp(mem, instr);
|
|
} else if constexpr (std::is_same_v<decltype(cpu), JIT&>) {
|
|
sdc1JIT(cpu, mem, instr);
|
|
} else {
|
|
Util::panic("What the fuck did you just give me?!!");
|
|
}
|
|
}
|
|
|
|
template void Cop1::sdc1<Interpreter>(Interpreter&, Mem&, u32);
|
|
template void Cop1::sdc1<JIT>(JIT&, Mem&, u32);
|
|
|
|
void Cop1::lwc1Interp(Mem& mem, u32 instr) {
|
|
u64 addr = (s64)(s16)instr + regs.gpr[BASE(instr)];
|
|
|
|
u32 physical;
|
|
if(!regs.cop0.MapVAddr(Cop0::LOAD, addr, physical)) {
|
|
regs.cop0.HandleTLBException(addr);
|
|
regs.cop0.FireException(regs.cop0.GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, regs.oldPC);
|
|
} else {
|
|
u32 data = mem.Read<u32>(regs, physical);
|
|
FGR_T<u32>(regs.cop0.status, FT(instr)) = data;
|
|
}
|
|
}
|
|
|
|
void Cop1::swc1Interp(Mem& mem, u32 instr) {
|
|
u64 addr = (s64)(s16)instr + regs.gpr[BASE(instr)];
|
|
|
|
u32 physical;
|
|
if(!regs.cop0.MapVAddr(Cop0::STORE, addr, physical)) {
|
|
regs.cop0.HandleTLBException(addr);
|
|
regs.cop0.FireException(regs.cop0.GetTLBExceptionCode(regs.cop0.tlbError, Cop0::STORE), 0, regs.oldPC);
|
|
} else {
|
|
mem.Write<u32>(regs, physical, FGR_T<u32>(regs.cop0.status, FT(instr)));
|
|
}
|
|
}
|
|
|
|
void Cop1::unimplemented() {
|
|
if(!CheckFPUUsable()) return;
|
|
fcr31.cause_unimplemented_operation = true;
|
|
FireException();
|
|
}
|
|
|
|
void Cop1::ldc1Interp(Mem& mem, u32 instr) {
|
|
u64 addr = (s64)(s16)instr + regs.gpr[BASE(instr)];
|
|
|
|
u32 physical;
|
|
if(!regs.cop0.MapVAddr(Cop0::LOAD, addr, physical)) {
|
|
regs.cop0.HandleTLBException(addr);
|
|
regs.cop0.FireException(regs.cop0.GetTLBExceptionCode(regs.cop0.tlbError, Cop0::LOAD), 0, regs.oldPC);
|
|
} else {
|
|
u64 data = mem.Read<u64>(regs, physical);
|
|
FGR_T<u64>(regs.cop0.status, FT(instr)) = data;
|
|
}
|
|
}
|
|
|
|
void Cop1::sdc1Interp(Mem& mem, u32 instr) {
|
|
u64 addr = (s64)(s16)instr + regs.gpr[BASE(instr)];
|
|
|
|
u32 physical;
|
|
if(!regs.cop0.MapVAddr(Cop0::STORE, addr, physical)) {
|
|
regs.cop0.HandleTLBException(addr);
|
|
regs.cop0.FireException(regs.cop0.GetTLBExceptionCode(regs.cop0.tlbError, Cop0::STORE), 0, regs.oldPC);
|
|
} else {
|
|
mem.Write(regs, physical, FGR_T<u64>(regs.cop0.status, FT(instr)));
|
|
}
|
|
}
|
|
|
|
void Cop1::mfc1(u32 instr) {
|
|
if(!CheckFPUUsable<true>()) return;
|
|
regs.gpr[RT(instr)] = FGR_T<s32>(regs.cop0.status, FS(instr));
|
|
}
|
|
|
|
void Cop1::dmfc1(u32 instr) {
|
|
if(!CheckFPUUsable<true>()) return;
|
|
regs.gpr[RT(instr)] = FGR_S<s64>(regs.cop0.status, FS(instr));
|
|
}
|
|
|
|
void Cop1::mtc1(u32 instr) {
|
|
if(!CheckFPUUsable<true>()) return;
|
|
FGR_T<s32>(regs.cop0.status, FS(instr)) = regs.gpr[RT(instr)];
|
|
}
|
|
|
|
void Cop1::dmtc1(u32 instr) {
|
|
if(!CheckFPUUsable<true>()) return;
|
|
FGR_S<u64>(regs.cop0.status, FS(instr)) = regs.gpr[RT(instr)];
|
|
}
|
|
|
|
}
|