Implemented a basic UT framework for PPULLVMRecompiler

This commit is contained in:
S Gopal Rajagopal 2014-09-15 19:46:53 +05:30
parent 2bb63ad051
commit 2ea881301a
2 changed files with 4562 additions and 4452 deletions

View file

@ -1,19 +1,25 @@
#include "stdafx.h"
#include "Utilities/Log.h"
#include "Emu/Cell/PPULLVMRecompiler.h"
#include "Emu/Memory/Memory.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/Host.h"
#include "llvm/Support/ManagedStatic.h"
#undef _
#include "llvm/Support/raw_ostream.h"
#include "llvm/ExecutionEngine/GenericValue.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/Analysis/Verifier.h"
#include "llvm/CodeGen/MachineCodeInfo.h"
using namespace llvm;
PPULLVMRecompiler::PPULLVMRecompiler(PPUThread & ppu)
: m_ppu(ppu)
, m_decoder(new PPUDecoder(this))
, m_decoder(this)
, m_ir_builder(m_llvm_context)
{
InitializeNativeTarget();
InitializeNativeTargetDisassembler();
m_module = new Module("Module", m_llvm_context);
m_gpr = new GlobalVariable(*m_module, ArrayType::get(Type::getInt64Ty(m_llvm_context), 32), false, GlobalValue::ExternalLinkage, nullptr, "gpr");
@ -24,31 +30,35 @@ PPULLVMRecompiler::PPULLVMRecompiler(PPUThread & ppu)
m_execution_engine->addGlobalMapping(m_gpr, m_ppu.GPR);
m_execution_engine->addGlobalMapping(m_vpr, m_ppu.VPR);
m_execution_engine->addGlobalMapping(m_vscr, &m_ppu.VSCR);
m_disassembler = LLVMCreateDisasm(sys::getProcessTriple().c_str(), nullptr, 0, nullptr, nullptr);
RunUnitTests();
}
PPULLVMRecompiler::~PPULLVMRecompiler()
{
LLVMDisasmDispose(m_disassembler);
delete m_execution_engine;
llvm_shutdown();
delete m_decoder;
}
u8 PPULLVMRecompiler::DecodeMemory(const u64 address)
{
std::string function_name = fmt::Format("fn_%llx", address);
Function * function = m_module->getFunction(function_name);
auto function_name = fmt::Format("fn_0x%llx", address);
auto function = m_module->getFunction(function_name);
if (!function)
{
function = cast<Function>(m_module->getOrInsertFunction(function_name, Type::getVoidTy(m_llvm_context), (Type *)nullptr));
BasicBlock * block = BasicBlock::Create(m_llvm_context, "start", function);
auto block = BasicBlock::Create(m_llvm_context, "start", function);
m_ir_builder.SetInsertPoint(block);
//m_hit_branch_instruction = false;
//while (!m_hit_branch_instruction)
//{
// u32 instr = Memory.Read32(address);
// m_decoder->Decode(instr);
// m_decoder.Decode(instr);
//}
VADDSWS(14, 23, 25);
@ -93,7 +103,7 @@ void PPULLVMRecompiler::TDI(u32 to, u32 ra, s32 simm16)
void PPULLVMRecompiler::TWI(u32 to, u32 ra, s32 simm16)
{
s32 a = m_ppu.GPR[ra];
s32 a = (s32)m_ppu.GPR[ra];
if ((a < simm16 && (to & 0x10)) ||
(a > simm16 && (to & 0x8)) ||
@ -194,7 +204,7 @@ void PPULLVMRecompiler::VADDSWS(u32 vd, u32 va, u32 vb)
auto tmp7_v4i32 = m_ir_builder.CreateAShr(tmp6_v4i32, 31);
// tmp7 is equal to 0xFFFFFFFF if an overflow occured and 0x00000000 otherwise. tmp9 is bitwise inverse of tmp7.
u32 tmp8_v4i32[4] ={0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF};
u32 tmp8_v4i32[4] = {0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF};
auto tmp9_v4i32 = m_ir_builder.CreateXor(tmp7_v4i32, ConstantDataVector::get(m_llvm_context, tmp8_v4i32));
auto tmp10_v4i32 = m_ir_builder.CreateAnd(tmp3_v4i32, tmp7_v4i32);
auto tmp11_v4i32 = m_ir_builder.CreateAnd(sum_v4i32, tmp9_v4i32);
@ -4005,7 +4015,73 @@ void PPULLVMRecompiler::SetVr(u32 vr, Value * val)
m_ir_builder.CreateStore(val_i128, vr_i128_ptr);
}
void PPULLVMRecompiler::RunTests()
void PPULLVMRecompiler::RunUnitTests()
{
LOG_NOTICE(PPU, "Running Unit Tests");
RunUnitTest("MFVSCR",
[this] ()
{
MFVSCR(1);
},
[this] ()
{
m_ppu.VPR[1]._u32[0] = m_ppu.VPR[1]._u32[0] = m_ppu.VPR[1]._u32[0] = m_ppu.VPR[1]._u32[0] = 0x9ABCDEF0;
m_ppu.VSCR.VSCR = 0x12345678;
},
[this](std::string & msg)
{
msg = fmt::Format("VPR[1] = %s", m_ppu.VPR[1].ToString(true).c_str());
return m_ppu.VPR[1].Equals(0x12345678, 0, 0, 0);
});
}
void PPULLVMRecompiler::RunUnitTest(const char * name, std::function<void()> test_case, std::function<void()> input, std::function<bool(std::string & msg)> check_result)
{
// Create the unit test function
auto function = cast<Function>(m_module->getOrInsertFunction(name, Type::getVoidTy(m_llvm_context), (Type *)nullptr));
auto block = BasicBlock::Create(m_llvm_context, "start", function);
m_ir_builder.SetInsertPoint(block);
test_case();
m_ir_builder.CreateRetVoid();
verifyFunction(*function);
// Print the IR
std::string ir;
raw_string_ostream ir_ostream(ir);
function->print(ir_ostream);
LOG_NOTICE(PPU, "[UT %s] LLVM IR:%s", name, ir.c_str());
// Generate the function
MachineCodeInfo mci;
m_execution_engine->runJITOnFunction(function, &mci);
// Disassember the generated function
LOG_NOTICE(PPU, "[UT %s] Disassembly:", name);
for (uint64_t pc = 0; pc < mci.size();)
{
char str[1024];
pc += LLVMDisasmInstruction(m_disassembler, (uint8_t *)mci.address() + pc, mci.size() - pc, (uint64_t)((uint8_t *)mci.address() + pc), str, sizeof(str));
LOG_NOTICE(PPU, "[UT %s] %s.", name, str);
}
// Run the test
input();
std::vector<GenericValue> args;
m_execution_engine->runFunction(function, args);
// Verify results
std::string msg;
bool pass = check_result(msg);
if (pass)
{
LOG_NOTICE(PPU, "[UT %s] Test passed. %s.", name, msg.c_str());
}
else
{
LOG_ERROR(PPU, "[UT %s] Test failed. %s.", name, msg.c_str());
}
m_execution_engine->freeMachineCodeForFunction(function);
}

View file

@ -7,9 +7,9 @@
#include "llvm/IR/Module.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/ExecutionEngine/JIT.h"
#include "llvm/MC/MCDisassembler.h"
using namespace llvm;
/// LLVM based PPU emulator
class PPULLVMRecompiler : public CPUDecoder, protected PPUOpcodes
{
public:
@ -420,22 +420,56 @@ protected:
void UNK(const u32 code, const u32 opcode, const u32 gcode) override;
private:
/// PPU processor context
PPUThread & m_ppu;
PPUDecoder * m_decoder;
/// PPU instruction decoder
PPUDecoder m_decoder;
/// 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.
bool m_hit_branch_instruction;
LLVMContext m_llvm_context;
IRBuilder<> m_ir_builder;
/// LLVM context
llvm::LLVMContext m_llvm_context;
/// LLVM IR builder
llvm::IRBuilder<> m_ir_builder;
/// Module to which all generated code is output to
llvm::Module * m_module;
GlobalVariable * m_gpr;
GlobalVariable * m_vpr;
GlobalVariable * m_vscr;
ExecutionEngine * m_execution_engine;
Value * GetVrAsIntVec(u32 vr, u32 vec_elt_num_bits);
Value * GetVrAsFloatVec(u32 vr);
Value * GetVrAsDoubleVec(u32 vr);
void SetVr(u32 vr, Value * val);
/// Global variable in m_module that corresponds to m_ppu.GPR
llvm::GlobalVariable * m_gpr;
void RunTests();
/// Global variable in m_module that corresponds to m_ppu.VPR
llvm::GlobalVariable * m_vpr;
/// Global variable in m_module that corresponds to m_ppu.VSCR
llvm::GlobalVariable * m_vscr;
/// JIT execution engine
llvm::ExecutionEngine * m_execution_engine;
/// Disassembler
LLVMDisasmContextRef m_disassembler;
/// Load VR and convert it to an integer vector
llvm::Value * GetVrAsIntVec(u32 vr, u32 vec_elt_num_bits);
/// Load VR and convert it to a float vector with 4 elements
llvm::Value * GetVrAsFloatVec(u32 vr);
/// Load VR and convert it to a double vector with 2 elements
llvm::Value * GetVrAsDoubleVec(u32 vr);
/// Set VR to the specified value
void SetVr(u32 vr, llvm::Value * val);
/// Execute all unit tests
void RunUnitTests();
/// Excute an unit test
void RunUnitTest(const char * name, std::function<void()> test_case, std::function<void()> input, std::function<bool(std::string & msg)> check_result);
};