SPU LLVM/ASMJIT: fix BRA/BRASL instructions for PIC

Handle absolute branch addressing correctly.
This commit is contained in:
Nekotekina 2019-05-16 02:41:31 +03:00
parent f95ec8a37c
commit 91897fa69d
3 changed files with 110 additions and 86 deletions

View file

@ -952,7 +952,7 @@ static void check_state(spu_thread* _spu)
} }
} }
void spu_recompiler::branch_fixed(u32 target) void spu_recompiler::branch_fixed(u32 target, bool absolute)
{ {
using namespace asmjit; using namespace asmjit;
@ -961,6 +961,15 @@ void spu_recompiler::branch_fixed(u32 target)
if (local != instr_labels.end() && local->second.isValid()) if (local != instr_labels.end() && local->second.isValid())
{ {
Label fail;
if (absolute)
{
fail = c->newLabel();
c->cmp(pc0->r32(), m_base);
c->jne(fail);
}
c->cmp(SPU_OFF_32(state), 0); c->cmp(SPU_OFF_32(state), 0);
c->jz(local->second); c->jz(local->second);
c->lea(addr->r64(), get_pc(target)); c->lea(addr->r64(), get_pc(target));
@ -969,14 +978,30 @@ void spu_recompiler::branch_fixed(u32 target)
c->mov(*arg0, *cpu); c->mov(*arg0, *cpu);
c->call(imm_ptr(&check_state)); c->call(imm_ptr(&check_state));
c->jmp(local->second); c->jmp(local->second);
return;
if (absolute)
{
c->bind(fail);
}
else
{
return;
}
} }
const auto ppptr = !g_cfg.core.spu_verification ? nullptr : m_spurt->make_branch_patchpoint(); const auto ppptr = !g_cfg.core.spu_verification ? nullptr : m_spurt->make_branch_patchpoint();
c->lea(addr->r64(), get_pc(target)); if (absolute)
c->and_(*addr, 0x3fffc); {
c->mov(SPU_OFF_32(pc), *addr); c->mov(SPU_OFF_32(pc), target);
}
else
{
c->lea(addr->r64(), get_pc(target));
c->and_(*addr, 0x3fffc);
c->mov(SPU_OFF_32(pc), *addr);
}
c->xor_(rip->r32(), rip->r32()); c->xor_(rip->r32(), rip->r32());
c->cmp(SPU_OFF_32(state), 0); c->cmp(SPU_OFF_32(state), 0);
c->jnz(label_stop); c->jnz(label_stop);
@ -4132,11 +4157,8 @@ void spu_recompiler::BRA(spu_opcode_t op)
{ {
const u32 target = spu_branch_target(0, op.i16); const u32 target = spu_branch_target(0, op.i16);
if (target != m_pos + 4) branch_fixed(target, true);
{ m_pos = -1;
branch_fixed(target);
m_pos = -1;
}
} }
void spu_recompiler::LQA(spu_opcode_t op) void spu_recompiler::LQA(spu_opcode_t op)
@ -4170,12 +4192,9 @@ void spu_recompiler::BRASL(spu_opcode_t op)
c->pslldq(vr, 12); c->pslldq(vr, 12);
c->movdqa(SPU_OFF_128(gpr, op.rt), vr); c->movdqa(SPU_OFF_128(gpr, op.rt), vr);
if (target != m_pos + 4) branch_set_link(m_pos + 4);
{ branch_fixed(target, true);
branch_set_link(m_pos + 4); m_pos = -1;
branch_fixed(target);
m_pos = -1;
}
} }
void spu_recompiler::BR(spu_opcode_t op) void spu_recompiler::BR(spu_opcode_t op)

View file

@ -90,7 +90,7 @@ private:
asmjit::X86Mem XmmConst(__m128i data); asmjit::X86Mem XmmConst(__m128i data);
asmjit::X86Mem get_pc(u32 addr); asmjit::X86Mem get_pc(u32 addr);
void branch_fixed(u32 target); void branch_fixed(u32 target, bool absolute = false);
void branch_indirect(spu_opcode_t op, bool jt = false, bool ret = true); void branch_indirect(spu_opcode_t op, bool jt = false, bool ret = true);
void branch_set_link(u32 target); void branch_set_link(u32 target);
void fall(spu_opcode_t op); void fall(spu_opcode_t op);

View file

