Skip to content

Commit e6b6bfe

Browse files
committed
MemoryUtil: platform-specific allocation tweaks
1 parent b78e3b6 commit e6b6bfe

11 files changed

Lines changed: 226 additions & 16 deletions

File tree

Source/Core/Common/Arm64Emitter.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ void ARM64XEmitter::Write32(u32 value)
118118
return;
119119
}
120120

121-
std::memcpy(m_code, &value, sizeof(u32));
121+
std::memcpy(m_code + m_writable_region_diff, &value, sizeof(u32));
122122
m_code += sizeof(u32);
123123
}
124124

@@ -730,7 +730,7 @@ void ARM64XEmitter::SetJumpTarget(FixupBranch const& branch)
730730
break;
731731
}
732732

733-
std::memcpy(branch.ptr, &inst, sizeof(inst));
733+
std::memcpy(branch.ptr + m_writable_region_diff, &inst, sizeof(inst));
734734
}
735735

736736
FixupBranch ARM64XEmitter::WriteFixupBranch()

Source/Core/Common/Arm64Emitter.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,8 @@ class ARM64XEmitter
618618
// Writes that would reach this memory are refused and will set the m_write_failed flag instead.
619619
u8* m_code_end = nullptr;
620620

621+
ptrdiff_t m_writable_region_diff = 0;
622+
621623
u8* m_lastCacheFlushEnd = nullptr;
622624

623625
// Set to true when a write request happens that would write past m_code_end.
@@ -690,6 +692,9 @@ class ARM64XEmitter
690692
void FlushIcache();
691693
void FlushIcacheSection(u8* start, u8* end);
692694

695+
ptrdiff_t GetWritableRegionDiff() { return m_writable_region_diff; }
696+
void SetWritableRegionDiff(ptrdiff_t diff) { m_writable_region_diff = diff; }
697+
693698
// Should be checked after a block of code has been generated to see if the code has been
694699
// successfully written to memory. Do not call the generated code when this returns true!
695700
bool HasWriteFailed() const { return m_write_failed; }
@@ -1488,7 +1493,7 @@ class ARM64CodeBlock : public Common::CodeBlock<ARM64XEmitter>
14881493

14891494
for (size_t i = 0; i < region_size; i += sizeof(u32))
14901495
{
1491-
std::memcpy(region + i, &brk_0, sizeof(u32));
1496+
std::memcpy(region + writable_region_diff + i, &brk_0, sizeof(u32));
14921497
}
14931498
}
14941499
};

Source/Core/Common/CodeBlock.h

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class CodeBlock : public T
3232
size_t region_size = 0;
3333
// Original size of the region we allocated.
3434
size_t total_region_size = 0;
35+
// Offset from executable region to writable region (0 when not dual-mapped).
36+
ptrdiff_t writable_region_diff = 0;
3537

3638
bool m_is_child = false;
3739
std::vector<CodeBlock*> m_children;
@@ -58,6 +60,13 @@ class CodeBlock : public T
5860
else
5961
region = static_cast<u8*>(Common::AllocateMemoryPages(total_region_size));
6062
T::SetCodePtr(region, region + size);
63+
64+
#if defined(IPHONEOS) || (defined(__APPLE__) && defined(__aarch64__) && !TARGET_OS_IPHONE)
65+
if constexpr (executable)
66+
writable_region_diff = Common::AllocateWritableRegionAndGetDiff(region, size);
67+
#endif
68+
69+
T::SetWritableRegionDiff(writable_region_diff);
6170
}
6271

6372
// Always clear code space with breakpoints, so that if someone accidentally executes
@@ -72,15 +81,27 @@ class CodeBlock : public T
7281
void FreeCodeSpace()
7382
{
7483
ASSERT(!m_is_child);
75-
Common::FreeMemoryPages(region, total_region_size);
84+
#if defined(IPHONEOS) || (defined(__APPLE__) && defined(__aarch64__) && !TARGET_OS_IPHONE)
85+
if constexpr (executable)
86+
{
87+
Common::FreeWritableRegion(region, total_region_size, writable_region_diff);
88+
Common::FreeExecutableMemory(region, total_region_size);
89+
}
90+
else
91+
#endif
92+
{
93+
Common::FreeMemoryPages(region, total_region_size);
94+
}
7695
region = nullptr;
7796
region_size = 0;
7897
total_region_size = 0;
98+
writable_region_diff = 0;
7999
for (CodeBlock* child : m_children)
80100
{
81101
child->region = nullptr;
82102
child->region_size = 0;
83103
child->total_region_size = 0;
104+
child->writable_region_diff = 0;
84105
}
85106
}
86107

