Savestates: Track dirty pages for the purposes of speeding up sequential savestates

This commit is contained in:
Jared M. White 2024-07-02 23:13:39 -05:00
parent 1a376e46d5
commit 6184b8c0cb
8 changed files with 194 additions and 7 deletions

View File

@ -22,6 +22,7 @@ struct WindowsMemoryFunctions
void* m_address_UnmapViewOfFileEx = nullptr;
void* m_address_VirtualAlloc2 = nullptr;
void* m_address_MapViewOfFile3 = nullptr;
void* m_address_VirtualProtectEx = nullptr;
};
#endif
@ -111,6 +112,14 @@ public:
///
void UnmapFromMemoryRegion(void* view, size_t size);
///
/// Write protect a section from the memory region previously mapped by CreateView.
///
/// @param data Pointer to data to protect.
/// @param size Size of the protection.
///
bool WriteProtectMemoryRegion(void* data, size_t size);
private:
#ifdef _WIN32
WindowsMemoryRegion* EnsureSplitRegionForMapping(void* address, size_t size);

View File

@ -34,8 +34,11 @@ using PMapViewOfFile3 = PVOID(WINAPI*)(HANDLE FileMapping, HANDLE Process, PVOID
using PUnmapViewOfFileEx = BOOL(WINAPI*)(PVOID BaseAddress, ULONG UnmapFlags);
using PVirtualProtectEx = BOOL(WINAPI*)(HANDLE Process, LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD* lpflOldProtect);
using PIsApiSetImplemented = BOOL(APIENTRY*)(PCSTR Contract);
namespace Common
{
struct WindowsMemoryRegion
@ -78,11 +81,14 @@ static bool InitWindowsMemoryFunctions(WindowsMemoryFunctions* functions)
functions->m_api_ms_win_core_memory_l1_1_6_handle.GetSymbolAddress("MapViewOfFile3FromApp");
void* const address_UnmapViewOfFileEx =
functions->m_kernel32_handle.GetSymbolAddress("UnmapViewOfFileEx");
void* const address_VirtualProtectEx =
functions->m_kernel32_handle.GetSymbolAddress("VirtualProtectEx");
if (address_VirtualAlloc2 && address_MapViewOfFile3 && address_UnmapViewOfFileEx)
{
functions->m_address_VirtualAlloc2 = address_VirtualAlloc2;
functions->m_address_MapViewOfFile3 = address_MapViewOfFile3;
functions->m_address_UnmapViewOfFileEx = address_UnmapViewOfFileEx;
functions->m_address_VirtualProtectEx = address_VirtualProtectEx;
return true;
}
@ -209,6 +215,13 @@ void MemArena::ReleaseMemoryRegion()
}
}
bool MemArena::WriteProtectMemoryRegion(void* data, size_t size)
{
PDWORD lpflOldProtect = 0;
return static_cast<PVirtualProtectEx>(m_memory_functions.m_address_VirtualProtectEx)(
nullptr, data, size, FILE_MAP_READ, &lpflOldProtect);
}
WindowsMemoryRegion* MemArena::EnsureSplitRegionForMapping(void* start_address, size_t size)
{
u8* const address = static_cast<u8*>(start_address);

View File

@ -289,4 +289,15 @@ size_t MemPhysical()
#endif
}
size_t PageSize()
{
#ifdef _WIN32
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
return sysInfo.dwPageSize;
#else
return sysconf(_SC_PAGESIZE);
#endif
}
} // namespace Common

View File

@ -34,5 +34,6 @@ bool ReadProtectMemory(void* ptr, size_t size);
bool WriteProtectMemory(void* ptr, size_t size, bool executable = false);
bool UnWriteProtectMemory(void* ptr, size_t size, bool allowExecute = false);
size_t MemPhysical();
size_t PageSize();
} // namespace Common

View File

