Savestates: Track dirty pages for the purposes of speeding up sequential savestates
This commit is contained in:
parent
1a376e46d5
commit
6184b8c0cb
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue