early-access version 1255

This commit is contained in:
pineappleEA
2020-12-28 15:15:37 +00:00
parent 84b39492d1
commit 78b48028e1
6254 changed files with 1868140 additions and 0 deletions

374
externals/dynarmic/tests/A32/fuzz_arm.cpp vendored Executable file
View File

@@ -0,0 +1,374 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2016 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <algorithm>
#include <array>
#include <cstdio>
#include <functional>
#include <tuple>
#include <vector>
#include <catch.hpp>
#include <dynarmic/A32/a32.h>
#include "common/common_types.h"
#include "common/fp/fpcr.h"
#include "common/fp/fpsr.h"
#include "common/scope_exit.h"
#include "frontend/A32/disassembler/disassembler.h"
#include "frontend/A32/location_descriptor.h"
#include "frontend/A32/translate/translate.h"
#include "frontend/A32/types.h"
#include "frontend/ir/basic_block.h"
#include "frontend/ir/location_descriptor.h"
#include "frontend/ir/opcodes.h"
#include "fuzz_util.h"
#include "rand_int.h"
#include "testenv.h"
#include "unicorn_emu/a32_unicorn.h"
// Must be declared last for all necessary operator<< to be declared prior to this.
#include <fmt/format.h>
#include <fmt/ostream.h>
namespace {
using namespace Dynarmic;
bool ShouldTestInst(u32 instruction, u32 pc, bool is_last_inst) {
const A32::LocationDescriptor location{pc, {}, {}};
IR::Block block{location};
const bool should_continue = A32::TranslateSingleInstruction(block, location, instruction);
if (!should_continue && !is_last_inst) {
return false;
}
if (auto terminal = block.GetTerminal(); boost::get<IR::Term::Interpret>(&terminal)) {
return false;
}
for (const auto& ir_inst : block) {
switch (ir_inst.GetOpcode()) {
case IR::Opcode::A32ExceptionRaised:
case IR::Opcode::A32CallSupervisor:
case IR::Opcode::A32CoprocInternalOperation:
case IR::Opcode::A32CoprocSendOneWord:
case IR::Opcode::A32CoprocSendTwoWords:
case IR::Opcode::A32CoprocGetOneWord:
case IR::Opcode::A32CoprocGetTwoWords:
case IR::Opcode::A32CoprocLoadWords:
case IR::Opcode::A32CoprocStoreWords:
return false;
// Currently unimplemented in Unicorn
case IR::Opcode::FPVectorRecipEstimate16:
case IR::Opcode::FPVectorRSqrtEstimate16:
case IR::Opcode::VectorPolynomialMultiplyLong64:
return false;
default:
continue;
}
}
return true;
}
u32 GenRandomInst(u32 pc, bool is_last_inst) {
static const struct InstructionGeneratorInfo {
std::vector<InstructionGenerator> generators;
std::vector<InstructionGenerator> invalid;
} instructions = []{
const std::vector<std::tuple<std::string, const char*>> list {
#define INST(fn, name, bitstring) {#fn, bitstring},
#include "frontend/A32/decoder/arm.inc"
#include "frontend/A32/decoder/asimd.inc"
#include "frontend/A32/decoder/vfp.inc"
#undef INST
};
std::vector<InstructionGenerator> generators;
std::vector<InstructionGenerator> invalid;
// List of instructions not to test
static constexpr std::array do_not_test {
// Translating load/stores
"arm_LDRBT", "arm_LDRBT", "arm_LDRHT", "arm_LDRHT", "arm_LDRSBT", "arm_LDRSBT", "arm_LDRSHT", "arm_LDRSHT", "arm_LDRT", "arm_LDRT",
"arm_STRBT", "arm_STRBT", "arm_STRHT", "arm_STRHT", "arm_STRT", "arm_STRT",
// Exclusive load/stores
"arm_LDREXB", "arm_LDREXD", "arm_LDREXH", "arm_LDREX", "arm_LDAEXB", "arm_LDAEXD", "arm_LDAEXH", "arm_LDAEX",
"arm_STREXB", "arm_STREXD", "arm_STREXH", "arm_STREX", "arm_STLEXB", "arm_STLEXD", "arm_STLEXH", "arm_STLEX",
"arm_SWP", "arm_SWPB",
// Elevated load/store multiple instructions.
"arm_LDM_eret", "arm_LDM_usr",
"arm_STM_usr",
// Hint instructions
"arm_NOP", "arm_PLD_imm", "arm_PLD_reg", "arm_SEV",
"arm_WFE", "arm_WFI", "arm_YIELD",
// E, T, J
"arm_BLX_reg", "arm_BLX_imm", "arm_BXJ", "arm_SETEND",
// Coprocessor
"arm_CDP", "arm_LDC", "arm_MCR", "arm_MCRR", "arm_MRC", "arm_MRRC", "arm_STC",
// System
"arm_CPS", "arm_RFE", "arm_SRS",
// Undefined
"arm_UDF",
// FPSCR is inaccurate
"vfp_VMRS",
// Incorrect Unicorn implementations
"asimd_VRECPS", // Unicorn does not fuse the multiply and subtraction, resulting in being off by 1ULP.
"asimd_VRSQRTS", // Unicorn does not fuse the multiply and subtraction, resulting in being off by 1ULP.
"vfp_VCVT_from_fixed", // Unicorn does not do round-to-nearest-even for this instruction correctly.
};
for (const auto& [fn, bitstring] : list) {
if (std::find(do_not_test.begin(), do_not_test.end(), fn) != do_not_test.end()) {
invalid.emplace_back(InstructionGenerator{bitstring});
continue;
}
generators.emplace_back(InstructionGenerator{bitstring});
}
return InstructionGeneratorInfo{generators, invalid};
}();
while (true) {
const size_t index = RandInt<size_t>(0, instructions.generators.size() - 1);
const u32 inst = instructions.generators[index].Generate();
if ((instructions.generators[index].Mask() & 0xF0000000) == 0 && (inst & 0xF0000000) == 0xF0000000) {
continue;
}
if (ShouldTestInst(inst, pc, is_last_inst)) {
return inst;
}
}
}
Dynarmic::A32::UserConfig GetUserConfig(ArmTestEnv& testenv) {
Dynarmic::A32::UserConfig user_config;
user_config.optimizations &= ~OptimizationFlag::FastDispatch;
user_config.callbacks = &testenv;
user_config.always_little_endian = true;
return user_config;
}
static void RunTestInstance(Dynarmic::A32::Jit& jit, A32Unicorn<ArmTestEnv>& uni,
ArmTestEnv& jit_env, ArmTestEnv& uni_env,
const A32Unicorn<ArmTestEnv>::RegisterArray& regs,
const A32Unicorn<ArmTestEnv>::ExtRegArray& vecs,
const std::vector<u32>& instructions, const u32 cpsr, const u32 fpscr) {
const u32 initial_pc = regs[15];
const u32 num_words = initial_pc / sizeof(u32);
const u32 code_mem_size = num_words + static_cast<u32>(instructions.size());
jit_env.code_mem.resize(code_mem_size + 1);
uni_env.code_mem.resize(code_mem_size + 1);
std::copy(instructions.begin(), instructions.end(), jit_env.code_mem.begin() + num_words);
std::copy(instructions.begin(), instructions.end(), uni_env.code_mem.begin() + num_words);
jit_env.code_mem.back() = 0xEAFFFFFE; // B .
uni_env.code_mem.back() = 0xEAFFFFFE; // B .
jit_env.modified_memory.clear();
uni_env.modified_memory.clear();
jit_env.interrupts.clear();
uni_env.interrupts.clear();
jit.Regs() = regs;
jit.ExtRegs() = vecs;
jit.SetFpscr(fpscr);
jit.SetCpsr(cpsr);
jit.ClearCache();
uni.SetRegisters(regs);
uni.SetExtRegs(vecs);
uni.SetFpscr(fpscr);
uni.EnableFloatingPointAccess();
uni.SetCpsr(cpsr);
uni.ClearPageCache();
jit_env.ticks_left = instructions.size();
jit.Run();
uni_env.ticks_left = instructions.size();
uni.Run();
SCOPE_FAIL {
fmt::print("Instruction Listing:\n");
for (u32 instruction : instructions) {
fmt::print("{:08x} {}\n", instruction, A32::DisassembleArm(instruction));
}
fmt::print("\n");
fmt::print("Initial register listing:\n");
for (size_t i = 0; i < regs.size(); ++i) {
fmt::print("{:3s}: {:08x}\n", static_cast<A32::Reg>(i), regs[i]);
}
for (size_t i = 0; i < vecs.size(); ++i) {
fmt::print("{:3s}: {:08x}\n", static_cast<A32::ExtReg>(i), vecs[i]);
}
fmt::print("cpsr {:08x}\n", cpsr);
fmt::print("fpcr {:08x}\n", fpscr);
fmt::print("fpcr.AHP {}\n", FP::FPCR{fpscr}.AHP());
fmt::print("fpcr.DN {}\n", FP::FPCR{fpscr}.DN());
fmt::print("fpcr.FZ {}\n", FP::FPCR{fpscr}.FZ());
fmt::print("fpcr.RMode {}\n", static_cast<size_t>(FP::FPCR{fpscr}.RMode()));
fmt::print("fpcr.FZ16 {}\n", FP::FPCR{fpscr}.FZ16());
fmt::print("\n");
fmt::print("Final register listing:\n");
fmt::print(" unicorn dynarmic\n");
const auto uni_regs = uni.GetRegisters();
for (size_t i = 0; i < regs.size(); ++i) {
fmt::print("{:3s}: {:08x} {:08x} {}\n", static_cast<A32::Reg>(i), uni_regs[i], jit.Regs()[i], uni_regs[i] != jit.Regs()[i] ? "*" : "");
}
const auto uni_ext_regs = uni.GetExtRegs();
for (size_t i = 0; i < vecs.size(); ++i) {
fmt::print("s{:2d}: {:08x} {:08x} {}\n", static_cast<size_t>(i), uni_ext_regs[i], jit.ExtRegs()[i], uni_ext_regs[i] != jit.ExtRegs()[i] ? "*" : "");
}
fmt::print("cpsr {:08x} {:08x} {}\n", uni.GetCpsr(), jit.Cpsr(), uni.GetCpsr() != jit.Cpsr() ? "*" : "");
fmt::print("fpsr {:08x} {:08x} {}\n", uni.GetFpscr(), jit.Fpscr(), (uni.GetFpscr() & 0xF0000000) != (jit.Fpscr() & 0xF0000000) ? "*" : "");
fmt::print("\n");
fmt::print("Modified memory:\n");
fmt::print(" uni dyn\n");
auto uni_iter = uni_env.modified_memory.begin();
auto jit_iter = jit_env.modified_memory.begin();
while (uni_iter != uni_env.modified_memory.end() || jit_iter != jit_env.modified_memory.end()) {
if (uni_iter == uni_env.modified_memory.end() || (jit_iter != jit_env.modified_memory.end() && uni_iter->first > jit_iter->first)) {
fmt::print("{:08x}: {:02x} *\n", jit_iter->first, jit_iter->second);
jit_iter++;
} else if (jit_iter == jit_env.modified_memory.end() || jit_iter->first > uni_iter->first) {
fmt::print("{:08x}: {:02x} *\n", uni_iter->first, uni_iter->second);
uni_iter++;
} else if (uni_iter->first == jit_iter->first) {
fmt::print("{:08x}: {:02x} {:02x} {}\n", uni_iter->first, uni_iter->second, jit_iter->second, uni_iter->second != jit_iter->second ? "*" : "");
uni_iter++;
jit_iter++;
}
}
fmt::print("\n");
fmt::print("x86_64:\n");
fmt::print("{}\n", jit.Disassemble());
fmt::print("Interrupts:\n");
for (const auto& i : uni_env.interrupts) {
std::puts(i.c_str());
}
};
REQUIRE(uni_env.code_mem_modified_by_guest == jit_env.code_mem_modified_by_guest);
if (uni_env.code_mem_modified_by_guest) {
return;
}
// Qemu doesn't do Thumb transitions??
{
const u32 uni_pc = uni.GetPC();
const bool is_thumb = (jit.Cpsr() & (1 << 5)) != 0;
const u32 new_uni_pc = uni_pc & (is_thumb ? 0xFFFFFFFE : 0xFFFFFFFC);
uni.SetPC(new_uni_pc);
}
REQUIRE(uni.GetRegisters() == jit.Regs());
REQUIRE(uni.GetExtRegs() == jit.ExtRegs());
REQUIRE((uni.GetCpsr() & 0xFFFFFDDF) == (jit.Cpsr() & 0xFFFFFDDF));
REQUIRE((uni.GetFpscr() & 0xF0000000) == (jit.Fpscr() & 0xF0000000));
REQUIRE(uni_env.modified_memory == jit_env.modified_memory);
REQUIRE(uni_env.interrupts.empty());
}
} // Anonymous namespace
TEST_CASE("A32: Single random instruction", "[arm]") {
ArmTestEnv jit_env{};
ArmTestEnv uni_env{};
Dynarmic::A32::Jit jit{GetUserConfig(jit_env)};
A32Unicorn<ArmTestEnv> uni{uni_env};
A32Unicorn<ArmTestEnv>::RegisterArray regs;
A32Unicorn<ArmTestEnv>::ExtRegArray ext_reg;
std::vector<u32> instructions(1);
for (size_t iteration = 0; iteration < 100000; ++iteration) {
std::generate(regs.begin(), regs.end(), [] { return RandInt<u32>(0, ~u32(0)); });
std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt<u32>(0, ~u32(0)); });
instructions[0] = GenRandomInst(0, true);
const u32 start_address = 100;
const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x10;
const u32 fpcr = RandomFpcr();
INFO("Instruction: 0x" << std::hex << instructions[0]);
regs[15] = start_address;
RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr);
}
}
TEST_CASE("A32: Small random block", "[arm]") {
ArmTestEnv jit_env{};
ArmTestEnv uni_env{};
Dynarmic::A32::Jit jit{GetUserConfig(jit_env)};
A32Unicorn<ArmTestEnv> uni{uni_env};
A32Unicorn<ArmTestEnv>::RegisterArray regs;
A32Unicorn<ArmTestEnv>::ExtRegArray ext_reg;
std::vector<u32> instructions(5);
for (size_t iteration = 0; iteration < 100000; ++iteration) {
std::generate(regs.begin(), regs.end(), [] { return RandInt<u32>(0, ~u32(0)); });
std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt<u32>(0, ~u32(0)); });
instructions[0] = GenRandomInst(0, false);
instructions[1] = GenRandomInst(4, false);
instructions[2] = GenRandomInst(8, false);
instructions[3] = GenRandomInst(12, false);
instructions[4] = GenRandomInst(16, true);
const u32 start_address = 100;
const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x10;
const u32 fpcr = RandomFpcr();
INFO("Instruction 1: 0x" << std::hex << instructions[0]);
INFO("Instruction 2: 0x" << std::hex << instructions[1]);
INFO("Instruction 3: 0x" << std::hex << instructions[2]);
INFO("Instruction 4: 0x" << std::hex << instructions[3]);
INFO("Instruction 5: 0x" << std::hex << instructions[4]);
regs[15] = start_address;
RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr);
}
}
TEST_CASE("A32: Large random block", "[arm]") {
ArmTestEnv jit_env{};
ArmTestEnv uni_env{};
Dynarmic::A32::Jit jit{GetUserConfig(jit_env)};
A32Unicorn<ArmTestEnv> uni{uni_env};
A32Unicorn<ArmTestEnv>::RegisterArray regs;
A32Unicorn<ArmTestEnv>::ExtRegArray ext_reg;
constexpr size_t instruction_count = 100;
std::vector<u32> instructions(instruction_count);
for (size_t iteration = 0; iteration < 10000; ++iteration) {
std::generate(regs.begin(), regs.end(), [] { return RandInt<u32>(0, ~u32(0)); });
std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt<u32>(0, ~u32(0)); });
for (size_t j = 0; j < instruction_count; ++j) {
instructions[j] = GenRandomInst(j * 4, j == instruction_count - 1);
}
const u64 start_address = 100;
const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x10;
const u32 fpcr = RandomFpcr();
regs[15] = start_address;
RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr);
}
}

351
externals/dynarmic/tests/A32/fuzz_thumb.cpp vendored Executable file
View File

@@ -0,0 +1,351 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2016 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <algorithm>
#include <array>
#include <cinttypes>
#include <cstdio>
#include <cstring>
#include <functional>
#include <tuple>
#include <catch.hpp>
#include <dynarmic/A32/a32.h>
#include "common/bit_util.h"
#include "common/common_types.h"
#include "frontend/A32/disassembler/disassembler.h"
#include "frontend/A32/FPSCR.h"
#include "frontend/A32/location_descriptor.h"
#include "frontend/A32/PSR.h"
#include "frontend/A32/translate/translate.h"
#include "frontend/ir/basic_block.h"
#include "ir_opt/passes.h"
#include "rand_int.h"
#include "testenv.h"
#include "unicorn_emu/a32_unicorn.h"
using namespace Dynarmic;
static A32::UserConfig GetUserConfig(ThumbTestEnv* testenv) {
A32::UserConfig user_config;
user_config.optimizations &= ~OptimizationFlag::FastDispatch;
user_config.callbacks = testenv;
return user_config;
}
using WriteRecords = std::map<u32, u8>;
struct ThumbInstGen final {
public:
ThumbInstGen(const char* format, std::function<bool(u16)> is_valid = [](u16){ return true; }) : is_valid(is_valid) {
REQUIRE(strlen(format) == 16);
for (int i = 0; i < 16; i++) {
const u16 bit = 1 << (15 - i);
switch (format[i]) {
case '0':
mask |= bit;
break;
case '1':
bits |= bit;
mask |= bit;
break;
default:
// Do nothing
break;
}
}
}
u16 Generate() const {
u16 inst;
do {
const u16 random = RandInt<u16>(0, 0xFFFF);
inst = bits | (random & ~mask);
} while (!is_valid(inst));
ASSERT((inst & mask) == bits);
return inst;
}
private:
u16 bits = 0;
u16 mask = 0;
std::function<bool(u16)> is_valid;
};
static bool DoesBehaviorMatch(const A32Unicorn<ThumbTestEnv>& uni, const A32::Jit& jit,
const WriteRecords& interp_write_records, const WriteRecords& jit_write_records) {
const auto interp_regs = uni.GetRegisters();
const auto jit_regs = jit.Regs();
return std::equal(interp_regs.begin(), interp_regs.end(), jit_regs.begin(), jit_regs.end()) &&
uni.GetCpsr() == jit.Cpsr() &&
interp_write_records == jit_write_records;
}
static void RunInstance(size_t run_number, ThumbTestEnv& test_env, A32Unicorn<ThumbTestEnv>& uni, A32::Jit& jit, const ThumbTestEnv::RegisterArray& initial_regs,
size_t instruction_count, size_t instructions_to_execute_count) {
uni.ClearPageCache();
jit.ClearCache();
// Setup initial state
uni.SetCpsr(0x000001F0);
uni.SetRegisters(initial_regs);
jit.SetCpsr(0x000001F0);
jit.Regs() = initial_regs;
// Run interpreter
test_env.modified_memory.clear();
test_env.ticks_left = instructions_to_execute_count;
uni.SetPC(uni.GetPC() | 1);
uni.Run();
const bool uni_code_memory_modified = test_env.code_mem_modified_by_guest;
const auto interp_write_records = test_env.modified_memory;
// Run jit
test_env.code_mem_modified_by_guest = false;
test_env.modified_memory.clear();
test_env.ticks_left = instructions_to_execute_count;
jit.Run();
const bool jit_code_memory_modified = test_env.code_mem_modified_by_guest;
const auto jit_write_records = test_env.modified_memory;
test_env.code_mem_modified_by_guest = false;
REQUIRE(uni_code_memory_modified == jit_code_memory_modified);
if (uni_code_memory_modified) {
return;
}
// Compare
if (!DoesBehaviorMatch(uni, jit, interp_write_records, jit_write_records)) {
printf("Failed at execution number %zu\n", run_number);
printf("\nInstruction Listing: \n");
for (size_t i = 0; i < instruction_count; i++) {
printf("%04x %s\n", test_env.code_mem[i], A32::DisassembleThumb16(test_env.code_mem[i]).c_str());
}
printf("\nInitial Register Listing: \n");
for (size_t i = 0; i < initial_regs.size(); i++) {
printf("%4zu: %08x\n", i, initial_regs[i]);
}
printf("\nFinal Register Listing: \n");
printf(" unicorn jit\n");
const auto uni_registers = uni.GetRegisters();
for (size_t i = 0; i < uni_registers.size(); i++) {
printf("%4zu: %08x %08x %s\n", i, uni_registers[i], jit.Regs()[i], uni_registers[i] != jit.Regs()[i] ? "*" : "");
}
printf("CPSR: %08x %08x %s\n", uni.GetCpsr(), jit.Cpsr(), uni.GetCpsr() != jit.Cpsr() ? "*" : "");
printf("\nUnicorn Write Records:\n");
for (const auto& record : interp_write_records) {
printf("[%08x] = %02x\n", record.first, record.second);
}
printf("\nJIT Write Records:\n");
for (const auto& record : jit_write_records) {
printf("[%08x] = %02x\n", record.first, record.second);
}
A32::PSR cpsr;
cpsr.T(true);
size_t num_insts = 0;
while (num_insts < instructions_to_execute_count) {
A32::LocationDescriptor descriptor = {u32(num_insts * 4), cpsr, A32::FPSCR{}};
IR::Block ir_block = A32::Translate(descriptor, [&test_env](u32 vaddr) { return test_env.MemoryReadCode(vaddr); }, {});
Optimization::A32GetSetElimination(ir_block);
Optimization::DeadCodeElimination(ir_block);
Optimization::A32ConstantMemoryReads(ir_block, &test_env);
Optimization::ConstantPropagation(ir_block);
Optimization::DeadCodeElimination(ir_block);
Optimization::VerificationPass(ir_block);
printf("\n\nIR:\n%s", IR::DumpBlock(ir_block).c_str());
printf("\n\nx86_64:\n%s", jit.Disassemble().c_str());
num_insts += ir_block.CycleCount();
}
#ifdef _MSC_VER
__debugbreak();
#endif
FAIL();
}
}
void FuzzJitThumb(const size_t instruction_count, const size_t instructions_to_execute_count, const size_t run_count, const std::function<u16()> instruction_generator) {
ThumbTestEnv test_env;
// Prepare memory.
test_env.code_mem.resize(instruction_count + 1);
test_env.code_mem.back() = 0xE7FE; // b +#0
// Prepare test subjects
A32Unicorn uni{test_env};
A32::Jit jit{GetUserConfig(&test_env)};
for (size_t run_number = 0; run_number < run_count; run_number++) {
ThumbTestEnv::RegisterArray initial_regs;
std::generate_n(initial_regs.begin(), initial_regs.size() - 1, []{ return RandInt<u32>(0, 0xFFFFFFFF); });
initial_regs[15] = 0;
std::generate_n(test_env.code_mem.begin(), instruction_count, instruction_generator);
RunInstance(run_number, test_env, uni, jit, initial_regs, instruction_count, instructions_to_execute_count);
}
}
TEST_CASE("Fuzz Thumb instructions set 1", "[JitX64][Thumb]") {
const std::array instructions = {
ThumbInstGen("00000xxxxxxxxxxx"), // LSL <Rd>, <Rm>, #<imm5>
ThumbInstGen("00001xxxxxxxxxxx"), // LSR <Rd>, <Rm>, #<imm5>
ThumbInstGen("00010xxxxxxxxxxx"), // ASR <Rd>, <Rm>, #<imm5>
ThumbInstGen("000110oxxxxxxxxx"), // ADD/SUB_reg
ThumbInstGen("000111oxxxxxxxxx"), // ADD/SUB_imm
ThumbInstGen("001ooxxxxxxxxxxx"), // ADD/SUB/CMP/MOV_imm
ThumbInstGen("010000ooooxxxxxx"), // Data Processing
ThumbInstGen("010001000hxxxxxx"), // ADD (high registers)
ThumbInstGen("0100010101xxxxxx", // CMP (high registers)
[](u16 inst){ return Common::Bits<3, 5>(inst) != 0b111; }), // R15 is UNPREDICTABLE
ThumbInstGen("0100010110xxxxxx", // CMP (high registers)
[](u16 inst){ return Common::Bits<0, 2>(inst) != 0b111; }), // R15 is UNPREDICTABLE
ThumbInstGen("010001100hxxxxxx"), // MOV (high registers)
ThumbInstGen("10110000oxxxxxxx"), // Adjust stack pointer
ThumbInstGen("10110010ooxxxxxx"), // SXT/UXT
ThumbInstGen("1011101000xxxxxx"), // REV
ThumbInstGen("1011101001xxxxxx"), // REV16
ThumbInstGen("1011101011xxxxxx"), // REVSH
ThumbInstGen("01001xxxxxxxxxxx"), // LDR Rd, [PC, #]
ThumbInstGen("0101oooxxxxxxxxx"), // LDR/STR Rd, [Rn, Rm]
ThumbInstGen("011xxxxxxxxxxxxx"), // LDR(B)/STR(B) Rd, [Rn, #]
ThumbInstGen("1000xxxxxxxxxxxx"), // LDRH/STRH Rd, [Rn, #offset]
ThumbInstGen("1001xxxxxxxxxxxx"), // LDR/STR Rd, [SP, #]
ThumbInstGen("1011010xxxxxxxxx", // PUSH
[](u16 inst){ return Common::Bits<0, 7>(inst) != 0; }), // Empty reg_list is UNPREDICTABLE
ThumbInstGen("10111100xxxxxxxx", // POP (P = 0)
[](u16 inst){ return Common::Bits<0, 7>(inst) != 0; }), // Empty reg_list is UNPREDICTABLE
ThumbInstGen("1100xxxxxxxxxxxx", // STMIA/LDMIA
[](u16 inst) {
// Ensure that the architecturally undefined case of
// the base register being within the list isn't hit.
const u32 rn = Common::Bits<8, 10>(inst);
return (inst & (1U << rn)) == 0 && Common::Bits<0, 7>(inst) != 0;
}),
// TODO: We should properly test against swapped
// endianness cases, however Unicorn doesn't
// expose the intended endianness of a load/store
// operation to memory through its hooks.
#if 0
ThumbInstGen("101101100101x000"), // SETEND
#endif
};
const auto instruction_select = [&]() -> u16 {
size_t inst_index = RandInt<size_t>(0, instructions.size() - 1);
return instructions[inst_index].Generate();
};
SECTION("single instructions") {
FuzzJitThumb(1, 2, 10000, instruction_select);
}
SECTION("short blocks") {
FuzzJitThumb(5, 6, 3000, instruction_select);
}
// TODO: Test longer blocks when Unicorn can consistently
// run these without going into an infinite loop.
#if 0
SECTION("long blocks") {
FuzzJitThumb(1024, 1025, 1000, instruction_select);
}
#endif
}
TEST_CASE("Fuzz Thumb instructions set 2 (affects PC)", "[JitX64][Thumb]") {
const std::array instructions = {
// TODO: We currently can't test BX/BLX as we have
// no way of preventing the unpredictable
// condition from occurring with the current interface.
// (bits zero and one within the specified register
// must not be address<1:0> == '10'.
#if 0
ThumbInstGen("01000111xmmmm000", // BLX/BX
[](u16 inst){
const u32 Rm = Common::Bits<3, 6>(inst);
return Rm != 15;
}),
#endif
ThumbInstGen("1010oxxxxxxxxxxx"), // add to pc/sp
ThumbInstGen("11100xxxxxxxxxxx"), // B
ThumbInstGen("01000100h0xxxxxx"), // ADD (high registers)
ThumbInstGen("01000110h0xxxxxx"), // MOV (high registers)
ThumbInstGen("1101ccccxxxxxxxx", // B<cond>
[](u16 inst){
const u32 c = Common::Bits<9, 12>(inst);
return c < 0b1110; // Don't want SWI or undefined instructions.
}),
ThumbInstGen("1011o0i1iiiiinnn"), // CBZ/CBNZ
ThumbInstGen("10110110011x0xxx"), // CPS
// TODO: We currently have no control over the generated
// values when creating new pages, so we can't
// reliably test this yet.
#if 0
ThumbInstGen("10111101xxxxxxxx"), // POP (R = 1)
#endif
};
const auto instruction_select = [&]() -> u16 {
size_t inst_index = RandInt<size_t>(0, instructions.size() - 1);
return instructions[inst_index].Generate();
};
FuzzJitThumb(1, 1, 10000, instruction_select);
}
TEST_CASE("Verify fix for off by one error in MemoryRead32 worked", "[Thumb]") {
ThumbTestEnv test_env;
// Prepare test subjects
A32Unicorn<ThumbTestEnv> uni{test_env};
A32::Jit jit{GetUserConfig(&test_env)};
constexpr ThumbTestEnv::RegisterArray initial_regs {
0xe90ecd70,
0x3e3b73c3,
0x571616f9,
0x0b1ef45a,
0xb3a829f2,
0x915a7a6a,
0x579c38f4,
0xd9ffe391,
0x55b6682b,
0x458d8f37,
0x8f3eb3dc,
0xe18c0e7d,
0x6752657a,
0x00001766,
0xdbbf23e3,
0x00000000,
};
test_env.code_mem = {
0x40B8, // lsls r0, r7, #0
0x01CA, // lsls r2, r1, #7
0x83A1, // strh r1, [r4, #28]
0x708A, // strb r2, [r1, #2]
0xBCC4, // pop {r2, r6, r7}
0xE7FE, // b +#0
};
RunInstance(1, test_env, uni, jit, initial_regs, 5, 5);
}

