From 5e69da7ebaa3957f99ba1a866616b9827b2586f2 Mon Sep 17 00:00:00 2001 From: Martino Fontana Date: Tue, 2 Sep 2025 15:11:02 +0200 Subject: [PATCH 1/3] x64Emitter: Support YMM registers This is accomplished by adding a 0x100 bit the the register. Made sure that, on AVX instructions, that bit is trucated. --- Source/Core/Common/x64Emitter.cpp | 9 +++++---- Source/Core/Common/x64Reg.h | 3 ++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Source/Core/Common/x64Emitter.cpp b/Source/Core/Common/x64Emitter.cpp index 14458ae7d4..31f33edc28 100644 --- a/Source/Core/Common/x64Emitter.cpp +++ b/Source/Core/Common/x64Emitter.cpp @@ -250,7 +250,7 @@ void OpArg::WriteVEX(XEmitter* emit, X64Reg regOp1, X64Reg regOp2, int L, int pp int X = !(indexReg & 8); int B = !(offsetOrBaseReg & 8); - int vvvv = (regOp2 == X64Reg::INVALID_REG) ? 0xf : (regOp2 ^ 0xf); + u8 vvvv = (regOp2 == X64Reg::INVALID_REG) ? 0xf : (regOp2 ^ 0xf); // do we need any VEX fields that only appear in the three-byte form? if (X == 1 && B == 1 && W == 0 && mmmmm == 1) @@ -343,7 +343,7 @@ void OpArg::WriteRest(XEmitter* emit, int extraBytes, X64Reg _operandReg, if (SIB) oreg = 4; - emit->WriteModRM(mod, _operandReg & 7, oreg & 7); + emit->WriteModRM(mod, _operandReg, oreg); if (SIB) { @@ -1844,8 +1844,9 @@ void XEmitter::WriteVEXOp(u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, con { int mmmmm = GetVEXmmmmm(op); int pp = GetVEXpp(opPrefix); - // FIXME: we currently don't support 256-bit instructions, and "size" is not the vector size here - arg.WriteVEX(this, regOp1, regOp2, 0, pp, mmmmm, W); + // Note that mixing an XMM register with a YMM register is invalid, which isn't checked here. + int L = (regOp1 != INVALID_REG && regOp1 & 0x100) || (regOp2 != INVALID_REG && regOp2 & 0x100); + arg.WriteVEX(this, regOp1, regOp2, L, pp, mmmmm, W); Write8(op & 0xFF); arg.WriteRest(this, extrabytes, regOp1); } diff --git a/Source/Core/Common/x64Reg.h b/Source/Core/Common/x64Reg.h index a868e3047a..c359738429 100644 --- a/Source/Core/Common/x64Reg.h +++ b/Source/Core/Common/x64Reg.h @@ -72,7 +72,8 @@ enum X64Reg XMM14, XMM15, - YMM0 = 0, + // Use the bit 0x100 to distinguish XMM and YMM registers. + YMM0 = 0x100, YMM1, YMM2, YMM3, From d1ba849876d336b9d7d8d87d716e496066050276 Mon Sep 17 00:00:00 2001 From: Martino Fontana Date: Tue, 2 Sep 2025 15:12:37 +0200 Subject: [PATCH 2/3] Jit64: `dcbz`, use VMOVAPS with YMM registers on AVX CPUs LLVM does this, so let's do it as well. --- Source/Core/Common/x64Emitter.cpp | 25 ++++++++++++++++--- Source/Core/Common/x64Emitter.h | 4 +++ .../Core/Core/PowerPC/Jit64/Jit_LoadStore.cpp | 16 +++++++++--- 3 files changed, 38 insertions(+), 7 deletions(-) diff --git a/Source/Core/Common/x64Emitter.cpp b/Source/Core/Common/x64Emitter.cpp index 31f33edc28..f1aa26eae3 100644 --- a/Source/Core/Common/x64Emitter.cpp +++ b/Source/Core/Common/x64Emitter.cpp @@ -1858,19 +1858,23 @@ void XEmitter::WriteVEXOp4(u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, co Write8((u8)regOp3 << 4); } -void XEmitter::WriteAVXOp(u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, const OpArg& arg, - int W, int extrabytes) +void CheckAVXSupport() { if (!cpu_info.bAVX) PanicAlertFmt("Trying to use AVX on a system that doesn't support it. Bad programmer."); +} + +void XEmitter::WriteAVXOp(u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, const OpArg& arg, + int W, int extrabytes) +{ + CheckAVXSupport(); WriteVEXOp(opPrefix, op, regOp1, regOp2, arg, W, extrabytes); } void XEmitter::WriteAVXOp4(u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, const OpArg& arg, X64Reg regOp3, int W) { - if (!cpu_info.bAVX) - PanicAlertFmt("Trying to use AVX on a system that doesn't support it. Bad programmer."); + CheckAVXSupport(); WriteVEXOp4(opPrefix, op, regOp1, regOp2, arg, regOp3, W); } @@ -3030,6 +3034,19 @@ void XEmitter::VPXOR(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) WriteAVXOp(0x66, 0xEF, regOp1, regOp2, arg); } +void XEmitter::VMOVAPS(const OpArg& arg, X64Reg regOp) +{ + WriteAVXOp(0x00, 0x29, X64Reg::INVALID_REG, regOp, arg); +} + +void XEmitter::VZEROUPPER() +{ + CheckAVXSupport(); + Write8(0xC5); + Write8(0xF8); + Write8(0x77); +} + void XEmitter::VFMADD132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) { WriteFMA3Op(0x98, regOp1, regOp2, arg); diff --git a/Source/Core/Common/x64Emitter.h b/Source/Core/Common/x64Emitter.h index 2b7bbb7726..3c8a411159 100644 --- a/Source/Core/Common/x64Emitter.h +++ b/Source/Core/Common/x64Emitter.h @@ -876,6 +876,10 @@ public: void VPOR(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); void VPXOR(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); + void VMOVAPS(const OpArg& arg, X64Reg regOp); + + void VZEROUPPER(); + // FMA3 void VFMADD132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); void VFMADD213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg); diff --git a/Source/Core/Core/PowerPC/Jit64/Jit_LoadStore.cpp b/Source/Core/Core/PowerPC/Jit64/Jit_LoadStore.cpp index 05bd690694..2b26a9c26e 100644 --- a/Source/Core/Core/PowerPC/Jit64/Jit_LoadStore.cpp +++ b/Source/Core/Core/PowerPC/Jit64/Jit_LoadStore.cpp @@ -8,6 +8,7 @@ #include "Common/Assert.h" #include "Common/BitSet.h" +#include "Common/CPUDetect.h" #include "Common/CommonTypes.h" #include "Common/MsgHandler.h" #include "Common/x64ABI.h" @@ -469,9 +470,18 @@ void Jit64::dcbz(UGeckoInstruction inst) FixupBranch slow = J_CC(CC_Z, Jump::Near); // Fast path: compute full address, then zero out 32 bytes of memory. - XORPS(XMM0, R(XMM0)); - MOVAPS(MComplex(RMEM, RSCRATCH, SCALE_1, 0), XMM0); - MOVAPS(MComplex(RMEM, RSCRATCH, SCALE_1, 16), XMM0); + if (cpu_info.bAVX) + { + VXORPS(XMM0, XMM0, R(XMM0)); + VMOVAPS(MComplex(RMEM, RSCRATCH, SCALE_1, 0), YMM0); + VZEROUPPER(); + } + else + { + XORPS(XMM0, R(XMM0)); + MOVAPS(MComplex(RMEM, RSCRATCH, SCALE_1, 16), XMM0); + MOVAPS(MComplex(RMEM, RSCRATCH, SCALE_1, 0), XMM0); + } // Slow path: call the general-case code. SwitchToFarCode(); From 667c5237552a8bc2a8ad8f55be3034967fa74f56 Mon Sep 17 00:00:00 2001 From: Martino Fontana Date: Tue, 2 Sep 2025 15:15:03 +0200 Subject: [PATCH 3/3] Jit: Use `dcbz()` emitter for `dcbz_l` instead of interpreter fallback Without cache emulation, these instructions are functionally identical. In the interpreter, their only difference is related to HID registers checks, which the JIT already doesn't do for `dcbz`. A loop with `dcbz_l` is used in the SDK function `LCEnable`, which is called frequently in some games. --- Source/Core/Core/PowerPC/Jit64/Jit64_Tables.cpp | 2 +- Source/Core/Core/PowerPC/JitArm64/JitArm64_Tables.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Core/Core/PowerPC/Jit64/Jit64_Tables.cpp b/Source/Core/Core/PowerPC/Jit64/Jit64_Tables.cpp index 62275b3dd8..be5458bffc 100644 --- a/Source/Core/Core/PowerPC/Jit64/Jit64_Tables.cpp +++ b/Source/Core/Core/PowerPC/Jit64/Jit64_Tables.cpp @@ -101,7 +101,7 @@ constexpr std::array s_table4{{ {592, &Jit64::ps_mergeXX}, // ps_merge10 {624, &Jit64::ps_mergeXX}, // ps_merge11 - {1014, &Jit64::FallBackToInterpreter}, // dcbz_l + {1014, &Jit64::dcbz}, // dcbz_l }}; constexpr std::array s_table4_2{{ diff --git a/Source/Core/Core/PowerPC/JitArm64/JitArm64_Tables.cpp b/Source/Core/Core/PowerPC/JitArm64/JitArm64_Tables.cpp index 0aac345296..eb97db9b91 100644 --- a/Source/Core/Core/PowerPC/JitArm64/JitArm64_Tables.cpp +++ b/Source/Core/Core/PowerPC/JitArm64/JitArm64_Tables.cpp @@ -101,7 +101,7 @@ constexpr std::array s_table4{{ {592, &JitArm64::ps_mergeXX}, // ps_merge10 {624, &JitArm64::ps_mergeXX}, // ps_merge11 - {1014, &JitArm64::FallBackToInterpreter}, // dcbz_l + {1014, &JitArm64::dcbz}, // dcbz_l }}; constexpr std::array s_table4_2{{