@ -75,6 +75,7 @@
#include "Core/State.h"
#include "Core/System.h"
#include "Core/WiiRoot.h"
#include "Core/HW/Memmap.h"
#ifdef USE_MEMORYWATCHER
#include "Core/MemoryWatcher.h"
@ -380,8 +381,10 @@ static void CpuThread(Core::System& system, const std::optional<std::string>& sa
// The JIT need to be able to intercept faults, both for fastmem and for the BLR optimization.
const bool exception_handler = EMM::IsExceptionHandlerSupported();
if (exception_handler)
{
EMM::InstallExceptionHandler();
system.GetMemory().InitDirtyPages();
}
#ifdef USE_MEMORYWATCHER
s_memory_watcher = std::make_unique<MemoryWatcher>();
#endif

View File

@ -47,6 +47,37 @@ MemoryManager::MemoryManager(Core::System& system) : m_system(system)
MemoryManager::~MemoryManager() = default;
std::optional<size_t> MemoryManager::GetDirtyPageIndexFromAddress(u64 address)
{
size_t page_size = Common::PageSize();
size_t page_mask = page_size - 1;
u64 page_base = address & ~page_mask;
size_t index = 0;
bool foundPageBase = false;
for (const PhysicalMemoryRegion& region : m_physical_regions)
{
if (!region.active)
continue;
uintptr_t region_address = reinterpret_cast<uintptr_t>(*region.out_pointer);
if (page_base >= region_address && page_base <= region_address + region.size)
{
foundPageBase = true;
index += std::floorl((page_base - region_address) / page_size);
break;
}
index += (region.size / page_size);
}
if (!foundPageBase)
{
PanicAlertFmt("Unknown PTE in Dirty Bit Lookup: {:#010x}", page_base);
return std::nullopt;
}
return index;
}
void MemoryManager::InitMMIO(bool is_wii)
{
m_mmio_mapping = std::make_unique<MMIO::Mapping>();
@ -71,6 +102,29 @@ void MemoryManager::InitMMIO(bool is_wii)
}
}
void MemoryManager::InitDirtyPages()
{
for (const PhysicalMemoryRegion& region : m_physical_regions)
{
if (!region.active)
continue;
size_t page_size = Common::PageSize();
for (size_t i = 0; i < region.size; i += page_size)
{
DWORD lpflOldProtect = 0;
bool change_protection =
VirtualProtect((*region.out_pointer) + i, page_size, PAGE_READONLY, &lpflOldProtect);
if (!change_protection)
{
PanicAlertFmt(
"Memory::Init(): Failed to write protect for this block of memory at 0x{:08X}.",
reinterpret_cast<u64>(*region.out_pointer));
}
m_dirty_pages.push_back(false);
}
}
}
void MemoryManager::Init()
{
const auto get_mem1_size = [] {
@ -327,16 +381,54 @@ void MemoryManager::DoState(PointerWrap& p)
p.SetVerifyMode();
return;
}
p.DoArray(m_ram, current_ram_size);
p.DoArray(m_l1_cache, current_l1_cache_size);
u32 page_count = static_cast<u32>(Common::PageSize());
p.Do(m_dirty_pages);
for (size_t i = 0; i < current_ram_size; i++)
{
if (IsPageDirty(reinterpret_cast<uintptr_t>(&m_ram[i])))
{
p.DoArray(m_ram + i, page_count);
i += page_count + 1;
}
}
for (size_t i = 0; i < current_l1_cache_size; i++)
{
if (IsPageDirty(reinterpret_cast<uintptr_t>(&m_l1_cache[i])))
{
p.DoArray(m_l1_cache + i, page_count);
i += page_count + 1;
}
}
p.DoMarker("Memory RAM");
if (current_have_fake_vmem)
p.DoArray(m_fake_vmem, current_fake_vmem_size);
{
for (size_t i = 0; i < current_fake_vmem_size; i++)
{
if (IsPageDirty(reinterpret_cast<uintptr_t>(&m_fake_vmem[i])))
{
p.DoArray(m_fake_vmem + i, page_count);
i += page_count + 1;
}
}
}
p.DoMarker("Memory FakeVMEM");
if (current_have_exram)
p.DoArray(m_exram, current_exram_size);
{
for (size_t i = 0; i < current_exram_size; i++)
{
if (IsPageDirty(reinterpret_cast<uintptr_t>(&m_exram[i])))
{
p.DoArray(m_exram + i, page_count);
i += page_count + 1;
}
}
}
p.DoMarker("Memory EXRAM");
if (p.IsWriteMode())
{
ResetDirtyPages();
}
}
void MemoryManager::Shutdown()
@ -573,4 +665,35 @@ void MemoryManager::Write_U64_Swap(u64 value, u32 address)
CopyToEmu(address, &value, sizeof(value));
}
bool MemoryManager::IsPageDirty(uintptr_t address)
{
std::optional<size_t> index = GetDirtyPageIndexFromAddress(address);
if (index.has_value())
{
return m_dirty_pages[index.value()];
}
else
{
return true;
}
}
void MemoryManager::SetPageDirtyBit(uintptr_t address, size_t size, bool dirty)
{
for (size_t i = 0; i < size; i++)
{
std::optional<size_t> index = GetDirtyPageIndexFromAddress(address + i);
if (index.has_value())
{
m_dirty_pages[index.value()] = dirty;
}
}
}
void MemoryManager::ResetDirtyPages()
{
for (int i = 0; i < m_dirty_pages.size(); i++)
m_dirty_pages[i] = false;
}
} // namespace Memory