View File

@@ -0,0 +1,381 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2016 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <catch.hpp>
#include "frontend/A32/disassembler/disassembler.h"
using Dynarmic::A32::DisassembleArm;
TEST_CASE("Disassemble branch instructions", "[arm][disassembler]") {
REQUIRE(DisassembleArm(0xEAFFFFFE) == "b +#0");
REQUIRE(DisassembleArm(0xEB000008) == "bl +#40");
REQUIRE(DisassembleArm(0xFBFFFFFE) == "blx +#2");
REQUIRE(DisassembleArm(0xFAFFFFFF) == "blx +#4");
REQUIRE(DisassembleArm(0xFBE1E7FE) == "blx -#7888894");
REQUIRE(DisassembleArm(0xE12FFF3D) == "blx sp");
REQUIRE(DisassembleArm(0x312FFF13) == "bxcc r3");
REQUIRE(DisassembleArm(0x012FFF29) == "bxjeq r9");
}
TEST_CASE("Disassemble data processing instructions", "[arm][disassembler]") {
REQUIRE(DisassembleArm(0xE2A21004) == "adc r1, r2, #4");
REQUIRE(DisassembleArm(0xE0A21143) == "adc r1, r2, r3, asr #2");
REQUIRE(DisassembleArm(0xE0A21103) == "adc r1, r2, r3, lsl #2");
REQUIRE(DisassembleArm(0xE0A21123) == "adc r1, r2, r3, lsr #2");
REQUIRE(DisassembleArm(0xE0A21163) == "adc r1, r2, r3, ror #2");
REQUIRE(DisassembleArm(0xE0A21003) == "adc r1, r2, r3");
REQUIRE(DisassembleArm(0xE0A21063) == "adc r1, r2, r3, rrx");
REQUIRE(DisassembleArm(0xE0A21453) == "adc r1, r2, r3, asr r4");
REQUIRE(DisassembleArm(0xE0A21413) == "adc r1, r2, r3, lsl r4");
REQUIRE(DisassembleArm(0xE0A21433) == "adc r1, r2, r3, lsr r4");
REQUIRE(DisassembleArm(0xE0A21473) == "adc r1, r2, r3, ror r4");
REQUIRE(DisassembleArm(0xE2B21004) == "adcs r1, r2, #4");
REQUIRE(DisassembleArm(0xE0B21143) == "adcs r1, r2, r3, asr #2");
REQUIRE(DisassembleArm(0xE0B21103) == "adcs r1, r2, r3, lsl #2");
REQUIRE(DisassembleArm(0xE0B21123) == "adcs r1, r2, r3, lsr #2");
REQUIRE(DisassembleArm(0xE0B21163) == "adcs r1, r2, r3, ror #2");
REQUIRE(DisassembleArm(0xE0B21003) == "adcs r1, r2, r3");
REQUIRE(DisassembleArm(0xE0B21063) == "adcs r1, r2, r3, rrx");
REQUIRE(DisassembleArm(0xE0B21453) == "adcs r1, r2, r3, asr r4");
REQUIRE(DisassembleArm(0xE0B21413) == "adcs r1, r2, r3, lsl r4");
REQUIRE(DisassembleArm(0xE0B21433) == "adcs r1, r2, r3, lsr r4");
REQUIRE(DisassembleArm(0xE0B21473) == "adcs r1, r2, r3, ror r4");
REQUIRE(DisassembleArm(0xE2853004) == "add r3, r5, #4");
REQUIRE(DisassembleArm(0xE0821143) == "add r1, r2, r3, asr #2");
REQUIRE(DisassembleArm(0xE0821103) == "add r1, r2, r3, lsl #2");
REQUIRE(DisassembleArm(0xE0821123) == "add r1, r2, r3, lsr #2");
REQUIRE(DisassembleArm(0xE0821163) == "add r1, r2, r3, ror #2");
REQUIRE(DisassembleArm(0xE0821003) == "add r1, r2, r3");
REQUIRE(DisassembleArm(0xE0821453) == "add r1, r2, r3, asr r4");
REQUIRE(DisassembleArm(0xE0821413) == "add r1, r2, r3, lsl r4");
REQUIRE(DisassembleArm(0xE0821433) == "add r1, r2, r3, lsr r4");
REQUIRE(DisassembleArm(0xE0821473) == "add r1, r2, r3, ror r4");
REQUIRE(DisassembleArm(0xE0821063) == "add r1, r2, r3, rrx");
REQUIRE(DisassembleArm(0xE2953004) == "adds r3, r5, #4");
REQUIRE(DisassembleArm(0xE0921143) == "adds r1, r2, r3, asr #2");
REQUIRE(DisassembleArm(0xE0921103) == "adds r1, r2, r3, lsl #2");
REQUIRE(DisassembleArm(0xE0921123) == "adds r1, r2, r3, lsr #2");
REQUIRE(DisassembleArm(0xE0921163) == "adds r1, r2, r3, ror #2");
REQUIRE(DisassembleArm(0xE0921003) == "adds r1, r2, r3");
REQUIRE(DisassembleArm(0xE0921063) == "adds r1, r2, r3, rrx");
REQUIRE(DisassembleArm(0xE0921453) == "adds r1, r2, r3, asr r4");
REQUIRE(DisassembleArm(0xE0921413) == "adds r1, r2, r3, lsl r4");
REQUIRE(DisassembleArm(0xE0921433) == "adds r1, r2, r3, lsr r4");
REQUIRE(DisassembleArm(0xE0921473) == "adds r1, r2, r3, ror r4");
REQUIRE(DisassembleArm(0xE2021004) == "and r1, r2, #4");
REQUIRE(DisassembleArm(0xE0021143) == "and r1, r2, r3, asr #2");
REQUIRE(DisassembleArm(0xE0021103) == "and r1, r2, r3, lsl #2");
REQUIRE(DisassembleArm(0xE0021123) == "and r1, r2, r3, lsr #2");
REQUIRE(DisassembleArm(0xE0021163) == "and r1, r2, r3, ror #2");
REQUIRE(DisassembleArm(0xE0021003) == "and r1, r2, r3");
REQUIRE(DisassembleArm(0xE0021453) == "and r1, r2, r3, asr r4");
REQUIRE(DisassembleArm(0xE0021413) == "and r1, r2, r3, lsl r4");
REQUIRE(DisassembleArm(0xE0021433) == "and r1, r2, r3, lsr r4");
REQUIRE(DisassembleArm(0xE0021473) == "and r1, r2, r3, ror r4");
REQUIRE(DisassembleArm(0xE0021063) == "and r1, r2, r3, rrx");
REQUIRE(DisassembleArm(0xE2121004) == "ands r1, r2, #4");
REQUIRE(DisassembleArm(0xE0121143) == "ands r1, r2, r3, asr #2");
REQUIRE(DisassembleArm(0xE0121103) == "ands r1, r2, r3, lsl #2");
REQUIRE(DisassembleArm(0xE0121123) == "ands r1, r2, r3, lsr #2");
REQUIRE(DisassembleArm(0xE0121163) == "ands r1, r2, r3, ror #2");
REQUIRE(DisassembleArm(0xE0121003) == "ands r1, r2, r3");
REQUIRE(DisassembleArm(0xE0121063) == "ands r1, r2, r3, rrx");
REQUIRE(DisassembleArm(0xE0121453) == "ands r1, r2, r3, asr r4");
REQUIRE(DisassembleArm(0xE0121413) == "ands r1, r2, r3, lsl r4");
REQUIRE(DisassembleArm(0xE0121433) == "ands r1, r2, r3, lsr r4");
REQUIRE(DisassembleArm(0xE0121473) == "ands r1, r2, r3, ror r4");
REQUIRE(DisassembleArm(0xE3C21004) == "bic r1, r2, #4");
REQUIRE(DisassembleArm(0xE1C21143) == "bic r1, r2, r3, asr #2");
REQUIRE(DisassembleArm(0xE1C21103) == "bic r1, r2, r3, lsl #2");
REQUIRE(DisassembleArm(0xE1C21123) == "bic r1, r2, r3, lsr #2");
REQUIRE(DisassembleArm(0xE1C21163) == "bic r1, r2, r3, ror #2");
REQUIRE(DisassembleArm(0xE1C21003) == "bic r1, r2, r3");
REQUIRE(DisassembleArm(0xE1C21453) == "bic r1, r2, r3, asr r4");
REQUIRE(DisassembleArm(0xE1C21413) == "bic r1, r2, r3, lsl r4");
REQUIRE(DisassembleArm(0xE1C21433) == "bic r1, r2, r3, lsr r4");
REQUIRE(DisassembleArm(0xE1C21473) == "bic r1, r2, r3, ror r4");
REQUIRE(DisassembleArm(0xE1C21063) == "bic r1, r2, r3, rrx");
REQUIRE(DisassembleArm(0xE3D21004) == "bics r1, r2, #4");
REQUIRE(DisassembleArm(0xE1D21143) == "bics r1, r2, r3, asr #2");
REQUIRE(DisassembleArm(0xE1D21103) == "bics r1, r2, r3, lsl #2");
REQUIRE(DisassembleArm(0xE1D21123) == "bics r1, r2, r3, lsr #2");
REQUIRE(DisassembleArm(0xE1D21163) == "bics r1, r2, r3, ror #2");
REQUIRE(DisassembleArm(0xE1D21003) == "bics r1, r2, r3");
REQUIRE(DisassembleArm(0xE1D21063) == "bics r1, r2, r3, rrx");
REQUIRE(DisassembleArm(0xE1D21453) == "bics r1, r2, r3, asr r4");
REQUIRE(DisassembleArm(0xE1D21413) == "bics r1, r2, r3, lsl r4");
REQUIRE(DisassembleArm(0xE1D21433) == "bics r1, r2, r3, lsr r4");
REQUIRE(DisassembleArm(0xE1D21473) == "bics r1, r2, r3, ror r4");
REQUIRE(DisassembleArm(0xE3710004) == "cmn r1, #4");
REQUIRE(DisassembleArm(0xE1710142) == "cmn r1, r2, asr #2");
REQUIRE(DisassembleArm(0xE1710102) == "cmn r1, r2, lsl #2");
REQUIRE(DisassembleArm(0xE1710122) == "cmn r1, r2, lsr #2");
REQUIRE(DisassembleArm(0xE1710162) == "cmn r1, r2, ror #2");
REQUIRE(DisassembleArm(0xE1710002) == "cmn r1, r2");
REQUIRE(DisassembleArm(0xE1710062) == "cmn r1, r2, rrx");
REQUIRE(DisassembleArm(0xE1710352) == "cmn r1, r2, asr r3");
REQUIRE(DisassembleArm(0xE1710312) == "cmn r1, r2, lsl r3");
REQUIRE(DisassembleArm(0xE1710332) == "cmn r1, r2, lsr r3");
REQUIRE(DisassembleArm(0xE1710372) == "cmn r1, r2, ror r3");
REQUIRE(DisassembleArm(0xE3510004) == "cmp r1, #4");
REQUIRE(DisassembleArm(0xE1510142) == "cmp r1, r2, asr #2");
REQUIRE(DisassembleArm(0xE1510102) == "cmp r1, r2, lsl #2");
REQUIRE(DisassembleArm(0xE1510122) == "cmp r1, r2, lsr #2");
REQUIRE(DisassembleArm(0xE1510162) == "cmp r1, r2, ror #2");
REQUIRE(DisassembleArm(0xE1510002) == "cmp r1, r2");
REQUIRE(DisassembleArm(0xE1510062) == "cmp r1, r2, rrx");
REQUIRE(DisassembleArm(0xE1510352) == "cmp r1, r2, asr r3");
REQUIRE(DisassembleArm(0xE1510312) == "cmp r1, r2, lsl r3");
REQUIRE(DisassembleArm(0xE1510332) == "cmp r1, r2, lsr r3");
REQUIRE(DisassembleArm(0xE1510372) == "cmp r1, r2, ror r3");
REQUIRE(DisassembleArm(0xE2221004) == "eor r1, r2, #4");
REQUIRE(DisassembleArm(0xE0221243) == "eor r1, r2, r3, asr #4");
REQUIRE(DisassembleArm(0xE0221203) == "eor r1, r2, r3, lsl #4");
REQUIRE(DisassembleArm(0xE0221223) == "eor r1, r2, r3, lsr #4");
REQUIRE(DisassembleArm(0xE0221263) == "eor r1, r2, r3, ror #4");
REQUIRE(DisassembleArm(0xE0221003) == "eor r1, r2, r3");
REQUIRE(DisassembleArm(0xE0221453) == "eor r1, r2, r3, asr r4");
REQUIRE(DisassembleArm(0xE0221413) == "eor r1, r2, r3, lsl r4");
REQUIRE(DisassembleArm(0xE0221433) == "eor r1, r2, r3, lsr r4");
REQUIRE(DisassembleArm(0xE0221473) == "eor r1, r2, r3, ror r4");
REQUIRE(DisassembleArm(0xE0221063) == "eor r1, r2, r3, rrx");
REQUIRE(DisassembleArm(0xE2321004) == "eors r1, r2, #4");
REQUIRE(DisassembleArm(0xE0321243) == "eors r1, r2, r3, asr #4");
REQUIRE(DisassembleArm(0xE0321203) == "eors r1, r2, r3, lsl #4");
REQUIRE(DisassembleArm(0xE0321223) == "eors r1, r2, r3, lsr #4");
REQUIRE(DisassembleArm(0xE0321263) == "eors r1, r2, r3, ror #4");
REQUIRE(DisassembleArm(0xE0321003) == "eors r1, r2, r3");
REQUIRE(DisassembleArm(0xE0321453) == "eors r1, r2, r3, asr r4");
REQUIRE(DisassembleArm(0xE0321413) == "eors r1, r2, r3, lsl r4");
REQUIRE(DisassembleArm(0xE0321433) == "eors r1, r2, r3, lsr r4");
REQUIRE(DisassembleArm(0xE0321473) == "eors r1, r2, r3, ror r4");
REQUIRE(DisassembleArm(0xE0321063) == "eors r1, r2, r3, rrx");
REQUIRE(DisassembleArm(0xE3A010FF) == "mov r1, #255");
REQUIRE(DisassembleArm(0xE1A01142) == "mov r1, r2, asr #2");
REQUIRE(DisassembleArm(0xE1A01102) == "mov r1, r2, lsl #2");
REQUIRE(DisassembleArm(0xE1A01122) == "mov r1, r2, lsr #2");
REQUIRE(DisassembleArm(0xE1A01162) == "mov r1, r2, ror #2");
REQUIRE(DisassembleArm(0xE1A01062) == "mov r1, r2, rrx");
REQUIRE(DisassembleArm(0xE1A0E00F) == "mov lr, pc");
REQUIRE(DisassembleArm(0xE3B010FF) == "movs r1, #255");
REQUIRE(DisassembleArm(0xE1B0E00F) == "movs lr, pc");
REQUIRE(DisassembleArm(0xE1B01142) == "movs r1, r2, asr #2");
REQUIRE(DisassembleArm(0xE1B01102) == "movs r1, r2, lsl #2");
REQUIRE(DisassembleArm(0xE1B01122) == "movs r1, r2, lsr #2");
REQUIRE(DisassembleArm(0xE1B01162) == "movs r1, r2, ror #2");
REQUIRE(DisassembleArm(0xE3E01004) == "mvn r1, #4");
REQUIRE(DisassembleArm(0xE1E01142) == "mvn r1, r2, asr #2");
REQUIRE(DisassembleArm(0xE1E01102) == "mvn r1, r2, lsl #2");
REQUIRE(DisassembleArm(0xE1E01122) == "mvn r1, r2, lsr #2");
REQUIRE(DisassembleArm(0xE1E01162) == "mvn r1, r2, ror #2");
REQUIRE(DisassembleArm(0xE1E01062) == "mvn r1, r2, rrx");
REQUIRE(DisassembleArm(0xE1E01002) == "mvn r1, r2");
REQUIRE(DisassembleArm(0xE1E01352) == "mvn r1, r2, asr r3");
REQUIRE(DisassembleArm(0xE1E01312) == "mvn r1, r2, lsl r3");
REQUIRE(DisassembleArm(0xE1E01332) == "mvn r1, r2, lsr r3");
REQUIRE(DisassembleArm(0xE1E01372) == "mvn r1, r2, ror r3");
REQUIRE(DisassembleArm(0xE3F01004) == "mvns r1, #4");
REQUIRE(DisassembleArm(0xE1F01142) == "mvns r1, r2, asr #2");
REQUIRE(DisassembleArm(0xE1F01102) == "mvns r1, r2, lsl #2");
REQUIRE(DisassembleArm(0xE1F01122) == "mvns r1, r2, lsr #2");
REQUIRE(DisassembleArm(0xE1F01162) == "mvns r1, r2, ror #2");
REQUIRE(DisassembleArm(0xE1F01062) == "mvns r1, r2, rrx");
REQUIRE(DisassembleArm(0xE1F01002) == "mvns r1, r2");
REQUIRE(DisassembleArm(0xE1F01352) == "mvns r1, r2, asr r3");
REQUIRE(DisassembleArm(0xE1F01312) == "mvns r1, r2, lsl r3");
REQUIRE(DisassembleArm(0xE1F01332) == "mvns r1, r2, lsr r3");
REQUIRE(DisassembleArm(0xE1F01372) == "mvns r1, r2, ror r3");
REQUIRE(DisassembleArm(0xE3821004) == "orr r1, r2, #4");
REQUIRE(DisassembleArm(0xE1821143) == "orr r1, r2, r3, asr #2");
REQUIRE(DisassembleArm(0xE1821103) == "orr r1, r2, r3, lsl #2");
REQUIRE(DisassembleArm(0xE1821123) == "orr r1, r2, r3, lsr #2");
REQUIRE(DisassembleArm(0xE1821163) == "orr r1, r2, r3, ror #2");
REQUIRE(DisassembleArm(0xE1821063) == "orr r1, r2, r3, rrx");
REQUIRE(DisassembleArm(0xE1821003) == "orr r1, r2, r3");
REQUIRE(DisassembleArm(0xE1821453) == "orr r1, r2, r3, asr r4");
REQUIRE(DisassembleArm(0xE1821413) == "orr r1, r2, r3, lsl r4");
REQUIRE(DisassembleArm(0xE1821433) == "orr r1, r2, r3, lsr r4");
REQUIRE(DisassembleArm(0xE1821473) == "orr r1, r2, r3, ror r4");
REQUIRE(DisassembleArm(0xE3921004) == "orrs r1, r2, #4");
REQUIRE(DisassembleArm(0xE1921143) == "orrs r1, r2, r3, asr #2");
REQUIRE(DisassembleArm(0xE1921103) == "orrs r1, r2, r3, lsl #2");
REQUIRE(DisassembleArm(0xE1921123) == "orrs r1, r2, r3, lsr #2");
REQUIRE(DisassembleArm(0xE1921163) == "orrs r1, r2, r3, ror #2");
REQUIRE(DisassembleArm(0xE1921063) == "orrs r1, r2, r3, rrx");
REQUIRE(DisassembleArm(0xE1921003) == "orrs r1, r2, r3");
REQUIRE(DisassembleArm(0xE1921453) == "orrs r1, r2, r3, asr r4");
REQUIRE(DisassembleArm(0xE1921413) == "orrs r1, r2, r3, lsl r4");
REQUIRE(DisassembleArm(0xE1921433) == "orrs r1, r2, r3, lsr r4");
REQUIRE(DisassembleArm(0xE1921473) == "orrs r1, r2, r3, ror r4");
REQUIRE(DisassembleArm(0xE2621004) == "rsb r1, r2, #4");
REQUIRE(DisassembleArm(0xE0621143) == "rsb r1, r2, r3, asr #2");
REQUIRE(DisassembleArm(0xE0621103) == "rsb r1, r2, r3, lsl #2");
REQUIRE(DisassembleArm(0xE0621123) == "rsb r1, r2, r3, lsr #2");
REQUIRE(DisassembleArm(0xE0621163) == "rsb r1, r2, r3, ror #2");
REQUIRE(DisassembleArm(0xE0621063) == "rsb r1, r2, r3, rrx");
REQUIRE(DisassembleArm(0xE0621003) == "rsb r1, r2, r3");
REQUIRE(DisassembleArm(0xE0621453) == "rsb r1, r2, r3, asr r4");
REQUIRE(DisassembleArm(0xE0621413) == "rsb r1, r2, r3, lsl r4");
REQUIRE(DisassembleArm(0xE0621433) == "rsb r1, r2, r3, lsr r4");
REQUIRE(DisassembleArm(0xE0621473) == "rsb r1, r2, r3, ror r4");
REQUIRE(DisassembleArm(0xE2721004) == "rsbs r1, r2, #4");
REQUIRE(DisassembleArm(0xE0721143) == "rsbs r1, r2, r3, asr #2");
REQUIRE(DisassembleArm(0xE0721103) == "rsbs r1, r2, r3, lsl #2");
REQUIRE(DisassembleArm(0xE0721123) == "rsbs r1, r2, r3, lsr #2");
REQUIRE(DisassembleArm(0xE0721163) == "rsbs r1, r2, r3, ror #2");
REQUIRE(DisassembleArm(0xE0721063) == "rsbs r1, r2, r3, rrx");
REQUIRE(DisassembleArm(0xE0721003) == "rsbs r1, r2, r3");
REQUIRE(DisassembleArm(0xE0721453) == "rsbs r1, r2, r3, asr r4");
REQUIRE(DisassembleArm(0xE0721413) == "rsbs r1, r2, r3, lsl r4");
REQUIRE(DisassembleArm(0xE0721433) == "rsbs r1, r2, r3, lsr r4");
REQUIRE(DisassembleArm(0xE0721473) == "rsbs r1, r2, r3, ror r4");
REQUIRE(DisassembleArm(0xE2E21004) == "rsc r1, r2, #4");
REQUIRE(DisassembleArm(0xE0E21143) == "rsc r1, r2, r3, asr #2");
REQUIRE(DisassembleArm(0xE0E21103) == "rsc r1, r2, r3, lsl #2");
REQUIRE(DisassembleArm(0xE0E21123) == "rsc r1, r2, r3, lsr #2");
REQUIRE(DisassembleArm(0xE0E21163) == "rsc r1, r2, r3, ror #2");
REQUIRE(DisassembleArm(0xE0E21063) == "rsc r1, r2, r3, rrx");
REQUIRE(DisassembleArm(0xE0E21003) == "rsc r1, r2, r3");
REQUIRE(DisassembleArm(0xE0E21453) == "rsc r1, r2, r3, asr r4");
REQUIRE(DisassembleArm(0xE0E21413) == "rsc r1, r2, r3, lsl r4");
REQUIRE(DisassembleArm(0xE0E21433) == "rsc r1, r2, r3, lsr r4");
REQUIRE(DisassembleArm(0xE0E21473) == "rsc r1, r2, r3, ror r4");
REQUIRE(DisassembleArm(0xE2F21004) == "rscs r1, r2, #4");
REQUIRE(DisassembleArm(0xE0F21143) == "rscs r1, r2, r3, asr #2");
REQUIRE(DisassembleArm(0xE0F21103) == "rscs r1, r2, r3, lsl #2");
REQUIRE(DisassembleArm(0xE0F21123) == "rscs r1, r2, r3, lsr #2");
REQUIRE(DisassembleArm(0xE0F21163) == "rscs r1, r2, r3, ror #2");
REQUIRE(DisassembleArm(0xE0F21063) == "rscs r1, r2, r3, rrx");
REQUIRE(DisassembleArm(0xE0F21003) == "rscs r1, r2, r3");
REQUIRE(DisassembleArm(0xE0F21453) == "rscs r1, r2, r3, asr r4");
REQUIRE(DisassembleArm(0xE0F21413) == "rscs r1, r2, r3, lsl r4");
REQUIRE(DisassembleArm(0xE0F21433) == "rscs r1, r2, r3, lsr r4");
REQUIRE(DisassembleArm(0xE0F21473) == "rscs r1, r2, r3, ror r4");
REQUIRE(DisassembleArm(0xE2C21004) == "sbc r1, r2, #4");
REQUIRE(DisassembleArm(0xE0C21143) == "sbc r1, r2, r3, asr #2");
REQUIRE(DisassembleArm(0xE0C21103) == "sbc r1, r2, r3, lsl #2");
REQUIRE(DisassembleArm(0xE0C21123) == "sbc r1, r2, r3, lsr #2");
REQUIRE(DisassembleArm(0xE0C21163) == "sbc r1, r2, r3, ror #2");
REQUIRE(DisassembleArm(0xE0C21063) == "sbc r1, r2, r3, rrx");
REQUIRE(DisassembleArm(0xE0C21003) == "sbc r1, r2, r3");
REQUIRE(DisassembleArm(0xE0C21453) == "sbc r1, r2, r3, asr r4");
REQUIRE(DisassembleArm(0xE0C21413) == "sbc r1, r2, r3, lsl r4");
REQUIRE(DisassembleArm(0xE0C21433) == "sbc r1, r2, r3, lsr r4");
REQUIRE(DisassembleArm(0xE0C21473) == "sbc r1, r2, r3, ror r4");
REQUIRE(DisassembleArm(0xE2D21004) == "sbcs r1, r2, #4");
REQUIRE(DisassembleArm(0xE0D21143) == "sbcs r1, r2, r3, asr #2");
REQUIRE(DisassembleArm(0xE0D21103) == "sbcs r1, r2, r3, lsl #2");
REQUIRE(DisassembleArm(0xE0D21123) == "sbcs r1, r2, r3, lsr #2");
REQUIRE(DisassembleArm(0xE0D21163) == "sbcs r1, r2, r3, ror #2");
REQUIRE(DisassembleArm(0xE0D21063) == "sbcs r1, r2, r3, rrx");
REQUIRE(DisassembleArm(0xE0D21003) == "sbcs r1, r2, r3");
REQUIRE(DisassembleArm(0xE0D21453) == "sbcs r1, r2, r3, asr r4");
REQUIRE(DisassembleArm(0xE0D21413) == "sbcs r1, r2, r3, lsl r4");
REQUIRE(DisassembleArm(0xE0D21433) == "sbcs r1, r2, r3, lsr r4");
REQUIRE(DisassembleArm(0xE0D21473) == "sbcs r1, r2, r3, ror r4");
REQUIRE(DisassembleArm(0xE3310004) == "teq r1, #4");
REQUIRE(DisassembleArm(0xE1310142) == "teq r1, r2, asr #2");
REQUIRE(DisassembleArm(0xE1310102) == "teq r1, r2, lsl #2");
REQUIRE(DisassembleArm(0xE1310122) == "teq r1, r2, lsr #2");
REQUIRE(DisassembleArm(0xE1310162) == "teq r1, r2, ror #2");
REQUIRE(DisassembleArm(0xE1310002) == "teq r1, r2");
REQUIRE(DisassembleArm(0xE1310062) == "teq r1, r2, rrx");
REQUIRE(DisassembleArm(0xE1310352) == "teq r1, r2, asr r3");
REQUIRE(DisassembleArm(0xE1310312) == "teq r1, r2, lsl r3");
REQUIRE(DisassembleArm(0xE1310332) == "teq r1, r2, lsr r3");
REQUIRE(DisassembleArm(0xE1310372) == "teq r1, r2, ror r3");
REQUIRE(DisassembleArm(0xE3110004) == "tst r1, #4");
REQUIRE(DisassembleArm(0xE1110142) == "tst r1, r2, asr #2");
REQUIRE(DisassembleArm(0xE1110102) == "tst r1, r2, lsl #2");
REQUIRE(DisassembleArm(0xE1110122) == "tst r1, r2, lsr #2");
REQUIRE(DisassembleArm(0xE1110162) == "tst r1, r2, ror #2");
REQUIRE(DisassembleArm(0xE1110002) == "tst r1, r2");
REQUIRE(DisassembleArm(0xE1110062) == "tst r1, r2, rrx");
REQUIRE(DisassembleArm(0xE1110352) == "tst r1, r2, asr r3");
REQUIRE(DisassembleArm(0xE1110312) == "tst r1, r2, lsl r3");
REQUIRE(DisassembleArm(0xE1110332) == "tst r1, r2, lsr r3");
REQUIRE(DisassembleArm(0xE1110372) == "tst r1, r2, ror r3");
}
TEST_CASE("Disassemble half-word multiply and multiply accumulate instructions", "[arm][disassembler]") {
REQUIRE(DisassembleArm(0xE1003281) == "smlabb r0, r1, r2, r3");
REQUIRE(DisassembleArm(0xE10032C1) == "smlabt r0, r1, r2, r3");
REQUIRE(DisassembleArm(0xE10032A1) == "smlatb r0, r1, r2, r3");
REQUIRE(DisassembleArm(0xE10032E1) == "smlatt r0, r1, r2, r3");
REQUIRE(DisassembleArm(0xE1203281) == "smlawb r0, r1, r2, r3");
REQUIRE(DisassembleArm(0xE12032C1) == "smlawt r0, r1, r2, r3");
REQUIRE(DisassembleArm(0xE12002A1) == "smulwb r0, r1, r2");
REQUIRE(DisassembleArm(0xE12002E1) == "smulwt r0, r1, r2");
REQUIRE(DisassembleArm(0xE1410382) == "smlalbb r0, r1, r2, r3");
REQUIRE(DisassembleArm(0xE14103C2) == "smlalbt r0, r1, r2, r3");
REQUIRE(DisassembleArm(0xE14103A2) == "smlaltb r0, r1, r2, r3");
REQUIRE(DisassembleArm(0xE14103E2) == "smlaltt r0, r1, r2, r3");
REQUIRE(DisassembleArm(0xE1600281) == "smulbb r0, r1, r2");
REQUIRE(DisassembleArm(0xE16002C1) == "smulbt r0, r1, r2");
REQUIRE(DisassembleArm(0xE16002A1) == "smultb r0, r1, r2");
REQUIRE(DisassembleArm(0xE16002E1) == "smultt r0, r1, r2");
}
TEST_CASE("Disassemble multiply and multiply accumulate instructions", "[arm][disassembler]") {
REQUIRE(DisassembleArm(0xE0214392) == "mla r1, r2, r3, r4");
REQUIRE(DisassembleArm(0xE0314392) == "mlas r1, r2, r3, r4");
REQUIRE(DisassembleArm(0xE0010392) == "mul r1, r2, r3");
REQUIRE(DisassembleArm(0xE0110392) == "muls r1, r2, r3");
// TODO: MLS should be here whenever it's supported.
REQUIRE(DisassembleArm(0xE0E21493) == "smlal r1, r2, r3, r4");
REQUIRE(DisassembleArm(0xE0F21493) == "smlals r1, r2, r3, r4");
REQUIRE(DisassembleArm(0xE0C21493) == "smull r1, r2, r3, r4");
REQUIRE(DisassembleArm(0xE0D21493) == "smulls r1, r2, r3, r4");
REQUIRE(DisassembleArm(0xE0421493) == "umaal r1, r2, r3, r4");
REQUIRE(DisassembleArm(0xE0A21493) == "umlal r1, r2, r3, r4");
REQUIRE(DisassembleArm(0xE0B21493) == "umlals r1, r2, r3, r4");
REQUIRE(DisassembleArm(0xE0821493) == "umull r1, r2, r3, r4");
REQUIRE(DisassembleArm(0xE0921493) == "umulls r1, r2, r3, r4");
}
TEST_CASE("Disassemble synchronization primitive instructions", "[arm][disassembler]") {
REQUIRE(DisassembleArm(0xE1921F9F) == "ldrex r1, [r2]");
REQUIRE(DisassembleArm(0xE1D21F9F) == "ldrexb r1, [r2]");
REQUIRE(DisassembleArm(0xE1B31F9F) == "ldrexd r1, r2, [r3]");
REQUIRE(DisassembleArm(0xE1F21F9F) == "ldrexh r1, [r2]");
REQUIRE(DisassembleArm(0xE1831F92) == "strex r1, r2, [r3]");
REQUIRE(DisassembleArm(0xE1C31F92) == "strexb r1, r2, [r3]");
REQUIRE(DisassembleArm(0xE1A41F92) == "strexd r1, r2, r3, [r4]");
REQUIRE(DisassembleArm(0xE1E31F92) == "strexh r1, r2, [r3]");
REQUIRE(DisassembleArm(0xE1031092) == "swp r1, r2, [r3]");
REQUIRE(DisassembleArm(0xE1431092) == "swpb r1, r2, [r3]");
}
TEST_CASE("Disassemble load / store multiple instructions", "[arm][disassembler]") {
REQUIRE(DisassembleArm(0xE92D500F) == "stmdb sp!, {r0, r1, r2, r3, r12, lr}");
}

