Savestates: HLE state saving POC in sys_lwmutex

This commit is contained in:
Eladash 2022-07-05 14:12:21 +03:00 committed by Ivan
parent 2815aecd0c
commit b692108f1e
12 changed files with 159 additions and 63 deletions

View file

@ -39,6 +39,8 @@ public:
{
return value;
}
ENABLE_BITWISE_SERIALIZATION;
};
enum CellNotAnError : s32

View file

@ -69,10 +69,7 @@ error_code sys_lwcond_signal(ppu_thread& ppu, vm::ptr<sys_lwcond_t> 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<void>(ppu.test_stopped());
lwmutex->all_info--;
@ -108,10 +105,7 @@ error_code sys_lwcond_signal(ppu_thread& ppu, vm::ptr<sys_lwcond_t> 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<void>(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<sys_lwcond_t> lwcond)
return res;
}
if (ppu.test_stopped())
{
return 0;
}
static_cast<void>(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<sys_lwcond_t> 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<void>(ppu.test_stopped());
if (res > 0)
{
@ -225,10 +213,7 @@ error_code sys_lwcond_signal_to(ppu_thread& ppu, vm::ptr<sys_lwcond_t> 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<void>(ppu.test_stopped());
lwmutex->all_info--;
@ -261,10 +246,7 @@ error_code sys_lwcond_signal_to(ppu_thread& ppu, vm::ptr<sys_lwcond_t> 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<void>(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<sys_lwcond_t> 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<error_code>().second;
const be_t<u32> tid(ppu.id);
const vm::ptr<sys_lwmutex_t> lwmutex = lwcond->lwmutex;
@ -301,18 +286,22 @@ error_code sys_lwcond_wait(ppu_thread& ppu, vm::ptr<sys_lwcond_t> lwcond, u64 ti
}
// save old recursive value
const be_t<u32> recursive_value = lwmutex->recursive_count;
const be_t<u32> recursive_value = !lwcond_ec ? lwmutex->recursive_count : sstate.operator be_t<u32>();
// 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<void>(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<sys_lwcond_t> 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;

View file

@ -99,6 +99,15 @@ error_code sys_lwmutex_lock(ppu_thread& ppu, vm::ptr<sys_lwmutex_t> lwmutex, u64
return sys_mutex_lock(ppu, lwmutex->sleep_queue, timeout);
}
auto& sstate = *ppu.optional_savestate_state;
const bool aborted = sstate.try_read<bool>().second;
if (aborted)
{
// Restore timeout (SYS_SYNC_RETRY mode)
sstate(timeout);
}
const be_t<u32> tid(ppu.id);
// try to lock lightweight mutex
@ -154,7 +163,10 @@ error_code sys_lwmutex_lock(ppu_thread& ppu, vm::ptr<sys_lwmutex_t> 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<sys_lwmutex_t> lwmutex, u64
// lock using the syscall
const error_code res = _sys_lwmutex_lock(ppu, lwmutex->sleep_queue, timeout);
if (ppu.test_stopped())
static_cast<void>(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<sys_lwmutex_t> lwmutex, u64
const error_code res_ = _sys_lwmutex_lock(ppu, lwmutex->sleep_queue, timeout);
if (ppu.test_stopped())
static_cast<void>(ppu.test_stopped());
if (ppu.state & cpu_flag::again)
{
return 0;
sstate.pos = 0;
sstate(true, timeout); // Aborted
return {};
}
if (res_ == CELL_OK)

View file

@ -24,7 +24,8 @@ error_code sys_spinlock_lock(ppu_thread& ppu, vm::ptr<atomic_be_t<u32>> lock)
{
if (ppu.test_stopped())
{
return 0;
ppu.state += cpu_flag::again;
return {};
}
}

View file

@ -1396,7 +1396,7 @@ void ppu_thread::cpu_task()
thread_ctrl::wait_on<atomic_wait::op_ne>(g_progr_ptotal, 0);
g_fxo->get<progress_dialog_workaround>().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<utils::serial>();
// 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_interpreter_rt>();
ppu.loaded_from_savestate = true;
ppu_execute_syscall(ppu, ppu.gpr[11]);
table.decode(op)(ppu, {op}, vm::_ptr<u32>(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<u64>(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!");
}

View file

@ -320,7 +320,7 @@ public:
// For savestates
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<utils::serial> optional_savestate_state;
bool interrupt_thread_executing = false;
be_t<u64>* get_stack_arg(s32 i, u64 align = alignof(u64));

View file

@ -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<lv2_obj, lv2_cond>(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<u64>().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<u32>(ppu.optional_syscall_state >> 32);
return static_cast<u32>(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<u32>(cond.ret)} << 32);
const u64 optional_syscall_state = u32{mutex_sleep} | (u64{static_cast<u32>(cond.ret)} << 32);
sstate(optional_syscall_state);
ppu.state += cpu_flag::again;
return {};
}

View file

@ -321,6 +321,8 @@ error_code _sys_lwcond_queue_wait(ppu_thread& ppu, u32 lwcond_id, u32 lwmutex_id
std::shared_ptr<lv2_lwmutex> mutex;
auto& sstate = *ppu.optional_savestate_state;
const auto cond = idm::get<lv2_obj, lv2_lwcond>(lwcond_id, [&](lv2_lwcond& cond)
{
mutex = idm::get_unlocked<lv2_obj, lv2_lwmutex>(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<bool>().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;
}

View file

@ -9,6 +9,19 @@ namespace id_manager
thread_local u32 g_id = 0;
}
template <>
bool serialize<std::shared_ptr<utils::serial>>(utils::serial& ar, std::shared_ptr<utils::serial>& o)
{
if (!o || !ar.is_writing())
{
o.reset();
o = std::make_shared<utils::serial>();
}
ar(o->data);
return true;
}
std::vector<std::pair<u128, id_manager::typeinfo>>& id_manager::get_typeinfo_map()
{
// Magic static

View file

@ -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;
}

View file

@ -71,7 +71,7 @@ struct serial_ver_t
std::set<u32> compatible_versions;
};
static std::array<serial_ver_t, 18> s_serial_versions;
static std::array<serial_ver_t, 22> s_serial_versions;
#define SERIALIZATION_VER(name, identifier, ...) \
\
@ -88,10 +88,10 @@ static std::array<serial_ver_t, 18> 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<u64, 1> 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<file_header>().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);

View file

@ -35,7 +35,8 @@ namespace utils
struct serial
{
std::vector<u8> 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<const u8*>(ptr), static_cast<const u8*>(ptr) + size);
data.insert(data.begin() + pos, static_cast<const u8*>(ptr), static_cast<const u8*>(ptr) + size);
pos += size;
return true;
}
@ -287,7 +289,7 @@ namespace utils
, serialize(const_cast<std::remove_cvref_t<Args>&>(static_cast<const Args&>(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<u8>&& _data = std::vector<u8>{})
{
@ -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 <typename T> requires (std::is_copy_constructible_v<std::remove_const_t<T>>) && (std::is_constructible_v<std::remove_const_t<T>> || Bitcopy<std::remove_const_t<T>> ||
std::is_constructible_v<std::remove_const_t<T>, stx::exact_t<serial&>> || TupleAlike<std::remove_const_t<T>>)
operator T()
@ -331,10 +349,32 @@ namespace utils
}
}
// Returns true if writable or readable and valid
template <typename T> requires (std::is_copy_constructible_v<T> && std::is_constructible_v<T> && Bitcopy<T>)
std::pair<bool, T> try_read()
{
if (is_writing())
{
return {};
}
const usz left = data.size() - pos;
using type = std::remove_const_t<T>;
if (left >= sizeof(type))
{
u8 buf[sizeof(type)]{};
ensure(raw_serialize(buf, sizeof(buf)));
return {true, std::bit_cast<type>(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();
}
};
}