This commit is contained in:
Elad 2026-03-25 18:25:59 +00:00 committed by GitHub
commit 50977f8788
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 2871 additions and 173 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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)
{

View File

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

View File

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

View File

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

View File

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

View File

@ -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)>

View File

@ -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()

View File

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

View File

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

View File

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

View File

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

View File

@ -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())

View File

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

View File

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

View File

@ -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();