View File

@@ -0,0 +1,507 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2016 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <catch.hpp>
#include <dynarmic/A32/a32.h>
#include "A32/testenv.h"
#include "frontend/A32/location_descriptor.h"
using namespace Dynarmic;
static A32::UserConfig GetUserConfig(ArmTestEnv* testenv) {
A32::UserConfig user_config;
user_config.optimizations &= ~OptimizationFlag::FastDispatch;
user_config.callbacks = testenv;
return user_config;
}
TEST_CASE("arm: Opt Failure: Const folding in MostSignificantWord", "[arm][A32]") {
// This was a randomized test-case that was failing.
// This was due to constant folding for MostSignificantWord
// failing to take into account an associated GetCarryFromOp
// pseudoinstruction.
ArmTestEnv test_env;
A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0xe30ad071, // movw, sp, #41073
0xe75efd3d, // smmulr lr, sp, sp
0xa637af1e, // shadd16ge r10, r7, lr
0xf57ff01f, // clrex
0x86b98879, // sxtahhi r8, r9, r9, ror #16
0xeafffffe, // b +#0
};
jit.SetCpsr(0x000001d0); // User-mode
test_env.ticks_left = 6;
jit.Run();
// If we don't trigger the GetCarryFromOp ASSERT, we're fine.
}
TEST_CASE("arm: Unintended modification in SetCFlag", "[arm][A32]") {
// This was a randomized test-case that was failing.
//
// IR produced for location {12, !T, !E} was:
// %0 = GetRegister r1
// %1 = SubWithCarry %0, #0x3e80000, #1
// %2 = GetCarryFromOp %1
// %3 = GetOverflowFromOp %1
// %4 = MostSignificantBit %1
// SetNFlag %4
// %6 = IsZero %1
// SetZFlag %6
// SetCFlag %2
// SetVFlag %3
// %10 = GetRegister r5
// %11 = AddWithCarry %10, #0x8a00, %2
// SetRegister r4, %11
//
// The reference to %2 in instruction %11 was the issue, because instruction %8
// told the register allocator it was a Use but then modified the value.
// Changing the EmitSet*Flag instruction to declare their arguments as UseScratch
// solved this bug.
ArmTestEnv test_env;
A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0xe35f0cd9, // cmp pc, #55552
0xe11c0474, // tst r12, r4, ror r4
0xe1a006a7, // mov r0, r7, lsr #13
0xe35107fa, // cmp r1, #0x3E80000
0xe2a54c8a, // adc r4, r5, #35328
0xeafffffe, // b +#0
};
jit.Regs() = {
0x6973b6bb, 0x267ea626, 0x69debf49, 0x8f976895, 0x4ecd2d0d, 0xcf89b8c7, 0xb6713f85, 0x15e2aa5,
0xcd14336a, 0xafca0f3e, 0xace2efd9, 0x68fb82cd, 0x775447c0, 0xc9e1f8cd, 0xebe0e626, 0x0
};
jit.SetCpsr(0x000001d0); // User-mode
test_env.ticks_left = 6;
jit.Run();
REQUIRE(jit.Regs()[0] == 0x00000af1);
REQUIRE(jit.Regs()[1] == 0x267ea626);
REQUIRE(jit.Regs()[2] == 0x69debf49);
REQUIRE(jit.Regs()[3] == 0x8f976895);
REQUIRE(jit.Regs()[4] == 0xcf8a42c8);
REQUIRE(jit.Regs()[5] == 0xcf89b8c7);
REQUIRE(jit.Regs()[6] == 0xb6713f85);
REQUIRE(jit.Regs()[7] == 0x015e2aa5);
REQUIRE(jit.Regs()[8] == 0xcd14336a);
REQUIRE(jit.Regs()[9] == 0xafca0f3e);
REQUIRE(jit.Regs()[10] == 0xace2efd9);
REQUIRE(jit.Regs()[11] == 0x68fb82cd);
REQUIRE(jit.Regs()[12] == 0x775447c0);
REQUIRE(jit.Regs()[13] == 0xc9e1f8cd);
REQUIRE(jit.Regs()[14] == 0xebe0e626);
REQUIRE(jit.Regs()[15] == 0x00000014);
REQUIRE(jit.Cpsr() == 0x200001d0);
}
TEST_CASE( "arm: shsax (Edge-case)", "[arm][A32]" ) {
// This was a randomized test-case that was failing.
//
// The issue here was one of the words to be subtracted was 0x8000.
// When the 2s complement was calculated by (~a + 1), it was 0x8000.
ArmTestEnv test_env;
A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0xe63dbf59, // shsax r11, sp, r9
0xeafffffe, // b +#0
};
jit.Regs() = {
0x3a3b8b18, 0x96156555, 0xffef039f, 0xafb946f2, 0x2030a69a, 0xafe09b2a, 0x896823c8, 0xabde0ded,
0x9825d6a6, 0x17498000, 0x999d2c95, 0x8b812a59, 0x209bdb58, 0x2f7fb1d4, 0x0f378107, 0x00000000
};
jit.SetCpsr(0x000001d0); // User-mode
test_env.ticks_left = 2;
jit.Run();
REQUIRE(jit.Regs()[0] == 0x3a3b8b18);
REQUIRE(jit.Regs()[1] == 0x96156555);
REQUIRE(jit.Regs()[2] == 0xffef039f);
REQUIRE(jit.Regs()[3] == 0xafb946f2);
REQUIRE(jit.Regs()[4] == 0x2030a69a);
REQUIRE(jit.Regs()[5] == 0xafe09b2a);
REQUIRE(jit.Regs()[6] == 0x896823c8);
REQUIRE(jit.Regs()[7] == 0xabde0ded);
REQUIRE(jit.Regs()[8] == 0x9825d6a6);
REQUIRE(jit.Regs()[9] == 0x17498000);
REQUIRE(jit.Regs()[10] == 0x999d2c95);
REQUIRE(jit.Regs()[11] == 0x57bfe48e);
REQUIRE(jit.Regs()[12] == 0x209bdb58);
REQUIRE(jit.Regs()[13] == 0x2f7fb1d4);
REQUIRE(jit.Regs()[14] == 0x0f378107);
REQUIRE(jit.Regs()[15] == 0x00000004);
REQUIRE(jit.Cpsr() == 0x000001d0);
}
TEST_CASE( "arm: uasx (Edge-case)", "[arm][A32]" ) {
// UASX's Rm<31:16> == 0x0000.
// An implementation that depends on addition overflow to detect
// if diff >= 0 will fail this testcase.
ArmTestEnv test_env;
A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0xe6549f35, // uasx r9, r4, r5
0xeafffffe, // b +#0
};
jit.Regs()[4] = 0x8ed38f4c;
jit.Regs()[5] = 0x0000261d;
jit.Regs()[15] = 0x00000000;
jit.SetCpsr(0x000001d0); // User-mode
test_env.ticks_left = 2;
jit.Run();
REQUIRE(jit.Regs()[4] == 0x8ed38f4c);
REQUIRE(jit.Regs()[5] == 0x0000261d);
REQUIRE(jit.Regs()[9] == 0xb4f08f4c);
REQUIRE(jit.Regs()[15] == 0x00000004);
REQUIRE(jit.Cpsr() == 0x000301d0);
}
TEST_CASE("arm: smuad (Edge-case)", "[arm][A32]") {
ArmTestEnv test_env;
A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0xE700F211, // smuad r0, r1, r2
0xeafffffe, // b +#0
};
jit.Regs() = {
0, // Rd
0x80008000, // Rn
0x80008000, // Rm
0,
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
};
jit.SetCpsr(0x000001d0); // User-mode
test_env.ticks_left = 2;
jit.Run();
REQUIRE(jit.Regs()[0] == 0x80000000);
REQUIRE(jit.Regs()[1] == 0x80008000);
REQUIRE(jit.Regs()[2] == 0x80008000);
REQUIRE(jit.Cpsr() == 0x080001d0);
}
TEST_CASE("arm: Test InvalidateCacheRange", "[arm][A32]") {
ArmTestEnv test_env;
A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0xe3a00005, // mov r0, #5
0xe3a0100D, // mov r1, #13
0xe0812000, // add r2, r1, r0
0xeafffffe, // b +#0 (infinite loop)
};
jit.Regs() = {};
jit.SetCpsr(0x000001d0); // User-mode
test_env.ticks_left = 4;
jit.Run();
REQUIRE(jit.Regs()[0] == 5);
REQUIRE(jit.Regs()[1] == 13);
REQUIRE(jit.Regs()[2] == 18);
REQUIRE(jit.Regs()[15] == 0x0000000c);
REQUIRE(jit.Cpsr() == 0x000001d0);
// Change the code
test_env.code_mem[1] = 0xe3a01007; // mov r1, #7
jit.InvalidateCacheRange(/*start_memory_location = */ 4, /* length_in_bytes = */ 4);
// Reset position of PC
jit.Regs()[15] = 0;
test_env.ticks_left = 4;
jit.Run();
REQUIRE(jit.Regs()[0] == 5);
REQUIRE(jit.Regs()[1] == 7);
REQUIRE(jit.Regs()[2] == 12);
REQUIRE(jit.Regs()[15] == 0x0000000c);
REQUIRE(jit.Cpsr() == 0x000001d0);
}
TEST_CASE("arm: Step blx", "[arm]") {
ArmTestEnv test_env;
A32::UserConfig config = GetUserConfig(&test_env);
config.optimizations |= OptimizationFlag::FastDispatch;
Dynarmic::A32::Jit jit{config};
test_env.code_mem = {
0xe12fff30, // blx r0
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xeafffffe, // b +#0 (infinite loop)
};
jit.Regs()[0] = 8;
jit.Regs()[15] = 0; // PC = 0
jit.SetCpsr(0x000001d0); // User-mode
test_env.ticks_left = 10;
jit.Step();
REQUIRE(jit.Regs()[0] == 8);
REQUIRE(jit.Regs()[14] == 4);
REQUIRE(jit.Regs()[15] == 8);
REQUIRE(jit.Cpsr() == 0x000001d0);
}
TEST_CASE("arm: Step bx", "[arm]") {
ArmTestEnv test_env;
A32::UserConfig config = GetUserConfig(&test_env);
config.optimizations |= OptimizationFlag::FastDispatch;
Dynarmic::A32::Jit jit{config};
test_env.code_mem = {
0xe12fff10, // bx r0
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xeafffffe, // b +#0 (infinite loop)
};
jit.Regs()[0] = 8;
jit.Regs()[15] = 0; // PC = 0
jit.SetCpsr(0x000001d0); // User-mode
test_env.ticks_left = 10;
jit.Step();
REQUIRE(jit.Regs()[0] == 8);
REQUIRE(jit.Regs()[15] == 8);
REQUIRE(jit.Cpsr() == 0x000001d0);
}
TEST_CASE("arm: Test stepping", "[arm]") {
ArmTestEnv test_env;
Dynarmic::A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xeafffffe, // b +#0 (infinite loop)
};
jit.Regs()[0] = 8;
jit.Regs()[15] = 0; // PC = 0
jit.SetCpsr(0x000001d0); // User-mode
for (size_t i = 0; i < 5; ++i) {
test_env.ticks_left = 10;
jit.Step();
REQUIRE(jit.Regs()[15] == (i + 1) * 4);
REQUIRE(jit.Cpsr() == 0x000001d0);
}
test_env.ticks_left = 20;
jit.Run();
REQUIRE(jit.Regs()[15] == 80);
REQUIRE(jit.Cpsr() == 0x000001d0);
}
TEST_CASE("arm: Test stepping 2", "[arm]") {
ArmTestEnv test_env;
Dynarmic::A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0xe12fff10, // bx r0
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xeafffffe, // b +#0 (infinite loop)
};
jit.Regs()[0] = 4;
jit.Regs()[15] = 0; // PC = 0
jit.SetCpsr(0x000001d0); // User-mode
for (size_t i = 0; i < 5; ++i) {
test_env.ticks_left = 10;
jit.Step();
REQUIRE(jit.Regs()[15] == (i + 1) * 4);
REQUIRE(jit.Cpsr() == 0x000001d0);
}
test_env.ticks_left = 20;
jit.Run();
REQUIRE(jit.Regs()[15] == 80);
REQUIRE(jit.Cpsr() == 0x000001d0);
}
TEST_CASE("arm: Test stepping 3", "[arm]") {
ArmTestEnv test_env;
Dynarmic::A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0xe12fff10, // bx r0
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xe320f000, // nop
0xeafffffe, // b +#0 (infinite loop)
};
jit.Regs()[0] = 4;
jit.Regs()[15] = 0; // PC = 0
jit.SetCpsr(0x000001d0); // User-mode
test_env.ticks_left = 10;
jit.Step();
REQUIRE(jit.Regs()[15] == 4);
REQUIRE(jit.Cpsr() == 0x000001d0);
test_env.ticks_left = 20;
jit.Run();
REQUIRE(jit.Regs()[15] == 20);
REQUIRE(jit.Cpsr() == 0x000001d0);
}
TEST_CASE("arm: PackedAbsDiffSumS8", "[arm][A32]") {
// This was a randomized test-case that was failing.
// In circumstances there were cases when the upper 32 bits of an argument to psadbw were not zero.
ArmTestEnv test_env;
A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0x87414354, // smlsldhi r4, r1, r4, r3
0xe7886412, // usad8a r8, r2, r4, r6
0xeafffffe, // b +#0
};
jit.Regs() = {
0xea85297c, 0x417ad918, 0x64f8b70b, 0xcca0373e, 0xbc722361, 0xc528c69e, 0xca926de8, 0xd665d210,
0xb5650555, 0x4a24b25b, 0xaed44144, 0xe87230b2, 0x98e391de, 0x126efc0c, 0xe591fd11, 0x00000000,
};
jit.SetCpsr(0xb0000010);
test_env.ticks_left = 3;
jit.Run();
REQUIRE(jit.Regs()[0] == 0xea85297c);
REQUIRE(jit.Regs()[1] == 0x417ad918);
REQUIRE(jit.Regs()[2] == 0x64f8b70b);
REQUIRE(jit.Regs()[3] == 0xcca0373e);
REQUIRE(jit.Regs()[4] == 0xb685ec9f);
REQUIRE(jit.Regs()[5] == 0xc528c69e);
REQUIRE(jit.Regs()[6] == 0xca926de8);
REQUIRE(jit.Regs()[7] == 0xd665d210);
REQUIRE(jit.Regs()[8] == 0xca926f76);
REQUIRE(jit.Regs()[9] == 0x4a24b25b);
REQUIRE(jit.Regs()[10] == 0xaed44144);
REQUIRE(jit.Regs()[11] == 0xe87230b2);
REQUIRE(jit.Regs()[12] == 0x98e391de);
REQUIRE(jit.Regs()[13] == 0x126efc0c);
REQUIRE(jit.Regs()[14] == 0xe591fd11);
REQUIRE(jit.Regs()[15] == 0x00000008);
REQUIRE(jit.Cpsr() == 0xb0000010);
}
TEST_CASE("arm: vclt.f32 with zero", "[arm][A32]") {
ArmTestEnv test_env;
A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0xf3b93628, // vclt.f32 d3, d24, #0
0xeafffffe, // b +#0
};
jit.ExtRegs()[48] = 0x3a87d9f1;
jit.ExtRegs()[49] = 0x80796dc0;
jit.SetCpsr(0x000001d0); // User-mode
test_env.ticks_left = 2;
jit.Run();
REQUIRE(jit.ExtRegs()[6] == 0x00000000);
REQUIRE(jit.ExtRegs()[7] == 0x00000000);
}
TEST_CASE("arm: vcvt.s16.f64", "[arm][A32]") {
ArmTestEnv test_env;
A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0xeebe8b45, // vcvt.s16.f64 d8, d8, #6
0xeafffffe, // b +#0
};
jit.ExtRegs()[16] = 0x9a7110b0;
jit.ExtRegs()[17] = 0xcd78f4e7;
jit.SetCpsr(0x000001d0); // User-mode
test_env.ticks_left = 2;
jit.Run();
REQUIRE(jit.ExtRegs()[16] == 0xffff8000);
REQUIRE(jit.ExtRegs()[17] == 0xffffffff);
}

