From acb215696f7b462d36393fc1f4f1fafeffd475e3 Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Sat, 21 Mar 2026 01:10:50 +0900 Subject: [PATCH] [Memory] Handle host page sizes larger than 4KB in heap operations Adds support for systems with host pages > 4KB (some Linux distros, MacOS) where guest 4KB page operations can't map 1:1 to host mprotect calls. No-op on windows and posix systems with 4KB page size. Co-authored-by: Will Martin --- src/xenia/memory.cc | 139 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 112 insertions(+), 27 deletions(-) diff --git a/src/xenia/memory.cc b/src/xenia/memory.cc index a81691d60..88a52efcf 100644 --- a/src/xenia/memory.cc +++ b/src/xenia/memory.cc @@ -92,6 +92,18 @@ void CrashDump() { --in_crash_dump; } +static inline bool ShouldSkipHostCommit(const BaseHeap& heap) { + // When the host page size is larger than 4 KB (e.g. 16 KB on macOS ARM64, + // 64 KB on some ARM64 Linux kernels), mprotect on 4 KB guest page boundaries + // fails with EINVAL. All heaps are backed by a shared file mapping + // (MapFileView) that is already mapped RW, so the commit is a no-op — skip + // it. + if (xe::memory::page_size() > 0x1000) { + return true; + } + return false; +} + xe::memory::PageAccess ToPageAccess(uint32_t protect) { // Write-combine memory is CPU-writable (for GPU uploads) bool is_writable = @@ -993,19 +1005,24 @@ bool BaseHeap::AllocFixed(uint32_t base_address, uint32_t size, if (allocation_type == kMemoryAllocationReserve) { // Reserve is not needed, as we are mapped already. } else { - auto alloc_type = (allocation_type & kMemoryAllocationCommit) - ? xe::memory::AllocationType::kCommit - : xe::memory::AllocationType::kReserve; - void* result = xe::memory::AllocFixed( - TranslateRelative(start_page_number * page_size_), - page_count * page_size_, alloc_type, ToPageAccess(protect)); - if (!result) { - XELOGE("BaseHeap::AllocFixed failed to alloc range from host"); - return false; - } + if (!ShouldSkipHostCommit(*this)) { + auto alloc_type = (allocation_type & kMemoryAllocationCommit) + ? xe::memory::AllocationType::kCommit + : xe::memory::AllocationType::kReserve; + void* result = xe::memory::AllocFixed( + TranslateRelative(start_page_number * page_size_), + page_count * page_size_, alloc_type, ToPageAccess(protect)); + if (!result) { + XELOGE("BaseHeap::AllocFixed failed to alloc range from host"); + return false; + } - if (cvars::scribble_heap && protect & kMemoryProtectWrite) { - RandomizeMemory(result, page_count * page_size_); + if (cvars::scribble_heap && protect & kMemoryProtectWrite) { + RandomizeMemory(result, page_count * page_size_); + } + } else if (cvars::scribble_heap && protect & kMemoryProtectWrite) { + RandomizeMemory(TranslateRelative(start_page_number * page_size_), + page_count * page_size_); } } @@ -1155,19 +1172,24 @@ bool BaseHeap::AllocRange(uint32_t low_address, uint32_t high_address, if (allocation_type == kMemoryAllocationReserve) { // Reserve is not needed, as we are mapped already. } else { - auto alloc_type = (allocation_type & kMemoryAllocationCommit) - ? xe::memory::AllocationType::kCommit - : xe::memory::AllocationType::kReserve; - void* result = xe::memory::AllocFixed( - TranslateRelative(start_page_number << page_size_shift_), - page_count << page_size_shift_, alloc_type, ToPageAccess(protect)); - if (!result) { - XELOGE("BaseHeap::Alloc failed to alloc range from host"); - return false; - } + if (!ShouldSkipHostCommit(*this)) { + auto alloc_type = (allocation_type & kMemoryAllocationCommit) + ? xe::memory::AllocationType::kCommit + : xe::memory::AllocationType::kReserve; + void* result = xe::memory::AllocFixed( + TranslateRelative(start_page_number << page_size_shift_), + page_count << page_size_shift_, alloc_type, ToPageAccess(protect)); + if (!result) { + XELOGE("BaseHeap::Alloc failed to alloc range from host"); + return false; + } - if (cvars::scribble_heap && (protect & kMemoryProtectWrite)) { - RandomizeMemory(result, page_count << page_size_shift_); + if (cvars::scribble_heap && (protect & kMemoryProtectWrite)) { + RandomizeMemory(result, page_count << page_size_shift_); + } + } else if (cvars::scribble_heap && (protect & kMemoryProtectWrite)) { + RandomizeMemory(TranslateRelative(start_page_number << page_size_shift_), + page_count << page_size_shift_); } } @@ -1355,8 +1377,10 @@ bool BaseHeap::Protect(uint32_t address, uint32_t size, uint32_t protect, // Attempt host change (hopefully won't fail). // We can only do this if our size matches system page granularity. uint32_t page_count = end_page_number - start_page_number + 1; + bool host_offset_aligned = (host_address_offset_ & page_size_mask) == 0; if (page_size_ == xe_page_size || - ((((page_count << page_size_shift_) & page_size_mask) == 0) && + (host_offset_aligned && + (((page_count << page_size_shift_) & page_size_mask) == 0) && (((start_page_number << page_size_shift_) & page_size_mask) == 0))) { memory::PageAccess old_protect_access; if (!xe::memory::Protect( @@ -1371,8 +1395,69 @@ bool BaseHeap::Protect(uint32_t address, uint32_t size, uint32_t protect, *old_protect = FromPageAccess(old_protect_access); } } else { - XELOGW("BaseHeap::Protect: ignoring request as not 4k page aligned"); - return false; + // If the host page size is larger than the guest page size, align + // protection to host pages and use the most permissive access needed by + // any guest page in each host page to avoid over-restricting smaller guest + // pages within a host page. + if (page_size_ < xe_page_size) { + uint32_t start_offset = + host_address_offset_ + (start_page_number << page_size_shift_); + uint32_t end_offset = host_address_offset_ + + ((end_page_number + 1) << page_size_shift_) - 1; + + uint32_t aligned_start_offset = start_offset & ~page_size_mask; + uint32_t aligned_end_offset = (end_offset | page_size_mask) + 1; + + for (uint32_t host_offset = aligned_start_offset; + host_offset < aligned_end_offset; host_offset += xe_page_size) { + uint32_t first_guest_page = 0; + if (host_offset > host_address_offset_) { + first_guest_page = + (host_offset - host_address_offset_) >> page_size_shift_; + } + uint32_t host_page_end = host_offset + xe_page_size - 1; + if (host_page_end < host_address_offset_) { + continue; + } + uint32_t last_guest_page = + (host_page_end - host_address_offset_) >> page_size_shift_; + if (last_guest_page >= page_table_.size()) { + last_guest_page = static_cast(page_table_.size()) - 1; + } + + xe::memory::PageAccess host_access = xe::memory::PageAccess::kNoAccess; + for (uint32_t p = first_guest_page; p <= last_guest_page; ++p) { + uint32_t page_prot = (p >= start_page_number && p <= end_page_number) + ? protect + : page_table_[p].current_protect; + xe::memory::PageAccess page_access = ToPageAccess(page_prot); + if (page_access == xe::memory::PageAccess::kReadWrite) { + host_access = xe::memory::PageAccess::kReadWrite; + break; + } + if (page_access == xe::memory::PageAccess::kReadOnly && + host_access == xe::memory::PageAccess::kNoAccess) { + host_access = xe::memory::PageAccess::kReadOnly; + } + } + + xe::memory::Protect( + reinterpret_cast(reinterpret_cast(membase_) + + heap_base_ + host_offset), + xe_page_size, host_access, nullptr); + } + + if (old_protect) { + *old_protect = page_table_[start_page_number].current_protect; + } + } else { + XELOGW( + "BaseHeap::Protect: unaligned to host page size; skipping mprotect"); + if (old_protect) { + *old_protect = page_table_[start_page_number].current_protect; + } + return false; + } } // Perform table change.