From a8f81b428934b34797aa5a143a19569fe09d5b63 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 7 Sep 2023 17:38:46 -0700 Subject: [PATCH 1/6] arm64jit: Add breakpoints/memchecks. --- Core/MIPS/ARM64/Arm64IRCompSystem.cpp | 28 +++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64IRCompSystem.cpp b/Core/MIPS/ARM64/Arm64IRCompSystem.cpp index 8b2af1734a..282d4fd2ef 100644 --- a/Core/MIPS/ARM64/Arm64IRCompSystem.cpp +++ b/Core/MIPS/ARM64/Arm64IRCompSystem.cpp @@ -24,6 +24,7 @@ #include "Core/HLE/HLE.h" #include "Core/HLE/ReplaceTables.h" #include "Core/MemMap.h" +#include "Core/MIPS/IR/IRInterpreter.h" #include "Core/MIPS/ARM64/Arm64IRJit.h" #include "Core/MIPS/ARM64/Arm64IRRegCache.h" @@ -84,14 +85,37 @@ void Arm64JitBackend::CompIR_Breakpoint(IRInst inst) { switch (inst.op) { case IROp::Breakpoint: - case IROp::MemoryCheck: - CompIR_Generic(inst); + FlushAll(); + // Note: the constant could be a delay slot. + MOVI2R(W0, inst.constant); + QuickCallFunction(SCRATCH2_64, &IRRunBreakpoint); break; + case IROp::MemoryCheck: + { + ARM64Reg addrBase = regs_.MapGPR(inst.src1); + FlushAll(); + ADDI2R(W1, addrBase, inst.constant, SCRATCH1); + MovFromPC(W0); + ADDI2R(W0, W0, inst.dest, SCRATCH1); + QuickCallFunction(SCRATCH2_64, &IRRunMemCheck); + break; + } + default: INVALIDOP; break; } + + // Both return a flag on whether to bail out. + ptrdiff_t distance = dispatcherCheckCoreState_ - GetCodePointer(); + if (distance >= -0x100000 && distance < 0x100000) { + CBNZ(W0, dispatcherCheckCoreState_); + } else { + FixupBranch keepOnKeepingOn = CBZ(W0); + B(dispatcherCheckCoreState_); + SetJumpTarget(keepOnKeepingOn); + } } void Arm64JitBackend::CompIR_System(IRInst inst) { From a21a882add4e2a3a207e1b19c56330d985321371 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 7 Sep 2023 17:55:06 -0700 Subject: [PATCH 2/6] arm64jit: Implement a few vector ops. --- Core/MIPS/ARM64/Arm64IRCompVec.cpp | 68 +++++++++++++++++++++++++++--- 1 file changed, 63 insertions(+), 5 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64IRCompVec.cpp b/Core/MIPS/ARM64/Arm64IRCompVec.cpp index a75e451126..39613c4869 100644 --- a/Core/MIPS/ARM64/Arm64IRCompVec.cpp +++ b/Core/MIPS/ARM64/Arm64IRCompVec.cpp @@ -581,8 +581,15 @@ void Arm64JitBackend::CompIR_VecClamp(IRInst inst) { switch (inst.op) { case IROp::Vec4ClampToZero: + regs_.Map(inst); + fp_.MOVI(32, EncodeRegToQuad(SCRATCHF1), 0); + fp_.SMAX(32, regs_.FQ(inst.dest), regs_.FQ(inst.src1), EncodeRegToQuad(SCRATCHF1)); + break; + case IROp::Vec2ClampToZero: - CompIR_Generic(inst); + regs_.Map(inst); + fp_.MOVI(32, EncodeRegToDouble(SCRATCHF1), 0); + fp_.SMAX(32, regs_.FD(inst.dest), regs_.FD(inst.src1), EncodeRegToDouble(SCRATCHF1)); break; default: @@ -625,27 +632,78 @@ void Arm64JitBackend::CompIR_VecPack(IRInst inst) { switch (inst.op) { case IROp::Vec2Unpack16To31: - case IROp::Vec4Pack32To8: - case IROp::Vec2Pack31To16: case IROp::Vec4Unpack8To32: case IROp::Vec2Unpack16To32: case IROp::Vec4DuplicateUpperBitsAndShift1: - case IROp::Vec4Pack31To8: CompIR_Generic(inst); break; + case IROp::Vec2Pack31To16: + // Same as Vec2Pack32To16, but we shift left 1 first to nuke the sign bit. + if (Overlap(inst.dest, 1, inst.src1, 2)) { + regs_.MapVec2(inst.src1, MIPSMap::DIRTY); + fp_.SHL(32, EncodeRegToDouble(SCRATCHF1), regs_.FD(inst.src1), 1); + fp_.UZP2(16, EncodeRegToDouble(SCRATCHF1), EncodeRegToDouble(SCRATCHF1), EncodeRegToDouble(SCRATCHF1)); + fp_.INS(32, regs_.FD(inst.dest & ~1), inst.dest & 1, EncodeRegToDouble(SCRATCHF1), 0); + } else { + regs_.Map(inst); + fp_.SHL(32, regs_.FD(inst.dest), regs_.FD(inst.src1), 1); + fp_.UZP2(16, regs_.FD(inst.dest), regs_.FD(inst.dest), regs_.FD(inst.dest)); + } + break; + case IROp::Vec2Pack32To16: // Viewed as 16 bit lanes: xAxB -> AB00... that's UZP2. if (Overlap(inst.dest, 1, inst.src1, 2)) { regs_.MapVec2(inst.src1, MIPSMap::DIRTY); fp_.UZP2(16, EncodeRegToDouble(SCRATCHF1), regs_.FD(inst.src1), regs_.FD(inst.src1)); - fp_.INS(32, regs_.FD(inst.dest), inst.dest & 1, EncodeRegToDouble(SCRATCHF1), 0); + fp_.INS(32, regs_.FD(inst.dest & ~1), inst.dest & 1, EncodeRegToDouble(SCRATCHF1), 0); } else { regs_.Map(inst); fp_.UZP2(16, regs_.FD(inst.dest), regs_.FD(inst.src1), regs_.FD(inst.src1)); } break; + case IROp::Vec4Pack31To8: + if (Overlap(inst.dest, 1, inst.src1, 4)) { + regs_.MapVec4(inst.src1, MIPSMap::DIRTY); + } else { + regs_.Map(inst); + } + + // Viewed as 8-bit lanes, after a shift by 23: AxxxBxxxCxxxDxxx. + // So: UZP1 -> AxBxCxDx -> UZP1 again -> ABCD + fp_.USHR(32, EncodeRegToQuad(SCRATCHF1), regs_.FQ(inst.src1), 23); + fp_.UZP1(8, EncodeRegToQuad(SCRATCHF1), EncodeRegToQuad(SCRATCHF1), EncodeRegToQuad(SCRATCHF1)); + // Second one directly to dest, if we can. + if (Overlap(inst.dest, 1, inst.src1, 4)) { + fp_.UZP1(8, EncodeRegToQuad(SCRATCHF1), EncodeRegToQuad(SCRATCHF1), EncodeRegToQuad(SCRATCHF1)); + fp_.INS(32, regs_.FQ(inst.dest & ~3), inst.dest & 3, EncodeRegToQuad(SCRATCHF1), 0); + } else { + fp_.UZP1(8, regs_.FQ(inst.dest), EncodeRegToQuad(SCRATCHF1), EncodeRegToQuad(SCRATCHF1)); + } + break; + + case IROp::Vec4Pack32To8: + if (Overlap(inst.dest, 1, inst.src1, 4)) { + regs_.MapVec4(inst.src1, MIPSMap::DIRTY); + } else { + regs_.Map(inst); + } + + // Viewed as 8-bit lanes, after a shift by 24: AxxxBxxxCxxxDxxx. + // Same as Vec4Pack31To8, just a different shift. + fp_.USHR(32, EncodeRegToQuad(SCRATCHF1), regs_.FQ(inst.src1), 24); + fp_.UZP1(8, EncodeRegToQuad(SCRATCHF1), EncodeRegToQuad(SCRATCHF1), EncodeRegToQuad(SCRATCHF1)); + // Second one directly to dest, if we can. + if (Overlap(inst.dest, 1, inst.src1, 4)) { + fp_.UZP1(8, EncodeRegToQuad(SCRATCHF1), EncodeRegToQuad(SCRATCHF1), EncodeRegToQuad(SCRATCHF1)); + fp_.INS(32, regs_.FQ(inst.dest & ~3), inst.dest & 3, EncodeRegToQuad(SCRATCHF1), 0); + } else { + fp_.UZP1(8, regs_.FQ(inst.dest), EncodeRegToQuad(SCRATCHF1), EncodeRegToQuad(SCRATCHF1)); + } + break; + default: INVALIDOP; break; From e03ae26d20526c9ccc30f2c14cff1cc32a239fe7 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 7 Sep 2023 18:53:50 -0700 Subject: [PATCH 3/6] arm64jit: Implement vuc2i. --- Core/MIPS/ARM64/Arm64IRCompVec.cpp | 22 ++++++++++++++++++---- Core/MIPS/MIPSIntVFPU.cpp | 9 +++++---- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Core/MIPS/ARM64/Arm64IRCompVec.cpp b/Core/MIPS/ARM64/Arm64IRCompVec.cpp index 39613c4869..8f80af9af7 100644 --- a/Core/MIPS/ARM64/Arm64IRCompVec.cpp +++ b/Core/MIPS/ARM64/Arm64IRCompVec.cpp @@ -631,11 +631,19 @@ void Arm64JitBackend::CompIR_VecPack(IRInst inst) { CONDITIONAL_DISABLE; switch (inst.op) { - case IROp::Vec2Unpack16To31: - case IROp::Vec4Unpack8To32: - case IROp::Vec2Unpack16To32: case IROp::Vec4DuplicateUpperBitsAndShift1: - CompIR_Generic(inst); + // This operation swizzles the high 8 bits and converts to a signed int. + // It's always after Vec4Unpack8To32. + // 000A000B000C000D -> AAAABBBBCCCCDDDD and then shift right one (to match INT_MAX.) + regs_.Map(inst); + // First, USHR+ORR to get 0A0A0B0B0C0C0D0D. + fp_.USHR(32, EncodeRegToQuad(SCRATCHF1), regs_.FQ(inst.src1), 16); + fp_.ORR(EncodeRegToQuad(SCRATCHF1), EncodeRegToQuad(SCRATCHF1), regs_.FQ(inst.src1)); + // Now again, but by 8. + fp_.USHR(32, regs_.FQ(inst.dest), EncodeRegToQuad(SCRATCHF1), 8); + fp_.ORR(regs_.FQ(inst.dest), regs_.FQ(inst.dest), EncodeRegToQuad(SCRATCHF1)); + // Finally, shift away the sign. The goal is to saturate 0xFF -> 0x7FFFFFFF. + fp_.USHR(32, regs_.FQ(inst.dest), regs_.FQ(inst.dest), 1); break; case IROp::Vec2Pack31To16: @@ -704,6 +712,12 @@ void Arm64JitBackend::CompIR_VecPack(IRInst inst) { } break; + case IROp::Vec2Unpack16To31: + case IROp::Vec4Unpack8To32: + case IROp::Vec2Unpack16To32: + CompIR_Generic(inst); + break; + default: INVALIDOP; break; diff --git a/Core/MIPS/MIPSIntVFPU.cpp b/Core/MIPS/MIPSIntVFPU.cpp index 1ceca72630..35d3caa56a 100644 --- a/Core/MIPS/MIPSIntVFPU.cpp +++ b/Core/MIPS/MIPSIntVFPU.cpp @@ -926,10 +926,8 @@ namespace MIPSInt switch ((op >> 16) & 3) { case 0: // vuc2i // Quad is the only option. - // This operation is weird. This particular way of working matches hw but does not - // seem quite sane. - // I guess it's used for fixed-point math, and fills more bits to facilitate - // conversion between 8-bit and 16-bit values. But then why not do it in vc2i? + // This converts 8-bit unsigned to 31-bit signed, swizzling to saturate. + // Similar to 5-bit to 8-bit color swizzling, but clamping to INT_MAX. { u32 value = s[0]; for (int i = 0; i < 4; i++) { @@ -942,6 +940,8 @@ namespace MIPSInt case 1: // vc2i // Quad is the only option + // Unlike vuc2i, the source and destination are signed so there is no shift. + // It lacks the swizzle because of negative values. { u32 value = s[0]; d[0] = (value & 0xFF) << 24; @@ -953,6 +953,7 @@ namespace MIPSInt break; case 2: // vus2i + // Note: for some reason, this skips swizzle such that 0xFFFF -> 0x7FFF8000 unlike vuc2i. oz = V_Pair; switch (sz) { case V_Quad: From c523273d512cefc0b70b4cea50d49dae6cb4c8b7 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 7 Sep 2023 19:53:07 -0700 Subject: [PATCH 4/6] arm64jit: Implement vector unpacks. --- Common/Arm64Emitter.cpp | 11 +++++++ Common/Arm64Emitter.h | 4 +++ Core/MIPS/ARM64/Arm64IRCompVec.cpp | 47 ++++++++++++++++++++++++++++-- 3 files changed, 60 insertions(+), 2 deletions(-) diff --git a/Common/Arm64Emitter.cpp b/Common/Arm64Emitter.cpp index 4f66b9b129..7cace77e89 100644 --- a/Common/Arm64Emitter.cpp +++ b/Common/Arm64Emitter.cpp @@ -3684,6 +3684,12 @@ void ARM64FloatEmitter::USHLL2(u8 src_size, ARM64Reg Rd, ARM64Reg Rn, u32 shift) { USHLL(src_size, Rd, Rn, shift, true); } +void ARM64FloatEmitter::SHLL(u8 src_size, ARM64Reg Rd, ARM64Reg Rn) { + SHLL(src_size, Rd, Rn, false); +} +void ARM64FloatEmitter::SHLL2(u8 src_size, ARM64Reg Rd, ARM64Reg Rn) { + SHLL(src_size, Rd, Rn, true); +} void ARM64FloatEmitter::SXTL(u8 src_size, ARM64Reg Rd, ARM64Reg Rn) { SXTL(src_size, Rd, Rn, false); @@ -3723,6 +3729,11 @@ void ARM64FloatEmitter::USHLL(u8 src_size, ARM64Reg Rd, ARM64Reg Rn, u32 shift, EmitShiftImm(upper, 1, imm >> 3, imm & 7, 0x14, Rd, Rn); } +void ARM64FloatEmitter::SHLL(u8 src_size, ARM64Reg Rd, ARM64Reg Rn, bool upper) { + _assert_msg_(src_size <= 32, "%s shift amount cannot be 64", __FUNCTION__); + Emit2RegMisc(upper, 1, src_size >> 4, 0b10011, Rd, Rn); +} + void ARM64FloatEmitter::SHRN(u8 dest_size, ARM64Reg Rd, ARM64Reg Rn, u32 shift, bool upper) { _assert_msg_(shift > 0, "%s shift amount must be greater than zero!", __FUNCTION__); diff --git a/Common/Arm64Emitter.h b/Common/Arm64Emitter.h index c8672583b1..3173714f4c 100644 --- a/Common/Arm64Emitter.h +++ b/Common/Arm64Emitter.h @@ -976,6 +976,9 @@ public: void SSHLL2(u8 src_size, ARM64Reg Rd, ARM64Reg Rn, u32 shift); void USHLL(u8 src_size, ARM64Reg Rd, ARM64Reg Rn, u32 shift); void USHLL2(u8 src_size, ARM64Reg Rd, ARM64Reg Rn, u32 shift); + // Shift == src_size for these. + void SHLL(u8 src_size, ARM64Reg Rd, ARM64Reg Rn); + void SHLL2(u8 src_size, ARM64Reg Rd, ARM64Reg Rn); void SHRN(u8 dest_size, ARM64Reg Rd, ARM64Reg Rn, u32 shift); void SHRN2(u8 dest_size, ARM64Reg Rd, ARM64Reg Rn, u32 shift); void SXTL(u8 src_size, ARM64Reg Rd, ARM64Reg Rn); @@ -1034,6 +1037,7 @@ private: void SSHLL(u8 src_size, ARM64Reg Rd, ARM64Reg Rn, u32 shift, bool upper); void USHLL(u8 src_size, ARM64Reg Rd, ARM64Reg Rn, u32 shift, bool upper); + void SHLL(u8 src_size, ARM64Reg Rd, ARM64Reg Rn, bool upper); void SHRN(u8 dest_size, ARM64Reg Rd, ARM64Reg Rn, u32 shift, bool upper); void SXTL(u8 src_size, ARM64Reg Rd, ARM64Reg Rn, bool upper); void UXTL(u8 src_size, ARM64Reg Rd, ARM64Reg Rn, bool upper); diff --git a/Core/MIPS/ARM64/Arm64IRCompVec.cpp b/Core/MIPS/ARM64/Arm64IRCompVec.cpp index 8f80af9af7..9b6325a3d0 100644 --- a/Core/MIPS/ARM64/Arm64IRCompVec.cpp +++ b/Core/MIPS/ARM64/Arm64IRCompVec.cpp @@ -713,9 +713,52 @@ void Arm64JitBackend::CompIR_VecPack(IRInst inst) { break; case IROp::Vec2Unpack16To31: - case IROp::Vec4Unpack8To32: + // Viewed as 16-bit: ABxx -> 0A0B, then shift a zero into the sign place. + if (Overlap(inst.dest, 2, inst.src1, 1)) { + regs_.MapVec2(inst.dest, MIPSMap::DIRTY); + } else { + regs_.Map(inst); + } + if (inst.src1 == inst.dest + 1) { + fp_.USHLL2(16, regs_.FQ(inst.dest), regs_.FD(inst.src1), 15); + } else { + fp_.USHLL(16, regs_.FQ(inst.dest), regs_.FD(inst.src1), 15); + } + break; + case IROp::Vec2Unpack16To32: - CompIR_Generic(inst); + // Just Vec2Unpack16To31, without the shift. + if (Overlap(inst.dest, 2, inst.src1, 1)) { + regs_.MapVec2(inst.dest, MIPSMap::DIRTY); + } else { + regs_.Map(inst); + } + if (inst.src1 == inst.dest + 1) { + fp_.SHLL2(16, regs_.FQ(inst.dest), regs_.FD(inst.src1)); + } else { + fp_.SHLL(16, regs_.FQ(inst.dest), regs_.FD(inst.src1)); + } + break; + + case IROp::Vec4Unpack8To32: + // Viewed as 8-bit: ABCD -> 000A000B000C000D. + if (Overlap(inst.dest, 4, inst.src1, 1)) { + regs_.MapVec4(inst.dest, MIPSMap::DIRTY); + if (inst.dest == inst.src1 + 2) { + fp_.SHLL2(8, regs_.FQ(inst.dest), regs_.FD(inst.src1 & ~3)); + } else if (inst.dest != inst.src1) { + fp_.DUP(32, regs_.FQ(inst.dest), regs_.FQ(inst.src1), inst.src1 & 3); + fp_.SHLL(8, regs_.FQ(inst.dest), regs_.FD(inst.dest)); + } else { + fp_.SHLL(8, regs_.FQ(inst.dest), regs_.FD(inst.src1)); + } + fp_.SHLL(16, regs_.FQ(inst.dest), regs_.FD(inst.dest)); + } else { + regs_.Map(inst); + // Two steps: ABCD -> 0A0B0C0D, then to 000A000B000C000D. + fp_.SHLL(8, regs_.FQ(inst.dest), regs_.FD(inst.src1)); + fp_.SHLL(16, regs_.FQ(inst.dest), regs_.FD(inst.dest)); + } break; default: From b698c673a86f6c5cc71e8ad4dfa690ae50183685 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 7 Sep 2023 20:08:47 -0700 Subject: [PATCH 5/6] arm64jit: Implement FSat. --- Core/MIPS/ARM64/Arm64IRCompFPU.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Core/MIPS/ARM64/Arm64IRCompFPU.cpp b/Core/MIPS/ARM64/Arm64IRCompFPU.cpp index de4cee7f51..5436eaae55 100644 --- a/Core/MIPS/ARM64/Arm64IRCompFPU.cpp +++ b/Core/MIPS/ARM64/Arm64IRCompFPU.cpp @@ -433,8 +433,22 @@ void Arm64JitBackend::CompIR_FSat(IRInst inst) { switch (inst.op) { case IROp::FSat0_1: + regs_.Map(inst); + fp_.MOVI2F(SCRATCHF1, 1.0f); + // Note that FMAX takes the larger of the two zeros, which is what we want. + fp_.MOVI2F(SCRATCHF2, 0.0f); + + fp_.FMIN(regs_.F(inst.dest), regs_.F(inst.src1), SCRATCHF1); + fp_.FMAX(regs_.F(inst.dest), regs_.F(inst.dest), SCRATCHF2); + break; + case IROp::FSatMinus1_1: - CompIR_Generic(inst); + regs_.Map(inst); + fp_.MOVI2F(SCRATCHF1, 1.0f); + fp_.FNEG(SCRATCHF2, SCRATCHF1); + + fp_.FMIN(regs_.F(inst.dest), regs_.F(inst.src1), SCRATCHF1); + fp_.FMAX(regs_.F(inst.dest), regs_.F(inst.dest), SCRATCHF2); break; default: From 7a5cdafdf307c721c9877a7a82951e610983eeca Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 7 Sep 2023 21:28:22 -0700 Subject: [PATCH 6/6] arm64jit: Implement convert/int conversions. --- Common/Arm64Emitter.cpp | 83 ++++++++++++++++++++++++++---- Common/Arm64Emitter.h | 4 ++ Core/MIPS/ARM64/Arm64IRCompFPU.cpp | 44 +++++++++++++++- Core/MIPS/RiscV/RiscVCompFPU.cpp | 43 ++++++++-------- 4 files changed, 140 insertions(+), 34 deletions(-) diff --git a/Common/Arm64Emitter.cpp b/Common/Arm64Emitter.cpp index 7cace77e89..a5d87c5a11 100644 --- a/Common/Arm64Emitter.cpp +++ b/Common/Arm64Emitter.cpp @@ -2234,6 +2234,45 @@ void ARM64FloatEmitter::FCVTU(ARM64Reg Rd, ARM64Reg Rn, RoundingMode round) { EmitConvertScalarToInt(Rd, Rn, round, true); } +void ARM64FloatEmitter::FCVTZS(ARM64Reg Rd, ARM64Reg Rn, int scale) { + if (IsScalar(Rd)) { + int imm = (IsDouble(Rn) ? 64 : 32) * 2 - scale; + Rd = DecodeReg(Rd); + Rn = DecodeReg(Rn); + + Write32((1 << 30) | (0 << 29) | (0x1F << 24) | (imm << 16) | (0x1F << 11) | (1 << 10) | (Rn << 5) | Rd); + } else { + bool sf = Is64Bit(Rd); + u32 type = 0; + if (IsDouble(Rd)) + type = 1; + int rmode = 3; + int opcode = 0; + + Write32((sf << 31) | (0 << 29) | (0x1E << 24) | (type << 22) | (rmode << 19) | (opcode << 16) | (scale << 10) | (Rn << 5) | Rd); + + } +} + +void ARM64FloatEmitter::FCVTZU(ARM64Reg Rd, ARM64Reg Rn, int scale) { + if (IsScalar(Rd)) { + int imm = (IsDouble(Rn) ? 64 : 32) * 2 - scale; + Rd = DecodeReg(Rd); + Rn = DecodeReg(Rn); + + Write32((1 << 30) | (1 << 29) | (0x1F << 24) | (imm << 16) | (0x1F << 11) | (1 << 10) | (Rn << 5) | Rd); + } else { + bool sf = Is64Bit(Rd); + u32 type = 0; + if (IsDouble(Rd)) + type = 1; + int rmode = 3; + int opcode = 1; + + Write32((sf << 31) | (0 << 29) | (0x1E << 24) | (type << 22) | (rmode << 19) | (opcode << 16) | (scale << 10) | (Rn << 5) | Rd); + } +} + void ARM64FloatEmitter::EmitConversion2(bool sf, bool S, bool direction, u32 type, u32 rmode, u32 opcode, int scale, ARM64Reg Rd, ARM64Reg Rn) { Rd = DecodeReg(Rd); @@ -3098,6 +3137,14 @@ void ARM64FloatEmitter::FCVTZU(u8 size, ARM64Reg Rd, ARM64Reg Rn) { Emit2RegMisc(IsQuad(Rd), 1, 2 | (size >> 6), 0x1B, Rd, Rn); } +void ARM64FloatEmitter::FCVTZS(u8 size, ARM64Reg Rd, ARM64Reg Rn, int scale) { + int imm = size * 2 - scale; + EmitShiftImm(IsQuad(Rd), false, imm >> 3, imm & 7, 0x1F, Rd, Rn); +} +void ARM64FloatEmitter::FCVTZU(u8 size, ARM64Reg Rd, ARM64Reg Rn, int scale) { + int imm = size * 2 - scale; + EmitShiftImm(IsQuad(Rd), true, imm >> 3, imm & 7, 0x1F, Rd, Rn); +} void ARM64FloatEmitter::FDIV(u8 size, ARM64Reg Rd, ARM64Reg Rn, ARM64Reg Rm) { EmitThreeSame(1, size >> 6, 0x1F, Rd, Rn, Rm); @@ -3539,22 +3586,38 @@ void ARM64FloatEmitter::UCVTF(ARM64Reg Rd, ARM64Reg Rn) void ARM64FloatEmitter::SCVTF(ARM64Reg Rd, ARM64Reg Rn, int scale) { - bool sf = Is64Bit(Rn); - u32 type = 0; - if (IsDouble(Rd)) - type = 1; + if (IsScalar(Rn)) { + int imm = (IsDouble(Rn) ? 64 : 32) * 2 - scale; + Rd = DecodeReg(Rd); + Rn = DecodeReg(Rn); - EmitConversion2(sf, 0, false, type, 0, 2, 64 - scale, Rd, Rn); + Write32((1 << 30) | (0 << 29) | (0x1F << 24) | (imm << 16) | (0x1C << 11) | (1 << 10) | (Rn << 5) | Rd); + } else { + bool sf = Is64Bit(Rn); + u32 type = 0; + if (IsDouble(Rd)) + type = 1; + + EmitConversion2(sf, 0, false, type, 0, 2, 64 - scale, Rd, Rn); + } } void ARM64FloatEmitter::UCVTF(ARM64Reg Rd, ARM64Reg Rn, int scale) { - bool sf = Is64Bit(Rn); - u32 type = 0; - if (IsDouble(Rd)) - type = 1; + if (IsScalar(Rn)) { + int imm = (IsDouble(Rn) ? 64 : 32) * 2 - scale; + Rd = DecodeReg(Rd); + Rn = DecodeReg(Rn); - EmitConversion2(sf, 0, false, type, 0, 3, 64 - scale, Rd, Rn); + Write32((1 << 30) | (1 << 29) | (0x1F << 24) | (imm << 16) | (0x1C << 11) | (1 << 10) | (Rn << 5) | Rd); + } else { + bool sf = Is64Bit(Rn); + u32 type = 0; + if (IsDouble(Rd)) + type = 1; + + EmitConversion2(sf, 0, false, type, 0, 3, 64 - scale, Rd, Rn); + } } void ARM64FloatEmitter::FCMP(ARM64Reg Rn, ARM64Reg Rm) diff --git a/Common/Arm64Emitter.h b/Common/Arm64Emitter.h index 3173714f4c..cd4a54cb73 100644 --- a/Common/Arm64Emitter.h +++ b/Common/Arm64Emitter.h @@ -866,6 +866,8 @@ public: void FCVTN(u8 dest_size, ARM64Reg Rd, ARM64Reg Rn); void FCVTZS(u8 size, ARM64Reg Rd, ARM64Reg Rn); void FCVTZU(u8 size, ARM64Reg Rd, ARM64Reg Rn); + void FCVTZS(u8 size, ARM64Reg Rd, ARM64Reg Rn, int scale); + void FCVTZU(u8 size, ARM64Reg Rd, ARM64Reg Rn, int scale); void FDIV(u8 size, ARM64Reg Rd, ARM64Reg Rn, ARM64Reg Rm); void FMUL(u8 size, ARM64Reg Rd, ARM64Reg Rn, ARM64Reg Rm); void FNEG(u8 size, ARM64Reg Rd, ARM64Reg Rn); @@ -931,6 +933,8 @@ public: // and one that outputs to a scalar fp register. void FCVTS(ARM64Reg Rd, ARM64Reg Rn, RoundingMode round); void FCVTU(ARM64Reg Rd, ARM64Reg Rn, RoundingMode round); + void FCVTZS(ARM64Reg Rd, ARM64Reg Rn, int scale); + void FCVTZU(ARM64Reg Rd, ARM64Reg Rn, int scale); // Scalar convert int to float. No rounding mode specifier necessary. void SCVTF(ARM64Reg Rd, ARM64Reg Rn); diff --git a/Core/MIPS/ARM64/Arm64IRCompFPU.cpp b/Core/MIPS/ARM64/Arm64IRCompFPU.cpp index 5436eaae55..74f62da5aa 100644 --- a/Core/MIPS/ARM64/Arm64IRCompFPU.cpp +++ b/Core/MIPS/ARM64/Arm64IRCompFPU.cpp @@ -374,7 +374,15 @@ void Arm64JitBackend::CompIR_FCvt(IRInst inst) { switch (inst.op) { case IROp::FCvtWS: - CompIR_Generic(inst); + // TODO: Unfortunately, we don't currently have the hasSetRounding flag, could skip lookup. + regs_.Map(inst); + fp_.FMOV(S0, regs_.F(inst.src1)); + + MOVP2R(SCRATCH1_64, ¤tRoundingFunc_); + LDR(INDEX_UNSIGNED, SCRATCH1_64, SCRATCH1_64, 0); + BLR(SCRATCH1_64); + + fp_.FMOV(regs_.F(inst.dest), S0); break; case IROp::FCvtSW: @@ -383,8 +391,40 @@ void Arm64JitBackend::CompIR_FCvt(IRInst inst) { break; case IROp::FCvtScaledWS: + if (IRRoundMode(inst.src2 >> 6) == IRRoundMode::CAST_1) { + regs_.Map(inst); + // NAN would convert to zero, so detect it specifically and replace with 0x7FFFFFFF. + fp_.MVNI(32, EncodeRegToDouble(SCRATCHF2), 0x80, 24); + fp_.FCMP(regs_.F(inst.src1), regs_.F(inst.src1)); + fp_.FCVTZS(regs_.F(inst.dest), regs_.F(inst.src1), inst.src2 & 0x1F); + fp_.FCSEL(regs_.F(inst.dest), regs_.F(inst.dest), SCRATCHF2, CC_VC); + } else { + RoundingMode rm; + switch (IRRoundMode(inst.src2 >> 6)) { + case IRRoundMode::RINT_0: rm = RoundingMode::ROUND_N; break; + case IRRoundMode::CEIL_2: rm = RoundingMode::ROUND_P; break; + case IRRoundMode::FLOOR_3: rm = RoundingMode::ROUND_M; break; + default: + _assert_msg_(false, "Invalid rounding mode for FCvtScaledWS"); + } + + // Unfortunately, only Z has a direct scaled instruction. + // We'll have to multiply. + regs_.Map(inst); + fp_.MOVI2F(SCRATCHF1, (float)(1UL << (inst.src2 & 0x1F)), SCRATCH1); + // This is for the NAN result. + fp_.MVNI(32, EncodeRegToDouble(SCRATCHF2), 0x80, 24); + fp_.FCMP(regs_.F(inst.src1), regs_.F(inst.src1)); + fp_.FMUL(regs_.F(inst.dest), regs_.F(inst.src1), SCRATCHF1); + fp_.FCVTS(regs_.F(inst.dest), regs_.F(inst.dest), rm); + fp_.FCSEL(regs_.F(inst.dest), regs_.F(inst.dest), SCRATCHF2, CC_VC); + } + break; + case IROp::FCvtScaledSW: - CompIR_Generic(inst); + // TODO: This is probably proceeded by a GPR transfer, might be ideal to combine. + regs_.Map(inst); + fp_.SCVTF(regs_.F(inst.dest), regs_.F(inst.src1), inst.src2 & 0x1F); break; default: diff --git a/Core/MIPS/RiscV/RiscVCompFPU.cpp b/Core/MIPS/RiscV/RiscVCompFPU.cpp index df5cdb4262..3836ae77e4 100644 --- a/Core/MIPS/RiscV/RiscVCompFPU.cpp +++ b/Core/MIPS/RiscV/RiscVCompFPU.cpp @@ -233,30 +233,29 @@ void RiscVJitBackend::CompIR_FCvt(IRInst inst) { break; case IROp::FCvtScaledWS: - if (cpu_info.RiscV_D) { - Round rm = Round::NEAREST_EVEN; - switch (inst.src2 >> 6) { - case 0: rm = Round::NEAREST_EVEN; break; - case 1: rm = Round::TOZERO; break; - case 2: rm = Round::UP; break; - case 3: rm = Round::DOWN; break; - } - - tempReg = regs_.MapWithFPRTemp(inst); - // Prepare the double src1 and the multiplier. - FCVT(FConv::D, FConv::S, regs_.F(inst.dest), regs_.F(inst.src1)); - LI(SCRATCH1, 1UL << (inst.src2 & 0x1F)); - FCVT(FConv::D, FConv::WU, tempReg, SCRATCH1, rm); - - FMUL(64, regs_.F(inst.dest), regs_.F(inst.dest), tempReg, rm); - // NAN and clamping should all be correct. - FCVT(FConv::W, FConv::D, SCRATCH1, regs_.F(inst.dest), rm); - // TODO: Could combine with a transfer, often is one... - FMV(FMv::W, FMv::X, regs_.F(inst.dest), SCRATCH1); - } else { - CompIR_Generic(inst); + { + Round rm = Round::NEAREST_EVEN; + switch (inst.src2 >> 6) { + case 0: rm = Round::NEAREST_EVEN; break; + case 1: rm = Round::TOZERO; break; + case 2: rm = Round::UP; break; + case 3: rm = Round::DOWN; break; + default: + _assert_msg_(false, "Invalid rounding mode for FCvtScaledWS"); } + + tempReg = regs_.MapWithFPRTemp(inst); + // Prepare the multiplier. + LI(SCRATCH1, 1UL << (inst.src2 & 0x1F)); + FCVT(FConv::S, FConv::WU, tempReg, SCRATCH1, rm); + + FMUL(32, regs_.F(inst.dest), regs_.F(inst.src1), tempReg, rm); + // NAN and clamping should all be correct. + FCVT(FConv::W, FConv::S, SCRATCH1, regs_.F(inst.dest), rm); + // TODO: Could combine with a transfer, often is one... + FMV(FMv::W, FMv::X, regs_.F(inst.dest), SCRATCH1); break; + } case IROp::FCvtScaledSW: // TODO: This is probably proceeded by a GPR transfer, might be ideal to combine.