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;
@ -961,6 +961,15 @@ void spu_recompiler::branch_fixed(u32 target)
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->jz(local->second);
c->lea(addr->r64(), get_pc(target));
@ -969,14 +978,30 @@ void spu_recompiler::branch_fixed(u32 target)
c->mov(*arg0, *cpu);
c->call(imm_ptr(&check_state));
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();
c->lea(addr->r64(), get_pc(target));
c->and_(*addr, 0x3fffc);
c->mov(SPU_OFF_32(pc), *addr);
if (absolute)
{
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->cmp(SPU_OFF_32(state), 0);
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);
if (target != m_pos + 4)
{
branch_fixed(target);
m_pos = -1;
}
branch_fixed(target, true);
m_pos = -1;
}
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->movdqa(SPU_OFF_128(gpr, op.rt), vr);
if (target != m_pos + 4)
{
branch_set_link(m_pos + 4);
branch_fixed(target);
m_pos = -1;
}
branch_set_link(m_pos + 4);
branch_fixed(target, true);
m_pos = -1;
}
void spu_recompiler::BR(spu_opcode_t op)

View file

@ -90,7 +90,7 @@ private:
asmjit::X86Mem XmmConst(__m128i data);
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_set_link(u32 target);
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);
if (target == pos + 4)
{
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);
}
LOG_WARNING(SPU, "[0x%x] At 0x%x: indirect branch to 0x%x%s", result[0], pos, target, op.d ? " (D)" : op.e ? " (E)" : "");
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
{
if (op.d || op.e)
{
m_entry_info[target / 4] = true;
}
m_entry_info[target / 4] = true;
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;
values[op.rt] = pos + 4;
if (target == pos + 4)
if (type == spu_itype::BRSL && target == pos + 4)
{
// Get next instruction address idiom
break;
@ -1616,14 +1605,39 @@ const std::vector<u32>& spu_recompiler_base::analyse(const be_t<u32>* ls, u32 en
break;
}
case spu_itype::BR:
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::BRNZ:
case spu_itype::BRHZ:
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)
{
@ -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);
add_block(target);
if (type != spu_itype::BR && type != spu_itype::BRA)
if (type != spu_itype::BR)
{
m_targets[pos].push_back(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:
is_call = spu_branch_target(0, op.i16) != ia + 4;
break;
case spu_itype::BRA:
is_call = true;
is_tail = true;
break;
case spu_itype::BISL:
case spu_itype::BISLED:
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)
{
case spu_itype::BR:
case spu_itype::BRA:
case spu_itype::BRNZ:
case spu_itype::BRZ:
case spu_itype::BRHNZ:
case spu_itype::BRHZ:
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)
{
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
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;
}
case spu_itype::BRA:
case spu_itype::BRASL:
{
bb.terminator = term_type::indirect_call;
break;
}
case spu_itype::BI:
{
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
llvm::BasicBlock* add_block(u32 target)
llvm::BasicBlock* add_block(u32 target, bool absolute = false)
{
// Check the predecessor
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);
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)
{
// 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 result = llvm::BasicBlock::Create(m_context, "", m_function);
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);
m_ir->SetInsertPoint(cblock);
return result;
}
verify(HERE), !absolute;
auto& result = m_blocks[target].block;
if (!result)
@ -7639,34 +7687,11 @@ public:
return result;
}
// Convert an indirect branch into a static one if possible
if (const auto _int = llvm::dyn_cast<llvm::ConstantInt>(addr.value); _int && op.opcode)
if (llvm::isa<llvm::Constant>(addr.value))
{
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)
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)
{
@ -8011,33 +8036,13 @@ public:
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));
}
m_block->block_end = m_ir->GetInsertBlock();
m_ir->CreateBr(add_block(target, true));
}
void BRASL(spu_opcode_t 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);
}