From b692108f1ed471e051e782eebf32aabb06aeb760 Mon Sep 17 00:00:00 2001 From: Eladash Date: Tue, 5 Jul 2022 14:12:21 +0300 Subject: [PATCH] Savestates: HLE state saving POC in sys_lwmutex --- rpcs3/Emu/Cell/ErrorCodes.h | 2 + rpcs3/Emu/Cell/Modules/sys_lwcond_.cpp | 52 ++++++++++++------------- rpcs3/Emu/Cell/Modules/sys_lwmutex_.cpp | 30 +++++++++++--- rpcs3/Emu/Cell/Modules/sys_spinlock.cpp | 3 +- rpcs3/Emu/Cell/PPUThread.cpp | 21 ++++++++-- rpcs3/Emu/Cell/PPUThread.h | 4 +- rpcs3/Emu/Cell/lv2/sys_cond.cpp | 13 +++++-- rpcs3/Emu/Cell/lv2/sys_lwcond.cpp | 9 ++++- rpcs3/Emu/IdManager.cpp | 13 +++++++ rpcs3/Emu/Memory/vm.cpp | 2 +- rpcs3/Emu/System.cpp | 21 +++++----- rpcs3/util/serialization.hpp | 52 ++++++++++++++++++++++--- 12 files changed, 159 insertions(+), 63 deletions(-) diff --git a/rpcs3/Emu/Cell/ErrorCodes.h b/rpcs3/Emu/Cell/ErrorCodes.h index 68581460c5..15e8a1fc3e 100644 --- a/rpcs3/Emu/Cell/ErrorCodes.h +++ b/rpcs3/Emu/Cell/ErrorCodes.h @@ -39,6 +39,8 @@ public: { return value; } + + ENABLE_BITWISE_SERIALIZATION; }; enum CellNotAnError : s32 diff --git a/rpcs3/Emu/Cell/Modules/sys_lwcond_.cpp b/rpcs3/Emu/Cell/Modules/sys_lwcond_.cpp index 9a7f930ce3..8b362c022f 100644 --- a/rpcs3/Emu/Cell/Modules/sys_lwcond_.cpp +++ b/rpcs3/Emu/Cell/Modules/sys_lwcond_.cpp @@ -69,10 +69,7 @@ error_code sys_lwcond_signal(ppu_thread& ppu, vm::ptr lwcond) // call the syscall if (error_code res = _sys_lwcond_signal(ppu, lwcond->lwcond_queue, lwmutex->sleep_queue, u32{umax}, 1)) { - if (ppu.test_stopped()) - { - return 0; - } + static_cast(ppu.test_stopped()); lwmutex->all_info--; @@ -108,10 +105,7 @@ error_code sys_lwcond_signal(ppu_thread& ppu, vm::ptr lwcond) // call the syscall if (error_code res = _sys_lwcond_signal(ppu, lwcond->lwcond_queue, lwmutex->sleep_queue, u32{umax}, 3)) { - if (ppu.test_stopped()) - { - return 0; - } + static_cast(ppu.test_stopped()); lwmutex->lock_var.atomic_op([&](sys_lwmutex_t::sync_var_t& var) { @@ -158,10 +152,7 @@ error_code sys_lwcond_signal_all(ppu_thread& ppu, vm::ptr lwcond) return res; } - if (ppu.test_stopped()) - { - return 0; - } + static_cast(ppu.test_stopped()); lwmutex->all_info += +res; return CELL_OK; @@ -183,10 +174,7 @@ error_code sys_lwcond_signal_all(ppu_thread& ppu, vm::ptr lwcond) // if locking succeeded, call the syscall error_code res = _sys_lwcond_signal_all(ppu, lwcond->lwcond_queue, lwmutex->sleep_queue, 1); - if (ppu.test_stopped()) - { - return 0; - } + static_cast(ppu.test_stopped()); if (res > 0) { @@ -225,10 +213,7 @@ error_code sys_lwcond_signal_to(ppu_thread& ppu, vm::ptr lwcond, u // call the syscall if (error_code res = _sys_lwcond_signal(ppu, lwcond->lwcond_queue, lwmutex->sleep_queue, ppu_thread_id, 1)) { - if (ppu.test_stopped()) - { - return 0; - } + static_cast(ppu.test_stopped()); lwmutex->all_info--; @@ -261,10 +246,7 @@ error_code sys_lwcond_signal_to(ppu_thread& ppu, vm::ptr lwcond, u // call the syscall if (error_code res = _sys_lwcond_signal(ppu, lwcond->lwcond_queue, lwmutex->sleep_queue, ppu_thread_id, 3)) { - if (ppu.test_stopped()) - { - return 0; - } + static_cast(ppu.test_stopped()); lwmutex->lock_var.atomic_op([&](sys_lwmutex_t::sync_var_t& var) { @@ -290,6 +272,9 @@ error_code sys_lwcond_wait(ppu_thread& ppu, vm::ptr lwcond, u64 ti return sys_cond_wait(ppu, lwcond->lwcond_queue, timeout); } + auto& sstate = *ppu.optional_savestate_state; + const auto lwcond_ec = sstate.try_read().second; + const be_t tid(ppu.id); const vm::ptr lwmutex = lwcond->lwmutex; @@ -301,18 +286,22 @@ error_code sys_lwcond_wait(ppu_thread& ppu, vm::ptr lwcond, u64 ti } // save old recursive value - const be_t recursive_value = lwmutex->recursive_count; + const be_t recursive_value = !lwcond_ec ? lwmutex->recursive_count : sstate.operator be_t(); // set special value lwmutex->vars.owner = lwmutex_reserved; lwmutex->recursive_count = 0; // call the syscall - const error_code res = _sys_lwcond_queue_wait(ppu, lwcond->lwcond_queue, lwmutex->sleep_queue, timeout); + const error_code res = !lwcond_ec ? _sys_lwcond_queue_wait(ppu, lwcond->lwcond_queue, lwmutex->sleep_queue, timeout) : lwcond_ec; - if (ppu.test_stopped()) + static_cast(ppu.test_stopped()); + + if (ppu.state & cpu_flag::again) { - return 0; + sstate.pos = 0; + sstate(error_code{}, recursive_value); // Not aborted on mutex sleep + return {}; } if (res == CELL_OK || res + 0u == CELL_ESRCH) @@ -341,6 +330,13 @@ error_code sys_lwcond_wait(ppu_thread& ppu, vm::ptr lwcond, u64 ti return res2; } + if (ppu.state & cpu_flag::again) + { + sstate.pos = 0; + sstate(res, recursive_value); + return {}; + } + // if successfully locked, restore recursive value lwmutex->recursive_count = recursive_value; diff --git a/rpcs3/Emu/Cell/Modules/sys_lwmutex_.cpp b/rpcs3/Emu/Cell/Modules/sys_lwmutex_.cpp index 7fb6cea6d3..8882e685f0 100644 --- a/rpcs3/Emu/Cell/Modules/sys_lwmutex_.cpp +++ b/rpcs3/Emu/Cell/Modules/sys_lwmutex_.cpp @@ -99,6 +99,15 @@ error_code sys_lwmutex_lock(ppu_thread& ppu, vm::ptr lwmutex, u64 return sys_mutex_lock(ppu, lwmutex->sleep_queue, timeout); } + auto& sstate = *ppu.optional_savestate_state; + const bool aborted = sstate.try_read().second; + + if (aborted) + { + // Restore timeout (SYS_SYNC_RETRY mode) + sstate(timeout); + } + const be_t tid(ppu.id); // try to lock lightweight mutex @@ -154,7 +163,10 @@ error_code sys_lwmutex_lock(ppu_thread& ppu, vm::ptr lwmutex, u64 } // atomically increment waiter value using 64 bit op - lwmutex->all_info++; + if (!aborted) + { + lwmutex->all_info++; + } if (lwmutex->vars.owner.compare_and_swap_test(lwmutex_free, tid)) { @@ -167,9 +179,13 @@ error_code sys_lwmutex_lock(ppu_thread& ppu, vm::ptr lwmutex, u64 // lock using the syscall const error_code res = _sys_lwmutex_lock(ppu, lwmutex->sleep_queue, timeout); - if (ppu.test_stopped()) + static_cast(ppu.test_stopped()); + + if (ppu.state & cpu_flag::again) { - return 0; + sstate.pos = 0; + sstate(true, timeout); // Aborted + return {}; } lwmutex->all_info--; @@ -216,9 +232,13 @@ error_code sys_lwmutex_lock(ppu_thread& ppu, vm::ptr lwmutex, u64 const error_code res_ = _sys_lwmutex_lock(ppu, lwmutex->sleep_queue, timeout); - if (ppu.test_stopped()) + static_cast(ppu.test_stopped()); + + if (ppu.state & cpu_flag::again) { - return 0; + sstate.pos = 0; + sstate(true, timeout); // Aborted + return {}; } if (res_ == CELL_OK) diff --git a/rpcs3/Emu/Cell/Modules/sys_spinlock.cpp b/rpcs3/Emu/Cell/Modules/sys_spinlock.cpp index 97014ad442..e49998029c 100644 --- a/rpcs3/Emu/Cell/Modules/sys_spinlock.cpp +++ b/rpcs3/Emu/Cell/Modules/sys_spinlock.cpp @@ -24,7 +24,8 @@ error_code sys_spinlock_lock(ppu_thread& ppu, vm::ptr> lock) { if (ppu.test_stopped()) { - return 0; + ppu.state += cpu_flag::again; + return {}; } } diff --git a/rpcs3/Emu/Cell/PPUThread.cpp b/rpcs3/Emu/Cell/PPUThread.cpp index 91a5c0acba..2b825d06e1 100644 --- a/rpcs3/Emu/Cell/PPUThread.cpp +++ b/rpcs3/Emu/Cell/PPUThread.cpp @@ -1396,7 +1396,7 @@ void ppu_thread::cpu_task() thread_ctrl::wait_on(g_progr_ptotal, 0); g_fxo->get().skip_the_progress_dialog = true; - // Sadly we can't postpone initializing guest time because we need ti run PPU threads + // Sadly we can't postpone initializing guest time because we need to run PPU threads // (the farther it's postponed, the less accuracy of guest time has been lost) Emu.FixGuestTime(); @@ -1527,6 +1527,8 @@ ppu_thread::ppu_thread(const ppu_thread_params& param, std::string_view name, u3 gpr[4] = param.arg1; } + optional_savestate_state = std::make_shared(); + // Trigger the scheduler state += cpu_flag::suspend; @@ -1575,10 +1577,15 @@ bool ppu_thread::savable() const void ppu_thread::serialize_common(utils::serial& ar) { - ar(gpr, fpr, cr, fpscr.bits, lr, ctr, vrsave, cia, xer, sat, nj, prio, optional_syscall_state); + ar(gpr, fpr, cr, fpscr.bits, lr, ctr, vrsave, cia, xer, sat, nj, prio, optional_savestate_state); for (v128& reg : vr) ar(reg._bytes); + + if (optional_savestate_state->data.empty()) + { + optional_savestate_state->clear(); + } } ppu_thread::ppu_thread(utils::serial& ar) @@ -1669,8 +1676,12 @@ ppu_thread::ppu_thread(utils::serial& ar) +[](ppu_thread& ppu) -> bool { + const u32 op = vm::read32(ppu.cia); + const auto& table = g_fxo->get(); ppu.loaded_from_savestate = true; - ppu_execute_syscall(ppu, ppu.gpr[11]); + table.decode(op)(ppu, {op}, vm::_ptr(ppu.cia), &ppu_ret); + + ppu.optional_savestate_state->clear(); // Reset to writing state ppu.loaded_from_savestate = false; return true; } @@ -1708,6 +1719,8 @@ ppu_thread::ppu_thread(utils::serial& ar) void ppu_thread::save(utils::serial& ar) { + USING_SERIALIZATION_VERSION(ppu); + const u64 entry = std::bit_cast(entry_func); ppu_join_status _joiner = joiner; @@ -1870,7 +1883,7 @@ void ppu_thread::fast_call(u32 addr, u64 rtoc) } else if (old_cia) { - if (state & cpu_flag::exit) + if (state & cpu_flag::again) { ppu_log.error("HLE callstack savestate is not implemented!"); } diff --git a/rpcs3/Emu/Cell/PPUThread.h b/rpcs3/Emu/Cell/PPUThread.h index bb9d1e5ffe..a748c315c7 100644 --- a/rpcs3/Emu/Cell/PPUThread.h +++ b/rpcs3/Emu/Cell/PPUThread.h @@ -318,9 +318,9 @@ public: } thread_name{ this }; // For savestates - bool stop_flag_removal_protection = false; // If set, Emulator::Run won't remove stop flag + bool stop_flag_removal_protection = false; // If set, Emulator::Run won't remove stop flag bool loaded_from_savestate = false; // Indicates the thread had just started straight from savestate load - u64 optional_syscall_state{}; + std::shared_ptr optional_savestate_state; bool interrupt_thread_executing = false; be_t* get_stack_arg(s32 i, u64 align = alignof(u64)); diff --git a/rpcs3/Emu/Cell/lv2/sys_cond.cpp b/rpcs3/Emu/Cell/lv2/sys_cond.cpp index d3f48358e4..e2668501f7 100644 --- a/rpcs3/Emu/Cell/lv2/sys_cond.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_cond.cpp @@ -285,6 +285,8 @@ error_code sys_cond_wait(ppu_thread& ppu, u32 cond_id, u64 timeout) // Further function result ppu.gpr[3] = CELL_OK; + auto& sstate = *ppu.optional_savestate_state; + const auto cond = idm::get(cond_id, [&](lv2_cond& cond) -> s64 { if (!ppu.loaded_from_savestate && cond.mutex->owner >> 1 != ppu.id) @@ -294,7 +296,10 @@ error_code sys_cond_wait(ppu_thread& ppu, u32 cond_id, u64 timeout) std::lock_guard lock(cond.mutex->mutex); - if (ppu.loaded_from_savestate && ppu.optional_syscall_state & 1) + const u64 syscall_state = sstate.try_read().second; + sstate.clear(); + + if (syscall_state & 1) { // Mutex sleep ensure(!cond.mutex->try_own(ppu, ppu.id)); @@ -309,7 +314,7 @@ error_code sys_cond_wait(ppu_thread& ppu, u32 cond_id, u64 timeout) if (ppu.loaded_from_savestate) { cond.sleep(ppu, timeout); - return static_cast(ppu.optional_syscall_state >> 32); + return static_cast(syscall_state >> 32); } // Unlock the mutex @@ -356,7 +361,9 @@ error_code sys_cond_wait(ppu_thread& ppu, u32 cond_id, u64 timeout) break; } - ppu.optional_syscall_state = u32{mutex_sleep} | (u64{static_cast(cond.ret)} << 32); + const u64 optional_syscall_state = u32{mutex_sleep} | (u64{static_cast(cond.ret)} << 32); + sstate(optional_syscall_state); + ppu.state += cpu_flag::again; return {}; } diff --git a/rpcs3/Emu/Cell/lv2/sys_lwcond.cpp b/rpcs3/Emu/Cell/lv2/sys_lwcond.cpp index dd4b866c68..aad96f2ab5 100644 --- a/rpcs3/Emu/Cell/lv2/sys_lwcond.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_lwcond.cpp @@ -321,6 +321,8 @@ error_code _sys_lwcond_queue_wait(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id std::shared_ptr mutex; + auto& sstate = *ppu.optional_savestate_state; + const auto cond = idm::get(lwcond_id, [&](lv2_lwcond& cond) { mutex = idm::get_unlocked(lwmutex_id); @@ -335,7 +337,10 @@ error_code _sys_lwcond_queue_wait(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id std::lock_guard lock(cond.mutex); - if (ppu.loaded_from_savestate && ppu.optional_syscall_state) + const bool mutex_sleep = sstate.try_read().second; + sstate.clear(); + + if (mutex_sleep) { // Special: loading state from the point of waiting on lwmutex sleep queue std::lock_guard lock2(mutex->mutex); @@ -403,7 +408,7 @@ error_code _sys_lwcond_queue_wait(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id break; } - ppu.optional_syscall_state = +mutex_sleep; + sstate(mutex_sleep); ppu.state += cpu_flag::again; break; } diff --git a/rpcs3/Emu/IdManager.cpp b/rpcs3/Emu/IdManager.cpp index 9c60543fe5..5c09ae831d 100644 --- a/rpcs3/Emu/IdManager.cpp +++ b/rpcs3/Emu/IdManager.cpp @@ -9,6 +9,19 @@ namespace id_manager thread_local u32 g_id = 0; } +template <> +bool serialize>(utils::serial& ar, std::shared_ptr& o) +{ + if (!o || !ar.is_writing()) + { + o.reset(); + o = std::make_shared(); + } + + ar(o->data); + return true; +} + std::vector>& id_manager::get_typeinfo_map() { // Magic static diff --git a/rpcs3/Emu/Memory/vm.cpp b/rpcs3/Emu/Memory/vm.cpp index 996eef129b..5892e8df1d 100644 --- a/rpcs3/Emu/Memory/vm.cpp +++ b/rpcs3/Emu/Memory/vm.cpp @@ -1569,7 +1569,7 @@ namespace vm 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))); + ar.data.resize(ar.seek_end(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; } diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 2bc9a7c5cc..c0e7169084 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -71,7 +71,7 @@ struct serial_ver_t std::set compatible_versions; }; -static std::array s_serial_versions; +static std::array s_serial_versions; #define SERIALIZATION_VER(name, identifier, ...) \ \ @@ -88,10 +88,10 @@ static std::array s_serial_versions; return ::s_serial_versions[identifier].current_version;\ } -SERIALIZATION_VER(global_version, 0, 10) // For stuff not listed here +SERIALIZATION_VER(global_version, 0, 11) // For stuff not listed here SERIALIZATION_VER(ppu, 1, 1) SERIALIZATION_VER(spu, 2, 1, 2) -SERIALIZATION_VER(lv2_sync, 3, 1) +SERIALIZATION_VER(lv2_sync, 3, 1) SERIALIZATION_VER(lv2_vm, 4, 1) SERIALIZATION_VER(lv2_net, 5, 1) SERIALIZATION_VER(lv2_fs, 6, 1) @@ -121,7 +121,10 @@ SERIALIZATION_VER(cellCamera, 14, 1) SERIALIZATION_VER(cellGem, 15, 1) SERIALIZATION_VER(sceNpTrophy, 16, 1) SERIALIZATION_VER(cellMusic, 17, 1) -SERIALIZATION_VER(cellVoice, 15, 1) +SERIALIZATION_VER(cellVoice, 18, 1) +SERIALIZATION_VER(cellGcm, 19, 1) +SERIALIZATION_VER(sysPrxForUser, 20, 1) +SERIALIZATION_VER(cellSaveData, 21, 1) #undef SERIALIZATION_VER @@ -857,12 +860,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool nse_t offset; }; - if (m_ar->data.size() <= sizeof(file_header)) - { - return game_boot_result::savestate_corrupted; - } - - const file_header header = m_ar->operator file_header(); + const auto header = m_ar->try_read().second; if (header.magic != "RPCS3SAV"_u64) { @@ -2376,6 +2374,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate) ar(usz{}); // Reserve memory to be patched later with correct size const usz old_size = ar.data.size(); ar.data = tar_object::save_directory(path, std::move(ar.data)); + ar.seek_end(); const usz tar_size = ar.data.size() - old_size; std::memcpy(ar.data.data() + old_size - sizeof(usz), &tar_size, sizeof(usz)); sys_log.success("Saved the contents of directory '%s' (size=0x%x)", path, tar_size); @@ -2511,7 +2510,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate) } auto& ar = *m_ar; - const usz pos = ar.data.size(); + const usz pos = ar.seek_end(); std::memcpy(&ar.data[10], &pos, 8);// Set offset ar(used_serial); diff --git a/rpcs3/util/serialization.hpp b/rpcs3/util/serialization.hpp index d6f48aeda3..4a30c7eaa3 100644 --- a/rpcs3/util/serialization.hpp +++ b/rpcs3/util/serialization.hpp @@ -35,7 +35,8 @@ namespace utils struct serial { std::vector data; - usz pos = umax; + usz pos = 0; + bool m_is_writing = true; serial() = default; serial(const serial&) = delete; @@ -44,7 +45,7 @@ namespace utils // Checks if this instance is currently used for serialization bool is_writing() const { - return pos == umax; + return m_is_writing; } // Reserve memory for serialization @@ -61,7 +62,8 @@ namespace utils { if (is_writing()) { - data.insert(data.end(), static_cast(ptr), static_cast(ptr) + size); + data.insert(data.begin() + pos, static_cast(ptr), static_cast(ptr) + size); + pos += size; return true; } @@ -287,7 +289,7 @@ namespace utils , serialize(const_cast&>(static_cast(args)))), ...); } - // Convert serialization manager to deserializion manager (can't go the other way) + // Convert serialization manager to deserializion manager // If no arg is provided reuse saved buffer void set_reading_state(std::vector&& _data = std::vector{}) { @@ -296,9 +298,25 @@ namespace utils data = std::move(_data); } + m_is_writing = false; pos = 0; } + // Reset to empty serialization manager + void clear() + { + data.clear(); + m_is_writing = true; + pos = 0; + } + + usz seek_end(usz backwards = 0) + { + ensure(pos >= backwards); + pos = data.size() - backwards; + return pos; + } + template requires (std::is_copy_constructible_v>) && (std::is_constructible_v> || Bitcopy> || std::is_constructible_v, stx::exact_t> || TupleAlike>) operator T() @@ -331,10 +349,32 @@ namespace utils } } - // Returns true if writable or readable and valid + template requires (std::is_copy_constructible_v && std::is_constructible_v && Bitcopy) + std::pair try_read() + { + if (is_writing()) + { + return {}; + } + + const usz left = data.size() - pos; + using type = std::remove_const_t; + + if (left >= sizeof(type)) + { + u8 buf[sizeof(type)]{}; + ensure(raw_serialize(buf, sizeof(buf))); + return {true, std::bit_cast(buf)}; + } + + return {}; + } + + // Returns true if valid, can be invalidated by setting pos to umax + // Used when an invalid state is encountered somewhere in a place we can't check success code such as constructor) bool is_valid() const { - return is_writing() || pos < data.size(); + return pos <= data.size(); } }; }