View File

@@ -0,0 +1,159 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2016 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <catch.hpp>
#include <dynarmic/A32/a32.h>
#include "common/common_types.h"
#include "testenv.h"
static Dynarmic::A32::UserConfig GetUserConfig(ThumbTestEnv* testenv) {
Dynarmic::A32::UserConfig user_config;
user_config.callbacks = testenv;
return user_config;
}
TEST_CASE("thumb: lsls r0, r1, #2", "[thumb]") {
ThumbTestEnv test_env;
Dynarmic::A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0x0088, // lsls r0, r1, #2
0xE7FE, // b +#0
};
jit.Regs()[0] = 1;
jit.Regs()[1] = 2;
jit.Regs()[15] = 0; // PC = 0
jit.SetCpsr(0x00000030); // Thumb, User-mode
test_env.ticks_left = 1;
jit.Run();
REQUIRE(jit.Regs()[0] == 8);
REQUIRE(jit.Regs()[1] == 2);
REQUIRE(jit.Regs()[15] == 2);
REQUIRE(jit.Cpsr() == 0x00000030);
}
TEST_CASE("thumb: lsls r0, r1, #31", "[thumb]") {
ThumbTestEnv test_env;
Dynarmic::A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0x07C8, // lsls r0, r1, #31
0xE7FE, // b +#0
};
jit.Regs()[0] = 1;
jit.Regs()[1] = 0xFFFFFFFF;
jit.Regs()[15] = 0; // PC = 0
jit.SetCpsr(0x00000030); // Thumb, User-mode
test_env.ticks_left = 1;
jit.Run();
REQUIRE(jit.Regs()[0] == 0x80000000);
REQUIRE(jit.Regs()[1] == 0xffffffff);
REQUIRE(jit.Regs()[15] == 2);
REQUIRE(jit.Cpsr() == 0xA0000030); // N, C flags, Thumb, User-mode
}
TEST_CASE("thumb: revsh r4, r3", "[thumb]") {
ThumbTestEnv test_env;
Dynarmic::A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0xBADC, // revsh r4, r3
0xE7FE, // b +#0
};
jit.Regs()[3] = 0x12345678;
jit.Regs()[15] = 0; // PC = 0
jit.SetCpsr(0x00000030); // Thumb, User-mode
test_env.ticks_left = 1;
jit.Run();
REQUIRE(jit.Regs()[3] == 0x12345678);
REQUIRE(jit.Regs()[4] == 0x00007856);
REQUIRE(jit.Regs()[15] == 2);
REQUIRE(jit.Cpsr() == 0x00000030); // Thumb, User-mode
}
TEST_CASE("thumb: ldr r3, [r3, #28]", "[thumb]") {
ThumbTestEnv test_env;
Dynarmic::A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0x69DB, // ldr r3, [r3, #28]
0xE7FE, // b +#0
};
jit.Regs()[3] = 0x12345678;
jit.Regs()[15] = 0; // PC = 0
jit.SetCpsr(0x00000030); // Thumb, User-mode
test_env.ticks_left = 1;
jit.Run();
REQUIRE(jit.Regs()[3] == 0x97969594); // Memory location 0x12345694
REQUIRE(jit.Regs()[15] == 2);
REQUIRE(jit.Cpsr() == 0x00000030); // Thumb, User-mode
}
TEST_CASE("thumb: blx +#67712", "[thumb]") {
ThumbTestEnv test_env;
Dynarmic::A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0xF010, 0xEC3E, // blx +#67712
0xE7FE // b +#0
};
jit.Regs()[15] = 0; // PC = 0
jit.SetCpsr(0x00000030); // Thumb, User-mode
test_env.ticks_left = 1;
jit.Run();
REQUIRE(jit.Regs()[14] == (0x4 | 1));
REQUIRE(jit.Regs()[15] == 0x10880);
REQUIRE(jit.Cpsr() == 0x00000010); // User-mode
}
TEST_CASE("thumb: bl +#234584", "[thumb]") {
ThumbTestEnv test_env;
Dynarmic::A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0xF039, 0xFA2A, // bl +#234584
0xE7FE // b +#0
};
jit.Regs()[15] = 0; // PC = 0
jit.SetCpsr(0x00000030); // Thumb, User-mode
test_env.ticks_left = 1;
jit.Run();
REQUIRE(jit.Regs()[14] == (0x4 | 1));
REQUIRE(jit.Regs()[15] == 0x39458);
REQUIRE(jit.Cpsr() == 0x00000030); // Thumb, User-mode
}
TEST_CASE("thumb: bl -#42", "[thumb]") {
ThumbTestEnv test_env;
Dynarmic::A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0xF7FF, 0xFFE9, // bl -#42
0xE7FE // b +#0
};
jit.Regs()[15] = 0; // PC = 0
jit.SetCpsr(0x00000030); // Thumb, User-mode
test_env.ticks_left = 1;
jit.Run();
REQUIRE(jit.Regs()[14] == (0x4 | 1));
REQUIRE(jit.Regs()[15] == 0xFFFFFFD6);
REQUIRE(jit.Cpsr() == 0x00000030); // Thumb, User-mode
}

99
externals/dynarmic/tests/A32/testenv.h vendored Executable file
View File

@@ -0,0 +1,99 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#pragma once
#include <array>
#include <cstring>
#include <map>
#include <string>
#include <vector>
#include <dynarmic/A32/a32.h>
#include "common/assert.h"
#include "common/common_types.h"
template <typename InstructionType_, u32 infinite_loop>
class A32TestEnv final : public Dynarmic::A32::UserCallbacks {
public:
using InstructionType = InstructionType_;
using RegisterArray = std::array<u32, 16>;
using ExtRegsArray = std::array<u32, 64>;
u64 ticks_left = 0;
bool code_mem_modified_by_guest = false;
std::vector<InstructionType> code_mem;
std::map<u32, u8> modified_memory;
std::vector<std::string> interrupts;
std::uint32_t MemoryReadCode(u32 vaddr) override {
const size_t index = vaddr / sizeof(InstructionType);
if (index < code_mem.size()) {
u32 value;
std::memcpy(&value, &code_mem[index], sizeof(u32));
return value;
}
return infinite_loop; // B .
}
std::uint8_t MemoryRead8(u32 vaddr) override {
if (vaddr < sizeof(InstructionType) * code_mem.size()) {
return reinterpret_cast<u8*>(code_mem.data())[vaddr];
}
if (auto iter = modified_memory.find(vaddr); iter != modified_memory.end()) {
return iter->second;
}
return static_cast<u8>(vaddr);
}
std::uint16_t MemoryRead16(u32 vaddr) override {
return u16(MemoryRead8(vaddr)) | u16(MemoryRead8(vaddr + 1)) << 8;
}
std::uint32_t MemoryRead32(u32 vaddr) override {
return u32(MemoryRead16(vaddr)) | u32(MemoryRead16(vaddr + 2)) << 16;
}
std::uint64_t MemoryRead64(u32 vaddr) override {
return u64(MemoryRead32(vaddr)) | u64(MemoryRead32(vaddr + 4)) << 32;
}
void MemoryWrite8(u32 vaddr, std::uint8_t value) override {
if (vaddr < code_mem.size() * sizeof(u32)) {
code_mem_modified_by_guest = true;
}
modified_memory[vaddr] = value;
}
void MemoryWrite16(u32 vaddr, std::uint16_t value) override {
MemoryWrite8(vaddr, static_cast<u8>(value));
MemoryWrite8(vaddr + 1, static_cast<u8>(value >> 8));
}
void MemoryWrite32(u32 vaddr, std::uint32_t value) override {
MemoryWrite16(vaddr, static_cast<u16>(value));
MemoryWrite16(vaddr + 2, static_cast<u16>(value >> 16));
}
void MemoryWrite64(u32 vaddr, std::uint64_t value) override {
MemoryWrite32(vaddr, static_cast<u32>(value));
MemoryWrite32(vaddr + 4, static_cast<u32>(value >> 32));
}
void InterpreterFallback(u32 pc, size_t num_instructions) override { ASSERT_MSG(false, "InterpreterFallback({:08x}, {}) code = {:08x}", pc, num_instructions, MemoryReadCode(pc)); }
void CallSVC(std::uint32_t swi) override { ASSERT_MSG(false, "CallSVC({})", swi); }
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception /*exception*/) override { ASSERT_MSG(false, "ExceptionRaised({:08x})", pc); }
void AddTicks(std::uint64_t ticks) override {
if (ticks > ticks_left) {
ticks_left = 0;
return;
}
ticks_left -= ticks;
}
std::uint64_t GetTicksRemaining() override {
return ticks_left;
}
};
using ArmTestEnv = A32TestEnv<u32, 0xEAFFFFFE>;
using ThumbTestEnv = A32TestEnv<u16, 0xE7FEE7FE>;

13456
externals/dynarmic/tests/A32/vfp_vadd_f32.inc vendored Executable file

File diff suppressed because it is too large Load Diff

13456
externals/dynarmic/tests/A32/vfp_vsub_f32.inc vendored Executable file

File diff suppressed because it is too large Load Diff

636
externals/dynarmic/tests/A64/a64.cpp vendored Executable file
View File

@@ -0,0 +1,636 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <catch.hpp>
#include <dynarmic/exclusive_monitor.h>
#include "common/fp/fpsr.h"
#include "testenv.h"
using namespace Dynarmic;
TEST_CASE("A64: ADD", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0x8b020020); // ADD X0, X1, X2
env.code_mem.emplace_back(0x14000000); // B .
jit.SetRegister(0, 0);
jit.SetRegister(1, 1);
jit.SetRegister(2, 2);
jit.SetPC(0);
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetRegister(0) == 3);
REQUIRE(jit.GetRegister(1) == 1);
REQUIRE(jit.GetRegister(2) == 2);
REQUIRE(jit.GetPC() == 4);
}
TEST_CASE("A64: REV", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0xdac00c00); // REV X0, X0
env.code_mem.emplace_back(0x5ac00821); // REV W1, W1
env.code_mem.emplace_back(0x14000000); // B .
jit.SetRegister(0, 0xaabbccddeeff1100);
jit.SetRegister(1, 0xaabbccdd);
jit.SetPC(0);
env.ticks_left = 3;
jit.Run();
REQUIRE(jit.GetRegister(0) == 0x11ffeeddccbbaa);
REQUIRE(jit.GetRegister(1) == 0xddccbbaa);
REQUIRE(jit.GetPC() == 8);
}
TEST_CASE("A64: REV32", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0xdac00800); // REV32 X0, X0
env.code_mem.emplace_back(0x14000000); // B .
jit.SetRegister(0, 0xaabbccddeeff1100);
jit.SetPC(0);
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetRegister(0) == 0xddccbbaa0011ffee);
REQUIRE(jit.GetPC() == 4);
}
TEST_CASE("A64: REV16", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0xdac00400); // REV16 X0, X0
env.code_mem.emplace_back(0x5ac00421); // REV16 W1, W1
env.code_mem.emplace_back(0x14000000); // B .
jit.SetRegister(0, 0xaabbccddeeff1100);
jit.SetRegister(1, 0xaabbccdd);
jit.SetPC(0);
env.ticks_left = 3;
jit.Run();
REQUIRE(jit.GetRegister(0) == 0xbbaaddccffee0011);
REQUIRE(jit.GetRegister(1) == 0xbbaaddcc);
REQUIRE(jit.GetPC() == 8);
}
TEST_CASE("A64: AND", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0x8a020020); // AND X0, X1, X2
env.code_mem.emplace_back(0x14000000); // B .
jit.SetRegister(0, 0);
jit.SetRegister(1, 1);
jit.SetRegister(2, 3);
jit.SetPC(0);
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetRegister(0) == 1);
REQUIRE(jit.GetRegister(1) == 1);
REQUIRE(jit.GetRegister(2) == 3);
REQUIRE(jit.GetPC() == 4);
}
TEST_CASE("A64: Bitmasks", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0x3200c3e0); // ORR W0, WZR, #0x01010101
env.code_mem.emplace_back(0x320c8fe1); // ORR W1, WZR, #0x00F000F0
env.code_mem.emplace_back(0x320003e2); // ORR W2, WZR, #1
env.code_mem.emplace_back(0x14000000); // B .
jit.SetPC(0);
env.ticks_left = 4;
jit.Run();
REQUIRE(jit.GetRegister(0) == 0x01010101);
REQUIRE(jit.GetRegister(1) == 0x00F000F0);
REQUIRE(jit.GetRegister(2) == 1);
REQUIRE(jit.GetPC() == 12);
}
TEST_CASE("A64: ANDS NZCV", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0x6a020020); // ANDS W0, W1, W2
env.code_mem.emplace_back(0x14000000); // B .
SECTION("N=1, Z=0") {
jit.SetRegister(0, 0);
jit.SetRegister(1, 0xFFFFFFFF);
jit.SetRegister(2, 0xFFFFFFFF);
jit.SetPC(0);
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetRegister(0) == 0xFFFFFFFF);
REQUIRE(jit.GetRegister(1) == 0xFFFFFFFF);
REQUIRE(jit.GetRegister(2) == 0xFFFFFFFF);
REQUIRE(jit.GetPC() == 4);
REQUIRE((jit.GetPstate() & 0xF0000000) == 0x80000000);
}
SECTION("N=0, Z=1") {
jit.SetRegister(0, 0);
jit.SetRegister(1, 0xFFFFFFFF);
jit.SetRegister(2, 0x00000000);
jit.SetPC(0);
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetRegister(0) == 0x00000000);
REQUIRE(jit.GetRegister(1) == 0xFFFFFFFF);
REQUIRE(jit.GetRegister(2) == 0x00000000);
REQUIRE(jit.GetPC() == 4);
REQUIRE((jit.GetPstate() & 0xF0000000) == 0x40000000);
}
SECTION("N=0, Z=0") {
jit.SetRegister(0, 0);
jit.SetRegister(1, 0x12345678);
jit.SetRegister(2, 0x7324a993);
jit.SetPC(0);
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetRegister(0) == 0x12240010);
REQUIRE(jit.GetRegister(1) == 0x12345678);
REQUIRE(jit.GetRegister(2) == 0x7324a993);
REQUIRE(jit.GetPC() == 4);
REQUIRE((jit.GetPstate() & 0xF0000000) == 0x00000000);
}
}
TEST_CASE("A64: CBZ", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0x34000060); // 0x00 : CBZ X0, label
env.code_mem.emplace_back(0x320003e2); // 0x04 : MOV X2, 1
env.code_mem.emplace_back(0x14000000); // 0x08 : B.
env.code_mem.emplace_back(0x321f03e2); // 0x0C : label: MOV X2, 2
env.code_mem.emplace_back(0x14000000); // 0x10 : B .
SECTION("no branch") {
jit.SetPC(0);
jit.SetRegister(0, 1);
env.ticks_left = 4;
jit.Run();
REQUIRE(jit.GetRegister(2) == 1);
REQUIRE(jit.GetPC() == 8);
}
SECTION("branch") {
jit.SetPC(0);
jit.SetRegister(0, 0);
env.ticks_left = 4;
jit.Run();
REQUIRE(jit.GetRegister(2) == 2);
REQUIRE(jit.GetPC() == 16);
}
}
TEST_CASE("A64: TBZ", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0x36180060); // 0x00 : TBZ X0, 3, label
env.code_mem.emplace_back(0x320003e2); // 0x04 : MOV X2, 1
env.code_mem.emplace_back(0x14000000); // 0x08 : B .
env.code_mem.emplace_back(0x321f03e2); // 0x0C : label: MOV X2, 2
env.code_mem.emplace_back(0x14000000); // 0x10 : B .
SECTION("no branch") {
jit.SetPC(0);
jit.SetRegister(0, 0xFF);
env.ticks_left = 4;
jit.Run();
REQUIRE(jit.GetRegister(2) == 1);
REQUIRE(jit.GetPC() == 8);
}
SECTION("branch with zero") {
jit.SetPC(0);
jit.SetRegister(0, 0);
env.ticks_left = 4;
jit.Run();
REQUIRE(jit.GetRegister(2) == 2);
REQUIRE(jit.GetPC() == 16);
}
SECTION("branch with non-zero") {
jit.SetPC(0);
jit.SetRegister(0, 1);
env.ticks_left = 4;
jit.Run();
REQUIRE(jit.GetRegister(2) == 2);
REQUIRE(jit.GetPC() == 16);
}
}
TEST_CASE("A64: FABD", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0x6eb5d556); // FABD.4S V22, V10, V21
env.code_mem.emplace_back(0x14000000); // B .
jit.SetPC(0);
jit.SetVector(10, {0xb4858ac77ff39a87, 0x9fce5e14c4873176});
jit.SetVector(21, {0x56d3f085ff890e2b, 0x6e4b0a41801a2d00});
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetVector(22) == Vector{0x56d3f0857fc90e2b, 0x6e4b0a4144873176});
}
TEST_CASE("A64: 128-bit exclusive read/write", "[a64]") {
A64TestEnv env;
ExclusiveMonitor monitor{1};
A64::UserConfig conf;
conf.callbacks = &env;
conf.processor_id = 0;
SECTION("Global Monitor") {
conf.global_monitor = &monitor;
}
A64::Jit jit{conf};
env.code_mem.emplace_back(0xc87f0861); // LDXP X1, X2, [X3]
env.code_mem.emplace_back(0xc8241865); // STXP W4, X5, X6, [X3]
env.code_mem.emplace_back(0x14000000); // B .
jit.SetPC(0);
jit.SetRegister(3, 0x1234567812345678);
jit.SetRegister(4, 0xbaadbaadbaadbaad);
jit.SetRegister(5, 0xaf00d1e5badcafe0);
jit.SetRegister(6, 0xd0d0cacad0d0caca);
env.ticks_left = 3;
jit.Run();
REQUIRE(jit.GetRegister(1) == 0x7f7e7d7c7b7a7978);
REQUIRE(jit.GetRegister(2) == 0x8786858483828180);
REQUIRE(jit.GetRegister(4) == 0);
REQUIRE(env.MemoryRead64(0x1234567812345678) == 0xaf00d1e5badcafe0);
REQUIRE(env.MemoryRead64(0x1234567812345680) == 0xd0d0cacad0d0caca);
}
TEST_CASE("A64: CNTPCT_EL0", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0xd53be021); // MRS X1, CNTPCT_EL0
env.code_mem.emplace_back(0xd503201f); // NOP
env.code_mem.emplace_back(0xd503201f); // NOP
env.code_mem.emplace_back(0xd503201f); // NOP
env.code_mem.emplace_back(0xd503201f); // NOP
env.code_mem.emplace_back(0xd503201f); // NOP
env.code_mem.emplace_back(0xd503201f); // NOP
env.code_mem.emplace_back(0xd53be022); // MRS X2, CNTPCT_EL0
env.code_mem.emplace_back(0xcb010043); // SUB X3, X2, X1
env.code_mem.emplace_back(0x14000000); // B .
env.ticks_left = 10;
jit.Run();
REQUIRE(jit.GetRegister(3) == 7);
}
TEST_CASE("A64: FNMSUB 1", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0x1f618a9c); // FNMSUB D28, D20, D1, D2
env.code_mem.emplace_back(0x14000000); // B .
jit.SetPC(0);
jit.SetVector(20, {0xe73a51346164bd6c, 0x8080000000002b94});
jit.SetVector(1, {0xbf8000007fffffff, 0xffffffff00002b94});
jit.SetVector(2, {0x0000000000000000, 0xc79b271e3f000000});
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetVector(28) == Vector{0x66ca513533ee6076, 0x0000000000000000});
}
TEST_CASE("A64: FNMSUB 2", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0x1f2ab88e); // FNMSUB S14, S4, S10, S14
env.code_mem.emplace_back(0x14000000); // B .
jit.SetPC(0);
jit.SetVector(4, {0x3c9623b101398437, 0x7ff0abcd0ba98d27});
jit.SetVector(10, {0xffbfffff3eaaaaab, 0x3f0000003f8147ae});
jit.SetVector(14, {0x80000000007fffff, 0xe73a513400000000});
jit.SetFpcr(0x00400000);
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetVector(14) == Vector{0x0000000080045284, 0x0000000000000000});
}
TEST_CASE("A64: FMADD", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0x1f5e0e4a); // FMADD D10, D18, D30, D3
env.code_mem.emplace_back(0x14000000); // B .
jit.SetPC(0);
jit.SetVector(18, {0x8000007600800000, 0x7ff812347f800000});
jit.SetVector(30, {0xff984a3700000000, 0xe73a513480800000});
jit.SetVector(3, {0x3f000000ff7fffff, 0x8139843780000000});
jit.SetFpcr(0x00400000);
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetVector(10) == Vector{0x3f059921bf0dbfff, 0x0000000000000000});
}
TEST_CASE("A64: FMLA.4S (denormal)", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0x4e2fcccc); // FMLA.4S V12, V6, V15
env.code_mem.emplace_back(0x14000000); // B .
jit.SetPC(0);
jit.SetVector(12, {0x3c9623b17ff80000, 0xbff0000080000076});
jit.SetVector(6, {0x7ff80000ff800000, 0x09503366c1200000});
jit.SetVector(15, {0x3ff0000080636d24, 0xbf800000e73a5134});
jit.SetFpcr(0x01000000);
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetVector(12) == Vector{0x7ff800007fc00000, 0xbff0000068e8e581});
}
TEST_CASE("A64: FMLA.4S (0x80800000)", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0x4e38cc2b); // FMLA.4S V11, V1, V24
env.code_mem.emplace_back(0x14000000); // B .
jit.SetPC(0);
jit.SetVector(11, {0xc79b271efff05678, 0xffc0000080800000});
jit.SetVector(1, {0x00636d2400800000, 0x0966320bb26bddee});
jit.SetVector(24, {0x460e8c84fff00000, 0x8ba98d2780800002});
jit.SetFpcr(0x03000000);
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetVector(11) == Vector{0xc79b271e7fc00000, 0x7fc0000080000000});
}
// x64 has different rounding behaviour to AArch64.
// AArch64 performs rounding after flushing-to-zero.
// x64 performs rounding before flushing-to-zero.
TEST_CASE("A64: FMADD (0x80800000)", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0x1f0f7319); // FMADD S25, S24, S15, S28
env.code_mem.emplace_back(0x14000000); // B .
jit.SetPC(0);
jit.SetVector(24, {0x00800000, 0});
jit.SetVector(15, {0x0ba98d27, 0});
jit.SetVector(28, {0x80800000, 0});
jit.SetFpcr(0x01000000);
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetVector(25) == Vector{0x80000000, 0});
}
TEST_CASE("A64: FNEG failed to zero upper", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0x2ea0fb50); // FNEG.2S V16, V26
env.code_mem.emplace_back(0x2e207a1c); // SQNEG.8B V28, V16
env.code_mem.emplace_back(0x14000000); // B .
jit.SetPC(0);
jit.SetVector(26, {0x071286fde8f34a90, 0x837cffa8be382f60});
jit.SetFpcr(0x01000000);
env.ticks_left = 6;
jit.Run();
REQUIRE(jit.GetVector(28) == Vector{0x79ee7a03980db670, 0});
REQUIRE(FP::FPSR{jit.GetFpsr()}.QC() == false);
}
TEST_CASE("A64: FRSQRTS", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0x5eb8fcad); // FRSQRTS S13, S5, S24
env.code_mem.emplace_back(0x14000000); // B .
// These particular values result in an intermediate value during
// the calculation that is close to infinity. We want to verify
// that this special case is handled appropriately.
jit.SetPC(0);
jit.SetVector(5, {0xfc6a0206, 0});
jit.SetVector(24, {0xfc6a0206, 0});
jit.SetFpcr(0x00400000);
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetVector(13) == Vector{0xff7fffff, 0});
}
TEST_CASE("A64: SQDMULH.8H (saturate)", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0x4e62b420); // SQDMULH.8H V0, V1, V2
env.code_mem.emplace_back(0x14000000); // B .
// Make sure that saturating values are tested
jit.SetPC(0);
jit.SetVector(1, {0x7fff80007ffe8001, 0x7fff80007ffe8001});
jit.SetVector(2, {0x7fff80007ffe8001, 0x80007fff80017ffe});
jit.SetFpsr(0);
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetVector(0) == Vector{0x7ffe7fff7ffc7ffe, 0x8001800180028002});
REQUIRE(FP::FPSR{jit.GetFpsr()}.QC() == true);
}
TEST_CASE("A64: SQDMULH.4S (saturate)", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0x4ea2b420); // SQDMULH.4S V0, V1, V2
env.code_mem.emplace_back(0x14000000); // B .
// Make sure that saturating values are tested
jit.SetPC(0);
jit.SetVector(1, {0x7fffffff80000000, 0x7fffffff80000000});
jit.SetVector(2, {0x7fffffff80000000, 0x800000007fffffff});
jit.SetFpsr(0);
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetVector(0) == Vector{0x7ffffffe7fffffff, 0x8000000180000001});
REQUIRE(FP::FPSR{jit.GetFpsr()}.QC() == true);
}
TEST_CASE("A64: This is an infinite loop if fast dispatch is enabled", "[a64]") {
A64TestEnv env;
A64::UserConfig conf{&env};
conf.optimizations &= ~OptimizationFlag::FastDispatch;
A64::Jit jit{conf};
env.code_mem.emplace_back(0x2ef998fa);
env.code_mem.emplace_back(0x2ef41c11);
env.code_mem.emplace_back(0x0f07fdd8);
env.code_mem.emplace_back(0x9ac90d09);
env.code_mem.emplace_back(0xd63f0120); // BLR X9
env.code_mem.emplace_back(0x14000000); // B .
env.ticks_left = 6;
jit.Run();
}
TEST_CASE("A64: Optimization failure when folding ADD", "[a64]") {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(0xbc4f84be); // LDR S30, [X5], #248
env.code_mem.emplace_back(0x9a0c00ea); // ADC X10, X7, X12
env.code_mem.emplace_back(0x5a1a0079); // SBC W25, W3, W26
env.code_mem.emplace_back(0x9b0e2be9); // MADD X9, XZR, X14, X10
env.code_mem.emplace_back(0xfa5fe8a9); // CCMP X5, #31, #9, AL
env.code_mem.emplace_back(0x14000000); // B .
jit.SetPC(0);
jit.SetRegister(0, 0x46e15845dba57924);
jit.SetRegister(1, 0x6f60d04350581fea);
jit.SetRegister(2, 0x85cface50edcfc03);
jit.SetRegister(3, 0x47e1e8906e10ec5a);
jit.SetRegister(4, 0x70717c9450b6b707);
jit.SetRegister(5, 0x300d83205baeaff4);
jit.SetRegister(6, 0xb7890de7c6fee082);
jit.SetRegister(7, 0xa89fb6d6f1b42f4a);
jit.SetRegister(8, 0x04e36b8aada91d4f);
jit.SetRegister(9, 0xa03bf6bde71c6ac5);
jit.SetRegister(10, 0x319374d14baa83b0);
jit.SetRegister(11, 0x5a78fc0fffca7c5f);
jit.SetRegister(12, 0xc012b5063f43b8ad);
jit.SetRegister(13, 0x821ade159d39fea1);
jit.SetRegister(14, 0x41f97b2f5525c25e);
jit.SetRegister(15, 0xab0cd3653cb93738);
jit.SetRegister(16, 0x50dfcb55a4ebd554);
jit.SetRegister(17, 0x30dd7d18ae52df03);
jit.SetRegister(18, 0x4e53b20d252bf085);
jit.SetRegister(19, 0x013582d71f5fd42a);
jit.SetRegister(20, 0x97a151539dad44e7);
jit.SetRegister(21, 0xa6fcc6bb220a2ad3);
jit.SetRegister(22, 0x4c84d3c84a6c5c5c);
jit.SetRegister(23, 0x1a7596a5ef930dff);
jit.SetRegister(24, 0x06248d96a02ff210);
jit.SetRegister(25, 0xfcb8772aec4b1dfd);
jit.SetRegister(26, 0x63619787b6a17665);
jit.SetRegister(27, 0xbd50c3352d001e40);
jit.SetRegister(28, 0x4e186aae63c81553);
jit.SetRegister(29, 0x57462b7163bd6508);
jit.SetRegister(30, 0xa977c850d16d562c);
jit.SetSP(0x000000da9b761d8c);
jit.SetFpsr(0x03480000);
jit.SetPstate(0x30000000);
env.ticks_left = 6;
jit.Run();
REQUIRE(jit.GetRegister(0) == 0x46e15845dba57924);
REQUIRE(jit.GetRegister(1) == 0x6f60d04350581fea);
REQUIRE(jit.GetRegister(2) == 0x85cface50edcfc03);
REQUIRE(jit.GetRegister(3) == 0x47e1e8906e10ec5a);
REQUIRE(jit.GetRegister(4) == 0x70717c9450b6b707);
REQUIRE(jit.GetRegister(5) == 0x300d83205baeb0ec);
REQUIRE(jit.GetRegister(6) == 0xb7890de7c6fee082);
REQUIRE(jit.GetRegister(7) == 0xa89fb6d6f1b42f4a);
REQUIRE(jit.GetRegister(8) == 0x04e36b8aada91d4f);
REQUIRE(jit.GetRegister(9) == 0x68b26bdd30f7e7f8);
REQUIRE(jit.GetRegister(10) == 0x68b26bdd30f7e7f8);
REQUIRE(jit.GetRegister(11) == 0x5a78fc0fffca7c5f);
REQUIRE(jit.GetRegister(12) == 0xc012b5063f43b8ad);
REQUIRE(jit.GetRegister(13) == 0x821ade159d39fea1);
REQUIRE(jit.GetRegister(14) == 0x41f97b2f5525c25e);
REQUIRE(jit.GetRegister(15) == 0xab0cd3653cb93738);
REQUIRE(jit.GetRegister(16) == 0x50dfcb55a4ebd554);
REQUIRE(jit.GetRegister(17) == 0x30dd7d18ae52df03);
REQUIRE(jit.GetRegister(18) == 0x4e53b20d252bf085);
REQUIRE(jit.GetRegister(19) == 0x013582d71f5fd42a);
REQUIRE(jit.GetRegister(20) == 0x97a151539dad44e7);
REQUIRE(jit.GetRegister(21) == 0xa6fcc6bb220a2ad3);
REQUIRE(jit.GetRegister(22) == 0x4c84d3c84a6c5c5c);
REQUIRE(jit.GetRegister(23) == 0x1a7596a5ef930dff);
REQUIRE(jit.GetRegister(24) == 0x06248d96a02ff210);
REQUIRE(jit.GetRegister(25) == 0x00000000b76f75f5);
REQUIRE(jit.GetRegister(26) == 0x63619787b6a17665);
REQUIRE(jit.GetRegister(27) == 0xbd50c3352d001e40);
REQUIRE(jit.GetRegister(28) == 0x4e186aae63c81553);
REQUIRE(jit.GetRegister(29) == 0x57462b7163bd6508);
REQUIRE(jit.GetRegister(30) == 0xa977c850d16d562c);
REQUIRE(jit.GetPstate() == 0x20000000);
REQUIRE(jit.GetVector(30) == Vector{0xf7f6f5f4, 0});
}

