mirror of
https://github.com/citra-emu/citra.git
synced 2024-11-24 14:41:04 +00:00
Added an option to verify the output. This is done by stopping after each instruction and checking what the interpreter produced vs. the binary translation.
This commit is contained in:
parent
cf4914e4fc
commit
1b87113184
@ -18,9 +18,10 @@
|
|||||||
|
|
||||||
using namespace llvm;
|
using namespace llvm;
|
||||||
|
|
||||||
CodeGen::CodeGen(const char* output_object_filename, const char* output_debug_filename)
|
CodeGen::CodeGen(const char* output_object_filename, const char* output_debug_filename, bool verify)
|
||||||
: output_object_filename(output_object_filename),
|
: output_object_filename(output_object_filename),
|
||||||
output_debug_filename(output_debug_filename)
|
output_debug_filename(output_debug_filename),
|
||||||
|
verify(verify)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +69,7 @@ void CodeGen::IntializeLLVM()
|
|||||||
|
|
||||||
void CodeGen::GenerateModule()
|
void CodeGen::GenerateModule()
|
||||||
{
|
{
|
||||||
moduleGenerator = std::make_unique<ModuleGen>(module.get());
|
moduleGenerator = std::make_unique<ModuleGen>(module.get(), verify);
|
||||||
moduleGenerator->Run();
|
moduleGenerator->Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ class ModuleGen;
|
|||||||
class CodeGen
|
class CodeGen
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
CodeGen(const char *output_object_filename, const char *output_debug_filename);
|
CodeGen(const char *output_object_filename, const char *output_debug_filename, bool verify);
|
||||||
~CodeGen();
|
~CodeGen();
|
||||||
|
|
||||||
void Run();
|
void Run();
|
||||||
@ -27,6 +27,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
const char *output_object_filename;
|
const char *output_object_filename;
|
||||||
const char *output_debug_filename;
|
const char *output_debug_filename;
|
||||||
|
bool verify;
|
||||||
|
|
||||||
std::unique_ptr<ModuleGen> moduleGenerator;
|
std::unique_ptr<ModuleGen> moduleGenerator;
|
||||||
|
|
||||||
|
@ -14,8 +14,9 @@
|
|||||||
|
|
||||||
using namespace llvm;
|
using namespace llvm;
|
||||||
|
|
||||||
ModuleGen::ModuleGen(llvm::Module* module)
|
ModuleGen::ModuleGen(llvm::Module* module, bool verify)
|
||||||
: module(module)
|
: module(module),
|
||||||
|
verify(verify)
|
||||||
{
|
{
|
||||||
ir_builder = make_unique<IRBuilder<>>(getGlobalContext());
|
ir_builder = make_unique<IRBuilder<>>(getGlobalContext());
|
||||||
machine = make_unique<MachineState>(this);
|
machine = make_unique<MachineState>(this);
|
||||||
@ -46,13 +47,28 @@ void ModuleGen::Run()
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ModuleGen::BranchReadPC()
|
void ModuleGen::BranchReadPC()
|
||||||
|
{
|
||||||
|
if (verify)
|
||||||
|
{
|
||||||
|
ir_builder->CreateRetVoid();
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
auto call = ir_builder->CreateCall(run_function);
|
auto call = ir_builder->CreateCall(run_function);
|
||||||
call->setTailCall();
|
call->setTailCall();
|
||||||
ir_builder->CreateRetVoid();
|
ir_builder->CreateRetVoid();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ModuleGen::BranchWritePCConst(InstructionBlock *current, u32 pc)
|
void ModuleGen::BranchWritePCConst(InstructionBlock *current, u32 pc)
|
||||||
|
{
|
||||||
|
if (verify)
|
||||||
|
{
|
||||||
|
// Just write PC and exit on verify
|
||||||
|
machine->WriteRegiser(Register::PC, ir_builder->getInt32(pc));
|
||||||
|
ir_builder->CreateRetVoid();
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
auto i = instruction_blocks_by_pc.find(pc);
|
auto i = instruction_blocks_by_pc.find(pc);
|
||||||
if (i != instruction_blocks_by_pc.end())
|
if (i != instruction_blocks_by_pc.end())
|
||||||
@ -68,6 +84,7 @@ void ModuleGen::BranchWritePCConst(InstructionBlock *current, u32 pc)
|
|||||||
ir_builder->CreateRetVoid();
|
ir_builder->CreateRetVoid();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ModuleGen::GenerateGlobals()
|
void ModuleGen::GenerateGlobals()
|
||||||
{
|
{
|
||||||
@ -91,6 +108,9 @@ void ModuleGen::GenerateGlobals()
|
|||||||
|
|
||||||
block_address_array_type = ArrayType::get(block_address_type, block_address_array_size);
|
block_address_array_type = ArrayType::get(block_address_type, block_address_array_size);
|
||||||
block_address_array = new GlobalVariable(*module, block_address_array_type, true, GlobalValue::ExternalLinkage, nullptr, "BlockAddressArray");
|
block_address_array = new GlobalVariable(*module, block_address_array_type, true, GlobalValue::ExternalLinkage, nullptr, "BlockAddressArray");
|
||||||
|
|
||||||
|
// bool Verify - contains the value of verify for citra usage
|
||||||
|
new GlobalVariable(*module, ir_builder->getInt1Ty(), true, GlobalValue::ExternalLinkage, ir_builder->getInt1(verify), "Verify");
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModuleGen::GenerateBlockAddressArray()
|
void ModuleGen::GenerateBlockAddressArray()
|
||||||
|
@ -18,7 +18,11 @@ namespace llvm
|
|||||||
class ModuleGen
|
class ModuleGen
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit ModuleGen(llvm::Module *module);
|
/*
|
||||||
|
* Verify - produce a code that can be verified
|
||||||
|
* this is done by returning after every opcode
|
||||||
|
*/
|
||||||
|
explicit ModuleGen(llvm::Module *module, bool verify);
|
||||||
~ModuleGen();
|
~ModuleGen();
|
||||||
|
|
||||||
void Run();
|
void Run();
|
||||||
@ -50,11 +54,13 @@ private:
|
|||||||
// inter block jumps
|
// inter block jumps
|
||||||
void ColorBlocks();
|
void ColorBlocks();
|
||||||
|
|
||||||
|
llvm::Module *module;
|
||||||
|
bool verify;
|
||||||
|
|
||||||
std::unique_ptr<MachineState> machine;
|
std::unique_ptr<MachineState> machine;
|
||||||
std::unique_ptr<TBAA> tbaa;
|
std::unique_ptr<TBAA> tbaa;
|
||||||
|
|
||||||
std::unique_ptr<llvm::IRBuilder<>> ir_builder;
|
std::unique_ptr<llvm::IRBuilder<>> ir_builder;
|
||||||
llvm::Module *module;
|
|
||||||
|
|
||||||
size_t block_address_array_base;
|
size_t block_address_array_base;
|
||||||
size_t block_address_array_size;
|
size_t block_address_array_size;
|
||||||
|
@ -12,6 +12,7 @@ namespace cl = llvm::cl;
|
|||||||
cl::opt<std::string> InputFilename(cl::Positional, cl::Required, cl::desc("<input rom filename>"));
|
cl::opt<std::string> InputFilename(cl::Positional, cl::Required, cl::desc("<input rom filename>"));
|
||||||
cl::opt<std::string> OutputFilename(cl::Positional, cl::Required, cl::desc("<output object filename>"));
|
cl::opt<std::string> OutputFilename(cl::Positional, cl::Required, cl::desc("<output object filename>"));
|
||||||
cl::opt<std::string> DebugFilename(cl::Positional, cl::Optional, cl::desc("<debug filename>"));
|
cl::opt<std::string> DebugFilename(cl::Positional, cl::Optional, cl::desc("<debug filename>"));
|
||||||
|
cl::opt<bool> Verify("verify", cl::desc("<verify>"), cl::init(false));
|
||||||
|
|
||||||
int main(int argc, const char *const *argv)
|
int main(int argc, const char *const *argv)
|
||||||
{
|
{
|
||||||
@ -39,6 +40,7 @@ int main(int argc, const char *const *argv)
|
|||||||
auto input_rom = InputFilename.c_str();
|
auto input_rom = InputFilename.c_str();
|
||||||
auto output_object = OutputFilename.c_str();
|
auto output_object = OutputFilename.c_str();
|
||||||
auto output_debug = DebugFilename.getNumOccurrences() ? DebugFilename.c_str() : nullptr;
|
auto output_debug = DebugFilename.getNumOccurrences() ? DebugFilename.c_str() : nullptr;
|
||||||
|
bool verify = Verify;
|
||||||
|
|
||||||
Core::Init();
|
Core::Init();
|
||||||
Memory::Init();
|
Memory::Init();
|
||||||
@ -50,6 +52,6 @@ int main(int argc, const char *const *argv)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
CodeGen code_generator(output_object, output_debug);
|
CodeGen code_generator(output_object, output_debug, verify);
|
||||||
code_generator.Run();
|
code_generator.Run();
|
||||||
}
|
}
|
@ -3717,6 +3717,7 @@ unsigned InterpreterMainLoop(ARMul_State* cpu) {
|
|||||||
#define GOTO_NEXT_INST \
|
#define GOTO_NEXT_INST \
|
||||||
if (num_instrs >= cpu->NumInstrsToExecute) goto END; \
|
if (num_instrs >= cpu->NumInstrsToExecute) goto END; \
|
||||||
num_instrs++; \
|
num_instrs++; \
|
||||||
|
BinaryTranslationLoader::VerifyCallback(); \
|
||||||
switch(inst_base->idx) { \
|
switch(inst_base->idx) { \
|
||||||
case 0: goto VMLA_INST; \
|
case 0: goto VMLA_INST; \
|
||||||
case 1: goto VMLS_INST; \
|
case 1: goto VMLS_INST; \
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
using namespace llvm;
|
using namespace llvm;
|
||||||
|
|
||||||
bool g_enabled = false;
|
bool g_enabled = false;
|
||||||
|
bool g_verify = false;
|
||||||
|
|
||||||
std::unique_ptr<SectionMemoryManager> g_memory_manager;
|
std::unique_ptr<SectionMemoryManager> g_memory_manager;
|
||||||
std::unique_ptr<RuntimeDyld> g_dyld;
|
std::unique_ptr<RuntimeDyld> g_dyld;
|
||||||
@ -17,6 +18,15 @@ std::unique_ptr<RuntimeDyld::LoadedObjectInfo> g_loaded_object_info;
|
|||||||
|
|
||||||
void (*g_run_function)();
|
void (*g_run_function)();
|
||||||
|
|
||||||
|
// Used by the verifier
|
||||||
|
bool (*g_can_run_function)();
|
||||||
|
bool g_have_saved_state; // Whether there is a copied state
|
||||||
|
ARMul_State *g_state;
|
||||||
|
u32 g_regs_copy[16];
|
||||||
|
u32 g_flags_copy[4];
|
||||||
|
u32 g_regs_copy_before[16];
|
||||||
|
u32 g_flags_copy_before[4];
|
||||||
|
|
||||||
void BinaryTranslationLoader::Load(FileUtil::IOFile& file)
|
void BinaryTranslationLoader::Load(FileUtil::IOFile& file)
|
||||||
{
|
{
|
||||||
if (offsetof(ARMul_State, ZFlag) - offsetof(ARMul_State, NFlag) != 4 ||
|
if (offsetof(ARMul_State, ZFlag) - offsetof(ARMul_State, NFlag) != 4 ||
|
||||||
@ -61,6 +71,8 @@ void BinaryTranslationLoader::Load(FileUtil::IOFile& file)
|
|||||||
g_memory_manager->finalizeMemory();
|
g_memory_manager->finalizeMemory();
|
||||||
|
|
||||||
g_run_function = static_cast<decltype(g_run_function)>(g_dyld->getSymbolAddress("Run"));
|
g_run_function = static_cast<decltype(g_run_function)>(g_dyld->getSymbolAddress("Run"));
|
||||||
|
g_can_run_function = static_cast<decltype(g_can_run_function)>(g_dyld->getSymbolAddress("CanRun"));
|
||||||
|
g_verify = *static_cast<bool*>(g_dyld->getSymbolAddress("Verify"));
|
||||||
|
|
||||||
g_enabled = true;
|
g_enabled = true;
|
||||||
}
|
}
|
||||||
@ -74,11 +86,88 @@ void BinaryTranslationLoader::SetCpuState(ARMul_State* state)
|
|||||||
|
|
||||||
*regs_ptr = state->Reg;
|
*regs_ptr = state->Reg;
|
||||||
*flags_ptr = &state->NFlag;
|
*flags_ptr = &state->NFlag;
|
||||||
|
g_have_saved_state = false;
|
||||||
|
g_state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
void BinaryTranslationLoader::Run()
|
void BinaryTranslationLoader::Run()
|
||||||
{
|
{
|
||||||
if (!g_enabled) return;
|
// If verify is enabled, it will run opcodes
|
||||||
|
if (!g_enabled || g_verify) return;
|
||||||
|
|
||||||
g_run_function();
|
g_run_function();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Swap(void *a, void *b, size_t size)
|
||||||
|
{
|
||||||
|
auto a_char = (char *)a;
|
||||||
|
auto b_char = (char *)b;
|
||||||
|
for (auto i = 0; i < size; ++i)
|
||||||
|
{
|
||||||
|
std::swap(a_char[i], b_char[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShowRegs(u32 *regs, u32 *flags)
|
||||||
|
{
|
||||||
|
LOG_ERROR(BinaryTranslator, "%08x %08x %08x %08x %08x %08x %08x %08x",
|
||||||
|
regs[0], regs[1], regs[2], regs[3],
|
||||||
|
regs[4], regs[5], regs[6], regs[7]);
|
||||||
|
LOG_ERROR(BinaryTranslator, "%08x %08x %08x %08x %08x %08x %08x %08x",
|
||||||
|
regs[8], regs[9], regs[10], regs[11],
|
||||||
|
regs[12], regs[13], regs[14], regs[15]);
|
||||||
|
LOG_ERROR(BinaryTranslator, "%01x %01x %01x %01x", flags[0], flags[1], flags[2], flags[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BinaryTranslationLoader::VerifyCallback()
|
||||||
|
{
|
||||||
|
if (!g_enabled || !g_verify) return;
|
||||||
|
|
||||||
|
// Swap the PC to the old state before checking if it can run
|
||||||
|
std::swap(g_regs_copy[15], g_state->Reg[15]);
|
||||||
|
auto can_run = g_can_run_function();
|
||||||
|
std::swap(g_regs_copy[15], g_state->Reg[15]);
|
||||||
|
|
||||||
|
if (g_have_saved_state && can_run)
|
||||||
|
{
|
||||||
|
// An opcode is finished, simulate it
|
||||||
|
|
||||||
|
// Copy the state before
|
||||||
|
memcpy(g_regs_copy_before, g_regs_copy, sizeof(g_regs_copy));
|
||||||
|
memcpy(g_flags_copy_before, &g_flags_copy, sizeof(g_flags_copy));
|
||||||
|
|
||||||
|
// Swap to the state before the opcode
|
||||||
|
Swap(g_state->Reg, g_regs_copy, sizeof(g_regs_copy));
|
||||||
|
Swap(&g_state->NFlag, g_flags_copy, sizeof(g_flags_copy));
|
||||||
|
|
||||||
|
// Run the opcode
|
||||||
|
g_run_function();
|
||||||
|
|
||||||
|
// Test
|
||||||
|
if (memcmp(g_state->Reg, g_regs_copy, sizeof(g_regs_copy)) || memcmp(&g_state->NFlag, g_flags_copy, sizeof(g_flags_copy)))
|
||||||
|
{
|
||||||
|
LOG_ERROR(BinaryTranslator, "Verify failed");
|
||||||
|
LOG_ERROR(BinaryTranslator, "Regs Before");
|
||||||
|
ShowRegs(g_regs_copy_before, g_flags_copy_before);
|
||||||
|
LOG_ERROR(BinaryTranslator, "Regs OK");
|
||||||
|
ShowRegs(g_regs_copy, g_flags_copy);
|
||||||
|
LOG_ERROR(BinaryTranslator, "Regs not OK");
|
||||||
|
ShowRegs(g_state->Reg, &g_state->NFlag);
|
||||||
|
|
||||||
|
// Don't spam
|
||||||
|
g_enabled = false;
|
||||||
|
|
||||||
|
// Make sure it has a valid state to continue to run
|
||||||
|
Swap(g_state->Reg, g_regs_copy, sizeof(g_regs_copy));
|
||||||
|
Swap(&g_state->NFlag, g_flags_copy, sizeof(g_flags_copy));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If this opcode is not translated or there is no saved state, just save the state and continue
|
||||||
|
memcpy(g_regs_copy, g_state->Reg, sizeof(g_regs_copy));
|
||||||
|
memcpy(g_flags_copy, &g_state->NFlag, sizeof(g_flags_copy));
|
||||||
|
|
||||||
|
g_have_saved_state = true;
|
||||||
|
}
|
||||||
|
}
|
@ -11,4 +11,5 @@ public:
|
|||||||
static void SetCpuState(ARMul_State *state);
|
static void SetCpuState(ARMul_State *state);
|
||||||
// Runs the state provided at SetCpuState.
|
// Runs the state provided at SetCpuState.
|
||||||
static void Run();
|
static void Run();
|
||||||
|
static void VerifyCallback();
|
||||||
};
|
};
|
Loading…
Reference in New Issue
Block a user