mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-04-20 11:36:13 +00:00
Integrated execution engine, tracer, recompilation engine and compiler
This commit is contained in:
parent
ee6a239679
commit
34e34910fd
3 changed files with 425 additions and 351 deletions
|
@ -113,6 +113,8 @@ public:
|
|||
return temp;
|
||||
}
|
||||
|
||||
CPUDecoder * GetDecoder() { return m_dec; };
|
||||
|
||||
public:
|
||||
u32 entry;
|
||||
u32 PC;
|
||||
|
|
|
@ -26,10 +26,8 @@ using namespace ppu_recompiler_llvm;
|
|||
u64 Compiler::s_rotate_mask[64][64];
|
||||
bool Compiler::s_rotate_mask_inited = false;
|
||||
|
||||
Compiler::Compiler(RecompilationEngine & recompilation_engine, const Executable default_function_executable, const Executable default_block_executable)
|
||||
: m_recompilation_engine(recompilation_engine)
|
||||
, m_default_function_executable(default_function_executable)
|
||||
, m_default_block_executable(default_block_executable) {
|
||||
Compiler::Compiler(RecompilationEngine & recompilation_engine, const Executable unknown_function, const Executable unknown_block)
|
||||
: m_recompilation_engine(recompilation_engine) {
|
||||
InitializeNativeTarget();
|
||||
InitializeNativeTargetAsmPrinter();
|
||||
InitializeNativeTargetDisassembler();
|
||||
|
@ -67,11 +65,18 @@ Compiler::Compiler(RecompilationEngine & recompilation_engine, const Executable
|
|||
m_fpm->doInitialization();
|
||||
|
||||
std::vector<Type *> arg_types;
|
||||
arg_types.push_back(m_ir_builder->getInt64Ty()->getPointerTo());
|
||||
arg_types.push_back(m_ir_builder->getInt64Ty()->getPointerTo());
|
||||
arg_types.push_back(m_ir_builder->getInt64Ty()->getPointerTo());
|
||||
arg_types.push_back(m_ir_builder->getInt64Ty()->getPointerTo());
|
||||
m_compiled_function_type = FunctionType::get(m_ir_builder->getInt64Ty(), arg_types, false);
|
||||
arg_types.push_back(m_ir_builder->getInt8PtrTy());
|
||||
arg_types.push_back(m_ir_builder->getInt8PtrTy());
|
||||
arg_types.push_back(m_ir_builder->getInt64Ty());
|
||||
m_compiled_function_type = FunctionType::get(m_ir_builder->getInt32Ty(), arg_types, false);
|
||||
|
||||
m_unknown_function = (Function *)m_module->getOrInsertFunction("unknown_function", m_compiled_function_type);
|
||||
m_unknown_function->setCallingConv(CallingConv::X86_64_Win64);
|
||||
m_execution_engine->addGlobalMapping(m_unknown_function, unknown_function);
|
||||
|
||||
m_unknown_block = (Function *)m_module->getOrInsertFunction("unknown_block", m_compiled_function_type);
|
||||
m_unknown_block->setCallingConv(CallingConv::X86_64_Win64);
|
||||
m_execution_engine->addGlobalMapping(m_unknown_block, unknown_block);
|
||||
|
||||
if (!s_rotate_mask_inited) {
|
||||
InitRotateMask();
|
||||
|
@ -86,50 +91,53 @@ Compiler::~Compiler() {
|
|||
delete m_llvm_context;
|
||||
}
|
||||
|
||||
Executable Compiler::Compile(const std::string & name, const ControlFlowGraph & cfg, bool inline_all_blocks, bool generate_linkable_exits, bool generate_trace) {
|
||||
assert(!name.empty());
|
||||
assert(!cfg.empty());
|
||||
|
||||
Executable Compiler::Compile(const std::string & name, const ControlFlowGraph & cfg, bool inline_all, bool generate_linkable_exits) {
|
||||
auto compilation_start = std::chrono::high_resolution_clock::now();
|
||||
|
||||
m_state.cfg = &cfg;
|
||||
m_state.inline_all_blocks = inline_all_blocks;
|
||||
m_state.inline_all = inline_all;
|
||||
m_state.generate_linkable_exits = generate_linkable_exits;
|
||||
m_state.generate_trace = generate_trace;
|
||||
m_state.address_to_block.clear();
|
||||
|
||||
// Create the function
|
||||
m_state.function = (Function *)m_module->getOrInsertFunction(name, m_compiled_function_type);
|
||||
m_state.function->setCallingConv(CallingConv::X86_64_Win64);
|
||||
auto arg_i = m_state.function->arg_begin();
|
||||
arg_i->setName("execution_engine");
|
||||
m_state.args[CompileTaskState::Args::ExecutionEngine] = arg_i;
|
||||
(++arg_i)->setName("state");
|
||||
arg_i->setName("ppu_state");
|
||||
m_state.args[CompileTaskState::Args::State] = arg_i;
|
||||
(++arg_i)->setName("interpreter");
|
||||
m_state.args[CompileTaskState::Args::Interpreter] = arg_i;
|
||||
(++arg_i)->setName("tracer");
|
||||
m_state.args[CompileTaskState::Args::Tracer] = arg_i;
|
||||
(++arg_i)->setName("context");
|
||||
m_state.args[CompileTaskState::Args::Context] = arg_i;
|
||||
|
||||
// Create the entry block and add code to branch to the first instruction
|
||||
m_ir_builder->SetInsertPoint(GetBasicBlockFromAddress(0));
|
||||
m_ir_builder->CreateBr(GetBasicBlockFromAddress(cfg[0].first));
|
||||
m_ir_builder->CreateBr(GetBasicBlockFromAddress(cfg.start_address));
|
||||
|
||||
// Convert each block in this CFG to LLVM IR
|
||||
for (m_state.cfg_entry = cfg.begin(); m_state.cfg_entry != cfg.end(); m_state.cfg_entry++) {
|
||||
m_state.current_instruction_address = m_state.cfg_entry->first;
|
||||
auto block = GetBasicBlockFromAddress(m_state.current_instruction_address);
|
||||
m_ir_builder->SetInsertPoint(block);
|
||||
// Convert each instruction in the CFG to LLVM IR
|
||||
std::vector<PHINode *> exit_instr_list;
|
||||
for (auto instr_i = cfg.instruction_addresses.begin(); instr_i != cfg.instruction_addresses.end(); instr_i++) {
|
||||
m_state.current_instruction_address = *instr_i;
|
||||
auto instr_bb = GetBasicBlockFromAddress(m_state.current_instruction_address);
|
||||
m_ir_builder->SetInsertPoint(instr_bb);
|
||||
|
||||
m_state.hit_branch_instruction = false;
|
||||
if (!inline_all_blocks && m_state.cfg_entry != cfg.begin()) {
|
||||
if (!inline_all && instr_i != cfg.instruction_addresses.begin()) {
|
||||
// Use an already compiled implementation of this block if available
|
||||
auto ordinal = m_recompilation_engine.GetOrdinal(m_state.cfg_entry->first);
|
||||
auto ordinal = m_recompilation_engine.GetOrdinal(*instr_i);
|
||||
if (ordinal != 0xFFFFFFFF) {
|
||||
auto ret_i64 = IndirectCall(m_state.cfg_entry->first, false);
|
||||
auto switch_instr = m_ir_builder->CreateSwitch(ret_i64, GetBasicBlockFromAddress(0xFFFFFFFF));
|
||||
for (auto i = m_state.cfg_entry->second.begin(); i != m_state.cfg_entry->second.end(); i++) {
|
||||
switch_instr->addCase(m_ir_builder->getInt64(i->address), GetBasicBlockFromAddress(i->address));
|
||||
auto exit_instr_i32 = m_ir_builder->CreatePHI(m_ir_builder->getInt32Ty(), 0);
|
||||
exit_instr_list.push_back(exit_instr_i32);
|
||||
|
||||
auto context_i64 = m_ir_builder->CreateZExt(exit_instr_i32, m_ir_builder->getInt64Ty());
|
||||
context_i64 = m_ir_builder->CreateOr(context_i64, (u64)cfg.function_address << 32);
|
||||
auto ret_i32 = IndirectCall(*instr_i, context_i64, false);
|
||||
|
||||
auto switch_instr = m_ir_builder->CreateSwitch(ret_i32, GetBasicBlockFromAddress(0xFFFFFFFF));
|
||||
auto branch_i = cfg.branches.find(*instr_i);
|
||||
if (branch_i != cfg.branches.end()) {
|
||||
for (auto next_instr_i = branch_i->second.begin(); next_instr_i != branch_i->second.end(); next_instr_i++) {
|
||||
switch_instr->addCase(m_ir_builder->getInt32(*next_instr_i), GetBasicBlockFromAddress(*next_instr_i));
|
||||
}
|
||||
}
|
||||
|
||||
m_state.hit_branch_instruction = true;
|
||||
|
@ -137,7 +145,7 @@ Executable Compiler::Compile(const std::string & name, const ControlFlowGraph &
|
|||
}
|
||||
|
||||
while (!m_state.hit_branch_instruction) {
|
||||
if (!block->getInstList().empty()) {
|
||||
if (!instr_bb->getInstList().empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -146,54 +154,51 @@ Executable Compiler::Compile(const std::string & name, const ControlFlowGraph &
|
|||
|
||||
if (!m_state.hit_branch_instruction) {
|
||||
m_state.current_instruction_address += 4;
|
||||
block = GetBasicBlockFromAddress(m_state.current_instruction_address);
|
||||
m_ir_builder->CreateBr(block);
|
||||
m_ir_builder->SetInsertPoint(block);
|
||||
instr_bb = GetBasicBlockFromAddress(m_state.current_instruction_address);
|
||||
m_ir_builder->CreateBr(instr_bb);
|
||||
m_ir_builder->SetInsertPoint(instr_bb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_recompilation_engine.Log() << *m_state.function;
|
||||
|
||||
// Generate exit logic for all empty blocks
|
||||
auto default_exit_block_name = GetBasicBlockNameFromAddress(0xFFFFFFFF);
|
||||
for (auto block_i = m_state.function->begin(); block_i != m_state.function->end(); block_i++) {
|
||||
if (!block_i->getInstList().empty() || block_i->getName() == default_exit_block_name) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// An empty block. Generate exit logic.
|
||||
// Found an empty block
|
||||
m_recompilation_engine.Log() << "Empty block: " << block_i->getName() << "\n";
|
||||
|
||||
m_ir_builder->SetInsertPoint(block_i);
|
||||
auto exit_block_i64 = m_ir_builder->CreatePHI(m_ir_builder->getInt64Ty(), 0);
|
||||
for (auto i = pred_begin(block_i); i != pred_end(block_i); i++) {
|
||||
auto pred_address = GetAddressFromBasicBlockName(block_i->getName());
|
||||
exit_block_i64->addIncoming(m_ir_builder->getInt64(m_state.address_to_block[pred_address]), *i);
|
||||
}
|
||||
auto exit_instr_i32 = m_ir_builder->CreatePHI(m_ir_builder->getInt32Ty(), 0);
|
||||
exit_instr_list.push_back(exit_instr_i32);
|
||||
|
||||
auto block_address = GetAddressFromBasicBlockName(block_i->getName());
|
||||
SetPc(m_ir_builder->getInt32(block_address));
|
||||
auto instr_address = GetAddressFromBasicBlockName(block_i->getName());
|
||||
SetPc(m_ir_builder->getInt32(instr_address));
|
||||
|
||||
if (generate_linkable_exits) {
|
||||
if (generate_trace) {
|
||||
Call<void>("Tracer.Trace", &Tracer::Trace, m_ir_builder->getInt32((uint32_t)Tracer::TraceType::ExitFromCompiledFunction),
|
||||
m_ir_builder->getInt32(cfg[0].first), m_ir_builder->CreateTrunc(exit_block_i64, m_ir_builder->getInt32Ty()));
|
||||
}
|
||||
|
||||
auto ret_i64 = IndirectCall(block_address, false);
|
||||
auto cmp_i1 = m_ir_builder->CreateICmpNE(ret_i64, m_ir_builder->getInt64(0));
|
||||
auto then_bb = BasicBlock::Create(m_ir_builder->getContext());
|
||||
auto merge_bb = BasicBlock::Create(m_ir_builder->getContext());
|
||||
auto context_i64 = m_ir_builder->CreateZExt(exit_instr_i32, m_ir_builder->getInt64Ty());
|
||||
context_i64 = m_ir_builder->CreateOr(context_i64, (u64)cfg.function_address << 32);
|
||||
auto ret_i32 = IndirectCall(instr_address, context_i64, false);
|
||||
auto cmp_i1 = m_ir_builder->CreateICmpNE(ret_i32, m_ir_builder->getInt32(0));
|
||||
auto then_bb = GetBasicBlockFromAddress(instr_address, "then");
|
||||
auto merge_bb = GetBasicBlockFromAddress(instr_address, "merge");
|
||||
m_ir_builder->CreateCondBr(cmp_i1, then_bb, merge_bb);
|
||||
|
||||
m_ir_builder->SetInsertPoint(then_bb);
|
||||
IndirectCall(1, false);
|
||||
context_i64 = m_ir_builder->CreateZExt(ret_i32, m_ir_builder->getInt64Ty());
|
||||
context_i64 = m_ir_builder->CreateOr(context_i64, (u64)cfg.function_address << 32);
|
||||
m_ir_builder->CreateCall3(m_unknown_block, m_state.args[CompileTaskState::Args::State], m_state.args[CompileTaskState::Args::Interpreter], context_i64);
|
||||
m_ir_builder->CreateBr(merge_bb);
|
||||
|
||||
m_ir_builder->SetInsertPoint(merge_bb);
|
||||
m_ir_builder->CreateRet(m_ir_builder->getInt64(0));
|
||||
m_ir_builder->CreateRet(m_ir_builder->getInt32(0));
|
||||
} else {
|
||||
m_ir_builder->CreateRet(exit_block_i64);
|
||||
m_ir_builder->CreateRet(exit_instr_i32);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -203,33 +208,34 @@ Executable Compiler::Compile(const std::string & name, const ControlFlowGraph &
|
|||
auto default_exit_bb = GetBasicBlockFromAddress(0xFFFFFFFF, false);
|
||||
if (default_exit_bb) {
|
||||
m_ir_builder->SetInsertPoint(default_exit_bb);
|
||||
auto exit_block_i64 = m_ir_builder->CreatePHI(m_ir_builder->getInt64Ty(), 1);
|
||||
for (auto i = pred_begin(default_exit_bb); i != pred_end(default_exit_bb); i++) {
|
||||
// the last but one instruction of the predecessor sets the exit block address
|
||||
auto j = (*i)->rbegin();
|
||||
j++;
|
||||
exit_block_i64->addIncoming(&(*j), *i);
|
||||
}
|
||||
auto exit_instr_i32 = m_ir_builder->CreatePHI(m_ir_builder->getInt32Ty(), 0);
|
||||
exit_instr_list.push_back(exit_instr_i32);
|
||||
|
||||
if (generate_linkable_exits) {
|
||||
auto cmp_i1 = m_ir_builder->CreateICmpNE(exit_block_i64, m_ir_builder->getInt64(0));
|
||||
auto then_bb = BasicBlock::Create(m_ir_builder->getContext());
|
||||
auto merge_bb = BasicBlock::Create(m_ir_builder->getContext());
|
||||
auto cmp_i1 = m_ir_builder->CreateICmpNE(exit_instr_i32, m_ir_builder->getInt32(0));
|
||||
auto then_bb = GetBasicBlockFromAddress(0xFFFFFFFF, "then");
|
||||
auto merge_bb = GetBasicBlockFromAddress(0xFFFFFFFF, "merge");
|
||||
m_ir_builder->CreateCondBr(cmp_i1, then_bb, merge_bb);
|
||||
|
||||
m_ir_builder->SetInsertPoint(then_bb);
|
||||
if (generate_trace) {
|
||||
Call<void>("Tracer.Trace", &Tracer::Trace, m_ir_builder->getInt32((uint32_t)Tracer::TraceType::ExitFromCompiledFunction),
|
||||
m_ir_builder->getInt32(cfg[0].first), m_ir_builder->CreateTrunc(exit_block_i64, m_ir_builder->getInt32Ty()));
|
||||
}
|
||||
|
||||
IndirectCall(1, false);
|
||||
auto context_i64 = m_ir_builder->CreateZExt(exit_instr_i32, m_ir_builder->getInt64Ty());
|
||||
context_i64 = m_ir_builder->CreateOr(context_i64, (u64)cfg.function_address << 32);
|
||||
m_ir_builder->CreateCall3(m_unknown_block, m_state.args[CompileTaskState::Args::State], m_state.args[CompileTaskState::Args::Interpreter], context_i64);
|
||||
m_ir_builder->CreateBr(merge_bb);
|
||||
|
||||
m_ir_builder->SetInsertPoint(merge_bb);
|
||||
m_ir_builder->CreateRet(m_ir_builder->getInt64(0));
|
||||
m_ir_builder->CreateRet(m_ir_builder->getInt32(0));
|
||||
} else {
|
||||
m_ir_builder->CreateRet(exit_block_i64);
|
||||
m_ir_builder->CreateRet(exit_instr_i32);
|
||||
}
|
||||
}
|
||||
|
||||
// Add incoming values for all exit instr PHI nodes
|
||||
for (auto exit_instr_i = exit_instr_list.begin(); exit_instr_i != exit_instr_list.end(); exit_instr_i++) {
|
||||
auto block = (*exit_instr_i)->getParent();
|
||||
for (auto pred_i = pred_begin(block); pred_i != pred_end(block); pred_i++) {
|
||||
auto pred_address = GetAddressFromBasicBlockName((*pred_i)->getName());
|
||||
(*exit_instr_i)->addIncoming(m_ir_builder->getInt32(pred_address), *pred_i);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1575,7 +1581,8 @@ void Compiler::ADDIS(u32 rd, u32 ra, s32 simm16) {
|
|||
|
||||
void Compiler::BC(u32 bo, u32 bi, s32 bd, u32 aa, u32 lk) {
|
||||
auto target_i64 = m_ir_builder->getInt64(branchTarget(aa ? 0 : m_state.current_instruction_address, bd));
|
||||
CreateBranch(CheckBranchCondition(bo, bi), target_i64, lk ? true : false);
|
||||
auto target_i32 = m_ir_builder->CreateTrunc(target_i64, m_ir_builder->getInt32Ty());
|
||||
CreateBranch(CheckBranchCondition(bo, bi), target_i32, lk ? true : false);
|
||||
//m_hit_branch_instruction = true;
|
||||
//SetPc(m_ir_builder->getInt32(m_current_instruction_address));
|
||||
//InterpreterCall("BC", &PPUInterpreter::BC, bo, bi, bd, aa, lk);
|
||||
|
@ -1589,7 +1596,8 @@ void Compiler::SC(u32 sc_code) {
|
|||
|
||||
void Compiler::B(s32 ll, u32 aa, u32 lk) {
|
||||
auto target_i64 = m_ir_builder->getInt64(branchTarget(aa ? 0 : m_state.current_instruction_address, ll));
|
||||
CreateBranch(nullptr, target_i64, lk ? true : false);
|
||||
auto target_i32 = m_ir_builder->CreateTrunc(target_i64, m_ir_builder->getInt32Ty());
|
||||
CreateBranch(nullptr, target_i32, lk ? true : false);
|
||||
//m_hit_branch_instruction = true;
|
||||
//SetPc(m_ir_builder->getInt32(m_current_instruction_address));
|
||||
//InterpreterCall("B", &PPUInterpreter::B, ll, aa, lk);
|
||||
|
@ -1609,7 +1617,8 @@ void Compiler::MCRF(u32 crfd, u32 crfs) {
|
|||
void Compiler::BCLR(u32 bo, u32 bi, u32 bh, u32 lk) {
|
||||
auto lr_i64 = GetLr();
|
||||
lr_i64 = m_ir_builder->CreateAnd(lr_i64, ~0x3ULL);
|
||||
CreateBranch(CheckBranchCondition(bo, bi), lr_i64, lk ? true : false, true);
|
||||
auto lr_i32 = m_ir_builder->CreateTrunc(lr_i64, m_ir_builder->getInt32Ty());
|
||||
CreateBranch(CheckBranchCondition(bo, bi), lr_i32, lk ? true : false, true);
|
||||
//m_hit_branch_instruction = true;
|
||||
//SetPc(m_ir_builder->getInt32(m_current_instruction_address));
|
||||
//InterpreterCall("BCLR", &PPUInterpreter::BCLR, bo, bi, bh, lk);
|
||||
|
@ -1710,7 +1719,8 @@ void Compiler::CROR(u32 crbd, u32 crba, u32 crbb) {
|
|||
void Compiler::BCCTR(u32 bo, u32 bi, u32 bh, u32 lk) {
|
||||
auto ctr_i64 = GetCtr();
|
||||
ctr_i64 = m_ir_builder->CreateAnd(ctr_i64, ~0x3ULL);
|
||||
CreateBranch(CheckBranchCondition(bo, bi), ctr_i64, lk ? true : false);
|
||||
auto ctr_i32 = m_ir_builder->CreateTrunc(ctr_i64, m_ir_builder->getInt32Ty());
|
||||
CreateBranch(CheckBranchCondition(bo, bi), ctr_i32, lk ? true : false);
|
||||
//m_hit_branch_instruction = true;
|
||||
//SetPc(m_ir_builder->getInt32(m_current_instruction_address));
|
||||
//InterpreterCall("BCCTR", &PPUInterpreter::BCCTR, bo, bi, bh, lk);
|
||||
|
@ -4148,7 +4158,7 @@ void Compiler::UNK(const u32 code, const u32 opcode, const u32 gcode) {
|
|||
//InterpreterCall("UNK", &PPUInterpreter::UNK, code, opcode, gcode);
|
||||
}
|
||||
|
||||
std::string Compiler::GetBasicBlockNameFromAddress(u32 address, const std::string & suffix) {
|
||||
std::string Compiler::GetBasicBlockNameFromAddress(u32 address, const std::string & suffix) const {
|
||||
std::string name;
|
||||
|
||||
if (address == 0) {
|
||||
|
@ -4166,7 +4176,7 @@ std::string Compiler::GetBasicBlockNameFromAddress(u32 address, const std::strin
|
|||
return name;
|
||||
}
|
||||
|
||||
u32 Compiler::GetAddressFromBasicBlockName(const std::string & name) {
|
||||
u32 Compiler::GetAddressFromBasicBlockName(const std::string & name) const {
|
||||
if (name.compare(0, 6, "instr_") == 0) {
|
||||
return strtoul(name.c_str() + 6, nullptr, 0);
|
||||
} else if (name == GetBasicBlockNameFromAddress(0)) {
|
||||
|
@ -4585,7 +4595,7 @@ Value * Compiler::CheckBranchCondition(u32 bo, u32 bi) {
|
|||
return cmp_i1;
|
||||
}
|
||||
|
||||
void Compiler::CreateBranch(llvm::Value * cmp_i1, llvm::Value * target_i64, bool lk, bool target_is_lr) {
|
||||
void Compiler::CreateBranch(llvm::Value * cmp_i1, llvm::Value * target_i32, bool lk, bool target_is_lr) {
|
||||
if (lk) {
|
||||
SetLr(m_ir_builder->getInt64(m_state.current_instruction_address + 4));
|
||||
}
|
||||
|
@ -4593,18 +4603,18 @@ void Compiler::CreateBranch(llvm::Value * cmp_i1, llvm::Value * target_i64, bool
|
|||
auto current_block = m_ir_builder->GetInsertBlock();
|
||||
|
||||
BasicBlock * target_block = nullptr;
|
||||
if (dyn_cast<ConstantInt>(target_i64)) {
|
||||
if (dyn_cast<ConstantInt>(target_i32)) {
|
||||
// Target address is an immediate value.
|
||||
u32 target_address = (u32)(dyn_cast<ConstantInt>(target_i64)->getLimitedValue());
|
||||
u32 target_address = (u32)(dyn_cast<ConstantInt>(target_i32)->getLimitedValue());
|
||||
if (lk) {
|
||||
// Function call
|
||||
if (cmp_i1) { // There is no need to create a new block for an unconditional jump
|
||||
target_block = BasicBlock::Create(m_ir_builder->getContext(), "", m_state.function);
|
||||
target_block = GetBasicBlockFromAddress(m_state.current_instruction_address, "target");
|
||||
m_ir_builder->SetInsertPoint(target_block);
|
||||
}
|
||||
|
||||
SetPc(target_i64);
|
||||
IndirectCall(target_address, true);
|
||||
SetPc(target_i32);
|
||||
IndirectCall(target_address, m_ir_builder->getInt64(0), true);
|
||||
m_ir_builder->CreateBr(GetBasicBlockFromAddress(m_state.current_instruction_address + 4));
|
||||
} else {
|
||||
// Local branch
|
||||
|
@ -4613,34 +4623,40 @@ void Compiler::CreateBranch(llvm::Value * cmp_i1, llvm::Value * target_i64, bool
|
|||
} else {
|
||||
// Target address is in a register
|
||||
if (cmp_i1) { // There is no need to create a new block for an unconditional jump
|
||||
target_block = BasicBlock::Create(m_ir_builder->getContext(), "", m_state.function);
|
||||
target_block = GetBasicBlockFromAddress(m_state.current_instruction_address, "target");
|
||||
m_ir_builder->SetInsertPoint(target_block);
|
||||
}
|
||||
|
||||
SetPc(target_i64);
|
||||
|
||||
SetPc(target_i32);
|
||||
if (target_is_lr && !lk) {
|
||||
// Return from function call
|
||||
m_ir_builder->CreateRet(m_ir_builder->getInt64(0));
|
||||
// Return from this function
|
||||
m_ir_builder->CreateRet(m_ir_builder->getInt32(0));
|
||||
} else if (lk) {
|
||||
auto next_block = GetBasicBlockFromAddress(m_state.current_instruction_address + 4);
|
||||
auto call_block = BasicBlock::Create(m_ir_builder->getContext(), "", m_state.function);
|
||||
m_ir_builder->CreateOr(m_ir_builder->getInt64(m_state.cfg_entry->first), (uint64_t)0);
|
||||
auto switch_instr = m_ir_builder->CreateSwitch(target_i64, call_block);
|
||||
m_ir_builder->SetInsertPoint(call_block);
|
||||
IndirectCall(0, true);
|
||||
auto next_block = GetBasicBlockFromAddress(m_state.current_instruction_address + 4);
|
||||
auto unknown_function_block = GetBasicBlockFromAddress(m_state.current_instruction_address, "unknown_function");
|
||||
|
||||
auto switch_instr = m_ir_builder->CreateSwitch(target_i32, unknown_function_block);
|
||||
m_ir_builder->SetInsertPoint(unknown_function_block);
|
||||
m_ir_builder->CreateCall3(m_unknown_function, m_state.args[CompileTaskState::Args::State], m_state.args[CompileTaskState::Args::Interpreter], m_ir_builder->getInt64(0));
|
||||
m_ir_builder->CreateBr(next_block);
|
||||
for (auto i = m_state.cfg_entry->second.begin(); i != m_state.cfg_entry->second.end(); i++) {
|
||||
call_block = BasicBlock::Create(m_ir_builder->getContext(), "", m_state.function);
|
||||
m_ir_builder->SetInsertPoint(call_block);
|
||||
IndirectCall(i->address, true);
|
||||
m_ir_builder->CreateBr(next_block);
|
||||
switch_instr->addCase(m_ir_builder->getInt32(i->address), call_block);
|
||||
|
||||
auto call_i = m_state.cfg->calls.find(m_state.current_instruction_address);
|
||||
if (call_i != m_state.cfg->calls.end()) {
|
||||
for (auto function_i = call_i->second.begin(); function_i != call_i->second.end(); function_i++) {
|
||||
auto block = GetBasicBlockFromAddress(m_state.current_instruction_address, fmt::Format("0x%08X", *function_i));
|
||||
m_ir_builder->SetInsertPoint(block);
|
||||
IndirectCall(*function_i, m_ir_builder->getInt64(0), true);
|
||||
m_ir_builder->CreateBr(next_block);
|
||||
switch_instr->addCase(m_ir_builder->getInt32(*function_i), block);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
auto switch_instr = m_ir_builder->CreateSwitch(target_i64, GetBasicBlockFromAddress(0xFFFFFFFF));
|
||||
for (auto i = m_state.cfg_entry->second.begin(); i != m_state.cfg_entry->second.end(); i++) {
|
||||
switch_instr->addCase(m_ir_builder->getInt64(i->address), GetBasicBlockFromAddress(i->address));
|
||||
auto switch_instr = m_ir_builder->CreateSwitch(target_i32, GetBasicBlockFromAddress(0xFFFFFFFF));
|
||||
auto branch_i = m_state.cfg->branches.find(m_state.current_instruction_address);
|
||||
if (branch_i != m_state.cfg->branches.end()) {
|
||||
for (auto next_instr_i = branch_i->second.begin(); next_instr_i != branch_i->second.end(); next_instr_i++) {
|
||||
switch_instr->addCase(m_ir_builder->getInt32(*next_instr_i), GetBasicBlockFromAddress(*next_instr_i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4820,14 +4836,11 @@ Value * Compiler::Call(const char * name, Func function, Args... args) {
|
|||
return m_ir_builder->CreateCall(fn, fn_args);
|
||||
}
|
||||
|
||||
llvm::Value * Compiler::IndirectCall(u32 address, bool is_function) {
|
||||
llvm::Value * Compiler::IndirectCall(u32 address, Value * context_i64, bool is_function) {
|
||||
auto ordinal = m_recompilation_engine.AllocateOrdinal(address, is_function);
|
||||
auto executable_addr_i64 = m_ir_builder->getInt64(m_recompilation_engine.GetAddressOfExecutableLookup() + (ordinal * sizeof(u64)));
|
||||
auto executable_ptr = m_ir_builder->CreateIntToPtr(executable_addr_i64, m_compiled_function_type);
|
||||
return m_ir_builder->CreateCall4(executable_ptr, m_state.args[CompileTaskState::Args::ExecutionEngine],
|
||||
m_state.args[CompileTaskState::Args::State],
|
||||
m_state.args[CompileTaskState::Args::Interpreter],
|
||||
m_state.args[CompileTaskState::Args::Tracer]);
|
||||
return m_ir_builder->CreateCall3(executable_ptr, m_state.args[CompileTaskState::Args::State], m_state.args[CompileTaskState::Args::Interpreter], context_i64);
|
||||
}
|
||||
|
||||
void Compiler::InitRotateMask() {
|
||||
|
@ -4986,7 +4999,7 @@ void RecompilationEngine::Task() {
|
|||
}
|
||||
|
||||
void RecompilationEngine::ProcessExecutionTrace(const ExecutionTrace & execution_trace) {
|
||||
auto execution_trace_id = GetExecutionTraceId(execution_trace);
|
||||
auto execution_trace_id = execution_trace.GetId();
|
||||
auto processed_execution_trace_i = m_processed_execution_traces.find(execution_trace_id);
|
||||
if (processed_execution_trace_i == m_processed_execution_traces.end()) {
|
||||
Log() << "Trace: " << execution_trace.ToString() << "\n";
|
||||
|
@ -4995,38 +5008,33 @@ void RecompilationEngine::ProcessExecutionTrace(const ExecutionTrace & execution
|
|||
|
||||
auto split_trace = false;
|
||||
auto block_i = m_block_table.end();
|
||||
auto trace_block_i = execution_trace.blocks.begin();
|
||||
for (; trace_block_i != execution_trace.blocks.end(); trace_block_i++) {
|
||||
if (trace_block_i->type == BlockId::Type::Exit) {
|
||||
for (auto trace_i = execution_trace.entries.begin(); trace_i != execution_trace.entries.end(); trace_i++) {
|
||||
if (trace_i->type == ExecutionTraceEntry::Type::CompiledBlock) {
|
||||
block_i = m_block_table.end();
|
||||
split_trace = true;
|
||||
} else if (block_i == m_block_table.end()) {
|
||||
BlockEntry key(trace_block_i->address);
|
||||
}
|
||||
|
||||
if (block_i == m_block_table.end()) {
|
||||
BlockEntry key(trace_i->GetPrimaryAddress(), execution_trace.function_address);
|
||||
|
||||
block_i = m_block_table.find(&key);
|
||||
if (block_i == m_block_table.end()) {
|
||||
block_i = m_block_table.insert(m_block_table.end(), new BlockEntry(key.address));
|
||||
block_i = m_block_table.insert(m_block_table.end(), new BlockEntry(key.cfg.start_address, key.cfg.function_address));
|
||||
}
|
||||
|
||||
(*block_i)->is_function_start = key.address == execution_trace.function_address;
|
||||
tmp_block_list.push_back(*block_i);
|
||||
}
|
||||
|
||||
if (block_i != m_block_table.end()) {
|
||||
BlockId next_block;
|
||||
if (trace_block_i + 1 != execution_trace.blocks.end()) {
|
||||
next_block = *(trace_block_i + 1);
|
||||
} else {
|
||||
if (!split_trace && execution_trace.type == ExecutionTrace::Type::Loop) {
|
||||
next_block = *(execution_trace.blocks.begin());
|
||||
} else {
|
||||
next_block.address = 0;
|
||||
next_block.type = BlockId::Type::Exit;
|
||||
}
|
||||
const ExecutionTraceEntry * next_trace = nullptr;
|
||||
if (trace_i + 1 != execution_trace.entries.end()) {
|
||||
next_trace = &(*(trace_i + 1));
|
||||
} else if (!split_trace && execution_trace.type == ExecutionTrace::Type::Loop) {
|
||||
if (!split_trace && execution_trace.type == ExecutionTrace::Type::Loop) {
|
||||
next_trace = &(*(execution_trace.entries.begin()));
|
||||
}
|
||||
|
||||
UpdateControlFlowGraph((*block_i)->cfg, *trace_block_i, next_block);
|
||||
}
|
||||
|
||||
UpdateControlFlowGraph((*block_i)->cfg, *trace_i, next_trace);
|
||||
}
|
||||
|
||||
processed_execution_trace_i = m_processed_execution_traces.insert(m_processed_execution_traces.end(), std::make_pair(execution_trace_id, std::move(tmp_block_list)));
|
||||
|
@ -5045,24 +5053,26 @@ void RecompilationEngine::ProcessExecutionTrace(const ExecutionTrace & execution
|
|||
std::remove_if(processed_execution_trace_i->second.begin(), processed_execution_trace_i->second.end(), [](const BlockEntry * b)->bool { return b->is_compiled; });
|
||||
}
|
||||
|
||||
void RecompilationEngine::UpdateControlFlowGraph(ControlFlowGraph & cfg, BlockId block, BlockId next_block) {
|
||||
if (block.type == BlockId::Type::Exit && next_block.type == BlockId::Type::Exit) {
|
||||
return;
|
||||
}
|
||||
void RecompilationEngine::UpdateControlFlowGraph(ControlFlowGraph & cfg, const ExecutionTraceEntry & this_entry, const ExecutionTraceEntry * next_entry) {
|
||||
if (this_entry.type == ExecutionTraceEntry::Type::Instruction) {
|
||||
cfg.instruction_addresses.insert(this_entry.GetPrimaryAddress());
|
||||
|
||||
if (block.type == BlockId::Type::FunctionCall) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto block_i = std::find_if(cfg.begin(), cfg.end(), [&block](const ControlFlowGraph::value_type & v)->bool { return v.first == block.address; });
|
||||
if (block.type == BlockId::Type::Normal && block_i == cfg.end()) {
|
||||
block_i = cfg.insert(cfg.end(), std::make_pair(block.address, std::vector<BlockId>()));
|
||||
}
|
||||
|
||||
if (block_i != cfg.end() && next_block.address && next_block.type != BlockId::Type::Exit) {
|
||||
auto next_block_i = std::find(block_i->second.begin(), block_i->second.end(), next_block);
|
||||
if (next_block_i == block_i->second.end()) {
|
||||
block_i->second.push_back(next_block);
|
||||
if (next_entry) {
|
||||
if (next_entry->type == ExecutionTraceEntry::Type::Instruction || next_entry->type == ExecutionTraceEntry::Type::CompiledBlock) {
|
||||
if (next_entry->GetPrimaryAddress() != (this_entry.GetPrimaryAddress() + 4)) {
|
||||
cfg.branches[this_entry.GetPrimaryAddress()].insert(next_entry->GetPrimaryAddress());
|
||||
}
|
||||
} else if (next_entry->type == ExecutionTraceEntry::Type::FunctionCall) {
|
||||
cfg.calls[this_entry.instruction.address].insert(next_entry->GetPrimaryAddress());
|
||||
}
|
||||
}
|
||||
} else if (this_entry.type == ExecutionTraceEntry::Type::CompiledBlock) {
|
||||
if (next_entry) {
|
||||
if (next_entry->type == ExecutionTraceEntry::Type::Instruction || next_entry->type == ExecutionTraceEntry::Type::CompiledBlock) {
|
||||
cfg.branches[this_entry.compiled_block.exit_address].insert(next_entry->GetPrimaryAddress());
|
||||
} else if (next_entry->type == ExecutionTraceEntry::Type::FunctionCall) {
|
||||
cfg.calls[this_entry.compiled_block.exit_address].insert(next_entry->GetPrimaryAddress());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5070,11 +5080,11 @@ void RecompilationEngine::UpdateControlFlowGraph(ControlFlowGraph & cfg, BlockId
|
|||
void RecompilationEngine::CompileBlock(BlockEntry & block_entry, bool inline_referenced_blocks) {
|
||||
Log() << "Compile: " << block_entry.ToString() << "\n";
|
||||
|
||||
auto ordinal = AllocateOrdinal(block_entry.address, block_entry.is_function_start);
|
||||
auto executable = m_compiler.Compile(fmt::Format("fn_0x%08X_%u", block_entry.address, block_entry.revision++), block_entry.cfg,
|
||||
block_entry.is_function_start ? false : true /*inline_all_blocks*/,
|
||||
block_entry.is_function_start ? true : false /*generate_linkable_exits*/,
|
||||
block_entry.is_function_start ? true : false /*generate_trace*/);
|
||||
auto is_funciton = block_entry.cfg.start_address == block_entry.cfg.function_address;
|
||||
auto ordinal = AllocateOrdinal(block_entry.cfg.start_address, is_funciton);
|
||||
auto executable = m_compiler.Compile(fmt::Format("fn_0x%08X_%u", block_entry.cfg.start_address, block_entry.revision++), block_entry.cfg,
|
||||
is_funciton ? false : true /*inline_all*/,
|
||||
is_funciton ? true : false /*generate_linkable_exits*/);
|
||||
m_executable_lookup[ordinal] = executable;
|
||||
}
|
||||
|
||||
|
@ -5090,7 +5100,6 @@ std::shared_ptr<RecompilationEngine> RecompilationEngine::GetInstance() {
|
|||
|
||||
Tracer::Tracer()
|
||||
: m_recompilation_engine(RecompilationEngine::GetInstance()) {
|
||||
m_trace.reserve(1000);
|
||||
m_stack.reserve(100);
|
||||
}
|
||||
|
||||
|
@ -5100,81 +5109,59 @@ Tracer::~Tracer() {
|
|||
|
||||
void Tracer::Trace(TraceType trace_type, u32 arg1, u32 arg2) {
|
||||
ExecutionTrace * execution_trace = nullptr;
|
||||
BlockId block_id;
|
||||
int function;
|
||||
|
||||
switch (trace_type) {
|
||||
case TraceType::CallFunction:
|
||||
// arg1 is address of the function
|
||||
block_id.address = arg1;
|
||||
block_id.type = BlockId::Type::FunctionCall;
|
||||
m_trace.push_back(block_id);
|
||||
m_stack.back()->entries.push_back(ExecutionTraceEntry(ExecutionTraceEntry::Type::FunctionCall, arg1));
|
||||
break;
|
||||
case TraceType::EnterFunction:
|
||||
// No args used
|
||||
m_stack.push_back((u32)m_trace.size());
|
||||
// arg1 is address of the function
|
||||
m_stack.push_back(new ExecutionTrace(arg1));
|
||||
break;
|
||||
case TraceType::ExitFromCompiledFunction:
|
||||
// arg1 is address of function.
|
||||
// arg2 is the address of the exit block.
|
||||
block_id.address = arg1;
|
||||
block_id.type = BlockId::Type::Normal;
|
||||
m_stack.push_back((u32)m_trace.size());
|
||||
m_trace.push_back(block_id);
|
||||
|
||||
block_id.address = arg2;
|
||||
block_id.type = BlockId::Type::Exit;
|
||||
m_trace.push_back(block_id);
|
||||
// arg2 is the address of the exit instruction.
|
||||
if (arg2) {
|
||||
m_stack.push_back(new ExecutionTrace(arg1));
|
||||
m_stack.back()->entries.push_back(ExecutionTraceEntry(ExecutionTraceEntry::Type::CompiledBlock, arg1, arg2));
|
||||
}
|
||||
break;
|
||||
case TraceType::Return:
|
||||
// No args used
|
||||
function = m_stack.back();
|
||||
execution_trace = m_stack.back();
|
||||
execution_trace->type = ExecutionTrace::Type::Linear;
|
||||
m_stack.pop_back();
|
||||
|
||||
execution_trace = new ExecutionTrace();
|
||||
execution_trace->type = ExecutionTrace::Type::Linear;
|
||||
execution_trace->function_address = m_trace[function].address;
|
||||
std::copy(m_trace.begin() + function, m_trace.end(), std::back_inserter(execution_trace->blocks));
|
||||
m_trace.erase(m_trace.begin() + function, m_trace.end());
|
||||
break;
|
||||
case TraceType::EnterBlock:
|
||||
// arg1 is address. Other args are not used.
|
||||
function = m_stack.back();
|
||||
for (int i = (int)m_trace.size() - 1; i >= function; i--) {
|
||||
if (m_trace[i].address == arg1 && m_trace[i].type == BlockId::Type::Normal) {
|
||||
// Found a loop within the current function
|
||||
execution_trace = new ExecutionTrace();
|
||||
execution_trace->type = ExecutionTrace::Type::Loop;
|
||||
execution_trace->function_address = m_trace[function].address;
|
||||
std::copy(m_trace.begin() + i, m_trace.end(), std::back_inserter(execution_trace->blocks));
|
||||
m_trace.erase(m_trace.begin() + i + 1, m_trace.end());
|
||||
case TraceType::Instruction:
|
||||
// arg1 is the address of the instruction
|
||||
for (int i = (int)m_stack.back()->entries.size() - 1; i >= 0; i--) {
|
||||
if ((m_stack.back()->entries[i].type == ExecutionTraceEntry::Type::Instruction && m_stack.back()->entries[i].instruction.address == arg1) ||
|
||||
(m_stack.back()->entries[i].type == ExecutionTraceEntry::Type::CompiledBlock && m_stack.back()->entries[i].compiled_block.entry_address == arg1)) {
|
||||
// Found a loop
|
||||
execution_trace = new ExecutionTrace(m_stack.back()->function_address);
|
||||
execution_trace->type = ExecutionTrace::Type::Loop;
|
||||
std::copy(m_stack.back()->entries.begin() + i, m_stack.back()->entries.end(), std::back_inserter(execution_trace->entries));
|
||||
m_stack.back()->entries.erase(m_stack.back()->entries.begin() + i + 1, m_stack.back()->entries.end());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!execution_trace) {
|
||||
// A loop was not found
|
||||
block_id.address = arg1;
|
||||
block_id.type = BlockId::Type::Normal;
|
||||
m_trace.push_back(block_id);
|
||||
m_stack.back()->entries.push_back(ExecutionTraceEntry(ExecutionTraceEntry::Type::Instruction, arg1));
|
||||
}
|
||||
break;
|
||||
case TraceType::ExitFromCompiledBlock:
|
||||
// arg1 is address of the exit block.
|
||||
block_id.address = arg1;
|
||||
block_id.type = BlockId::Type::Exit;
|
||||
m_trace.push_back(block_id);
|
||||
// arg1 is address of the compiled block.
|
||||
// arg2 is the address of the exit instruction.
|
||||
m_stack.back()->entries.push_back(ExecutionTraceEntry(ExecutionTraceEntry::Type::CompiledBlock, arg1, arg2));
|
||||
|
||||
if (arg1 == 0) {
|
||||
if (arg2 == 0) {
|
||||
// Return from function
|
||||
function = m_stack.back();
|
||||
execution_trace = m_stack.back();
|
||||
execution_trace->type = ExecutionTrace::Type::Linear;
|
||||
m_stack.pop_back();
|
||||
|
||||
execution_trace = new ExecutionTrace();
|
||||
execution_trace->type = ExecutionTrace::Type::Linear;
|
||||
execution_trace->function_address = m_trace[function].address;
|
||||
std::copy(m_trace.begin() + function, m_trace.end(), std::back_inserter(execution_trace->blocks));
|
||||
m_trace.erase(m_trace.begin() + function, m_trace.end());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
@ -5204,7 +5191,7 @@ ppu_recompiler_llvm::ExecutionEngine::~ExecutionEngine() {
|
|||
}
|
||||
|
||||
u8 ppu_recompiler_llvm::ExecutionEngine::DecodeMemory(const u32 address) {
|
||||
ExecuteFunction(this, &m_ppu, m_interpreter, &m_tracer);
|
||||
ExecuteFunction(&m_ppu, m_interpreter, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -5245,14 +5232,20 @@ Executable ppu_recompiler_llvm::ExecutionEngine::GetExecutable(u32 address, Exec
|
|||
return executable;
|
||||
}
|
||||
|
||||
u64 ppu_recompiler_llvm::ExecutionEngine::ExecuteFunction(ExecutionEngine * execution_engine, PPUThread * ppu_state, PPUInterpreter * interpreter, Tracer * tracer) {
|
||||
tracer->Trace(Tracer::TraceType::EnterFunction, 0, 0);
|
||||
return ExecuteTillReturn(execution_engine, ppu_state, interpreter, tracer);
|
||||
u32 ppu_recompiler_llvm::ExecutionEngine::ExecuteFunction(PPUThread * ppu_state, PPUInterpreter * interpreter, u64 context) {
|
||||
auto execution_engine = (ExecutionEngine *)ppu_state->GetDecoder();
|
||||
execution_engine->m_tracer.Trace(Tracer::TraceType::EnterFunction, ppu_state->PC, 0);
|
||||
return ExecuteTillReturn(ppu_state, interpreter, 0);
|
||||
}
|
||||
|
||||
u64 ppu_recompiler_llvm::ExecutionEngine::ExecuteTillReturn(ExecutionEngine * execution_engine, PPUThread * ppu_state, PPUInterpreter * interpreter, Tracer * tracer) {
|
||||
bool terminate = false;
|
||||
bool returned = true;
|
||||
u32 ppu_recompiler_llvm::ExecutionEngine::ExecuteTillReturn(PPUThread * ppu_state, PPUInterpreter * interpreter, u64 context) {
|
||||
auto execution_engine = (ExecutionEngine *)ppu_state->GetDecoder();
|
||||
auto terminate = false;
|
||||
auto branch_type = BranchType::NonBranch;
|
||||
|
||||
if (context) {
|
||||
execution_engine->m_tracer.Trace(Tracer::TraceType::ExitFromCompiledFunction, context >> 32, context & 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
while (!terminate && !Emu.IsStopped()) {
|
||||
if (Emu.IsPaused()) {
|
||||
|
@ -5260,68 +5253,45 @@ u64 ppu_recompiler_llvm::ExecutionEngine::ExecuteTillReturn(ExecutionEngine * ex
|
|||
continue;
|
||||
}
|
||||
|
||||
BranchType branch_type;
|
||||
if (!returned) {
|
||||
auto executable = execution_engine->GetExecutable(ppu_state->PC, ExecuteTillReturn);
|
||||
if (executable != ExecuteTillReturn) {
|
||||
auto entry = ppu_state->PC;
|
||||
auto exit = (u32)executable(ppu_state, interpreter, 0);
|
||||
execution_engine->m_tracer.Trace(Tracer::TraceType::ExitFromCompiledBlock, entry, exit);
|
||||
if (exit == 0) {
|
||||
terminate = true;
|
||||
}
|
||||
} else {
|
||||
execution_engine->m_tracer.Trace(Tracer::TraceType::Instruction, ppu_state->PC, 0);
|
||||
auto instruction = re32(vm::get_ref<u32>(ppu_state->PC));
|
||||
execution_engine->m_decoder.Decode(instruction);
|
||||
branch_type = ppu_state->m_is_branch ? GetBranchTypeFromInstruction(instruction) : BranchType::NonBranch;
|
||||
ppu_state->NextPc(4);
|
||||
} else {
|
||||
returned = false;
|
||||
branch_type = BranchType::LocalBranch;
|
||||
}
|
||||
|
||||
Executable executable;
|
||||
switch (branch_type) {
|
||||
case BranchType::Return:
|
||||
tracer->Trace(Tracer::TraceType::Return, 0, 0);
|
||||
terminate = true;
|
||||
break;
|
||||
case BranchType::FunctionCall:
|
||||
tracer->Trace(Tracer::TraceType::CallFunction, ppu_state->PC, 0);
|
||||
executable = execution_engine->GetExecutable(ppu_state->PC, ExecuteFunction);
|
||||
executable(execution_engine, ppu_state, interpreter, tracer);
|
||||
returned = true;
|
||||
break;
|
||||
case BranchType::LocalBranch:
|
||||
tracer->Trace(Tracer::TraceType::EnterBlock, ppu_state->PC, 0);
|
||||
executable = execution_engine->GetExecutable(ppu_state->PC, nullptr);
|
||||
if (executable != nullptr) {
|
||||
auto exit_block = executable(execution_engine, ppu_state, interpreter, tracer);
|
||||
tracer->Trace(Tracer::TraceType::ExitFromCompiledBlock, (u32)exit_block, 0);
|
||||
if (exit_block == 0) {
|
||||
terminate = true;
|
||||
}
|
||||
switch (branch_type) {
|
||||
case BranchType::Return:
|
||||
execution_engine->m_tracer.Trace(Tracer::TraceType::Return, 0, 0);
|
||||
terminate = true;
|
||||
break;
|
||||
case BranchType::FunctionCall:
|
||||
execution_engine->m_tracer.Trace(Tracer::TraceType::CallFunction, ppu_state->PC, 0);
|
||||
executable = execution_engine->GetExecutable(ppu_state->PC, ExecuteFunction);
|
||||
executable(ppu_state, interpreter, 0);
|
||||
break;
|
||||
case BranchType::LocalBranch:
|
||||
break;
|
||||
case BranchType::NonBranch:
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case BranchType::NonBranch:
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::string ppu_recompiler_llvm::ControlFlowGraphToString(const ControlFlowGraph & cfg) {
|
||||
std::string s;
|
||||
|
||||
for (auto i = cfg.begin(); i != cfg.end(); i++) {
|
||||
s += fmt::Format("0x%08X ->", i->first);
|
||||
for (auto j = i->second.begin(); j != i->second.end(); j++) {
|
||||
s += " " + j->ToString();
|
||||
}
|
||||
|
||||
if (i != (cfg.end() - 1)) {
|
||||
s += "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
BranchType ppu_recompiler_llvm::GetBranchTypeFromInstruction(u32 instruction) {
|
||||
auto type = BranchType::NonBranch;
|
||||
auto field1 = instruction >> 26;
|
||||
|
@ -5340,13 +5310,3 @@ BranchType ppu_recompiler_llvm::GetBranchTypeFromInstruction(u32 instruction) {
|
|||
|
||||
return type;
|
||||
}
|
||||
|
||||
ExecutionTraceId ppu_recompiler_llvm::GetExecutionTraceId(const ExecutionTrace & execution_trace) {
|
||||
ExecutionTraceId id = 0;
|
||||
|
||||
for (auto i = execution_trace.blocks.begin(); i != execution_trace.blocks.end(); i++) {
|
||||
id = (id << 8) ^ ((u64)i->address << 32 | _byteswap_ulong((u64)i->address));
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
|
|
@ -19,45 +19,106 @@ namespace ppu_recompiler_llvm {
|
|||
class ExecutionEngine;
|
||||
struct PPUState;
|
||||
|
||||
enum class BranchType {
|
||||
NonBranch,
|
||||
LocalBranch,
|
||||
FunctionCall,
|
||||
Return,
|
||||
};
|
||||
/// An entry in an execution trace
|
||||
struct ExecutionTraceEntry {
|
||||
/// Data associated with the entry. This is discriminated by type.
|
||||
union {
|
||||
struct Instruction {
|
||||
u32 address;
|
||||
} instruction;
|
||||
|
||||
/// Unique id of a block
|
||||
struct BlockId {
|
||||
/// Address of the block
|
||||
u32 address;
|
||||
struct FunctionCall {
|
||||
u32 address;
|
||||
} function_call;
|
||||
|
||||
/// The type of the block
|
||||
struct CompiledBlock {
|
||||
u32 entry_address;
|
||||
u32 exit_address;
|
||||
} compiled_block;
|
||||
};
|
||||
|
||||
/// The type of the entry
|
||||
enum class Type {
|
||||
FunctionCall,
|
||||
Normal,
|
||||
Exit,
|
||||
Instruction,
|
||||
CompiledBlock,
|
||||
} type;
|
||||
|
||||
bool operator == (const BlockId & other) const {
|
||||
return (address == other.address && type == other.type);
|
||||
ExecutionTraceEntry(Type type, u32 arg1, u32 arg2 = 0)
|
||||
: type(type) {
|
||||
switch (type) {
|
||||
case Type::Instruction:
|
||||
instruction.address = arg1;
|
||||
break;
|
||||
case Type::FunctionCall:
|
||||
function_call.address = arg1;
|
||||
break;
|
||||
case Type::CompiledBlock:
|
||||
compiled_block.entry_address = arg1;
|
||||
compiled_block.exit_address = arg2;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
u32 GetPrimaryAddress() const {
|
||||
switch (type) {
|
||||
case Type::Instruction:
|
||||
return instruction.address;
|
||||
case Type::FunctionCall:
|
||||
return function_call.address;
|
||||
case Type::CompiledBlock:
|
||||
return compiled_block.entry_address;
|
||||
default:
|
||||
assert(0);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::string ToString() const {
|
||||
return fmt::Format("%c:0x%08X", type == BlockId::Type::Normal ? 'N' : type == BlockId::Type::FunctionCall ? 'F' : 'E', address);
|
||||
switch (type) {
|
||||
case Type::Instruction:
|
||||
return fmt::Format("I:0x%08X", instruction.address);
|
||||
case Type::FunctionCall:
|
||||
return fmt::Format("F:0x%08X", function_call.address);
|
||||
case Type::CompiledBlock:
|
||||
return fmt::Format("C:0x%08X-0x%08X", compiled_block.entry_address, compiled_block.exit_address);
|
||||
default:
|
||||
assert(0);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
u64 hash() const {
|
||||
u64 hash = ((u64)type << 32);
|
||||
switch (type) {
|
||||
case Type::Instruction:
|
||||
hash |= instruction.address;
|
||||
break;
|
||||
case Type::FunctionCall:
|
||||
hash |= function_call.address;
|
||||
break;
|
||||
case Type::CompiledBlock:
|
||||
hash = compiled_block.exit_address;
|
||||
hash <<= 32;
|
||||
hash |= compiled_block.entry_address;
|
||||
break;
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
|
||||
return hash;
|
||||
}
|
||||
};
|
||||
|
||||
/// Control flow graph of a block. A list of (block address, list of next blocks) pairs.
|
||||
typedef std::vector<std::pair<u32, std::vector<BlockId>>> ControlFlowGraph;
|
||||
|
||||
/// Get a string representation of a ControlFlowGraph
|
||||
std::string ControlFlowGraphToString(const ControlFlowGraph & cfg);
|
||||
|
||||
/// Uniquely identifies an execution trace
|
||||
typedef u64 ExecutionTraceId;
|
||||
|
||||
/// An execution trace.
|
||||
struct ExecutionTrace {
|
||||
/// Unique id of an execution trace;
|
||||
typedef u64 Id;
|
||||
|
||||
/// The function to which this trace belongs
|
||||
u32 function_address;
|
||||
|
||||
|
@ -67,27 +128,98 @@ namespace ppu_recompiler_llvm {
|
|||
Loop,
|
||||
} type;
|
||||
|
||||
/// Sequence of blocks enountered in this trace
|
||||
std::vector<BlockId> blocks;
|
||||
/// entries in the trace
|
||||
std::vector<ExecutionTraceEntry> entries;
|
||||
|
||||
ExecutionTrace(u32 address)
|
||||
: function_address(address) {
|
||||
}
|
||||
|
||||
std::string ToString() const {
|
||||
auto s = fmt::Format("0x%08X %s ->", function_address, type == ExecutionTrace::Type::Loop ? "Loop" : "Linear");
|
||||
for (auto i = 0; i < blocks.size(); i++) {
|
||||
s += " " + blocks[i].ToString();
|
||||
for (auto i = 0; i < entries.size(); i++) {
|
||||
s += " " + entries[i].ToString();
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
Id GetId() const {
|
||||
Id id = 0;
|
||||
|
||||
for (auto i = entries.begin(); i != entries.end(); i++) {
|
||||
id ^= i->hash();
|
||||
id <<= 1;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
};
|
||||
|
||||
/// A control flow graph
|
||||
struct ControlFlowGraph {
|
||||
/// Address of the first instruction
|
||||
u32 start_address;
|
||||
|
||||
/// Address of the function to which this CFG belongs to
|
||||
u32 function_address;
|
||||
|
||||
/// Set of addresses of the instructions in the CFG
|
||||
std::set<u32> instruction_addresses;
|
||||
|
||||
/// Branches in the CFG.
|
||||
/// Key is the address of an instruction
|
||||
/// Data is the set of all instructions to which this instruction branches to.
|
||||
std::map<u32, std::set<u32>> branches;
|
||||
|
||||
/// Function calls in the CFG
|
||||
/// Key is the address of an instruction
|
||||
/// Data is the set of all functions which this instruction invokes.
|
||||
std::map<u32, std::set<u32>> calls;
|
||||
|
||||
ControlFlowGraph(u32 start_address, u32 function_address)
|
||||
: start_address(start_address)
|
||||
, function_address(function_address) {
|
||||
}
|
||||
|
||||
std::string ToString() const {
|
||||
auto s = fmt::Format("0x%08X (0x%08X):", start_address, function_address);
|
||||
for (auto i = instruction_addresses.begin(); i != instruction_addresses.end(); i++) {
|
||||
s += fmt::Format(" 0x%08X", *i);
|
||||
}
|
||||
|
||||
s += "\nBranches:";
|
||||
for (auto i = branches.begin(); i != branches.end(); i++) {
|
||||
s += fmt::Format("\n0x%08X ->", i->first);
|
||||
for (auto j = i->second.begin(); j != i->second.end(); j++) {
|
||||
s += fmt::Format(" 0x%08X", *j);
|
||||
}
|
||||
}
|
||||
|
||||
s += "\nCalls:";
|
||||
for (auto i = calls.begin(); i != calls.end(); i++) {
|
||||
s += fmt::Format("\n0x%08X ->", i->first);
|
||||
for (auto j = i->second.begin(); j != i->second.end(); j++) {
|
||||
s += fmt::Format(" 0x%08X", *j);
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
};
|
||||
|
||||
enum class BranchType {
|
||||
NonBranch,
|
||||
LocalBranch,
|
||||
FunctionCall,
|
||||
Return,
|
||||
};
|
||||
|
||||
/// Pointer to an executable
|
||||
typedef u64(*Executable)(ExecutionEngine * execution_engine, PPUThread * ppu_state, PPUInterpreter * interpreter, Tracer * tracer);
|
||||
typedef u32(*Executable)(PPUThread * ppu_state, PPUInterpreter * interpreter, u64 context);
|
||||
|
||||
/// An entry in the block table
|
||||
struct BlockEntry {
|
||||
/// Address of the block
|
||||
u32 address;
|
||||
|
||||
/// Number of times this block was hit
|
||||
u32 num_hits;
|
||||
|
||||
|
@ -100,23 +232,19 @@ namespace ppu_recompiler_llvm {
|
|||
/// Indicates whether the block has been compiled or not
|
||||
bool is_compiled;
|
||||
|
||||
/// Indicates whether the block is the first block of a function or not
|
||||
bool is_function_start;
|
||||
|
||||
BlockEntry(u32 addr)
|
||||
: address(addr)
|
||||
, num_hits(0)
|
||||
BlockEntry(u32 start_address, u32 function_address)
|
||||
: num_hits(0)
|
||||
, revision(0)
|
||||
, is_compiled(false) {
|
||||
, is_compiled(false)
|
||||
, cfg(start_address, function_address) {
|
||||
}
|
||||
|
||||
std::string ToString() const {
|
||||
return fmt::Format("%c:0x%08X, NumHits=%u, IsCompiled=%c\n%s", is_function_start ? 'F' : 'N', address, num_hits,
|
||||
is_compiled ? 'Y' : 'N', ControlFlowGraphToString(cfg).c_str());
|
||||
return fmt::Format("%s\nNumHits=%u, Revision=%u, IsCompiled=%c\n", cfg.ToString().c_str(), num_hits, revision, is_compiled ? 'Y' : 'N');
|
||||
}
|
||||
|
||||
bool operator == (const BlockEntry & other) const {
|
||||
return address == other.address;
|
||||
return cfg.start_address == other.cfg.start_address;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -124,7 +252,7 @@ namespace ppu_recompiler_llvm {
|
|||
namespace std {
|
||||
template<> struct hash<ppu_recompiler_llvm::BlockEntry *> {
|
||||
size_t operator()(const ppu_recompiler_llvm::BlockEntry * e) const {
|
||||
return e->address;
|
||||
return e->cfg.start_address;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -150,7 +278,7 @@ namespace ppu_recompiler_llvm {
|
|||
std::map<std::string, u64> interpreter_fallback_stats;
|
||||
};
|
||||
|
||||
Compiler(RecompilationEngine & recompilation_engine, const Executable default_function_executable, const Executable default_block_executable);
|
||||
Compiler(RecompilationEngine & recompilation_engine, const Executable unknown_function, const Executable unknown_block);
|
||||
|
||||
Compiler(const Compiler & other) = delete;
|
||||
Compiler(Compiler && other) = delete;
|
||||
|
@ -161,7 +289,7 @@ namespace ppu_recompiler_llvm {
|
|||
Compiler & operator = (Compiler && other) = delete;
|
||||
|
||||
/// Compile a code fragment described by a cfg and return an executable
|
||||
Executable Compile(const std::string & name, const ControlFlowGraph & cfg, bool inline_all_blocks, bool generate_linkable_exits, bool generate_trace);
|
||||
Executable Compile(const std::string & name, const ControlFlowGraph & cfg, bool inline_all_blocks, bool generate_linkable_exits);
|
||||
|
||||
/// Free an executable earilier obtained via a call to Compile
|
||||
void FreeExecutable(const std::string & name);
|
||||
|
@ -579,10 +707,9 @@ namespace ppu_recompiler_llvm {
|
|||
/// State of a compilation task
|
||||
struct CompileTaskState {
|
||||
enum Args {
|
||||
ExecutionEngine,
|
||||
State,
|
||||
Interpreter,
|
||||
Tracer,
|
||||
Context,
|
||||
MaxArgs,
|
||||
};
|
||||
|
||||
|
@ -595,38 +722,29 @@ namespace ppu_recompiler_llvm {
|
|||
/// The CFG being compiled
|
||||
const ControlFlowGraph * cfg;
|
||||
|
||||
/// The current entry of the CFG being compiled
|
||||
ControlFlowGraph::const_iterator cfg_entry;
|
||||
|
||||
/// Address of the current instruction being compiled
|
||||
u32 current_instruction_address;
|
||||
|
||||
/// Map from an address to the address of the block that it belongs to
|
||||
std::unordered_map<u32, u32> address_to_block;
|
||||
|
||||
/// A flag used to detect branch instructions.
|
||||
/// This is set to false at the start of compilation of a block.
|
||||
/// When a branch instruction is encountered, this is set to true by the decode function.
|
||||
/// This is set to false at the start of compilation of an instruction.
|
||||
/// If a branch instruction is encountered, this is set to true by the decode function.
|
||||
bool hit_branch_instruction;
|
||||
|
||||
/// Indicates whether a block should be inlined even if an already compiled version of the block exists
|
||||
bool inline_all_blocks;
|
||||
bool inline_all;
|
||||
|
||||
/// Create code such that exit points can be linked to other blocks
|
||||
bool generate_linkable_exits;
|
||||
|
||||
/// Notify the tracer upon exit
|
||||
bool generate_trace;
|
||||
};
|
||||
|
||||
/// Recompilation engine
|
||||
RecompilationEngine & m_recompilation_engine;
|
||||
|
||||
/// The executable that will be called to process unknown functions
|
||||
const Executable m_default_function_executable;
|
||||
/// The function that will be called to process unknown functions
|
||||
llvm::Function * m_unknown_function;
|
||||
|
||||
/// The executable that will be called to process unknown blocks
|
||||
const Executable m_default_block_executable;
|
||||
llvm::Function * m_unknown_block;
|
||||
|
||||
/// LLVM context
|
||||
llvm::LLVMContext * m_llvm_context;
|
||||
|
@ -653,10 +771,10 @@ namespace ppu_recompiler_llvm {
|
|||
Stats m_stats;
|
||||
|
||||
/// Get the name of the basic block for the specified address
|
||||
std::string GetBasicBlockNameFromAddress(u32 address, const std::string & suffix = "");
|
||||
std::string GetBasicBlockNameFromAddress(u32 address, const std::string & suffix = "") const;
|
||||
|
||||
/// Get the address of a basic block from its name
|
||||
u32 GetAddressFromBasicBlockName(const std::string & name);
|
||||
u32 GetAddressFromBasicBlockName(const std::string & name) const;
|
||||
|
||||
/// Get the basic block in for the specified address.
|
||||
llvm::BasicBlock * GetBasicBlockFromAddress(u32 address, const std::string & suffix = "", bool create_if_not_exist = true);
|
||||
|
@ -785,7 +903,7 @@ namespace ppu_recompiler_llvm {
|
|||
llvm::Value * CheckBranchCondition(u32 bo, u32 bi);
|
||||
|
||||
/// Create IR for a branch instruction
|
||||
void CreateBranch(llvm::Value * cmp_i1, llvm::Value * target_i64, bool lk, bool target_is_lr = false);
|
||||
void CreateBranch(llvm::Value * cmp_i1, llvm::Value * target_i32, bool lk, bool target_is_lr = false);
|
||||
|
||||
/// Read from memory
|
||||
llvm::Value * ReadMemory(llvm::Value * addr_i64, u32 bits, u32 alignment = 0, bool bswap = true, bool could_be_mmio = true);
|
||||
|
@ -806,7 +924,7 @@ namespace ppu_recompiler_llvm {
|
|||
llvm::Value * Call(const char * name, Func function, Args... args);
|
||||
|
||||
/// Indirect call
|
||||
llvm::Value * IndirectCall(u32 address, bool is_function);
|
||||
llvm::Value * IndirectCall(u32 address, llvm::Value * context_i64, bool is_function);
|
||||
|
||||
/// Test an instruction against the interpreter
|
||||
template <class PPULLVMRecompilerFn, class PPUInterpreterFn, class... Args>
|
||||
|
@ -863,7 +981,7 @@ namespace ppu_recompiler_llvm {
|
|||
std::unordered_set<BlockEntry *> m_block_table;
|
||||
|
||||
/// Execution traces that have been already encountered. Data is the list of all blocks that this trace includes.
|
||||
std::unordered_map<ExecutionTraceId, std::vector<BlockEntry *>> m_processed_execution_traces;
|
||||
std::unordered_map<ExecutionTrace::Id, std::vector<BlockEntry *>> m_processed_execution_traces;
|
||||
|
||||
/// Lock for accessing m_address_to_ordinal.
|
||||
// TODO: Make this a RW lock
|
||||
|
@ -896,7 +1014,7 @@ namespace ppu_recompiler_llvm {
|
|||
void ProcessExecutionTrace(const ExecutionTrace & execution_trace);
|
||||
|
||||
/// Update a CFG
|
||||
void UpdateControlFlowGraph(ControlFlowGraph & cfg, BlockId block, BlockId next_block);
|
||||
void UpdateControlFlowGraph(ControlFlowGraph & cfg, const ExecutionTraceEntry & this_entry, const ExecutionTraceEntry * next_entry);
|
||||
|
||||
/// Compile a block
|
||||
void CompileBlock(BlockEntry & block_entry, bool inline_referenced_blocks);
|
||||
|
@ -917,7 +1035,7 @@ namespace ppu_recompiler_llvm {
|
|||
EnterFunction,
|
||||
ExitFromCompiledFunction,
|
||||
Return,
|
||||
EnterBlock,
|
||||
Instruction,
|
||||
ExitFromCompiledBlock,
|
||||
};
|
||||
|
||||
|
@ -938,11 +1056,8 @@ namespace ppu_recompiler_llvm {
|
|||
void Terminate();
|
||||
|
||||
private:
|
||||
/// Current execution trace
|
||||
std::vector<BlockId> m_trace;
|
||||
|
||||
/// Call stack
|
||||
std::vector<u32> m_stack;
|
||||
std::vector<ExecutionTrace *> m_stack;
|
||||
|
||||
/// Recompilation engine
|
||||
std::shared_ptr<RecompilationEngine> m_recompilation_engine;
|
||||
|
@ -994,17 +1109,14 @@ namespace ppu_recompiler_llvm {
|
|||
Executable GetExecutable(u32 address, Executable default_executable) const;
|
||||
|
||||
/// Execute a function
|
||||
static u64 ExecuteFunction(ExecutionEngine * execution_engine, PPUThread * ppu_state, PPUInterpreter * interpreter, Tracer * tracer);
|
||||
static u32 ExecuteFunction(PPUThread * ppu_state, PPUInterpreter * interpreter, u64 context);
|
||||
|
||||
/// Execute till the current function returns
|
||||
static u64 ExecuteTillReturn(ExecutionEngine * execution_engine, PPUThread * ppu_state, PPUInterpreter * interpreter, Tracer * tracer);
|
||||
static u32 ExecuteTillReturn(PPUThread * ppu_state, PPUInterpreter * interpreter, u64 context);
|
||||
};
|
||||
|
||||
/// Get the branch type from a branch instruction
|
||||
BranchType GetBranchTypeFromInstruction(u32 instruction);
|
||||
|
||||
/// Get the execution trace id of an execution trace
|
||||
ExecutionTraceId GetExecutionTraceId(const ExecutionTrace & execution_trace);
|
||||
}
|
||||
|
||||
#endif // PPU_LLVM_RECOMPILER_H
|
||||
|
|
Loading…
Add table
Reference in a new issue