mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-01 14:58:14 +00:00
x64Analyzer: add movss/movups/movaps support and an instruction class field
The crash handler's instruction analyzer only understood mov/movzx/movsx, so a fault on an FP or SIMD load/store (used for lwc1/swc1 and lv.q/sv.q in the x86 JIT) couldn't be classified. Add decoding for movss (scalar, distinguished from movups by the mandatory 0xF3 prefix), movups, and movaps, and add an InstructionClass field (GPR/FP/FP_SIMD) so callers know how to interpret the decoded register operand. Covered by new unit tests in TestX64Emitter.cpp that emit each instruction and check the analyzer's output against it.
This commit is contained in:
1 parent
6a873df4ea
commit
2c315be708
3 files changed
+109
-1
No files matched your search
@@ -33,6 +33,7 @@ bool X86AnalyzeMOV(const unsigned char *codePtr, LSInstructionInfo &info)
|
||||
info.signExtend = false;
|
||||
info.hasImmediate = false;
|
||||
info.isMemoryWrite = false;
|
||||
info.instructionClass = InstructionClass::GPR;
|
||||
|
||||
int addressSize = 8;
|
||||
u8 modRMbyte = 0;
|
||||
@@ -40,6 +41,7 @@ bool X86AnalyzeMOV(const unsigned char *codePtr, LSInstructionInfo &info)
|
||||
bool hasModRM = false;
|
||||
bool hasSIBbyte = false;
|
||||
bool hasDisplacement = false;
|
||||
bool hasF3Prefix = false;
|
||||
|
||||
int displacementSize = 0;
|
||||
|
||||
@@ -53,6 +55,12 @@ bool X86AnalyzeMOV(const unsigned char *codePtr, LSInstructionInfo &info)
|
||||
addressSize = 4;
|
||||
codePtr++;
|
||||
}
|
||||
else if (*codePtr == 0xF3)
|
||||
{
|
||||
// Mandatory prefix, distinguishes MOVSS (scalar) from MOVUPS (full xmm) on the same opcode.
|
||||
hasF3Prefix = true;
|
||||
codePtr++;
|
||||
}
|
||||
|
||||
//Check for REX prefix
|
||||
if ((*codePtr & 0xF0) == 0x40)
|
||||
@@ -219,6 +227,24 @@ bool X86AnalyzeMOV(const unsigned char *codePtr, LSInstructionInfo &info)
|
||||
info.signExtend = true;
|
||||
info.operandSize = 2;
|
||||
break;
|
||||
case MOVUPS_MOVSS_FROM_RM: //movups/movss xmm, xmm/m (load)
|
||||
info.instructionClass = hasF3Prefix ? InstructionClass::FP : InstructionClass::FP_SIMD;
|
||||
info.operandSize = hasF3Prefix ? 4 : 16;
|
||||
break;
|
||||
case MOVUPS_MOVSS_TO_RM: //movups/movss xmm/m, xmm (store)
|
||||
info.instructionClass = hasF3Prefix ? InstructionClass::FP : InstructionClass::FP_SIMD;
|
||||
info.operandSize = hasF3Prefix ? 4 : 16;
|
||||
info.isMemoryWrite = true;
|
||||
break;
|
||||
case MOVAPS_FROM_RM: //movaps xmm, xmm/m (load)
|
||||
info.instructionClass = InstructionClass::FP_SIMD;
|
||||
info.operandSize = 16;
|
||||
break;
|
||||
case MOVAPS_TO_RM: //movaps xmm/m, xmm (store)
|
||||
info.instructionClass = InstructionClass::FP_SIMD;
|
||||
info.operandSize = 16;
|
||||
info.isMemoryWrite = true;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
+15
-1
@@ -19,9 +19,17 @@
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
// What kind of register regOperandReg (and otherReg, if used as a source/dest rather than
|
||||
// just an address component) refers to, and thus how the instruction should be interpreted.
|
||||
enum class InstructionClass {
|
||||
GPR, // General-purpose register (mov, movzx, movsx, ...)
|
||||
FP, // Scalar floating point (movss, movsd, ...)
|
||||
FP_SIMD, // Full vector register (movups, movaps, movdqa, ...)
|
||||
};
|
||||
|
||||
struct LSInstructionInfo
|
||||
{
|
||||
int operandSize; //8, 16, 32, 64
|
||||
int operandSize; //1, 2, 4, 8 (in bytes, despite the field name suggesting bits)
|
||||
int instructionSize;
|
||||
int regOperandReg;
|
||||
int otherReg;
|
||||
@@ -32,6 +40,7 @@ struct LSInstructionInfo
|
||||
bool isMemoryWrite;
|
||||
u64 immediate;
|
||||
s32 displacement;
|
||||
InstructionClass instructionClass;
|
||||
};
|
||||
|
||||
struct ModRM
|
||||
@@ -54,6 +63,11 @@ enum {
|
||||
MOVE_16_32BIT = 0xC7, //move 16 or 32-bit immediate
|
||||
MOVE_REG_TO_MEM = 0x89, //move reg to memory
|
||||
MOVE_MEM_TO_REG = 0x8B, //move memory to reg
|
||||
// These two opcodes are shared between MOVUPS (no mandatory prefix) and MOVSS (mandatory 0xF3 prefix).
|
||||
MOVUPS_MOVSS_FROM_RM = 0x10, //movups/movss xmm, xmm/m
|
||||
MOVUPS_MOVSS_TO_RM = 0x11, //movups/movss xmm/m, xmm
|
||||
MOVAPS_FROM_RM = 0x28, //movaps xmm, xmm/m
|
||||
MOVAPS_TO_RM = 0x29, //movaps xmm/m, xmm
|
||||
};
|
||||
|
||||
enum AccessType {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#if PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(X86)
|
||||
|
||||
#include "Common/CPUDetect.h"
|
||||
#include "Common/x64Analyzer.h"
|
||||
#include "Common/x64Emitter.h"
|
||||
#include "Core/MIPS/x86/RegCacheFPU.h"
|
||||
#include "Core/MIPS/x86/Jit.h"
|
||||
@@ -21,6 +22,22 @@ static bool CheckLast(const Gen::XEmitter &emit, const char *comp) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Emits nothing itself - runs the x64 crash-handler instruction analyzer (Common/x64Analyzer.cpp)
|
||||
// on the instruction most recently emitted (from prevStart to the emitter's current position),
|
||||
// and checks that it decoded it the way we expect.
|
||||
static bool CheckAnalyze(const Gen::XEmitter &emit, bool expectWrite, InstructionClass expectClass, int expectOperandSize, bool expectZeroExtend = false, bool expectSignExtend = false) {
|
||||
LSInstructionInfo info{};
|
||||
bool success = X86AnalyzeMOV(prevStart, info);
|
||||
EXPECT_TRUE(success);
|
||||
EXPECT_EQ_INT(info.instructionSize, (int)(emit.GetCodePointer() - prevStart));
|
||||
EXPECT_EQ_INT(info.isMemoryWrite, expectWrite);
|
||||
EXPECT_EQ_INT((int)info.instructionClass, (int)expectClass);
|
||||
EXPECT_EQ_INT(info.operandSize, expectOperandSize);
|
||||
EXPECT_EQ_INT(info.zeroExtend, expectZeroExtend);
|
||||
EXPECT_EQ_INT(info.signExtend, expectSignExtend);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void PrintLast(const Gen::XEmitter &emit) {
|
||||
for (const u8 *p = prevStart; p < emit.GetCodePointer(); p++) {
|
||||
printf("%02x ", *p);
|
||||
@@ -47,6 +64,57 @@ bool TestX64Emitter() {
|
||||
|
||||
cpu_info.bAVX = prevAVX;
|
||||
|
||||
// Exercise Common/x64Analyzer.cpp (used by the JIT crash handler to figure out what a faulting
|
||||
// load/store instruction was doing) against instructions written by the emitter, for the most
|
||||
// common memory access instructions.
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOV(32, R(EAX), MDisp(RCX, 4));
|
||||
RET(CheckAnalyze(emitter, false, InstructionClass::GPR, 4));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOV(32, MDisp(RCX, 4), R(EAX));
|
||||
RET(CheckAnalyze(emitter, true, InstructionClass::GPR, 4));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOVZX(32, 8, EAX, MDisp(RCX, 4));
|
||||
RET(CheckAnalyze(emitter, false, InstructionClass::GPR, 1, true, false));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOVZX(32, 16, EAX, MDisp(RCX, 4));
|
||||
RET(CheckAnalyze(emitter, false, InstructionClass::GPR, 2, true, false));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOVSX(32, 8, EAX, MDisp(RCX, 4));
|
||||
RET(CheckAnalyze(emitter, false, InstructionClass::GPR, 1, false, true));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOVSX(32, 16, EAX, MDisp(RCX, 4));
|
||||
RET(CheckAnalyze(emitter, false, InstructionClass::GPR, 2, false, true));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOVSS(XMM0, MDisp(RCX, 4));
|
||||
RET(CheckAnalyze(emitter, false, InstructionClass::FP, 4));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOVSS(MDisp(RCX, 4), XMM0);
|
||||
RET(CheckAnalyze(emitter, true, InstructionClass::FP, 4));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOVUPS(XMM0, MDisp(RCX, 4));
|
||||
RET(CheckAnalyze(emitter, false, InstructionClass::FP_SIMD, 16));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOVUPS(MDisp(RCX, 4), XMM0);
|
||||
RET(CheckAnalyze(emitter, true, InstructionClass::FP_SIMD, 16));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOVAPS(XMM0, MDisp(RCX, 4));
|
||||
RET(CheckAnalyze(emitter, false, InstructionClass::FP_SIMD, 16));
|
||||
|
||||
prevStart = emitter.GetCodePointer();
|
||||
emitter.MOVAPS(MDisp(RCX, 4), XMM0);
|
||||
RET(CheckAnalyze(emitter, true, InstructionClass::FP_SIMD, 16));
|
||||
|
||||
// Just for checking.
|
||||
PrintLast(emitter);
|
||||
return true;
|
||||
|
||||
Reference in new issue
Block a user