2015-08-26 04:54:06 +02:00
|
|
|
#include "stdafx.h"
|
2018-04-09 16:45:37 +02:00
|
|
|
#include "Emu/Memory/vm.h"
|
2015-08-26 04:54:06 +02:00
|
|
|
#include "SPUAnalyser.h"
|
2016-06-05 12:14:20 +02:00
|
|
|
#include "SPURecompiler.h"
|
|
|
|
|
#include "SPUOpcodes.h"
|
2015-08-26 04:54:06 +02:00
|
|
|
|
2016-06-05 12:14:20 +02:00
|
|
|
const spu_decoder<spu_itype> s_spu_itype;
|
2015-08-26 04:54:06 +02:00
|
|
|
|
2018-04-09 16:45:37 +02:00
|
|
|
std::shared_ptr<spu_function> spu_analyse(const be_t<u32>* ls, u32 entry, u32 max_limit)
|
2015-08-26 04:54:06 +02:00
|
|
|
{
|
|
|
|
|
// Check arguments (bounds and alignment)
|
|
|
|
|
if (max_limit > 0x40000 || entry >= max_limit || entry % 4 || max_limit % 4)
|
|
|
|
|
{
|
2016-08-08 18:01:06 +02:00
|
|
|
fmt::throw_exception("Invalid arguments (entry=0x%05x, limit=0x%05x)" HERE, entry, max_limit);
|
2015-08-26 04:54:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Key for multimap
|
|
|
|
|
const u64 key = entry | u64{ ls[entry / 4] } << 32;
|
2017-07-10 23:36:54 +02:00
|
|
|
const be_t<u32>* base = ls + entry / 4;
|
|
|
|
|
const u32 block_sz = max_limit - entry;
|
2015-08-26 04:54:06 +02:00
|
|
|
|
2015-09-18 00:41:14 +02:00
|
|
|
{
|
2018-04-09 16:45:37 +02:00
|
|
|
//reader_lock lock(m_mutex);
|
2015-09-18 00:41:14 +02:00
|
|
|
|
|
|
|
|
// Try to find existing function in the database
|
2018-04-09 16:45:37 +02:00
|
|
|
// if (auto func = find(base, key, block_sz))
|
|
|
|
|
// {
|
|
|
|
|
// return func;
|
|
|
|
|
// }
|
2015-09-18 00:41:14 +02:00
|
|
|
}
|
|
|
|
|
|
2015-08-26 04:54:06 +02:00
|
|
|
{
|
2018-04-09 16:45:37 +02:00
|
|
|
//writer_lock lock(m_mutex);
|
2017-07-10 23:36:54 +02:00
|
|
|
|
|
|
|
|
// Double-check
|
2018-04-09 16:45:37 +02:00
|
|
|
// if (auto func = find(base, key, block_sz))
|
|
|
|
|
// {
|
|
|
|
|
// return func;
|
|
|
|
|
// }
|
2015-08-26 04:54:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize block entries with the function entry point
|
|
|
|
|
std::set<u32> blocks{ entry };
|
|
|
|
|
|
|
|
|
|
// Entries of adjacent functions; jump table entries
|
|
|
|
|
std::set<u32> adjacent, jt;
|
|
|
|
|
|
|
|
|
|
// Set initial limit which will be narrowed later
|
|
|
|
|
u32 limit = max_limit;
|
|
|
|
|
|
2015-09-04 01:23:31 +02:00
|
|
|
// Minimal position of ila $SP,* instruction
|
|
|
|
|
u32 ila_sp_pos = max_limit;
|
|
|
|
|
|
2017-12-01 21:11:06 +01:00
|
|
|
// pigeonhole optimization, addr of last ila r2, addr, or 0 if last instruction was not
|
|
|
|
|
u32 ila_r2_addr = 0;
|
2017-12-01 03:50:01 +01:00
|
|
|
|
2015-08-26 04:54:06 +02:00
|
|
|
// Find preliminary set of possible block entries (first pass), `start` is the current block address
|
|
|
|
|
for (u32 start = entry, pos = entry; pos < limit; pos += 4)
|
|
|
|
|
{
|
|
|
|
|
const spu_opcode_t op{ ls[pos / 4] };
|
|
|
|
|
|
2016-04-14 01:09:41 +02:00
|
|
|
const auto type = s_spu_itype.decode(op.opcode);
|
2015-08-26 04:54:06 +02:00
|
|
|
|
2015-09-04 01:23:31 +02:00
|
|
|
{
|
2018-04-09 16:45:37 +02:00
|
|
|
//reader_lock lock(m_mutex);
|
2017-07-10 23:36:54 +02:00
|
|
|
|
|
|
|
|
// Find existing function
|
2018-04-09 16:45:37 +02:00
|
|
|
// if (pos != entry && find(ls + pos / 4, pos | u64{ op.opcode } << 32, limit - pos))
|
|
|
|
|
// {
|
|
|
|
|
// limit = pos;
|
|
|
|
|
// break;
|
|
|
|
|
// }
|
2015-09-04 01:23:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Additional analysis at the beginning of the block
|
|
|
|
|
if (start != entry && start == pos)
|
2015-08-26 04:54:06 +02:00
|
|
|
{
|
2016-06-05 12:14:20 +02:00
|
|
|
std::vector<u32> jt_abs;
|
|
|
|
|
std::vector<u32> jt_rel;
|
2015-08-26 04:54:06 +02:00
|
|
|
|
|
|
|
|
for (; pos < limit; pos += 4)
|
|
|
|
|
{
|
|
|
|
|
const u32 target = ls[pos / 4];
|
|
|
|
|
|
|
|
|
|
if (target % 4)
|
|
|
|
|
{
|
2016-06-05 12:14:20 +02:00
|
|
|
// Address cannot be misaligned: abort jt scan
|
2015-08-26 04:54:06 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (target >= entry && target < limit)
|
|
|
|
|
{
|
|
|
|
|
// Possible jump table entry (absolute)
|
|
|
|
|
jt_abs.emplace_back(target);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (target + start >= entry && target + start < limit)
|
|
|
|
|
{
|
|
|
|
|
// Possible jump table entry (relative)
|
|
|
|
|
jt_rel.emplace_back(target + start);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (std::max(jt_abs.size(), jt_rel.size()) * 4 + start <= pos)
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pos - start >= 8)
|
|
|
|
|
{
|
|
|
|
|
// Register jump table block (at least 2 entries) as an ordinary block
|
|
|
|
|
blocks.emplace(start);
|
|
|
|
|
|
|
|
|
|
// Register jump table entries (absolute)
|
|
|
|
|
if (jt_abs.size() * 4 == pos - start)
|
|
|
|
|
{
|
|
|
|
|
blocks.insert(jt_abs.begin(), jt_abs.end());
|
|
|
|
|
|
|
|
|
|
jt.insert(jt_abs.begin(), jt_abs.end());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Register jump table entries (relative)
|
|
|
|
|
if (jt_rel.size() * 4 == pos - start)
|
|
|
|
|
{
|
|
|
|
|
blocks.insert(jt_rel.begin(), jt_rel.end());
|
|
|
|
|
|
|
|
|
|
jt.insert(jt_rel.begin(), jt_rel.end());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fix pos value
|
|
|
|
|
start = pos; pos = pos - 4;
|
2018-04-09 16:45:37 +02:00
|
|
|
|
2015-08-26 04:54:06 +02:00
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Restore pos value
|
|
|
|
|
pos = start;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!type || (start == pos && start > *blocks.rbegin())) // Invalid instruction or "unrelated" block started
|
|
|
|
|
{
|
|
|
|
|
// Discard current block and abort the operation
|
|
|
|
|
limit = start;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2015-09-04 01:23:31 +02:00
|
|
|
|
|
|
|
|
if (op.opcode == 0) // Hack: special case (STOP 0)
|
2015-08-26 04:54:06 +02:00
|
|
|
{
|
|
|
|
|
limit = pos + 4;
|
|
|
|
|
break;
|
|
|
|
|
}
|
2017-12-01 03:50:01 +01:00
|
|
|
|
2017-12-01 21:11:06 +01:00
|
|
|
// if upcoming instruction is not BI, reset the pigeonhole optimization
|
|
|
|
|
// todo: can constant propogation somewhere get rid of this check?
|
2018-04-09 16:45:37 +02:00
|
|
|
if ((type != spu_itype::BI))
|
2017-12-01 21:11:06 +01:00
|
|
|
ila_r2_addr = 0; // reset
|
2018-04-09 16:45:37 +02:00
|
|
|
|
|
|
|
|
if (type == spu_itype::BI || type == spu_itype::IRET) // Branch Indirect
|
2015-08-26 04:54:06 +02:00
|
|
|
{
|
2016-06-05 12:14:20 +02:00
|
|
|
blocks.emplace(start);
|
|
|
|
|
start = pos + 4;
|
2017-12-01 03:50:01 +01:00
|
|
|
|
2017-12-01 21:11:06 +01:00
|
|
|
if (op.ra == 2 && ila_r2_addr > entry)
|
|
|
|
|
blocks.emplace(ila_r2_addr);
|
2015-08-26 04:54:06 +02:00
|
|
|
}
|
2018-04-09 16:45:37 +02:00
|
|
|
else if (type == spu_itype::BR || type == spu_itype::BRA) // Branch Relative/Absolute
|
2015-08-26 04:54:06 +02:00
|
|
|
{
|
2018-04-09 16:45:37 +02:00
|
|
|
const u32 target = spu_branch_target(type == spu_itype::BR ? pos : 0, op.i16);
|
2015-08-26 04:54:06 +02:00
|
|
|
|
|
|
|
|
// Add adjacent function because it always could be
|
|
|
|
|
adjacent.emplace(target);
|
|
|
|
|
|
|
|
|
|
if (target > entry)
|
|
|
|
|
{
|
|
|
|
|
blocks.emplace(target);
|
|
|
|
|
}
|
|
|
|
|
|
2016-06-05 12:14:20 +02:00
|
|
|
blocks.emplace(start);
|
|
|
|
|
start = pos + 4;
|
2015-08-26 04:54:06 +02:00
|
|
|
}
|
2018-04-09 16:45:37 +02:00
|
|
|
else if (type == spu_itype::BRSL || type == spu_itype::BRASL) // Branch Relative/Absolute and Set Link
|
2015-08-26 04:54:06 +02:00
|
|
|
{
|
2018-04-09 16:45:37 +02:00
|
|
|
const u32 target = spu_branch_target(type == spu_itype::BRSL ? pos : 0, op.i16);
|
2015-08-26 04:54:06 +02:00
|
|
|
|
|
|
|
|
if (target == pos + 4)
|
|
|
|
|
{
|
|
|
|
|
// Branch to the next instruction and set link ("get next instruction address" idiom)
|
|
|
|
|
|
2015-09-04 01:23:31 +02:00
|
|
|
if (op.rt == 0) LOG_ERROR(SPU, "[0x%05x] Branch-to-next with $LR", pos);
|
2015-08-26 04:54:06 +02:00
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Add adjacent function
|
|
|
|
|
adjacent.emplace(target);
|
|
|
|
|
|
|
|
|
|
if (target > entry)
|
|
|
|
|
{
|
|
|
|
|
limit = std::min<u32>(limit, target);
|
|
|
|
|
}
|
2015-09-04 01:23:31 +02:00
|
|
|
|
|
|
|
|
if (op.rt != 0) LOG_ERROR(SPU, "[0x%05x] Function call without $LR", pos);
|
2015-08-26 04:54:06 +02:00
|
|
|
}
|
|
|
|
|
}
|
2018-04-09 16:45:37 +02:00
|
|
|
else if (type == spu_itype::BISL || type == spu_itype::BISLED) // Branch Indirect and Set Link
|
2015-08-26 04:54:06 +02:00
|
|
|
{
|
2015-09-04 01:23:31 +02:00
|
|
|
if (op.rt != 0) LOG_ERROR(SPU, "[0x%05x] Indirect function call without $LR", pos);
|
2015-08-26 04:54:06 +02:00
|
|
|
}
|
2018-04-09 16:45:37 +02:00
|
|
|
else if (type == spu_itype::BRNZ || type == spu_itype::BRZ || type == spu_itype::BRHNZ || type == spu_itype::BRHZ) // Branch Relative if (Not) Zero (Half)word
|
2015-08-26 04:54:06 +02:00
|
|
|
{
|
|
|
|
|
const u32 target = spu_branch_target(pos, op.i16);
|
|
|
|
|
|
|
|
|
|
// Add adjacent function because it always could be
|
|
|
|
|
adjacent.emplace(target);
|
|
|
|
|
|
|
|
|
|
if (target > entry)
|
|
|
|
|
{
|
|
|
|
|
blocks.emplace(target);
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-04-09 16:45:37 +02:00
|
|
|
else if (type == spu_itype::LNOP || type == spu_itype::NOP) {
|
2017-12-01 21:11:06 +01:00
|
|
|
// theres a chance that theres some random lnops/nops after the end of a function
|
|
|
|
|
// havent found a definite pattern, but, is an easy optimization to check for, just push start down if lnop is tagged as a start
|
|
|
|
|
// todo: remove the last added start pos as its probly unnecessary
|
|
|
|
|
if (pos == start)
|
|
|
|
|
start = pos + 4;
|
|
|
|
|
}
|
2015-09-04 01:23:31 +02:00
|
|
|
else // Other instructions (writing rt reg)
|
|
|
|
|
{
|
2016-06-05 12:14:20 +02:00
|
|
|
const u32 rt = type & spu_itype::_quadrop ? +op.rt4 : +op.rt;
|
2015-09-04 01:23:31 +02:00
|
|
|
|
|
|
|
|
// Analyse link register access
|
|
|
|
|
if (rt == 0)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
// Analyse stack pointer access
|
2017-12-01 03:50:01 +01:00
|
|
|
else if (rt == 1)
|
2015-09-04 01:23:31 +02:00
|
|
|
{
|
2018-04-09 16:45:37 +02:00
|
|
|
if (type == spu_itype::ILA && pos < ila_sp_pos)
|
2015-09-04 01:23:31 +02:00
|
|
|
{
|
|
|
|
|
// set minimal ila $SP,* instruction position
|
|
|
|
|
ila_sp_pos = pos;
|
|
|
|
|
}
|
|
|
|
|
}
|
2017-12-01 21:11:06 +01:00
|
|
|
// pigeonhole optimize
|
|
|
|
|
// ila r2, addr
|
|
|
|
|
// bi r2
|
|
|
|
|
else if (rt == 2) {
|
2018-04-09 16:45:37 +02:00
|
|
|
if (type == spu_itype::ILA)
|
2017-12-01 21:11:06 +01:00
|
|
|
ila_r2_addr = spu_branch_target(op.i18);
|
|
|
|
|
}
|
2015-09-04 01:23:31 +02:00
|
|
|
}
|
2015-08-26 04:54:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find more function calls (second pass, questionable)
|
|
|
|
|
for (u32 pos = 0; pos < 0x40000; pos += 4)
|
|
|
|
|
{
|
|
|
|
|
const spu_opcode_t op{ ls[pos / 4] };
|
|
|
|
|
|
2016-04-14 01:09:41 +02:00
|
|
|
const auto type = s_spu_itype.decode(op.opcode);
|
2015-08-26 04:54:06 +02:00
|
|
|
|
2018-04-09 16:45:37 +02:00
|
|
|
if (type == spu_itype::BRSL || type == spu_itype::BRASL) // Branch Relative/Absolute and Set Link
|
2015-08-26 04:54:06 +02:00
|
|
|
{
|
2018-04-09 16:45:37 +02:00
|
|
|
const u32 target = spu_branch_target(type == spu_itype::BRSL ? pos : 0, op.i16);
|
2015-08-26 04:54:06 +02:00
|
|
|
|
|
|
|
|
if (target != pos + 4 && target > entry && limit > target)
|
|
|
|
|
{
|
|
|
|
|
// Narrow the limit
|
|
|
|
|
limit = target;
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-06-05 12:14:20 +02:00
|
|
|
else if (!type) // Invalid instruction
|
|
|
|
|
{
|
|
|
|
|
break;
|
|
|
|
|
}
|
2015-08-26 04:54:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (limit <= entry)
|
|
|
|
|
{
|
|
|
|
|
LOG_ERROR(SPU, "Function not found [0x%05x]", entry);
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Prepare new function (set addr and size)
|
2018-04-09 16:45:37 +02:00
|
|
|
auto func = std::make_shared<spu_function>(entry, limit - entry);
|
2015-08-26 04:54:06 +02:00
|
|
|
|
2015-09-04 01:23:31 +02:00
|
|
|
// Copy function contents
|
|
|
|
|
func->data = { ls + entry / 4, ls + limit / 4 };
|
|
|
|
|
|
2015-08-26 04:54:06 +02:00
|
|
|
// Fill function block info
|
|
|
|
|
for (auto i = blocks.crbegin(); i != blocks.crend(); i++)
|
|
|
|
|
{
|
|
|
|
|
if (limit > *i)
|
|
|
|
|
{
|
|
|
|
|
func->blocks.emplace_hint(func->blocks.begin(), *i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fill adjacent function info
|
|
|
|
|
for (auto i = adjacent.crbegin(); i != adjacent.crend(); i++)
|
|
|
|
|
{
|
|
|
|
|
if (limit <= *i || entry >= *i)
|
|
|
|
|
{
|
|
|
|
|
func->adjacent.emplace_hint(func->adjacent.begin(), *i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fill jump table entries
|
|
|
|
|
for (auto i = jt.crbegin(); i != jt.crend(); i++)
|
|
|
|
|
{
|
|
|
|
|
if (limit > *i)
|
|
|
|
|
{
|
|
|
|
|
func->jtable.emplace_hint(func->jtable.begin(), *i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-09-04 01:23:31 +02:00
|
|
|
// Set whether the function can reset stack
|
|
|
|
|
func->does_reset_stack = ila_sp_pos < limit;
|
2015-08-26 04:54:06 +02:00
|
|
|
|
2017-07-10 23:36:54 +02:00
|
|
|
// Lock here just before we write to the db
|
|
|
|
|
// Its is unlikely that the second check will pass anyway so we delay this step since compiling functions is very fast
|
|
|
|
|
{
|
2018-04-09 16:45:37 +02:00
|
|
|
//writer_lock lock(m_mutex);
|
2017-07-10 23:36:54 +02:00
|
|
|
|
|
|
|
|
// Add function to the database
|
2018-04-09 16:45:37 +02:00
|
|
|
//m_db.emplace(key, func);
|
2017-07-10 23:36:54 +02:00
|
|
|
}
|
2015-08-26 04:54:06 +02:00
|
|
|
|
2017-07-20 16:20:28 +02:00
|
|
|
LOG_NOTICE(SPU, "Function detected [0x%05x-0x%05x] (size=0x%x)", func->addr, func->addr + func->size, func->size);
|
2015-08-26 04:54:06 +02:00
|
|
|
|
2018-04-09 16:45:37 +02:00
|
|
|
return func;
|
2015-08-26 04:54:06 +02:00
|
|
|
}
|