View File

@@ -0,0 +1,505 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <algorithm>
#include <cstring>
#include <string>
#include <vector>
#include <catch.hpp>
#include "common/common_types.h"
#include "common/fp/fpcr.h"
#include "common/fp/fpsr.h"
#include "common/llvm_disassemble.h"
#include "common/scope_exit.h"
#include "frontend/A64/decoder/a64.h"
#include "frontend/A64/location_descriptor.h"
#include "frontend/A64/translate/translate.h"
#include "frontend/A64/types.h"
#include "frontend/ir/basic_block.h"
#include "frontend/ir/opcodes.h"
#include "fuzz_util.h"
#include "ir_opt/passes.h"
#include "rand_int.h"
#include "testenv.h"
#include "unicorn_emu/a64_unicorn.h"
// Must be declared last for all necessary operator<< to be declared prior to this.
#include <fmt/format.h>
#include <fmt/ostream.h>
using namespace Dynarmic;
static bool ShouldTestInst(u32 instruction, u64 pc, bool is_last_inst) {
const A64::LocationDescriptor location{pc, {}};
IR::Block block{location};
bool should_continue = A64::TranslateSingleInstruction(block, location, instruction);
if (!should_continue && !is_last_inst)
return false;
if (auto terminal = block.GetTerminal(); boost::get<IR::Term::Interpret>(&terminal))
return false;
for (const auto& ir_inst : block) {
switch (ir_inst.GetOpcode()) {
case IR::Opcode::A64ExceptionRaised:
case IR::Opcode::A64CallSupervisor:
case IR::Opcode::A64DataCacheOperationRaised:
case IR::Opcode::A64GetCNTPCT:
return false;
default:
continue;
}
}
return true;
}
static u32 GenRandomInst(u64 pc, bool is_last_inst) {
static const struct InstructionGeneratorInfo {
std::vector<InstructionGenerator> generators;
std::vector<InstructionGenerator> invalid;
} instructions = []{
const std::vector<std::tuple<std::string, const char*>> list {
#define INST(fn, name, bitstring) {#fn, bitstring},
#include "frontend/A64/decoder/a64.inc"
#undef INST
};
std::vector<InstructionGenerator> generators;
std::vector<InstructionGenerator> invalid;
// List of instructions not to test
const std::vector<std::string> do_not_test {
// Unimplemented in QEMU
"STLLR",
// Unimplemented in QEMU
"LDLAR",
// Dynarmic and QEMU currently differ on how the exclusive monitor's address range works.
"STXR", "STLXR", "STXP", "STLXP", "LDXR", "LDAXR", "LDXP", "LDAXP",
// Behaviour differs from QEMU
"MSR_reg", "MSR_imm", "MRS",
};
for (const auto& [fn, bitstring] : list) {
if (fn == "UnallocatedEncoding") {
continue;
}
if (std::find(do_not_test.begin(), do_not_test.end(), fn) != do_not_test.end()) {
invalid.emplace_back(InstructionGenerator{bitstring});
continue;
}
generators.emplace_back(InstructionGenerator{bitstring});
}
return InstructionGeneratorInfo{generators, invalid};
}();
while (true) {
const size_t index = RandInt<size_t>(0, instructions.generators.size() - 1);
const u32 inst = instructions.generators[index].Generate();
if (std::any_of(instructions.invalid.begin(), instructions.invalid.end(), [inst](const auto& invalid) { return invalid.Match(inst); })) {
continue;
}
if (ShouldTestInst(inst, pc, is_last_inst)) {
return inst;
}
}
}
static u32 GenFloatInst(u64 pc, bool is_last_inst) {
static const std::vector<InstructionGenerator> instruction_generators = []{
const std::vector<std::tuple<std::string, std::string, const char*>> list {
#define INST(fn, name, bitstring) {#fn, #name, bitstring},
#include "frontend/A64/decoder/a64.inc"
#undef INST
};
// List of instructions not to test
const std::vector<std::string> do_not_test {
};
std::vector<InstructionGenerator> result;
for (const auto& [fn, name, bitstring] : list) {
(void)name;
if (fn[0] != 'F') {
continue;
} else if (std::find(do_not_test.begin(), do_not_test.end(), fn) != do_not_test.end()) {
continue;
}
result.emplace_back(InstructionGenerator{bitstring});
}
return result;
}();
while (true) {
const size_t index = RandInt<size_t>(0, instruction_generators.size() - 1);
const u32 instruction = instruction_generators[index].Generate();
if ((instruction & 0x00800000) == 0 && ShouldTestInst(instruction, pc, is_last_inst)) {
return instruction;
}
}
}
static Dynarmic::A64::UserConfig GetUserConfig(A64TestEnv& jit_env) {
Dynarmic::A64::UserConfig jit_user_config{&jit_env};
jit_user_config.optimizations &= ~OptimizationFlag::FastDispatch;
// The below corresponds to the settings for qemu's aarch64_max_initfn
jit_user_config.dczid_el0 = 7;
jit_user_config.ctr_el0 = 0x80038003;
return jit_user_config;
}
static void RunTestInstance(Dynarmic::A64::Jit& jit, A64Unicorn& uni, A64TestEnv& jit_env, A64TestEnv& uni_env,
const A64Unicorn::RegisterArray& regs, const A64Unicorn::VectorArray& vecs, const size_t instructions_start,
const std::vector<u32>& instructions, const u32 pstate, const u32 fpcr) {
jit_env.code_mem = instructions;
uni_env.code_mem = instructions;
jit_env.code_mem.emplace_back(0x14000000); // B .
uni_env.code_mem.emplace_back(0x14000000); // B .
jit_env.code_mem_start_address = instructions_start;
uni_env.code_mem_start_address = instructions_start;
jit_env.modified_memory.clear();
uni_env.modified_memory.clear();
jit_env.interrupts.clear();
uni_env.interrupts.clear();
const u64 initial_sp = RandInt<u64>(0x30'0000'0000, 0x40'0000'0000) * 4;
jit.SetRegisters(regs);
jit.SetVectors(vecs);
jit.SetPC(instructions_start);
jit.SetSP(initial_sp);
jit.SetFpcr(fpcr);
jit.SetFpsr(0);
jit.SetPstate(pstate);
jit.ClearCache();
uni.SetRegisters(regs);
uni.SetVectors(vecs);
uni.SetPC(instructions_start);
uni.SetSP(initial_sp);
uni.SetFpcr(fpcr);
uni.SetFpsr(0);
uni.SetPstate(pstate);
uni.ClearPageCache();
jit_env.ticks_left = instructions.size();
jit.Run();
uni_env.ticks_left = instructions.size();
uni.Run();
SCOPE_FAIL {
fmt::print("Instruction Listing:\n");
for (u32 instruction : instructions) {
fmt::print("{:08x} {}\n", instruction, Common::DisassembleAArch64(instruction));
}
fmt::print("\n");
fmt::print("Initial register listing:\n");
for (size_t i = 0; i < regs.size(); ++i) {
fmt::print("{:3s}: {:016x}\n", static_cast<A64::Reg>(i), regs[i]);
}
for (size_t i = 0; i < vecs.size(); ++i) {
fmt::print("{:3s}: {}\n", static_cast<A64::Vec>(i), vecs[i]);
}
fmt::print("sp : {:016x}\n", initial_sp);
fmt::print("pc : {:016x}\n", instructions_start);
fmt::print("p : {:08x}\n", pstate);
fmt::print("fpcr {:08x}\n", fpcr);
fmt::print("fpcr.AHP {}\n", FP::FPCR{fpcr}.AHP());
fmt::print("fpcr.DN {}\n", FP::FPCR{fpcr}.DN());
fmt::print("fpcr.FZ {}\n", FP::FPCR{fpcr}.FZ());
fmt::print("fpcr.RMode {}\n", static_cast<size_t>(FP::FPCR{fpcr}.RMode()));
fmt::print("fpcr.FZ16 {}\n", FP::FPCR{fpcr}.FZ16());
fmt::print("\n");
fmt::print("Final register listing:\n");
fmt::print(" unicorn dynarmic\n");
const auto uni_regs = uni.GetRegisters();
for (size_t i = 0; i < regs.size(); ++i) {
fmt::print("{:3s}: {:016x} {:016x} {}\n", static_cast<A64::Reg>(i), uni_regs[i], jit.GetRegisters()[i], uni_regs[i] != jit.GetRegisters()[i] ? "*" : "");
}
const auto uni_vecs = uni.GetVectors();
for (size_t i = 0; i < vecs.size(); ++i) {
fmt::print("{:3s}: {} {} {}\n", static_cast<A64::Vec>(i), uni_vecs[i], jit.GetVectors()[i], uni_vecs[i] != jit.GetVectors()[i] ? "*" : "");
}
fmt::print("sp : {:016x} {:016x} {}\n", uni.GetSP(), jit.GetSP(), uni.GetSP() != jit.GetSP() ? "*" : "");
fmt::print("pc : {:016x} {:016x} {}\n", uni.GetPC(), jit.GetPC(), uni.GetPC() != jit.GetPC() ? "*" : "");
fmt::print("p : {:08x} {:08x} {}\n", uni.GetPstate(), jit.GetPstate(), (uni.GetPstate() & 0xF0000000) != (jit.GetPstate() & 0xF0000000) ? "*" : "");
fmt::print("qc : {:08x} {:08x} {}\n", uni.GetFpsr(), jit.GetFpsr(), FP::FPSR{uni.GetFpsr()}.QC() != FP::FPSR{jit.GetFpsr()}.QC() ? "*" : "");
fmt::print("\n");
fmt::print("Modified memory:\n");
fmt::print(" uni dyn\n");
auto uni_iter = uni_env.modified_memory.begin();
auto jit_iter = jit_env.modified_memory.begin();
while (uni_iter != uni_env.modified_memory.end() || jit_iter != jit_env.modified_memory.end()) {
if (uni_iter == uni_env.modified_memory.end() || (jit_iter != jit_env.modified_memory.end() && uni_iter->first > jit_iter->first)) {
fmt::print("{:016x}: {:02x} *\n", jit_iter->first, jit_iter->second);
jit_iter++;
} else if (jit_iter == jit_env.modified_memory.end() || jit_iter->first > uni_iter->first) {
fmt::print("{:016x}: {:02x} *\n", uni_iter->first, uni_iter->second);
uni_iter++;
} else if (uni_iter->first == jit_iter->first) {
fmt::print("{:016x}: {:02x} {:02x} {}\n", uni_iter->first, uni_iter->second, jit_iter->second, uni_iter->second != jit_iter->second ? "*" : "");
uni_iter++;
jit_iter++;
}
}
fmt::print("\n");
const auto get_code = [&jit_env](u64 vaddr) { return jit_env.MemoryReadCode(vaddr); };
IR::Block ir_block = A64::Translate({instructions_start, FP::FPCR{fpcr}}, get_code, {});
Optimization::A64CallbackConfigPass(ir_block, GetUserConfig(jit_env));
fmt::print("IR:\n");
fmt::print("{}\n", IR::DumpBlock(ir_block));
Optimization::A64GetSetElimination(ir_block);
Optimization::DeadCodeElimination(ir_block);
Optimization::ConstantPropagation(ir_block);
Optimization::DeadCodeElimination(ir_block);
fmt::print("Optimized IR:\n");
fmt::print("{}\n", IR::DumpBlock(ir_block));
fmt::print("x86_64:\n");
fmt::print("{}\n", jit.Disassemble());
fmt::print("Interrupts:\n");
for (auto& i : uni_env.interrupts) {
puts(i.c_str());
}
};
REQUIRE(uni_env.code_mem_modified_by_guest == jit_env.code_mem_modified_by_guest);
if (uni_env.code_mem_modified_by_guest) {
return;
}
REQUIRE(uni.GetPC() == jit.GetPC());
REQUIRE(uni.GetRegisters() == jit.GetRegisters());
REQUIRE(uni.GetVectors() == jit.GetVectors());
REQUIRE(uni.GetSP() == jit.GetSP());
REQUIRE((uni.GetPstate() & 0xF0000000) == (jit.GetPstate() & 0xF0000000));
REQUIRE(uni_env.modified_memory == jit_env.modified_memory);
REQUIRE(uni_env.interrupts.empty());
REQUIRE(FP::FPSR{uni.GetFpsr()}.QC() == FP::FPSR{jit.GetFpsr()}.QC());
}
TEST_CASE("A64: Single random instruction", "[a64]") {
A64TestEnv jit_env{};
A64TestEnv uni_env{};
Dynarmic::A64::Jit jit{GetUserConfig(jit_env)};
A64Unicorn uni{uni_env};
A64Unicorn::RegisterArray regs;
A64Unicorn::VectorArray vecs;
std::vector<u32> instructions(1);
for (size_t iteration = 0; iteration < 100000; ++iteration) {
std::generate(regs.begin(), regs.end(), []{ return RandInt<u64>(0, ~u64(0)); });
std::generate(vecs.begin(), vecs.end(), RandomVector);
instructions[0] = GenRandomInst(0, true);
const u64 start_address = RandInt<u64>(0, 0x10'0000'0000) * 4;
const u32 pstate = RandInt<u32>(0, 0xF) << 28;
const u32 fpcr = RandomFpcr();
INFO("Instruction: 0x" << std::hex << instructions[0]);
RunTestInstance(jit, uni, jit_env, uni_env, regs, vecs, start_address, instructions, pstate, fpcr);
}
}
TEST_CASE("A64: Floating point instructions", "[a64]") {
A64TestEnv jit_env{};
A64TestEnv uni_env{};
Dynarmic::A64::Jit jit{GetUserConfig(jit_env)};
A64Unicorn uni{uni_env};
static constexpr std::array<u64, 80> float_numbers {
0x00000000, // positive zero
0x00000001, // smallest positive denormal
0x00000076, //
0x00002b94, //
0x00636d24, //
0x007fffff, // largest positive denormal
0x00800000, // smallest positive normalised real
0x00800002, //
0x01398437, //
0x0ba98d27, //
0x0ba98d7a, //
0x751f853a, //
0x7f7ffff0, //
0x7f7fffff, // largest positive normalised real
0x7f800000, // positive infinity
0x7f800001, // first positive SNaN
0x7f984a37, //
0x7fbfffff, // last positive SNaN
0x7fc00000, // first positive QNaN
0x7fd9ba98, //
0x7fffffff, // last positive QNaN
0x80000000, // negative zero
0x80000001, // smallest negative denormal
0x80000076, //
0x80002b94, //
0x80636d24, //
0x807fffff, // largest negative denormal
0x80800000, // smallest negative normalised real
0x80800002, //
0x81398437, //
0x8ba98d27, //
0x8ba98d7a, //
0xf51f853a, //
0xff7ffff0, //
0xff7fffff, // largest negative normalised real
0xff800000, // negative infinity
0xff800001, // first negative SNaN
0xff984a37, //
0xffbfffff, // last negative SNaN
0xffc00000, // first negative QNaN
0xffd9ba98, //
0xffffffff, // last negative QNaN
// some random numbers follow
0x4f3495cb,
0xe73a5134,
0x7c994e9e,
0x6164bd6c,
0x09503366,
0xbf5a97c9,
0xe6ff1a14,
0x77f31e2f,
0xaab4d7d8,
0x0966320b,
0xb26bddee,
0xb5c8e5d3,
0x317285d3,
0x3c9623b1,
0x51fd2c7c,
0x7b906a6c,
0x3f800000,
0x3dcccccd,
0x3f000000,
0x42280000,
0x3eaaaaab,
0xc1200000,
0xbf800000,
0xbf8147ae,
0x3f8147ae,
0x415df525,
0xc79b271e,
0x460e8c84,
// some 64-bit-float upper-halves
0x7ff00000, // +SNaN / +Inf
0x7ff0abcd, // +SNaN
0x7ff80000, // +QNaN
0x7ff81234, // +QNaN
0xfff00000, // -SNaN / -Inf
0xfff05678, // -SNaN
0xfff80000, // -QNaN
0xfff809ef, // -QNaN
0x3ff00000, // Number near +1.0
0xbff00000, // Number near -1.0
};
const auto gen_float = [&]{
return float_numbers[RandInt<size_t>(0, float_numbers.size() - 1)];
};
const auto gen_vector = [&]{
u64 upper = (gen_float() << 32) | gen_float();
u64 lower = (gen_float() << 32) | gen_float();
return Vector{lower, upper};
};
A64Unicorn::RegisterArray regs;
A64Unicorn::VectorArray vecs;
std::vector<u32> instructions(1);
for (size_t iteration = 0; iteration < 100000; ++iteration) {
std::generate(regs.begin(), regs.end(), gen_float);
std::generate(vecs.begin(), vecs.end(), gen_vector);
instructions[0] = GenFloatInst(0, true);
const u64 start_address = RandInt<u64>(0, 0x10'0000'0000) * 4;
const u32 pstate = RandInt<u32>(0, 0xF) << 28;
const u32 fpcr = RandomFpcr();
INFO("Instruction: 0x" << std::hex << instructions[0]);
RunTestInstance(jit, uni, jit_env, uni_env, regs, vecs, start_address, instructions, pstate, fpcr);
}
}
TEST_CASE("A64: Small random block", "[a64]") {
A64TestEnv jit_env{};
A64TestEnv uni_env{};
Dynarmic::A64::Jit jit{GetUserConfig(jit_env)};
A64Unicorn uni{uni_env};
A64Unicorn::RegisterArray regs;
A64Unicorn::VectorArray vecs;
std::vector<u32> instructions(5);
for (size_t iteration = 0; iteration < 100000; ++iteration) {
std::generate(regs.begin(), regs.end(), [] { return RandInt<u64>(0, ~u64(0)); });
std::generate(vecs.begin(), vecs.end(), RandomVector);
instructions[0] = GenRandomInst(0, false);
instructions[1] = GenRandomInst(4, false);
instructions[2] = GenRandomInst(8, false);
instructions[3] = GenRandomInst(12, false);
instructions[4] = GenRandomInst(16, true);
const u64 start_address = RandInt<u64>(0, 0x10'0000'0000) * 4;
const u32 pstate = RandInt<u32>(0, 0xF) << 28;
const u32 fpcr = RandomFpcr();
INFO("Instruction 1: 0x" << std::hex << instructions[0]);
INFO("Instruction 2: 0x" << std::hex << instructions[1]);
INFO("Instruction 3: 0x" << std::hex << instructions[2]);
INFO("Instruction 4: 0x" << std::hex << instructions[3]);
INFO("Instruction 5: 0x" << std::hex << instructions[4]);
RunTestInstance(jit, uni, jit_env, uni_env, regs, vecs, start_address, instructions, pstate, fpcr);
}
}
TEST_CASE("A64: Large random block", "[a64]") {
A64TestEnv jit_env{};
A64TestEnv uni_env{};
Dynarmic::A64::Jit jit{GetUserConfig(jit_env)};
A64Unicorn uni{uni_env};
A64Unicorn::RegisterArray regs;
A64Unicorn::VectorArray vecs;
constexpr size_t instruction_count = 100;
std::vector<u32> instructions(instruction_count);
for (size_t iteration = 0; iteration < 500; ++iteration) {
std::generate(regs.begin(), regs.end(), [] { return RandInt<u64>(0, ~u64(0)); });
std::generate(vecs.begin(), vecs.end(), RandomVector);
for (size_t j = 0; j < instruction_count; ++j) {
instructions[j] = GenRandomInst(j * 4, j == instruction_count - 1);
}
const u64 start_address = RandInt<u64>(0, 0x10'0000'0000) * 4;
const u32 pstate = RandInt<u32>(0, 0xF) << 28;
const u32 fpcr = RandomFpcr();
RunTestInstance(jit, uni, jit_env, uni_env, regs, vecs, start_address, instructions, pstate, fpcr);
}
}

