From 5bd7756064e4759fe13a557106cf9629908a04f0 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sat, 2 Sep 2023 04:01:32 +0200 Subject: [PATCH 1/2] Common/MemArena: Add LazyMemoryRegion to represent a zero-initialized memory region whose pages are only allocated on first access. --- Source/Core/Common/MemArena.h | 38 +++++++++++++++++++++++ Source/Core/Common/MemArenaAndroid.cpp | 43 ++++++++++++++++++++++++++ Source/Core/Common/MemArenaUnix.cpp | 43 ++++++++++++++++++++++++++ Source/Core/Common/MemArenaWin.cpp | 43 ++++++++++++++++++++++++++ 4 files changed, 167 insertions(+) diff --git a/Source/Core/Common/MemArena.h b/Source/Core/Common/MemArena.h index adb944acf9..33ebb66eb4 100644 --- a/Source/Core/Common/MemArena.h +++ b/Source/Core/Common/MemArena.h @@ -122,4 +122,42 @@ private: #endif }; +// This class represents a single fixed-size memory region where the individual memory pages are +// only actually allocated on first access. The memory will be zero on first access. +class LazyMemoryRegion final +{ +public: + LazyMemoryRegion(); + ~LazyMemoryRegion(); + LazyMemoryRegion(const LazyMemoryRegion&) = delete; + LazyMemoryRegion(LazyMemoryRegion&&) = delete; + LazyMemoryRegion& operator=(const LazyMemoryRegion&) = delete; + LazyMemoryRegion& operator=(LazyMemoryRegion&&) = delete; + + /// + /// Reserve a memory region. + /// + /// @param size The size of the region. + /// + /// @return The address the region was mapped at. Returns nullptr on failure. + /// + void* Create(size_t size); + + /// + /// Reset the memory region back to zero, throwing away any mapped pages. + /// This can only be called after a successful call to Create(). + /// + void Clear(); + + /// + /// Release the memory previously reserved with Create(). After this call the pointer that was + /// returned by Create() will become invalid. + /// + void Release(); + +private: + void* m_memory = nullptr; + size_t m_size = 0; +}; + } // namespace Common diff --git a/Source/Core/Common/MemArenaAndroid.cpp b/Source/Core/Common/MemArenaAndroid.cpp index 01b210100a..4a9e2f68b4 100644 --- a/Source/Core/Common/MemArenaAndroid.cpp +++ b/Source/Core/Common/MemArenaAndroid.cpp @@ -19,6 +19,7 @@ #include #include +#include "Common/Assert.h" #include "Common/CommonFuncs.h" #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" @@ -142,4 +143,46 @@ void MemArena::UnmapFromMemoryRegion(void* view, size_t size) if (retval == MAP_FAILED) NOTICE_LOG_FMT(MEMMAP, "mmap failed"); } + +LazyMemoryRegion::LazyMemoryRegion() = default; + +LazyMemoryRegion::~LazyMemoryRegion() +{ + Release(); +} + +void* LazyMemoryRegion::Create(size_t size) +{ + ASSERT(!m_memory); + + void* memory = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (!memory) + { + NOTICE_LOG_FMT(MEMMAP, "Memory allocation of {} bytes failed.", size); + return nullptr; + } + + m_memory = memory; + m_size = size; + + return memory; +} + +void LazyMemoryRegion::Clear() +{ + ASSERT(m_memory); + + mmap(m_memory, m_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); +} + +void LazyMemoryRegion::Release() +{ + if (m_memory) + { + munmap(m_memory, m_size); + m_memory = nullptr; + m_size = 0; + } +} + } // namespace Common diff --git a/Source/Core/Common/MemArenaUnix.cpp b/Source/Core/Common/MemArenaUnix.cpp index 1532699276..452c2c50c8 100644 --- a/Source/Core/Common/MemArenaUnix.cpp +++ b/Source/Core/Common/MemArenaUnix.cpp @@ -16,6 +16,7 @@ #include #include +#include "Common/Assert.h" #include "Common/CommonFuncs.h" #include "Common/CommonTypes.h" #include "Common/Logging/Log.h" @@ -108,4 +109,46 @@ void MemArena::UnmapFromMemoryRegion(void* view, size_t size) if (retval == MAP_FAILED) NOTICE_LOG_FMT(MEMMAP, "mmap failed"); } + +LazyMemoryRegion::LazyMemoryRegion() = default; + +LazyMemoryRegion::~LazyMemoryRegion() +{ + Release(); +} + +void* LazyMemoryRegion::Create(size_t size) +{ + ASSERT(!m_memory); + + void* memory = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (!memory) + { + NOTICE_LOG_FMT(MEMMAP, "Memory allocation of {} bytes failed.", size); + return nullptr; + } + + m_memory = memory; + m_size = size; + + return memory; +} + +void LazyMemoryRegion::Clear() +{ + ASSERT(m_memory); + + mmap(m_memory, m_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); +} + +void LazyMemoryRegion::Release() +{ + if (m_memory) + { + munmap(m_memory, m_size); + m_memory = nullptr; + m_size = 0; + } +} + } // namespace Common diff --git a/Source/Core/Common/MemArenaWin.cpp b/Source/Core/Common/MemArenaWin.cpp index 9d4a88d54a..ebf078f45b 100644 --- a/Source/Core/Common/MemArenaWin.cpp +++ b/Source/Core/Common/MemArenaWin.cpp @@ -433,4 +433,47 @@ void MemArena::UnmapFromMemoryRegion(void* view, size_t size) UnmapViewOfFile(view); } + +LazyMemoryRegion::LazyMemoryRegion() = default; + +LazyMemoryRegion::~LazyMemoryRegion() +{ + Release(); +} + +void* LazyMemoryRegion::Create(size_t size) +{ + ASSERT(!m_memory); + + void* memory = VirtualAlloc(nullptr, size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + if (!memory) + { + NOTICE_LOG_FMT(MEMMAP, "Memory allocation of {} bytes failed.", size); + return nullptr; + } + + m_memory = memory; + m_size = size; + + return memory; +} + +void LazyMemoryRegion::Clear() +{ + ASSERT(m_memory); + + VirtualFree(m_memory, m_size, MEM_DECOMMIT); + VirtualAlloc(m_memory, m_size, MEM_COMMIT, PAGE_READWRITE); +} + +void LazyMemoryRegion::Release() +{ + if (m_memory) + { + VirtualFree(m_memory, 0, MEM_RELEASE); + m_memory = nullptr; + m_size = 0; + } +} + } // namespace Common From f1c1c6ded690b34c8e5add75fecc96fff00848e5 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sat, 2 Sep 2023 04:03:15 +0200 Subject: [PATCH 2/2] JitCache: Fix potentially dangling pointer to fast block map. Whenever JitBaseBlockCache::Clear() got called, it threw away the memory mapping for the fast block map and created a new one. This new mapping typically got mapped at the same address at the old one, but this is not guaranteed. The pointer to the mapping gets embedded in the generated dispatcher code in Jit64AsmRoutineManager::Generate(), which is only called once on game boot, so if the new mapping ended up at a different address than the old one, the pointer in the ASM pointed at garbage, leading to a crash. This fixes the issue by guaranteeing that the new mapping is mapped at the same address. --- .../Core/Core/PowerPC/JitCommon/JitCache.cpp | 31 +++++-------------- Source/Core/Core/PowerPC/JitCommon/JitCache.h | 2 +- 2 files changed, 8 insertions(+), 25 deletions(-) diff --git a/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp b/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp index a41ebb71b2..98ce48f24a 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp +++ b/Source/Core/Core/PowerPC/JitCommon/JitCache.cpp @@ -42,7 +42,11 @@ void JitBaseBlockCache::Init() { Common::JitRegister::Init(Config::Get(Config::MAIN_PERF_MAP_DIR)); - m_block_map_arena.GrabSHMSegment(FAST_BLOCK_MAP_SIZE, "dolphin-emu-jitblock"); + m_fast_block_map = reinterpret_cast(m_block_map_arena.Create(FAST_BLOCK_MAP_SIZE)); + if (m_fast_block_map) + m_fast_block_map_ptr = m_fast_block_map; + else + m_fast_block_map_ptr = m_fast_block_map_fallback.data(); Clear(); } @@ -51,12 +55,7 @@ void JitBaseBlockCache::Shutdown() { Common::JitRegister::Shutdown(); - if (m_fast_block_map) - { - m_block_map_arena.ReleaseView(m_fast_block_map, FAST_BLOCK_MAP_SIZE); - } - - m_block_map_arena.ReleaseSHMSegment(); + m_block_map_arena.Release(); } // This clears the JIT cache. It's called from JitCache.cpp when the JIT cache @@ -80,23 +79,7 @@ void JitBaseBlockCache::Clear() valid_block.ClearAll(); if (m_fast_block_map) - { - m_block_map_arena.ReleaseView(m_fast_block_map, FAST_BLOCK_MAP_SIZE); - m_block_map_arena.ReleaseSHMSegment(); - m_block_map_arena.GrabSHMSegment(FAST_BLOCK_MAP_SIZE, "dolphin-emu-jitblock"); - } - - m_fast_block_map = - reinterpret_cast(m_block_map_arena.CreateView(0, FAST_BLOCK_MAP_SIZE)); - - if (m_fast_block_map) - { - m_fast_block_map_ptr = m_fast_block_map; - } - else - { - m_fast_block_map_ptr = m_fast_block_map_fallback.data(); - } + m_block_map_arena.Clear(); } void JitBaseBlockCache::Reset() diff --git a/Source/Core/Core/PowerPC/JitCommon/JitCache.h b/Source/Core/Core/PowerPC/JitCommon/JitCache.h index d3f353e815..cf6f785d98 100644 --- a/Source/Core/Core/PowerPC/JitCommon/JitCache.h +++ b/Source/Core/Core/PowerPC/JitCommon/JitCache.h @@ -212,7 +212,7 @@ private: // This is used as a fast cache of block_map used in the assembly dispatcher. // It is implemented via a shm segment using m_block_map_arena. JitBlock** m_fast_block_map = 0; - Common::MemArena m_block_map_arena; + Common::LazyMemoryRegion m_block_map_arena; // An alternative for the above fast_block_map but without a shm segment // in case the shm memory region couldn't be allocated.