|
|
|
@ -21,23 +21,20 @@ bool CodeGenerator::CompileBlock(CodeBlock* block, CodeBlock::HostCodePointer* o
|
|
|
|
|
m_block = block;
|
|
|
|
|
m_block_start = block->instructions.data();
|
|
|
|
|
m_block_end = block->instructions.data() + block->instructions.size();
|
|
|
|
|
|
|
|
|
|
m_pc = block->GetPC();
|
|
|
|
|
m_pc_valid = true;
|
|
|
|
|
|
|
|
|
|
EmitBeginBlock();
|
|
|
|
|
m_fastmem_load_base_in_register = false;
|
|
|
|
|
m_fastmem_store_base_in_register = false;
|
|
|
|
|
|
|
|
|
|
EmitBeginBlock(true);
|
|
|
|
|
BlockPrologue();
|
|
|
|
|
|
|
|
|
|
const CodeBlockInstruction* cbi = m_block_start;
|
|
|
|
|
while (cbi != m_block_end)
|
|
|
|
|
m_current_instruction = m_block_start;
|
|
|
|
|
while (m_current_instruction != m_block_end)
|
|
|
|
|
{
|
|
|
|
|
#ifdef _DEBUG
|
|
|
|
|
SmallString disasm;
|
|
|
|
|
DisassembleInstruction(&disasm, cbi->pc, cbi->instruction.bits);
|
|
|
|
|
Log_DebugPrintf("Compiling instruction '%s'", disasm.GetCharArray());
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
m_current_instruction = cbi;
|
|
|
|
|
if (!CompileInstruction(*cbi))
|
|
|
|
|
if (!CompileInstruction(*m_current_instruction))
|
|
|
|
|
{
|
|
|
|
|
m_current_instruction = nullptr;
|
|
|
|
|
m_block_end = nullptr;
|
|
|
|
@ -46,11 +43,14 @@ bool CodeGenerator::CompileBlock(CodeBlock* block, CodeBlock::HostCodePointer* o
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cbi++;
|
|
|
|
|
m_current_instruction++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
BlockEpilogue();
|
|
|
|
|
EmitEndBlock();
|
|
|
|
|
if (!m_block_linked)
|
|
|
|
|
{
|
|
|
|
|
BlockEpilogue();
|
|
|
|
|
EmitEndBlock(true, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FinalizeBlock(out_host_code, out_host_code_size);
|
|
|
|
|
Log_ProfilePrintf("JIT block 0x%08X: %zu instructions (%u bytes), %u host bytes", block->GetPC(),
|
|
|
|
@ -957,6 +957,10 @@ void CodeGenerator::BlockPrologue()
|
|
|
|
|
|
|
|
|
|
EmitStoreCPUStructField(offsetof(State, exception_raised), Value::FromConstantU8(0));
|
|
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
|
EmitFunctionCall(nullptr, &Thunks::LogPC, Value::FromConstantU32(m_pc));
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
if (m_block->uncached_fetch_ticks > 0 || m_block->icache_line_count > 0)
|
|
|
|
|
EmitICacheCheckAndUpdate();
|
|
|
|
|
|
|
|
|
@ -2184,7 +2188,10 @@ bool CodeGenerator::Compile_Branch(const CodeBlockInstruction& cbi)
|
|
|
|
|
{
|
|
|
|
|
InstructionPrologue(cbi, 1);
|
|
|
|
|
|
|
|
|
|
auto DoBranch = [this](Condition condition, const Value& lhs, const Value& rhs, Reg lr_reg, Value&& branch_target) {
|
|
|
|
|
auto DoBranch = [this, &cbi](Condition condition, const Value& lhs, const Value& rhs, Reg lr_reg,
|
|
|
|
|
Value&& branch_target) {
|
|
|
|
|
const bool can_link_block = cbi.is_direct_branch_instruction && g_settings.cpu_recompiler_block_linking;
|
|
|
|
|
|
|
|
|
|
// ensure the lr register is flushed, since we want it's correct value after the branch
|
|
|
|
|
// we don't want to invalidate it yet because of "jalr r0, r0", branch_target could be the lr_reg.
|
|
|
|
|
if (lr_reg != Reg::count && lr_reg != Reg::zero)
|
|
|
|
@ -2199,16 +2206,58 @@ bool CodeGenerator::Compile_Branch(const CodeBlockInstruction& cbi)
|
|
|
|
|
EmitCopyValue(next_pc.GetHostRegister(), CalculatePC(4));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LabelType branch_not_taken;
|
|
|
|
|
Value take_branch;
|
|
|
|
|
LabelType branch_taken, branch_not_taken;
|
|
|
|
|
if (condition != Condition::Always)
|
|
|
|
|
{
|
|
|
|
|
// condition is inverted because we want the case for skipping it
|
|
|
|
|
if (lhs.IsValid() && rhs.IsValid())
|
|
|
|
|
EmitConditionalBranch(condition, true, lhs.host_reg, rhs, &branch_not_taken);
|
|
|
|
|
else if (lhs.IsValid())
|
|
|
|
|
EmitConditionalBranch(condition, true, lhs.host_reg, lhs.size, &branch_not_taken);
|
|
|
|
|
if (!can_link_block)
|
|
|
|
|
{
|
|
|
|
|
// condition is inverted because we want the case for skipping it
|
|
|
|
|
if (lhs.IsValid() && rhs.IsValid())
|
|
|
|
|
EmitConditionalBranch(condition, true, lhs.host_reg, rhs, &branch_not_taken);
|
|
|
|
|
else if (lhs.IsValid())
|
|
|
|
|
EmitConditionalBranch(condition, true, lhs.host_reg, lhs.size, &branch_not_taken);
|
|
|
|
|
else
|
|
|
|
|
EmitConditionalBranch(condition, true, &branch_not_taken);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
EmitConditionalBranch(condition, true, &branch_not_taken);
|
|
|
|
|
{
|
|
|
|
|
take_branch = m_register_cache.AllocateScratch(RegSize_32);
|
|
|
|
|
switch (condition)
|
|
|
|
|
{
|
|
|
|
|
case Condition::NotEqual:
|
|
|
|
|
case Condition::Equal:
|
|
|
|
|
case Condition::Overflow:
|
|
|
|
|
case Condition::Greater:
|
|
|
|
|
case Condition::GreaterEqual:
|
|
|
|
|
case Condition::LessEqual:
|
|
|
|
|
case Condition::Less:
|
|
|
|
|
case Condition::Above:
|
|
|
|
|
case Condition::AboveEqual:
|
|
|
|
|
case Condition::Below:
|
|
|
|
|
case Condition::BelowEqual:
|
|
|
|
|
{
|
|
|
|
|
EmitCmp(lhs.GetHostRegister(), rhs);
|
|
|
|
|
EmitSetConditionResult(take_branch.GetHostRegister(), take_branch.size, condition);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case Condition::Negative:
|
|
|
|
|
case Condition::PositiveOrZero:
|
|
|
|
|
case Condition::NotZero:
|
|
|
|
|
case Condition::Zero:
|
|
|
|
|
{
|
|
|
|
|
Assert(!rhs.IsValid() || (rhs.IsConstant() && rhs.GetS64ConstantValue() == 0));
|
|
|
|
|
EmitTest(lhs.GetHostRegister(), lhs);
|
|
|
|
|
EmitSetConditionResult(take_branch.GetHostRegister(), take_branch.size, condition);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
UnreachableCode();
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// save the old PC if we want to
|
|
|
|
@ -2218,6 +2267,9 @@ bool CodeGenerator::Compile_Branch(const CodeBlockInstruction& cbi)
|
|
|
|
|
// if we don't cancel it, at the end of the instruction the value we write can be overridden.
|
|
|
|
|
EmitCancelInterpreterLoadDelayForReg(lr_reg);
|
|
|
|
|
EmitStoreGuestRegister(lr_reg, next_pc);
|
|
|
|
|
|
|
|
|
|
// now invalidate lr because it was possibly written in the branch
|
|
|
|
|
m_register_cache.InvalidateGuestRegister(lr_reg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// we don't need to test the address of constant branches unless they're definitely misaligned, which would be
|
|
|
|
@ -2256,24 +2308,125 @@ bool CodeGenerator::Compile_Branch(const CodeBlockInstruction& cbi)
|
|
|
|
|
m_register_cache.PopState();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (condition != Condition::Always)
|
|
|
|
|
if (can_link_block)
|
|
|
|
|
{
|
|
|
|
|
// branch taken path - modify the next pc
|
|
|
|
|
EmitCopyValue(next_pc.GetHostRegister(), branch_target);
|
|
|
|
|
// if it's an in-block branch, compile the delay slot now
|
|
|
|
|
// TODO: Make this more optimal by moving the condition down if it's a nop
|
|
|
|
|
Assert((m_current_instruction + 1) != m_block_end);
|
|
|
|
|
InstructionEpilogue(cbi);
|
|
|
|
|
m_current_instruction++;
|
|
|
|
|
if (!CompileInstruction(*m_current_instruction))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// flush all regs since we're at the end of the block now
|
|
|
|
|
BlockEpilogue();
|
|
|
|
|
m_block_linked = true;
|
|
|
|
|
|
|
|
|
|
// check downcount
|
|
|
|
|
Value pending_ticks = m_register_cache.AllocateScratch(RegSize_32);
|
|
|
|
|
Value downcount = m_register_cache.AllocateScratch(RegSize_32);
|
|
|
|
|
EmitLoadCPUStructField(pending_ticks.GetHostRegister(), RegSize_32, offsetof(State, pending_ticks));
|
|
|
|
|
EmitLoadCPUStructField(downcount.GetHostRegister(), RegSize_32, offsetof(State, downcount));
|
|
|
|
|
|
|
|
|
|
// pending < downcount
|
|
|
|
|
LabelType return_to_dispatcher;
|
|
|
|
|
|
|
|
|
|
// converge point
|
|
|
|
|
EmitBindLabel(&branch_not_taken);
|
|
|
|
|
WriteNewPC(next_pc, true);
|
|
|
|
|
if (condition != Condition::Always)
|
|
|
|
|
{
|
|
|
|
|
EmitBranchIfBitClear(take_branch.GetHostRegister(), take_branch.size, 0, &branch_not_taken);
|
|
|
|
|
m_register_cache.PushState();
|
|
|
|
|
{
|
|
|
|
|
WriteNewPC(branch_target, false);
|
|
|
|
|
EmitConditionalBranch(Condition::GreaterEqual, false, pending_ticks.GetHostRegister(), downcount,
|
|
|
|
|
&return_to_dispatcher);
|
|
|
|
|
|
|
|
|
|
// we're committed at this point :D
|
|
|
|
|
EmitStoreCPUStructField(offsetof(State, current_instruction_pc), branch_target);
|
|
|
|
|
EmitEndBlock(true, false);
|
|
|
|
|
|
|
|
|
|
const void* jump_pointer = GetCurrentCodePointer();
|
|
|
|
|
const void* resolve_pointer = GetCurrentFarCodePointer();
|
|
|
|
|
EmitBranch(resolve_pointer);
|
|
|
|
|
const u32 jump_size = static_cast<u32>(static_cast<const char*>(GetCurrentCodePointer()) -
|
|
|
|
|
static_cast<const char*>(jump_pointer));
|
|
|
|
|
SwitchToFarCode();
|
|
|
|
|
|
|
|
|
|
EmitBeginBlock(true);
|
|
|
|
|
EmitFunctionCall(nullptr, &CPU::Recompiler::Thunks::ResolveBranch, Value::FromConstantPtr(m_block),
|
|
|
|
|
Value::FromConstantPtr(jump_pointer), Value::FromConstantPtr(resolve_pointer),
|
|
|
|
|
Value::FromConstantU32(jump_size));
|
|
|
|
|
EmitEndBlock(true, true);
|
|
|
|
|
}
|
|
|
|
|
m_register_cache.PopState();
|
|
|
|
|
|
|
|
|
|
SwitchToNearCode();
|
|
|
|
|
EmitBindLabel(&branch_not_taken);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_register_cache.PushState();
|
|
|
|
|
|
|
|
|
|
if (condition != Condition::Always)
|
|
|
|
|
{
|
|
|
|
|
WriteNewPC(next_pc, true);
|
|
|
|
|
EmitStoreCPUStructField(offsetof(State, current_instruction_pc), next_pc);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
WriteNewPC(branch_target, true);
|
|
|
|
|
EmitStoreCPUStructField(offsetof(State, current_instruction_pc), branch_target);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
EmitConditionalBranch(Condition::GreaterEqual, false, pending_ticks.GetHostRegister(), downcount,
|
|
|
|
|
&return_to_dispatcher);
|
|
|
|
|
|
|
|
|
|
if (condition != Condition::Always)
|
|
|
|
|
EmitStoreCPUStructField(offsetof(State, current_instruction_pc), next_pc);
|
|
|
|
|
else
|
|
|
|
|
EmitStoreCPUStructField(offsetof(State, current_instruction_pc), branch_target);
|
|
|
|
|
|
|
|
|
|
EmitEndBlock(true, false);
|
|
|
|
|
|
|
|
|
|
const void* jump_pointer = GetCurrentCodePointer();
|
|
|
|
|
const void* resolve_pointer = GetCurrentFarCodePointer();
|
|
|
|
|
EmitBranch(GetCurrentFarCodePointer());
|
|
|
|
|
const u32 jump_size =
|
|
|
|
|
static_cast<u32>(static_cast<const char*>(GetCurrentCodePointer()) - static_cast<const char*>(jump_pointer));
|
|
|
|
|
SwitchToFarCode();
|
|
|
|
|
|
|
|
|
|
EmitBeginBlock(true);
|
|
|
|
|
EmitFunctionCall(nullptr, &CPU::Recompiler::Thunks::ResolveBranch, Value::FromConstantPtr(m_block),
|
|
|
|
|
Value::FromConstantPtr(jump_pointer), Value::FromConstantPtr(resolve_pointer),
|
|
|
|
|
Value::FromConstantU32(jump_size));
|
|
|
|
|
EmitEndBlock(true, true);
|
|
|
|
|
|
|
|
|
|
m_register_cache.PopState();
|
|
|
|
|
|
|
|
|
|
SwitchToNearCode();
|
|
|
|
|
EmitBindLabel(&return_to_dispatcher);
|
|
|
|
|
EmitEndBlock(true, true);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// next_pc is not used for unconditional branches
|
|
|
|
|
WriteNewPC(branch_target, true);
|
|
|
|
|
if (condition != Condition::Always)
|
|
|
|
|
{
|
|
|
|
|
// branch taken path - modify the next pc
|
|
|
|
|
EmitBindLabel(&branch_taken);
|
|
|
|
|
EmitCopyValue(next_pc.GetHostRegister(), branch_target);
|
|
|
|
|
|
|
|
|
|
// converge point
|
|
|
|
|
EmitBindLabel(&branch_not_taken);
|
|
|
|
|
WriteNewPC(next_pc, true);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// next_pc is not used for unconditional branches
|
|
|
|
|
WriteNewPC(branch_target, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
InstructionEpilogue(cbi);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// now invalidate lr becuase it was possibly written in the branch
|
|
|
|
|
if (lr_reg != Reg::count && lr_reg != Reg::zero)
|
|
|
|
|
m_register_cache.InvalidateGuestRegister(lr_reg);
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Compute the branch target.
|
|
|
|
@ -2287,10 +2440,9 @@ bool CodeGenerator::Compile_Branch(const CodeBlockInstruction& cbi)
|
|
|
|
|
Value branch_target = OrValues(AndValues(CalculatePC(), Value::FromConstantU32(0xF0000000)),
|
|
|
|
|
Value::FromConstantU32(cbi.instruction.j.target << 2));
|
|
|
|
|
|
|
|
|
|
DoBranch(Condition::Always, Value(), Value(), (cbi.instruction.op == InstructionOp::jal) ? Reg::ra : Reg::count,
|
|
|
|
|
std::move(branch_target));
|
|
|
|
|
return DoBranch(Condition::Always, Value(), Value(),
|
|
|
|
|
(cbi.instruction.op == InstructionOp::jal) ? Reg::ra : Reg::count, std::move(branch_target));
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case InstructionOp::funct:
|
|
|
|
|
{
|
|
|
|
@ -2298,9 +2450,9 @@ bool CodeGenerator::Compile_Branch(const CodeBlockInstruction& cbi)
|
|
|
|
|
{
|
|
|
|
|
// npc = rs, link to rt
|
|
|
|
|
Value branch_target = m_register_cache.ReadGuestRegister(cbi.instruction.r.rs);
|
|
|
|
|
DoBranch(Condition::Always, Value(), Value(),
|
|
|
|
|
(cbi.instruction.r.funct == InstructionFunct::jalr) ? cbi.instruction.r.rd : Reg::count,
|
|
|
|
|
std::move(branch_target));
|
|
|
|
|
return DoBranch(Condition::Always, Value(), Value(),
|
|
|
|
|
(cbi.instruction.r.funct == InstructionFunct::jalr) ? cbi.instruction.r.rd : Reg::count,
|
|
|
|
|
std::move(branch_target));
|
|
|
|
|
}
|
|
|
|
|
else if (cbi.instruction.r.funct == InstructionFunct::syscall ||
|
|
|
|
|
cbi.instruction.r.funct == InstructionFunct::break_)
|
|
|
|
@ -2308,13 +2460,15 @@ bool CodeGenerator::Compile_Branch(const CodeBlockInstruction& cbi)
|
|
|
|
|
const Exception excode =
|
|
|
|
|
(cbi.instruction.r.funct == InstructionFunct::syscall) ? Exception::Syscall : Exception::BP;
|
|
|
|
|
GenerateExceptionExit(cbi, excode);
|
|
|
|
|
InstructionEpilogue(cbi);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
UnreachableCode();
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case InstructionOp::beq:
|
|
|
|
|
case InstructionOp::bne:
|
|
|
|
@ -2326,7 +2480,7 @@ bool CodeGenerator::Compile_Branch(const CodeBlockInstruction& cbi)
|
|
|
|
|
if (cbi.instruction.op == InstructionOp::beq && cbi.instruction.i.rs == Reg::zero &&
|
|
|
|
|
cbi.instruction.i.rt == Reg::zero)
|
|
|
|
|
{
|
|
|
|
|
DoBranch(Condition::Always, Value(), Value(), Reg::count, std::move(branch_target));
|
|
|
|
|
return DoBranch(Condition::Always, Value(), Value(), Reg::count, std::move(branch_target));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
@ -2334,10 +2488,9 @@ bool CodeGenerator::Compile_Branch(const CodeBlockInstruction& cbi)
|
|
|
|
|
Value lhs = m_register_cache.ReadGuestRegister(cbi.instruction.i.rs, true, true);
|
|
|
|
|
Value rhs = m_register_cache.ReadGuestRegister(cbi.instruction.i.rt);
|
|
|
|
|
const Condition condition = (cbi.instruction.op == InstructionOp::beq) ? Condition::Equal : Condition::NotEqual;
|
|
|
|
|
DoBranch(condition, lhs, rhs, Reg::count, std::move(branch_target));
|
|
|
|
|
return DoBranch(condition, lhs, rhs, Reg::count, std::move(branch_target));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case InstructionOp::bgtz:
|
|
|
|
|
case InstructionOp::blez:
|
|
|
|
@ -2350,9 +2503,8 @@ bool CodeGenerator::Compile_Branch(const CodeBlockInstruction& cbi)
|
|
|
|
|
|
|
|
|
|
const Condition condition =
|
|
|
|
|
(cbi.instruction.op == InstructionOp::bgtz) ? Condition::Greater : Condition::LessEqual;
|
|
|
|
|
DoBranch(condition, lhs, Value::FromConstantU32(0), Reg::count, std::move(branch_target));
|
|
|
|
|
return DoBranch(condition, lhs, Value::FromConstantU32(0), Reg::count, std::move(branch_target));
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case InstructionOp::b:
|
|
|
|
|
{
|
|
|
|
@ -2378,17 +2530,13 @@ bool CodeGenerator::Compile_Branch(const CodeBlockInstruction& cbi)
|
|
|
|
|
m_register_cache.WriteGuestRegister(Reg::ra, CalculatePC(4));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DoBranch(condition, lhs, Value(), Reg::count, std::move(branch_target));
|
|
|
|
|
return DoBranch(condition, lhs, Value(), Reg::count, std::move(branch_target));
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
UnreachableCode();
|
|
|
|
|
break;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
InstructionEpilogue(cbi);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool CodeGenerator::Compile_lui(const CodeBlockInstruction& cbi)
|
|
|
|
|