View File

@@ -0,0 +1,30 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <catch.hpp>
#include <dynarmic/A64/a64.h>
#include "testenv.h"
TEST_CASE("misaligned load/store do not use page_table when detect_misaligned_access_via_page_table is set", "[a64]") {
A64TestEnv env;
Dynarmic::A64::UserConfig conf{&env};
conf.page_table = nullptr;
conf.detect_misaligned_access_via_page_table = 128;
conf.only_detect_misalignment_via_page_table_on_page_boundary = true;
Dynarmic::A64::Jit jit{conf};
env.code_mem.emplace_back(0x3c800400); // STR Q0, [X0], #0
env.code_mem.emplace_back(0x14000000); // B .
jit.SetPC(0);
jit.SetRegister(0, 0x000000000b0afff8);
env.ticks_left = 2;
jit.Run();
// If we don't crash we're fine.
}

127
externals/dynarmic/tests/A64/testenv.h vendored Executable file
View File

@@ -0,0 +1,127 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#pragma once
#include <array>
#include <map>
#include <dynarmic/A64/a64.h>
#include "common/assert.h"
#include "common/common_types.h"
using Vector = Dynarmic::A64::Vector;
class A64TestEnv final : public Dynarmic::A64::UserCallbacks {
public:
u64 ticks_left = 0;
bool code_mem_modified_by_guest = false;
u64 code_mem_start_address = 0;
std::vector<u32> code_mem;
std::map<u64, u8> modified_memory;
std::vector<std::string> interrupts;
bool IsInCodeMem(u64 vaddr) const {
return vaddr >= code_mem_start_address && vaddr < code_mem_start_address + code_mem.size() * 4;
}
std::uint32_t MemoryReadCode(u64 vaddr) override {
if (!IsInCodeMem(vaddr)) {
return 0x14000000; // B .
}
const size_t index = (vaddr - code_mem_start_address) / 4;
return code_mem[index];
}
std::uint8_t MemoryRead8(u64 vaddr) override {
if (IsInCodeMem(vaddr)) {
return reinterpret_cast<u8*>(code_mem.data())[vaddr - code_mem_start_address];
}
if (auto iter = modified_memory.find(vaddr); iter != modified_memory.end()) {
return iter->second;
}
return static_cast<u8>(vaddr);
}
std::uint16_t MemoryRead16(u64 vaddr) override {
return u16(MemoryRead8(vaddr)) | u16(MemoryRead8(vaddr + 1)) << 8;
}
std::uint32_t MemoryRead32(u64 vaddr) override {
return u32(MemoryRead16(vaddr)) | u32(MemoryRead16(vaddr + 2)) << 16;
}
std::uint64_t MemoryRead64(u64 vaddr) override {
return u64(MemoryRead32(vaddr)) | u64(MemoryRead32(vaddr + 4)) << 32;
}
Vector MemoryRead128(u64 vaddr) override {
return {MemoryRead64(vaddr), MemoryRead64(vaddr + 8)};
}
void MemoryWrite8(u64 vaddr, std::uint8_t value) override {
if (IsInCodeMem(vaddr)) {
code_mem_modified_by_guest = true;
}
modified_memory[vaddr] = value;
}
void MemoryWrite16(u64 vaddr, std::uint16_t value) override {
MemoryWrite8(vaddr, static_cast<u8>(value));
MemoryWrite8(vaddr + 1, static_cast<u8>(value >> 8));
}
void MemoryWrite32(u64 vaddr, std::uint32_t value) override {
MemoryWrite16(vaddr, static_cast<u16>(value));
MemoryWrite16(vaddr + 2, static_cast<u16>(value >> 16));
}
void MemoryWrite64(u64 vaddr, std::uint64_t value) override {
MemoryWrite32(vaddr, static_cast<u32>(value));
MemoryWrite32(vaddr + 4, static_cast<u32>(value >> 32));
}
void MemoryWrite128(u64 vaddr, Vector value) override {
MemoryWrite64(vaddr, value[0]);
MemoryWrite64(vaddr + 8, value[1]);
}
bool MemoryWriteExclusive8(u64 vaddr, std::uint8_t value, [[maybe_unused]] std::uint8_t expected) override {
MemoryWrite8(vaddr, value);
return true;
}
bool MemoryWriteExclusive16(u64 vaddr, std::uint16_t value, [[maybe_unused]] std::uint16_t expected) override {
MemoryWrite16(vaddr, value);
return true;
}
bool MemoryWriteExclusive32(u64 vaddr, std::uint32_t value, [[maybe_unused]] std::uint32_t expected) override {
MemoryWrite32(vaddr, value);
return true;
}
bool MemoryWriteExclusive64(u64 vaddr, std::uint64_t value, [[maybe_unused]] std::uint64_t expected) override {
MemoryWrite64(vaddr, value);
return true;
}
bool MemoryWriteExclusive128(u64 vaddr, Vector value, [[maybe_unused]] Vector expected) override {
MemoryWrite128(vaddr, value);
return true;
}
void InterpreterFallback(u64 pc, size_t num_instructions) override { ASSERT_MSG(false, "InterpreterFallback({:016x}, {})", pc, num_instructions); }
void CallSVC(std::uint32_t swi) override { ASSERT_MSG(false, "CallSVC({})", swi); }
void ExceptionRaised(u64 pc, Dynarmic::A64::Exception /*exception*/) override { ASSERT_MSG(false, "ExceptionRaised({:016x})", pc); }
void AddTicks(std::uint64_t ticks) override {
if (ticks > ticks_left) {
ticks_left = 0;
return;
}
ticks_left -= ticks;
}
std::uint64_t GetTicksRemaining() override {
return ticks_left;
}
std::uint64_t GetCNTPCT() override {
return 0x10000000000 - ticks_left;
}
};

View File

@@ -0,0 +1,81 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <array>
#include <catch.hpp>
#include "rand_int.h"
#include "testenv.h"
#include "unicorn_emu/a64_unicorn.h"
using namespace Dynarmic;
TEST_CASE("Unicorn: Sanity test", "[a64]") {
A64TestEnv env;
env.code_mem.emplace_back(0x8b020020); // ADD X0, X1, X2
env.code_mem.emplace_back(0x14000000); // B .
constexpr A64Unicorn::RegisterArray regs{
0, 1, 2, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0
};
A64Unicorn unicorn{env};
unicorn.SetRegisters(regs);
unicorn.SetPC(0);
env.ticks_left = 2;
unicorn.Run();
REQUIRE(unicorn.GetRegisters()[0] == 3);
REQUIRE(unicorn.GetRegisters()[1] == 1);
REQUIRE(unicorn.GetRegisters()[2] == 2);
REQUIRE(unicorn.GetPC() == 4);
}
TEST_CASE("Unicorn: Ensure 0xFFFF'FFFF'FFFF'FFFF is readable", "[a64]") {
A64TestEnv env;
env.code_mem.emplace_back(0x385fed99); // LDRB W25, [X12, #0xfffffffffffffffe]!
env.code_mem.emplace_back(0x14000000); // B .
A64Unicorn::RegisterArray regs{};
regs[12] = 1;
A64Unicorn unicorn{env};
unicorn.SetRegisters(regs);
unicorn.SetPC(0);
env.ticks_left = 2;
unicorn.Run();
REQUIRE(unicorn.GetPC() == 4);
}
TEST_CASE("Unicorn: Ensure is able to read across page boundaries", "[a64]") {
A64TestEnv env;
env.code_mem.emplace_back(0xb85f93d9); // LDUR W25, [X30, #0xfffffffffffffff9]
env.code_mem.emplace_back(0x14000000); // B .
A64Unicorn::RegisterArray regs{};
regs[30] = 4;
A64Unicorn unicorn{env};
unicorn.SetRegisters(regs);
unicorn.SetPC(0);
env.ticks_left = 2;
unicorn.Run();
REQUIRE(unicorn.GetPC() == 4);
}

57
externals/dynarmic/tests/CMakeLists.txt vendored Executable file
View File

@@ -0,0 +1,57 @@
if ((NOT "A32" IN_LIST DYNARMIC_FRONTENDS) OR (NOT "A64" IN_LIST DYNARMIC_FRONTENDS))
return()
endif()
add_executable(dynarmic_tests
A32/test_arm_disassembler.cpp
A32/test_arm_instructions.cpp
A32/test_thumb_instructions.cpp
A32/testenv.h
A64/a64.cpp
A64/testenv.h
cpu_info.cpp
decoder_tests.cpp
fp/FPToFixed.cpp
fp/FPValue.cpp
fp/mantissa_util_tests.cpp
fp/unpacked_tests.cpp
main.cpp
rand_int.h
)
if (DYNARMIC_TESTS_USE_UNICORN)
target_sources(dynarmic_tests PRIVATE
A32/fuzz_arm.cpp
A32/fuzz_thumb.cpp
A64/fuzz_with_unicorn.cpp
A64/misaligned_page_table.cpp
A64/verify_unicorn.cpp
fuzz_util.cpp
fuzz_util.h
unicorn_emu/a32_unicorn.cpp
unicorn_emu/a32_unicorn.h
unicorn_emu/a64_unicorn.cpp
unicorn_emu/a64_unicorn.h
)
target_link_libraries(dynarmic_tests PRIVATE Unicorn::Unicorn)
endif()
add_executable(dynarmic_print_info
print_info.cpp
)
include(CreateDirectoryGroups)
create_target_directory_groups(dynarmic_tests)
create_target_directory_groups(dynarmic_print_info)
target_link_libraries(dynarmic_tests PRIVATE dynarmic boost catch fmt mp xbyak)
target_include_directories(dynarmic_tests PRIVATE . ../src)
target_compile_options(dynarmic_tests PRIVATE ${DYNARMIC_CXX_FLAGS})
target_compile_definitions(dynarmic_tests PRIVATE FMT_USE_USER_DEFINED_LITERALS=0)
target_link_libraries(dynarmic_print_info PRIVATE dynarmic boost catch fmt mp)
target_include_directories(dynarmic_print_info PRIVATE . ../src)
target_compile_options(dynarmic_print_info PRIVATE ${DYNARMIC_CXX_FLAGS})
target_compile_definitions(dynarmic_print_info PRIVATE FMT_USE_USER_DEFINED_LITERALS=0)
add_test(dynarmic_tests dynarmic_tests)

82
externals/dynarmic/tests/cpu_info.cpp vendored Executable file
View File

@@ -0,0 +1,82 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2020 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <array>
#include <utility>
#include <catch.hpp>
#include <fmt/format.h>
#include <xbyak_util.h>
TEST_CASE("Host CPU supports", "[a64]") {
Xbyak::util::Cpu cpu_info;
static constexpr std::array types{
std::make_pair(Xbyak::util::Cpu::tMMX, "MMX"),
std::make_pair(Xbyak::util::Cpu::tMMX2, "MMX2"),
std::make_pair(Xbyak::util::Cpu::tCMOV, "CMOV"),
std::make_pair(Xbyak::util::Cpu::tSSE, "SSE"),
std::make_pair(Xbyak::util::Cpu::tSSE2, "SSE2"),
std::make_pair(Xbyak::util::Cpu::tSSE3, "SSE3"),
std::make_pair(Xbyak::util::Cpu::tSSSE3, "SSSE3"),
std::make_pair(Xbyak::util::Cpu::tSSE41, "SSE41"),
std::make_pair(Xbyak::util::Cpu::tSSE42, "SSE42"),
std::make_pair(Xbyak::util::Cpu::tPOPCNT, "POPCNT"),
std::make_pair(Xbyak::util::Cpu::tAESNI, "AESNI"),
std::make_pair(Xbyak::util::Cpu::tSSE5, "SSE5"),
std::make_pair(Xbyak::util::Cpu::tOSXSAVE, "OSXSAVE"),
std::make_pair(Xbyak::util::Cpu::tPCLMULQDQ, "PCLMULQDQ"),
std::make_pair(Xbyak::util::Cpu::tAVX, "AVX"),
std::make_pair(Xbyak::util::Cpu::tFMA, "FMA"),
std::make_pair(Xbyak::util::Cpu::t3DN, "3DN"),
std::make_pair(Xbyak::util::Cpu::tE3DN, "E3DN"),
std::make_pair(Xbyak::util::Cpu::tSSE4a, "SSE4a"),
std::make_pair(Xbyak::util::Cpu::tRDTSCP, "RDTSCP"),
std::make_pair(Xbyak::util::Cpu::tAVX2, "AVX2"),
std::make_pair(Xbyak::util::Cpu::tBMI1, "BMI1"),
std::make_pair(Xbyak::util::Cpu::tBMI2, "BMI2"),
std::make_pair(Xbyak::util::Cpu::tLZCNT, "LZCNT"),
std::make_pair(Xbyak::util::Cpu::tINTEL, "INTEL"),
std::make_pair(Xbyak::util::Cpu::tAMD, "AMD"),
std::make_pair(Xbyak::util::Cpu::tENHANCED_REP, "ENHANCED_REP"),
std::make_pair(Xbyak::util::Cpu::tRDRAND, "RDRAND"),
std::make_pair(Xbyak::util::Cpu::tADX, "ADX"),
std::make_pair(Xbyak::util::Cpu::tRDSEED, "RDSEED"),
std::make_pair(Xbyak::util::Cpu::tSMAP, "SMAP"),
std::make_pair(Xbyak::util::Cpu::tHLE, "HLE"),
std::make_pair(Xbyak::util::Cpu::tRTM, "RTM"),
std::make_pair(Xbyak::util::Cpu::tF16C, "F16C"),
std::make_pair(Xbyak::util::Cpu::tMOVBE, "MOVBE"),
std::make_pair(Xbyak::util::Cpu::tAVX512F, "AVX512F"),
std::make_pair(Xbyak::util::Cpu::tAVX512DQ, "AVX512DQ"),
std::make_pair(Xbyak::util::Cpu::tAVX512_IFMA, "AVX512_IFMA"),
std::make_pair(Xbyak::util::Cpu::tAVX512IFMA, "AVX512IFMA"),
std::make_pair(Xbyak::util::Cpu::tAVX512PF, "AVX512PF"),
std::make_pair(Xbyak::util::Cpu::tAVX512ER, "AVX512ER"),
std::make_pair(Xbyak::util::Cpu::tAVX512CD, "AVX512CD"),
std::make_pair(Xbyak::util::Cpu::tAVX512BW, "AVX512BW"),
std::make_pair(Xbyak::util::Cpu::tAVX512VL, "AVX512VL"),
std::make_pair(Xbyak::util::Cpu::tAVX512_VBMI, "AVX512_VBMI"),
std::make_pair(Xbyak::util::Cpu::tAVX512VBMI, "AVX512VBMI"),
std::make_pair(Xbyak::util::Cpu::tAVX512_4VNNIW, "AVX512_4VNNIW"),
std::make_pair(Xbyak::util::Cpu::tAVX512_4FMAPS, "AVX512_4FMAPS"),
std::make_pair(Xbyak::util::Cpu::tPREFETCHWT1, "PREFETCHWT1"),
std::make_pair(Xbyak::util::Cpu::tPREFETCHW, "PREFETCHW"),
std::make_pair(Xbyak::util::Cpu::tSHA, "SHA"),
std::make_pair(Xbyak::util::Cpu::tMPX, "MPX"),
std::make_pair(Xbyak::util::Cpu::tAVX512_VBMI2, "AVX512_VBMI2"),
std::make_pair(Xbyak::util::Cpu::tGFNI, "GFNI"),
std::make_pair(Xbyak::util::Cpu::tVAES, "VAES"),
std::make_pair(Xbyak::util::Cpu::tVPCLMULQDQ, "VPCLMULQDQ"),
std::make_pair(Xbyak::util::Cpu::tAVX512_VNNI, "AVX512_VNNI"),
std::make_pair(Xbyak::util::Cpu::tAVX512_BITALG, "AVX512_BITALG"),
std::make_pair(Xbyak::util::Cpu::tAVX512_VPOPCNTDQ, "AVX512_VPOPCNTDQ"),
};
for (const auto& [type, name] : types) {
if (cpu_info.has(type)) {
fmt::print("CPU has {}\n", name);
}
}
}

75
externals/dynarmic/tests/decoder_tests.cpp vendored Executable file
View File

@@ -0,0 +1,75 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2020 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <cstring>
#include <iostream>
#include <iomanip>
#include <catch.hpp>
#include <dynarmic/A32/config.h>
#include "common/assert.h"
#include "frontend/A32/decoder/asimd.h"
#include "frontend/A32/translate/impl/translate_arm.h"
#include "frontend/ir/opcodes.h"
using namespace Dynarmic;
TEST_CASE("ASIMD Decoder: Ensure table order correctness", "[decode][a32]") {
const auto table = A32::GetASIMDDecodeTable<A32::ArmTranslatorVisitor>();
const auto get_ir = [](const A32::ASIMDMatcher<A32::ArmTranslatorVisitor>& matcher, u32 instruction) {
ASSERT(matcher.Matches(instruction));
const A32::LocationDescriptor location{0, {}, {}};
IR::Block block{location};
A32::ArmTranslatorVisitor visitor{block, location, {}};
matcher.call(visitor, instruction);
return block;
};
const auto is_decode_error = [&get_ir](const A32::ASIMDMatcher<A32::ArmTranslatorVisitor>& matcher, u32 instruction){
const auto block = get_ir(matcher, instruction);
for (const auto& ir_inst : block) {
if (ir_inst.GetOpcode() == IR::Opcode::A32ExceptionRaised) {
if (static_cast<A32::Exception>(ir_inst.GetArg(1).GetU64()) == A32::Exception::DecodeError) {
return true;
}
}
}
return false;
};
for (auto iter = table.cbegin(); iter != table.cend(); ++iter) {
if (std::strncmp(iter->GetName(), "UNALLOCATED", 11) == 0) {
continue;
}
const u32 expect = iter->GetExpected();
const u32 mask = iter->GetMask();
u32 x = 0;
do {
const u32 instruction = expect | x;
const bool iserr = is_decode_error(*iter, instruction);
const auto alternative = std::find_if(table.cbegin(), iter, [instruction](const auto& m) { return m.Matches(instruction); });
const bool altiserr = is_decode_error(*alternative, instruction);
INFO("Instruction: " << std::hex << std::setfill('0') << std::setw(8) << instruction);
INFO("Expect: " << std::hex << std::setfill('0') << std::setw(8) << expect);
INFO("Fill: " << std::hex << std::setfill('0') << std::setw(8) << x);
INFO("Name: " << iter->GetName());
INFO("iserr: " << iserr);
INFO("alternative: " << alternative->GetName());
INFO("altiserr: " << altiserr);
REQUIRE(((!iserr && alternative == iter) || (iserr && alternative != iter && !altiserr)));
x = ((x | mask) + 1) & ~mask;
} while (x != 0);
}
}

40
externals/dynarmic/tests/fp/FPToFixed.cpp vendored Executable file
View File

