From b0e2a28e14b28acecbc3648efcaaa0f04c974967 Mon Sep 17 00:00:00 2001 From: JosJuice Date: Sun, 28 Dec 2025 14:47:40 +0100 Subject: [PATCH] Core: Combine guest pages into host pages larger than 4K Most systems that Dolphin runs on have a page size of 4 KiB, which conveniently matches the page size of the GameCube and Wii. But there are also systems that use larger page sizes, notably Apple CPUs with 16 KiB page sizes. To let us create host mappings on such systems, this commit implements combining guest mappings into host page sized mappings wherever possible. For this to work for a given mapping, not only do four (in the case of 16 KiB) guest mappings have to exist adjacent to each other, but the corresponding translated addresses also have to be adjacent, and the lowest bits of the addresses have to match. When I tested a few games, the following percentages of guest mappings met these criteria: Spider-Man 2: 0%-12% Rogue Squadron 2: 39%-42% Rogue Squadron 3: 28%-41% So while 16 KiB systems don't get as much of a performance improvement as 4 KiB systems, they do still get some improvement. --- Source/Core/Core/HW/Memmap.cpp | 119 +++++++++++++++++++++++++++++++-- Source/Core/Core/HW/Memmap.h | 33 ++++++++- 2 files changed, 147 insertions(+), 5 deletions(-) diff --git a/Source/Core/Core/HW/Memmap.cpp b/Source/Core/Core/HW/Memmap.cpp index a331522487..352b68fdb7 100644 --- a/Source/Core/Core/HW/Memmap.cpp +++ b/Source/Core/Core/HW/Memmap.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -44,12 +45,22 @@ namespace Memory { MemoryManager::MemoryManager(Core::System& system) - : m_page_size(m_arena.GetPageSize()), m_system(system) + : m_page_size(static_cast(m_arena.GetPageSize())), + m_guest_pages_per_host_page(m_page_size / PowerPC::HW_PAGE_SIZE), + m_host_page_type(GetHostPageTypeForPageSize(m_page_size)), m_system(system) { } MemoryManager::~MemoryManager() = default; +MemoryManager::HostPageType MemoryManager::GetHostPageTypeForPageSize(u32 page_size) +{ + if (!std::has_single_bit(page_size)) + return HostPageType::Unsupported; + + return page_size > PowerPC::HW_PAGE_SIZE ? HostPageType::LargePages : HostPageType::SmallPages; +} + void MemoryManager::InitMMIO(bool is_wii) { m_mmio_mapping = std::make_unique(); @@ -312,10 +323,69 @@ void MemoryManager::UpdateDBATMappings(const PowerPC::BatTable& dbat_table) void MemoryManager::AddPageTableMapping(u32 logical_address, u32 translated_address, bool writeable) { - if (!m_is_fastmem_arena_initialized || m_page_size > PowerPC::HW_PAGE_SIZE) + if (!m_is_fastmem_arena_initialized) return; - constexpr u32 logical_size = PowerPC::HW_PAGE_SIZE; + switch (m_host_page_type) + { + case HostPageType::SmallPages: + return AddHostPageTableMapping(logical_address, translated_address, writeable, + PowerPC::HW_PAGE_SIZE); + case HostPageType::LargePages: + return TryAddLargePageTableMapping(logical_address, translated_address, writeable); + default: + return; + } +} + +void MemoryManager::TryAddLargePageTableMapping(u32 logical_address, u32 translated_address, + bool writeable) +{ + const bool add_readable = + TryAddLargePageTableMapping(logical_address, translated_address, m_large_readable_pages); + + const bool add_writeable = + writeable && + TryAddLargePageTableMapping(logical_address, translated_address, m_large_writeable_pages); + + if (add_readable || add_writeable) + { + AddHostPageTableMapping(logical_address & ~(m_page_size - 1), + translated_address & ~(m_page_size - 1), add_writeable, m_page_size); + } +} + +bool MemoryManager::TryAddLargePageTableMapping(u32 logical_address, u32 translated_address, + std::map>& map) +{ + std::vector& entries = map[logical_address & ~(m_page_size - 1)]; + + if (entries.empty()) + entries = std::vector(m_guest_pages_per_host_page, INVALID_MAPPING); + + entries[(logical_address & (m_page_size - 1)) / PowerPC::HW_PAGE_SIZE] = translated_address; + + return CanCreateHostMappingForGuestPages(entries); +} + +bool MemoryManager::CanCreateHostMappingForGuestPages(const std::vector& entries) const +{ + const u32 translated_address = entries[0]; + if ((translated_address & (m_page_size - 1)) != 0) + return false; + + for (size_t i = 1; i < m_guest_pages_per_host_page; ++i) + { + if (entries[i] != translated_address + i * PowerPC::HW_PAGE_SIZE) + return false; + } + + return true; +} + +void MemoryManager::AddHostPageTableMapping(u32 logical_address, u32 translated_address, + bool writeable, u32 logical_size) +{ for (const auto& physical_region : m_physical_regions) { if (!physical_region.active) @@ -367,9 +437,45 @@ void MemoryManager::AddPageTableMapping(u32 logical_address, u32 translated_addr void MemoryManager::RemovePageTableMappings(const std::set& mappings) { - if (m_page_size > PowerPC::HW_PAGE_SIZE) + switch (m_host_page_type) + { + case HostPageType::SmallPages: + return RemoveHostPageTableMappings(mappings); + case HostPageType::LargePages: + for (u32 logical_address : mappings) + RemoveLargePageTableMapping(logical_address); return; + default: + return; + } +} +void MemoryManager::RemoveLargePageTableMapping(u32 logical_address) +{ + RemoveLargePageTableMapping(logical_address, m_large_readable_pages); + RemoveLargePageTableMapping(logical_address, m_large_writeable_pages); + + const u32 aligned_logical_address = logical_address & ~(m_page_size - 1); + const auto it = m_page_table_mapped_entries.find(aligned_logical_address); + if (it != m_page_table_mapped_entries.end()) + { + const LogicalMemoryView& entry = it->second; + m_arena.UnmapFromMemoryRegion(entry.mapped_pointer, entry.mapped_size); + + m_page_table_mapped_entries.erase(it); + } +} + +void MemoryManager::RemoveLargePageTableMapping(u32 logical_address, + std::map>& map) +{ + const auto it = map.find(logical_address & ~(m_page_size - 1)); + if (it != map.end()) + it->second[(logical_address & (m_page_size - 1)) / PowerPC::HW_PAGE_SIZE] = INVALID_MAPPING; +} + +void MemoryManager::RemoveHostPageTableMappings(const std::set& mappings) +{ if (mappings.empty()) return; @@ -389,6 +495,8 @@ void MemoryManager::RemoveAllPageTableMappings() m_arena.UnmapFromMemoryRegion(entry.mapped_pointer, entry.mapped_size); } m_page_table_mapped_entries.clear(); + m_large_readable_pages.clear(); + m_large_writeable_pages.clear(); } void MemoryManager::DoState(PointerWrap& p) @@ -486,6 +594,9 @@ void MemoryManager::ShutdownFastmemArena() m_arena.ReleaseMemoryRegion(); + m_large_readable_pages.clear(); + m_large_writeable_pages.clear(); + m_fastmem_arena = nullptr; m_fastmem_arena_size = 0; m_physical_base = nullptr; diff --git a/Source/Core/Core/HW/Memmap.h b/Source/Core/Core/HW/Memmap.h index e78e3d1c62..afe74bf747 100644 --- a/Source/Core/Core/HW/Memmap.h +++ b/Source/Core/Core/HW/Memmap.h @@ -9,6 +9,7 @@ #include #include #include +#include #include "Common/CommonTypes.h" #include "Common/MathUtil.h" @@ -161,6 +162,16 @@ public: } private: + enum class HostPageType + { + // 4K or smaller + SmallPages, + // 8K or larger + LargePages, + // Required APIs aren't available, or the page size isn't a power of 2 + Unsupported, + }; + // Base is a pointer to the base of the memory map. Yes, some MMU tricks // are used to set up a full GC or Wii memory map in process memory. // In 64-bit, this might point to "high memory" (above the 32-bit limit), @@ -212,7 +223,9 @@ private: // The MemArena class Common::MemArena m_arena; - const size_t m_page_size; + const u32 m_page_size; + const u32 m_guest_pages_per_host_page; + const HostPageType m_host_page_type; // Dolphin allocates memory to represent four regions: // - 32MB RAM (actually 24MB on hardware), available on GameCube and Wii @@ -261,8 +274,26 @@ private: std::array m_physical_page_mappings{}; std::array m_logical_page_mappings{}; + // If the host page size is larger than the guest page size, these two maps are used + // to keep track of which guest pages can be combined and mapped as one host page. + static constexpr u32 INVALID_MAPPING = 0xFFFFFFFF; + std::map> m_large_readable_pages; + std::map> m_large_writeable_pages; + Core::System& m_system; + static HostPageType GetHostPageTypeForPageSize(u32 page_size); + void InitMMIO(bool is_wii); + + void TryAddLargePageTableMapping(u32 logical_address, u32 translated_address, bool writeable); + bool TryAddLargePageTableMapping(u32 logical_address, u32 translated_address, + std::map>& map); + bool CanCreateHostMappingForGuestPages(const std::vector& entries) const; + void AddHostPageTableMapping(u32 logical_address, u32 translated_address, bool writeable, + u32 logical_size); + void RemoveLargePageTableMapping(u32 logical_address); + void RemoveLargePageTableMapping(u32 logical_address, std::map>& map); + void RemoveHostPageTableMappings(const std::set& mappings); }; } // namespace Memory