diff --git a/Common/Log/LogManager.cpp b/Common/Log/LogManager.cpp index f2d9d75bb7..f08eb757ef 100644 --- a/Common/Log/LogManager.cpp +++ b/Common/Log/LogManager.cpp @@ -60,6 +60,7 @@ static const char level_to_char[8] = "-NEWIDV"; void AndroidLog(const LogMessage &message); #endif +// TODO: Get rid of this wrapper, not much point. void GenericLog(Log type, LogLevel level, const char *file, int line, const char* fmt, ...) { va_list args; va_start(args, fmt); diff --git a/Common/Net/HTTPServer.cpp b/Common/Net/HTTPServer.cpp index 6f4789d59e..91505edf74 100644 --- a/Common/Net/HTTPServer.cpp +++ b/Common/Net/HTTPServer.cpp @@ -114,7 +114,7 @@ void ServerRequest::WriteHttpResponseHeader(const char *ver, int status, int64_t buffer->Push("Connection: close\r\n"); } if (size >= 0) { - buffer->Printf("Content-Length: %llu\r\n", size); + buffer->Printf("Content-Length: %llu\r\n", (unsigned long long)size); } if (otherHeaders) { buffer->Push(otherHeaders, strlen(otherHeaders)); diff --git a/Common/System/Request.h b/Common/System/Request.h index 2db3ddcf5a..9ba9bb5b61 100644 --- a/Common/System/Request.h +++ b/Common/System/Request.h @@ -207,5 +207,3 @@ void System_RunCallbackInWndProc(void (*callback)(void *, void *), void *userdat // Non-inline to avoid including Path.h void System_CreateGameShortcut(const Path &path, std::string_view title); void System_ShowFileInFolder(const Path &path); -bool System_SendDebugOutput(std::string_view string); -void System_SendDebugScreenshot(const uint8_t *data, int width, int height); diff --git a/Common/x64Analyzer.cpp b/Common/x64Analyzer.cpp index 04662498b5..06689f86ec 100644 --- a/Common/x64Analyzer.cpp +++ b/Common/x64Analyzer.cpp @@ -28,11 +28,12 @@ bool X86AnalyzeMOV(const unsigned char *codePtr, LSInstructionInfo &info) u8 codeByte2 = 0; //Check for regular prefix - info.operandSize = 4; + info.operandSizeInBytes = 4; info.zeroExtend = false; info.signExtend = false; info.hasImmediate = false; info.isMemoryWrite = false; + info.instructionClass = InstructionClass::GPR; int addressSize = 8; u8 modRMbyte = 0; @@ -40,19 +41,26 @@ bool X86AnalyzeMOV(const unsigned char *codePtr, LSInstructionInfo &info) bool hasModRM = false; bool hasSIBbyte = false; bool hasDisplacement = false; + bool hasF3Prefix = false; int displacementSize = 0; if (*codePtr == 0x66) { - info.operandSize = 2; + info.operandSizeInBytes = 2; codePtr++; - } + } else if (*codePtr == 0x67) { 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) @@ -60,7 +68,7 @@ bool X86AnalyzeMOV(const unsigned char *codePtr, LSInstructionInfo &info) rex = *codePtr; if (rex & 8) //REX.W { - info.operandSize = 8; + info.operandSizeInBytes = 8; } codePtr++; } @@ -159,19 +167,19 @@ bool X86AnalyzeMOV(const unsigned char *codePtr, LSInstructionInfo &info) case MOVE_16_32BIT: //move 16 or 32-bit immediate, easiest case for writes { - if (info.operandSize == 2) + if (info.operandSizeInBytes == 2) { info.hasImmediate = true; info.immediate = *(u16*)codePtr; codePtr += 2; } - else if (info.operandSize == 4) + else if (info.operandSizeInBytes == 4) { info.hasImmediate = true; info.immediate = *(u32*)codePtr; codePtr += 4; } - else if (info.operandSize == 8) + else if (info.operandSizeInBytes == 8) { info.zeroExtend = true; info.immediate = *(u32*)codePtr; @@ -205,28 +213,46 @@ bool X86AnalyzeMOV(const unsigned char *codePtr, LSInstructionInfo &info) { case MOVZX_BYTE: //movzx on byte info.zeroExtend = true; - info.operandSize = 1; + info.operandSizeInBytes = 1; break; case MOVZX_SHORT: //movzx on short info.zeroExtend = true; - info.operandSize = 2; + info.operandSizeInBytes = 2; break; case MOVSX_BYTE: //movsx on byte info.signExtend = true; - info.operandSize = 1; + info.operandSizeInBytes = 1; break; case MOVSX_SHORT: //movsx on short info.signExtend = true; - info.operandSize = 2; + info.operandSizeInBytes = 2; + break; + case MOVUPS_MOVSS_FROM_RM: //movups/movss xmm, xmm/m (load) + info.instructionClass = hasF3Prefix ? InstructionClass::FP : InstructionClass::FP_SIMD; + info.operandSizeInBytes = hasF3Prefix ? 4 : 16; + break; + case MOVUPS_MOVSS_TO_RM: //movups/movss xmm/m, xmm (store) + info.instructionClass = hasF3Prefix ? InstructionClass::FP : InstructionClass::FP_SIMD; + info.operandSizeInBytes = hasF3Prefix ? 4 : 16; + info.isMemoryWrite = true; + break; + case MOVAPS_FROM_RM: //movaps xmm, xmm/m (load) + info.instructionClass = InstructionClass::FP_SIMD; + info.operandSizeInBytes = 16; + break; + case MOVAPS_TO_RM: //movaps xmm/m, xmm (store) + info.instructionClass = InstructionClass::FP_SIMD; + info.operandSizeInBytes = 16; + info.isMemoryWrite = true; break; default: return false; } break; case 0x8a: - if (info.operandSize == 4) + if (info.operandSizeInBytes == 4) { - info.operandSize = 1; + info.operandSizeInBytes = 1; break; } else diff --git a/Common/x64Analyzer.h b/Common/x64Analyzer.h index 1e5c970221..4bcdb7bd78 100644 --- a/Common/x64Analyzer.h +++ b/Common/x64Analyzer.h @@ -19,9 +19,16 @@ #include "Common/CommonTypes.h" -struct LSInstructionInfo -{ - int operandSize; //8, 16, 32, 64 +// 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 operandSizeInBytes; // 1, 2, 4, 8 (in bytes, despite the field name suggesting bits) int instructionSize; int regOperandReg; int otherReg; @@ -32,6 +39,9 @@ struct LSInstructionInfo bool isMemoryWrite; u64 immediate; s32 displacement; + InstructionClass instructionClass; + + int OperandSizeInBytes() const { return operandSizeInBytes; } }; struct ModRM @@ -54,6 +64,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 { diff --git a/Core/CmdLine.cpp b/Core/CmdLine.cpp index 6b22d561eb..ef0db77bd8 100644 --- a/Core/CmdLine.cpp +++ b/Core/CmdLine.cpp @@ -242,10 +242,11 @@ int CommandLineOptions::PrintUsage(const char *progname, const char *situationTe PRINT_STDOUT(" -v set the log level to verbose\n"); PRINT_STDOUT(" --loglevel=INTEGER set the log level to specified value\n"); if (mode == CmdLineMode::Application) { - PRINT_STDOUT(" --log=FILE output log to FILE\n"); + PRINT_STDOUT(" --log=FILE output log to FILE\n"); } PRINT_STDOUT(" --state=FILE load state from FILE\n"); + PRINT_STDOUT(" --cpu=CPU use the specified CPU core (interpreter, ir, jit, jit-ir)\n"); PRINT_STDOUT(" -i use the interpreter\n"); PRINT_STDOUT(" -r use IR interpreter\n"); PRINT_STDOUT(" -j use JIT\n"); @@ -290,6 +291,7 @@ int CommandLineOptions::PrintUsage(const char *progname, const char *situationTe // Actually might want to reconsider given Android... CommandLineParseResult CommandLineOptions::Parse(int argc, const char *argv[], CmdLineMode mode) { this->mode = mode; + constexpr std::string_view cpuBackendStr = "--cpu="; constexpr std::string_view gpuBackendStr = "--graphics="; constexpr std::string_view configOption = "--config="; constexpr std::string_view controlsOption = "--controlconfig="; @@ -413,7 +415,27 @@ CommandLineParseResult CommandLineOptions::Parse(int argc, const char *argv[], C } else { // Bad value, report error and exit. PRINT_STDERR("Invalid value for --graphics=: %s", restOfOption.c_str()); - return CommandLineParseResult::Exit; + return CommandLineParseResult::Error; + } + } else if (startsWith(argv[i], cpuBackendStr)) { + const std::string restOfOption = argv[i] + cpuBackendStr.size(); + // Force software rendering off, as picking gles implies HW acceleration. + // We could add more options for software such as "software-gles", + // "software-vulkan" and "software-d3d11", or something similar. + // For now, software rendering force-activates OpenGL. + double glVersionTemp = 0.0f; + if (restOfOption == "interpreter") { + cpuCore = CPUCore::INTERPRETER; + } else if (restOfOption == "jit") { + cpuCore = CPUCore::JIT; + } else if (restOfOption == "jit-ir") { + cpuCore = CPUCore::JIT_IR; + } else if (restOfOption == "ir") { + cpuCore = CPUCore::IR_INTERPRETER; + } else { + // Bad value, report error and exit. + PRINT_STDERR("Invalid value for --cpu=: %s", restOfOption.c_str()); + return CommandLineParseResult::Error; } } else if (startsWith(argv[i], configOption)) { configFilename = std::string(argv[i] + configOption.size()); @@ -429,10 +451,11 @@ CommandLineParseResult CommandLineOptions::Parse(int argc, const char *argv[], C continue; } else { PRINT_STDERR("Error: --ignore requires an argument.\n"); - return CommandLineParseResult::Exit; + return CommandLineParseResult::Error; } - } else { - // Report unknown argument later once this is complete. + } else if (startsWith(argv[i], "--")) { + PRINT_STDERR("Error: Unknown parameter: %s\n", argv[i]); + return CommandLineParseResult::Error; } // To the next argument. i++; @@ -456,6 +479,9 @@ void CommandLineOptions::ApplyToConfig() const { g_Config.iGPUBackend = (int)gpuBackend.value(); g_Config.DoNotSaveSetting(&g_Config.iGPUBackend); } + if (cpuCore.has_value()) { + g_Config.iCpuCore = (int)cpuCore.value(); + } if (softwareRendering.has_value()) { g_Config.bSoftwareRendering = softwareRendering.value(); g_Config.DoNotSaveSetting(&g_Config.bSoftwareRendering); @@ -468,9 +494,6 @@ void CommandLineOptions::ApplyToConfig() const { g_Config.bAutoRun = false; g_Config.bSaveSettings = false; } - if (cpuCore.has_value()) { - g_Config.iCpuCore = (int)cpuCore.value(); - } if (escapeExit.has_value()) { g_Config.bPauseExitsEmulator = escapeExit.value(); } diff --git a/Core/Core.cpp b/Core/Core.cpp index fbe0b3d8a9..67133ecb60 100644 --- a/Core/Core.cpp +++ b/Core/Core.cpp @@ -32,6 +32,7 @@ #include "Common/GPU/GraphicsContext.h" #include "Common/Thread/ThreadUtil.h" #include "Common/Log.h" +#include "Common/StringUtils.h" #include "Core/Core.h" #include "Core/Config.h" #include "Core/HLE/HLE.h" @@ -155,6 +156,28 @@ static MIPSExceptionInfo g_exceptionInfo; // This is called on EmuThread before RunLoop. static bool Core_ProcessStepping(MIPSDebugInterface *cpu); +static std::function g_debugOutputListener; +static std::function g_debugScreenshotListener; + +void Core_RegisterDebugOutputListeners(std::function listener, std::function screenshotListener) { + g_debugOutputListener = std::move(listener); + g_debugScreenshotListener = std::move(screenshotListener); +} + +void Core_SendDebugOutput(LogLevel level, std::string_view string) { + if (g_debugOutputListener) { + g_debugOutputListener(string); + } else { + GENERIC_LOG(Log::sceIo, level, "%.*s", STR_VIEW(string)); + } +} + +void Core_SendDebugScreenshot(const DebugScreenshotDesc &desc) { + if (g_debugScreenshotListener) { + g_debugScreenshotListener(desc); + } +} + BreakReason Core_BreakReason() { return g_breakReason; } @@ -642,11 +665,25 @@ static std::string ModuleAddressSuffix(u32 address) { void Core_MemoryException(u32 address, u32 accessSize, u32 pc, MemoryExceptionType type, std::string_view additionalInfo) { // In jit, we only flush PC when bIgnoreBadMemAccess is off. - char pcDetails[128]; pcDetails[0] = 0; - if ((CPUCore)g_Config.iCpuCore == CPUCore::INTERPRETER) { - snprintf(pcDetails, sizeof(pcDetails), " PC %08x%s RA %08x%s", currentMIPS->pc, ModuleAddressSuffix(currentMIPS->pc).c_str(), currentMIPS->r[MIPS_REG_RA], ModuleAddressSuffix(currentMIPS->r[MIPS_REG_RA]).c_str()); + switch ((CPUCore)g_Config.iCpuCore) { + case CPUCore::INTERPRETER: + snprintf(pcDetails, sizeof(pcDetails), "Interpreter: PC %08x%s RA %08x%s", + currentMIPS->pc, ModuleAddressSuffix(currentMIPS->pc).c_str(), + currentMIPS->r[MIPS_REG_RA], ModuleAddressSuffix(currentMIPS->r[MIPS_REG_RA]).c_str()); + break; + case CPUCore::JIT: + snprintf(pcDetails, sizeof(pcDetails), "JIT: (PC approximate)=%08x%s", pc, ModuleAddressSuffix(pc).c_str()); + break; + case CPUCore::JIT_IR: + snprintf(pcDetails, sizeof(pcDetails), "JIT_IR: (PC approximate)=%08x%s", pc, ModuleAddressSuffix(pc).c_str()); + break; + case CPUCore::IR_INTERPRETER: + snprintf(pcDetails, sizeof(pcDetails), "IR_INTERPRETER: (PC approximate)=%08x%s", pc, ModuleAddressSuffix(pc).c_str()); + break; + default: + break; } const std::string addressSuffix = ModuleAddressSuffix(address); @@ -665,15 +702,15 @@ void Core_MemoryException(u32 address, u32 accessSize, u32 pc, MemoryExceptionTy } const char *desc = MemoryExceptionTypeAsString(type); + char msg[512]; + snprintf(msg, sizeof(msg), "%s: SIGSEGV at %08x%s (size: %d bytes) %s\nHost:%.*s", desc, address, addressSuffix.c_str(), accessSize, pcDetails, STR_VIEW(additionalInfo)); if (action == ExceptionAction::Ignore) { - // Simplest logging and continue. - WARN_LOG(Log::MemMap, "%s: Invalid access at %08x%s (size %08x) %s%.*s", desc, address, addressSuffix.c_str(), accessSize, pcDetails, (int)additionalInfo.length(), additionalInfo.data()); + Core_SendDebugOutput(LogLevel::LWARNING, msg); return; } - const std::string stackTrace = FormatStackTrace(WalkCurrentStack(-1)); // Do the most detailed logging we can. - ERROR_LOG(Log::MemMap, "%s: Invalid access at %08x%s (size %08x) %s%.*s\n%s", desc, address, addressSuffix.c_str(), accessSize, pcDetails, (int)additionalInfo.length(), additionalInfo.data(), stackTrace.c_str()); + Core_SendDebugOutput(LogLevel::LERROR, StringFromFormat("%sMIPS call stack:\n%s", msg, stackTrace.c_str())); if (action == ExceptionAction::Break) { MIPSExceptionInfo &e = g_exceptionInfo; e = {}; @@ -706,7 +743,7 @@ void Core_MemoryExceptionHLE(MIPSState *mips, u32 address, u32 accessSize, Memor const HLEFunction *func = HLEGetFunctionBeingCalled(); const char *funcName = func ? func->name : "unknown"; - char args[512] = ""; + char args[256] = ""; if (func) { HLEFormatLogArgs(mips, args, sizeof(args), func->argmask); } @@ -724,22 +761,23 @@ void Core_MemoryExceptionHLE(MIPSState *mips, u32 address, u32 accessSize, Memor } const u32 pc = mips->pc; + const char *desc = MemoryExceptionTypeAsString(type); + char msg[512]; - snprintf(msg, sizeof(msg), "Invalid access in %s(%s) %s at %08x%s (size %08x) PC %08x%s RA %08x%s", - funcName, args, - extra, address, ModuleAddressSuffix(address).c_str(), accessSize, + snprintf(msg, sizeof(msg), "%s: Invalid access %s in %s(%s) at %08x%s (size %08x) PC %08x%s RA %08x%s", + desc, extra, funcName, args, + address, ModuleAddressSuffix(address).c_str(), accessSize, pc, ModuleAddressSuffix(pc).c_str(), mips->r[MIPS_REG_RA], ModuleAddressSuffix(mips->r[MIPS_REG_RA]).c_str()); - const char *desc = MemoryExceptionTypeAsString(type); if (action == ExceptionAction::Ignore) { // Simplest logging and continue. - WARN_LOG(Log::MemMap, "HLE %s: %s", MemoryExceptionTypeAsString(type), msg); + Core_SendDebugOutput(LogLevel::LWARNING, msg); return; } const std::string stackTrace = FormatStackTrace(WalkCurrentStack(-1)); - ERROR_LOG(Log::MemMap, "%s: %s\n%s", desc, msg, stackTrace.c_str()); + Core_SendDebugOutput(LogLevel::LERROR, StringFromFormat("%s\n%s", msg, stackTrace.c_str())); if (action == ExceptionAction::Break) { MIPSExceptionInfo &e = g_exceptionInfo; e = {}; @@ -757,7 +795,10 @@ void Core_MemoryExceptionHLE(MIPSState *mips, u32 address, u32 accessSize, Memor // Can't be ignored, must break. Not sure we can get a meaningful stack trace here (since the PC is invalid). void Core_ExecException(u32 address, u32 pc, ExecExceptionType type) { const char *desc = ExecExceptionTypeAsString(type); - WARN_LOG(Log::MemMap, "%s: Invalid exec address %08x%s pc=%08x%s ra=%08x%s", desc, address, ModuleAddressSuffix(address).c_str(), pc, ModuleAddressSuffix(pc).c_str(), currentMIPS->r[MIPS_REG_RA], ModuleAddressSuffix(currentMIPS->r[MIPS_REG_RA]).c_str()); + + char msg[512]; + snprintf(msg, sizeof(msg), "%s: Invalid exec address %08x%s pc=%08x%s ra=%08x%s", desc, address, ModuleAddressSuffix(address).c_str(), pc, ModuleAddressSuffix(pc).c_str(), currentMIPS->r[MIPS_REG_RA], ModuleAddressSuffix(currentMIPS->r[MIPS_REG_RA]).c_str()); + Core_SendDebugOutput(LogLevel::LERROR, msg); MIPSExceptionInfo &e = g_exceptionInfo; e = {}; @@ -781,15 +822,18 @@ void Core_BreakException(u32 pc) { const std::string pcSuffix = ModuleAddressSuffix(pc); + char msg[512]; + snprintf(msg, sizeof(msg), "CPU exception: break instruction hit at %08x%s. Ignoring (use --break=log for more details or --break=break to break)", pc, pcSuffix.c_str()); + const ExceptionAction action = ResolveExceptionAction((ExceptionAction)g_Config.iExceptionActionBreak); if (action == ExceptionAction::Ignore) { // Simplest logging and continue. - WARN_LOG(Log::CPU, "CPU exception: break instruction hit at %08x%s. Ignoring (use --break=log for more details or --break=break to break)", pc, pcSuffix.c_str()); + Core_SendDebugOutput(LogLevel::LINFO, StringFromFormat("Ignoring CPU exception: break instruction hit at %08x%s", pc, pcSuffix.c_str())); return; } const std::string stackTrace = FormatStackTrace(WalkCurrentStack(-1)); - ERROR_LOG(Log::CPU, "CPU exception: break instruction hit at %08x%s (ra=%08x%s)\n%s", pc, pcSuffix.c_str(), currentMIPS->r[MIPS_REG_RA], ModuleAddressSuffix(currentMIPS->r[MIPS_REG_RA]).c_str(), stackTrace.c_str()); + Core_SendDebugOutput(LogLevel::LERROR, StringFromFormat("%s\n%s", msg, stackTrace.c_str())); if (action == ExceptionAction::Break) { Core_Break(BreakReason::BreakInstruction, currentMIPS->pc); } diff --git a/Core/Core.h b/Core/Core.h index c43689aed5..632c9f11c9 100644 --- a/Core/Core.h +++ b/Core/Core.h @@ -215,6 +215,21 @@ void Core_BreakException(u32 pc); // Call when loading save states, etc. void Core_ResetException(); +// Used by headless/pspautotest to collect data for the diffs. Crash reports are also sent here. +// Log level is only used if the listener is not registered. +enum class LogLevel : int; + +enum GEBufferFormat : uint8_t; +struct DebugScreenshotDesc { + const uint8_t *data; + u32 stride; + u32 height; + GEBufferFormat format; +}; +void Core_SendDebugOutput(LogLevel level, std::string_view string); +void Core_SendDebugScreenshot(const DebugScreenshotDesc &desc); +void Core_RegisterDebugOutputListeners(std::function listener, std::function screenshotListener); + class MIPSState; // Shortcut, just calls Core_MemoryException with automatically determined parameters (function name, etc). void Core_MemoryExceptionHLE(MIPSState *mips, u32 address, u32 accessSize, MemoryExceptionType type); diff --git a/Core/Debugger/MemBlockInfo.cpp b/Core/Debugger/MemBlockInfo.cpp index 31cbed4c80..9a9379f473 100644 --- a/Core/Debugger/MemBlockInfo.cpp +++ b/Core/Debugger/MemBlockInfo.cpp @@ -569,7 +569,7 @@ void NotifyMemInfoCopy(uint32_t destPtr, uint32_t srcPtr, uint32_t size, const c info.pc = currentMIPS->pc; // Store the prefix for now. The correct tag will be calculated on flush. - info.tagLen = std::min(sizeof(info.tag), prefixLen); + info.tagLen = (uint8_t)std::min(sizeof(info.tag), prefixLen); memcpy(info.tag, prefix, info.tagLen); std::lock_guard guard(pendingWriteMutex); diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index c9c75946f7..b81a848fb8 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -940,7 +940,7 @@ int sceDisplaySetFramebuf(u32 topaddr, int linesize, int pixelformat, int sync) } } -bool __DisplayGetFramebuf(PSPPointer *topaddr, u32 *linesize, u32 *pixelFormat, int latchedMode) { +bool __DisplayGetFramebuf(PSPPointer *topaddr, u32 *linesize, GEBufferFormat *pixelFormat, int latchedMode) { const FrameBufferState &fbState = latchedMode == PSP_DISPLAY_SETBUF_NEXTFRAME ? latchedFramebuf : framebuf; if (topaddr != nullptr) (*topaddr).ptr = fbState.topaddr; diff --git a/Core/HLE/sceDisplay.h b/Core/HLE/sceDisplay.h index 2f8d05aa28..eef5d2c9da 100644 --- a/Core/HLE/sceDisplay.h +++ b/Core/HLE/sceDisplay.h @@ -19,6 +19,8 @@ #include "Core/MemMap.h" +enum GEBufferFormat : uint8_t; + void __DisplayInit(); void __DisplayDoState(PointerWrap &p); void __DisplayShutdown(); @@ -26,7 +28,7 @@ void __DisplayShutdown(); void Register_sceDisplay(); // Get information about the current framebuffer. -bool __DisplayGetFramebuf(PSPPointer *topaddr, u32 *linesize, u32 *pixelFormat, int mode); +bool __DisplayGetFramebuf(PSPPointer *topaddr, u32 *linesize, GEBufferFormat *pixelFormat, int mode); void __DisplaySetFramebuf(u32 topaddr, int linesize, int pixelformat, int sync); // Call this when resuming to avoid a small speedup burst diff --git a/Core/HLE/sceIo.cpp b/Core/HLE/sceIo.cpp index 3641881906..e54165c6fe 100644 --- a/Core/HLE/sceIo.cpp +++ b/Core/HLE/sceIo.cpp @@ -2037,8 +2037,7 @@ static u32 sceIoDevctl(const char *name, int cmd, u32 argAddr, int argLen, u32 o case EMULATOR_DEVCTL__SEND_OUTPUT: if (Memory::IsValidRange(argAddr, argLen)) { std::string data(Memory::GetCharPointerUnchecked(argAddr), argLen); - if (!System_SendDebugOutput(data)) - DEBUG_LOG(Log::sceIo, "%s", data.c_str()); + Core_SendDebugOutput(LogLevel::LINFO, data); if (PSP_CoreParameter().collectDebugOutput) *PSP_CoreParameter().collectDebugOutput += data; } @@ -2055,12 +2054,14 @@ static u32 sceIoDevctl(const char *name, int cmd, u32 argAddr, int argLen, u32 o case EMULATOR_DEVCTL__EMIT_SCREENSHOT: { - PSPPointer topaddr; - u32 linesize; - - __DisplayGetFramebuf(&topaddr, &linesize, nullptr, 0); + // TODO: Add a high-res path for screenshots, and maybe a way to specify the filename. // TODO: Convert based on pixel format / mode / something? - System_SendDebugScreenshot(&topaddr[0], linesize, 272); + DebugScreenshotDesc desc; + PSPPointer topaddr; + __DisplayGetFramebuf(&topaddr, &desc.stride, &desc.format, 0); + desc.data = &topaddr[0]; + desc.height = 272; + Core_SendDebugScreenshot(desc); return hleLogDebug(Log::sceIo, 0); } case EMULATOR_DEVCTL__TOGGLE_FASTFORWARD: diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 3b0212eb64..06488ec44d 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -1913,10 +1913,12 @@ int __KernelGPUReplay() { } if (result == GPURecord::ReplayResult::Done && PSP_CoreParameter().headLess && !PSP_CoreParameter().startBreak) { + DebugScreenshotDesc desc; PSPPointer topaddr; - u32 linesize = 512; - __DisplayGetFramebuf(&topaddr, &linesize, nullptr, 0); - System_SendDebugScreenshot(&topaddr[0], linesize, 272); + __DisplayGetFramebuf(&topaddr, &desc.stride, &desc.format, 0); + desc.data = &topaddr[0]; + desc.height = 272; + Core_SendDebugScreenshot(desc); Core_Stop(); } diff --git a/Core/MemFault.cpp b/Core/MemFault.cpp index cc584fbab7..21b1a8aef4 100644 --- a/Core/MemFault.cpp +++ b/Core/MemFault.cpp @@ -32,6 +32,8 @@ #include "Core/Util/DisArm64.h" #elif PPSSPP_ARCH(ARM) #include "ext/disarm.h" +#elif PPSSPP_ARCH(RISCV64) +#include "ext/riscv-disas.h" #elif PPSSPP_ARCH(LOONGARCH64) #include "ext/loongarch-disasm.h" #endif @@ -184,67 +186,12 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) { ArmLSInstructionInfo info{}; success = ArmAnalyzeLoadStore((uint32_t)codePtr, word, &info); #elif PPSSPP_ARCH(RISCV64) - // TODO: Put in a disassembler. - struct RiscVLSInstructionInfo { - int instructionSize; - bool isIntegerLoadStore; - bool isFPLoadStore; - int size; - bool isMemoryWrite; - }; - uint32_t word; memcpy(&word, codePtr, 4); - + // To ignore the access, we need to disassemble the instruction and modify context->CTX_PC RiscVLSInstructionInfo info{}; - // Compressed instructions have low bits 00, 01, or 10. - info.instructionSize = (word & 3) == 3 ? 4 : 2; + success = RiscVAnalyzeLoadStore((uint64_t)codePtr, word, &info); instructionSize = info.instructionSize; - - success = true; - switch (word & 0x7F) { - case 3: - info.isIntegerLoadStore = true; - info.size = 1 << ((word >> 12) & 3); - break; - case 7: - info.isFPLoadStore = true; - info.size = 1 << ((word >> 12) & 3); - break; - case 35: - info.isIntegerLoadStore = true; - info.isMemoryWrite = true; - info.size = 1 << ((word >> 12) & 3); - break; - case 39: - info.isFPLoadStore = true; - info.isMemoryWrite = true; - info.size = 1 << ((word >> 12) & 3); - break; - default: - // Compressed instruction. - switch (word & 0x6003) { - case 0x4000: - case 0x4002: - case 0x6000: - case 0x6002: - info.isIntegerLoadStore = true; - info.size = (word & 0x2000) != 0 ? 8 : 4; - info.isMemoryWrite = (word & 0x8000) != 0; - break; - case 0x2000: - case 0x2002: - info.isFPLoadStore = true; - info.size = 8; - info.isMemoryWrite = (word & 0x8000) != 0; - break; - default: - // Not a read or a write. - success = false; - break; - } - break; - } #elif PPSSPP_ARCH(LOONGARCH64) uint32_t word; memcpy(&word, codePtr, 4); @@ -308,11 +255,11 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) { std::string infoString = ""; std::string temp; if (MIPSComp::jit && MIPSComp::jit->DescribeCodePtr(codePtr, temp)) { - infoString += temp + "\n"; + infoString += temp + " "; } temp.clear(); if (DisassembleNativeAt(codePtr, instructionSize, &temp)) { - infoString += temp + "\n"; + infoString += "(" + temp + ") "; } // Either bIgnoreBadMemAccess is off, or we failed recovery analysis. @@ -320,7 +267,7 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) { uint32_t approximatePC = currentMIPS->pc; // TODO: Determine access size from the disassembled native instruction. We have some partial info already, // just need to clean it up. - Core_MemoryException(guestAddress, 0, approximatePC, type, infoString); + Core_MemoryException(guestAddress, info.OperandSizeInBytes(), approximatePC, type, infoString); // There's a small chance we can resume from this type of crash. g_lastCrashAddress = codePtr; @@ -333,7 +280,7 @@ bool HandleFault(uintptr_t hostAddress, void *ctx) { context->CTX_PC = crashHandler; else handled = false; - ERROR_LOG(Log::MemMap, "Bad memory access detected! %08x (%p) Stopping emulation. Info:\n%s", guestAddress, (void *)hostAddress, infoString.c_str()); + // ERROR_LOG(Log::MemMap, "Bad memory access detected! %08x (%p) Stopping emulation. Info:\n%s", guestAddress, (void *)hostAddress, infoString.c_str()); } inCrashHandler = false; @@ -373,16 +320,13 @@ std::vector WalkCurrentStack(int threadID) { std::string FormatStackTrace(const std::vector &frames) { std::stringstream str; for (const auto &frame : frames) { - if (frame.pc == 0xFFFFFFFF) { - // Bottom of stack, probably. - continue; - } + const u32 frameEntry = frame.entry == 0xFFFFFFFF ? 0 : frame.entry; std::string desc = g_symbolMap->GetDescription(frame.entry); char moduleDesc[96]; if (DescribeKernelModuleAddress(frame.entry, moduleDesc, sizeof(moduleDesc))) { - str << StringFromFormat("%s [%s] (%08x+%03x, pc: %08x sp: %08x)\n", desc.c_str(), moduleDesc, frame.entry, frame.pc - frame.entry, frame.pc, frame.sp); + str << StringFromFormat("%s [%s] (%08x+%03x, pc: %08x sp: %08x)\n", desc.c_str(), moduleDesc, frameEntry, frame.pc - frameEntry, frame.pc, frame.sp); } else { - str << StringFromFormat("%s (%08x+%03x, pc: %08x sp: %08x)\n", desc.c_str(), frame.entry, frame.pc - frame.entry, frame.pc, frame.sp); + str << StringFromFormat("%s (%08x+%03x, pc: %08x sp: %08x)\n", desc.c_str(), frameEntry, frame.pc - frameEntry, frame.pc, frame.sp); } } return str.str(); diff --git a/Core/System.cpp b/Core/System.cpp index 69d3dbfdcb..9e22df9527 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -36,6 +36,7 @@ #include "Common/System/OSD.h" #include "Common/Data/Text/I18n.h" #include "Common/File/Path.h" +#include "Common/StringUtils.h" #include "Common/File/FileUtil.h" #include "Common/File/DirListing.h" #include "Common/File/AndroidContentURI.h" @@ -359,7 +360,8 @@ static bool CPU_Init(FileLoader *fileLoader, IdentifiedFileType type, std::strin default: { // Trying to boot other things lands us here. We need to return a sensible error string. - ERROR_LOG(Log::Loader, "CPU_Init didn't recognize file. %s", errorString->c_str()); + ERROR_LOG(Log::Loader, "CPU_Init didn't recognize file: %s. %s", fileLoader->GetPath().c_str(), errorString->c_str()); + Core_SendDebugOutput(LogLevel::LINFO, StringFromFormat("File not recognized: %s. %s", fileLoader->GetPath().c_str(), errorString->c_str())); auto sy = GetI18NCategory(I18NCat::SYSTEM); if (errorString->empty()) { *errorString = sy->T("Not a PSP game"); diff --git a/Core/Util/DisArm64.h b/Core/Util/DisArm64.h index feb98ea9bc..c408965240 100644 --- a/Core/Util/DisArm64.h +++ b/Core/Util/DisArm64.h @@ -41,7 +41,7 @@ struct Arm64LSInstructionInfo { int Rn; int Rm; - // TODO: more. + int OperandSizeInBytes() const { return 1 << size; } }; bool Arm64AnalyzeLoadStore(uint64_t addr, uint32_t op, Arm64LSInstructionInfo *info); diff --git a/GPU/Common/VertexDecoderCommon.cpp b/GPU/Common/VertexDecoderCommon.cpp index 82c6a2af34..7fd37575b0 100644 --- a/GPU/Common/VertexDecoderCommon.cpp +++ b/GPU/Common/VertexDecoderCommon.cpp @@ -1623,6 +1623,7 @@ VertexDecoderJitCache::VertexDecoderJitCache() } void VertexDecoderJitCache::Clear() { + // TODO: These should check CoreParameter instead. if (g_Config.iCpuCore == (int)CPUCore::JIT || g_Config.iCpuCore == (int)CPUCore::JIT_IR) { ClearCodeSpace(0); } diff --git a/GPU/Debugger/Record.cpp b/GPU/Debugger/Record.cpp index 9d92296a28..0cc3eb881d 100644 --- a/GPU/Debugger/Record.cpp +++ b/GPU/Debugger/Record.cpp @@ -798,7 +798,8 @@ void Recorder::NotifyBeginFrame() { CheckEdramTrans(); struct DisplayBufData { PSPPointer topaddr; - u32 linesize, pixelFormat; + u32 linesize; + GEBufferFormat pixelFormat; }; DisplayBufData disp; diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index 65b1a59eb2..dfb7f52e7f 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -1240,9 +1240,6 @@ void System_Notify(SystemNotification notification) { } } -bool System_SendDebugOutput(std::string_view data) { return false; } -void System_SendDebugScreenshot(const uint8_t *data, int width, int height) {} - void UpdateWindowState(SDL_Window *window) { SDL_SetWindowTitle(window, g_windowState.title.c_str()); if (g_windowState.applyFullScreenNextFrame) { diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index edb8852837..4457f661e2 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -268,7 +268,7 @@ void DrawDisplayWindow(ImConfig &cfg, FramebufferManagerCommon *framebufferManag PSPPointer topaddr; u32 linesize; - u32 pixelFormat; + GEBufferFormat pixelFormat; __DisplayGetFramebuf(&topaddr, &linesize, &pixelFormat, cfg.displayLatched); diff --git a/UWP/PPSSPP_UWPMain.cpp b/UWP/PPSSPP_UWPMain.cpp index f9f6dd9c0b..7f585c1ea5 100644 --- a/UWP/PPSSPP_UWPMain.cpp +++ b/UWP/PPSSPP_UWPMain.cpp @@ -402,9 +402,6 @@ std::vector System_GetPropertyStringVec(SystemProperty prop) { } } -bool System_SendDebugOutput(std::string_view data) { return false; } -void System_SendDebugScreenshot(const uint8_t *data, int width, int height) {} - extern AudioBackend *g_audioBackend; int64_t System_GetPropertyInt(SystemProperty prop) { diff --git a/Windows/main.cpp b/Windows/main.cpp index e6fd35dd8d..f27468b19c 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -890,9 +890,6 @@ static std::string GetDefaultLangRegion() { } } -bool System_SendDebugOutput(std::string_view data) { return false; } -void System_SendDebugScreenshot(const uint8_t *data, int width, int height) {} - static const int EXIT_CODE_VULKAN_WORKS = 42; #ifndef _DEBUG diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index d8dafa02a2..029c05c7eb 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -291,9 +291,6 @@ void System_LaunchUrl(LaunchUrlType urlType, std::string_view url) { } } -bool System_SendDebugOutput(std::string_view data) { return false; } -void System_SendDebugScreenshot(const uint8_t *data, int width, int height) {} - std::string System_GetProperty(SystemProperty prop) { switch (prop) { case SYSPROP_NAME: diff --git a/ext/at3_standalone/atrac3plusdsp.cpp b/ext/at3_standalone/atrac3plusdsp.cpp index 63d4d7c57f..c920eb01c1 100644 --- a/ext/at3_standalone/atrac3plusdsp.cpp +++ b/ext/at3_standalone/atrac3plusdsp.cpp @@ -163,7 +163,7 @@ static void waves_synth(Atrac3pWaveSynthParams *synth_param, : 1.0f); inc = wave_param->freq_index; - pos = DEQUANT_PHASE(wave_param->phase_index) - (reg_offset ^ 128) * inc & 2047; + pos = (DEQUANT_PHASE(wave_param->phase_index) - (reg_offset ^ 128) * inc) & 2047; /* waveform generation */ for (i = 0; i < 128; i++) { diff --git a/ext/disarm.h b/ext/disarm.h index 901e74cd4f..5384baf4c1 100644 --- a/ext/disarm.h +++ b/ext/disarm.h @@ -45,6 +45,7 @@ struct ArmLSInstructionInfo { int Rm; // TODO: more. + int OperandSizeInBytes() const { return 1 << size; } }; bool ArmAnalyzeLoadStore(uint32_t addr, uint32_t op, ArmLSInstructionInfo *info); diff --git a/ext/loongarch-disasm.h b/ext/loongarch-disasm.h index 2cef32edb7..5140d05bd3 100644 --- a/ext/loongarch-disasm.h +++ b/ext/loongarch-disasm.h @@ -2761,6 +2761,8 @@ struct LoongArch64LSInstructionInfo { bool isFPLoadStore; int size; // 0 = 8-bit, 1 = 16-bit, 2 = 32-bit, 3 = 64-bit bool isMemoryWrite; + + int OperandSizeInBytes() const { return size; } }; uint32_t la_assemble(Ins *ins); diff --git a/ext/riscv-disas.cpp b/ext/riscv-disas.cpp index a4868ae50d..0825b68644 100644 --- a/ext/riscv-disas.cpp +++ b/ext/riscv-disas.cpp @@ -2526,3 +2526,54 @@ void riscv_disasm_inst(char *buf, size_t buflen, rv_isa isa, uint64_t pc, rv_ins decode_inst_lift_pseudo(&dec); decode_inst_format(buf, buflen, 32, &dec); } + +/* PPSSPP: analyze a load/store instruction, used by the JIT crash handler */ + +bool RiscVAnalyzeLoadStore(uint64_t addr, uint32_t word, RiscVLSInstructionInfo *info) +{ + *info = {}; + // Compressed instructions have low bits 00, 01, or 10. + info->instructionSize = (word & 3) == 3 ? 4 : 2; + + switch (word & 0x7F) { + case 3: + info->isIntegerLoadStore = true; + info->size = 1 << ((word >> 12) & 3); + return true; + case 7: + info->isFPLoadStore = true; + info->size = 1 << ((word >> 12) & 3); + return true; + case 35: + info->isIntegerLoadStore = true; + info->isMemoryWrite = true; + info->size = 1 << ((word >> 12) & 3); + return true; + case 39: + info->isFPLoadStore = true; + info->isMemoryWrite = true; + info->size = 1 << ((word >> 12) & 3); + return true; + default: + // Compressed instruction. + switch (word & 0x6003) { + case 0x4000: + case 0x4002: + case 0x6000: + case 0x6002: + info->isIntegerLoadStore = true; + info->size = (word & 0x2000) != 0 ? 8 : 4; + info->isMemoryWrite = (word & 0x8000) != 0; + return true; + case 0x2000: + case 0x2002: + info->isFPLoadStore = true; + info->size = 8; + info->isMemoryWrite = (word & 0x8000) != 0; + return true; + default: + // Not a read or a write. + return false; + } + } +} diff --git a/ext/riscv-disas.h b/ext/riscv-disas.h index b7fcdfa509..53cc475b9c 100644 --- a/ext/riscv-disas.h +++ b/ext/riscv-disas.h @@ -594,4 +594,16 @@ size_t riscv_inst_length(rv_inst inst); void riscv_inst_fetch(const uint8_t *data, rv_inst *instp, size_t *length); void riscv_disasm_inst(char *buf, size_t buflen, rv_isa isa, uint64_t pc, rv_inst inst); +// PPSSPP: information about a load/store instruction, used by the JIT crash handler. +struct RiscVLSInstructionInfo { + int instructionSize; + bool isIntegerLoadStore; + bool isFPLoadStore; + int size; // Size of the access, in bytes (1, 2, 4, or 8). + bool isMemoryWrite; + int OperandSizeInBytes() const { return size; } +}; + +bool RiscVAnalyzeLoadStore(uint64_t addr, uint32_t word, RiscVLSInstructionInfo *info); + #endif diff --git a/headless/Headless.cpp b/headless/Headless.cpp index 34b4587103..a61aac1ffc 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -9,6 +9,8 @@ // > -l --graphics=vulkan --screenshot-save=vt_ref.bmp "D:\PSP ISO\dump\Depth\11578 Virtua Tennis pause menu ULES00126_0002.zip" --resolution-scale=2 // Example command line for messing with the vsh: // > -l --vsh --memread=break --memwrite=break --break=break +// Example command line for looking at crash output: +// > -l --graphics=software pspautotests/tests/cpu/crash/crash_read_f32.prx // // NOTE: In MSVC, don't forget to set the working directory to $ProjectDir\.. in debug settings. @@ -144,10 +146,6 @@ void FlushDebugOutput() { } } -void SetWriteDebugOutput(bool flag) { - g_writeDebugOutput = flag; -} - void SetComparisonScreenshot(const Path &filename, double maxError) { g_comparisonScreenshot = filename; g_maxScreenshotError = maxError; @@ -174,31 +172,27 @@ void SendDebugOutput(std::string_view output) { } } -bool System_SendDebugOutput(std::string_view data) { - SendDebugOutput(data); - return true; -} - -void SendAndCollectOutput(const std::string &output) { +void SendAndCollectOutput(std::string_view output) { SendDebugOutput(output); if (PSP_CoreParameter().collectDebugOutput) { *PSP_CoreParameter().collectDebugOutput += output; } } -void System_SendDebugScreenshot(const uint8_t *data, int width, int height) { - const u8 *pixbuf = (const u8 *)data; - u32 w = width; - u32 h = height; +void SendDebugScreenshot(const DebugScreenshotDesc &desc) { + const u8 *pixbuf = (const u8 *)desc.data; + u32 w = desc.stride; + u32 h = desc.height; // We ignore the current framebuffer parameters and just grab the full screen. + // TOOD: Uh, why not use them? They should be the same. const static u32 FRAME_STRIDE = 512; const static u32 FRAME_WIDTH = 480; const static u32 FRAME_HEIGHT = 272; GPUDebugBuffer buffer; gpu->GetCurrentFramebuffer(buffer, GPU_DBG_FRAMEBUF_DISPLAY); - const std::vector pixels = TranslateDebugBufferToCompare(&buffer, 512, 272); + const std::vector pixels = TranslateDebugBufferToCompare(&buffer, FRAME_STRIDE, FRAME_HEIGHT); // If a screenshot save path is set, save unconditionally. if (!g_screenshotSavePath.empty()) { @@ -301,8 +295,9 @@ static bool RunAutoTest(GraphicsContext *graphicsContext, CoreParameter &corePar g_screenshotFailed = false; std::string output; - if (opt.compare || opt.bench) + if (opt.compare || opt.bench) { coreParameter.collectDebugOutput = &output; + } if (!PSP_InitStart(coreParameter)) { // Shouldn't really happen anymore, the errors happen later in PSP_InitUpdate. @@ -397,7 +392,7 @@ static bool RunAutoTest(GraphicsContext *graphicsContext, CoreParameter &corePar passed = CompareOutput(coreParameter.fileToStart, output, opt.verbose, opt.printEqualLines); } - // Screenshot comparison failures are recorded in System_SendDebugScreenshot. + // Screenshot comparison failures are recorded in SendDebugScreenshot. if (!g_comparisonScreenshot.empty() && g_screenshotFailed) { passed = false; } @@ -571,8 +566,6 @@ int main(int argc, const char* argv[]) { bool fullLog = cmdLineOptions.enableLogging.value_or(false); const char *stateToLoad = cmdLineOptions.stateToLoad.has_value() ? cmdLineOptions.stateToLoad.value().c_str() : nullptr; - GPUCore gpuCore = GPUCORE_SOFTWARE; - CPUCore cpuCore = CPUCore::JIT; bool oldAtrac = false; bool outputDebugStringLog = cmdLineOptions.odsLog.value_or(false); @@ -581,10 +574,6 @@ int main(int argc, const char* argv[]) { std::string mountIso = cmdLineOptions.mountIso.value_or(""); std::string mountRoot; - if (cmdLineOptions.cpuCore.has_value()) { - cpuCore = cmdLineOptions.cpuCore.value(); - } - if (cmdLineOptions.root.has_value()) { mountRoot = cmdLineOptions.root.value().c_str(); } @@ -621,6 +610,8 @@ int main(int argc, const char* argv[]) { g_Config.RestoreDefaults(RestoreSettingsBits::SETTINGS | RestoreSettingsBits::CONTROLS | RestoreSettingsBits::RECENT, false); + Core_RegisterDebugOutputListeners(&SendDebugOutput, &SendDebugScreenshot); + // Needs to be after log so we don't interfere with test output. g_threadManager.Init(cpu_info.num_cores, cpu_info.logical_cpu_count); @@ -630,10 +621,10 @@ int main(int argc, const char* argv[]) { // ApplyToConfig() below, so a matching command line flag can still override any of it - // ApplyToConfig() always has the final say on the settings in g_Config. // - // Somehow this affects the test execution of pspautotests/tests/gpu/vertices/morph.prx, even though - // we actually set the cpu core in CoreParameter below. Probably because we end up using the JIT vs non-JIT - // vertex decoder. - g_Config.iCpuCore = 0; + // This affects the test execution of pspautotests/tests/gpu/vertices/morph.prx, even though + // we actually set the cpu core in CoreParameter below. + // The check that decides that is in the DrawEngineCommon constructor. + g_Config.iCpuCore = (int)CPUCore::INTERPRETER; // NOTE: In headless mode, we never save the config. This is just for this run. g_Config.iDumpFileTypes = 0; @@ -674,18 +665,23 @@ int main(int argc, const char* argv[]) { g_Config.internalDataDirectory.clear(); g_Config.bUseOldAtrac = oldAtrac; g_Config.iForceEnableHLE = 0xFFFFFFFF; // Run all modules as HLE. We don't have anything to load in this context. + g_Config.bSkipDeadbeefFilling = false; // ApplyToConfig() has the final say, applied after RestoreDefaults() and the headless // overrides above, so a matching command line flag always wins. cmdLineOptions.ApplyToConfig(); + // This looks contradictory to the above. But, this preserves the old test behavior which apparently ran the JIT for the CPU + // but ended up running software vertex decoding due to the setting in g_Config. Yeah, it's a mess. + CPUCore cpuCore = CPUCore::JIT; + if (cmdLineOptions.cpuCore.has_value()) { + cpuCore = cmdLineOptions.cpuCore.value(); + } + + GPUCore gpuCore = GPUCORE_SOFTWARE; // Translate backend to core. We probably should consider merging these enums. if (!g_Config.bSoftwareRendering) { - if (!cmdLineOptions.gpuBackend.has_value()) { - fprintf(stderr, "No graphics backend specified, but software rendering is disabled. Use --graphics=software, gles, directx11, or vulkan.\n"); - return 1; - } - switch (cmdLineOptions.gpuBackend.value()) { + switch ((GPUBackend)g_Config.iGPUBackend) { case GPUBackend::OPENGL: gpuCore = GPUCORE_GLES; break; @@ -730,8 +726,8 @@ int main(int argc, const char* argv[]) { // TODO: This whole function should be refactored to set up CoreParameter in one place, // but not now. CoreParameter coreParameter; - coreParameter.cpuCore = cpuCore; // apprently this gets overwritten somehow by g_Config above. - coreParameter.gpuCore = gpuCore; + coreParameter.cpuCore = (CPUCore)cpuCore; + coreParameter.gpuCore = (GPUCore)gpuCore; coreParameter.graphicsContext = graphicsContext; coreParameter.enableSound = false; coreParameter.mountIso = mountIso.empty() ? Path() : Path(mountIso); @@ -786,8 +782,9 @@ int main(int argc, const char* argv[]) { if (cmdLineOptions.screenshotSaveKeepAlpha.has_value()) { g_screenshotSaveKeepAlpha = cmdLineOptions.screenshotSaveKeepAlpha.value(); } + SetWriteFailureScreenshot(!getenv("GITHUB_ACTIONS") && !testOptions.bench); - SetWriteDebugOutput(!testOptions.compare && !testOptions.bench); + g_writeDebugOutput = !testOptions.compare && !testOptions.bench; #if PPSSPP_PLATFORM(ANDROID) // For some reason the debugger installs it with this name? diff --git a/ios/main.mm b/ios/main.mm index 51c646b507..ccbcee2576 100644 --- a/ios/main.mm +++ b/ios/main.mm @@ -664,9 +664,6 @@ AudioBackend *System_CreateAudioBackend() { return nullptr; } -bool System_SendDebugOutput(std::string_view data) { return false; } -void System_SendDebugScreenshot(const uint8_t *data, int width, int height) {} - int main(int argc, char *argv[]) { version = [[[UIDevice currentDevice] systemVersion] UTF8String]; if (1 != sscanf(version.c_str(), "%d", &g_iosVersionMajor)) { diff --git a/libretro/libretro.cpp b/libretro/libretro.cpp index 142a0176e4..2a11aa6d5c 100644 --- a/libretro/libretro.cpp +++ b/libretro/libretro.cpp @@ -1939,9 +1939,6 @@ int64_t System_GetPropertyInt(SystemProperty prop) { return -1; } -bool System_SendDebugOutput(std::string_view data) { return false; } -void System_SendDebugScreenshot(const uint8_t *data, int width, int height) {} - float System_GetPropertyFloat(SystemProperty prop) { switch (prop) { case SYSPROP_DISPLAY_REFRESH_RATE: diff --git a/unittest/TestX64Emitter.cpp b/unittest/TestX64Emitter.cpp index 52433cdb91..a846461deb 100644 --- a/unittest/TestX64Emitter.cpp +++ b/unittest/TestX64Emitter.cpp @@ -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.operandSizeInBytes, 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; diff --git a/unittest/UnitTest.cpp b/unittest/UnitTest.cpp index 4b77ccc20b..f8c3578d6d 100644 --- a/unittest/UnitTest.cpp +++ b/unittest/UnitTest.cpp @@ -127,8 +127,6 @@ void System_RunOnMainThread(std::function) {} void System_AudioGetDebugStats(char *buf, size_t bufSize) { if (buf) buf[0] = '\0'; } void System_AudioClear() {} void System_AudioPushSamples(const s32 *audio, int numSamples, float volume) {} -bool System_SendDebugOutput(std::string_view data) { return false; } -void System_SendDebugScreenshot(const uint8_t *data, int width, int height) {} std::vector System_GetCameraDeviceList() { return std::vector(); } // Temporary hacks around annoying linking errors. Copied from Headless.