Construct CFG from execution traces

This commit is contained in:
S Gopal Rajagopal 2014-11-05 01:01:20 +05:30
parent 7846fa5829
commit 7c3c5ae08e
3 changed files with 354 additions and 279 deletions

View File

@ -76,124 +76,124 @@ Compiler::~Compiler() {
delete m_llvm_context; delete m_llvm_context;
} }
Executable Compiler::Compile(const std::string & name, const CodeFragment & code_fragment) { //Executable Compiler::Compile(const std::string & name, const CodeFragment & code_fragment) {
assert(!name.empty()); // assert(!name.empty());
assert(!code_fragment.empty()); // assert(!code_fragment.empty());
//
auto compilation_start = std::chrono::high_resolution_clock::now(); // auto compilation_start = std::chrono::high_resolution_clock::now();
//
// Create the function // // Create the function
m_current_function = (Function *)m_module->getOrInsertFunction(name, m_ir_builder->getVoidTy(), // m_current_function = (Function *)m_module->getOrInsertFunction(name, m_ir_builder->getVoidTy(),
m_ir_builder->getInt8PtrTy() /*ppu_state*/, // m_ir_builder->getInt8PtrTy() /*ppu_state*/,
m_ir_builder->getInt8PtrTy() /*interpreter*/, // m_ir_builder->getInt8PtrTy() /*interpreter*/,
m_ir_builder->getInt8PtrTy() /*tracer*/, nullptr); // m_ir_builder->getInt8PtrTy() /*tracer*/, nullptr);
m_current_function->setCallingConv(CallingConv::X86_64_Win64); // m_current_function->setCallingConv(CallingConv::X86_64_Win64);
auto arg_i = m_current_function->arg_begin(); // auto arg_i = m_current_function->arg_begin();
arg_i->setName("ppu_state"); // arg_i->setName("ppu_state");
(++arg_i)->setName("interpreter"); // (++arg_i)->setName("interpreter");
(++arg_i)->setName("tracer"); // (++arg_i)->setName("tracer");
//
// Create the entry block // // Create the entry block
GetBasicBlockFromAddress(0, m_current_function, true); // GetBasicBlockFromAddress(0, m_current_function, true);
//
// Create basic blocks for each instruction // // Create basic blocks for each instruction
for (auto i = code_fragment.begin(); i != code_fragment.end(); i++) { // for (auto i = code_fragment.begin(); i != code_fragment.end(); i++) {
u32 address = i->first.address; // u32 address = i->first.address;
while (1) { // while (1) {
GetBasicBlockFromAddress(address, m_current_function, true); // GetBasicBlockFromAddress(address, m_current_function, true);
//
u32 instr = vm::read32(address); // u32 instr = vm::read32(address);
if (IsBranchInstruction(instr)) { // if (IsBranchInstruction(instr)) {
break; // break;
} // }
//
address += 4; // address += 4;
} // }
} // }
//
// Add code to notify the tracer about this function and branch to the first instruction // // Add code to notify the tracer about this function and branch to the first instruction
m_ir_builder->SetInsertPoint(GetBasicBlockFromAddress(0, m_current_function)); // m_ir_builder->SetInsertPoint(GetBasicBlockFromAddress(0, m_current_function));
//Call<void>("Tracer.Trace", &Tracer::Trace, *arg_i, // //Call<void>("Tracer.Trace", &Tracer::Trace, *arg_i,
// m_ir_builder->getInt32(code_fragment[0].first.type == Function ? FunctionCall : Block), // // m_ir_builder->getInt32(code_fragment[0].first.type == Function ? FunctionCall : Block),
// m_ir_builder->getInt32(code_fragment[0].first.address)); // // m_ir_builder->getInt32(code_fragment[0].first.address));
m_ir_builder->CreateBr(GetBasicBlockFromAddress(code_fragment[0].first.address, m_current_function)); // m_ir_builder->CreateBr(GetBasicBlockFromAddress(code_fragment[0].first.address, m_current_function));
//
// Convert each block in this code fragment to LLVM IR // // Convert each block in this code fragment to LLVM IR
for (auto i = code_fragment.begin(); i != code_fragment.end(); i++) { // for (auto i = code_fragment.begin(); i != code_fragment.end(); i++) {
m_current_instruction_address = i->first.address; // m_current_instruction_address = i->first.address;
m_current_block_next_blocks = &(i->second); // m_current_block_next_blocks = &(i->second);
auto block = GetBasicBlockFromAddress(m_current_instruction_address, m_current_function); // auto block = GetBasicBlockFromAddress(m_current_instruction_address, m_current_function);
m_ir_builder->SetInsertPoint(block); // m_ir_builder->SetInsertPoint(block);
//
if (i != code_fragment.begin() && i->first.type == BlockId::Type::FunctionCall) { // if (i != code_fragment.begin() && i->first.type == BlockId::Type::FunctionCall) {
auto ordinal = RecompilationEngine::GetInstance()->GetOrdinal(i->first.address); // auto ordinal = RecompilationEngine::GetInstance()->GetOrdinal(i->first.address);
//
} // }
//
m_hit_branch_instruction = false; // m_hit_branch_instruction = false;
while (!m_hit_branch_instruction) { // while (!m_hit_branch_instruction) {
if (!block->getInstList().empty()) { // if (!block->getInstList().empty()) {
break; // break;
} // }
//
u32 instr = vm::read32(m_current_instruction_address); // u32 instr = vm::read32(m_current_instruction_address);
Decode(instr); // Decode(instr);
//
m_current_instruction_address += 4; // m_current_instruction_address += 4;
if (!m_hit_branch_instruction) { // if (!m_hit_branch_instruction) {
block = GetBasicBlockFromAddress(m_current_instruction_address, m_current_function); // block = GetBasicBlockFromAddress(m_current_instruction_address, m_current_function);
m_ir_builder->CreateBr(block); // m_ir_builder->CreateBr(block);
m_ir_builder->SetInsertPoint(block); // m_ir_builder->SetInsertPoint(block);
} // }
} // }
} // }
//
// If the function has an unknown block then add code to notify the tracer // // If the function has an unknown block then add code to notify the tracer
auto unknown_bb = GetBasicBlockFromAddress(0xFFFFFFFF, m_current_function); // auto unknown_bb = GetBasicBlockFromAddress(0xFFFFFFFF, m_current_function);
if (!unknown_bb) { // if (!unknown_bb) {
m_ir_builder->SetInsertPoint(unknown_bb); // m_ir_builder->SetInsertPoint(unknown_bb);
auto branch_type_i32 = m_ir_builder->CreatePHI(m_ir_builder->getInt32Ty(), 1); // auto branch_type_i32 = m_ir_builder->CreatePHI(m_ir_builder->getInt32Ty(), 1);
for (auto i = pred_begin(unknown_bb); i != pred_end(unknown_bb); i++) { // for (auto i = pred_begin(unknown_bb); i != pred_end(unknown_bb); i++) {
// We assume that the last but one instruction of the predecessor sets the branch type // // We assume that the last but one instruction of the predecessor sets the branch type
auto j = (*i)->rbegin(); // auto j = (*i)->rbegin();
j--; // j--;
branch_type_i32->addIncoming(&(*j), *i); // branch_type_i32->addIncoming(&(*j), *i);
} // }
//
//Call<void>("NotifyBranch", &Tracer::NotifyBranch, *arg_i, // //Call<void>("NotifyBranch", &Tracer::NotifyBranch, *arg_i,
// m_ir_builder->CreateZExtOrTrunc(branch_type_i32, m_ir_builder->getIntNTy(sizeof(Tracer::BranchType) * 8)), GetPc()); // // m_ir_builder->CreateZExtOrTrunc(branch_type_i32, m_ir_builder->getIntNTy(sizeof(Tracer::BranchType) * 8)), GetPc());
m_ir_builder->CreateRetVoid(); // m_ir_builder->CreateRetVoid();
} // }
//
auto ir_build_end = std::chrono::high_resolution_clock::now(); // auto ir_build_end = std::chrono::high_resolution_clock::now();
m_stats.ir_build_time += std::chrono::duration_cast<std::chrono::nanoseconds>(ir_build_end - compilation_start); // m_stats.ir_build_time += std::chrono::duration_cast<std::chrono::nanoseconds>(ir_build_end - compilation_start);
//
// Optimize this function // // Optimize this function
m_fpm->run(*m_current_function); // m_fpm->run(*m_current_function);
auto optimize_end = std::chrono::high_resolution_clock::now(); // auto optimize_end = std::chrono::high_resolution_clock::now();
m_stats.optimization_time += std::chrono::duration_cast<std::chrono::nanoseconds>(optimize_end - ir_build_end); // m_stats.optimization_time += std::chrono::duration_cast<std::chrono::nanoseconds>(optimize_end - ir_build_end);
//
// Translate to machine code // // Translate to machine code
MachineCodeInfo mci; // MachineCodeInfo mci;
m_execution_engine->runJITOnFunction(m_current_function, &mci); // m_execution_engine->runJITOnFunction(m_current_function, &mci);
auto translate_end = std::chrono::high_resolution_clock::now(); // auto translate_end = std::chrono::high_resolution_clock::now();
m_stats.translation_time += std::chrono::duration_cast<std::chrono::nanoseconds>(translate_end - optimize_end); // m_stats.translation_time += std::chrono::duration_cast<std::chrono::nanoseconds>(translate_end - optimize_end);
//
auto compilation_end = std::chrono::high_resolution_clock::now(); // auto compilation_end = std::chrono::high_resolution_clock::now();
m_stats.total_time += std::chrono::duration_cast<std::chrono::nanoseconds>(compilation_end - compilation_start); // m_stats.total_time += std::chrono::duration_cast<std::chrono::nanoseconds>(compilation_end - compilation_start);
//
//m_compiled[(CompiledCodeFragment)mci.address()] = m_current_function; // //m_compiled[(CompiledCodeFragment)mci.address()] = m_current_function;
//return (CompiledCodeFragment)mci.address(); // //return (CompiledCodeFragment)mci.address();
return nullptr; // return nullptr;
} //}
void Compiler::FreeCompiledCodeFragment(Executable compiled_code_fragment) { //void Compiler::FreeCompiledCodeFragment(Executable compiled_code_fragment) {
//auto i = m_compiled.find(compiled_code_fragment); // //auto i = m_compiled.find(compiled_code_fragment);
//if (i != m_compiled.end()) { // //if (i != m_compiled.end()) {
// m_execution_engine->freeMachineCodeForFunction(i->second); // // m_execution_engine->freeMachineCodeForFunction(i->second);
// i->second->eraseFromParent(); // // i->second->eraseFromParent();
// //}
//} //}
}
Compiler::Stats Compiler::GetStats() { Compiler::Stats Compiler::GetStats() {
return m_stats; return m_stats;
@ -4748,18 +4748,7 @@ void Compiler::InitRotateMask() {
} }
std::mutex RecompilationEngine::s_mutex; std::mutex RecompilationEngine::s_mutex;
RecompilationEngine * RecompilationEngine::s_the_instance; std::shared_ptr<RecompilationEngine> RecompilationEngine::s_the_instance = nullptr;
RecompilationEngine::BlockEntry::BlockEntry()
: num_hits(0)
, is_compiled(false) {
}
RecompilationEngine::BlockEntry::~BlockEntry() {
for (auto i = execution_traces.begin(); i != execution_traces.end(); i++) {
delete i->second;
}
}
RecompilationEngine::RecompilationEngine() RecompilationEngine::RecompilationEngine()
: ThreadBase("PPU Recompilation Engine") { : ThreadBase("PPU Recompilation Engine") {
@ -4815,10 +4804,7 @@ void RecompilationEngine::Task() {
} }
} }
auto block_i = ProcessExecutionTrace(execution_trace); ProcessExecutionTrace(*execution_trace);
if (block_i != m_block_table.end()) {
CompileBlock(block_i);
}
} }
// TODO: Reduce the priority of the recompilation engine thread // TODO: Reduce the priority of the recompilation engine thread
@ -4867,77 +4853,112 @@ void RecompilationEngine::Task() {
//log_file << "\nLLVM IR:\n" << *m_module; //log_file << "\nLLVM IR:\n" << *m_module;
LOG_NOTICE(PPU, "PPU LLVM Recompilation thread exiting."); LOG_NOTICE(PPU, "PPU LLVM Recompilation thread exiting.");
s_the_instance = nullptr; // Can cause deadlock if this is the last instance. Need to fix this.
} }
RecompilationEngine::BlockTable::iterator RecompilationEngine::ProcessExecutionTrace(ExecutionTrace * execution_trace) { void RecompilationEngine::ProcessExecutionTrace(const ExecutionTrace & execution_trace) {
auto block_i = m_block_table.find(execution_trace->blocks[0].address);
if (block_i == m_block_table.end()) {
// New block
block_i = m_block_table.insert(m_block_table.end(), std::make_pair(execution_trace->blocks[0].address, BlockEntry()));
}
block_i->second.num_hits++;
auto execution_trace_id = GetExecutionTraceId(execution_trace); auto execution_trace_id = GetExecutionTraceId(execution_trace);
auto execution_trace_i = block_i->second.execution_traces.find(execution_trace_id); auto processed_execution_trace_i = m_processed_execution_traces.find(execution_trace_id);
if (execution_trace_i == block_i->second.execution_traces.end()) { if (processed_execution_trace_i == m_processed_execution_traces.end()) {
block_i->second.execution_traces.insert(std::make_pair(execution_trace_id, execution_trace)); std::vector<BlockEntry *> tmp_block_list;
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) {
block_i = m_block_table.end();
split_trace = true;
} else if (block_i == m_block_table.end()) {
BlockEntry key(trace_block_i->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));
} }
if (!block_i->second.is_compiled && block_i->second.num_hits > 1000) { // TODO: Make threshold configurable (*block_i)->is_function_start = key.address == execution_trace.function_address;
return block_i; tmp_block_list.push_back(*block_i);
} }
return m_block_table.end(); 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;
}
} }
void RecompilationEngine::CompileBlock(BlockTable::iterator block_i) { UpdateControlFlowGraph((*block_i)->cfg, *trace_block_i, next_block);
auto code_fragment = BuildCodeFragmentFromBlock(block_i->second, false); }
} }
CodeFragment RecompilationEngine::BuildCodeFragmentFromBlock(const BlockEntry & block_entry, bool force_inline) { 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)));
CodeFragment code_fragment; }
//std::vector<const BlockEntry *> queue;
//queue.push_back(&block_entry); for (auto i = processed_execution_trace_i->second.begin(); i != processed_execution_trace_i->second.end(); i++) {
//for (auto q = queue.begin(); q != queue.end(); q++) { if (!(*i)->is_compiled) {
// for (auto i = (*q)->execution_traces.begin(); i != (*q)->execution_traces.end(); i++) { (*i)->num_hits++;
// for (auto j = i->second->blocks.begin(); j != i->second->blocks.end(); j++) { if ((*i)->num_hits >= 1) { // TODO: Make this configurable
// auto k = std::find_if(code_fragment.begin(), code_fragment.end(), CompileBlock(*(*i), false);
// [&j](const CodeFragment::value_type & v)->bool { return v.first.address == j->address; }); (*i)->is_compiled = true;
// if (k == code_fragment.end()) { }
// code_fragment.push_back(std::make_pair(*j, std::vector<BlockId>())); }
// k = code_fragment.end() - 1; }
// }
// if ((j + 1) != i->second->blocks.end()) { std::remove_if(processed_execution_trace_i->second.begin(), processed_execution_trace_i->second.end(), [](const BlockEntry * b)->bool { return b->is_compiled; });
// auto l = std::find(k->second.begin(), k->second.end(), *(j + 1)); }
// if (l == k->second.end()) {
// k->second.push_back(*(j + 1));
// }
// }
// if (force_inline && j->type == BlockId::Type::Normal) { void RecompilationEngine::UpdateControlFlowGraph(ControlFlowGraph & cfg, BlockId block, BlockId next_block) {
// auto block_i = m_block_table.find(j->address); if (block.type == BlockId::Type::Exit && next_block.type == BlockId::Type::Exit) {
// if (block_i != m_block_table.end()) { return;
// if (std::find(queue.begin(), queue.end(), block_i->second) == queue.end()) { }
// queue.push_back(&(block_i->second));
// }
// }
// }
// }
// }
//}
return code_fragment; 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);
}
}
}
void RecompilationEngine::CompileBlock(const BlockEntry & block_entry, bool inline_referenced_blocks) {
std::string cfg_str;
for (auto i = block_entry.cfg.begin(); i != block_entry.cfg.end(); i++) {
cfg_str += fmt::Format("0x%08X ->", i->first);
for (auto j = i->second.begin(); j != i->second.end(); j++) {
cfg_str += " " + j->ToString();
}
if (i != (block_entry.cfg.end() - 1)) {
cfg_str += "\n";
}
}
LOG_NOTICE(PPU, "Compile: %c:0x%08X, NumHits=%u\n%s", block_entry.is_function_start ? 'F' : 'N', block_entry.address, block_entry.num_hits, cfg_str.c_str());
} }
std::shared_ptr<RecompilationEngine> RecompilationEngine::GetInstance() { std::shared_ptr<RecompilationEngine> RecompilationEngine::GetInstance() {
if (s_the_instance == nullptr) {
std::lock_guard<std::mutex> lock(s_mutex); std::lock_guard<std::mutex> lock(s_mutex);
s_the_instance = new RecompilationEngine();
if (s_the_instance == nullptr) {
s_the_instance = std::shared_ptr<RecompilationEngine>(new RecompilationEngine());
} }
return std::shared_ptr<RecompilationEngine>(s_the_instance); return s_the_instance;
} }
Tracer::Tracer() Tracer::Tracer()
@ -4989,7 +5010,6 @@ void Tracer::Trace(TraceType trace_type, u32 arg1, u32 arg2) {
execution_trace = new ExecutionTrace(); execution_trace = new ExecutionTrace();
execution_trace->type = ExecutionTrace::Type::Linear; execution_trace->type = ExecutionTrace::Type::Linear;
execution_trace->function_address = m_trace[function].address; execution_trace->function_address = m_trace[function].address;
execution_trace->previous_block_address = 0;
std::copy(m_trace.begin() + function, m_trace.end(), std::back_inserter(execution_trace->blocks)); 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()); m_trace.erase(m_trace.begin() + function, m_trace.end());
break; break;
@ -5002,7 +5022,6 @@ void Tracer::Trace(TraceType trace_type, u32 arg1, u32 arg2) {
execution_trace = new ExecutionTrace(); execution_trace = new ExecutionTrace();
execution_trace->type = ExecutionTrace::Type::Loop; execution_trace->type = ExecutionTrace::Type::Loop;
execution_trace->function_address = m_trace[function].address; execution_trace->function_address = m_trace[function].address;
execution_trace->previous_block_address = i == function ? 0 : m_trace[i - 1].address;
std::copy(m_trace.begin() + i, m_trace.end(), std::back_inserter(execution_trace->blocks)); 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()); m_trace.erase(m_trace.begin() + i + 1, m_trace.end());
break; break;
@ -5021,6 +5040,18 @@ void Tracer::Trace(TraceType trace_type, u32 arg1, u32 arg2) {
block_id.address = arg1; block_id.address = arg1;
block_id.type = BlockId::Type::Exit; block_id.type = BlockId::Type::Exit;
m_trace.push_back(block_id); m_trace.push_back(block_id);
if (arg1 == 0) {
// Return from function
function = m_stack.back();
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; break;
default: default:
assert(0); assert(0);
@ -5028,17 +5059,8 @@ void Tracer::Trace(TraceType trace_type, u32 arg1, u32 arg2) {
} }
if (execution_trace) { if (execution_trace) {
auto s = fmt::Format("Trace: 0x%08X, 0x%08X, %s -> ", execution_trace->function_address, execution_trace->previous_block_address, LOG_NOTICE(PPU, "Trace: %s", execution_trace->ToString().c_str());
execution_trace->type == ExecutionTrace::Type::Loop ? "Loop" : "Linear"); m_recompilation_engine->NotifyTrace(execution_trace);
for (auto i = 0; i < execution_trace->blocks.size(); i++) {;
s += fmt::Format("%c:0x%08X ",
execution_trace->blocks[i].type == BlockId::Type::Normal ? 'N' :
execution_trace->blocks[i].type == BlockId::Type::FunctionCall ? 'F' : 'E',
execution_trace->blocks[i].address);
}
LOG_NOTICE(PPU, s.c_str());
//m_recompilation_engine->NotifyTrace(execution_trace);
} }
} }
@ -5056,6 +5078,7 @@ ppu_recompiler_llvm::ExecutionEngine::ExecutionEngine(PPUThread & ppu)
} }
ppu_recompiler_llvm::ExecutionEngine::~ExecutionEngine() { ppu_recompiler_llvm::ExecutionEngine::~ExecutionEngine() {
} }
u8 ppu_recompiler_llvm::ExecutionEngine::DecodeMemory(const u32 address) { u8 ppu_recompiler_llvm::ExecutionEngine::DecodeMemory(const u32 address) {
@ -5107,17 +5130,26 @@ u64 ppu_recompiler_llvm::ExecutionEngine::ExecuteFunction(ExecutionEngine * exec
u64 ppu_recompiler_llvm::ExecutionEngine::ExecuteTillReturn(ExecutionEngine * execution_engine, PPUThread * ppu_state, PPUInterpreter * interpreter, Tracer * tracer) { u64 ppu_recompiler_llvm::ExecutionEngine::ExecuteTillReturn(ExecutionEngine * execution_engine, PPUThread * ppu_state, PPUInterpreter * interpreter, Tracer * tracer) {
bool terminate = false; bool terminate = false;
bool returned = false;
while (!terminate) { while (!terminate && !Emu.IsStopped()) {
if (Emu.IsPaused()) {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
continue;
}
BranchType branch_type;
if (!returned) {
auto instruction = re32(vm::get_ref<u32>(ppu_state->PC)); auto instruction = re32(vm::get_ref<u32>(ppu_state->PC));
execution_engine->m_decoder.Decode(instruction); execution_engine->m_decoder.Decode(instruction);
auto is_branch = ppu_state->m_is_branch; branch_type = ppu_state->m_is_branch ? GetBranchTypeFromInstruction(instruction) : BranchType::NonBranch;
ppu_state->NextPc(4); ppu_state->NextPc(4);
} else {
returned = false;
branch_type = BranchType::LocalBranch;
}
if (is_branch) {
Executable executable; Executable executable;
auto branch_type = GetBranchTypeFromInstruction(instruction);
switch (branch_type) { switch (branch_type) {
case BranchType::Return: case BranchType::Return:
tracer->Trace(Tracer::TraceType::Return, 0, 0); tracer->Trace(Tracer::TraceType::Return, 0, 0);
@ -5127,26 +5159,26 @@ u64 ppu_recompiler_llvm::ExecutionEngine::ExecuteTillReturn(ExecutionEngine * ex
tracer->Trace(Tracer::TraceType::CallFunction, ppu_state->PC, 0); tracer->Trace(Tracer::TraceType::CallFunction, ppu_state->PC, 0);
executable = execution_engine->GetExecutable(ppu_state->PC, ExecuteFunction); executable = execution_engine->GetExecutable(ppu_state->PC, ExecuteFunction);
executable(execution_engine, ppu_state, interpreter, tracer); executable(execution_engine, ppu_state, interpreter, tracer);
// Fallthrough returned = true;
break;
case BranchType::LocalBranch: case BranchType::LocalBranch:
tracer->Trace(Tracer::TraceType::EnterBlock, ppu_state->PC, 0); tracer->Trace(Tracer::TraceType::EnterBlock, ppu_state->PC, 0);
executable = execution_engine->GetExecutable(ppu_state->PC, nullptr); executable = execution_engine->GetExecutable(ppu_state->PC, nullptr);
if (executable != nullptr) { if (executable != nullptr) {
auto exit_block = executable(execution_engine, ppu_state, interpreter, tracer); auto exit_block = executable(execution_engine, ppu_state, interpreter, tracer);
if (exit_block) {
tracer->Trace(Tracer::TraceType::ExitFromCompiledBlock, (u32)exit_block, 0); tracer->Trace(Tracer::TraceType::ExitFromCompiledBlock, (u32)exit_block, 0);
} else { if (exit_block == 0) {
tracer->Trace(Tracer::TraceType::Return, 0, 0);
terminate = true; terminate = true;
} }
} }
break; break;
case BranchType::NonBranch:
break;
default: default:
assert(0); assert(0);
break; break;
} }
} }
}
return 0; return 0;
} }
@ -5170,6 +5202,12 @@ BranchType ppu_recompiler_llvm::GetBranchTypeFromInstruction(u32 instruction) {
return type; return type;
} }
ExecutionTraceId ppu_recompiler_llvm::GetExecutionTraceId(const ExecutionTrace * execution_trace) { ExecutionTraceId ppu_recompiler_llvm::GetExecutionTraceId(const ExecutionTrace & execution_trace) {
return 0; 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;
} }

View File

@ -16,6 +16,7 @@ namespace ppu_recompiler_llvm {
class RecompilationEngine; class RecompilationEngine;
class Tracer; class Tracer;
class ExecutionEngine; class ExecutionEngine;
struct PPUState;
enum class BranchType { enum class BranchType {
NonBranch, NonBranch,
@ -35,8 +36,19 @@ namespace ppu_recompiler_llvm {
Normal, Normal,
Exit, Exit,
} type; } type;
bool operator == (const BlockId & other) const {
return (address == other.address && type == other.type);
}
std::string ToString() const {
return fmt::Format("%c:0x%08X", type == BlockId::Type::Normal ? 'N' : type == BlockId::Type::FunctionCall ? 'F' : 'E', address);
}
}; };
/// 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;
/// Uniquely identifies an execution trace /// Uniquely identifies an execution trace
typedef u64 ExecutionTraceId; typedef u64 ExecutionTraceId;
@ -45,9 +57,6 @@ namespace ppu_recompiler_llvm {
/// The function to which this trace belongs /// The function to which this trace belongs
u32 function_address; u32 function_address;
/// The address of the block that came before this trace
u32 previous_block_address;
/// Execution trace type /// Execution trace type
enum class Type { enum class Type {
Linear, Linear,
@ -56,16 +65,58 @@ namespace ppu_recompiler_llvm {
/// Sequence of blocks enountered in this trace /// Sequence of blocks enountered in this trace
std::vector<BlockId> blocks; std::vector<BlockId> blocks;
};
/// A fragment of PPU code. A list of (block, list of next blocks) pairs. std::string ToString() const {
typedef std::vector<std::pair<BlockId, std::vector<BlockId>>> CodeFragment; 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();
}
return s;
}
};
/// Pointer to an executable /// Pointer to an executable
typedef u64(*Executable)(ExecutionEngine * execution_engine, PPUThread * ppu_state, PPUInterpreter * interpreter, Tracer * tracer); typedef u64(*Executable)(ExecutionEngine * execution_engine, PPUThread * ppu_state, PPUInterpreter * interpreter, Tracer * tracer);
struct PPUState; /// An entry in the block table
struct BlockEntry {
/// Address of the block
u32 address;
/// Number of times this block was hit
u32 num_hits;
/// The CFG for this block
ControlFlowGraph cfg;
/// 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)
, is_compiled(false) {
}
bool operator == (const BlockEntry & other) const {
return address == other.address;
}
};
}
namespace std {
template<> struct hash<ppu_recompiler_llvm::BlockEntry *> {
size_t operator()(const ppu_recompiler_llvm::BlockEntry * e) const {
return e->address;
}
};
}
namespace ppu_recompiler_llvm {
/// PPU compiler that uses LLVM for code generation and optimization /// PPU compiler that uses LLVM for code generation and optimization
class Compiler : protected PPUOpcodes, protected PPCDecoder { class Compiler : protected PPUOpcodes, protected PPCDecoder {
public: public:
@ -97,10 +148,10 @@ namespace ppu_recompiler_llvm {
Compiler & operator = (Compiler && other) = delete; Compiler & operator = (Compiler && other) = delete;
/// Compile a code fragment and obtain an executable /// Compile a code fragment and obtain an executable
Executable Compile(const std::string & name, const CodeFragment & code_fragment); //Executable Compile(const std::string & name, const CodeFragment & code_fragment);
/// Free an executable earilier obtained from the Compile function /// Free an executable earilier obtained from the Compile function
void FreeCompiledCodeFragment(Executable executable); //void FreeCompiledCodeFragment(Executable executable);
/// Retrieve compiler stats /// Retrieve compiler stats
Stats GetStats(); Stats GetStats();
@ -742,24 +793,6 @@ namespace ppu_recompiler_llvm {
static std::shared_ptr<RecompilationEngine> GetInstance(); static std::shared_ptr<RecompilationEngine> GetInstance();
private: private:
/// An entry in the block table
struct BlockEntry {
BlockEntry();
~BlockEntry();
/// Number of times this block was hit
u32 num_hits;
/// Execution traces starting at this block
std::unordered_map<ExecutionTraceId, ExecutionTrace *> execution_traces;
/// Indicates whether the block has been compiled or not
bool is_compiled;
};
/// Block table type. Key is block address.
typedef std::unordered_map<u32, BlockEntry> BlockTable;
RecompilationEngine(); RecompilationEngine();
RecompilationEngine(const RecompilationEngine & other) = delete; RecompilationEngine(const RecompilationEngine & other) = delete;
@ -768,23 +801,26 @@ namespace ppu_recompiler_llvm {
RecompilationEngine & operator = (const RecompilationEngine & other) = delete; RecompilationEngine & operator = (const RecompilationEngine & other) = delete;
RecompilationEngine & operator = (RecompilationEngine && other) = delete; RecompilationEngine & operator = (RecompilationEngine && other) = delete;
/// Process an execution trace. Returns an iterator to a block table entry if the block should be compiled. /// Process an execution trace.
BlockTable::iterator ProcessExecutionTrace(ExecutionTrace * execution_trace); void ProcessExecutionTrace(const ExecutionTrace & execution_trace);
/// Update a CFG
void UpdateControlFlowGraph(ControlFlowGraph & cfg, BlockId block, BlockId next_block);
/// Compile a block /// Compile a block
void CompileBlock(BlockTable::iterator block_i); void CompileBlock(const BlockEntry & block_entry, bool inline_referenced_blocks);
/// Build code fragment from a block
CodeFragment BuildCodeFragmentFromBlock(const BlockEntry & block_entry, bool force_inline);
/// Lock for accessing m_pending_execution_traces. TODO: Eliminate this and use a lock-free queue. /// Lock for accessing m_pending_execution_traces. TODO: Eliminate this and use a lock-free queue.
std::mutex m_pending_execution_traces_lock; std::mutex m_pending_execution_traces_lock;
/// Queue of execution traces pending prcessing /// Queue of execution traces pending processing
std::list<ExecutionTrace *> m_pending_execution_traces; std::list<ExecutionTrace *> m_pending_execution_traces;
/// Block table /// Block table
BlockTable m_block_table; 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;
/// PPU Compiler /// PPU Compiler
Compiler m_compiler; Compiler m_compiler;
@ -793,7 +829,7 @@ namespace ppu_recompiler_llvm {
static std::mutex s_mutex; static std::mutex s_mutex;
/// The instance /// The instance
static RecompilationEngine * s_the_instance; static std::shared_ptr<RecompilationEngine> s_the_instance;
}; };
/// Finds interesting execution sequences /// Finds interesting execution sequences
@ -894,7 +930,7 @@ namespace ppu_recompiler_llvm {
BranchType GetBranchTypeFromInstruction(u32 instruction); BranchType GetBranchTypeFromInstruction(u32 instruction);
/// Get the execution trace id of an execution trace /// Get the execution trace id of an execution trace
ExecutionTraceId GetExecutionTraceId(const ExecutionTrace * execution_trace); ExecutionTraceId GetExecutionTraceId(const ExecutionTrace & execution_trace);
} }
#endif // PPU_LLVM_RECOMPILER_H #endif // PPU_LLVM_RECOMPILER_H

View File

@ -33,6 +33,7 @@
#include <functional> #include <functional>
#include <algorithm> #include <algorithm>
#include <random> #include <random>
#include <unordered_set>
#include <sys/stat.h> #include <sys/stat.h>
#include "Utilities/GNU.h" #include "Utilities/GNU.h"