@@ -90,6 +111,7 @@ class CodeBlock : public T
90111
return ptr >= region && ptr < (region + total_region_size);
91112
}
92113
u8* GetRegionPtr() { return region; }
114+
ptrdiff_t GetWritableRegionDiff() const { return writable_region_diff; }
93115
void WriteProtect(bool allow_execute)
94116
{
95117
Common::WriteProtectMemory(region, region_size, allow_execute);
@@ -127,6 +149,7 @@ class CodeBlock : public T
127149
child->region = child_region;
128150
child->region_size = child_size;
129151
child->total_region_size = child_size;
152+
child->writable_region_diff = writable_region_diff;
130153
child->ResetCodePtr();
131154
m_children.emplace_back(child);
132155
}

Source/Core/Common/MemoryUtil.cpp

Lines changed: 137 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
#if defined(_M_ARM_64) && defined(__APPLE__)
2020
#include <pthread.h>
2121
#endif
22+
#if defined(__APPLE__) && defined(__aarch64__)
23+
#include <mach/mach.h>
24+
#endif
2225
#if defined __APPLE__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __NetBSD__
2326
#include <sys/sysctl.h>
2427
#elif defined __HAIKU__
@@ -30,6 +33,30 @@
3033

3134
#ifdef IPHONEOS
3235
#include "Common/JITMemoryTracker.h"
36+
#ifdef __LIBRETRO__
37+
#include <algorithm>
38+
#include <mutex>
39+
#include <vector>
40+
#include "DolphinLibretro/Common/Globals.h"
41+
static void* s_last_exec_rw = nullptr;
42+
static unsigned s_last_exec_mode = 0;
43+
// Regions the frontend allocated for us. Whether they are dual-mapped says
44+
// nothing about who owns them, so ownership is recorded rather than inferred.
45+
static std::mutex s_frontend_regions_mutex;
46+
static std::vector<void*> s_frontend_regions;
47+
#endif
48+
#endif
49+
50+
#ifdef IPHONEOS
51+
namespace Common
52+
{
53+
static JitType g_jit_type = JitType::Legacy;
54+
55+
void SetJitType(JitType type)
56+
{
57+
g_jit_type = type;
58+
}
59+
} // namespace Common
3360
#endif
3461

3562
namespace Common
@@ -43,19 +70,43 @@ static JITMemoryTracker g_jit_memory_tracker;
4370

