mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-17 00:10:31 +02:00
Mac ARM64: Switch JIT memory between writable and executable per thread
JIT memory is now mapped with MAP_JIT, and pthread_jit_write_protect_np() switches the calling thread's view of it, in place of two mprotect() calls for every block compiled and every exit linked. This makes compiling cheaper, which matters mostly in the first seconds after a game or state loads. A hardened runtime without the JIT entitlement refuses the mapping, and then it's mprotect() as before. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
9de0f6e45d
commit
ee5f480fc9
2 files changed
+69
-2
No files matched your search
+2
-2
@@ -99,8 +99,8 @@ public:
|
||||
// If not WX Exclusive, no need to call ProtectMemoryPages because we never change the protection from RWX.
|
||||
PoisonMemory(offset);
|
||||
ResetCodePtr(offset);
|
||||
if (PlatformIsWXExclusive() && offset != 0) {
|
||||
// Need to re-protect the part we didn't clear.
|
||||
if (PlatformIsWXExclusive()) {
|
||||
// Need to re-protect the part we didn't clear (with MAP_JIT, this thread's view of it all).
|
||||
ProtectMemoryPages(region, offset, MEM_PROT_READ | MEM_PROT_EXEC);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,14 @@
|
||||
#include <mach/vm_param.h>
|
||||
#endif
|
||||
|
||||
#if PPSSPP_PLATFORM(MAC) && PPSSPP_ARCH(ARM64)
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <pthread.h>
|
||||
#define USE_MAP_JIT
|
||||
#endif
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
@@ -183,7 +191,43 @@ void *AllocateExecutableMemory(size_t size) {
|
||||
|
||||
#else // Non-Windows platforms
|
||||
|
||||
#ifdef USE_MAP_JIT
|
||||
// With MAP_JIT, a thread switches all of its view of the JIT memory between writable and executable
|
||||
// with pthread_jit_write_protect_np(), far cheaper than an mprotect() for every block compiled and
|
||||
// linked. A hardened runtime without the JIT entitlement refuses the mapping: then mprotect() it is.
|
||||
static std::mutex g_mapJitLock;
|
||||
static int g_mapJitState; // 0 untried, 1 in use, -1 refused.
|
||||
static std::vector<std::pair<uintptr_t, uintptr_t>> g_mapJitRanges;
|
||||
|
||||
static bool IsMapJitMemory(const void *ptr) {
|
||||
std::lock_guard<std::mutex> guard(g_mapJitLock);
|
||||
for (const auto &range : g_mapJitRanges) {
|
||||
if ((uintptr_t)ptr >= range.first && (uintptr_t)ptr < range.second)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
void *AllocateExecutableMemory(size_t size) {
|
||||
#ifdef USE_MAP_JIT
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(g_mapJitLock);
|
||||
if (g_mapJitState >= 0) {
|
||||
void *ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE | MAP_JIT, -1, 0);
|
||||
if (ptr != MAP_FAILED) {
|
||||
g_mapJitState = 1;
|
||||
g_mapJitRanges.emplace_back((uintptr_t)ptr, (uintptr_t)ptr + size);
|
||||
return ptr;
|
||||
}
|
||||
if (g_mapJitState == 0) {
|
||||
WARN_LOG(Log::MemMap, "MAP_JIT refused (errno=%d), using mprotect", errno);
|
||||
g_mapJitState = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static char *map_hint = nullptr;
|
||||
|
||||
#if PPSSPP_ARCH(AMD64)
|
||||
@@ -339,6 +383,19 @@ void FreeMemoryPages(void *ptr, size_t size) {
|
||||
}
|
||||
|
||||
void FreeExecutableMemory(void *ptr, size_t size) {
|
||||
#ifdef USE_MAP_JIT
|
||||
if (IsMapJitMemory(ptr)) {
|
||||
std::lock_guard<std::mutex> guard(g_mapJitLock);
|
||||
for (size_t i = 0; i < g_mapJitRanges.size(); i++) {
|
||||
if (g_mapJitRanges[i].first == (uintptr_t)ptr) {
|
||||
g_mapJitRanges.erase(g_mapJitRanges.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Back to executable, which the protection change before freeing undid.
|
||||
pthread_jit_write_protect_np(1);
|
||||
}
|
||||
#endif
|
||||
FreeMemoryPages(ptr, size);
|
||||
}
|
||||
|
||||
@@ -374,6 +431,16 @@ bool ProtectMemoryPages(const void* ptr, size_t size, uint32_t memProtFlags) {
|
||||
_assert_msg_(false, "Bad memory protect flags %d: W^X is in effect, can't both write and exec", memProtFlags);
|
||||
}
|
||||
}
|
||||
#ifdef USE_MAP_JIT
|
||||
if (IsMapJitMemory(ptr)) {
|
||||
_assert_msg_((memProtFlags & (MEM_PROT_WRITE | MEM_PROT_EXEC)) != 0, "MAP_JIT memory is writable or executable");
|
||||
pthread_jit_write_protect_np((memProtFlags & MEM_PROT_WRITE) == 0);
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
if (size == 0)
|
||||
return true;
|
||||
|
||||
// Note - VirtualProtect will affect the full pages containing the requested range.
|
||||
// mprotect does not seem to, at least not on Android unless I made a mistake somewhere, so we manually round.
|
||||
#ifdef _WIN32
|
||||
|
||||
Reference in new issue
Block a user