mirror of
https://github.com/RPCS3/rpcs3.git
synced 2026-03-26 04:18:36 -06:00
Merge f41770f57a into 9b6bc7c1b4
This commit is contained in:
commit
50977f8788
@ -1269,7 +1269,7 @@ namespace rsx
|
||||
extern std::function<bool(u32 addr, bool is_writing)> g_access_violation_handler;
|
||||
}
|
||||
|
||||
bool handle_access_violation(u32 addr, bool is_writing, ucontext_t* context) noexcept
|
||||
bool handle_access_violation(u32 addr, bool is_writing, bool is_exec, ucontext_t* context) noexcept
|
||||
{
|
||||
g_tls_fault_all++;
|
||||
|
||||
@ -1503,7 +1503,9 @@ bool handle_access_violation(u32 addr, bool is_writing, ucontext_t* context) noe
|
||||
static_cast<void>(context);
|
||||
#endif /* ARCH_ */
|
||||
|
||||
if (vm::check_addr(addr, is_writing ? vm::page_writable : vm::page_readable))
|
||||
const auto requred_page_perms = (is_writing ? vm::page_writable : vm::page_readable) + (is_exec ? vm::page_executable : 0);
|
||||
|
||||
if (vm::check_addr(addr, requred_page_perms))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -1511,9 +1513,7 @@ bool handle_access_violation(u32 addr, bool is_writing, ucontext_t* context) noe
|
||||
// Hack: allocate memory in case the emulator is stopping
|
||||
const auto hack_alloc = [&]()
|
||||
{
|
||||
g_tls_access_violation_recovered = true;
|
||||
|
||||
if (vm::check_addr(addr, is_writing ? vm::page_writable : vm::page_readable))
|
||||
if (vm::check_addr(addr, requred_page_perms))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@ -1525,14 +1525,42 @@ bool handle_access_violation(u32 addr, bool is_writing, ucontext_t* context) noe
|
||||
return false;
|
||||
}
|
||||
|
||||
extern void ppu_register_range(u32 addr, u32 size);
|
||||
|
||||
bool reprotected = false;
|
||||
|
||||
if (vm::writer_lock mlock; area->flags & vm::preallocated || vm::check_addr(addr, 0))
|
||||
{
|
||||
// For allocated memory with protection lower than required (such as protection::no or read-only while writing to it)
|
||||
utils::memory_protect(vm::base(addr & -0x1000), 0x1000, utils::protection::rw);
|
||||
reprotected = true;
|
||||
}
|
||||
|
||||
if (reprotected)
|
||||
{
|
||||
if (is_exec && !vm::check_addr(addr, vm::page_executable))
|
||||
{
|
||||
ppu_register_range(addr & -0x10000, 0x10000);
|
||||
}
|
||||
|
||||
g_tls_access_violation_recovered = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return area->falloc(addr & -0x10000, 0x10000) || vm::check_addr(addr, is_writing ? vm::page_writable : vm::page_readable);
|
||||
const bool allocated = area->falloc(addr & -0x10000, 0x10000);
|
||||
|
||||
if (allocated)
|
||||
{
|
||||
if (is_exec && !vm::check_addr(addr, vm::page_executable))
|
||||
{
|
||||
ppu_register_range(addr & -0x10000, 0x10000);
|
||||
}
|
||||
|
||||
g_tls_access_violation_recovered = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
if (cpu && (cpu->get_class() == thread_class::ppu || cpu->get_class() == thread_class::spu))
|
||||
@ -1766,8 +1794,13 @@ bool handle_access_violation(u32 addr, bool is_writing, ucontext_t* context) noe
|
||||
}
|
||||
}
|
||||
|
||||
if (Emu.IsStopped() && !hack_alloc())
|
||||
if (Emu.IsStopped())
|
||||
{
|
||||
while (!hack_alloc())
|
||||
{
|
||||
thread_ctrl::wait_for(1000);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1806,6 +1839,7 @@ static LONG exception_handler(PEXCEPTION_POINTERS pExp) noexcept
|
||||
if (pExp->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && !is_executing)
|
||||
{
|
||||
u32 addr = 0;
|
||||
bool is_exec = false;
|
||||
|
||||
if (auto [addr0, ok] = vm::try_get_addr(ptr); ok)
|
||||
{
|
||||
@ -1813,14 +1847,21 @@ static LONG exception_handler(PEXCEPTION_POINTERS pExp) noexcept
|
||||
}
|
||||
else if (const usz exec64 = (ptr - vm::g_exec_addr) / 2; exec64 <= u32{umax})
|
||||
{
|
||||
is_exec = true;
|
||||
addr = static_cast<u32>(exec64);
|
||||
}
|
||||
else
|
||||
else if (const usz exec64 = (ptr - vm::g_exec_addr - vm::g_exec_addr_seg_offset); exec64 <= u32{umax})
|
||||
{
|
||||
is_exec = true;
|
||||
addr = static_cast<u32>(exec64);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::this_thread::sleep_for(1ms);
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
if (thread_ctrl::get_current() && handle_access_violation(addr, is_writing, pExp->ContextRecord))
|
||||
if (thread_ctrl::get_current() && handle_access_violation(addr, is_writing, is_exec, pExp->ContextRecord))
|
||||
{
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
@ -2027,12 +2068,13 @@ static void signal_handler(int /*sig*/, siginfo_t* info, void* uct) noexcept
|
||||
#endif
|
||||
|
||||
const u64 exec64 = (reinterpret_cast<u64>(info->si_addr) - reinterpret_cast<u64>(vm::g_exec_addr)) / 2;
|
||||
const u64 exec64_2 = (reinterpret_cast<u64>(info->si_addr) - reinterpret_cast<u64>(vm::g_exec_addr)) - vm::g_exec_addr_seg_offset;
|
||||
const auto cause = is_executing ? "executing" : is_writing ? "writing" : "reading";
|
||||
|
||||
if (auto [addr, ok] = vm::try_get_addr(info->si_addr); ok && !is_executing)
|
||||
{
|
||||
// Try to process access violation
|
||||
if (thread_ctrl::get_current() && handle_access_violation(addr, is_writing, context))
|
||||
if (thread_ctrl::get_current() && handle_access_violation(addr, is_writing, false, context))
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -2040,7 +2082,14 @@ static void signal_handler(int /*sig*/, siginfo_t* info, void* uct) noexcept
|
||||
|
||||
if (exec64 < 0x100000000ull && !is_executing)
|
||||
{
|
||||
if (thread_ctrl::get_current() && handle_access_violation(static_cast<u32>(exec64), is_writing, context))
|
||||
if (thread_ctrl::get_current() && handle_access_violation(static_cast<u32>(exec64), is_writing, true, context))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (exec64_2 < 0x100000000ull && !is_executing)
|
||||
{
|
||||
if (thread_ctrl::get_current() && handle_access_violation(static_cast<u32>(exec64_2), is_writing, true, context))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@ -888,6 +888,14 @@ bool cpu_thread::check_state() noexcept
|
||||
store = true;
|
||||
}
|
||||
|
||||
if (flags & cpu_flag::req_exit)
|
||||
{
|
||||
// A request for the thread to quit has been made
|
||||
flags -= cpu_flag::req_exit;
|
||||
flags += cpu_flag::exit;
|
||||
store = true;
|
||||
}
|
||||
|
||||
// Can't process dbg_step if we only paused temporarily
|
||||
if (cpu_can_stop && flags & cpu_flag::dbg_step)
|
||||
{
|
||||
@ -1157,13 +1165,13 @@ void cpu_thread::notify()
|
||||
|
||||
cpu_thread& cpu_thread::operator=(thread_state)
|
||||
{
|
||||
if (state & cpu_flag::exit)
|
||||
if (state & (cpu_flag::exit + cpu_flag::req_exit))
|
||||
{
|
||||
// Must be notified elsewhere or self-raised
|
||||
return *this;
|
||||
}
|
||||
|
||||
const auto old = state.fetch_add(cpu_flag::exit);
|
||||
const auto old = state.fetch_add(cpu_flag::req_exit);
|
||||
|
||||
if (old & cpu_flag::wait && old.none_of(cpu_flag::again + cpu_flag::exit))
|
||||
{
|
||||
@ -1322,8 +1330,9 @@ extern std::shared_ptr<CPUDisAsm> make_disasm(const cpu_thread* cpu, shared_ptr<
|
||||
void cpu_thread::dump_all(std::string& ret) const
|
||||
{
|
||||
std::any func_data;
|
||||
std::any misc_data;
|
||||
|
||||
ret += dump_misc();
|
||||
dump_misc(ret, misc_data);
|
||||
ret += '\n';
|
||||
dump_regs(ret, func_data);
|
||||
ret += '\n';
|
||||
@ -1371,9 +1380,9 @@ std::vector<std::pair<u32, u32>> cpu_thread::dump_callstack_list() const
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string cpu_thread::dump_misc() const
|
||||
void cpu_thread::dump_misc(std::string& ret, std::any& /*custom_data*/) const
|
||||
{
|
||||
return fmt::format("%s[0x%x]; State: %s\n", get_class() == thread_class::ppu ? "PPU" : get_class() == thread_class::spu ? "SPU" : "RSX", id, state.load());
|
||||
fmt::append(ret, "%s[0x%x]; State: %s\n", get_class() == thread_class::ppu ? "PPU" : get_class() == thread_class::spu ? "SPU" : "RSX", id, state.load());
|
||||
}
|
||||
|
||||
bool cpu_thread::suspend_work::push(cpu_thread* _this) noexcept
|
||||
|
||||
@ -29,6 +29,7 @@ enum class cpu_flag : u32
|
||||
yield, // Thread is being requested to yield its execution time if it's running
|
||||
preempt, // Thread is being requested to preempt the execution of all CPU threads
|
||||
|
||||
req_exit, // Request the thread to exit
|
||||
dbg_global_pause, // Emulation paused
|
||||
dbg_pause, // Thread paused
|
||||
dbg_step, // Thread forced to pause after one step (one instruction, etc)
|
||||
@ -39,7 +40,7 @@ enum class cpu_flag : u32
|
||||
// Test stopped state
|
||||
constexpr bool is_stopped(bs_t<cpu_flag> state)
|
||||
{
|
||||
return !!(state & (cpu_flag::stop + cpu_flag::exit + cpu_flag::again));
|
||||
return !!(state & (cpu_flag::stop + cpu_flag::exit + cpu_flag::again + cpu_flag::req_exit));
|
||||
}
|
||||
|
||||
// Test paused state
|
||||
@ -176,7 +177,7 @@ public:
|
||||
virtual std::vector<std::pair<u32, u32>> dump_callstack_list() const;
|
||||
|
||||
// Get CPU dump of misc information
|
||||
virtual std::string dump_misc() const;
|
||||
virtual void dump_misc(std::string& ret, std::any& /*custom_data*/) const;
|
||||
|
||||
// Thread entry point function
|
||||
virtual void cpu_task() = 0;
|
||||
|
||||
@ -225,12 +225,66 @@ llvm::Value* cpu_translator::bitcast(llvm::Value* val, llvm::Type* type) const
|
||||
fmt::throw_exception("cpu_translator::bitcast(): incompatible type sizes (%u vs %u)", s1, s2);
|
||||
}
|
||||
|
||||
if (const auto c1 = llvm::dyn_cast<llvm::Constant>(val))
|
||||
if (val->getType() == type)
|
||||
{
|
||||
return val;
|
||||
}
|
||||
|
||||
llvm::CastInst* i;
|
||||
llvm::Value* source_val = val;
|
||||
|
||||
// Try to reuse older bitcasts
|
||||
while ((i = llvm::dyn_cast_or_null<llvm::CastInst>(source_val)) && i->getOpcode() == llvm::Instruction::BitCast)
|
||||
{
|
||||
source_val = i->getOperand(0);
|
||||
|
||||
if (source_val->getType() == type)
|
||||
{
|
||||
return source_val;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto it = source_val->use_begin(); it != source_val->use_end(); ++it)
|
||||
{
|
||||
llvm::Value* it_val = *it;
|
||||
|
||||
if (!it_val)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
llvm::CastInst* bci = llvm::dyn_cast_or_null<llvm::CastInst>(it_val);
|
||||
|
||||
// Walk through bitcasts
|
||||
while (bci && bci->getOpcode() == llvm::Instruction::BitCast)
|
||||
{
|
||||
if (bci->getParent() != m_ir->GetInsertBlock())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (bci->getType() == type)
|
||||
{
|
||||
return bci;
|
||||
}
|
||||
|
||||
if (bci->use_begin() == bci->use_end())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
bci = llvm::dyn_cast_or_null<llvm::CastInst>(*bci->use_begin());
|
||||
}
|
||||
}
|
||||
|
||||
// Do bitcast on the source
|
||||
|
||||
if (const auto c1 = llvm::dyn_cast<llvm::Constant>(source_val))
|
||||
{
|
||||
return ensure(llvm::ConstantFoldCastOperand(llvm::Instruction::BitCast, c1, type, m_module->getDataLayout()));
|
||||
}
|
||||
|
||||
return m_ir->CreateBitCast(val, type);
|
||||
return m_ir->CreateBitCast(source_val, type);
|
||||
}
|
||||
|
||||
template <>
|
||||
|
||||
@ -567,6 +567,32 @@ struct llvm_placeholder_t
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename U = llvm_common_t<llvm_value_t<T>>>
|
||||
struct llvm_place_stealer_t
|
||||
{
|
||||
// TODO: placeholder extracting actual constant values (u64, f64, vector, etc)
|
||||
|
||||
using type = T;
|
||||
|
||||
static constexpr bool is_ok = true;
|
||||
|
||||
llvm::Value* eval(llvm::IRBuilder<>*) const
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::tuple<> match(llvm::Value*& value, llvm::Module*) const
|
||||
{
|
||||
if (value && value->getType() == llvm_value_t<T>::get_type(value->getContext()))
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
value = nullptr;
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, bool ForceSigned = false>
|
||||
struct llvm_const_int
|
||||
{
|
||||
@ -3227,6 +3253,12 @@ public:
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static llvm_place_stealer_t<T> match_stealer()
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
requires requires { typename llvm_common_t<T>; }
|
||||
static auto match_expr(llvm::Value* v, llvm::Module* _m, T&& expr)
|
||||
@ -3951,6 +3983,15 @@ public:
|
||||
erase_stores({args.value...});
|
||||
}
|
||||
|
||||
// Debug breakpoint
|
||||
void debugtrap()
|
||||
{
|
||||
const auto _rty = llvm::Type::getVoidTy(m_context);
|
||||
const auto type = llvm::FunctionType::get(_rty, {}, false);
|
||||
const auto func = llvm::cast<llvm::Function>(m_ir->GetInsertBlock()->getParent()->getParent()->getOrInsertFunction("llvm.debugtrap", type).getCallee());
|
||||
m_ir->CreateCall(func);
|
||||
}
|
||||
|
||||
template <typename T, typename U>
|
||||
static auto pshufb(T&& a, U&& b)
|
||||
{
|
||||
|
||||
@ -1774,6 +1774,12 @@ public:
|
||||
|
||||
shared_mutex mutex;
|
||||
|
||||
gem_tracker& operator=(thread_state) noexcept
|
||||
{
|
||||
wake_up_tracker();
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
atomic_t<u32> m_wake_up_tracker = 0;
|
||||
atomic_t<u32> m_tracker_done = 0;
|
||||
|
||||
@ -1364,9 +1364,7 @@ void ppu_thread::dump_regs(std::string& ret, std::any& custom_data) const
|
||||
u32 preferred_cr_field_index = 7;
|
||||
};
|
||||
|
||||
dump_registers_data_t* func_data = nullptr;
|
||||
|
||||
func_data = std::any_cast<dump_registers_data_t>(&custom_data);
|
||||
dump_registers_data_t* func_data = std::any_cast<dump_registers_data_t>(&custom_data);
|
||||
|
||||
if (!func_data)
|
||||
{
|
||||
@ -2039,9 +2037,9 @@ std::vector<std::pair<u32, u32>> ppu_thread::dump_callstack_list() const
|
||||
return call_stack_list;
|
||||
}
|
||||
|
||||
std::string ppu_thread::dump_misc() const
|
||||
void ppu_thread::dump_misc(std::string& ret, std::any& custom_data) const
|
||||
{
|
||||
std::string ret = cpu_thread::dump_misc();
|
||||
cpu_thread::dump_misc(ret, custom_data);
|
||||
|
||||
if (ack_suspend)
|
||||
{
|
||||
@ -2096,7 +2094,6 @@ std::string ppu_thread::dump_misc() const
|
||||
{
|
||||
ret += '\n';
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void ppu_thread::dump_all(std::string& ret) const
|
||||
|
||||
@ -145,7 +145,7 @@ public:
|
||||
virtual void dump_regs(std::string&, std::any& custom_data) const override;
|
||||
virtual std::string dump_callstack() const override;
|
||||
virtual std::vector<std::pair<u32, u32>> dump_callstack_list() const override;
|
||||
virtual std::string dump_misc() const override;
|
||||
virtual void dump_misc(std::string& ret, std::any& custom_data) const override;
|
||||
virtual void dump_all(std::string&) const override;
|
||||
virtual void cpu_task() override final;
|
||||
virtual void cpu_sleep() override;
|
||||
|
||||
@ -13,6 +13,7 @@ struct spu_itype
|
||||
static constexpr struct quadrop_tag{} _quadrop{}; // 4-op Instructions
|
||||
static constexpr struct xfloat_tag{} xfloat{}; // Instructions producing xfloat values
|
||||
static constexpr struct zregmod_tag{} zregmod{}; // Instructions not modifying any GPR
|
||||
static constexpr struct pure_tag{} pure{}; // Instructions that always produce the same values as long as arguments are equal
|
||||
|
||||
enum class type : unsigned char
|
||||
{
|
||||
@ -158,6 +159,15 @@ struct spu_itype
|
||||
CUFLT,
|
||||
FRDS, // xfloat_tag last
|
||||
|
||||
CFLTS,
|
||||
CFLTU,
|
||||
FCEQ,
|
||||
FCMEQ,
|
||||
FCGT,
|
||||
FCMGT, // floating_tag last
|
||||
FSCRWR,
|
||||
FSCRRD,
|
||||
|
||||
DFA,
|
||||
DFS,
|
||||
DFM,
|
||||
@ -167,20 +177,11 @@ struct spu_itype
|
||||
DFNMA,
|
||||
FESD,
|
||||
|
||||
CFLTS,
|
||||
CFLTU,
|
||||
FCEQ,
|
||||
FCMEQ,
|
||||
FCGT,
|
||||
FCMGT,
|
||||
FSCRWR,
|
||||
FSCRRD,
|
||||
|
||||
DFCEQ,
|
||||
DFCMEQ,
|
||||
DFCGT,
|
||||
DFCMGT,
|
||||
DFTSV, // floating_tag last
|
||||
DFTSV,
|
||||
|
||||
SHLH, // shiftrot_tag first
|
||||
SHLHI,
|
||||
@ -248,10 +249,10 @@ struct spu_itype
|
||||
return value >= BR && value <= BISL;
|
||||
}
|
||||
|
||||
// Test for floating point instruction
|
||||
// Test for floating point instruction (32-bit float)
|
||||
friend constexpr bool operator &(type value, floating_tag)
|
||||
{
|
||||
return value >= FMA && value <= DFTSV;
|
||||
return value >= FMA && value <= FCMGT;
|
||||
}
|
||||
|
||||
// Test for 4-op instruction
|
||||
@ -301,8 +302,16 @@ struct spu_itype
|
||||
{
|
||||
return (value >= HEQ && value <= STQR) || (value >= BR && value <= BIHNZ);
|
||||
}
|
||||
|
||||
// Test for instructions which always produce the same values as long as arguments and immediate values are equal
|
||||
friend constexpr bool operator &(type value, pure_tag)
|
||||
{
|
||||
return (value >= ILH && value <= CLGTI);
|
||||
}
|
||||
};
|
||||
|
||||
using spu_itype_t = spu_itype::type;
|
||||
|
||||
struct spu_iflag
|
||||
{
|
||||
enum
|
||||
@ -528,6 +537,8 @@ struct spu_iflag
|
||||
}
|
||||
};
|
||||
|
||||
using spu_iflag_t = spu_iflag::flag;
|
||||
|
||||
#define NAME(x) static constexpr const char& x = *#x
|
||||
|
||||
struct spu_iname
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -24,6 +24,20 @@ union spu_opcode_t
|
||||
bf_t<u32, 7, 16> i16; // 9..24
|
||||
bf_t<s32, 7, 16> si16; // 9..24, signed
|
||||
bf_t<u32, 7, 18> i18; // 7..24
|
||||
|
||||
// For 16-bit instructions in the context of 32-bits
|
||||
u32 duplicate_si10() const
|
||||
{
|
||||
const u32 _16 = static_cast<u16>(static_cast<s16>(si10));
|
||||
return (_16 << 16) | _16;
|
||||
}
|
||||
|
||||
// For 8-bit instructions in the context of 32-bits
|
||||
u32 duplicate_duplicate_si10() const
|
||||
{
|
||||
const u32 _8 = static_cast<u8>(si10 & 0xff);
|
||||
return (_8 << 24) | (_8 << 16) | (_8 << 8) | _8;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr u32 spu_branch_target(u32 pc, u32 imm = 0)
|
||||
@ -42,6 +56,7 @@ constexpr u32 spu_decode(u32 inst)
|
||||
}
|
||||
|
||||
std::array<u32, 2> op_branch_targets(u32 pc, spu_opcode_t op);
|
||||
std::tuple<u32, std::array<u32, 3>, u32> op_register_targets(u32 /*pc*/, spu_opcode_t op);
|
||||
|
||||
// SPU decoder object. D provides functions. T is function pointer type returned.
|
||||
template <typename D, typename T = decltype(&D::UNK)>
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include "Utilities/lockless.h"
|
||||
#include "Utilities/address_range.h"
|
||||
#include "SPUThread.h"
|
||||
#include "SPUAnalyser.h"
|
||||
#include <vector>
|
||||
#include <bitset>
|
||||
#include <memory>
|
||||
@ -201,6 +202,25 @@ public:
|
||||
__bitset_enum_max
|
||||
};
|
||||
|
||||
enum compare_direction : u32
|
||||
{
|
||||
CMP_TURNAROUND_FLAG = 0x1,
|
||||
CMP_NEGATE_FLAG = 0x100,
|
||||
CMP_SLESS = 0,
|
||||
CMP_SGREATER = CMP_SLESS | CMP_TURNAROUND_FLAG,
|
||||
CMP_EQUAL,
|
||||
CMP_EQUAL2 = CMP_EQUAL | CMP_TURNAROUND_FLAG,
|
||||
CMP_LLESS,
|
||||
CMP_LGREATER = CMP_LLESS | CMP_TURNAROUND_FLAG,
|
||||
CMP_SGREATER_EQUAL = CMP_SLESS | CMP_NEGATE_FLAG,
|
||||
CMP_SLOWER_EQUAL = CMP_SGREATER | CMP_NEGATE_FLAG,
|
||||
CMP_NOT_EQUAL = CMP_EQUAL | CMP_NEGATE_FLAG,
|
||||
CMP_NOT_EQUAL2 = CMP_NOT_EQUAL | CMP_TURNAROUND_FLAG,
|
||||
CMP_LGREATER_EQUAL = CMP_LLESS | CMP_NEGATE_FLAG,
|
||||
CMP_LLOWER_EQUAL = CMP_LGREATER | CMP_NEGATE_FLAG,
|
||||
CMP_UNKNOWN,
|
||||
};
|
||||
|
||||
struct reg_state_t
|
||||
{
|
||||
bs_t<vf> flag{+vf::is_null};
|
||||
@ -273,6 +293,399 @@ public:
|
||||
static u32 alloc_tag(bool reset = false) noexcept;
|
||||
};
|
||||
|
||||
struct reduced_loop_t
|
||||
{
|
||||
bool active = false; // Single block loop detected
|
||||
bool failed = false;
|
||||
u32 loop_pc = SPU_LS_SIZE;
|
||||
u32 loop_end = SPU_LS_SIZE;
|
||||
|
||||
// False: single-block loop
|
||||
// True: loop with a trailing block of aftermath (iteration update) stuff (like for (u32 i = 0; i < 10; /*update*/ i++))
|
||||
bool is_two_block_loop = false;
|
||||
bool has_cond_state = false;
|
||||
|
||||
// Loop stay-in state requirement
|
||||
u64 cond_val_mask = umax;
|
||||
u64 cond_val_min = 0;
|
||||
u64 cond_val_size = 0;
|
||||
compare_direction cond_val_compare{};
|
||||
u64 cond_val_incr = 0;
|
||||
bool cond_val_incr_is_immediate = false;
|
||||
u64 cond_val_register_argument_idx = umax;
|
||||
u64 cond_val_register_idx = umax;
|
||||
bool cond_val_incr_before_cond = false;
|
||||
bool cond_val_incr_before_cond_taken_in_account = false;
|
||||
bool cond_val_is_immediate = false;
|
||||
|
||||
// Loop attributes
|
||||
bool is_constant_expression = false;
|
||||
bool is_secret = false;
|
||||
|
||||
struct supplemental_condition_t
|
||||
{
|
||||
u64 immediate_value = umax;
|
||||
u64 type_size = 0;
|
||||
compare_direction val_compare{};
|
||||
};
|
||||
|
||||
// Supplemental loop condition:
|
||||
// Inner conditions that depend on extrnal values (not produced inside the loop)
|
||||
// all should evaluate to false in order for the optimization to work (at the moment)
|
||||
// So succeeding can be treated linearly
|
||||
u64 expected_sup_conds = 0;
|
||||
u64 current_sup_conds_index = 0;
|
||||
std::vector<supplemental_condition_t> sup_conds;
|
||||
|
||||
void take_cond_val_incr_before_cond_into_account()
|
||||
{
|
||||
if (cond_val_is_immediate && cond_val_incr_before_cond_taken_in_account && !cond_val_incr_before_cond_taken_in_account)
|
||||
{
|
||||
cond_val_min -= cond_val_incr;
|
||||
cond_val_min &= cond_val_mask;
|
||||
cond_val_incr_before_cond_taken_in_account = true;
|
||||
}
|
||||
}
|
||||
|
||||
std::bitset<s_reg_max> loop_args;
|
||||
std::bitset<s_reg_max> loop_dicts;
|
||||
std::bitset<s_reg_max> loop_writes;
|
||||
|
||||
struct origin_t
|
||||
{
|
||||
std::bitset<s_reg_max> regs{};
|
||||
u32 modified = 0;
|
||||
spu_itype_t mod1_type = spu_itype::UNK;
|
||||
spu_itype_t mod2_type = spu_itype::UNK;
|
||||
spu_itype_t mod3_type = spu_itype::UNK;
|
||||
u32 IMM = 0;
|
||||
|
||||
private:
|
||||
// Internal, please access using fixed order
|
||||
spu_itype_t access_type(u32 i) const
|
||||
{
|
||||
if (i > modified)
|
||||
{
|
||||
return spu_itype::UNK;
|
||||
}
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case 1: return mod1_type;
|
||||
case 2: return mod2_type;
|
||||
case 3: return mod3_type;
|
||||
default: return spu_itype::UNK;
|
||||
}
|
||||
|
||||
return spu_itype::UNK;
|
||||
}
|
||||
public:
|
||||
|
||||
spu_itype_t reverse1_type()
|
||||
{
|
||||
return access_type(modified);
|
||||
}
|
||||
|
||||
spu_itype_t reverse2_type()
|
||||
{
|
||||
return access_type(modified - 1);
|
||||
}
|
||||
|
||||
spu_itype_t reverse3_type()
|
||||
{
|
||||
return access_type(modified - 2);
|
||||
}
|
||||
|
||||
origin_t& join_with_this(const origin_t& rhs)
|
||||
{
|
||||
regs |= rhs.regs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
origin_t& join_with_this(u32 rhs)
|
||||
{
|
||||
regs.set(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
origin_t& add_register_origin(u32 reg_val)
|
||||
{
|
||||
regs.set(reg_val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool is_single_reg_access(u32 reg_val) const
|
||||
{
|
||||
if (!modified)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return regs.count() == 1 && regs.test(reg_val);
|
||||
}
|
||||
|
||||
bool is_loop_dictator(u32 reg_val, bool test_predictable = false, bool should_predictable = true) const
|
||||
{
|
||||
if (!modified)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (regs.count() >= 1 && regs.test(reg_val))
|
||||
{
|
||||
if (!test_predictable)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (modified > 1)
|
||||
{
|
||||
return should_predictable ^ true;
|
||||
}
|
||||
|
||||
switch (mod1_type)
|
||||
{
|
||||
case spu_itype::A:
|
||||
{
|
||||
if (regs.count() == 2)
|
||||
{
|
||||
return should_predictable;
|
||||
}
|
||||
|
||||
return should_predictable ^ true;
|
||||
}
|
||||
case spu_itype::AI:
|
||||
case spu_itype::AHI:
|
||||
{
|
||||
if (IMM && regs.count() == 1)
|
||||
{
|
||||
return should_predictable;
|
||||
}
|
||||
|
||||
return should_predictable ^ true;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
|
||||
return should_predictable ^ true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_predictable_loop_dictator(u32 reg_val) const
|
||||
{
|
||||
return is_loop_dictator(reg_val, true, true);
|
||||
}
|
||||
|
||||
bool is_non_predictable_loop_dictator(u32 reg_val) const
|
||||
{
|
||||
return is_loop_dictator(reg_val, true, false);
|
||||
}
|
||||
|
||||
bool is_null(u32 reg_val) const noexcept
|
||||
{
|
||||
if (modified)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (regs.count() - (regs.test(reg_val) ? 1 : 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
origin_t& add_instruction_modifier(spu_itype_t inst_type, u32 imm = 0)
|
||||
{
|
||||
if (inst_type == spu_itype::UNK)
|
||||
{
|
||||
mod1_type = spu_itype::UNK;
|
||||
mod2_type = spu_itype::UNK;
|
||||
mod3_type = spu_itype::UNK;
|
||||
IMM = umax;
|
||||
modified = 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
if (modified == 1)
|
||||
{
|
||||
if (modified == 3)
|
||||
{
|
||||
mod1_type = spu_itype::UNK;
|
||||
mod2_type = spu_itype::UNK;
|
||||
mod3_type = spu_itype::UNK;
|
||||
IMM = umax;
|
||||
modified = 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool is_ok = false;
|
||||
switch (inst_type)
|
||||
{
|
||||
case spu_itype::XSBH:
|
||||
{
|
||||
const auto prev_type = modified == 1 ? mod1_type : mod2_type;
|
||||
is_ok &= mod1_type == spu_itype::CEQB || mod1_type == spu_itype::CEQBI || mod1_type == spu_itype::CGTB || mod1_type == spu_itype::CGTBI || mod1_type == spu_itype::CLGTB || mod1_type == spu_itype::CLGTBI;
|
||||
break;
|
||||
}
|
||||
case spu_itype::ANDI:
|
||||
{
|
||||
const auto prev_type = modified == 1 ? mod1_type : mod2_type;
|
||||
is_ok &= mod1_type == spu_itype::CEQB || mod1_type == spu_itype::CEQBI || mod1_type == spu_itype::CGTB || mod1_type == spu_itype::CGTBI || mod1_type == spu_itype::CLGTB || mod1_type == spu_itype::CLGTBI;
|
||||
is_ok &= (spu_opcode_t{imm}.si10 & 0xff) == 0xff;
|
||||
break;
|
||||
}
|
||||
case spu_itype::CEQ:
|
||||
case spu_itype::CEQH:
|
||||
case spu_itype::CEQB:
|
||||
case spu_itype::CGT:
|
||||
case spu_itype::CGTH:
|
||||
case spu_itype::CGTB:
|
||||
case spu_itype::CLGT:
|
||||
case spu_itype::CLGTH:
|
||||
case spu_itype::CLGTB:
|
||||
{
|
||||
is_ok = modified == 1 && (mod1_type == spu_itype::AI || mod1_type == spu_itype::AHI);
|
||||
IMM = imm;
|
||||
break;
|
||||
}
|
||||
case spu_itype::CEQI:
|
||||
case spu_itype::CEQHI:
|
||||
case spu_itype::CEQBI:
|
||||
case spu_itype::CGTI:
|
||||
case spu_itype::CGTHI:
|
||||
case spu_itype::CGTBI:
|
||||
case spu_itype::CLGTI:
|
||||
case spu_itype::CLGTHI:
|
||||
case spu_itype::CLGTBI:
|
||||
{
|
||||
is_ok = modified == 1 && (mod1_type == spu_itype::AI || mod1_type == spu_itype::AHI);
|
||||
IMM = spu_opcode_t{imm}.si10;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_ok)
|
||||
{
|
||||
mod1_type = spu_itype::UNK;
|
||||
mod2_type = spu_itype::UNK;
|
||||
mod3_type = spu_itype::UNK;
|
||||
IMM = umax;
|
||||
modified = 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
(modified == 1 ? mod2_type : mod3_type) = inst_type;
|
||||
modified++;
|
||||
return *this;
|
||||
}
|
||||
|
||||
mod1_type = inst_type;
|
||||
modified = 1;
|
||||
|
||||
switch (inst_type)
|
||||
{
|
||||
case spu_itype::AHI:
|
||||
{
|
||||
IMM = spu_opcode_t{imm}.duplicate_si10();
|
||||
return *this;
|
||||
}
|
||||
case spu_itype::AI:
|
||||
case spu_itype::ORI:
|
||||
case spu_itype::XORI:
|
||||
case spu_itype::ANDI:
|
||||
|
||||
case spu_itype::CEQI:
|
||||
case spu_itype::CEQHI:
|
||||
case spu_itype::CEQBI:
|
||||
case spu_itype::CGTI:
|
||||
case spu_itype::CGTHI:
|
||||
case spu_itype::CGTBI:
|
||||
case spu_itype::CLGTI:
|
||||
case spu_itype::CLGTHI:
|
||||
case spu_itype::CLGTBI:
|
||||
{
|
||||
IMM = spu_opcode_t{imm}.si10;
|
||||
return *this;
|
||||
}
|
||||
case spu_itype::ILA:
|
||||
{
|
||||
IMM = spu_opcode_t{imm}.i18;
|
||||
return *this;
|
||||
}
|
||||
case spu_itype::IOHL:
|
||||
case spu_itype::ILH:
|
||||
case spu_itype::ILHU:
|
||||
{
|
||||
IMM = spu_opcode_t{imm}.i16;
|
||||
return *this;
|
||||
}
|
||||
default:
|
||||
{
|
||||
IMM = imm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
static origin_t make_reg(u32 reg_val) noexcept
|
||||
{
|
||||
origin_t org{};
|
||||
org.add_register_origin(reg_val);
|
||||
return org;
|
||||
}
|
||||
|
||||
const origin_t* find_reg(u32 reg_val) const noexcept
|
||||
{
|
||||
for (auto& pair : regs)
|
||||
{
|
||||
if (pair.first == reg_val)
|
||||
{
|
||||
return &pair.second;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
origin_t* find_reg(u32 reg_val) noexcept
|
||||
{
|
||||
return const_cast<origin_t*>(std::as_const(*this).find_reg(reg_val));
|
||||
}
|
||||
|
||||
bool is_reg_null(u32 reg_val) const noexcept
|
||||
{
|
||||
if (const auto reg_found = find_reg(reg_val))
|
||||
{
|
||||
return reg_found->is_null(reg_val);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
origin_t get_reg(u32 reg_val) noexcept
|
||||
{
|
||||
const auto org = find_reg(reg_val);
|
||||
return org ? *org : regs.emplace_back(reg_val, std::remove_reference_t<decltype(*org)>{}).second;
|
||||
}
|
||||
|
||||
std::vector<std::pair<u8, origin_t>> regs;
|
||||
|
||||
// Return old state for error reporting
|
||||
reduced_loop_t discard()
|
||||
{
|
||||
const reduced_loop_t old = *this;
|
||||
*this = reduced_loop_t{};
|
||||
return old;
|
||||
}
|
||||
};
|
||||
|
||||
protected:
|
||||
spu_runtime* m_spurt{};
|
||||
|
||||
@ -326,8 +739,14 @@ protected:
|
||||
// Set if the initial register value in this block may be xfloat
|
||||
std::bitset<s_reg_max> reg_maybe_xf{};
|
||||
|
||||
// Bit mask of the registers used (before modified)
|
||||
std::bitset<s_reg_max> reg_use{};
|
||||
// Set if register is used in floating pont instruction
|
||||
std::bitset<s_reg_max> reg_maybe_float{};
|
||||
|
||||
// Set if register is used as shuffle mask
|
||||
std::bitset<s_reg_max> reg_maybe_shuffle_mask{};
|
||||
|
||||
// Number of times registers are used (before modified)
|
||||
std::array<u32, s_reg_max> reg_use{};
|
||||
|
||||
// Bit mask of the trivial (u32 x 4) constant value resulting in this block
|
||||
std::bitset<s_reg_max> reg_const{};
|
||||
@ -391,18 +810,23 @@ protected:
|
||||
putllc16,
|
||||
putllc0,
|
||||
rchcnt_loop,
|
||||
reduced_loop,
|
||||
};
|
||||
|
||||
std::vector<inst_attr> m_inst_attrs;
|
||||
|
||||
struct pattern_info
|
||||
{
|
||||
u64 info;
|
||||
// Info via integral
|
||||
u64 info{};
|
||||
|
||||
// Info via additional erased-typed pointer
|
||||
std::shared_ptr<void> info_ptr;
|
||||
};
|
||||
|
||||
std::unordered_map<u32, pattern_info> m_patterns;
|
||||
std::map<u32, pattern_info> m_patterns;
|
||||
|
||||
void add_pattern(inst_attr attr, u32 start, u64 info);
|
||||
void add_pattern(inst_attr attr, u32 start, u64 info, std::shared_ptr<void> info_ptr = nullptr);
|
||||
|
||||
private:
|
||||
// For private use
|
||||
@ -435,7 +859,7 @@ public:
|
||||
spu_program analyse(const be_t<u32>* ls, u32 entry_point, std::map<u32, std::vector<u32>>* out_target_list = nullptr);
|
||||
|
||||
// Print analyser internal state
|
||||
void dump(const spu_program& result, std::string& out);
|
||||
void dump(const spu_program& result, std::string& out, u32 block_min = 0, u32 block_max = SPU_LS_SIZE);
|
||||
|
||||
// Get SPU Runtime
|
||||
spu_runtime& get_runtime()
|
||||
|
||||
@ -495,7 +495,8 @@ void do_cell_atomic_128_store(u32 addr, const void* to_write);
|
||||
|
||||
extern thread_local u64 g_tls_fault_spu;
|
||||
|
||||
const spu_decoder<spu_itype> s_spu_itype;
|
||||
const extern spu_decoder<spu_itype> g_spu_itype;
|
||||
const extern spu_decoder<spu_iflag> g_spu_iflag;
|
||||
|
||||
namespace vm
|
||||
{
|
||||
@ -598,7 +599,7 @@ std::array<u32, 2> op_branch_targets(u32 pc, spu_opcode_t op)
|
||||
{
|
||||
std::array<u32, 2> res{spu_branch_target(pc + 4), umax};
|
||||
|
||||
switch (const auto type = s_spu_itype.decode(op.opcode))
|
||||
switch (const auto type = g_spu_itype.decode(op.opcode))
|
||||
{
|
||||
case spu_itype::BR:
|
||||
case spu_itype::BRA:
|
||||
@ -639,6 +640,54 @@ std::array<u32, 2> op_branch_targets(u32 pc, spu_opcode_t op)
|
||||
return res;
|
||||
}
|
||||
|
||||
std::tuple<u32, std::array<u32, 3>, u32> op_register_targets(u32 /*pc*/, spu_opcode_t op)
|
||||
{
|
||||
std::tuple<u32, std::array<u32, 3>, u32> result{u32{umax}, std::array<u32, 3>{128, 128, 128}, op.opcode};
|
||||
|
||||
const auto type = g_spu_itype.decode(op.opcode);
|
||||
|
||||
if (type & spu_itype::zregmod)
|
||||
{
|
||||
std::get<2>(result) = 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
std::get<0>(result) = type & spu_itype::_quadrop ? op.rt4 : op.rt;
|
||||
|
||||
spu_opcode_t op_masked = op;
|
||||
|
||||
if (type & spu_itype::_quadrop)
|
||||
{
|
||||
op_masked.rt4 = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
op_masked.rt = 0;
|
||||
}
|
||||
|
||||
std::get<2>(result) = op_masked.opcode;
|
||||
|
||||
if (auto iflags = g_spu_iflag.decode(op.opcode))
|
||||
{
|
||||
if (+iflags & +spu_iflag::use_ra)
|
||||
{
|
||||
std::get<1>(result)[0] = op.ra;
|
||||
}
|
||||
|
||||
if (+iflags & +spu_iflag::use_rb)
|
||||
{
|
||||
std::get<1>(result)[1] = op.rb;
|
||||
}
|
||||
|
||||
if (+iflags & +spu_iflag::use_rc)
|
||||
{
|
||||
std::get<1>(result)[2] = op.rc;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void spu_int_ctrl_t::set(u64 ints)
|
||||
{
|
||||
// leave only enabled interrupts
|
||||
@ -988,7 +1037,7 @@ std::vector<std::pair<u32, u32>> spu_thread::dump_callstack_list() const
|
||||
passed[i / 4] = true;
|
||||
|
||||
const spu_opcode_t op{_ref<u32>(i)};
|
||||
const auto type = s_spu_itype.decode(op.opcode);
|
||||
const auto type = g_spu_itype.decode(op.opcode);
|
||||
|
||||
if (start == 0 && type == spu_itype::STQD && op.ra == 1u && op.rt == 0u)
|
||||
{
|
||||
@ -1090,11 +1139,62 @@ std::vector<std::pair<u32, u32>> spu_thread::dump_callstack_list() const
|
||||
return call_stack_list;
|
||||
}
|
||||
|
||||
std::string spu_thread::dump_misc() const
|
||||
void spu_thread::dump_misc(std::string& ret, std::any& custom_data) const
|
||||
{
|
||||
std::string ret = cpu_thread::dump_misc();
|
||||
cpu_thread::dump_misc(ret, custom_data);
|
||||
|
||||
fmt::append(ret, "Block Weight: %u (Retreats: %u)", block_counter, block_failure);
|
||||
struct dump_misc_data_t
|
||||
{
|
||||
u32 cpu_id = umax;
|
||||
u64 last_read_time = umax;
|
||||
u64 last_block_counter = umax;
|
||||
u64 update_count = 0;
|
||||
|
||||
std::pair<u64, u64> update(u64 current_block_counter, u64 current_timestamp = get_system_time())
|
||||
{
|
||||
const u64 diff_time = current_timestamp <= last_read_time ? 0 : current_timestamp - last_read_time;
|
||||
const u64 diff_block = current_block_counter <= last_block_counter ? 0 : current_block_counter - last_block_counter;
|
||||
|
||||
if (last_read_time == umax || update_count >= 1000)
|
||||
{
|
||||
last_read_time = current_timestamp;
|
||||
last_block_counter = current_block_counter;
|
||||
update_count = 0;
|
||||
}
|
||||
else if (diff_time >= 100000 && diff_block >= 100)
|
||||
{
|
||||
// Update values to measure rate (but not fully so rate can be measured later)
|
||||
last_read_time += diff_time / 10 * 9;
|
||||
last_block_counter += diff_block / 10 * 9;
|
||||
update_count++;
|
||||
}
|
||||
|
||||
return {diff_time, diff_block};
|
||||
}
|
||||
};
|
||||
|
||||
dump_misc_data_t* func_data = std::any_cast<dump_misc_data_t>(&custom_data);
|
||||
|
||||
if (!func_data)
|
||||
{
|
||||
custom_data.reset();
|
||||
custom_data = std::make_any<dump_misc_data_t>();
|
||||
func_data = ensure(std::any_cast<dump_misc_data_t>(&custom_data));
|
||||
}
|
||||
|
||||
if (func_data->cpu_id != this->id)
|
||||
{
|
||||
*func_data = {};
|
||||
func_data->cpu_id = this->id;
|
||||
}
|
||||
|
||||
const u64 current_block_counter = atomic_storage<u64>::load(block_counter);
|
||||
|
||||
const auto [diff_time, diff_block] = func_data->update(current_block_counter);
|
||||
|
||||
const u64 rate_of_diff = diff_block ? std::max<u64>(1, utils::rational_mul<u64>(diff_block, 1'000'000, std::max<u64>(diff_time, 1))) : 0;
|
||||
|
||||
fmt::append(ret, "Block Weight: log10(%u/second): %.1f (Retreats: %u)", rate_of_diff, std::log10(std::max<u64>(rate_of_diff, 10)), block_failure);
|
||||
|
||||
if (u64 hash = atomic_storage<u64>::load(block_hash))
|
||||
{
|
||||
@ -1145,8 +1245,6 @@ std::string spu_thread::dump_misc() const
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void spu_thread::cpu_on_stop()
|
||||
@ -3761,7 +3859,7 @@ bool spu_thread::is_exec_code(u32 addr, std::span<const u8> ls_ptr, u32 base_add
|
||||
|
||||
const u32 addr0 = spu_branch_target(addr);
|
||||
const spu_opcode_t op{read_from_ptr<be_t<u32>>(ls_ptr, addr0 - base_addr)};
|
||||
const auto type = s_spu_itype.decode(op.opcode);
|
||||
const auto type = g_spu_itype.decode(op.opcode);
|
||||
|
||||
if (type == spu_itype::UNK || !op.opcode)
|
||||
{
|
||||
@ -3907,7 +4005,7 @@ bool spu_thread::is_exec_code(u32 addr, std::span<const u8> ls_ptr, u32 base_add
|
||||
// Test the validity of a single instruction of the optional target
|
||||
// This function can't be too slow and is unlikely to improve results by a great deal
|
||||
const u32 op0 = read_from_ptr<be_t<u32>>(ls_ptr, route_pc - base_addr);
|
||||
const spu_itype::type type0 = s_spu_itype.decode(op0);
|
||||
const spu_itype::type type0 = g_spu_itype.decode(op0);
|
||||
|
||||
if (type0 == spu_itype::UNK || !op0)
|
||||
{
|
||||
@ -6878,7 +6976,7 @@ spu_exec_object spu_thread::capture_memory_as_elf(std::span<spu_memory_segment_d
|
||||
const u32 op = read_from_ptr<be_t<u32>>(all_data, pc0 - 4);
|
||||
|
||||
// Try to find function entry (if they are placed sequentially search for BI $LR of previous function)
|
||||
if (!op || op == 0x35000000u || s_spu_itype.decode(op) == spu_itype::UNK)
|
||||
if (!op || op == 0x35000000u || g_spu_itype.decode(op) == spu_itype::UNK)
|
||||
{
|
||||
if (is_exec_code(pc0, { all_data.data(), SPU_LS_SIZE }))
|
||||
break;
|
||||
|
||||
@ -630,7 +630,7 @@ public:
|
||||
virtual void dump_regs(std::string&, std::any& custom_data) const override;
|
||||
virtual std::string dump_callstack() const override;
|
||||
virtual std::vector<std::pair<u32, u32>> dump_callstack_list() const override;
|
||||
virtual std::string dump_misc() const override;
|
||||
virtual void dump_misc(std::string& ret, std::any& custom_data) const override;
|
||||
virtual void cpu_task() override final;
|
||||
virtual void cpu_on_stop() override;
|
||||
virtual void cpu_return() override;
|
||||
|
||||
@ -2774,9 +2774,9 @@ namespace rsx
|
||||
recovered_fifo_cmds_history.push({fifo_ctrl->last_cmd(), current_time});
|
||||
}
|
||||
|
||||
std::string thread::dump_misc() const
|
||||
void thread::dump_misc(std::string& ret, std::any& custom_data) const
|
||||
{
|
||||
std::string ret = cpu_thread::dump_misc();
|
||||
cpu_thread::dump_misc(ret, custom_data);
|
||||
|
||||
const auto flags = +state;
|
||||
|
||||
@ -2789,8 +2789,6 @@ namespace rsx
|
||||
{
|
||||
fmt::append(ret, "\n");
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::vector<std::pair<u32, u32>> thread::dump_callstack_list() const
|
||||
|
||||
@ -122,7 +122,7 @@ namespace rsx
|
||||
std::unique_ptr<FIFO::FIFO_control> fifo_ctrl;
|
||||
atomic_t<bool> rsx_thread_running{ false };
|
||||
std::vector<std::pair<u32, u32>> dump_callstack_list() const override;
|
||||
std::string dump_misc() const override;
|
||||
void dump_misc(std::string& ret, std::any& custom_data) const override;
|
||||
|
||||
protected:
|
||||
FIFO::flattening_helper m_flattener;
|
||||
|
||||
@ -49,6 +49,7 @@
|
||||
|
||||
#include <memory>
|
||||
#include <regex>
|
||||
#include <shared_mutex>
|
||||
|
||||
#include "Utilities/JIT.h"
|
||||
|
||||
@ -3075,7 +3076,7 @@ void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op, bool savesta
|
||||
std::vector<stx::shared_ptr<named_thread<ppu_thread>>> ppu_thread_list;
|
||||
|
||||
// If EXITGAME signal is not read, force kill after a second.
|
||||
constexpr int loop_timeout_ms = 50;
|
||||
constexpr int loop_timeout_ms = 16;
|
||||
int kill_timeout_ms = 1000;
|
||||
int elapsed_ms = 0;
|
||||
|
||||
@ -3092,8 +3093,10 @@ void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op, bool savesta
|
||||
Resume();
|
||||
}, nullptr, true, read_counter);
|
||||
|
||||
std::shared_lock rlock(id_manager::g_mutex, std::defer_lock);
|
||||
|
||||
// Check if the EXITGAME signal was read. We allow the game to terminate itself if that's the case.
|
||||
if (!read_sysutil_signal && read_counter != get_sysutil_cb_manager_read_count())
|
||||
if (!read_sysutil_signal && read_counter != get_sysutil_cb_manager_read_count() && rlock.try_lock())
|
||||
{
|
||||
sys_log.notice("The game received the exit request. Waiting for it to terminate itself...");
|
||||
kill_timeout_ms += 5000; // Grant a couple more seconds
|
||||
@ -3103,7 +3106,12 @@ void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op, bool savesta
|
||||
idm::select<named_thread<ppu_thread>>([&](u32 id, cpu_thread&)
|
||||
{
|
||||
ppu_thread_list.emplace_back(idm::get_unlocked<named_thread<ppu_thread>>(id));
|
||||
});
|
||||
}, idm::unlocked);
|
||||
}
|
||||
|
||||
if (rlock)
|
||||
{
|
||||
rlock.unlock();
|
||||
}
|
||||
|
||||
if (static_cast<u64>(info) != m_stop_ctr || Emu.IsStopped())
|
||||
|
||||
@ -872,7 +872,7 @@ std::function<cpu_thread*()> debugger_frame::make_check_cpu(cpu_thread* cpu, boo
|
||||
{
|
||||
constexpr cpu_thread* null_cpu = nullptr;
|
||||
|
||||
if (Emu.IsStopped())
|
||||
if (Emu.IsStopped(true))
|
||||
{
|
||||
return []() { return null_cpu; };
|
||||
}
|
||||
@ -921,7 +921,7 @@ std::function<cpu_thread*()> debugger_frame::make_check_cpu(cpu_thread* cpu, boo
|
||||
|
||||
return [cpu, type, shared = std::move(shared), emulation_id = Emu.GetEmulationIdentifier()]() mutable -> cpu_thread*
|
||||
{
|
||||
if (emulation_id != Emu.GetEmulationIdentifier() || Emu.IsStopped())
|
||||
if (emulation_id != Emu.GetEmulationIdentifier() || Emu.IsStopped(true))
|
||||
{
|
||||
// Invalidate all data after Emu.Kill()
|
||||
shared.reset();
|
||||
@ -1040,7 +1040,7 @@ void debugger_frame::UpdateUnitList()
|
||||
const u64 emulation_id = static_cast<std::underlying_type_t<Emulator::stop_counter_t>>(Emu.GetEmulationIdentifier());
|
||||
const u64 threads_created = cpu_thread::g_threads_created;
|
||||
const u64 threads_deleted = cpu_thread::g_threads_deleted;
|
||||
const system_state emu_state = Emu.GetStatus();
|
||||
const system_state emu_state = Emu.GetStatus(false);
|
||||
|
||||
std::unique_lock<shared_mutex> lock{id_manager::g_mutex, std::defer_lock};
|
||||
|
||||
@ -1077,6 +1077,11 @@ void debugger_frame::UpdateUnitList()
|
||||
{
|
||||
std::function<cpu_thread*()> func_cpu = make_check_cpu(std::addressof(cpu), true);
|
||||
|
||||
if (cpu.state & cpu_flag::exit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Space at the end is to pad a gap on the right
|
||||
cpu_list.emplace_back(QString::fromStdString((id >> 24 == 0x55 ? "RSX[0x55555555]" : cpu.get_name()) + ' '), std::move(func_cpu));
|
||||
|
||||
@ -1469,8 +1474,11 @@ void debugger_frame::WritePanels(cpu_thread* cpu)
|
||||
|
||||
int loc = m_misc_state->verticalScrollBar()->value();
|
||||
int hloc = m_misc_state->horizontalScrollBar()->value();
|
||||
|
||||
m_last_misc_state.clear();
|
||||
cpu->dump_misc(m_last_misc_state, m_dump_misc_func_data);
|
||||
m_misc_state->clear();
|
||||
m_misc_state->setPlainText(QString::fromStdString(cpu->dump_misc()));
|
||||
m_misc_state->setPlainText(QString::fromStdString(m_last_misc_state));
|
||||
m_misc_state->verticalScrollBar()->setValue(loc);
|
||||
m_misc_state->horizontalScrollBar()->setValue(hloc);
|
||||
|
||||
|
||||
@ -69,7 +69,9 @@ class debugger_frame : public custom_dock_widget
|
||||
u32 m_last_pc = -1;
|
||||
std::vector<char> m_last_query_state;
|
||||
std::string m_last_reg_state;
|
||||
std::string m_last_misc_state;
|
||||
std::any m_dump_reg_func_data;
|
||||
std::any m_dump_misc_func_data;
|
||||
std::vector<std::function<cpu_thread*()>> m_threads_info;
|
||||
u32 m_last_step_over_breakpoint = -1;
|
||||
u64 m_ui_update_ctr = 0;
|
||||
|
||||
@ -128,7 +128,7 @@ extern void qt_events_aware_op(int repeat_duration_ms, std::function<bool()> wra
|
||||
}
|
||||
else
|
||||
{
|
||||
QTimer::singleShot(repeat_duration_ms, *check_iteration);
|
||||
QTimer::singleShot(repeat_duration_ms, event_loop, *check_iteration);
|
||||
}
|
||||
});
|
||||
|
||||
@ -138,7 +138,7 @@ extern void qt_events_aware_op(int repeat_duration_ms, std::function<bool()> wra
|
||||
event_loop = new QEventLoop();
|
||||
|
||||
// Queue event initially
|
||||
QTimer::singleShot(0, *check_iteration);
|
||||
QTimer::singleShot(0, event_loop, *check_iteration);
|
||||
|
||||
// Event loop
|
||||
event_loop->exec();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user