4471
void* AllocateExecutableMemory(size_t size)
4572
{
73+
#if defined(IPHONEOS) && defined(__LIBRETRO__)
74+
{
75+
struct retro_exec_mem_alloc alloc = {};
76+
alloc.version = 1;
77+
alloc.size = size;
78+
if (Libretro::environ_cb &&
79+
Libretro::environ_cb(RETRO_ENVIRONMENT_EXEC_MEM_ALLOC, &alloc) &&
80+
alloc.mode != RETRO_EXEC_MEM_MODE_UNAVAILABLE &&
81+
alloc.rx != nullptr)
82+
{
83+
s_last_exec_rw = alloc.rw;
84+
s_last_exec_mode = alloc.mode;
85+
SetJitType(alloc.mode == RETRO_EXEC_MEM_MODE_DUAL_MAP ? JitType::LuckTXM :
86+
JitType::Legacy);
87+
{
88+
std::scoped_lock lk(s_frontend_regions_mutex);
89+
s_frontend_regions.push_back(alloc.rx);
90+
}
91+
g_jit_memory_tracker.RegisterJITRegion(alloc.rx, size);
92+
return alloc.rx;
93+
}
94+
}
95+
#endif
4696
#if defined(_WIN32)
4797
void* ptr = VirtualAlloc(nullptr, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
98+
#elif defined(__APPLE__) && defined(__aarch64__) && !defined(IPHONEOS)
99+
// macOS ARM: mmap R-X for dual mapping (no MAP_JIT, which prevents vm_remap)
100+
void* ptr = mmap(nullptr, size, PROT_READ | PROT_EXEC, MAP_ANON | MAP_PRIVATE, -1, 0);
101+
if (ptr == MAP_FAILED)
102+
ptr = nullptr;
48103
#else
49104
int map_flags = MAP_ANON | MAP_PRIVATE;
50105
#if defined(__APPLE__) && !defined(IPHONEOS)
51106
map_flags |= MAP_JIT;
52107
#endif
53108

54-
int map_prot = PROT_READ | PROT_EXEC;
55-
#ifndef IPHONEOS
56-
// The default protection is r-x on non-iOS platforms.
57-
map_prot |= PROT_WRITE;
58-
#endif
109+
int map_prot = PROT_READ | PROT_WRITE | PROT_EXEC;
59110

60111
void* ptr = mmap(nullptr, size, map_prot, map_flags, -1, 0);
61112
if (ptr == MAP_FAILED)
@@ -142,12 +193,14 @@ void JITPageWriteDisableExecuteEnable()
142193
#else
143194
void JITPageWriteEnableExecuteDisable(void* ptr)
144195
{
145-
g_jit_memory_tracker.JITRegionWriteEnableExecuteDisable(ptr);
196+
if (g_jit_type == JitType::Legacy)
197+
g_jit_memory_tracker.JITRegionWriteEnableExecuteDisable(ptr);
146198
}
147199

148200
void JITPageWriteDisableExecuteEnable(void* ptr)
149201
{
150-
g_jit_memory_tracker.JITRegionWriteDisableExecuteEnable(ptr);
202+
if (g_jit_type == JitType::Legacy)
203+
g_jit_memory_tracker.JITRegionWriteDisableExecuteEnable(ptr);
151204
}
152205
#endif
153206

@@ -317,4 +370,81 @@ size_t MemPhysical()
317370
#endif
318371
}
319372

373+
#if defined(__APPLE__) && defined(__aarch64__)
374+
ptrdiff_t AllocateWritableRegionAndGetDiff(void* rx_ptr, size_t size)
375+
{
376+
#if defined(IPHONEOS) && defined(__LIBRETRO__)
377+
if (s_last_exec_rw && s_last_exec_mode == RETRO_EXEC_MEM_MODE_DUAL_MAP)
378+
{
379+
ptrdiff_t diff = static_cast<u8*>(s_last_exec_rw) - static_cast<u8*>(rx_ptr);
380+
s_last_exec_rw = nullptr;
381+
return diff;
382+
}
383+
return 0;
384+
#endif
385+
vm_address_t rw_region = 0;
386+
vm_prot_t cur_protection = 0;
387+
vm_prot_t max_protection = 0;
388+
389+
kern_return_t retval =
390+
vm_remap(mach_task_self(), &rw_region, size, 0, true, mach_task_self(),
391+
(vm_address_t)rx_ptr, false, &cur_protection, &max_protection,
392+
VM_INHERIT_DEFAULT);
393+
if (retval != KERN_SUCCESS)
394+
{
395+
PanicAlertFmt("AllocateWritableRegionAndGetDiff failed! vm_remap returned {0:#x}", retval);
396+
return 0;
397+
}
398+
399+
u8* rw_ptr = reinterpret_cast<u8*>(rw_region);
400+
if (mprotect(rw_ptr, size, PROT_READ | PROT_WRITE) != 0)
401+
{
402+
PanicAlertFmt("AllocateWritableRegionAndGetDiff failed! mprotect returned {}",
403+
LastStrerrorString());
404+
return 0;
405+
}
406+
407+
return rw_ptr - static_cast<u8*>(rx_ptr);
408+
}
409+
410+
void FreeWritableRegion(void* rx_ptr, size_t size, ptrdiff_t diff)
411+
{
412+
#if defined(IPHONEOS) && defined(__LIBRETRO__)
413+
// Frontend owns the writable region; nothing to do here.
414+
// FreeExecutableMemory will tell the frontend to free everything.
415+
#else
416+
if (diff == 0)
417+
return;
418+
u8* rw_ptr = static_cast<u8*>(rx_ptr) + diff;
419+
vm_deallocate(mach_task_self(), reinterpret_cast<vm_address_t>(rw_ptr), size);
420+
#endif
421+
}
422+
423+
void FreeExecutableMemory(void* ptr, size_t size)
424+
{
425+
if (!ptr)
426+
return;
427+
#ifdef IPHONEOS
428+
g_jit_memory_tracker.UnregisterJITRegion(ptr);
429+
#endif
430+
#if defined(IPHONEOS) && defined(__LIBRETRO__)
431+
{
432+
std::scoped_lock lk(s_frontend_regions_mutex);
433+
auto it = std::find(s_frontend_regions.begin(), s_frontend_regions.end(), ptr);
434+
if (it != s_frontend_regions.end())
435+
{
436+
s_frontend_regions.erase(it);
437+
struct retro_exec_mem_free f = {};
438+
f.rx = ptr;
439+
if (Libretro::environ_cb)
440+
Libretro::environ_cb(RETRO_ENVIRONMENT_EXEC_MEM_FREE, &f);
441+
return;
442+
}
443+
}
444+
#endif
445+
munmap(ptr, size);
446+
}
447+
#endif
448+
449+
320450
} // namespace Common

