Savestates Support For PS3 Emulation (#10478)

This commit is contained in:
Elad Ashkenazi 2022-07-04 16:02:17 +03:00 committed by GitHub
parent 969b9eb89d
commit fcd297ffb2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
154 changed files with 4948 additions and 635 deletions

View file

@ -13,13 +13,17 @@
#include "Emu/Cell/SPURecompiler.h"
#include "Emu/perf_meter.hpp"
#include <deque>
#include <span>
#include "util/vm.hpp"
#include "util/asm.hpp"
#include "util/simd.hpp"
#include "util/serialization.hpp"
LOG_CHANNEL(vm_log, "VM");
void ppu_remove_hle_instructions(u32 addr, u32 size);
extern bool is_memory_read_only_of_executable(u32 addr);
namespace vm
{
@ -883,7 +887,7 @@ namespace vm
// Notify rsx to invalidate range
// Note: This must be done *before* memory gets unmapped while holding the vm lock, otherwise
// the RSX might try to call VirtualProtect on memory that is already unmapped
if (auto& rsxthr = g_fxo->get<rsx::thread>(); g_fxo->is_init<rsx::thread>())
if (auto& rsxthr = g_fxo->get<rsx::thread>(); !Emu.IsPaused() && g_fxo->is_init<rsx::thread>())
{
rsxthr.on_notify_memory_unmapped(addr, size);
}
@ -1145,8 +1149,14 @@ namespace vm
return flags;
}
static u64 init_block_id()
{
static atomic_t<u64> s_id = 1;
return s_id++;
}
block_t::block_t(u32 addr, u32 size, u64 flags)
: m_id([](){ static atomic_t<u64> s_id = 1; return s_id++; }())
: m_id(init_block_id())
, addr(addr)
, size(size)
, flags(process_block_flags(flags))
@ -1450,6 +1460,210 @@ namespace vm
return imp_used(lock);
}
void block_t::get_shared_memory(std::vector<std::pair<utils::shm*, u32>>& shared)
{
auto& m_map = (m.*block_map)();
if (!(flags & preallocated))
{
shared.reserve(shared.size() + m_map.size());
for (const auto& [addr, shm] : m_map)
{
shared.emplace_back(shm.second.get(), addr);
}
}
}
u32 block_t::get_shm_addr(const std::shared_ptr<utils::shm>& shared)
{
auto& m_map = (m.*block_map)();
if (!(flags & preallocated))
{
for (auto& [addr, pair] : m_map)
{
if (pair.second == shared)
{
return addr;
}
}
}
return 0;
}
static bool check_cache_line_zero(const void* ptr)
{
const auto p = reinterpret_cast<const v128*>(ptr);
const v128 _1 = p[0] | p[1];
const v128 _2 = p[2] | p[3];
const v128 _3 = p[4] | p[5];
const v128 _4 = p[6] | p[7];
const v128 _5 = _1 | _2;
const v128 _6 = _3 | _4;
const v128 _7 = _5 | _6;
return _7 == v128{};
}
static void save_memory_bytes(utils::serial& ar, const u8* ptr, usz size)
{
AUDIT(ar.is_writing() && !(size % 1024));
for (; size; ptr += 128 * 8, size -= 128 * 8)
{
ar(u8{}); // bitmap of 1024 bytes (bit is 128-byte)
u8 bitmap = 0, count = 0;
for (usz i = 0, end = std::min<usz>(size, 128 * 8); i < end; i += 128)
{
if (!check_cache_line_zero(ptr + i))
{
bitmap |= 1u << (i / 128);
count++;
ar(std::span(ptr + i, 128));
}
}
// Patch bitmap with correct value
*std::prev(&ar.data.back(), count * 128) = bitmap;
}
}
static void load_memory_bytes(utils::serial& ar, u8* ptr, usz size)
{
AUDIT(ar.is_writing() && !(size % 128));
for (; size; ptr += 128 * 8, size -= 128 * 8)
{
const u8 bitmap{ar};
for (usz i = 0, end = std::min<usz>(size, 128 * 8); i < end; i += 128)
{
if (bitmap & (1u << (i / 128)))
{
ar(std::span(ptr + i, 128));
}
}
}
}
void block_t::save(utils::serial& ar, std::map<utils::shm*, usz>& shared)
{
auto& m_map = (m.*block_map)();
ar(addr, size, flags);
for (const auto& [addr, shm] : m_map)
{
// Assume first page flags represent all the map
ar(g_pages[addr / 4096 + !!(flags & stack_guarded)]);
ar(addr);
ar(shm.first);
if (flags & preallocated)
{
// Do not save read-only memory which comes from the executable
// Because it couldn't have changed
if (!(ar.data.back() & page_writable) && is_memory_read_only_of_executable(addr))
{
// Revert changes
ar.data.resize(ar.data.size() - (sizeof(u32) * 2 + sizeof(memory_page)));
vm_log.success("Removed read-only memory block of the executable from savestate. (addr=0x%x, size=0x%x)", addr, shm.first);
continue;
}
// Save raw binary image
const u32 guard_size = flags & stack_guarded ? 0x1000 : 0;
save_memory_bytes(ar, vm::get_super_ptr<const u8>(addr + guard_size), shm.first - guard_size * 2);
}
else
{
// Save index of shm
ar(shared[shm.second.get()]);
}
}
// Terminator
ar(u8{0});
}
block_t::block_t(utils::serial& ar, std::vector<std::shared_ptr<utils::shm>>& shared)
: m_id(init_block_id())
, addr(ar)
, size(ar)
, flags(ar)
{
if (flags & preallocated)
{
m_common = std::make_shared<utils::shm>(size);
m_common->map_critical(vm::base(addr), utils::protection::no);
m_common->map_critical(vm::get_super_ptr(addr));
lock_sudo(addr, size);
}
auto& m_map = (m.*block_map)();
std::shared_ptr<utils::shm> null_shm;
while (true)
{
const u8 flags0 = ar;
if (!(flags0 & page_allocated))
{
// Terminator found
break;
}
const u32 addr0 = ar;
const u32 size0 = ar;
u64 pflags = 0;
if (flags0 & page_executable)
{
pflags |= alloc_executable;
}
if (~flags0 & page_writable)
{
pflags |= alloc_unwritable;
}
if (~flags0 & page_readable)
{
pflags |= alloc_hidden;
}
if ((flags & page_size_64k) == page_size_64k)
{
pflags |= page_64k_size;
}
else if (!(flags & (page_size_mask & ~page_size_1m)))
{
pflags |= page_1m_size;
}
// Map the memory through the same method as alloc() and falloc()
// Copy the shared handle unconditionally
ensure(try_alloc(addr0, pflags, size0, ::as_rvalue(flags & preallocated ? null_shm : shared[ar.operator usz()])));
if (flags & preallocated)
{
// Load binary image
const u32 guard_size = flags & stack_guarded ? 0x1000 : 0;
load_memory_bytes(ar, vm::get_super_ptr<u8>(addr0 + guard_size), size0 - guard_size * 2);
}
}
}
bool _unmap_block(const std::shared_ptr<block_t>& block)
{
return block->unmap();
}
static bool _test_map(u32 addr, u32 size)
{
const auto range = utils::address_range::start_length(addr, size);
@ -1623,7 +1837,7 @@ namespace vm
result.first = std::move(*it);
g_locations.erase(it);
ensure(result.first->unmap());
ensure(_unmap_block(result.first));
result.second = true;
return result;
}
@ -1764,7 +1978,7 @@ namespace vm
for (auto& block : g_locations)
{
if (block) block->unmap();
if (block) _unmap_block(block);
}
g_locations.clear();
@ -1783,6 +1997,106 @@ namespace vm
std::memset(g_range_lock_set, 0, sizeof(g_range_lock_set));
g_range_lock_bits = 0;
}
void save(utils::serial& ar)
{
// Shared memory lookup, sample address is saved for easy memory copy
// Just need one address for this optimization
std::vector<std::pair<utils::shm*, u32>> shared;
for (auto& loc : g_locations)
{
if (loc) loc->get_shared_memory(shared);
}
shared.erase(std::unique(shared.begin(), shared.end(), [](auto& a, auto& b) { return a.first == b.first; }), shared.end());
std::map<utils::shm*, usz> shared_map;
for (auto& p : shared)
{
shared_map.emplace(p.first, &p - shared.data());
}
// TODO: proper serialization of std::map
ar(static_cast<usz>(shared_map.size()));
for (const auto& [shm, addr] : shared)
{
// Save shared memory
ar(shm->flags());
// TODO: string_view serialization (even with load function, so the loaded address points to a position of the stream's buffer)
ar(shm->size());
save_memory_bytes(ar, vm::get_super_ptr<u8>(addr), shm->size());
}
// TODO: Serialize std::vector direcly
ar(g_locations.size());
for (auto& loc : g_locations)
{
const u8 has = loc.operator bool();
ar(has);
if (loc)
{
loc->save(ar, shared_map);
}
}
}
void load(utils::serial& ar)
{
std::vector<std::shared_ptr<utils::shm>> shared;
shared.resize(ar.operator usz());
for (auto& shm : shared)
{
// Load shared memory
const u32 flags = ar;
const u64 size = ar;
shm = std::make_shared<utils::shm>(size, flags);
// Load binary image
// elad335: I'm not proud about it as well.. (ideal situation is to not call map_self())
load_memory_bytes(ar, shm->map_self(), shm->size());
}
for (auto& block : g_locations)
{
if (block) _unmap_block(block);
}
g_locations.clear();
g_locations.resize(ar.operator usz());
for (auto& loc : g_locations)
{
const u8 has = ar;
if (has)
{
loc = std::make_shared<block_t>(ar, shared);
}
}
g_range_lock = 0;
}
u32 get_shm_addr(const std::shared_ptr<utils::shm>& shared)
{
for (auto& loc : g_locations)
{
if (u32 addr = loc ? loc->get_shm_addr(shared) : 0)
{
return addr;
}
}
return 0;
}
}
void fmt_class_string<vm::_ptr_base<const void, u32>>::format(std::string& out, u64 arg)