Files
ppsspp/Common/MemArenaDarwin.cpp
T
Henrik RydgårdandClaude Opus 5.5 60c06d4f03 Darwin: Reserve space for the memory views instead of probing for it
On iOS, the base search stepped up from 0x100000000 in 8MB increments,
right through the app's binary and early heap: about 100 failed
vm_remaps (each logged twice) every time a game started. On macOS,
Find4GBBase freed its probe allocation before the views were mapped.

Now the kernel picks a free range of exactly the span the views need,
which stays reserved and inaccessible, and the views are mapped over it
with VM_FLAGS_OVERWRITE. One try, no race, and the gaps between views
fault. Unmasked (macOS), ask for 4GB alignment first, which lets the
ARM64 JIT pointerify. The old search remains as a fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 19:08:03 +02:00

146 lines
4.3 KiB
C++

// Copyright (C) 2003 Dolphin Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#include "ppsspp_config.h"
#if defined(__APPLE__)
#include <cstdint>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <mach/mach.h>
#include <mach/vm_map.h>
#include "Common/Log.h"
#include "Common/File/FileUtil.h"
#include "Common/MemoryUtil.h"
#include "Common/MemArena.h"
size_t MemArena::roundup(size_t x) {
return x;
}
bool MemArena::GrabMemSpace(size_t size) {
vm_size = size;
kern_return_t retval = vm_allocate(mach_task_self(), &vm_mem, size, VM_FLAGS_ANYWHERE);
if (retval != KERN_SUCCESS) {
ERROR_LOG(Log::MemMap, "Failed to grab a block of virtual memory");
return false;
} else {
INFO_LOG(Log::MemMap, "Successfully allocated %d bytes at %p", (int)size, (void *)vm_mem);
return true;
}
}
void MemArena::ReleaseSpace() {
ReleaseViewSpace();
vm_deallocate(mach_task_self(), vm_mem, vm_size);
vm_size = 0;
vm_mem = 0;
}
void *MemArena::CreateView(s64 offset, size_t size, void *base) {
mach_port_t self = mach_task_self();
vm_address_t target = (vm_address_t)base;
uint64_t mask = 0;
// Inside our reservation, replace the reserved pages in place.
const bool reserved = reserved_size != 0 && target >= reserved_mem && target + size <= reserved_mem + reserved_size;
const int flags = VM_FLAGS_FIXED | (reserved ? VM_FLAGS_OVERWRITE : 0);
vm_address_t source = vm_mem + offset;
vm_prot_t cur_protection = 0;
vm_prot_t max_protection = 0;
kern_return_t retval =
vm_remap(self, &target, size, mask, flags,
self, source, false,
&cur_protection, &max_protection, VM_INHERIT_DEFAULT);
if (retval != KERN_SUCCESS) {
// 1 == KERN_INVALID_ADDRESS
// 3 == KERN_NO_SPACE (race?)
// 4 == KERN_INVALID_ARGUMENT
ERROR_LOG(Log::MemMap, "vm_remap failed (%d) - could not remap from %llx (offset %llx) of size %llx to %p",
(int)retval, (uint64_t)source, (uint64_t)offset, (uint64_t)size, base);
return nullptr;
}
return (void *)target;
}
void MemArena::ReleaseView(s64 offset, void* view, size_t size) {
vm_address_t addr = (vm_address_t)view;
vm_deallocate(mach_task_self(), addr, size);
}
u8 *MemArena::ReserveViewSpace(size_t size, size_t alignMask) {
ReleaseViewSpace();
vm_address_t addr = 0;
// No access: the gaps between the views should fault, like unmapped memory.
kern_return_t retval = vm_map(mach_task_self(), &addr, size, alignMask, VM_FLAGS_ANYWHERE, MACH_PORT_NULL, 0, false,
VM_PROT_NONE, VM_PROT_ALL, VM_INHERIT_DEFAULT);
if (retval != KERN_SUCCESS) {
WARN_LOG(Log::MemMap, "Couldn't reserve %llx bytes (alignment mask %llx) for the memory views (%d)", (uint64_t)size, (uint64_t)alignMask, (int)retval);
return nullptr;
}
reserved_mem = addr;
reserved_size = size;
INFO_LOG(Log::MemMap, "Reserved %llx bytes for the memory views at %p", (uint64_t)size, (void *)addr);
return (u8 *)addr;
}
void MemArena::ReleaseViewSpace() {
if (reserved_size) {
vm_deallocate(mach_task_self(), reserved_mem, reserved_size);
reserved_mem = 0;
reserved_size = 0;
}
}
bool MemArena::NeedsProbing() {
#if PPSSPP_PLATFORM(IOS) && PPSSPP_ARCH(64BIT)
return true;
#else
return false;
#endif
}
u8* MemArena::Find4GBBase() {
#if PPSSPP_PLATFORM(IOS) && PPSSPP_ARCH(64BIT)
// The caller will need to do probing, like on 32-bit Windows.
return nullptr;
#else
size_t size;
#if PPSSPP_ARCH(64BIT)
size = 0xE1000000;
#else
size = 0x10000000;
#endif
vm_address_t addr = 0;
kern_return_t retval = vm_allocate(mach_task_self(), &addr, size, VM_FLAGS_ANYWHERE);
if (retval == KERN_SUCCESS) {
// Don't need the memory now, was just probing.
vm_deallocate(mach_task_self(), addr, size);
return (u8 *)addr;
}
#endif
return nullptr;
}
#endif // __APPLE__