@@ -0,0 +1,40 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <tuple>
#include <vector>
#include <catch.hpp>
#include "common/common_types.h"
#include "common/fp/fpcr.h"
#include "common/fp/fpsr.h"
#include "common/fp/op.h"
#include "common/fp/rounding_mode.h"
#include "rand_int.h"
using namespace Dynarmic;
using namespace Dynarmic::FP;
TEST_CASE("FPToFixed", "[fp]") {
const std::vector<std::tuple<u32, size_t, u64, u32>> test_cases {
{0x447A0000, 64, 0x000003E8, 0x00},
{0xC47A0000, 32, 0xFFFFFC18, 0x00},
{0x4479E000, 64, 0x000003E8, 0x10},
{0x50800000, 32, 0x7FFFFFFF, 0x01},
{0xD0800000, 32, 0x80000000, 0x01},
{0xCF000000, 32, 0x80000000, 0x00},
{0x80002B94, 64, 0x00000000, 0x10},
{0x80636D24, 64, 0x00000000, 0x10},
};
const FPCR fpcr;
for (auto [input, ibits, expected_output, expected_fpsr] : test_cases) {
FPSR fpsr;
const u64 output = FPToFixed<u32>(ibits, input, 0, false, fpcr, RoundingMode::ToNearest_TieEven, fpsr);
REQUIRE(output == expected_output);
REQUIRE(fpsr.Value() == expected_fpsr);
}
}

15
externals/dynarmic/tests/fp/FPValue.cpp vendored Executable file
View File

@@ -0,0 +1,15 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include "common/fp/info.h"
using namespace Dynarmic::FP;
static_assert(FPValue<u32, false, 0, 1>() == 0x3f800000);
static_assert(FPValue<u32, false, -1, 3>() == 0x3fc00000);
static_assert(FPValue<u32, false, 0, 12739812>() == 0x4b4264e4);
static_assert(FPValue<u32, false, -8, 100>() == 0x3ec80000);
static_assert(FPValue<u32, true, 0, 1>() == 0xbf800000);
static_assert(FPValue<u32, false, -1, 1>() == 0x3f000000);

View File

@@ -0,0 +1,63 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <tuple>
#include <vector>
#include <catch.hpp>
#include "common/common_types.h"
#include "common/fp/mantissa_util.h"
#include "common/safe_ops.h"
#include "rand_int.h"
using namespace Dynarmic;
using namespace Dynarmic::FP;
TEST_CASE("ResidualErrorOnRightShift", "[fp]") {
const std::vector<std::tuple<u32, int, ResidualError>> test_cases {
{0x00000001, 1, ResidualError::Half},
{0x00000002, 1, ResidualError::Zero},
{0x00000001, 2, ResidualError::LessThanHalf},
{0x00000002, 2, ResidualError::Half},
{0x00000003, 2, ResidualError::GreaterThanHalf},
{0x00000004, 2, ResidualError::Zero},
{0x00000005, 2, ResidualError::LessThanHalf},
{0x00000006, 2, ResidualError::Half},
{0x00000007, 2, ResidualError::GreaterThanHalf},
};
for (auto [mantissa, shift, expected_result] : test_cases) {
const ResidualError result = ResidualErrorOnRightShift(mantissa, shift);
REQUIRE(result == expected_result);
}
}
TEST_CASE("ResidualErrorOnRightShift Randomized", "[fp]") {
for (size_t test = 0; test < 100000; test++) {
const u64 mantissa = Common::SignExtend<32, u64>(RandInt<u32>(0, 0xFFFFFFFF));
const int shift = RandInt<int>(-60, 60);
const ResidualError result = ResidualErrorOnRightShift(mantissa, shift);
const u64 calculated_error = Safe::ArithmeticShiftRightDouble(mantissa, u64(0), shift);
const ResidualError expected_result = [&]{
constexpr u64 half_error = 0x8000'0000'0000'0000ull;
if (calculated_error == 0) {
return ResidualError::Zero;
}
if (calculated_error < half_error) {
return ResidualError::LessThanHalf;
}
if (calculated_error == half_error) {
return ResidualError::Half;
}
return ResidualError::GreaterThanHalf;
}();
INFO(std::hex << "mantissa " << mantissa << " shift " << shift << " calculated_error " << calculated_error);
REQUIRE(result == expected_result);
}
}

View File

@@ -0,0 +1,95 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <tuple>
#include <vector>
#include <catch.hpp>
#include "common/common_types.h"
#include "common/fp/fpcr.h"
#include "common/fp/fpsr.h"
#include "common/fp/unpacked.h"
#include "rand_int.h"
using namespace Dynarmic;
using namespace Dynarmic::FP;
TEST_CASE("FPUnpack Tests", "[fp]") {
const static std::vector<std::tuple<u32, std::tuple<FPType, bool, FPUnpacked>, u32>> test_cases {
{0x00000000, {FPType::Zero, false, ToNormalized(false, 0, 0)}, 0},
{0x7F800000, {FPType::Infinity, false, ToNormalized(false, 1000000, 1)}, 0},
{0xFF800000, {FPType::Infinity, true, ToNormalized(true, 1000000, 1)}, 0},
{0x7F800001, {FPType::SNaN, false, ToNormalized(false, 0, 0)}, 0},
{0xFF800001, {FPType::SNaN, true, ToNormalized(true, 0, 0)}, 0},
{0x7FC00001, {FPType::QNaN, false, ToNormalized(false, 0, 0)}, 0},
{0xFFC00001, {FPType::QNaN, true, ToNormalized(true, 0, 0)}, 0},
{0x00000001, {FPType::Nonzero, false, ToNormalized(false, -149, 1)}, 0}, // Smallest single precision denormal is 2^-149.
{0x3F7FFFFF, {FPType::Nonzero, false, ToNormalized(false, -24, 0xFFFFFF)}, 0}, // 1.0 - epsilon
};
const FPCR fpcr;
for (const auto& [input, expected_output, expected_fpsr] : test_cases) {
FPSR fpsr;
const auto output = FPUnpack<u32>(input, fpcr, fpsr);
INFO("Input: " << std::hex << input);
INFO("Output Sign: " << std::get<2>(output).sign);
INFO("Output Exponent: " << std::get<2>(output).exponent);
INFO("Output Mantissa: " << std::hex << std::get<2>(output).mantissa);
INFO("Expected Sign: " << std::get<2>(expected_output).sign);
INFO("Expected Exponent: " << std::get<2>(expected_output).exponent);
INFO("Expected Mantissa: " << std::hex << std::get<2>(expected_output).mantissa);
REQUIRE(output == expected_output);
REQUIRE(fpsr.Value() == expected_fpsr);
}
}
TEST_CASE("FPRound Tests", "[fp]") {
const static std::vector<std::tuple<u32, std::tuple<FPType, bool, FPUnpacked>, u32>> test_cases {
{0x7F800000, {FPType::Infinity, false, ToNormalized(false, 1000000, 1)}, 0x14},
{0xFF800000, {FPType::Infinity, true, ToNormalized(true, 1000000, 1)}, 0x14},
{0x00000001, {FPType::Nonzero, false, ToNormalized(false, -149, 1)}, 0}, // Smallest single precision denormal is 2^-149.
{0x3F7FFFFF, {FPType::Nonzero, false, ToNormalized(false, -24, 0xFFFFFF)}, 0}, // 1.0 - epsilon
{0x3F800000, {FPType::Nonzero, false, ToNormalized(false, -28, 0xFFFFFFF)}, 0x10}, // rounds to 1.0
};
const FPCR fpcr;
for (const auto& [expected_output, input, expected_fpsr] : test_cases) {
FPSR fpsr;
const auto output = FPRound<u32>(std::get<2>(input), fpcr, fpsr);
INFO("Expected Output: " << std::hex << expected_output);
REQUIRE(output == expected_output);
REQUIRE(fpsr.Value() == expected_fpsr);
}
}
TEST_CASE("FPUnpack<->FPRound Round-trip Tests", "[fp]") {
const FPCR fpcr;
for (size_t count = 0; count < 100000; count++) {
FPSR fpsr;
const u32 input = RandInt(0, 1) == 0 ? RandInt<u32>(0x00000001, 0x7F800000) : RandInt<u32>(0x80000001, 0xFF800000);
const auto intermediate = std::get<2>(FPUnpack<u32>(input, fpcr, fpsr));
const u32 output = FPRound<u32>(intermediate, fpcr, fpsr);
INFO("Count: " << count);
INFO("Intermediate Values: " << std::hex << intermediate.sign << ';' << intermediate.exponent << ';' << intermediate.mantissa);
REQUIRE(input == output);
}
}
TEST_CASE("FPRound (near zero, round to posinf)", "[fp]") {
const FPUnpacked input = {false, -353, 0x0a98d25ace5b2000};
FPSR fpsr;
FPCR fpcr;
fpcr.RMode(RoundingMode::TowardsPlusInfinity);
const u32 output = FPRound<u32>(input, fpcr, fpsr);
REQUIRE(output == 0x00000001);
}

60
externals/dynarmic/tests/fuzz_util.cpp vendored Executable file
View File

@@ -0,0 +1,60 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <cstring>
#include <fmt/format.h>
#include <fmt/ostream.h>
#include "common/assert.h"
#include "common/fp/fpcr.h"
#include "common/fp/rounding_mode.h"
#include "fuzz_util.h"
#include "rand_int.h"
using namespace Dynarmic;
std::ostream& operator<<(std::ostream& o, Vector vec) {
return o << fmt::format("{:016x}'{:016x}", vec[1], vec[0]);
}
Vector RandomVector() {
return {RandInt<u64>(0, ~u64(0)), RandInt<u64>(0, ~u64(0))};
}
u32 RandomFpcr() {
FP::FPCR fpcr;
fpcr.AHP(RandInt(0, 1) == 0);
fpcr.DN(RandInt(0, 1) == 0);
fpcr.FZ(RandInt(0, 1) == 0);
fpcr.RMode(static_cast<FP::RoundingMode>(RandInt(0, 3)));
fpcr.FZ16(RandInt(0, 1) == 0);
return fpcr.Value();
}
InstructionGenerator::InstructionGenerator(const char* format){
ASSERT(std::strlen(format) == 32);
for (int i = 0; i < 32; i++) {
const u32 bit = 1u << (31 - i);
switch (format[i]) {
case '0':
mask |= bit;
break;
case '1':
bits |= bit;
mask |= bit;
break;
default:
// Do nothing
break;
}
}
}
u32 InstructionGenerator::Generate() const {
const u32 random = RandInt<u32>(0, 0xFFFFFFFF);
return bits | (random & ~mask);
}

31
externals/dynarmic/tests/fuzz_util.h vendored Executable file
View File

@@ -0,0 +1,31 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#pragma once
#include <array>
#include <iosfwd>
#include "common/common_types.h"
using Vector = std::array<u64, 2>;
std::ostream& operator<<(std::ostream& o, Vector vec);
Vector RandomVector();
u32 RandomFpcr();
struct InstructionGenerator final {
public:
explicit InstructionGenerator(const char* format);
u32 Generate() const;
u32 Bits() const { return bits; }
u32 Mask() const { return mask; }
bool Match(u32 inst) const { return (inst & mask) == bits; }
private:
u32 bits = 0;
u32 mask = 0;
};

7
externals/dynarmic/tests/main.cpp vendored Executable file
View File

@@ -0,0 +1,7 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2016 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
#include <catch.hpp>

304
externals/dynarmic/tests/print_info.cpp vendored Executable file
View File

@@ -0,0 +1,304 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <map>
#include <optional>
#include <string>
#include <dynarmic/A32/a32.h>
#include <dynarmic/A32/disassembler.h>
#include "common/common_types.h"
#include "common/llvm_disassemble.h"
#include "frontend/A32/decoder/arm.h"
#include "frontend/A32/decoder/asimd.h"
#include "frontend/A32/decoder/vfp.h"
#include "frontend/A32/location_descriptor.h"
#include "frontend/A32/translate/impl/translate_arm.h"
#include "frontend/A32/translate/translate.h"
#include "frontend/A64/decoder/a64.h"
#include "frontend/A64/location_descriptor.h"
#include "frontend/A64/translate/impl/impl.h"
#include "frontend/A64/translate/translate.h"
#include "frontend/ir/basic_block.h"
#include "ir_opt/passes.h"
#include <fmt/format.h>
#include <fmt/ostream.h>
using namespace Dynarmic;
const char* GetNameOfA32Instruction(u32 instruction) {
if (auto vfp_decoder = A32::DecodeVFP<A32::ArmTranslatorVisitor>(instruction)) {
return vfp_decoder->get().GetName();
} else if (auto asimd_decoder = A32::DecodeASIMD<A32::ArmTranslatorVisitor>(instruction)) {
return asimd_decoder->get().GetName();
} else if (auto decoder = A32::DecodeArm<A32::ArmTranslatorVisitor>(instruction)) {
return decoder->get().GetName();
}
return "<null>";
}
const char* GetNameOfA64Instruction(u32 instruction) {
if (auto decoder = A64::Decode<A64::TranslatorVisitor>(instruction)) {
return decoder->get().GetName();
}
return "<null>";
}
void PrintA32Instruction(u32 instruction) {
fmt::print("{:08x} {}\n", instruction, Common::DisassembleAArch32(instruction));
fmt::print("Name: {}\n", GetNameOfA32Instruction(instruction));
const A32::LocationDescriptor location{0, {}, {}};
IR::Block block{location};
const bool should_continue = A32::TranslateSingleInstruction(block, location, instruction);
fmt::print("should_continue: {}\n\n", should_continue);
fmt::print("IR:\n");
fmt::print("{}\n", IR::DumpBlock(block));
Optimization::A32GetSetElimination(block);
Optimization::DeadCodeElimination(block);
Optimization::ConstantPropagation(block);
Optimization::DeadCodeElimination(block);
Optimization::IdentityRemovalPass(block);
fmt::print("Optimized IR:\n");
fmt::print("{}\n", IR::DumpBlock(block));
}
void PrintA64Instruction(u32 instruction) {
fmt::print("{:08x} {}\n", instruction, Common::DisassembleAArch64(instruction));
fmt::print("Name: {}\n", GetNameOfA64Instruction(instruction));
const A64::LocationDescriptor location{0, {}};
IR::Block block{location};
const bool should_continue = A64::TranslateSingleInstruction(block, location, instruction);
fmt::print("should_continue: {}\n\n", should_continue);
fmt::print("IR:\n");
fmt::print("{}\n", IR::DumpBlock(block));
Optimization::A64GetSetElimination(block);
Optimization::DeadCodeElimination(block);
Optimization::ConstantPropagation(block);
Optimization::DeadCodeElimination(block);
Optimization::IdentityRemovalPass(block);
fmt::print("Optimized IR:\n");
fmt::print("{}\n", IR::DumpBlock(block));
}
class ExecEnv final : public Dynarmic::A32::UserCallbacks {
public:
u64 ticks_left = 0;
std::map<u32, u8> memory;
std::uint8_t MemoryRead8(u32 vaddr) override {
if (auto iter = memory.find(vaddr); iter != memory.end()) {
return iter->second;
}
return 0;
}
std::uint16_t MemoryRead16(u32 vaddr) override {
return u16(MemoryRead8(vaddr)) | u16(MemoryRead8(vaddr + 1)) << 8;
}
std::uint32_t MemoryRead32(u32 vaddr) override {
return u32(MemoryRead16(vaddr)) | u32(MemoryRead16(vaddr + 2)) << 16;
}
std::uint64_t MemoryRead64(u32 vaddr) override {
return u64(MemoryRead32(vaddr)) | u64(MemoryRead32(vaddr + 4)) << 32;
}
void MemoryWrite8(u32 vaddr, std::uint8_t value) override {
memory[vaddr] = value;
}
void MemoryWrite16(u32 vaddr, std::uint16_t value) override {
MemoryWrite8(vaddr, static_cast<u8>(value));
MemoryWrite8(vaddr + 1, static_cast<u8>(value >> 8));
}
void MemoryWrite32(u32 vaddr, std::uint32_t value) override {
MemoryWrite16(vaddr, static_cast<u16>(value));
MemoryWrite16(vaddr + 2, static_cast<u16>(value >> 16));
}
void MemoryWrite64(u32 vaddr, std::uint64_t value) override {
MemoryWrite32(vaddr, static_cast<u32>(value));
MemoryWrite32(vaddr + 4, static_cast<u32>(value >> 32));
}
void InterpreterFallback(u32 pc, size_t num_instructions) override {
fmt::print("> InterpreterFallback({:08x}, {}) code = {:08x}\n", pc, num_instructions, MemoryReadCode(pc));
}
void CallSVC(std::uint32_t swi) override {
fmt::print("> CallSVC({})\n", swi);
}
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
fmt::print("> ExceptionRaised({:08x}, {})", pc, static_cast<size_t>(exception));
}
void AddTicks(std::uint64_t ticks) override {
if (ticks > ticks_left) {
ticks_left = 0;
return;
}
ticks_left -= ticks;
}
std::uint64_t GetTicksRemaining() override {
return ticks_left;
}
};
void ExecuteA32Instruction(u32 instruction) {
ExecEnv env;
A32::Jit cpu{A32::UserConfig{&env}};
env.ticks_left = 1;
std::array<u32, 16> regs{};
std::array<u32, 64> ext_regs{};
u32 cpsr = 0;
u32 fpscr = 0;
const std::map<std::string, u32*> name_map = [&regs, &ext_regs, &cpsr, &fpscr]{
std::map<std::string, u32*> name_map;
for (size_t i = 0; i < regs.size(); i++) {
name_map[fmt::format("r{}", i)] = &regs[i];
}
for (size_t i = 0; i < ext_regs.size(); i++) {
name_map[fmt::format("s{}", i)] = &ext_regs[i];
}
name_map["sp"] = &regs[13];
name_map["lr"] = &regs[14];
name_map["pc"] = &regs[15];
name_map["cpsr"] = &cpsr;
name_map["fpscr"] = &fpscr;
return name_map;
}();
const auto get_line = [](){
std::string line;
std::getline(std::cin, line);
std::transform(line.begin(), line.end(), line.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return line;
};
const auto get_value = [&get_line]() -> std::optional<u32> {
std::string line = get_line();
if (line.length() > 2 && line[0] == '0' && line[1] == 'x') line = line.substr(2);
if (line.length() > 8) return {};
char* endptr;
const u32 value = strtol(line.c_str(), &endptr, 16);
if (line.c_str() + line.length() != endptr) return {};
return value;
};
while (std::cin) {
fmt::print("register: ");
const std::string reg_name = get_line();
if (const auto iter = name_map.find(reg_name); iter != name_map.end()) {
fmt::print("value: ");
if (const auto value = get_value()) {
*(iter->second) = *value;
fmt::print("> {} = 0x{:08x}\n", reg_name, *value);
}
} else if (reg_name == "mem" || reg_name == "memory") {
fmt::print("address: ");
if (const auto address = get_value()) {
fmt::print("value: ");
if (const auto value = get_value()) {
env.MemoryWrite32(*address, *value);
fmt::print("> mem[0x{:08x}] = 0x{:08x}\n", *address, *value);
}
}
} else if (reg_name == "end") {
break;
}
}
fmt::print("\n\n");
cpu.Regs() = regs;
cpu.ExtRegs() = ext_regs;
cpu.SetCpsr(cpsr);
cpu.SetFpscr(fpscr);
const u32 initial_pc = regs[15];
env.MemoryWrite32(initial_pc + 0, instruction);
env.MemoryWrite32(initial_pc + 4, 0xEAFFFFFE); // B +0
cpu.Run();
fmt::print("Registers modified:\n");
for (size_t i = 0; i < regs.size(); ++i) {
if (regs[i] != cpu.Regs()[i]) {
fmt::print("{:3s}: {:08x}\n", static_cast<A32::Reg>(i), cpu.Regs()[i]);
}
}
for (size_t i = 0; i < ext_regs.size(); ++i) {
if (ext_regs[i] != cpu.ExtRegs()[i]) {
fmt::print("{:3s}: {:08x}\n", static_cast<A32::ExtReg>(i), cpu.Regs()[i]);
}
}
if (cpsr != cpu.Cpsr()) {
fmt::print("cpsr {:08x}\n", cpu.Cpsr());
}
if (fpscr != cpu.Fpscr()) {
fmt::print("fpscr{:08x}\n", cpu.Fpscr());
}
fmt::print("Modified memory:\n");
for (auto iter = env.memory.begin(); iter != env.memory.end(); ++iter) {
fmt::print("{:08x} {:02x}\n", iter->first, iter->second);
}
}
int main(int argc, char** argv) {
if (argc < 3 || argc > 4) {
fmt::print("usage: {} <a32/a64> <instruction_in_hex> [-exec]\n", argv[0]);
return 1;
}
const char* const hex_instruction = [argv]{
if (strlen(argv[2]) > 2 && argv[2][0] == '0' && argv[2][1] == 'x') {
return argv[2] + 2;
}
return argv[2];
}();
if (strlen(hex_instruction) > 8) {
fmt::print("hex string too long\n");
return 1;
}
const u32 instruction = strtol(hex_instruction, nullptr, 16);
if (strcmp(argv[1], "a32") == 0) {
PrintA32Instruction(instruction);
} else if (strcmp(argv[1], "a64") == 0) {
PrintA64Instruction(instruction);
} else {
fmt::print("Invalid mode: {}\nValid values: a32, a64\n", argv[1]);
return 1;
}
if (argc == 4) {
if (strcmp(argv[3], "-exec") != 0) {
fmt::print("Invalid option {}\n", argv[3]);
return 1;
}
if (strcmp(argv[1], "a32") == 0) {
ExecuteA32Instruction(instruction);
} else if (strcmp(argv[1], "a64") == 0) {
fmt::print("Executing a64 code not currently supported\n");
return 1;
}
}
return 0;
}

21
externals/dynarmic/tests/rand_int.h vendored Executable file
View File

@@ -0,0 +1,21 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2020 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#pragma once
#include <random>
#include <type_traits>
template <typename T>
T RandInt(T min, T max) {
static_assert(std::is_integral_v<T>, "T must be an integral type.");
static_assert(!std::is_same_v<T, signed char> && !std::is_same_v<T, unsigned char>,
"Using char with uniform_int_distribution is undefined behavior.");
static std::random_device rd;
static std::mt19937 mt(rd());
std::uniform_int_distribution<T> rand(min, max);
return rand(mt);
}

View File

@@ -0,0 +1,301 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <type_traits>
#include "A32/testenv.h"
#include "a32_unicorn.h"
#include "common/assert.h"
#include "common/bit_util.h"
#define CHECKED(expr) \
do { \
if (auto cerr_ = (expr)) { \
ASSERT_MSG(false, "Call " #expr " failed with error: {} ({})\n", cerr_, \
uc_strerror(cerr_)); \
} \
} while (0)
constexpr u32 BEGIN_ADDRESS = 0;
constexpr u32 END_ADDRESS = ~u32(0);
template <class TestEnvironment>
A32Unicorn<TestEnvironment>::A32Unicorn(TestEnvironment& testenv) : testenv{testenv} {
constexpr uc_mode open_mode = std::is_same_v<TestEnvironment, ArmTestEnv> ? UC_MODE_ARM : UC_MODE_THUMB;
CHECKED(uc_open(UC_ARCH_ARM, open_mode, &uc));
CHECKED(uc_hook_add(uc, &intr_hook, UC_HOOK_INTR, (void*)InterruptHook, this, BEGIN_ADDRESS, END_ADDRESS));
CHECKED(uc_hook_add(uc, &mem_invalid_hook, UC_HOOK_MEM_INVALID, (void*)UnmappedMemoryHook, this, BEGIN_ADDRESS, END_ADDRESS));
CHECKED(uc_hook_add(uc, &mem_write_prot_hook, UC_HOOK_MEM_WRITE, (void*)MemoryWriteHook, this, BEGIN_ADDRESS, END_ADDRESS));
}
template <class TestEnvironment>
A32Unicorn<TestEnvironment>::~A32Unicorn() {
ClearPageCache();
CHECKED(uc_hook_del(uc, intr_hook));
CHECKED(uc_hook_del(uc, mem_invalid_hook));
CHECKED(uc_close(uc));
}
template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::Run() {
// Thumb execution mode requires the LSB to be set to 1.
constexpr u64 pc_mask = std::is_same_v<TestEnvironment, ArmTestEnv> ? 0 : 1;
while (testenv.ticks_left > 0) {
const u32 pc = GetPC() | pc_mask;
if (auto cerr_ = uc_emu_start(uc, pc, END_ADDRESS, 0, 1)) {
ASSERT_MSG(false, "uc_emu_start failed @ {:08x} (code = {:08x}) with error {} ({})", pc, testenv.MemoryReadCode(pc), cerr_, uc_strerror(cerr_));
}
testenv.ticks_left--;
if (!testenv.interrupts.empty() || testenv.code_mem_modified_by_guest) {
return;
}
}
const bool T = Dynarmic::Common::Bit<5>(GetCpsr());
const u32 new_pc = GetPC() | (T ? 1 : 0);
SetPC(new_pc);
}
template <class TestEnvironment>
u32 A32Unicorn<TestEnvironment>::GetPC() const {
u32 pc;
CHECKED(uc_reg_read(uc, UC_ARM_REG_PC, &pc));
return pc;
}
template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::SetPC(u32 value) {
CHECKED(uc_reg_write(uc, UC_ARM_REG_PC, &value));
}
template <class TestEnvironment>
u32 A32Unicorn<TestEnvironment>::GetSP() const {
u32 sp;
CHECKED(uc_reg_read(uc, UC_ARM_REG_SP, &sp));
return sp;
}
template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::SetSP(u32 value) {
CHECKED(uc_reg_write(uc, UC_ARM_REG_SP, &value));
}
constexpr std::array<int, Unicorn::A32::num_gprs> gpr_ids{
UC_ARM_REG_R0, UC_ARM_REG_R1, UC_ARM_REG_R2, UC_ARM_REG_R3, UC_ARM_REG_R4, UC_ARM_REG_R5, UC_ARM_REG_R6, UC_ARM_REG_R7,
UC_ARM_REG_R8, UC_ARM_REG_R9, UC_ARM_REG_R10, UC_ARM_REG_R11, UC_ARM_REG_R12, UC_ARM_REG_R13, UC_ARM_REG_R14, UC_ARM_REG_R15,
};
template <class TestEnvironment>
Unicorn::A32::RegisterArray A32Unicorn<TestEnvironment>::GetRegisters() const {
Unicorn::A32::RegisterArray regs{};
Unicorn::A32::RegisterPtrArray ptrs;
for (size_t i = 0; i < ptrs.size(); ++i) {
ptrs[i] = &regs[i];
}
CHECKED(uc_reg_read_batch(uc, const_cast<int*>(gpr_ids.data()),
reinterpret_cast<void**>(ptrs.data()), static_cast<int>(Unicorn::A32::num_gprs)));
return regs;
}
template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::SetRegisters(const RegisterArray& value) {
Unicorn::A32::RegisterConstPtrArray ptrs;
for (size_t i = 0; i < ptrs.size(); ++i) {
ptrs[i] = &value[i];
}
CHECKED(uc_reg_write_batch(uc, const_cast<int*>(gpr_ids.data()),
reinterpret_cast<void**>(const_cast<u32**>(ptrs.data())), static_cast<int>(ptrs.size())));
}
using DoubleExtRegPtrArray = std::array<Unicorn::A32::ExtRegArray::pointer, Unicorn::A32::num_ext_regs/2>;
using DoubleExtRegConstPtrArray = std::array<Unicorn::A32::ExtRegArray::const_pointer, Unicorn::A32::num_ext_regs/2>;
constexpr std::array<int, Unicorn::A32::num_ext_regs/2> double_ext_reg_ids{
UC_ARM_REG_D0, UC_ARM_REG_D1, UC_ARM_REG_D2, UC_ARM_REG_D3, UC_ARM_REG_D4, UC_ARM_REG_D5, UC_ARM_REG_D6, UC_ARM_REG_D7,
UC_ARM_REG_D8, UC_ARM_REG_D9, UC_ARM_REG_D10, UC_ARM_REG_D11, UC_ARM_REG_D12, UC_ARM_REG_D13, UC_ARM_REG_D14, UC_ARM_REG_D15,
UC_ARM_REG_D16, UC_ARM_REG_D17, UC_ARM_REG_D18, UC_ARM_REG_D19, UC_ARM_REG_D20, UC_ARM_REG_D21, UC_ARM_REG_D22, UC_ARM_REG_D23,
UC_ARM_REG_D24, UC_ARM_REG_D25, UC_ARM_REG_D26, UC_ARM_REG_D27, UC_ARM_REG_D28, UC_ARM_REG_D29, UC_ARM_REG_D30, UC_ARM_REG_D31,
};
template <class TestEnvironment>
Unicorn::A32::ExtRegArray A32Unicorn<TestEnvironment>::GetExtRegs() const {
Unicorn::A32::ExtRegArray ext_regs{};
DoubleExtRegPtrArray ptrs;
for (size_t i = 0; i < ptrs.size(); ++i)
ptrs[i] = &ext_regs[i*2];
CHECKED(uc_reg_read_batch(uc, const_cast<int*>(double_ext_reg_ids.data()),
reinterpret_cast<void**>(ptrs.data()), static_cast<int>(ptrs.size())));
return ext_regs;
}
template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::SetExtRegs(const ExtRegArray& value) {
DoubleExtRegConstPtrArray ptrs;
for (size_t i = 0; i < ptrs.size(); ++i) {
ptrs[i] = &value[i*2];
}
CHECKED(uc_reg_write_batch(uc, const_cast<int*>(double_ext_reg_ids.data()),
reinterpret_cast<void* const *>(const_cast<u32**>(ptrs.data())), static_cast<int>(ptrs.size())));
}
template <class TestEnvironment>
u32 A32Unicorn<TestEnvironment>::GetFpscr() const {
u32 fpsr;
CHECKED(uc_reg_read(uc, UC_ARM_REG_FPSCR, &fpsr));
return fpsr;
}
template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::SetFpscr(u32 value) {
CHECKED(uc_reg_write(uc, UC_ARM_REG_FPSCR, &value));
}
template <class TestEnvironment>
u32 A32Unicorn<TestEnvironment>::GetFpexc() const {
u32 value = 0;
CHECKED(uc_reg_read(uc, UC_ARM_REG_FPEXC, &value));
return value;
}
template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::SetFpexc(u32 value) {
CHECKED(uc_reg_write(uc, UC_ARM_REG_FPEXC, &value));
}
template <class TestEnvironment>
u32 A32Unicorn<TestEnvironment>::GetCpsr() const {
u32 pstate;
CHECKED(uc_reg_read(uc, UC_ARM_REG_CPSR, &pstate));
return pstate;
}
template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::SetCpsr(u32 value) {
CHECKED(uc_reg_write(uc, UC_ARM_REG_CPSR, &value));
}
template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::EnableFloatingPointAccess() {
const u32 new_fpexc = GetFpexc() | (1U << 30);
SetFpexc(new_fpexc);
}
template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::ClearPageCache() {
for (const auto& page : pages) {
CHECKED(uc_mem_unmap(uc, page->address, 4096));
}
pages.clear();
}
template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::DumpMemoryInformation() {
uc_mem_region* regions;
u32 count;
CHECKED(uc_mem_regions(uc, &regions, &count));
for (u32 i = 0; i < count; ++i) {
printf("region: start 0x%08x end 0x%08x perms 0x%08x\n", static_cast<u32>(regions[i].begin), static_cast<u32>(regions[i].end), regions[i].perms);
}
CHECKED(uc_free(regions));
}
template <class TestEnvironment>
void A32Unicorn<TestEnvironment>::InterruptHook(uc_engine* /*uc*/, u32 int_number, void* user_data) {
auto* this_ = static_cast<A32Unicorn*>(user_data);
u32 esr = 0;
//CHECKED(uc_reg_read(uc, UC_ARM_REG_ESR, &esr));
auto ec = esr >> 26;
auto iss = esr & 0xFFFFFF;
switch (ec) {
case 0x15: // SVC
this_->testenv.CallSVC(iss);
break;
default:
this_->testenv.interrupts.emplace_back(fmt::format("Unhandled interrupt: int_number: {:#x}, esr: {:#x} (ec: {:#x}, iss: {:#x})", int_number, esr, ec, iss));
break;
}
}
template <class TestEnvironment>
bool A32Unicorn<TestEnvironment>::UnmappedMemoryHook(uc_engine* uc, uc_mem_type /*type*/, u32 start_address, int size, u64 /*value*/, void* user_data) {
auto* this_ = static_cast<A32Unicorn*>(user_data);
const auto generate_page = [&](u32 base_address) {
// printf("generate_page(%x)\n", base_address);
const u32 permissions = [&]() -> u32 {
if (base_address < this_->testenv.code_mem.size() * sizeof(typename TestEnvironment::InstructionType)) {
return UC_PROT_READ | UC_PROT_EXEC;
}
return UC_PROT_READ;
}();
auto page = std::make_unique<Page>();
page->address = base_address;
for (size_t i = 0; i < page->data.size(); ++i)
page->data[i] = this_->testenv.MemoryRead8(static_cast<u32>(base_address + i));
uc_err err = uc_mem_map_ptr(uc, base_address, page->data.size(), permissions, page->data.data());
if (err == UC_ERR_MAP)
return; // page already exists
CHECKED(err);
this_->pages.emplace_back(std::move(page));
};
const auto is_in_range = [](u32 addr, u32 start, u32 end) {
if (start <= end)
return addr >= start && addr <= end; // fffff[tttttt]fffff
return addr >= start || addr <= end; // ttttt]ffffff[ttttt
};
const u32 start_address_page = start_address & ~u32(0xFFF);
const u32 end_address = start_address + size - 1;
u32 current_address = start_address_page;
do {
generate_page(current_address);
current_address += 0x1000;
} while (is_in_range(current_address, start_address_page, end_address) && current_address != start_address_page);
return true;
}
template <class TestEnvironment>
bool A32Unicorn<TestEnvironment>::MemoryWriteHook(uc_engine* /*uc*/, uc_mem_type /*type*/, u32 start_address, int size, u64 value, void* user_data) {
auto* this_ = static_cast<A32Unicorn*>(user_data);
switch (size) {
case 1:
this_->testenv.MemoryWrite8(start_address, static_cast<u8>(value));
break;
case 2:
this_->testenv.MemoryWrite16(start_address, static_cast<u16>(value));
break;
case 4:
this_->testenv.MemoryWrite32(start_address, static_cast<u32>(value));
break;
case 8:
this_->testenv.MemoryWrite64(start_address, value);
break;
default:
UNREACHABLE();
}
return true;
}
template class A32Unicorn<ArmTestEnv>;
template class A32Unicorn<ThumbTestEnv>;

View File

@@ -0,0 +1,88 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#pragma once
#include <array>
#include <vector>
#ifdef _MSC_VER
#pragma warning(push, 0)
#include <unicorn/unicorn.h>
#pragma warning(pop)
#else
#include <unicorn/unicorn.h>
#endif
#include "common/common_types.h"
#include "A32/testenv.h"
namespace Unicorn::A32 {
static constexpr size_t num_gprs = 16;
static constexpr size_t num_ext_regs = 64;
using ExtRegArray = std::array<u32, num_ext_regs>;
using RegisterArray = std::array<u32, num_gprs>;
using RegisterPtrArray = std::array<RegisterArray::pointer, num_gprs>;
using RegisterConstPtrArray = std::array<RegisterArray::const_pointer, num_gprs>;
} // namespace Unicorn::A32
template <class TestEnvironment>
class A32Unicorn final {
public:
using ExtRegArray = Unicorn::A32::ExtRegArray;
using RegisterArray = Unicorn::A32::RegisterArray;
explicit A32Unicorn(TestEnvironment& testenv);
~A32Unicorn();
void Run();
u32 GetSP() const;
void SetSP(u32 value);
u32 GetPC() const;
void SetPC(u32 value);
RegisterArray GetRegisters() const;
void SetRegisters(const RegisterArray& value);
ExtRegArray GetExtRegs() const;
void SetExtRegs(const ExtRegArray& value);
u32 GetFpscr() const;
void SetFpscr(u32 value);
u32 GetFpexc() const;
void SetFpexc(u32 value);
u32 GetCpsr() const;
void SetCpsr(u32 value);
void EnableFloatingPointAccess();
void ClearPageCache();
void DumpMemoryInformation();
private:
static void InterruptHook(uc_engine* uc, u32 interrupt, void* user_data);
static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u32 addr, int size, u64 value, void* user_data);
static bool MemoryWriteHook(uc_engine* uc, uc_mem_type type, u32 addr, int size, u64 value, void* user_data);
struct Page {
u32 address;
std::array<u8, 4096> data;
};
TestEnvironment& testenv;
uc_engine* uc{};
uc_hook intr_hook{};
uc_hook mem_invalid_hook{};
uc_hook mem_write_prot_hook{};
std::vector<std::unique_ptr<Page>> pages;
};

