summaryrefslogtreecommitdiff
path: root/Source/Core/Common/MemArenaWin.cpp
diff options
context:
space:
mode:
authorAdmiral H. Curtiss <pikachu025@gmail.com>2021-06-21 06:15:56 +0200
committerAdmiral H. Curtiss <pikachu025@gmail.com>2022-02-06 18:06:04 +0100
commit79405de1037e93042b6357e19a43058d4a6b159f (patch)
tree3a6f9b3c6e00df18fe6a3edbbcfe440d33997dd8 /Source/Core/Common/MemArenaWin.cpp
parent09a0ba06c02b87dd799febc133c2cd24d94f44cb (diff)
MemArena: Use placeholders on Windows.
Diffstat (limited to 'Source/Core/Common/MemArenaWin.cpp')
-rw-r--r--Source/Core/Common/MemArenaWin.cpp368
1 files changed, 354 insertions, 14 deletions
diff --git a/Source/Core/Common/MemArenaWin.cpp b/Source/Core/Common/MemArenaWin.cpp
index 3229e417ab..6434627478 100644
--- a/Source/Core/Common/MemArenaWin.cpp
+++ b/Source/Core/Common/MemArenaWin.cpp
@@ -3,70 +3,410 @@
#include "Common/MemArena.h"
+#include <algorithm>
#include <cstddef>
#include <cstdlib>
-#include <set>
#include <string>
#include <windows.h>
+#include "Common/Assert.h"
#include "Common/CommonFuncs.h"
#include "Common/CommonTypes.h"
+#include "Common/DynamicLibrary.h"
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h"
#include "Common/StringUtil.h"
+using PVirtualAlloc2 = PVOID(WINAPI*)(HANDLE Process, PVOID BaseAddress, SIZE_T Size,
+ ULONG AllocationType, ULONG PageProtection,
+ MEM_EXTENDED_PARAMETER* ExtendedParameters,
+ ULONG ParameterCount);
+
+using PMapViewOfFile3 = PVOID(WINAPI*)(HANDLE FileMapping, HANDLE Process, PVOID BaseAddress,
+ ULONG64 Offset, SIZE_T ViewSize, ULONG AllocationType,
+ ULONG PageProtection,
+ MEM_EXTENDED_PARAMETER* ExtendedParameters,
+ ULONG ParameterCount);
+
+using PIsApiSetImplemented = BOOL(APIENTRY*)(PCSTR Contract);
+
namespace Common
{
-MemArena::MemArena() = default;
-MemArena::~MemArena() = default;
+struct WindowsMemoryRegion
+{
+ u8* m_start;
+ size_t m_size;
+ bool m_is_mapped;
+
+ WindowsMemoryRegion(u8* start, size_t size, bool is_mapped)
+ : m_start(start), m_size(size), m_is_mapped(is_mapped)
+ {
+ }
+};
+
+MemArena::MemArena()
+{
+ // Check if VirtualAlloc2 and MapViewOfFile3 are available, which provide functionality to reserve
+ // a memory region no other allocation may occupy while still allowing us to allocate and map
+ // stuff within it. If they're not available we'll instead fall back to the 'legacy' logic and
+ // just hope that nothing allocates in our address range.
+ DynamicLibrary kernelBase{"KernelBase.dll"};
+ if (!kernelBase.IsOpen())
+ return;
+
+ void* const ptr_IsApiSetImplemented = kernelBase.GetSymbolAddress("IsApiSetImplemented");
+ if (!ptr_IsApiSetImplemented)
+ return;
+ if (!static_cast<PIsApiSetImplemented>(ptr_IsApiSetImplemented)("api-ms-win-core-memory-l1-1-6"))
+ return;
+
+ const HMODULE handle = LoadLibraryW(L"api-ms-win-core-memory-l1-1-6.dll");
+ if (!handle)
+ return;
+
+ void* const address_VirtualAlloc2 = GetProcAddress(handle, "VirtualAlloc2FromApp");
+ void* const address_MapViewOfFile3 = GetProcAddress(handle, "MapViewOfFile3FromApp");
+ if (address_VirtualAlloc2 && address_MapViewOfFile3)
+ {
+ m_api_ms_win_core_memory_l1_1_6_handle = handle;
+ m_address_VirtualAlloc2 = address_VirtualAlloc2;
+ m_address_MapViewOfFile3 = address_MapViewOfFile3;
+ }
+ else
+ {
+ // at least one function is not available, use legacy logic
+ FreeLibrary(handle);
+ }
+}
+
+MemArena::~MemArena()
+{
+ ReleaseMemoryRegion();
+ ReleaseSHMSegment();
+ if (m_api_ms_win_core_memory_l1_1_6_handle)
+ FreeLibrary(static_cast<HMODULE>(m_api_ms_win_core_memory_l1_1_6_handle));
+}
void MemArena::GrabSHMSegment(size_t size)
{
const std::string name = "dolphin-emu." + std::to_string(GetCurrentProcessId());
- hMemoryMapping = CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0,
- static_cast<DWORD>(size), UTF8ToTStr(name).c_str());
+ m_memory_handle = CreateFileMapping(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0,
+ static_cast<DWORD>(size), UTF8ToTStr(name).c_str());
}
void MemArena::ReleaseSHMSegment()
{
- CloseHandle(hMemoryMapping);
- hMemoryMapping = 0;
+ if (!m_memory_handle)
+ return;
+ CloseHandle(m_memory_handle);
+ m_memory_handle = nullptr;
}
void* MemArena::CreateView(s64 offset, size_t size)
{
- return MapInMemoryRegion(offset, size, nullptr);
+ return MapViewOfFileEx(m_memory_handle, FILE_MAP_ALL_ACCESS, 0, (DWORD)((u64)offset), size,
+ nullptr);
}
void MemArena::ReleaseView(void* view, size_t size)
{
- UnmapFromMemoryRegion(view, size);
+ UnmapViewOfFile(view);
}
u8* MemArena::ReserveMemoryRegion(size_t memory_size)
{
- u8* base = static_cast<u8*>(VirtualAlloc(nullptr, memory_size, MEM_RESERVE, PAGE_READWRITE));
- if (!base)
+ if (m_reserved_region)
{
- PanicAlertFmt("Failed to map enough memory space: {}", GetLastErrorString());
+ PanicAlertFmt("Tried to reserve a second memory region from the same MemArena.");
return nullptr;
}
- VirtualFree(base, 0, MEM_RELEASE);
+
+ u8* base;
+ if (m_api_ms_win_core_memory_l1_1_6_handle)
+ {
+ base = static_cast<u8*>(static_cast<PVirtualAlloc2>(m_address_VirtualAlloc2)(
+ nullptr, nullptr, memory_size, MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS,
+ nullptr, 0));
+ if (base)
+ {
+ m_reserved_region = base;
+ m_regions.emplace_back(base, memory_size, false);
+ }
+ else
+ {
+ PanicAlertFmt("Failed to map enough memory space: {}", GetLastErrorString());
+ }
+ }
+ else
+ {
+ NOTICE_LOG_FMT(MEMMAP, "VirtualAlloc2 and/or MapViewFromFile3 unavailable. "
+ "Falling back to legacy memory mapping.");
+ base = static_cast<u8*>(VirtualAlloc(nullptr, memory_size, MEM_RESERVE, PAGE_READWRITE));
+ if (base)
+ VirtualFree(base, 0, MEM_RELEASE);
+ else
+ PanicAlertFmt("Failed to find enough memory space: {}", GetLastErrorString());
+ }
+
return base;
}
void MemArena::ReleaseMemoryRegion()
{
+ if (m_api_ms_win_core_memory_l1_1_6_handle && m_reserved_region)
+ {
+ // user should have unmapped everything by this point, check if that's true and yell if not
+ // (it indicates a bug in the emulated memory mapping logic)
+ size_t mapped_region_count = 0;
+ for (const auto& r : m_regions)
+ {
+ if (r.m_is_mapped)
+ ++mapped_region_count;
+ }
+
+ if (mapped_region_count > 0)
+ {
+ PanicAlertFmt("Error while releasing fastmem region: {} regions are still mapped!",
+ mapped_region_count);
+ }
+
+ // then free memory
+ VirtualFree(m_reserved_region, 0, MEM_RELEASE);
+ m_reserved_region = nullptr;
+ m_regions.clear();
+ }
+}
+
+WindowsMemoryRegion* MemArena::EnsureSplitRegionForMapping(void* start_address, size_t size)
+{
+ u8* const address = static_cast<u8*>(start_address);
+ auto& regions = m_regions;
+ if (regions.empty())
+ {
+ NOTICE_LOG_FMT(MEMMAP, "Tried to map a memory region without reserving a memory block first.");
+ return nullptr;
+ }
+
+ // find closest region that is <= the given address by using upper bound and decrementing
+ auto it = std::upper_bound(
+ regions.begin(), regions.end(), address,
+ [](u8* addr, const WindowsMemoryRegion& region) { return addr < region.m_start; });
+ if (it == regions.begin())
+ {
+ // this should never happen, implies that the given address is before the start of the
+ // reserved memory block
+ NOTICE_LOG_FMT(MEMMAP, "Invalid address {} given to map.", fmt::ptr(address));
+ return nullptr;
+ }
+ --it;
+
+ if (it->m_is_mapped)
+ {
+ NOTICE_LOG_FMT(MEMMAP,
+ "Address to map {} with a size of 0x{:x} overlaps with existing mapping "
+ "at {}.",
+ fmt::ptr(address), size, fmt::ptr(it->m_start));
+ return nullptr;
+ }
+
+ const size_t mapping_index = it - regions.begin();
+ u8* const mapping_address = it->m_start;
+ const size_t mapping_size = it->m_size;
+ if (mapping_address == address)
+ {
+ // if this region is already split up correctly we don't have to do anything
+ if (mapping_size == size)
+ return &*it;
+
+ // if this region is smaller than the requested size we can't map
+ if (mapping_size < size)
+ {
+ NOTICE_LOG_FMT(MEMMAP,
+ "Not enough free space at address {} to map 0x{:x} bytes (0x{:x} available).",
+ fmt::ptr(mapping_address), size, mapping_size);
+ return nullptr;
+ }
+
+ // split region
+ if (!VirtualFree(address, size, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER))
+ {
+ NOTICE_LOG_FMT(MEMMAP, "Region splitting failed: {}", GetLastErrorString());
+ return nullptr;
+ }
+
+ // update tracked mappings and return the first of the two
+ it->m_size = size;
+ u8* const new_mapping_start = address + size;
+ const size_t new_mapping_size = mapping_size - size;
+ regions.insert(it + 1, WindowsMemoryRegion(new_mapping_start, new_mapping_size, false));
+ return &regions[mapping_index];
+ }
+
+ ASSERT(mapping_address < address);
+
+ // is there enough space to map this?
+ const size_t size_before = static_cast<size_t>(address - mapping_address);
+ const size_t minimum_size = size + size_before;
+ if (mapping_size < minimum_size)
+ {
+ NOTICE_LOG_FMT(MEMMAP,
+ "Not enough free space at address {} to map memory region (need 0x{:x} "
+ "bytes, but only 0x{:x} available).",
+ fmt::ptr(address), minimum_size, mapping_size);
+ return nullptr;
+ }
+
+ // split region
+ if (!VirtualFree(address, size, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER))
+ {
+ NOTICE_LOG_FMT(MEMMAP, "Region splitting failed: {}", GetLastErrorString());
+ return nullptr;
+ }
+
+ // do we now have two regions or three regions?
+ if (mapping_size == minimum_size)
+ {
+ // split into two; update tracked mappings and return the second one
+ it->m_size = size_before;
+ u8* const new_mapping_start = address;
+ const size_t new_mapping_size = size;
+ regions.insert(it + 1, WindowsMemoryRegion(new_mapping_start, new_mapping_size, false));
+ return &regions[mapping_index + 1];
+ }
+ else
+ {
+ // split into three; update tracked mappings and return the middle one
+ it->m_size = size_before;
+ u8* const middle_mapping_start = address;
+ const size_t middle_mapping_size = size;
+ u8* const after_mapping_start = address + size;
+ const size_t after_mapping_size = mapping_size - minimum_size;
+ regions.insert(it + 1, WindowsMemoryRegion(after_mapping_start, after_mapping_size, false));
+ regions.insert(regions.begin() + mapping_index + 1,
+ WindowsMemoryRegion(middle_mapping_start, middle_mapping_size, false));
+ return &regions[mapping_index + 1];
+ }
}
void* MemArena::MapInMemoryRegion(s64 offset, size_t size, void* base)
{
- return MapViewOfFileEx(hMemoryMapping, FILE_MAP_ALL_ACCESS, 0, (DWORD)((u64)offset), size, base);
+ if (m_api_ms_win_core_memory_l1_1_6_handle)
+ {
+ WindowsMemoryRegion* const region = EnsureSplitRegionForMapping(base, size);
+ if (!region)
+ {
+ PanicAlertFmt("Splitting memory region failed.");
+ return nullptr;
+ }
+
+ void* rv = static_cast<PMapViewOfFile3>(m_address_MapViewOfFile3)(
+ m_memory_handle, nullptr, base, offset, size, MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE,
+ nullptr, 0);
+ if (rv)
+ {
+ region->m_is_mapped = true;
+ }
+ else
+ {
+ PanicAlertFmt("Mapping memory region failed: {}", GetLastErrorString());
+
+ // revert the split, if any
+ JoinRegionsAfterUnmap(base, size);
+ }
+ return rv;
+ }
+
+ return MapViewOfFileEx(m_memory_handle, FILE_MAP_ALL_ACCESS, 0, (DWORD)((u64)offset), size, base);
+}
+
+bool MemArena::JoinRegionsAfterUnmap(void* start_address, size_t size)
+{
+ u8* const address = static_cast<u8*>(start_address);
+ auto& regions = m_regions;
+ if (regions.empty())
+ {
+ NOTICE_LOG_FMT(MEMMAP,
+ "Tried to unmap a memory region without reserving a memory block first.");
+ return false;
+ }
+
+ // there should be a mapping that matches the request exactly, find it
+ auto it = std::lower_bound(
+ regions.begin(), regions.end(), address,
+ [](const WindowsMemoryRegion& region, u8* addr) { return region.m_start < addr; });
+ if (it == regions.end() || it->m_start != address || it->m_size != size)
+ {
+ // didn't find it, we were given bogus input
+ NOTICE_LOG_FMT(MEMMAP, "Invalid address/size given to unmap.");
+ return false;
+ }
+ it->m_is_mapped = false;
+
+ const bool can_join_with_preceding = it != regions.begin() && !(it - 1)->m_is_mapped;
+ const bool can_join_with_succeeding = (it + 1) != regions.end() && !(it + 1)->m_is_mapped;
+ if (can_join_with_preceding && can_join_with_succeeding)
+ {
+ // join three mappings to one
+ auto it_preceding = it - 1;
+ auto it_succeeding = it + 1;
+ const size_t total_size = it_preceding->m_size + size + it_succeeding->m_size;
+ if (!VirtualFree(it_preceding->m_start, total_size, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS))
+ {
+ NOTICE_LOG_FMT(MEMMAP, "Region coalescing failed: {}", GetLastErrorString());
+ return false;
+ }
+
+ it_preceding->m_size = total_size;
+ regions.erase(it, it + 2);
+ }
+ else if (can_join_with_preceding && !can_join_with_succeeding)
+ {
+ // join two mappings to one
+ auto it_preceding = it - 1;
+ const size_t total_size = it_preceding->m_size + size;
+ if (!VirtualFree(it_preceding->m_start, total_size, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS))
+ {
+ NOTICE_LOG_FMT(MEMMAP, "Region coalescing failed: {}", GetLastErrorString());
+ return false;
+ }
+
+ it_preceding->m_size = total_size;
+ regions.erase(it);
+ }
+ else if (!can_join_with_preceding && can_join_with_succeeding)
+ {
+ // join two mappings to one
+ auto it_succeeding = it + 1;
+ const size_t total_size = size + it_succeeding->m_size;
+ if (!VirtualFree(it->m_start, total_size, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS))
+ {
+ NOTICE_LOG_FMT(MEMMAP, "Region coalescing failed: {}", GetLastErrorString());
+ return false;
+ }
+
+ it->m_size = total_size;
+ regions.erase(it_succeeding);
+ }
+ return true;
}
void MemArena::UnmapFromMemoryRegion(void* view, size_t size)
{
+ if (m_api_ms_win_core_memory_l1_1_6_handle)
+ {
+ if (UnmapViewOfFileEx(view, MEM_PRESERVE_PLACEHOLDER))
+ {
+ if (!JoinRegionsAfterUnmap(view, size))
+ PanicAlertFmt("Joining memory region failed.");
+ }
+ else
+ {
+ PanicAlertFmt("Unmapping memory region failed: {}", GetLastErrorString());
+ }
+ return;
+ }
+
UnmapViewOfFile(view);
}
} // namespace Common