mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-04-28 13:28:01 +03:00

* Update asmjit dependency (aarch64 branch) * Disable USE_DISCORD_RPC by default * Dump some JIT objects in rpcs3 cache dir * Add SIGILL handler for all platforms * Fix resetting zeroing denormals in thread pool * Refactor most v128:: utils into global gv_** functions * Refactor PPU interpreter (incomplete), remove "precise" * - Instruction specializations with multiple accuracy flags * - Adjust calling convention for speed * - Removed precise/fast setting, replaced with static * - Started refactoring interpreters for building at runtime JIT * (I got tired of poor compiler optimizations) * - Expose some accuracy settings (SAT, NJ, VNAN, FPCC) * - Add exec_bytes PPU thread variable (akin to cycle count) * PPU LLVM: fix VCTUXS+VCTSXS instruction NaN results * SPU interpreter: remove "precise" for now (extremely non-portable) * - As with PPU, settings changed to static/dynamic for interpreters. * - Precise options will be implemented later * Fix termination after fatal error dialog
388 lines
7.8 KiB
C++
388 lines
7.8 KiB
C++
#include "stdafx.h"
|
|
#include "SPUDisAsm.h"
|
|
#include "SPUAnalyser.h"
|
|
#include "SPUThread.h"
|
|
|
|
const spu_decoder<SPUDisAsm> s_spu_disasm;
|
|
const extern spu_decoder<spu_itype> g_spu_itype;
|
|
const extern spu_decoder<spu_iname> g_spu_iname;
|
|
const extern spu_decoder<spu_iflag> g_spu_iflag;
|
|
|
|
#include "util/v128.hpp"
|
|
#include "util/simd.hpp"
|
|
|
|
u32 SPUDisAsm::disasm(u32 pc)
|
|
{
|
|
last_opcode.clear();
|
|
|
|
if (pc < m_start_pc || pc >= SPU_LS_SIZE)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
dump_pc = pc;
|
|
be_t<u32> op;
|
|
std::memcpy(&op, m_offset + pc, 4);
|
|
m_op = op;
|
|
(this->*(s_spu_disasm.decode(m_op)))({ m_op });
|
|
|
|
format_by_mode();
|
|
return 4;
|
|
}
|
|
|
|
std::pair<const void*, usz> SPUDisAsm::get_memory_span() const
|
|
{
|
|
return {m_offset + m_start_pc, SPU_LS_SIZE - m_start_pc};
|
|
}
|
|
|
|
std::unique_ptr<CPUDisAsm> SPUDisAsm::copy_type_erased() const
|
|
{
|
|
return std::make_unique<SPUDisAsm>(*this);
|
|
}
|
|
|
|
std::pair<bool, v128> SPUDisAsm::try_get_const_value(u32 reg, u32 pc, u32 TTL) const
|
|
{
|
|
if (!TTL)
|
|
{
|
|
// Recursion limit (Time To Live)
|
|
return {};
|
|
}
|
|
|
|
if (pc == umax)
|
|
{
|
|
// Default arg: choose pc of previous instruction
|
|
|
|
if (dump_pc == 0)
|
|
{
|
|
// Do not underflow
|
|
return {};
|
|
}
|
|
|
|
pc = dump_pc - 4;
|
|
}
|
|
|
|
// Scan LS backwards from this instruction (until PC=0)
|
|
// Search for the first register modification or branch instruction
|
|
|
|
for (s32 i = static_cast<s32>(pc); i >= static_cast<s32>(m_start_pc); i -= 4)
|
|
{
|
|
const u32 opcode = *reinterpret_cast<const be_t<u32>*>(m_offset + i);
|
|
const spu_opcode_t op0{ opcode };
|
|
|
|
const auto type = g_spu_itype.decode(opcode);
|
|
|
|
if (type & spu_itype::branch || type == spu_itype::UNK || !opcode)
|
|
{
|
|
if (reg < 80u)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
// We do not care about function calls if register is non-volatile
|
|
if ((type != spu_itype::BRSL && type != spu_itype::BRASL && type != spu_itype::BISL) || op0.rt == reg)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
// Get constant register value
|
|
#define GET_CONST_REG(var, reg) \
|
|
{\
|
|
/* Search for the constant value of the register*/\
|
|
const auto [is_const, value] = try_get_const_value(reg, i - 4, TTL - 1);\
|
|
\
|
|
if (!is_const)\
|
|
{\
|
|
/* Cannot compute constant value if register is not constant*/\
|
|
return {};\
|
|
}\
|
|
\
|
|
var = value;\
|
|
} void() /*<- Require a semicolon*/
|
|
|
|
//const auto flag = g_spu_iflag.decode(opcode);
|
|
|
|
// TODO: It detects spurious register modifications
|
|
if (u32 dst = type & spu_itype::_quadrop ? +op0.rt4 : +op0.rt; dst == reg)
|
|
{
|
|
// Note: It's not 100% reliable because it won't detect branch targets within [i, dump_pc] range (e.g. if-else statement for command's value)
|
|
switch (type)
|
|
{
|
|
case spu_itype::IL:
|
|
{
|
|
return { true, v128::from32p(op0.si16) };
|
|
}
|
|
case spu_itype::ILA:
|
|
{
|
|
return { true, v128::from32p(op0.i18) };
|
|
}
|
|
case spu_itype::ILHU:
|
|
{
|
|
return { true, v128::from32p(op0.i16 << 16) };
|
|
}
|
|
case spu_itype::ILH:
|
|
{
|
|
return { true, v128::from16p(op0.i16) };
|
|
}
|
|
case spu_itype::CBD:
|
|
case spu_itype::CHD:
|
|
case spu_itype::CWD:
|
|
case spu_itype::CDD:
|
|
{
|
|
// Aligned stack assumption
|
|
if (op0.ra == 1u)
|
|
{
|
|
u32 size = 0;
|
|
|
|
switch (type)
|
|
{
|
|
case spu_itype::CBD: size = 1; break;
|
|
case spu_itype::CHD: size = 2; break;
|
|
case spu_itype::CWD: size = 4; break;
|
|
case spu_itype::CDD: size = 8; break;
|
|
default: fmt::throw_exception("Unreachable");
|
|
}
|
|
|
|
const u32 index = (~op0.i7 & 0xf) / size;
|
|
auto res = v128::from64(0x18191A1B1C1D1E1Full, 0x1011121314151617ull);
|
|
|
|
switch (size)
|
|
{
|
|
case 1: res._u8[index] = 0x03; break;
|
|
case 2: res._u16[index] = 0x0203; break;
|
|
case 4: res._u32[index] = 0x00010203; break;
|
|
case 8: res._u64[index] = 0x0001020304050607ull; break;
|
|
default: fmt::throw_exception("Unreachable");
|
|
}
|
|
|
|
return {true, res};
|
|
}
|
|
|
|
return {};
|
|
}
|
|
case spu_itype::FSMBI:
|
|
{
|
|
v128 res;
|
|
|
|
for (s32 i = 0; i < 16; i++)
|
|
{
|
|
res._u8[i] = (op0.i16 & (1 << i)) ? 0xFF : 0x00;
|
|
}
|
|
|
|
return { true, res };
|
|
}
|
|
case spu_itype::IOHL:
|
|
{
|
|
v128 reg_val{};
|
|
|
|
// Search for ILHU+IOHL pattern (common pattern for 32-bit constants formation)
|
|
// But don't limit to it
|
|
GET_CONST_REG(reg_val, op0.rt);
|
|
|
|
return { true, reg_val | v128::from32p(op0.i16) };
|
|
}
|
|
case spu_itype::STQA:
|
|
case spu_itype::STQD:
|
|
case spu_itype::STQR:
|
|
case spu_itype::STQX:
|
|
case spu_itype::WRCH:
|
|
{
|
|
// Do not modify RT
|
|
break;
|
|
}
|
|
case spu_itype::SHLQBYI:
|
|
{
|
|
if (op0.si7)
|
|
{
|
|
// Unimplemented, doubt needed
|
|
return {};
|
|
}
|
|
|
|
// Move register value operation
|
|
v128 reg_val{};
|
|
GET_CONST_REG(reg_val, op0.ra);
|
|
|
|
return { true, reg_val };
|
|
}
|
|
case spu_itype::ORI:
|
|
{
|
|
v128 reg_val{};
|
|
GET_CONST_REG(reg_val, op0.ra);
|
|
|
|
return { true, reg_val | v128::from32p(op0.si10) };
|
|
}
|
|
default: return {};
|
|
}
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
SPUDisAsm::insert_mask_info SPUDisAsm::try_get_insert_mask_info(const v128& mask)
|
|
{
|
|
if ((mask & v128::from8p(0xe0)) != v128{})
|
|
{
|
|
return {};
|
|
}
|
|
|
|
s32 first = -1, src_first = 0, last = 16;
|
|
|
|
auto access = [&](u32 index) -> u8
|
|
{
|
|
return mask._u8[index ^ 0xf];
|
|
};
|
|
|
|
for (s32 i = 0; i < 16; i++)
|
|
{
|
|
if (access(i) & 0x10)
|
|
{
|
|
if ((access(i) & 0x0f) != i)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
if (first != -1 && last == 16)
|
|
{
|
|
last = i;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (last != 16)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
if (first == -1)
|
|
{
|
|
src_first = access(i);
|
|
first = i;
|
|
}
|
|
|
|
if (src_first + (i - first) != access(i))
|
|
{
|
|
return {};
|
|
}
|
|
}
|
|
|
|
if (first == -1)
|
|
{
|
|
return {};
|
|
}
|
|
|
|
const u32 size = last - first;
|
|
|
|
if ((size | src_first | first) & (size - 1))
|
|
{
|
|
return {};
|
|
}
|
|
|
|
if (size == 16)
|
|
{
|
|
// 0x0, 0x1, 0x2, .. 0xE, 0xF is not allowed
|
|
return {};
|
|
}
|
|
|
|
// [type size, dst index, src index]
|
|
return {size, first / size, src_first / size};
|
|
}
|
|
|
|
void SPUDisAsm::WRCH(spu_opcode_t op)
|
|
{
|
|
DisAsm("wrch", spu_ch_name[op.ra], spu_reg_name[op.rt]);
|
|
|
|
const auto [is_const, value] = try_get_const_value(op.rt);
|
|
|
|
if (is_const)
|
|
{
|
|
switch (op.ra)
|
|
{
|
|
case MFC_Cmd:
|
|
{
|
|
fmt::append(last_opcode, " #%s", MFC(value._u8[12]));
|
|
return;
|
|
}
|
|
case MFC_WrListStallAck:
|
|
case MFC_WrTagMask:
|
|
{
|
|
const u32 v = value._u32[3];
|
|
if (v && !(v & (v - 1)))
|
|
fmt::append(last_opcode, " #%s (tag=%u)", SignedHex(v), std::countr_zero(v)); // Single-tag mask
|
|
else
|
|
fmt::append(last_opcode, " #%s", SignedHex(v)); // Multi-tag mask (or zero)
|
|
return;
|
|
}
|
|
case MFC_EAH:
|
|
{
|
|
fmt::append(last_opcode, " #%s", SignedHex(value._u32[3]));
|
|
return;
|
|
}
|
|
case MFC_Size:
|
|
{
|
|
fmt::append(last_opcode, " #%s", SignedHex(value._u16[6]));
|
|
return;
|
|
}
|
|
case MFC_TagID:
|
|
{
|
|
fmt::append(last_opcode, " #%u", value._u8[12]);
|
|
return;
|
|
}
|
|
case MFC_WrTagUpdate:
|
|
{
|
|
const auto upd = fmt::format("%s", mfc_tag_update(value._u32[3]));
|
|
fmt::append(last_opcode, " #%s", upd == "empty" ? "IMMEDIATE" : upd);
|
|
return;
|
|
}
|
|
default:
|
|
{
|
|
fmt::append(last_opcode, " #%s", SignedHex(value._u32[3]));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
enum CellError : u32;
|
|
|
|
void SPUDisAsm::IOHL(spu_opcode_t op)
|
|
{
|
|
DisAsm("iohl", spu_reg_name[op.rt], op.i16);
|
|
|
|
const auto [is_const, value] = try_get_const_equal_value_array<u32>(op.rt);
|
|
|
|
// Only print constant for a 4 equal 32-bit constants array
|
|
if (is_const)
|
|
{
|
|
comment_constant(last_opcode, value | op.i16);
|
|
}
|
|
}
|
|
|
|
void SPUDisAsm::SHUFB(spu_opcode_t op)
|
|
{
|
|
DisAsm("shufb", spu_reg_name[op.rt4], spu_reg_name[op.ra], spu_reg_name[op.rb], spu_reg_name[op.rc]);
|
|
|
|
const auto [is_const, value] = try_get_const_value(op.rc);
|
|
|
|
if (is_const)
|
|
{
|
|
const auto [size, dst, src] = try_get_insert_mask_info(value);
|
|
|
|
if (size)
|
|
{
|
|
if ((size >= 4u && !src) || (size == 2u && src == 1u) || (size == 1u && src == 3u))
|
|
{
|
|
// Comment insertion pattern for CWD-alike instruction
|
|
fmt::append(last_opcode, " #i%u[%u]", size * 8, dst);
|
|
return;
|
|
}
|
|
|
|
// Comment insertion pattern for unknown instruction formations
|
|
fmt::append(last_opcode, " #i%u[%u] = [%u]", size * 8, dst, src);
|
|
return;
|
|
}
|
|
}
|
|
}
|