#include #include #include #include #include #include #include namespace n64 { FORCE_INLINE bool FireFPUException(Registers& regs) { FCR31& fcr31 = regs.cop1.fcr31; u32 enable = fcr31.enable | (1 << 5); if(fcr31.cause & enable) { FireException(regs, ExceptionCode::FloatingPointError, 0, regs.oldPC); return true; } return false; } #define CheckFPUException() do { if(FireFPUException(regs)) { return; } } while(0) #define CheckRound(a, b) do { if ((a) != (b)) { fcr31.cause_inexact_operation = true; if(!fcr31.enable_inexact_operation) { fcr31.flag_inexact_operation = true; } } CheckFPUException(); } while(0) #define SetCauseUnimplemented() do { regs.cop1.fcr31.cause_unimplemented_operation = true; } while(0) #define SetCauseUnderflow() do { \ regs.cop1.fcr31.cause_underflow = true; \ if(!regs.cop1.fcr31.enable_underflow) { \ regs.cop1.fcr31.flag_underflow = true; \ } \ } while(0) #define SetCauseInexact() do { \ regs.cop1.fcr31.cause_inexact_operation = true; \ if(!regs.cop1.fcr31.enable_inexact_operation) { \ regs.cop1.fcr31.flag_inexact_operation = true; \ } \ } while(0) #define SetCauseDivisionByZero() do { \ regs.cop1.fcr31.cause_division_by_zero = true; \ if(!regs.cop1.fcr31.enable_division_by_zero) { \ regs.cop1.fcr31.flag_division_by_zero = true; \ } \ } while(0) #define SetCauseOverflow() do { \ regs.cop1.fcr31.cause_overflow = true; \ if(!regs.cop1.fcr31.enable_overflow) { \ regs.cop1.fcr31.flag_overflow = true; \ } \ } while(0) #define SetCauseInvalid() do { \ regs.cop1.fcr31.cause_invalid_operation = true; \ if(!regs.cop1.fcr31.enable_invalid_operation) { \ regs.cop1.fcr31.flag_invalid_operation = true; \ } \ } 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; } return og; } #define CheckCVTArg(f) do { SetCauseByArgCVT(regs, f); CheckFPUException(); } while(0) #define CheckArg(f) do { SetCauseByArg(regs, f); CheckFPUException(); } while(0) #define PUSHROUNDING int orig_round = PushRoundingMode(regs.cop1.fcr31) #define POPROUNDING fesetround(orig_round) #define OP_CheckExcept(op) do { PUSHROUNDING; feclearexcept(FE_ALL_EXCEPT); op; SetFPUCauseRaised(regs, fetestexcept(FE_ALL_EXCEPT)); POPROUNDING; } while(0) #define CVT_OP_CheckExcept(op) do { feclearexcept(FE_ALL_EXCEPT); op; SetFPUCauseCVTRaised(regs, fetestexcept(FE_ALL_EXCEPT)); CheckFPUException(); } while(0) #define OP(T, op) do { \ CheckFPUUsable(); \ auto fs = GetFGR_FS(regs.cop0, FS(instr)); \ auto ft = GetFGR_FT(FT(instr)); \ CheckArg(fs); \ CheckArg(ft); \ T result; \ OP_CheckExcept({result = (op);}); \ CheckResult(result); \ SetFGR_Raw(FD(instr), result); \ } while(0) template FORCE_INLINE void SetCauseByArgCVT(Registers& regs, T f) { T min, max; if constexpr(std::is_same_v) { min = -2147483648.0f; max = 2147483648.0f; } else if constexpr(std::is_same_v) { min = -9007199254740992.000000; max = 9007199254740992.000000; } switch (std::fpclassify(f)) { case FP_NAN: case FP_INFINITE: case FP_SUBNORMAL: SetCauseUnimplemented(); break; case FP_NORMAL: // Check overflow if (f >= max || f <= min) { SetCauseUnimplemented(); } break; case FP_ZERO: break; // Fine } } FORCE_INLINE void SetFPUCauseRaised(Registers& regs, int raised) { if (raised == 0) { return; } if (raised & FE_UNDERFLOW) { if (!regs.cop1.fcr31.fs || regs.cop1.fcr31.enable_underflow || regs.cop1.fcr31.enable_inexact_operation) { SetCauseUnimplemented(); return; } else { SetCauseUnderflow(); } } if (raised & FE_INEXACT) { SetCauseInexact(); } if (raised & FE_DIVBYZERO) { SetCauseDivisionByZero(); } if (raised & FE_OVERFLOW) { SetCauseOverflow(); } if (raised & FE_INVALID) { SetCauseInvalid(); } } FORCE_INLINE void SetFPUCauseCVTRaised(Registers& regs, int raised) { if(raised & FE_INVALID) { SetCauseUnimplemented(); return; } SetFPUCauseRaised(regs, raised); } template FORCE_INLINE void SetCauseByArg(Registers& regs, T f) { int c = std::fpclassify(f); switch(c) { case FP_NAN: if(isqnan(f)) { SetCauseInvalid(); } else { SetCauseUnimplemented(); } break; case FP_SUBNORMAL: SetCauseUnimplemented(); break; case FP_INFINITE: case FP_ZERO: case FP_NORMAL: break; // No-op, these are fine. default: Util::panic("Unknown floating point classification: {}", c); } } #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 void SetCauseOnResult(Registers& regs, T& d) { Cop1& cop1 = regs.cop1; int classification = std::fpclassify(d); T magic, min; if constexpr(std::is_same_v) { u32 c = 0x7FBFFFFF; magic = U32_TO_F(c); min = FLT_MIN; } else if constexpr(std::is_same_v) { u64 c = 0x7FF7FFFFFFFFFFFF; magic = U64_TO_D(c); min = DBL_MIN; } switch (classification) { case FP_NAN: d = magic; // set result to sNAN break; case FP_SUBNORMAL: if (!cop1.fcr31.fs || cop1.fcr31.enable_underflow || cop1.fcr31.enable_inexact_operation) { SetCauseUnimplemented(); } else { // Since the if statement checks for the corresponding enable bits, it's safe to turn these cause bits on here. SetCauseUnderflow(); SetCauseInexact(); switch (cop1.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: {}", classification); } } #define CheckResult(f) do { SetCauseOnResult(regs, (f)); CheckFPUException(); } while(0) #define any_unordered(fs, ft) (std::isnan(fs) || std::isnan(ft)) template FORCE_INLINE bool isnan(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 isnan"); } } 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"); } } #define checknanregs(fs, ft) do { \ if(isnan(fs) || isnan(ft)) { \ regs.cop1.fcr31.cause_invalid_operation = true; \ if(!regs.cop1.fcr31.enable_invalid_operation) { \ regs.cop1.fcr31.flag_invalid_operation = true; \ } \ CheckFPUException(); \ } \ } while(0) #define checkqnanregs(fs, ft) do { \ if(isqnan(fs) || isqnan(ft)) { \ regs.cop1.fcr31.cause_invalid_operation = true; \ if(!regs.cop1.fcr31.enable_invalid_operation) { \ regs.cop1.fcr31.flag_invalid_operation = true; \ } \ CheckFPUException(); \ } \ } while(0) void Cop1::absd(Registers& regs, u32 instr) { OP(double, std::abs(fs)); } void Cop1::abss(Registers& regs, u32 instr) { OP(float, std::abs(fs)); } void Cop1::adds(Registers& regs, u32 instr) { OP(float, fs + ft); } void Cop1::addd(Registers& regs, u32 instr) { OP(double, fs + ft); } void Cop1::ceills(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckCVTArg(fs); s64 result; CVT_OP_CheckExcept({ result = std::ceil(fs); }); CheckRound(fs, result); SetFGR(FD(instr), result); } void Cop1::ceilws(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckCVTArg(fs); s32 result; CVT_OP_CheckExcept({ result = std::ceil(fs); }); CheckRound(fs, result); SetFGR(FD(instr), result); } void Cop1::ceilld(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckCVTArg(fs); s64 result; CVT_OP_CheckExcept({ result = std::ceil(fs); }); CheckRound(fs, result); SetFGR(FD(instr), result); } void Cop1::ceilwd(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckCVTArg(fs); s32 result; CVT_OP_CheckExcept({ result = std::ceil(fs); }); CheckRound(fs, result); SetFGR(FD(instr), result); } void Cop1::cfc1(Registers& regs, u32 instr) const { CheckFPUUsable_PreserveCause(); 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(Registers& regs, u32 instr) { CheckFPUUsable_PreserveCause(); u8 fs = RD(instr); u32 val = regs.gpr[RT(instr)]; switch(fs) { case 0: break; case 31: { fcr31.write(val); CheckFPUException(); } break; default: Util::panic("Undefined CTC1 with rd != 0 or 31"); } } void Cop1::cvtds(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckArg(fs); double result; OP_CheckExcept({ result = double(fs); }); CheckResult(result); SetFGR_Raw(FD(instr), result); } void Cop1::cvtsd(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckArg(fs); float result; OP_CheckExcept({ result = float(fs); }); CheckResult(result); SetFGR_Raw(FD(instr), result); } void Cop1::cvtwd(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckCVTArg(fs); s32 result; PUSHROUNDING; CVT_OP_CheckExcept({ result = s32(fs); }); POPROUNDING; CheckRound(fs, result); SetFGR(FD(instr), result); } void Cop1::cvtws(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckCVTArg(fs); s32 result; PUSHROUNDING; CVT_OP_CheckExcept({ result = s32(fs); }); POPROUNDING; CheckRound(fs, result); SetFGR(FD(instr), result); } void Cop1::cvtls(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckCVTArg(fs); s64 result; PUSHROUNDING; CVT_OP_CheckExcept({ result = std::rint(fs); }); POPROUNDING; CheckRound(fs, result); SetFGR(FD(instr), result); } void Cop1::cvtsl(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FR(regs.cop0, FS(instr)); if (fs >= s64(0x0080000000000000) || fs < s64(0xff80000000000000)) { SetCauseUnimplemented(); CheckFPUException(); } float result; OP_CheckExcept({ result = float(fs); }); CheckResult(result); SetFGR_Raw(FD(instr), result); } void Cop1::cvtdw(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); double result; OP_CheckExcept({ result = double(fs); }); CheckResult(result); SetFGR_Raw(FD(instr), result); } void Cop1::cvtsw(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); float result; OP_CheckExcept({ result = float(fs); }); CheckResult(result); SetFGR_Raw(FD(instr), result); } void Cop1::cvtdl(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FR(regs.cop0, FS(instr)); if (fs >= s64(0x0080000000000000) || fs < s64(0xff80000000000000)) { SetCauseUnimplemented(); CheckFPUException(); } double result; OP_CheckExcept({ result = double(fs); }); CheckResult(result); SetFGR_Raw(FD(instr), result); } void Cop1::cvtld(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckCVTArg(fs); s64 result; PUSHROUNDING; CVT_OP_CheckExcept({ result = std::rint(fs); }); POPROUNDING; CheckRound(fs, result); SetFGR(FD(instr), result); } template inline bool CalculateCondition(T fs, T ft, CompConds cond) { switch(cond) { case F: case SF: return false; case UN: case NGLE: return any_unordered(fs, ft); case EQ: case SEQ: return fs == ft; case UEQ: case NGL: return fs == ft || any_unordered(fs, ft); case OLT: case LT: return fs < ft; case ULT: case NGE: return fs < ft || any_unordered(fs, ft); case OLE: case LE: return fs <= ft; case ULE: case NGT: return fs <= ft || any_unordered(fs, ft); } } template inline void CheckInvalidRegs(Registers& regs, T fs, T ft, CompConds cond) { switch(cond) { case F ... ULE: checkqnanregs(fs, ft); break; case SF ... NGT: checknanregs(fs, ft); break; } } template void Cop1::ccond(Registers& regs, u32 instr, CompConds cond) { CheckFPUUsable(); T fs = GetFGR_FS(regs.cop0, FS(instr)); T ft = GetFGR_FT(FT(instr)); CheckInvalidRegs(regs, fs, ft, cond); fcr31.compare = CalculateCondition(fs, ft, cond); } template void Cop1::ccond(Registers& regs, u32 instr, CompConds cond); template void Cop1::ccond(Registers& regs, u32 instr, CompConds cond); void Cop1::divs(Registers ®s, u32 instr) { OP(float, fs / ft); } void Cop1::divd(Registers ®s, u32 instr) { OP(double, fs / ft); } void Cop1::muls(Registers ®s, u32 instr) { OP(float, fs * ft); } void Cop1::muld(Registers& regs, u32 instr) { OP(double, fs * ft); } void Cop1::subs(Registers ®s, u32 instr) { OP(float, fs - ft); } void Cop1::subd(Registers ®s, u32 instr) { OP(double, fs - ft); } void Cop1::movs(Registers& regs, u32 instr) { CheckFPUUsable_PreserveCause(); auto val = GetFGR_FR(regs.cop0, FS(instr)); SetFGR(FD(instr), val); } void Cop1::movd(Registers& regs, u32 instr) { CheckFPUUsable_PreserveCause(); auto val = GetFGR_FS(regs.cop0, FS(instr)); SetFGR_Raw(FD(instr), val); } void Cop1::negs(Registers ®s, u32 instr) { OP(float, -fs); } void Cop1::negd(Registers ®s, u32 instr) { OP(double, -fs); } void Cop1::sqrts(Registers ®s, u32 instr) { OP(float, std::sqrt(fs)); } void Cop1::sqrtd(Registers ®s, u32 instr) { OP(double, std::sqrt(fs)); } void Cop1::roundls(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckCVTArg(fs); s64 result; CVT_OP_CheckExcept({ result = std::nearbyint(fs); }); CheckRound(fs, result); SetFGR(FD(instr), result); } void Cop1::roundld(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckCVTArg(fs); s64 result; CVT_OP_CheckExcept({ result = std::nearbyint(fs); }); CheckRound(fs, result); SetFGR(FD(instr), result); } void Cop1::roundws(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckCVTArg(fs); s32 result; CVT_OP_CheckExcept({ result = std::nearbyint(fs); }); CheckRound(fs, result); SetFGR(FD(instr), result); } void Cop1::roundwd(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckCVTArg(fs); s32 result; CVT_OP_CheckExcept({ result = std::nearbyint(fs); }); CheckRound(fs, result); SetFGR(FD(instr), result); } void Cop1::floorls(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckCVTArg(fs); s64 result; CVT_OP_CheckExcept({ result = std::floor(fs); }); CheckRound(fs, result); SetFGR(FD(instr), result); } void Cop1::floorld(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckCVTArg(fs); s64 result; CVT_OP_CheckExcept({ result = std::floor(fs); }); CheckRound(fs, result); SetFGR(FD(instr), result); } void Cop1::floorws(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckCVTArg(fs); s32 result; CVT_OP_CheckExcept({ result = std::floor(fs); }); CheckRound(fs, result); SetFGR(FD(instr), result); } void Cop1::floorwd(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckCVTArg(fs); s32 result; CVT_OP_CheckExcept({ result = std::floor(fs); }); CheckRound(fs, result); SetFGR(FD(instr), result); } void Cop1::truncws(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckCVTArg(fs); s32 result; CVT_OP_CheckExcept({ result = std::trunc(fs); }); CheckRound(fs, result); SetFGR(FD(instr), result); } void Cop1::truncwd(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckCVTArg(fs); s32 result; CVT_OP_CheckExcept({ result = std::trunc(fs); }); CheckRound(fs, result); SetFGR(FD(instr), result); } void Cop1::truncls(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckCVTArg(fs); s64 result; CVT_OP_CheckExcept({ result = std::trunc(fs); }); CheckRound(fs, result); SetFGR(FD(instr), result); } void Cop1::truncld(Registers& regs, u32 instr) { CheckFPUUsable(); auto fs = GetFGR_FS(regs.cop0, FS(instr)); CheckCVTArg(fs); s64 result; CVT_OP_CheckExcept({ result = std::trunc(fs); }); CheckRound(fs, result); SetFGR(FD(instr), result); } template void Cop1::lwc1(T &cpu, Mem &mem, u32 instr) { if constexpr(std::is_same_v) { Registers& regs = cpu.regs; CheckFPUUsable_PreserveCause(); lwc1Interp(cpu.regs, mem, instr); } else if constexpr (std::is_same_v) { lwc1JIT(cpu, mem, instr); } else { Util::panic("What the fuck did you just give me?!!"); } } template void Cop1::lwc1(Interpreter&, Mem&, u32); template void Cop1::lwc1(JIT&, Mem&, u32); template void Cop1::swc1(T &cpu, Mem &mem, u32 instr) { if constexpr(std::is_same_v) { Registers& regs = cpu.regs; CheckFPUUsable_PreserveCause(); swc1Interp(cpu.regs, mem, instr); } else if constexpr (std::is_same_v) { swc1JIT(cpu, mem, instr); } else { Util::panic("What the fuck did you just give me?!!"); } } template void Cop1::swc1(Interpreter&, Mem&, u32); template void Cop1::swc1(JIT&, Mem&, u32); template void Cop1::ldc1(T &cpu, Mem &mem, u32 instr) { if constexpr(std::is_same_v) { Registers& regs = cpu.regs; CheckFPUUsable_PreserveCause(); ldc1Interp(cpu.regs, mem, instr); } else if constexpr (std::is_same_v) { ldc1JIT(cpu, mem, instr); } else { Util::panic("What the fuck did you just give me?!!"); } } template void Cop1::ldc1(Interpreter&, Mem&, u32); template void Cop1::ldc1(JIT&, Mem&, u32); template void Cop1::sdc1(T &cpu, Mem &mem, u32 instr) { if constexpr(std::is_same_v) { Registers& regs = cpu.regs; CheckFPUUsable_PreserveCause(); sdc1Interp(cpu.regs, mem, instr); } else if constexpr (std::is_same_v) { sdc1JIT(cpu, mem, instr); } else { Util::panic("What the fuck did you just give me?!!"); } } template void Cop1::sdc1(Interpreter&, Mem&, u32); template void Cop1::sdc1(JIT&, Mem&, u32); void Cop1::lwc1Interp(Registers& regs, Mem& mem, u32 instr) { u64 addr = (s64)(s16)instr + regs.gpr[BASE(instr)]; u32 physical; if(!MapVAddr(regs, LOAD, addr, physical)) { HandleTLBException(regs, addr); FireException(regs, GetTLBExceptionCode(regs.cop0.tlbError, LOAD), 0, regs.oldPC); } else { u32 data = mem.Read32(regs, physical); SetFGR_FR(regs.cop0, FT(instr), data); } } void Cop1::swc1Interp(Registers& regs, Mem& mem, u32 instr) { u64 addr = (s64)(s16)instr + regs.gpr[BASE(instr)]; u32 physical; if(!MapVAddr(regs, STORE, addr, physical)) { HandleTLBException(regs, addr); FireException(regs, GetTLBExceptionCode(regs.cop0.tlbError, STORE), 0, regs.oldPC); } else { mem.Write32(regs, physical, GetFGR_FR(regs.cop0, FT(instr))); } } void Cop1::unimplemented(Registers& regs) { CheckFPUUsable(); fcr31.cause_unimplemented_operation = true; FireFPUException(regs); } void Cop1::ldc1Interp(Registers& regs, Mem& mem, u32 instr) { u64 addr = (s64)(s16)instr + regs.gpr[BASE(instr)]; u32 physical; if(!MapVAddr(regs, LOAD, addr, physical)) { HandleTLBException(regs, addr); FireException(regs, GetTLBExceptionCode(regs.cop0.tlbError, LOAD), 0, regs.oldPC); } else { u64 data = mem.Read64(regs, physical); SetFGR_FR(regs.cop0, FT(instr), data); } } void Cop1::sdc1Interp(Registers& regs, Mem& mem, u32 instr) { u64 addr = (s64)(s16)instr + regs.gpr[BASE(instr)]; u32 physical; if(!MapVAddr(regs, STORE, addr, physical)) { HandleTLBException(regs, addr); FireException(regs, GetTLBExceptionCode(regs.cop0.tlbError, STORE), 0, regs.oldPC); } else { mem.Write64(regs, physical, GetFGR_FR(regs.cop0, FT(instr))); } } void Cop1::mfc1(Registers& regs, u32 instr) { CheckFPUUsable_PreserveCause(); regs.gpr[RT(instr)] = (s32)GetFGR_FR(regs.cop0, FS(instr)); } void Cop1::dmfc1(Registers& regs, u32 instr) { CheckFPUUsable_PreserveCause(); regs.gpr[RT(instr)] = (s64)GetFGR_FR(regs.cop0, FS(instr)); } void Cop1::mtc1(Registers& regs, u32 instr) { CheckFPUUsable_PreserveCause(); SetFGR_FR(regs.cop0, FS(instr), regs.gpr[RT(instr)]); } void Cop1::dmtc1(Registers& regs, u32 instr) { CheckFPUUsable_PreserveCause(); SetFGR_FR(regs.cop0, FS(instr), regs.gpr[RT(instr)]); } }