@ -1344,14 +1344,7 @@ const std::vector<u32>& spu_recompiler_base::analyse(const be_t<u32>* ls, u32 en
{ {
const u32 target = spu_branch_target(av); const u32 target = spu_branch_target(av);
if (target == pos + 4) LOG_WARNING(SPU, "[0x%x] At 0x%x: indirect branch to 0x%x%s", result[0], pos, target, op.d ? " (D)" : op.e ? " (E)" : "");
{
LOG_WARNING(SPU, "[0x%x] At 0x%x: indirect branch to next%s", result[0], pos, op.d ? " (D)" : op.e ? " (E)" : "");
}
else
{
LOG_WARNING(SPU, "[0x%x] At 0x%x: indirect branch to 0x%x", result[0], pos, target);
}
m_targets[pos].push_back(target); m_targets[pos].push_back(target);
@ -1368,11 +1361,7 @@ const std::vector<u32>& spu_recompiler_base::analyse(const be_t<u32>* ls, u32 en
} }
else else
{ {
if (op.d || op.e) m_entry_info[target / 4] = true;
{
m_entry_info[target / 4] = true;
}
add_block(target); add_block(target);
} }
} }
@ -1578,7 +1567,7 @@ const std::vector<u32>& spu_recompiler_base::analyse(const be_t<u32>* ls, u32 en
vflags[op.rt] = +vf::is_const; vflags[op.rt] = +vf::is_const;
values[op.rt] = pos + 4; values[op.rt] = pos + 4;
if (target == pos + 4) if (type == spu_itype::BRSL && target == pos + 4)
{ {
// Get next instruction address idiom // Get next instruction address idiom
break; break;
@ -1616,14 +1605,39 @@ const std::vector<u32>& spu_recompiler_base::analyse(const be_t<u32>* ls, u32 en
break; break;
} }
case spu_itype::BR:
case spu_itype::BRA: case spu_itype::BRA:
{
const u32 target = spu_branch_target(0, op.i16);
if (g_cfg.core.spu_block_size == spu_block_size_type::giga && !sync)
{
m_entry_info[target / 4] = true;
add_block(target);
}
else
{
if (g_cfg.core.spu_block_size == spu_block_size_type::giga)
{
LOG_NOTICE(SPU, "[0x%x] At 0x%x: ignoring fixed tail call to 0x%x (SYNC)", result[0], pos, target);
}
if (target > entry_point)
{
limit = std::min<u32>(limit, target);
}
}
next_block();
break;
}
case spu_itype::BR:
case spu_itype::BRZ: case spu_itype::BRZ:
case spu_itype::BRNZ: case spu_itype::BRNZ:
case spu_itype::BRHZ: case spu_itype::BRHZ:
case spu_itype::BRHNZ: case spu_itype::BRHNZ:
{ {
const u32 target = spu_branch_target(type == spu_itype::BRA ? 0 : pos, op.i16); const u32 target = spu_branch_target(pos, op.i16);
if (target == pos + 4) if (target == pos + 4)
{ {
@ -1634,7 +1648,7 @@ const std::vector<u32>& spu_recompiler_base::analyse(const be_t<u32>* ls, u32 en
m_targets[pos].push_back(target); m_targets[pos].push_back(target);
add_block(target); add_block(target);
if (type != spu_itype::BR && type != spu_itype::BRA) if (type != spu_itype::BR)
{ {
m_targets[pos].push_back(pos + 4); m_targets[pos].push_back(pos + 4);
add_block(pos + 4); add_block(pos + 4);
@ -2193,6 +2207,10 @@ const std::vector<u32>& spu_recompiler_base::analyse(const be_t<u32>* ls, u32 en
case spu_itype::BRASL: case spu_itype::BRASL:
is_call = spu_branch_target(0, op.i16) != ia + 4; is_call = spu_branch_target(0, op.i16) != ia + 4;
break; break;
case spu_itype::BRA:
is_call = true;
is_tail = true;
break;
case spu_itype::BISL: case spu_itype::BISL:
case spu_itype::BISLED: case spu_itype::BISLED:
is_call = true; is_call = true;
@ -2779,21 +2797,19 @@ const std::vector<u32>& spu_recompiler_base::analyse(const be_t<u32>* ls, u32 en
switch (last_inst) switch (last_inst)
{ {
case spu_itype::BR: case spu_itype::BR:
case spu_itype::BRA:
case spu_itype::BRNZ: case spu_itype::BRNZ:
case spu_itype::BRZ: case spu_itype::BRZ:
case spu_itype::BRHNZ: case spu_itype::BRHNZ:
case spu_itype::BRHZ: case spu_itype::BRHZ:
case spu_itype::BRSL: case spu_itype::BRSL:
case spu_itype::BRASL:
{ {
const u32 target = spu_branch_target(last_inst == spu_itype::BRA || last_inst == spu_itype::BRASL ? 0 : tia, op.i16); const u32 target = spu_branch_target(tia, op.i16);
if (target == tia + 4) if (target == tia + 4)
{ {
bb.terminator = term_type::fallthrough; bb.terminator = term_type::fallthrough;
} }
else if (last_inst != spu_itype::BRSL && last_inst != spu_itype::BRASL) else if (last_inst != spu_itype::BRSL)
{ {
// No-op terminator or simple branch instruction // No-op terminator or simple branch instruction
bb.terminator = term_type::br; bb.terminator = term_type::br;
@ -2815,6 +2831,12 @@ const std::vector<u32>& spu_recompiler_base::analyse(const be_t<u32>* ls, u32 en
break; break;
} }
case spu_itype::BRA:
case spu_itype::BRASL:
{
bb.terminator = term_type::indirect_call;
break;
}
case spu_itype::BI: case spu_itype::BI:
{ {
if (op.d || op.e || bb.targets.size() == 1) if (op.d || op.e || bb.targets.size() == 1)
@ -3449,7 +3471,7 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator
} }
// Add block with current block as a predecessor // Add block with current block as a predecessor
llvm::BasicBlock* add_block(u32 target) llvm::BasicBlock* add_block(u32 target, bool absolute = false)
{ {
// Check the predecessor // Check the predecessor
const bool pred_found = m_block_info[target / 4] && m_preds[target].find_first_of(m_pos) + 1; const bool pred_found = m_block_info[target / 4] && m_preds[target].find_first_of(m_pos) + 1;
@ -3497,6 +3519,19 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator
m_ir->SetInsertPoint(result); m_ir->SetInsertPoint(result);
const auto pfinfo = add_function(target); const auto pfinfo = add_function(target);
if (absolute)
{
verify(HERE), !m_finfo->fn;
const auto next = llvm::BasicBlock::Create(m_context, "", m_function);
const auto fail = llvm::BasicBlock::Create(m_context, "", m_function);
m_ir->CreateCondBr(m_ir->CreateICmpEQ(m_base_pc, m_ir->getInt32(m_base)), next, fail);
m_ir->SetInsertPoint(fail);
m_ir->CreateStore(m_ir->getInt32(target), spu_ptr<u32>(&spu_thread::pc), true);
tail_chunk(nullptr);
m_ir->SetInsertPoint(next);
}
if (pfinfo->fn) if (pfinfo->fn)
{ {
// Tail call to the real function // Tail call to the real function
@ -3525,12 +3560,25 @@ class spu_llvm_recompiler : public spu_recompiler_base, public cpu_translator
const auto cblock = m_ir->GetInsertBlock(); const auto cblock = m_ir->GetInsertBlock();
const auto result = llvm::BasicBlock::Create(m_context, "", m_function); const auto result = llvm::BasicBlock::Create(m_context, "", m_function);
m_ir->SetInsertPoint(result); m_ir->SetInsertPoint(result);
update_pc(target);
if (absolute)
{
verify(HERE), !m_finfo->fn;
m_ir->CreateStore(m_ir->getInt32(target), spu_ptr<u32>(&spu_thread::pc), true);
}
else
{
update_pc(target);
}
tail_chunk(nullptr); tail_chunk(nullptr);
m_ir->SetInsertPoint(cblock); m_ir->SetInsertPoint(cblock);
return result; return result;
} }
verify(HERE), !absolute;
auto& result = m_blocks[target].block; auto& result = m_blocks[target].block;
if (!result) if (!result)
@ -7639,34 +7687,11 @@ public:
return result; return result;
} }
// Convert an indirect branch into a static one if possible if (llvm::isa<llvm::Constant>(addr.value))
if (const auto _int = llvm::dyn_cast<llvm::ConstantInt>(addr.value); _int && op.opcode)
{ {
const u32 target = ::narrow<u32>(_int->getZExtValue(), HERE);
LOG_WARNING(SPU, "[0x%x] Fixed branch to 0x%x", m_pos, target);
if (!op.e && !op.d)
{
return add_block(target);
}
if (!m_entry_info[target / 4])
{
LOG_ERROR(SPU, "[0x%x] Fixed branch to 0x%x", m_pos, target);
}
else
{
add_function(target);
}
// Fixed branch excludes the possibility it's a function return (TODO) // Fixed branch excludes the possibility it's a function return (TODO)
ret = false; ret = false;
} }
else if (llvm::isa<llvm::Constant>(addr.value) && op.opcode)
{
LOG_ERROR(SPU, "[0x%x] Unexpected constant (add_block_indirect)", m_pos);
}
if (m_finfo && m_finfo->fn && op.opcode) if (m_finfo && m_finfo->fn && op.opcode)
{ {
@ -8011,33 +8036,13 @@ public:
const u32 target = spu_branch_target(0, op.i16); const u32 target = spu_branch_target(0, op.i16);
if (target != m_pos + 4) m_block->block_end = m_ir->GetInsertBlock();
{ m_ir->CreateBr(add_block(target, true));
m_block->block_end = m_ir->GetInsertBlock();
m_ir->CreateBr(add_block(target));
}
} }
void BRASL(spu_opcode_t op) // void BRASL(spu_opcode_t op) //
{ {
set_link(op); set_link(op);
const u32 target = spu_branch_target(0, op.i16);
if (m_finfo && m_finfo->fn && target != m_pos + 4)
{
if (auto fn = add_function(target)->fn)
{
call_function(fn);
return;
}
else
{
LOG_FATAL(SPU, "[0x%x] Can't add function 0x%x", m_pos, target);
return;
}
}
BRA(op); BRA(op);
} }