Play-/Source/ee/EeExecutor.cpp
Jean-Philip Desjardins e16703ac58
Some checks failed
Build Linux / build_linux (push) Has been cancelled
Build Android / build_android (apk) (push) Has been cancelled
Build Android / build_android (libretro) (push) Has been cancelled
Build iOS / build_ios (push) Has been cancelled
Build JavaScript / build_js (push) Has been cancelled
Build Linux ARM32 / build_linux_arm32 (push) Has been cancelled
Build Linux ARM64 / build_linux_arm64 (push) Has been cancelled
Build macOS / build_macos (push) Has been cancelled
Build Windows / build_windows (x86_32, Visual Studio 16 2019, installer32.nsi, win32_msvc2019, Win32) (push) Has been cancelled
Build Windows / build_windows (x86_64, Visual Studio 16 2019, installer64.nsi, win64_msvc2019_64, x64) (push) Has been cancelled
Build Windows Psf / build_windows_psf (off, x86_64, Visual Studio 16 2019, installer64.nsi, x64) (push) Has been cancelled
Build Windows Psf / build_windows_psf (on, x86_64, Visual Studio 16 2019, installer64.nsi, x64) (push) Has been cancelled
Check Format / run_clangformat (push) Has been cancelled
Clear idle loop blocks upon reset.
2025-02-25 19:06:20 -05:00

350 lines
9.5 KiB
C++

