From 3e51426379e264764e4cfa3861c3bd0bb1e583ce Mon Sep 17 00:00:00 2001 From: Eladash Date: Thu, 14 Jul 2022 22:07:02 +0300 Subject: [PATCH] Savestates/SPU: Kill emulation when its safe to save SPU state --- rpcs3/Emu/CPU/CPUThread.cpp | 69 ++++++++++++++++++++++++++++++++ rpcs3/Emu/Cell/PPUThread.cpp | 16 ++++++++ rpcs3/Emu/Cell/SPURecompiler.cpp | 19 ++++++++- rpcs3/Emu/Cell/SPUThread.cpp | 2 + rpcs3/Emu/Cell/SPUThread.h | 1 + rpcs3/Emu/RSX/RSXThread.cpp | 3 +- rpcs3/Emu/System.cpp | 22 +++------- 7 files changed, 114 insertions(+), 18 deletions(-) diff --git a/rpcs3/Emu/CPU/CPUThread.cpp b/rpcs3/Emu/CPU/CPUThread.cpp index 5b04f9dd90..625c6c08be 100644 --- a/rpcs3/Emu/CPU/CPUThread.cpp +++ b/rpcs3/Emu/CPU/CPUThread.cpp @@ -1196,3 +1196,72 @@ u32 CPUDisAsm::DisAsmBranchTarget(s32 /*imm*/) // Unused return 0; } + +extern bool try_lock_spu_threads_in_a_state_compatible_with_savestates() +{ + const u64 start = get_system_time(); + + // Attempt to lock for half a second, if somehow takes longer abort it + do + { + if (cpu_thread::suspend_all(nullptr, {}, []() + { + return idm::select>([](u32, spu_thread& spu) + { + if (!spu.unsavable) + { + if (Emu.IsPaused()) + { + // If emulation is paused, we can only hope it's already in a state compatible with savestates + return !!(spu.state & (cpu_flag::dbg_global_pause + cpu_flag::dbg_pause)); + } + else + { + ensure(!spu.state.test_and_set(cpu_flag::dbg_global_pause)); + } + + return false; + } + + return true; + }).ret; + })) + { + if (Emu.IsPaused()) + { + return false; + } + + // It's faster to lock once + reader_lock lock(id_manager::g_mutex); + + idm::select>([](u32, spu_thread& spu) + { + spu.state -= cpu_flag::dbg_global_pause; + }, idm::unlocked); + + // For faster signalling, first remove state flags then batch notifications + idm::select>([](u32, spu_thread& spu) + { + if (spu.state & cpu_flag::wait) + { + spu.state.notify_one(); + } + }, idm::unlocked); + + continue; + } + + return true; + } while (std::this_thread::yield(), get_system_time() - start <= 500'000); + + idm::select>([&](u32, named_thread& spu) + { + if (spu.state.test_and_reset(cpu_flag::dbg_global_pause)) + { + spu.state.notify_one(); + } + }); + + return false; +} diff --git a/rpcs3/Emu/Cell/PPUThread.cpp b/rpcs3/Emu/Cell/PPUThread.cpp index 2e2a30bac8..4c8e20f682 100644 --- a/rpcs3/Emu/Cell/PPUThread.cpp +++ b/rpcs3/Emu/Cell/PPUThread.cpp @@ -24,6 +24,7 @@ #include "lv2/sys_prx.h" #include "lv2/sys_overlay.h" #include "lv2/sys_process.h" +#include "lv2/sys_spu.h" #ifdef LLVM_AVAILABLE #ifdef _MSC_VER @@ -1400,6 +1401,21 @@ void ppu_thread::cpu_task() // (the farther it's postponed, the less accuracy of guest time has been lost) Emu.FixGuestTime(); + // Run SPUs waiting on a syscall (savestates related) + idm::select>([&](u32, named_thread& spu) + { + if (spu.group && spu.index == spu.group->waiter_spu_index) + { + if (std::exchange(spu.stop_flag_removal_protection, false)) + { + return; + } + + ensure(spu.state.test_and_reset(cpu_flag::stop)); + spu.state.notify_one(cpu_flag::stop); + } + }); + // Check if this is the only PPU left to initialize (savestates related) if (lv2_obj::is_scheduler_ready()) { diff --git a/rpcs3/Emu/Cell/SPURecompiler.cpp b/rpcs3/Emu/Cell/SPURecompiler.cpp index 89ceef0e26..7bcbf07b64 100644 --- a/rpcs3/Emu/Cell/SPURecompiler.cpp +++ b/rpcs3/Emu/Cell/SPURecompiler.cpp @@ -4310,7 +4310,7 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator } // Call cpu_thread::check_state if necessary and return or continue (full check) - void check_state(u32 addr) + void check_state(u32 addr, bool may_be_unsafe_for_savestate = true) { const auto pstate = spu_ptr(&spu_thread::state); const auto _body = llvm::BasicBlock::Create(m_context, "", m_function); @@ -4318,7 +4318,24 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator m_ir->CreateCondBr(m_ir->CreateICmpEQ(m_ir->CreateLoad(pstate, true), m_ir->getInt32(0)), _body, check, m_md_likely); m_ir->SetInsertPoint(check); update_pc(addr); + + if (may_be_unsafe_for_savestate && std::none_of(std::begin(m_block->phi), std::end(m_block->phi), FN(!!x))) + { + may_be_unsafe_for_savestate = false; + } + + if (may_be_unsafe_for_savestate) + { + m_ir->CreateStore(m_ir->getInt8(1), spu_ptr(&spu_thread::unsavable), true); + } + m_ir->CreateStore(m_ir->getFalse(), m_fake_global1, true); + + if (may_be_unsafe_for_savestate) + { + m_ir->CreateStore(m_ir->getInt8(0), spu_ptr(&spu_thread::unsavable), true); + } + m_ir->CreateBr(_body); m_ir->SetInsertPoint(_body); } diff --git a/rpcs3/Emu/Cell/SPUThread.cpp b/rpcs3/Emu/Cell/SPUThread.cpp index bdc08ce176..0665f96d74 100644 --- a/rpcs3/Emu/Cell/SPUThread.cpp +++ b/rpcs3/Emu/Cell/SPUThread.cpp @@ -1468,6 +1468,8 @@ void spu_thread::cpu_task() spu_runtime::g_gateway(*this, _ptr(0), nullptr); } + unsavable = false; + // Print some stats spu_log.notice("Stats: Block Weight: %u (Retreats: %u);", block_counter, block_failure); } diff --git a/rpcs3/Emu/Cell/SPUThread.h b/rpcs3/Emu/Cell/SPUThread.h index e64104ab27..618fe16821 100644 --- a/rpcs3/Emu/Cell/SPUThread.h +++ b/rpcs3/Emu/Cell/SPUThread.h @@ -847,6 +847,7 @@ public: const char* current_func{}; // Current STOP or RDCH blocking function u64 start_time{}; // Starting time of STOP or RDCH bloking function + bool unsavable = false; // Flag indicating whether saving the spu thread state is currently unsafe atomic_t debugger_float_mode = 0; diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 66ad3f30f6..e1d392cdce 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -47,7 +47,8 @@ bool serialize(utils::serial& ar, rsx::rsx_state& o) { ar(o.transform_program); - if (GET_SERIALIZATION_VERSION(global_version)) + // Hack for compatiblity with previous RSX captures + if (rsx::get_current_renderer()->state & cpu_flag::exit || GET_SERIALIZATION_VERSION(global_version)) { ar(o.transform_constants); } diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 70220ebbbe..481b5f6e05 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -1878,22 +1878,6 @@ void Emulator::RunPPU() signalled_thread = true; }); - // Run SPUs waiting on a syscall (savestates related) - idm::select>([&](u32, named_thread& spu) - { - if (spu.group && spu.index == spu.group->waiter_spu_index) - { - if (std::exchange(spu.stop_flag_removal_protection, false)) - { - return; - } - - ensure(spu.state.test_and_reset(cpu_flag::stop)); - spu.state.notify_one(cpu_flag::stop); - signalled_thread = true; - } - }); - if (!signalled_thread) { FixGuestTime(); @@ -2169,9 +2153,15 @@ void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op, bool savesta } extern bool try_lock_vdec_context_creation(); +extern bool try_lock_spu_threads_in_a_state_compatible_with_savestates(); void Emulator::Kill(bool allow_autoexit, bool savestate) { + if (savestate && !try_lock_spu_threads_in_a_state_compatible_with_savestates()) + { + sys_log.error("Failed to savestate: failed to lock SPU threads execution."); + } + if (savestate && !try_lock_vdec_context_creation()) { sys_log.error("Failed to savestate: HLE VDEC (video decoder) context(s) exist."