View File

@@ -0,0 +1,252 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include "common/assert.h"
#include "a64_unicorn.h"
#define CHECKED(expr) \
do { \
if (auto cerr_ = (expr)) { \
ASSERT_MSG(false, "Call " #expr " failed with error: {} ({})\n", cerr_, \
uc_strerror(cerr_)); \
} \
} while (0)
constexpr u64 BEGIN_ADDRESS = 0;
constexpr u64 END_ADDRESS = ~u64(0);
A64Unicorn::A64Unicorn(A64TestEnv& testenv) : testenv(testenv) {
CHECKED(uc_open(UC_ARCH_ARM64, UC_MODE_ARM, &uc));
u64 fpv = 3 << 20;
CHECKED(uc_reg_write(uc, UC_ARM64_REG_CPACR_EL1, &fpv));
CHECKED(uc_hook_add(uc, &intr_hook, UC_HOOK_INTR, (void*)InterruptHook, this, BEGIN_ADDRESS, END_ADDRESS));
CHECKED(uc_hook_add(uc, &mem_invalid_hook, UC_HOOK_MEM_INVALID, (void*)UnmappedMemoryHook, this, BEGIN_ADDRESS, END_ADDRESS));
CHECKED(uc_hook_add(uc, &mem_write_prot_hook, UC_HOOK_MEM_WRITE, (void*)MemoryWriteHook, this, BEGIN_ADDRESS, END_ADDRESS));
}
A64Unicorn::~A64Unicorn() {
ClearPageCache();
CHECKED(uc_hook_del(uc, intr_hook));
CHECKED(uc_hook_del(uc, mem_invalid_hook));
CHECKED(uc_close(uc));
}
void A64Unicorn::Run() {
while (testenv.ticks_left > 0) {
CHECKED(uc_emu_start(uc, GetPC(), END_ADDRESS, 0, 1));
testenv.ticks_left--;
if (!testenv.interrupts.empty() || testenv.code_mem_modified_by_guest) {
return;
}
}
}
u64 A64Unicorn::GetSP() const {
u64 sp;
CHECKED(uc_reg_read(uc, UC_ARM64_REG_SP, &sp));
return sp;
}
void A64Unicorn::SetSP(u64 value) {
CHECKED(uc_reg_write(uc, UC_ARM64_REG_SP, &value));
}
u64 A64Unicorn::GetPC() const {
u64 pc;
CHECKED(uc_reg_read(uc, UC_ARM64_REG_PC, &pc));
return pc;
}
void A64Unicorn::SetPC(u64 value) {
CHECKED(uc_reg_write(uc, UC_ARM64_REG_PC, &value));
}
constexpr std::array<int, A64Unicorn::num_gprs> gpr_ids{
UC_ARM64_REG_X0, UC_ARM64_REG_X1, UC_ARM64_REG_X2, UC_ARM64_REG_X3, UC_ARM64_REG_X4, UC_ARM64_REG_X5, UC_ARM64_REG_X6, UC_ARM64_REG_X7,
UC_ARM64_REG_X8, UC_ARM64_REG_X9, UC_ARM64_REG_X10, UC_ARM64_REG_X11, UC_ARM64_REG_X12, UC_ARM64_REG_X13, UC_ARM64_REG_X14, UC_ARM64_REG_X15,
UC_ARM64_REG_X16, UC_ARM64_REG_X17, UC_ARM64_REG_X18, UC_ARM64_REG_X19, UC_ARM64_REG_X20, UC_ARM64_REG_X21, UC_ARM64_REG_X22, UC_ARM64_REG_X23,
UC_ARM64_REG_X24, UC_ARM64_REG_X25, UC_ARM64_REG_X26, UC_ARM64_REG_X27, UC_ARM64_REG_X28, UC_ARM64_REG_X29, UC_ARM64_REG_X30
};
A64Unicorn::RegisterArray A64Unicorn::GetRegisters() const {
RegisterArray regs{};
RegisterPtrArray ptrs;
for (size_t i = 0; i < ptrs.size(); ++i)
ptrs[i] = &regs[i];
CHECKED(uc_reg_read_batch(uc, const_cast<int*>(gpr_ids.data()),
reinterpret_cast<void**>(ptrs.data()), static_cast<int>(num_gprs)));
return regs;
}
void A64Unicorn::SetRegisters(const RegisterArray& value) {
RegisterConstPtrArray ptrs;
for (size_t i = 0; i < ptrs.size(); ++i)
ptrs[i] = &value[i];
CHECKED(uc_reg_write_batch(uc, const_cast<int*>(gpr_ids.data()),
reinterpret_cast<void**>(const_cast<u64**>(ptrs.data())), static_cast<int>(num_gprs)));
}
constexpr std::array<int, A64Unicorn::num_vecs> vec_ids{
UC_ARM64_REG_Q0, UC_ARM64_REG_Q1, UC_ARM64_REG_Q2, UC_ARM64_REG_Q3, UC_ARM64_REG_Q4, UC_ARM64_REG_Q5, UC_ARM64_REG_Q6, UC_ARM64_REG_Q7,
UC_ARM64_REG_Q8, UC_ARM64_REG_Q9, UC_ARM64_REG_Q10, UC_ARM64_REG_Q11, UC_ARM64_REG_Q12, UC_ARM64_REG_Q13, UC_ARM64_REG_Q14, UC_ARM64_REG_Q15,
UC_ARM64_REG_Q16, UC_ARM64_REG_Q17, UC_ARM64_REG_Q18, UC_ARM64_REG_Q19, UC_ARM64_REG_Q20, UC_ARM64_REG_Q21, UC_ARM64_REG_Q22, UC_ARM64_REG_Q23,
UC_ARM64_REG_Q24, UC_ARM64_REG_Q25, UC_ARM64_REG_Q26, UC_ARM64_REG_Q27, UC_ARM64_REG_Q28, UC_ARM64_REG_Q29, UC_ARM64_REG_Q30, UC_ARM64_REG_Q31
};
A64Unicorn::VectorArray A64Unicorn::GetVectors() const {
VectorArray vecs{};
VectorPtrArray ptrs;
for (size_t i = 0; i < ptrs.size(); ++i)
ptrs[i] = &vecs[i];
CHECKED(uc_reg_read_batch(uc, const_cast<int*>(vec_ids.data()),
reinterpret_cast<void**>(ptrs.data()), static_cast<int>(num_vecs)));
return vecs;
}
void A64Unicorn::SetVectors(const VectorArray& value) {
VectorConstPtrArray ptrs;
for (size_t i = 0; i < ptrs.size(); ++i)
ptrs[i] = &value[i];
CHECKED(uc_reg_write_batch(uc, const_cast<int*>(vec_ids.data()),
reinterpret_cast<void* const *>(const_cast<Vector**>(ptrs.data())), static_cast<int>(num_vecs)));
}
u32 A64Unicorn::GetFpcr() const {
u32 fpcr;
CHECKED(uc_reg_read(uc, UC_ARM64_REG_FPCR, &fpcr));
return fpcr;
}
void A64Unicorn::SetFpcr(u32 value) {
CHECKED(uc_reg_write(uc, UC_ARM64_REG_FPCR, &value));
}
u32 A64Unicorn::GetFpsr() const {
u32 fpsr;
CHECKED(uc_reg_read(uc, UC_ARM64_REG_FPSR, &fpsr));
return fpsr;
}
void A64Unicorn::SetFpsr(u32 value) {
CHECKED(uc_reg_write(uc, UC_ARM64_REG_FPSR, &value));
}
u32 A64Unicorn::GetPstate() const {
u32 pstate;
CHECKED(uc_reg_read(uc, UC_ARM64_REG_NZCV, &pstate));
return pstate;
}
void A64Unicorn::SetPstate(u32 value) {
CHECKED(uc_reg_write(uc, UC_ARM64_REG_NZCV, &value));
}
void A64Unicorn::ClearPageCache() {
for (const auto& page : pages) {
CHECKED(uc_mem_unmap(uc, page->address, 4096));
}
pages.clear();
}
void A64Unicorn::DumpMemoryInformation() {
uc_mem_region* regions;
u32 count;
CHECKED(uc_mem_regions(uc, &regions, &count));
for (u32 i = 0; i < count; ++i) {
printf("region: start 0x%016" PRIx64 " end 0x%016" PRIx64 " perms 0x%08x\n", regions[i].begin, regions[i].end, regions[i].perms);
}
CHECKED(uc_free(regions));
}
void A64Unicorn::InterruptHook(uc_engine* uc, u32 int_number, void* user_data) {
auto* this_ = static_cast<A64Unicorn*>(user_data);
u32 esr;
CHECKED(uc_reg_read(uc, UC_ARM64_REG_ESR, &esr));
auto ec = esr >> 26;
auto iss = esr & 0xFFFFFF;
switch (ec) {
case 0x15: // SVC
this_->testenv.CallSVC(iss);
break;
default:
this_->testenv.interrupts.emplace_back(fmt::format("Unhandled interrupt: int_number: {:#x}, esr: {:#x} (ec: {:#x}, iss: {:#x})", int_number, esr, ec, iss));
break;
}
}
bool A64Unicorn::UnmappedMemoryHook(uc_engine* uc, uc_mem_type /*type*/, u64 start_address, int size, u64 /*value*/, void* user_data) {
auto* this_ = static_cast<A64Unicorn*>(user_data);
const auto generate_page = [&](u64 base_address) {
// printf("generate_page(%" PRIx64 ")\n", base_address);
const u32 permissions = [&]() -> u32 {
if (base_address < this_->testenv.code_mem.size() * 4)
return UC_PROT_READ | UC_PROT_EXEC;
return UC_PROT_READ;
}();
auto page = std::make_unique<Page>();
page->address = base_address;
for (size_t i = 0; i < page->data.size(); ++i)
page->data[i] = this_->testenv.MemoryRead8(base_address + i);
uc_err err = uc_mem_map_ptr(uc, base_address, page->data.size(), permissions, page->data.data());
if (err == UC_ERR_MAP)
return; // page already exists
CHECKED(err);
this_->pages.emplace_back(std::move(page));
};
const auto is_in_range = [](u64 addr, u64 start, u64 end) {
if (start <= end)
return addr >= start && addr <= end; // fffff[tttttt]fffff
return addr >= start || addr <= end; // ttttt]ffffff[ttttt
};
const u64 start_address_page = start_address & ~u64(0xFFF);
const u64 end_address = start_address + size - 1;
u64 current_address = start_address_page;
do {
generate_page(current_address);
current_address += 0x1000;
} while (is_in_range(current_address, start_address_page, end_address) && current_address != start_address_page);
return true;
}
bool A64Unicorn::MemoryWriteHook(uc_engine* /*uc*/, uc_mem_type /*type*/, u64 start_address, int size, u64 value, void* user_data) {
auto* this_ = static_cast<A64Unicorn*>(user_data);
switch (size) {
case 1:
this_->testenv.MemoryWrite8(start_address, static_cast<u8>(value));
break;
case 2:
this_->testenv.MemoryWrite16(start_address, static_cast<u16>(value));
break;
case 4:
this_->testenv.MemoryWrite32(start_address, static_cast<u32>(value));
break;
case 8:
this_->testenv.MemoryWrite64(start_address, value);
break;
default:
UNREACHABLE();
}
return true;
}

View File

@@ -0,0 +1,81 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#pragma once
#include <array>
#include <vector>
#ifdef _MSC_VER
#pragma warning(push, 0)
#include <unicorn/unicorn.h>
#pragma warning(pop)
#else
#include <unicorn/unicorn.h>
#endif
#include "common/common_types.h"
#include "A64/testenv.h"
class A64Unicorn final {
public:
static constexpr size_t num_gprs = 31;
using RegisterArray = std::array<u64, num_gprs>;
using RegisterPtrArray = std::array<RegisterArray::pointer, num_gprs>;
using RegisterConstPtrArray = std::array<RegisterArray::const_pointer, num_gprs>;
static constexpr size_t num_vecs = 32;
using VectorArray = std::array<Vector, num_vecs>;
using VectorPtrArray = std::array<VectorArray::pointer, num_vecs>;
using VectorConstPtrArray = std::array<VectorArray::const_pointer, num_vecs>;
explicit A64Unicorn(A64TestEnv& testenv);
~A64Unicorn();
void Run();
u64 GetSP() const;
void SetSP(u64 value);
u64 GetPC() const;
void SetPC(u64 value);
RegisterArray GetRegisters() const;
void SetRegisters(const RegisterArray& value);
VectorArray GetVectors() const;
void SetVectors(const VectorArray& value);
u32 GetFpcr() const;
void SetFpcr(u32 value);
u32 GetFpsr() const;
void SetFpsr(u32 value);
u32 GetPstate() const;
void SetPstate(u32 value);
void ClearPageCache();
void DumpMemoryInformation();
private:
static void InterruptHook(uc_engine* uc, u32 interrupt, void* user_data);
static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int size, u64 value, void* user_data);
static bool MemoryWriteHook(uc_engine* uc, uc_mem_type type, u64 addr, int size, u64 value, void* user_data);
struct Page {
u64 address;
std::array<u8, 4096> data;
};
A64TestEnv& testenv;
uc_engine* uc{};
uc_hook intr_hook{};
uc_hook mem_invalid_hook{};
uc_hook mem_write_prot_hook{};
std::vector<std::unique_ptr<Page>> pages;
};