Source/Core/Common/MemoryUtil.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,33 @@
88

99
#include "Common/CommonTypes.h"
1010

11+
#ifdef __APPLE__
12+
#include <TargetConditionals.h>
13+
#endif
14+
1115
namespace Common
1216
{
1317
void* AllocateExecutableMemory(size_t size);
1418

19+
#ifdef IPHONEOS
20+
// Which of the executable-memory strategies is in use. Dual-mapped modes write
21+
// through an alias, so the W^X toggling below must not run for them.
22+
enum class JitType
23+
{
24+
Legacy,
25+
LuckNoTXM,
26+
LuckTXM
27+
};
28+
29+
void SetJitType(JitType type);
30+
#endif
31+
32+
#if defined(IPHONEOS) || (defined(__APPLE__) && defined(__aarch64__) && !TARGET_OS_IPHONE)
33+
ptrdiff_t AllocateWritableRegionAndGetDiff(void* rx_ptr, size_t size);
34+
void FreeWritableRegion(void* rx_ptr, size_t size, ptrdiff_t diff);
35+
void FreeExecutableMemory(void* ptr, size_t size);
36+
#endif
37+
1538
// These two functions control the executable/writable state of the W^X memory
1639
// allocations. More detailed documentation about them is in the .cpp file.
1740
// In general where applicable the ScopedJITPageWriteAndNoExecute wrapper

Source/Core/Common/x64Emitter.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,9 @@ class XEmitter
398398
const u8* GetCodeEnd() const { return m_code_end; }
399399
u8* GetWritableCodeEnd() { return m_code_end; }
400400

401+
ptrdiff_t GetWritableRegionDiff() { return 0; }
402+
void SetWritableRegionDiff(ptrdiff_t diff) {}
403+
401404
void LockFlags() { flags_locked = true; }
402405
void UnlockFlags() { flags_locked = false; }
403406

Source/Core/Core/PowerPC/CachedInterpreter/CachedInterpreterEmitter.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ class CachedInterpreterEmitter
8484
m_write_failed = false;
8585
}
8686

87+
ptrdiff_t GetWritableRegionDiff() { return 0; }
88+
void SetWritableRegionDiff(ptrdiff_t diff) {}
89+
8790
static s32 PoisonCallback(PowerPC::PowerPCState& ppc_state, const void* operands);
8891
static s32 PoisonCallback(std::ostream& stream, const void* operands);
8992

Source/Core/Core/PowerPC/JitArm64/JitArm64Cache.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,10 @@ void JitArm64BlockCache::WriteLinkBlock(const JitBlock::LinkData& source, const
8787
u8* location = source.exitPtrs;
8888
ARM64XEmitter emit(location, location + BLOCK_LINK_SIZE);
8989

90+
auto& jit = static_cast<JitArm64&>(m_jit);
91+
auto& jit_emitter = static_cast<Arm64Gen::ARM64XEmitter&>(jit);
92+
emit.SetWritableRegionDiff(jit_emitter.GetWritableRegionDiff());
93+
9094
WriteLinkBlock(emit, source, dest);
9195
emit.FlushIcache();
9296
}
@@ -95,6 +99,11 @@ void JitArm64BlockCache::WriteDestroyBlock(const JitBlock& block)
9599
{
96100
// Only clear the entry point as we might still be within this block.
97101
ARM64XEmitter emit(block.normalEntry, block.normalEntry + 4);
102+
103+
auto& jit = static_cast<JitArm64&>(m_jit);
104+
auto& jit_emitter = static_cast<Arm64Gen::ARM64XEmitter&>(jit);
105+
emit.SetWritableRegionDiff(jit_emitter.GetWritableRegionDiff());
106+
98107
const Common::ScopedJITPageWriteAndNoExecute enable_jit_page_writes(block.normalEntry);
99108
emit.BRK(0x123);
100109
emit.FlushIcache();

Source/Core/Core/PowerPC/JitArm64/JitArm64_BackPatch.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,8 @@ bool JitArm64::HandleFastmemFault(SContext* ctx)
346346
const Common::ScopedJITPageWriteAndNoExecute enable_jit_page_writes(GetRegionPtr());
347347
ARM64XEmitter emitter(const_cast<u8*>(fastmem_area_start), const_cast<u8*>(fastmem_area_end));
348348

349+
emitter.SetWritableRegionDiff(GetWritableRegionDiff());
350+
349351
emitter.BL(slow_handler_iter->second.slow_access_code);
350352

351353
while (emitter.GetCodePtr() < fastmem_area_end)

0 commit comments

Comments
 (0)