View File

@ -94,6 +94,7 @@ public:
// Init and Shutdown
bool IsInitialized() const { return m_is_initialized; }
void Init();
void InitDirtyPages();
void Shutdown();
bool InitFastmemArena();
void ShutdownFastmemArena();
@ -130,6 +131,12 @@ public:
void Write_U32_Swap(u32 var, u32 address);
void Write_U64_Swap(u64 var, u32 address);
bool IsPageDirty(uintptr_t address);
void SetPageDirtyBit(uintptr_t address, size_t size, bool dirty);
void ResetDirtyPages();
std::vector<u8>& GetDirtyPages() { return m_dirty_pages; }
// Templated functions for byteswapped copies.
template <typename T>
void CopyFromEmuSwapped(T* data, u32 address, size_t size) const
@ -254,6 +261,9 @@ private:
Core::System& m_system;
std::vector<u8> m_dirty_pages;
std::optional<size_t> GetDirtyPageIndexFromAddress(u64 address);
void InitMMIO(bool is_wii);
};
} // namespace Memory

View File

@ -17,6 +17,7 @@
#include "Core/MachineContext.h"
#include "Core/PowerPC/JitInterface.h"
#include "Core/System.h"
#include "Core/HW/Memmap.h"
#if defined(__FreeBSD__) || defined(__NetBSD__)
#include <signal.h>
@ -24,6 +25,7 @@
#ifndef _WIN32
#include <unistd.h> // Needed for _POSIX_VERSION
#endif
#include <Common/MemoryUtil.h>
#if defined(__APPLE__)
#ifdef _M_X86_64
@ -60,11 +62,26 @@ static LONG NTAPI Handler(PEXCEPTION_POINTERS pPtrs)
// virtual address of the inaccessible data
uintptr_t fault_address = (uintptr_t)pPtrs->ExceptionRecord->ExceptionInformation[1];
SContext* ctx = pPtrs->ContextRecord;
if (Core::System::GetInstance().GetJitInterface().HandleFault(fault_address, ctx))
{
return EXCEPTION_CONTINUE_EXECUTION;
}
else if (!Core::System::GetInstance().GetMemory().IsPageDirty(fault_address))
{
Core::System::GetInstance().GetMemory().SetPageDirtyBit(fault_address, 1, true);
size_t page_size = Common::PageSize();
size_t page_mask = page_size - 1;
u64 page_base = fault_address & ~page_mask;
DWORD lpflOldProtect = 0;
bool change_protection = VirtualProtect(reinterpret_cast<u8*>(page_base), page_size, PAGE_READONLY, &lpflOldProtect);
if (!change_protection)
{
return EXCEPTION_CONTINUE_SEARCH;
}
return EXCEPTION_CONTINUE_EXECUTION;
}
else
{
// Let's not prevent debugging.