#include "EeExecutor.h"
#include "../Ps2Const.h"
#include "AlignedAlloc.h"
#include "EeBasicBlock.h"
#include "xxhash.h"
#if defined(__unix__) || defined(__ANDROID__) || defined(__APPLE__)
#include <sys/mman.h>
#endif
#if defined(__APPLE__)
#include <TargetConditionals.h>
#if TARGET_CPU_ARM
#define DISABLE_PROTECTION
#define STATE_FLAVOR ARM_THREAD_STATE32
#define STATE_FLAVOR_COUNT ARM_THREAD_STATE32_COUNT
#elif TARGET_CPU_ARM64
#define STATE_FLAVOR ARM_THREAD_STATE64
#define STATE_FLAVOR_COUNT ARM_THREAD_STATE64_COUNT
#elif TARGET_CPU_X86
#define STATE_FLAVOR x86_THREAD_STATE32
#define STATE_FLAVOR_COUNT x86_THREAD_STATE32_COUNT
#elif TARGET_CPU_X86_64
#define STATE_FLAVOR x86_THREAD_STATE64
#define STATE_FLAVOR_COUNT x86_THREAD_STATE64_COUNT
#else
#error Unsupported CPU architecture
#endif
#if TARGET_OS_TV
#define DISABLE_PROTECTION
#endif
#endif
static CEeExecutor* g_eeExecutor = nullptr;
CEeExecutor::CEeExecutor(CMIPS& context, uint8* ram)
: CGenericMipsExecutor(context, 0x20000000, BLOCK_CATEGORY_PS2_EE)
, m_ram(ram)
{
m_pageSize = framework_getpagesize();
}
void CEeExecutor::SetBlockFpRoundingModes(BlockFpRoundingModeMap blockFpRoundingModes)
{
m_blockFpRoundingModes = std::move(blockFpRoundingModes);
}
void CEeExecutor::SetIdleLoopBlocks(IdleLoopBlockSet idleLoopBlocks)
{
m_idleLoopBlocks = std::move(idleLoopBlocks);
}
void CEeExecutor::AddExceptionHandler()
{
assert(g_eeExecutor == nullptr);
g_eeExecutor = this;
#ifdef DISABLE_PROTECTION
return;
#endif
#if defined(_WIN32)
m_handler = AddVectoredExceptionHandler(TRUE, &CEeExecutor::HandleException);
assert(m_handler != NULL);
#elif defined(__unix__) || defined(__ANDROID__)
struct sigaction sigAction;
sigAction.sa_handler = nullptr;
sigAction.sa_sigaction = &HandleException;
sigAction.sa_flags = SA_SIGINFO;
sigemptyset(&sigAction.sa_mask);
int result = sigaction(SIGSEGV, &sigAction, nullptr);
assert(result >= 0);
#elif defined(__APPLE__) && !TARGET_OS_TV
if(!m_running)
{
kern_return_t result = mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &m_port);
assert(result == KERN_SUCCESS);
m_running = true;
m_handlerThread = std::thread([this]() { HandlerThreadProc(); });
}
AttachExceptionHandlerToThread();
#endif
}
void CEeExecutor::RemoveExceptionHandler()
{
#ifndef DISABLE_PROTECTION
#if defined(_WIN32)
RemoveVectoredExceptionHandler(m_handler);
#elif defined(__APPLE__) && !TARGET_OS_TV
m_running = false;
m_handlerThread.join();
#endif
#endif //!DISABLE_PROTECTION
g_eeExecutor = nullptr;
}
void CEeExecutor::AttachExceptionHandlerToThread()
{
//Only necessary for macOS and iOS since the handler is set on a thread basis
#if defined(__APPLE__) && !TARGET_OS_TV
assert(m_running);
auto result = mach_port_insert_right(mach_task_self(), m_port, m_port, MACH_MSG_TYPE_MAKE_SEND);
assert(result == KERN_SUCCESS);
result = thread_set_exception_ports(mach_thread_self(), EXC_MASK_BAD_ACCESS, m_port, EXCEPTION_STATE | MACH_EXCEPTION_CODES, STATE_FLAVOR);
assert(result == KERN_SUCCESS);
result = mach_port_mod_refs(mach_task_self(), m_port, MACH_PORT_RIGHT_SEND, -1);
assert(result == KERN_SUCCESS);
#endif
}
void CEeExecutor::Reset()
{
SetMemoryProtected(m_ram, PS2::EE_RAM_SIZE, false);
m_cachedBlocks.clear();
m_blockFpRoundingModes.clear();
m_idleLoopBlocks.clear();
CGenericMipsExecutor::Reset();
}
void CEeExecutor::ClearActiveBlocksInRange(uint32 start, uint32 end, bool executing)
{
uint32 rangeSize = end - start;
SetMemoryProtected(m_ram + start, rangeSize, false);
CGenericMipsExecutor::ClearActiveBlocksInRange(start, end, executing);
}
BasicBlockPtr CEeExecutor::BlockFactory(CMIPS& context, uint32 start, uint32 end)
{
uint32 blockSize = (end - start) + 4;
//Kernel area is below 0x100000 and isn't protected. Some games will write code in there
//but it is safe to assume that it won't change (code writes some data just besides itself
//so it keeps generating exceptions, making the game slower)
if(start >= 0x100000 && start < PS2::EE_RAM_SIZE)
{
SetMemoryProtected(m_ram + start, blockSize, true);
}
auto blockMemory = reinterpret_cast<uint32*>(alloca(blockSize));
for(uint32 address = start; address <= end; address += 4)
{
uint32 index = (address - start) / 4;
uint32 opcode = m_context.m_pMemoryMap->GetInstruction(address);
blockMemory[index] = opcode;
}
auto xxHash = XXH3_128bits(blockMemory, blockSize);
uint128 hash;
memcpy(&hash, &xxHash, sizeof(xxHash));
static_assert(sizeof(hash) == sizeof(xxHash));
auto blockKey = std::make_pair(hash, blockSize);
bool hasBreakpoint = m_context.HasBreakpointInRange(start, end);
if(!hasBreakpoint)
{
auto blockIterator = m_cachedBlocks.find(blockKey);
if(blockIterator != std::end(m_cachedBlocks))
{
const auto& basicBlock(blockIterator->second);
if(basicBlock->GetBeginAddress() == start && basicBlock->GetEndAddress() == end)
{
uint32 recycleCount = basicBlock->GetRecycleCount();
basicBlock->SetRecycleCount(std::min<uint32>(RECYCLE_NOLINK_THRESHOLD, recycleCount + 1));
return basicBlock;
}
else
{
auto result = std::make_shared<CEeBasicBlock>(context, start, end, m_blockCategory);
result->CopyFunctionFrom(basicBlock);
return result;
}
}
}
auto result = std::make_shared<CEeBasicBlock>(context, start, end, m_blockCategory);
if(auto blockFpRoundingModeIterator = m_blockFpRoundingModes.find(start);
blockFpRoundingModeIterator != std::end(m_blockFpRoundingModes))
{
result->SetFpRoundingMode(blockFpRoundingModeIterator->second);
}
if(m_idleLoopBlocks.count(start))
{
result->SetIsIdleLoopBlock();
}
result->Compile();
if(!hasBreakpoint)
{
m_cachedBlocks.insert(std::make_pair(blockKey, result));
}
return result;
}
bool CEeExecutor::HandleAccessFault(intptr_t ptr)
{
ptrdiff_t addr = reinterpret_cast<uint8*>(ptr) - m_ram;
if(addr >= 0 && addr < PS2::EE_RAM_SIZE)
{
addr &= ~(m_pageSize - 1);
ClearActiveBlocksInRange(addr, addr + m_pageSize, true);
return true;
}
return false;
}
void CEeExecutor::SetMemoryProtected(void* addr, size_t size, bool protect)
{
#ifdef DISABLE_PROTECTION
return;
#endif
#if defined(_WIN32)
DWORD oldProtect = 0;
BOOL result = VirtualProtect(addr, size, protect ? PAGE_READONLY : PAGE_READWRITE, &oldProtect);
assert(result == TRUE);
#elif defined(__EMSCRIPTEN__)
//Not supported on JavaScript/WebAssembly env
#elif defined(__unix__) || defined(__ANDROID__) || defined(__APPLE__)
uintptr_t addrValue = reinterpret_cast<uintptr_t>(addr) & ~(m_pageSize - 1);
addr = reinterpret_cast<void*>(addrValue);
size = size + (m_pageSize - 1) & ~(m_pageSize - 1);
int result = mprotect(addr, size, protect ? PROT_READ : PROT_READ | PROT_WRITE);
assert(result >= 0);
#else
assert(false);
#endif
}
#if defined(_WIN32)
LONG WINAPI CEeExecutor::HandleException(_EXCEPTION_POINTERS* exceptionInfo)
{
return g_eeExecutor->HandleExceptionInternal(exceptionInfo);
}
LONG CEeExecutor::HandleExceptionInternal(_EXCEPTION_POINTERS* exceptionInfo)
{
auto exceptionRecord = exceptionInfo->ExceptionRecord;
if(exceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
{
if(HandleAccessFault(exceptionRecord->ExceptionInformation[1]))
{
return EXCEPTION_CONTINUE_EXECUTION;
}
}
return EXCEPTION_CONTINUE_SEARCH;
}
#elif defined(__unix__) || defined(__ANDROID__)
void CEeExecutor::HandleException(int sigId, siginfo_t* sigInfo, void* baseContext)
{
g_eeExecutor->HandleExceptionInternal(sigId, sigInfo, baseContext);
}
void CEeExecutor::HandleExceptionInternal(int sigId, siginfo_t* sigInfo, void* baseContext)
{
if(sigId != SIGSEGV) return;
if(HandleAccessFault(reinterpret_cast<intptr_t>(sigInfo->si_addr)))
{
return;
}
signal(SIGSEGV, SIG_DFL);
}
#elif defined(__APPLE__) && !TARGET_OS_TV
void CEeExecutor::HandlerThreadProc()
{
#pragma pack(push, 4)
struct INPUT_MESSAGE
{
mach_msg_header_t head;
NDR_record_t ndr;
exception_type_t exception;
mach_msg_type_number_t codeCount;
intptr_t code[2];
int flavor;
mach_msg_type_number_t stateCount;
natural_t state[STATE_FLAVOR_COUNT];
mach_msg_trailer_t trailer;
};
struct OUTPUT_MESSAGE
{
mach_msg_header_t head;
NDR_record_t ndr;
kern_return_t result;
int flavor;
mach_msg_type_number_t stateCount;
natural_t state[STATE_FLAVOR_COUNT];
};
#pragma pack(pop)
while(m_running)
{
kern_return_t result = KERN_SUCCESS;
INPUT_MESSAGE inMsg;
result = mach_msg(&inMsg.head, MACH_RCV_MSG | MACH_RCV_LARGE | MACH_RCV_TIMEOUT, 0, sizeof(inMsg), m_port, 1000, MACH_PORT_NULL);
if(result == MACH_RCV_TIMED_OUT) continue;
assert(result == KERN_SUCCESS);
assert(inMsg.head.msgh_id == 2406); //MACH_EXCEPTION_RAISE_RPC
assert(inMsg.flavor == STATE_FLAVOR);
assert(inMsg.stateCount == STATE_FLAVOR_COUNT);
bool success = HandleAccessFault(inMsg.code[1]);
OUTPUT_MESSAGE outMsg;
outMsg.head.msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(inMsg.head.msgh_bits), 0);
outMsg.head.msgh_remote_port = inMsg.head.msgh_remote_port;
outMsg.head.msgh_local_port = MACH_PORT_NULL;
outMsg.head.msgh_id = inMsg.head.msgh_id + 100;
outMsg.head.msgh_size = sizeof(outMsg);
outMsg.ndr = inMsg.ndr;
if(success)
{
outMsg.result = KERN_SUCCESS;
outMsg.flavor = STATE_FLAVOR;
outMsg.stateCount = STATE_FLAVOR_COUNT;
memcpy(outMsg.state, inMsg.state, STATE_FLAVOR_COUNT * sizeof(natural_t));
}
else
{
outMsg.result = KERN_FAILURE;
outMsg.flavor = 0;
outMsg.stateCount = 0;
}
result = mach_msg(&outMsg.head, MACH_SEND_MSG | MACH_RCV_LARGE, sizeof(outMsg), 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
assert(result == KERN_SUCCESS);
}
}
#endif