Made changes to tracer so that it understand compiled blocks

This commit is contained in:
S Gopal Rajagopal 2014-11-01 01:22:39 +05:30
parent 160b58cf61
commit 7846fa5829
3 changed files with 456 additions and 138 deletions

View File

@ -76,7 +76,7 @@ Compiler::~Compiler() {
delete m_llvm_context;
}
CompiledCodeFragment 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(!code_fragment.empty());
@ -114,7 +114,7 @@ CompiledCodeFragment Compiler::Compile(const std::string & name, const CodeFragm
// Add code to notify the tracer about this function and branch to the first instruction
m_ir_builder->SetInsertPoint(GetBasicBlockFromAddress(0, m_current_function));
//Call<void>("Tracer.Trace", &Tracer::Trace, *arg_i,
// m_ir_builder->getIntN(sizeof(Tracer::BranchType) * 8, code_fragment[0].first.type == FunctionStart ? Tracer::BranchType::CompiledFunctionCall : Tracer::BranchType::CompiledBlock),
// m_ir_builder->getInt32(code_fragment[0].first.type == Function ? FunctionCall : Block),
// m_ir_builder->getInt32(code_fragment[0].first.address));
m_ir_builder->CreateBr(GetBasicBlockFromAddress(code_fragment[0].first.address, m_current_function));
@ -123,9 +123,14 @@ CompiledCodeFragment Compiler::Compile(const std::string & name, const CodeFragm
m_current_instruction_address = i->first.address;
m_current_block_next_blocks = &(i->second);
auto block = GetBasicBlockFromAddress(m_current_instruction_address, m_current_function);
m_hit_branch_instruction = false;
m_ir_builder->SetInsertPoint(block);
if (i != code_fragment.begin() && i->first.type == BlockId::Type::FunctionCall) {
auto ordinal = RecompilationEngine::GetInstance()->GetOrdinal(i->first.address);
}
m_hit_branch_instruction = false;
while (!m_hit_branch_instruction) {
if (!block->getInstList().empty()) {
break;
@ -143,7 +148,7 @@ CompiledCodeFragment Compiler::Compile(const std::string & name, const CodeFragm
}
}
// If the function has an unknown block then 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);
if (!unknown_bb) {
m_ir_builder->SetInsertPoint(unknown_bb);
@ -177,16 +182,17 @@ CompiledCodeFragment Compiler::Compile(const std::string & name, const CodeFragm
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_compiled[(CompiledCodeFragment)mci.address()] = m_current_function;
return (CompiledCodeFragment)mci.address();
//m_compiled[(CompiledCodeFragment)mci.address()] = m_current_function;
//return (CompiledCodeFragment)mci.address();
return nullptr;
}
void Compiler::FreeCompiledCodeFragment(CompiledCodeFragment compiled_code_fragment) {
auto i = m_compiled.find(compiled_code_fragment);
if (i != m_compiled.end()) {
m_execution_engine->freeMachineCodeForFunction(i->second);
i->second->eraseFromParent();
}
void Compiler::FreeCompiledCodeFragment(Executable compiled_code_fragment) {
//auto i = m_compiled.find(compiled_code_fragment);
//if (i != m_compiled.end()) {
// m_execution_engine->freeMachineCodeForFunction(i->second);
// i->second->eraseFromParent();
//}
}
Compiler::Stats Compiler::GetStats() {
@ -4742,30 +4748,200 @@ void Compiler::InitRotateMask() {
}
std::mutex RecompilationEngine::s_mutex;
std::shared_ptr<RecompilationEngine> RecompilationEngine::s_the_instance;
RecompilationEngine * RecompilationEngine::s_the_instance;
CompiledCodeFragment RecompilationEngine::GetCompiledCodeFragment(u32 address) {
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()
: ThreadBase("PPU Recompilation Engine") {
Start();
}
RecompilationEngine::~RecompilationEngine() {
Stop();
}
u32 RecompilationEngine::GetOrdinal(u32 address) {
return 0xFFFFFFFF;
}
Executable * RecompilationEngine::GetExecutableLookup() const {
return nullptr;
}
void RecompilationEngine::ReleaseCompiledCodeFragment(CompiledCodeFragment compiled_code_fragment) {
void RecompilationEngine::NotifyTrace(ExecutionTrace * execution_trace) {
{
std::lock_guard<std::mutex> lock(m_pending_execution_traces_lock);
m_pending_execution_traces.push_back(execution_trace);
}
u32 RecompilationEngine::GetCurrentRevision() {
return 0;
Notify();
// TODO: Increase the priority of the recompilation engine thread
}
void RecompilationEngine::Task() {
std::chrono::nanoseconds idling_time(0);
auto start = std::chrono::high_resolution_clock::now();
while (!TestDestroy() && !Emu.IsStopped()) {
// Wait a few ms for something to happen
auto idling_start = std::chrono::high_resolution_clock::now();
WaitForAnySignal(250);
auto idling_end = std::chrono::high_resolution_clock::now();
idling_time += std::chrono::duration_cast<std::chrono::nanoseconds>(idling_end - idling_start);
u32 num_processed = 0;
while (!TestDestroy() && !Emu.IsStopped()) {
ExecutionTrace * execution_trace;
{
std::lock_guard<std::mutex> lock(m_pending_execution_traces_lock);
auto i = m_pending_execution_traces.begin();
if (i != m_pending_execution_traces.end()) {
execution_trace = *i;
m_pending_execution_traces.erase(i);
} else {
break;
}
}
auto block_i = ProcessExecutionTrace(execution_trace);
if (block_i != m_block_table.end()) {
CompileBlock(block_i);
}
}
// TODO: Reduce the priority of the recompilation engine thread
if (num_processed == 0) {
// If we get here, it means the recompilation engine is idling.
// We should use this oppurtunity to optimize the code.
}
}
std::chrono::high_resolution_clock::time_point end = std::chrono::high_resolution_clock::now();
auto total_time = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start);
auto compiler_stats = m_compiler.GetStats();
std::string error;
raw_fd_ostream log_file("PPULLVMRecompiler.log", error, sys::fs::F_Text);
log_file << "Total time = " << total_time.count() / 1000000 << "ms\n";
log_file << " Time spent compiling = " << compiler_stats.total_time.count() / 1000000 << "ms\n";
log_file << " Time spent building IR = " << compiler_stats.ir_build_time.count() / 1000000 << "ms\n";
log_file << " Time spent optimizing = " << compiler_stats.optimization_time.count() / 1000000 << "ms\n";
log_file << " Time spent translating = " << compiler_stats.translation_time.count() / 1000000 << "ms\n";
log_file << " Time spent idling = " << idling_time.count() / 1000000 << "ms\n";
log_file << " Time spent doing misc tasks = " << (total_time.count() - idling_time.count() - compiler_stats.total_time.count()) / 1000000 << "ms\n";
log_file << "\nInterpreter fallback stats:\n";
for (auto i = compiler_stats.interpreter_fallback_stats.begin(); i != compiler_stats.interpreter_fallback_stats.end(); i++) {
log_file << i->first << " = " << i->second << "\n";
}
//log_file << "\nDisassembly:\n";
//auto disassembler = LLVMCreateDisasm(sys::getProcessTriple().c_str(), nullptr, 0, nullptr, nullptr);
//for (auto i = m_compiled.begin(); i != m_compiled.end(); i++) {
// log_file << fmt::Format("%s: Size = %u bytes, Number of instructions = %u\n", i->second.llvm_function->getName().str().c_str(), i->second.size, i->second.num_instructions);
// uint8_t * fn_ptr = (uint8_t *)i->second.executable;
// for (size_t pc = 0; pc < i->second.size;) {
// char str[1024];
// auto size = LLVMDisasmInstruction(disassembler, fn_ptr + pc, i->second.size - pc, (uint64_t)(fn_ptr + pc), str, sizeof(str));
// log_file << str << '\n';
// pc += size;
// }
//}
//LLVMDisasmDispose(disassembler);
//log_file << "\nLLVM IR:\n" << *m_module;
LOG_NOTICE(PPU, "PPU LLVM Recompilation thread exiting.");
}
RecompilationEngine::BlockTable::iterator RecompilationEngine::ProcessExecutionTrace(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_i = block_i->second.execution_traces.find(execution_trace_id);
if (execution_trace_i == block_i->second.execution_traces.end()) {
block_i->second.execution_traces.insert(std::make_pair(execution_trace_id, execution_trace));
}
if (!block_i->second.is_compiled && block_i->second.num_hits > 1000) { // TODO: Make threshold configurable
return block_i;
}
return m_block_table.end();
}
void RecompilationEngine::CompileBlock(BlockTable::iterator block_i) {
auto code_fragment = BuildCodeFragmentFromBlock(block_i->second, false);
}
CodeFragment RecompilationEngine::BuildCodeFragmentFromBlock(const BlockEntry & block_entry, bool force_inline) {
CodeFragment code_fragment;
//std::vector<const BlockEntry *> queue;
//queue.push_back(&block_entry);
//for (auto q = queue.begin(); q != queue.end(); q++) {
// for (auto i = (*q)->execution_traces.begin(); i != (*q)->execution_traces.end(); i++) {
// for (auto j = i->second->blocks.begin(); j != i->second->blocks.end(); j++) {
// auto k = std::find_if(code_fragment.begin(), code_fragment.end(),
// [&j](const CodeFragment::value_type & v)->bool { return v.first.address == j->address; });
// 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()) {
// 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) {
// auto block_i = m_block_table.find(j->address);
// if (block_i != m_block_table.end()) {
// if (std::find(queue.begin(), queue.end(), block_i->second) == queue.end()) {
// queue.push_back(&(block_i->second));
// }
// }
// }
// }
// }
//}
return code_fragment;
}
std::shared_ptr<RecompilationEngine> RecompilationEngine::GetInstance() {
if (s_the_instance == nullptr) {
std::lock_guard<std::mutex> lock(s_mutex);
s_the_instance = std::shared_ptr<RecompilationEngine>(new RecompilationEngine());
s_the_instance = new RecompilationEngine();
}
return s_the_instance;
return std::shared_ptr<RecompilationEngine>(s_the_instance);
}
Tracer::Tracer() {
Tracer::Tracer()
: m_recompilation_engine(RecompilationEngine::GetInstance()) {
m_trace.reserve(1000);
m_stack.reserve(100);
}
@ -4774,27 +4950,60 @@ Tracer::~Tracer() {
Terminate();
}
void Tracer::Trace(BranchType branch_type, u32 address) {
void Tracer::Trace(TraceType trace_type, u32 arg1, u32 arg2) {
ExecutionTrace * execution_trace = nullptr;
BlockId block_id;
int function;
block_id.address = address;
block_id.type = branch_type;
switch (branch_type) {
case FunctionCall:
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);
break;
case TraceType::EnterFunction:
// arg1 is address.
block_id.address = arg1;
block_id.type = BlockId::Type::Normal;
m_stack.push_back((u32)m_trace.size());
m_trace.push_back(block_id);
break;
case Block:
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);
break;
case TraceType::Return:
// No args used
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;
execution_trace->previous_block_address = 0;
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 == address) {
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::Loop;
execution_trace->type = ExecutionTrace::Type::Loop;
execution_trace->function_address = m_trace[function].address;
execution_trace->blocks.insert(execution_trace->blocks.begin(), m_trace.begin() + i, m_trace.end());
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));
m_trace.erase(m_trace.begin() + i + 1, m_trace.end());
break;
}
@ -4802,20 +5011,16 @@ void Tracer::Trace(BranchType branch_type, u32 address) {
if (!execution_trace) {
// A loop was not found
block_id.address = arg1;
block_id.type = BlockId::Type::Normal;
m_trace.push_back(block_id);
}
break;
case Return:
function = m_stack.back();
m_stack.pop_back();
execution_trace = new ExecutionTrace();
execution_trace->function_address = m_trace[function].address;
execution_trace->type = ExecutionTrace::Linear;
execution_trace->blocks.insert(execution_trace->blocks.begin(), m_trace.begin() + function, m_trace.end());
m_trace.erase(m_trace.begin() + function + 1, m_trace.end());
break;
case None:
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);
break;
default:
assert(0);
@ -4823,14 +5028,17 @@ void Tracer::Trace(BranchType branch_type, u32 address) {
}
if (execution_trace) {
auto s = fmt::Format("Trace: 0x%08X, %s -> ", execution_trace->function_address, execution_trace->type == ExecutionTrace::Loop ? "Loop" : "Linear");
for (auto i = 0; i < execution_trace->blocks.size(); i++) {
s += fmt::Format("0x%08X ", execution_trace->blocks[i]);
auto s = fmt::Format("Trace: 0x%08X, 0x%08X, %s -> ", execution_trace->function_address, execution_trace->previous_block_address,
execution_trace->type == ExecutionTrace::Type::Loop ? "Loop" : "Linear");
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());
delete execution_trace;
// TODO: Notify recompilation engine
//m_recompilation_engine->NotifyTrace(execution_trace);
}
}
@ -4842,36 +5050,27 @@ ppu_recompiler_llvm::ExecutionEngine::ExecutionEngine(PPUThread & ppu)
: m_ppu(ppu)
, m_interpreter(new PPUInterpreter(ppu))
, m_decoder(m_interpreter)
, m_last_branch_type(FunctionCall)
, m_last_cache_clear_time(std::chrono::high_resolution_clock::now())
, m_recompiler_revision(0)
, m_recompilation_engine(RecompilationEngine::GetInstance()) {
m_executable_lookup = m_recompilation_engine->GetExecutableLookup();
}
ppu_recompiler_llvm::ExecutionEngine::~ExecutionEngine() {
for (auto iter = m_address_to_compiled_code_fragment.begin(); iter != m_address_to_compiled_code_fragment.end(); iter++) {
m_recompilation_engine->ReleaseCompiledCodeFragment(iter->second.first);
}
}
u8 ppu_recompiler_llvm::ExecutionEngine::DecodeMemory(const u32 address) {
auto now = std::chrono::high_resolution_clock::now();
if (std::chrono::duration_cast<std::chrono::milliseconds>(now - m_last_cache_clear_time).count() > 10000) {
bool clear_all = false;
u32 revision = m_recompilation_engine->GetCurrentRevision();
if (m_recompiler_revision != revision) {
m_recompiler_revision = revision;
clear_all = true;
ExecuteFunction(this, &m_ppu, m_interpreter, &m_tracer);
return 0;
}
for (auto i = m_address_to_compiled_code_fragment.begin(); i != m_address_to_compiled_code_fragment.end();) {
void ppu_recompiler_llvm::ExecutionEngine::RemoveUnusedEntriesFromCache() {
auto now = std::chrono::high_resolution_clock::now();
if (std::chrono::duration_cast<std::chrono::milliseconds>(now - m_last_cache_clear_time).count() > 10000) {
for (auto i = m_address_to_ordinal.begin(); i != m_address_to_ordinal.end();) {
auto tmp = i;
i++;
if (tmp->second.second == 0 || clear_all) {
m_address_to_compiled_code_fragment.erase(tmp);
m_recompilation_engine->ReleaseCompiledCodeFragment(tmp->second.first);
if (tmp->second.second == 0) {
m_address_to_ordinal.erase(tmp);
} else {
tmp->second.second = 0;
}
@ -4879,47 +5078,98 @@ u8 ppu_recompiler_llvm::ExecutionEngine::DecodeMemory(const u32 address) {
m_last_cache_clear_time = now;
}
}
auto i = m_address_to_compiled_code_fragment.find(address);
if (i == m_address_to_compiled_code_fragment.end()) {
auto compiled_code_fragment = m_recompilation_engine->GetCompiledCodeFragment(address);
if (compiled_code_fragment) {
i = m_address_to_compiled_code_fragment.insert(m_address_to_compiled_code_fragment.end(), std::make_pair(address, std::make_pair(compiled_code_fragment, 0)));
Executable ppu_recompiler_llvm::ExecutionEngine::GetExecutable(u32 address, Executable default_executable) {
// Find the ordinal for the specified address and insert it to the cache
auto i = m_address_to_ordinal.find(address);
if (i == m_address_to_ordinal.end()) {
auto ordinal = m_recompilation_engine->GetOrdinal(address);
if (ordinal != 0xFFFFFFFF) {
i = m_address_to_ordinal.insert(m_address_to_ordinal.end(), std::make_pair(address, std::make_pair(ordinal, 0)));
}
}
u8 ret = 0;
if (i != m_address_to_compiled_code_fragment.end()) {
m_last_branch_type = None;
Executable executable = default_executable;
if (i != m_address_to_ordinal.end()) {
i->second.second++;
i->second.first(&m_ppu, m_interpreter);
executable = m_executable_lookup[i->second.first];
}
RemoveUnusedEntriesFromCache();
return executable;
}
u64 ppu_recompiler_llvm::ExecutionEngine::ExecuteFunction(ExecutionEngine * execution_engine, PPUThread * ppu_state, PPUInterpreter * interpreter, Tracer * tracer) {
tracer->Trace(Tracer::TraceType::EnterFunction, ppu_state->PC, 0);
return ExecuteTillReturn(execution_engine, ppu_state, interpreter, tracer);
}
u64 ppu_recompiler_llvm::ExecutionEngine::ExecuteTillReturn(ExecutionEngine * execution_engine, PPUThread * ppu_state, PPUInterpreter * interpreter, Tracer * tracer) {
bool terminate = false;
while (!terminate) {
auto instruction = re32(vm::get_ref<u32>(ppu_state->PC));
execution_engine->m_decoder.Decode(instruction);
auto is_branch = ppu_state->m_is_branch;
ppu_state->NextPc(4);
if (is_branch) {
Executable executable;
auto branch_type = GetBranchTypeFromInstruction(instruction);
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);
// Fallthrough
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);
if (exit_block) {
tracer->Trace(Tracer::TraceType::ExitFromCompiledBlock, (u32)exit_block, 0);
} else {
if (m_last_branch_type != None) {
m_tracer.Trace(m_last_branch_type, address);
tracer->Trace(Tracer::TraceType::Return, 0, 0);
terminate = true;
}
}
break;
default:
assert(0);
break;
}
}
}
ret = m_decoder.DecodeMemory(address);
m_last_branch_type = m_ppu.m_is_branch ? GetBranchTypeFromInstruction(vm::read32(address)) : None;
}
return ret;
return 0;
}
BranchType ppu_recompiler_llvm::GetBranchTypeFromInstruction(u32 instruction) {
auto type = BranchType::None;
auto type = BranchType::NonBranch;
auto field1 = instruction >> 26;
auto lk = instruction & 1;
if (field1 == 16 || field1 == 18) {
type = lk ? FunctionCall : Block;
type = lk ? BranchType::FunctionCall : BranchType::LocalBranch;
} else if (field1 == 19) {
u32 field2 = (instruction >> 1) & 0x3FF;
if (field2 == 16) {
type = lk ? FunctionCall : Return;
type = lk ? BranchType::FunctionCall : BranchType::Return;
} else if (field2 == 528) {
type = lk ? FunctionCall : Block;
type = lk ? BranchType::FunctionCall : BranchType::LocalBranch;
}
}
return type;
}
ExecutionTraceId ppu_recompiler_llvm::GetExecutionTraceId(const ExecutionTrace * execution_trace) {
return 0;
}

View File

@ -12,34 +12,44 @@
#include "llvm/PassManager.h"
namespace ppu_recompiler_llvm {
/// Branch type
enum BranchType {
None,
class Compiler;
class RecompilationEngine;
class Tracer;
class ExecutionEngine;
enum class BranchType {
NonBranch,
LocalBranch,
FunctionCall,
Block,
Return,
};
/// Unique id of a block
union BlockId {
u64 block_id;
struct {
struct BlockId {
/// Address of the block
u32 address;
/// The type of the block
BranchType type;
};
enum class Type {
FunctionCall,
Normal,
Exit,
} type;
};
/// Uniquely identifies an execution trace
typedef u64 ExecutionTraceId;
/// An execution trace.
struct ExecutionTrace {
/// The function in which this trace was found
/// The function to which this trace belongs
u32 function_address;
/// The address of the block that came before this trace
u32 previous_block_address;
/// Execution trace type
enum {
enum class Type {
Linear,
Loop,
} type;
@ -51,8 +61,8 @@ namespace ppu_recompiler_llvm {
/// A fragment of PPU code. A list of (block, list of next blocks) pairs.
typedef std::vector<std::pair<BlockId, std::vector<BlockId>>> CodeFragment;
/// Pointer to a function built by compiling a fragment of PPU code
typedef u64(*CompiledCodeFragment)(PPUThread * ppu_state, PPUInterpreter * interpreter);
/// Pointer to an executable
typedef u64(*Executable)(ExecutionEngine * execution_engine, PPUThread * ppu_state, PPUInterpreter * interpreter, Tracer * tracer);
struct PPUState;
@ -86,11 +96,11 @@ namespace ppu_recompiler_llvm {
Compiler & operator = (const Compiler & other) = delete;
Compiler & operator = (Compiler && other) = delete;
/// Compile a code fragment
CompiledCodeFragment Compile(const std::string & name, const CodeFragment & code_fragment);
/// Compile a code fragment and obtain an executable
Executable Compile(const std::string & name, const CodeFragment & code_fragment);
/// Free a compiled code fragment
void FreeCompiledCodeFragment(CompiledCodeFragment compiled_code_fragment);
/// Free an executable earilier obtained from the Compile function
void FreeCompiledCodeFragment(Executable executable);
/// Retrieve compiler stats
Stats GetStats();
@ -502,9 +512,6 @@ namespace ppu_recompiler_llvm {
void UNK(const u32 code, const u32 opcode, const u32 gcode) override;
private:
/// Map from compiled code fragment to the LLVM function for the code fragment
std::map<CompiledCodeFragment, llvm::Function *> m_compiled;
/// LLVM context
llvm::LLVMContext * m_llvm_context;
@ -716,29 +723,44 @@ namespace ppu_recompiler_llvm {
static void InitRotateMask();
};
/// Analyses execution traces and finds hot paths
class Profiler {
};
class RecompilationEngine {
class RecompilationEngine : public ThreadBase {
public:
virtual ~RecompilationEngine() = default;
virtual ~RecompilationEngine();
/// Get the compiled code fragment for the specified address
CompiledCodeFragment GetCompiledCodeFragment(u32 address);
/// Get the ordinal for the specified address
u32 GetOrdinal(u32 address);
/// Release a compiled code fragment earlier obtained through GetCompiledCodeFragment
void ReleaseCompiledCodeFragment(CompiledCodeFragment compiled_code_fragment);
/// Get the executable lookup table
Executable * GetExecutableLookup() const;
/// Get the current revision
u32 GetCurrentRevision();
/// Notify the recompilation engine about a newly detected trace. It takes ownership of the trace.
void NotifyTrace(ExecutionTrace * execution_trace);
void Task() override;
/// Get a pointer to the instance of this class
static std::shared_ptr<RecompilationEngine> GetInstance();
private:
RecompilationEngine() = default;
/// 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(const RecompilationEngine & other) = delete;
RecompilationEngine(RecompilationEngine && other) = delete;
@ -746,16 +768,47 @@ namespace ppu_recompiler_llvm {
RecompilationEngine & operator = (const 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.
BlockTable::iterator ProcessExecutionTrace(ExecutionTrace * execution_trace);
/// Compile a block
void CompileBlock(BlockTable::iterator block_i);
/// 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.
std::mutex m_pending_execution_traces_lock;
/// Queue of execution traces pending prcessing
std::list<ExecutionTrace *> m_pending_execution_traces;
/// Block table
BlockTable m_block_table;
/// PPU Compiler
Compiler m_compiler;
/// Mutex used to prevent multiple creation
static std::mutex s_mutex;
/// The instance
static std::shared_ptr<RecompilationEngine> s_the_instance;
static RecompilationEngine * s_the_instance;
};
/// Finds interesting execution sequences
class Tracer {
public:
/// Trace type
enum class TraceType {
CallFunction,
EnterFunction,
ExitFromCompiledFunction,
Return,
EnterBlock,
ExitFromCompiledBlock,
};
Tracer();
Tracer(const Tracer & other) = delete;
@ -766,8 +819,8 @@ namespace ppu_recompiler_llvm {
Tracer & operator = (const Tracer & other) = delete;
Tracer & operator = (Tracer && other) = delete;
/// Notify the tracer that a branch was encountered
void Trace(BranchType branch_type, u32 address);
/// Notify the tracer
void Trace(TraceType trace_type, u32 arg1, u32 arg2);
/// Notify the tracer that the execution sequence is being terminated.
void Terminate();
@ -778,6 +831,9 @@ namespace ppu_recompiler_llvm {
/// Call stack
std::vector<u32> m_stack;
/// Recompilation engine
std::shared_ptr<RecompilationEngine> m_recompilation_engine;
};
/// PPU execution engine
@ -809,24 +865,36 @@ namespace ppu_recompiler_llvm {
/// Execution tracer
Tracer m_tracer;
/// Set to true if the last executed instruction was a branch
BranchType m_last_branch_type;
/// Executable lookup table
Executable * m_executable_lookup;
/// The time at which the m_address_to_compiled_code_fragment cache was last cleared
/// The time at which the m_address_to_ordinal cache was last cleared
std::chrono::high_resolution_clock::time_point m_last_cache_clear_time;
/// The revision of the recompiler to which this thread is synced
u32 m_recompiler_revision;
/// Address to compiled code fragmnet lookup. Key is address. Data is the pair (compiled code fragment, times hit).
std::unordered_map<u32, std::pair<CompiledCodeFragment, u32>> m_address_to_compiled_code_fragment;
/// Address to ordinal lookup. Key is address. Data is the pair (ordinal, times hit).
std::unordered_map<u32, std::pair<u32, u32>> m_address_to_ordinal;
/// Recompilation engine
std::shared_ptr<RecompilationEngine> m_recompilation_engine;
/// Remove unused entries from the m_address_to_ordinal cache
void RemoveUnusedEntriesFromCache();
/// Get the executable for the specified address
Executable GetExecutable(u32 address, Executable default_executable);
/// Execute a function
static u64 ExecuteFunction(ExecutionEngine * execution_engine, PPUThread * ppu_state, PPUInterpreter * interpreter, Tracer * tracer);
/// Execute till the current function returns
static u64 ExecuteTillReturn(ExecutionEngine * execution_engine, PPUThread * ppu_state, PPUInterpreter * interpreter, Tracer * tracer);
};
// Get the branch type from a branch instruction
/// 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