Move dead submodules in-tree

Signed-off-by: swurl <swurl@swurl.xyz>
This commit is contained in:
swurl 2025-05-31 02:33:02 -04:00
parent c0cceff365
commit 6c655321e6
No known key found for this signature in database
GPG key ID: A5A7629F109C8FD1
4081 changed files with 1185566 additions and 45 deletions

View file

@ -0,0 +1,687 @@
/* 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 <type_traits>
#include <vector>
#include <catch2/catch_test_macros.hpp>
#include <mcl/bit/bit_count.hpp>
#include <mcl/bit/swap.hpp>
#include <mcl/scope_exit.hpp>
#include <mcl/stdint.hpp>
#include "../fuzz_util.h"
#include "../rand_int.h"
#include "../unicorn_emu/a32_unicorn.h"
#include "./testenv.h"
#include "dynarmic/common/fp/fpcr.h"
#include "dynarmic/common/fp/fpsr.h"
#include "dynarmic/common/llvm_disassemble.h"
#include "dynarmic/common/variant_util.h"
#include "dynarmic/frontend/A32/ITState.h"
#include "dynarmic/frontend/A32/a32_location_descriptor.h"
#include "dynarmic/frontend/A32/a32_types.h"
#include "dynarmic/frontend/A32/translate/a32_translate.h"
#include "dynarmic/interface/A32/a32.h"
#include "dynarmic/ir/basic_block.h"
#include "dynarmic/ir/location_descriptor.h"
#include "dynarmic/ir/opcodes.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;
template<typename Fn>
bool AnyLocationDescriptorForTerminalHas(IR::Terminal terminal, Fn fn) {
return Common::VisitVariant<bool>(terminal, [&](auto t) -> bool {
using T = std::decay_t<decltype(t)>;
if constexpr (std::is_same_v<T, IR::Term::Invalid>) {
return false;
} else if constexpr (std::is_same_v<T, IR::Term::ReturnToDispatch>) {
return false;
} else if constexpr (std::is_same_v<T, IR::Term::LinkBlock>) {
return fn(t.next);
} else if constexpr (std::is_same_v<T, IR::Term::LinkBlockFast>) {
return fn(t.next);
} else if constexpr (std::is_same_v<T, IR::Term::PopRSBHint>) {
return false;
} else if constexpr (std::is_same_v<T, IR::Term::Interpret>) {
return fn(t.next);
} else if constexpr (std::is_same_v<T, IR::Term::FastDispatchHint>) {
return false;
} else if constexpr (std::is_same_v<T, IR::Term::If>) {
return AnyLocationDescriptorForTerminalHas(t.then_, fn) || AnyLocationDescriptorForTerminalHas(t.else_, fn);
} else if constexpr (std::is_same_v<T, IR::Term::CheckBit>) {
return AnyLocationDescriptorForTerminalHas(t.then_, fn) || AnyLocationDescriptorForTerminalHas(t.else_, fn);
} else if constexpr (std::is_same_v<T, IR::Term::CheckHalt>) {
return AnyLocationDescriptorForTerminalHas(t.else_, fn);
} else {
ASSERT_MSG(false, "Invalid terminal type");
return false;
}
});
}
bool ShouldTestInst(u32 instruction, u32 pc, bool is_thumb, bool is_last_inst, A32::ITState it_state = {}) {
const A32::LocationDescriptor location = A32::LocationDescriptor{pc, {}, {}}.SetTFlag(is_thumb).SetIT(it_state);
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;
}
if (AnyLocationDescriptorForTerminalHas(block.GetTerminal(), [&](IR::LocationDescriptor ld) { return A32::LocationDescriptor{ld}.PC() <= pc; })) {
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 GenRandomArmInst(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 "dynarmic/frontend/A32/decoder/arm.inc"
#include "dynarmic/frontend/A32/decoder/asimd.inc"
#include "dynarmic/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, false, is_last_inst)) {
return inst;
}
}
}
std::vector<u16> GenRandomThumbInst(u32 pc, bool is_last_inst, A32::ITState it_state = {}) {
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 "dynarmic/frontend/A32/decoder/thumb16.inc"
#include "dynarmic/frontend/A32/decoder/thumb32.inc"
#undef INST
};
const std::vector<std::tuple<std::string, const char*>> vfp_list{
#define INST(fn, name, bitstring) {#fn, bitstring},
#include "dynarmic/frontend/A32/decoder/vfp.inc"
#undef INST
};
const std::vector<std::tuple<std::string, const char*>> asimd_list{
#define INST(fn, name, bitstring) {#fn, bitstring},
#include "dynarmic/frontend/A32/decoder/asimd.inc"
#undef INST
};
std::vector<InstructionGenerator> generators;
std::vector<InstructionGenerator> invalid;
// List of instructions not to test
static constexpr std::array do_not_test{
"thumb16_BKPT",
"thumb16_IT",
"thumb16_SETEND",
// Exclusive load/stores
"thumb32_LDREX",
"thumb32_LDREXB",
"thumb32_LDREXD",
"thumb32_LDREXH",
"thumb32_STREX",
"thumb32_STREXB",
"thumb32_STREXD",
"thumb32_STREXH",
// FPSCR is inaccurate
"vfp_VMRS",
// Unicorn is incorrect?
"thumb32_MRS_reg",
"thumb32_MSR_reg",
// Unicorn has incorrect implementation (incorrect rounding and unsets CPSR.T??)
"vfp_VCVT_to_fixed",
"vfp_VCVT_from_fixed",
"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.
// Coprocessor
"thumb32_CDP",
"thumb32_LDC",
"thumb32_MCR",
"thumb32_MCRR",
"thumb32_MRC",
"thumb32_MRRC",
"thumb32_STC",
};
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});
}
for (const auto& [fn, bs] : vfp_list) {
std::string bitstring = bs;
if (bitstring.substr(0, 4) == "cccc" || bitstring.substr(0, 4) == "----") {
bitstring.replace(0, 4, "1110");
}
if (std::find(do_not_test.begin(), do_not_test.end(), fn) != do_not_test.end()) {
invalid.emplace_back(InstructionGenerator{bitstring.c_str()});
continue;
}
generators.emplace_back(InstructionGenerator{bitstring.c_str()});
}
for (const auto& [fn, bs] : asimd_list) {
std::string bitstring = bs;
if (bitstring.substr(0, 7) == "1111001") {
const char U = bitstring[7];
bitstring.replace(0, 8, "111-1111");
bitstring[3] = U;
} else if (bitstring.substr(0, 8) == "11110100") {
bitstring.replace(0, 8, "11111001");
} else {
ASSERT_FALSE("Unhandled ASIMD instruction: {} {}", fn, bs);
}
if (std::find(do_not_test.begin(), do_not_test.end(), fn) != do_not_test.end()) {
invalid.emplace_back(InstructionGenerator{bitstring.c_str()});
continue;
}
generators.emplace_back(InstructionGenerator{bitstring.c_str()});
}
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();
const bool is_four_bytes = (inst >> 16) != 0;
if (ShouldTestInst(is_four_bytes ? mcl::bit::swap_halves_32(inst) : inst, pc, true, is_last_inst, it_state)) {
if (is_four_bytes)
return {static_cast<u16>(inst >> 16), static_cast<u16>(inst)};
return {static_cast<u16>(inst)};
}
}
}
template<typename TestEnv>
Dynarmic::A32::UserConfig GetUserConfig(TestEnv& testenv) {
Dynarmic::A32::UserConfig user_config;
user_config.optimizations &= ~OptimizationFlag::FastDispatch;
user_config.callbacks = &testenv;
user_config.always_little_endian = true;
return user_config;
}
template<typename TestEnv>
static void RunTestInstance(Dynarmic::A32::Jit& jit,
A32Unicorn<TestEnv>& uni,
TestEnv& jit_env,
TestEnv& uni_env,
const typename A32Unicorn<TestEnv>::RegisterArray& regs,
const typename A32Unicorn<TestEnv>::ExtRegArray& vecs,
const std::vector<typename TestEnv::InstructionType>& instructions,
const u32 cpsr,
const u32 fpscr,
const size_t ticks_left) {
const u32 initial_pc = regs[15];
const u32 num_words = initial_pc / sizeof(typename TestEnv::InstructionType);
const u32 code_mem_size = num_words + static_cast<u32>(instructions.size());
const u32 expected_end_pc = code_mem_size * sizeof(typename TestEnv::InstructionType);
jit_env.code_mem.resize(code_mem_size);
uni_env.code_mem.resize(code_mem_size);
std::fill(jit_env.code_mem.begin(), jit_env.code_mem.end(), TestEnv::infinite_loop);
std::fill(uni_env.code_mem.begin(), uni_env.code_mem.end(), TestEnv::infinite_loop);
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.PadCodeMem();
uni_env.PadCodeMem();
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 = ticks_left;
jit.Run();
uni_env.ticks_left = instructions.size(); // Unicorn counts thumb instructions weirdly.
uni.Run();
SCOPE_FAIL {
fmt::print("Instruction Listing:\n");
fmt::print("{}\n", Common::DisassembleAArch32(std::is_same_v<TestEnv, ThumbTestEnv>, initial_pc, (const u8*)instructions.data(), instructions.size() * sizeof(instructions[0])));
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");
jit.DumpDisassembly();
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);
}
if (uni.GetRegisters()[15] > jit.Regs()[15]) {
int trials = 0;
while (jit.Regs()[15] >= initial_pc && jit.Regs()[15] < expected_end_pc && trials++ < 100 && uni.GetRegisters()[15] != jit.Regs()[15]) {
fmt::print("Warning: Possible unicorn overrrun, attempt recovery\n");
jit.Step();
}
}
REQUIRE(uni.GetRegisters() == jit.Regs());
REQUIRE(uni.GetExtRegs() == jit.ExtRegs());
REQUIRE((uni.GetCpsr() & 0xFFFFFDDF) == (jit.Cpsr() & 0xFFFFFDDF));
REQUIRE((uni.GetFpscr() & 0xF8000000) == (jit.Fpscr() & 0xF8000000));
REQUIRE(uni_env.modified_memory == jit_env.modified_memory);
REQUIRE(uni_env.interrupts.empty());
}
} // Anonymous namespace
TEST_CASE("A32: Single random arm 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)); });
const u32 start_address = 100;
const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x10;
const u32 fpcr = RandomFpcr();
instructions[0] = GenRandomArmInst(start_address, true);
INFO("Instruction: 0x" << std::hex << instructions[0]);
regs[15] = start_address;
RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr, 1);
}
}
TEST_CASE("A32: Small random arm 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)); });
const u32 start_address = 100;
const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x10;
const u32 fpcr = RandomFpcr();
instructions[0] = GenRandomArmInst(start_address + 0, false);
instructions[1] = GenRandomArmInst(start_address + 4, false);
instructions[2] = GenRandomArmInst(start_address + 8, false);
instructions[3] = GenRandomArmInst(start_address + 12, false);
instructions[4] = GenRandomArmInst(start_address + 16, true);
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, 5);
}
}
TEST_CASE("A32: Large random arm 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)); });
const u64 start_address = 100;
const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x10;
const u32 fpcr = RandomFpcr();
for (size_t j = 0; j < instruction_count; ++j) {
instructions[j] = GenRandomArmInst(start_address + j * 4, j == instruction_count - 1);
}
regs[15] = start_address;
RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr, 100);
}
}
TEST_CASE("A32: Single random thumb instruction", "[thumb]") {
ThumbTestEnv jit_env{};
ThumbTestEnv uni_env{};
Dynarmic::A32::Jit jit{GetUserConfig(jit_env)};
A32Unicorn<ThumbTestEnv> uni{uni_env};
A32Unicorn<ThumbTestEnv>::RegisterArray regs;
A32Unicorn<ThumbTestEnv>::ExtRegArray ext_reg;
std::vector<u16> instructions;
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)); });
const u32 start_address = 100;
const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x1F0;
const u32 fpcr = RandomFpcr();
instructions = GenRandomThumbInst(start_address, true);
INFO("Instruction: 0x" << std::hex << instructions[0]);
regs[15] = start_address;
RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr, 1);
}
}
TEST_CASE("A32: Single random thumb instruction (offset)", "[thumb]") {
ThumbTestEnv jit_env{};
ThumbTestEnv uni_env{};
Dynarmic::A32::Jit jit{GetUserConfig(jit_env)};
A32Unicorn<ThumbTestEnv> uni{uni_env};
A32Unicorn<ThumbTestEnv>::RegisterArray regs;
A32Unicorn<ThumbTestEnv>::ExtRegArray ext_reg;
std::vector<u16> instructions;
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)); });
const u32 start_address = 100;
const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x1F0;
const u32 fpcr = RandomFpcr();
instructions.clear();
instructions.push_back(0xbf00); // NOP
const std::vector<u16> inst = GenRandomThumbInst(start_address + 2, true);
instructions.insert(instructions.end(), inst.begin(), inst.end());
INFO("Instruction: 0x" << std::hex << inst[0]);
regs[15] = start_address;
RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr, 2);
}
}
TEST_CASE("A32: Small random thumb block", "[thumb]") {
ThumbTestEnv jit_env{};
ThumbTestEnv uni_env{};
Dynarmic::A32::Jit jit{GetUserConfig(jit_env)};
A32Unicorn<ThumbTestEnv> uni{uni_env};
A32Unicorn<ThumbTestEnv>::RegisterArray regs;
A32Unicorn<ThumbTestEnv>::ExtRegArray ext_reg;
std::vector<u16> instructions;
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)); });
const u32 start_address = 100;
const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x1F0;
const u32 fpcr = RandomFpcr();
instructions.clear();
for (size_t i = 0; i < 5; i++) {
const std::vector<u16> inst = GenRandomThumbInst(start_address + instructions.size() * 2, i == 4);
instructions.insert(instructions.end(), inst.begin(), inst.end());
}
regs[15] = start_address;
RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr, 5);
}
}
TEST_CASE("A32: Test thumb IT instruction", "[thumb]") {
ThumbTestEnv jit_env{};
ThumbTestEnv uni_env{};
Dynarmic::A32::Jit jit{GetUserConfig(jit_env)};
A32Unicorn<ThumbTestEnv> uni{uni_env};
A32Unicorn<ThumbTestEnv>::RegisterArray regs;
A32Unicorn<ThumbTestEnv>::ExtRegArray ext_reg;
std::vector<u16> instructions;
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)); });
const size_t pre_instructions = RandInt<size_t>(0, 3);
const size_t post_instructions = RandInt<size_t>(5, 8);
const u32 start_address = 100;
const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x1F0;
const u32 fpcr = RandomFpcr();
instructions.clear();
for (size_t i = 0; i < pre_instructions; i++) {
const std::vector<u16> inst = GenRandomThumbInst(start_address + instructions.size() * 2, false);
instructions.insert(instructions.end(), inst.begin(), inst.end());
}
// Emit IT instruction
A32::ITState it_state = [&] {
while (true) {
const u16 imm8 = RandInt<u16>(0, 0xFF);
if (mcl::bit::get_bits<0, 3>(imm8) == 0b0000 || mcl::bit::get_bits<4, 7>(imm8) == 0b1111 || (mcl::bit::get_bits<4, 7>(imm8) == 0b1110 && mcl::bit::count_ones(mcl::bit::get_bits<0, 3>(imm8)) != 1)) {
continue;
}
instructions.push_back(0b1011111100000000 | imm8);
return A32::ITState{static_cast<u8>(imm8)};
}
}();
for (size_t i = 0; i < post_instructions; i++) {
const std::vector<u16> inst = GenRandomThumbInst(start_address + instructions.size() * 2, i == post_instructions - 1, it_state);
instructions.insert(instructions.end(), inst.begin(), inst.end());
it_state = it_state.Advance();
}
regs[15] = start_address;
RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr, pre_instructions + 1 + post_instructions);
}
}

View file

@ -0,0 +1,541 @@
/* 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 <string_view>
#include <tuple>
#include <catch2/catch_test_macros.hpp>
#include <mcl/bit/bit_field.hpp>
#include <mcl/stdint.hpp>
#include "../rand_int.h"
#include "../unicorn_emu/a32_unicorn.h"
#include "./testenv.h"
#include "dynarmic/frontend/A32/FPSCR.h"
#include "dynarmic/frontend/A32/PSR.h"
#include "dynarmic/frontend/A32/a32_location_descriptor.h"
#include "dynarmic/frontend/A32/disassembler/disassembler.h"
#include "dynarmic/frontend/A32/translate/a32_translate.h"
#include "dynarmic/interface/A32/a32.h"
#include "dynarmic/ir/basic_block.h"
#include "dynarmic/ir/opt/passes.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(
std::string_view format, std::function<bool(u32)> is_valid = [](u32) { return true; })
: is_valid(is_valid) {
REQUIRE((format.size() == 16 || format.size() == 32));
const auto bit_size = format.size();
for (size_t i = 0; i < bit_size; i++) {
const u32 bit = 1U << (bit_size - 1 - i);
switch (format[i]) {
case '0':
mask |= bit;
break;
case '1':
bits |= bit;
mask |= bit;
break;
default:
// Do nothing
break;
}
}
}
u16 Generate16() const {
u32 inst;
do {
const auto random = RandInt<u16>(0, 0xFFFF);
inst = bits | (random & ~mask);
} while (!is_valid(inst));
ASSERT((inst & mask) == bits);
return static_cast<u16>(inst);
}
u32 Generate32() const {
u32 inst;
do {
const auto random = RandInt<u32>(0, 0xFFFFFFFF);
inst = bits | (random & ~mask);
} while (!is_valid(inst));
ASSERT((inst & mask) == bits);
return inst;
}
private:
u32 bits = 0;
u32 mask = 0;
std::function<bool(u32)> 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, {});
Optimization::NamingPass(ir_block);
Optimization::A32GetSetElimination(ir_block, {.convert_nz_to_nzc = true});
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");
jit.DumpDisassembly();
num_insts += ir_block.CycleCount();
}
#ifdef _MSC_VER
__debugbreak();
#endif
FAIL();
}
}
void FuzzJitThumb16(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);
}
}
void FuzzJitThumb32(const size_t instruction_count, const size_t instructions_to_execute_count, const size_t run_count, const std::function<u32()> instruction_generator) {
ThumbTestEnv test_env;
// Prepare memory.
// A Thumb-32 instruction is 32-bits so we multiply our count
test_env.code_mem.resize(instruction_count * 2 + 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;
for (size_t i = 0; i < instruction_count; i++) {
const auto instruction = instruction_generator();
const auto first_halfword = static_cast<u16>(mcl::bit::get_bits<0, 15>(instruction));
const auto second_halfword = static_cast<u16>(mcl::bit::get_bits<16, 31>(instruction));
test_env.code_mem[i * 2 + 0] = second_halfword;
test_env.code_mem[i * 2 + 1] = first_halfword;
}
RunInstance(run_number, test_env, uni, jit, initial_regs, instruction_count, instructions_to_execute_count);
}
}
TEST_CASE("Fuzz Thumb instructions set 1", "[JitX64][Thumb][Thumb16]") {
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)
[](u32 inst) { return mcl::bit::get_bits<3, 5>(inst) != 0b111; }), // R15 is UNPREDICTABLE
ThumbInstGen("0100010110xxxxxx", // CMP (high registers)
[](u32 inst) { return mcl::bit::get_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
[](u32 inst) { return mcl::bit::get_bits<0, 7>(inst) != 0; }), // Empty reg_list is UNPREDICTABLE
ThumbInstGen("10111100xxxxxxxx", // POP (P = 0)
[](u32 inst) { return mcl::bit::get_bits<0, 7>(inst) != 0; }), // Empty reg_list is UNPREDICTABLE
ThumbInstGen("1100xxxxxxxxxxxx", // STMIA/LDMIA
[](u32 inst) {
// Ensure that the architecturally undefined case of
// the base register being within the list isn't hit.
const u32 rn = mcl::bit::get_bits<8, 10>(inst);
return (inst & (1U << rn)) == 0 && mcl::bit::get_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 {
const auto inst_index = RandInt<size_t>(0, instructions.size() - 1);
return instructions[inst_index].Generate16();
};
SECTION("single instructions") {
FuzzJitThumb16(1, 2, 10000, instruction_select);
}
SECTION("short blocks") {
FuzzJitThumb16(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") {
FuzzJitThumb16(1024, 1025, 1000, instruction_select);
}
#endif
}
TEST_CASE("Fuzz Thumb instructions set 2 (affects PC)", "[JitX64][Thumb][Thumb16]") {
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
[](u32 inst){
const u32 Rm = mcl::bit::get_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>
[](u32 inst) {
const u32 c = mcl::bit::get_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 {
const auto inst_index = RandInt<size_t>(0, instructions.size() - 1);
return instructions[inst_index].Generate16();
};
FuzzJitThumb16(1, 1, 10000, instruction_select);
}
TEST_CASE("Fuzz Thumb32 instructions set", "[JitX64][Thumb][Thumb32]") {
const auto three_reg_not_r15 = [](u32 inst) {
const auto d = mcl::bit::get_bits<8, 11>(inst);
const auto m = mcl::bit::get_bits<0, 3>(inst);
const auto n = mcl::bit::get_bits<16, 19>(inst);
return d != 15 && m != 15 && n != 15;
};
const std::array instructions = {
ThumbInstGen("111110101011nnnn1111dddd1000mmmm", // CLZ
[](u32 inst) {
const auto d = mcl::bit::get_bits<8, 11>(inst);
const auto m = mcl::bit::get_bits<0, 3>(inst);
const auto n = mcl::bit::get_bits<16, 19>(inst);
return m == n && d != 15 && m != 15;
}),
ThumbInstGen("111110101000nnnn1111dddd1000mmmm", // QADD
three_reg_not_r15),
ThumbInstGen("111110101000nnnn1111dddd0001mmmm", // QADD8
three_reg_not_r15),
ThumbInstGen("111110101001nnnn1111dddd0001mmmm", // QADD16
three_reg_not_r15),
ThumbInstGen("111110101010nnnn1111dddd0001mmmm", // QASX
three_reg_not_r15),
ThumbInstGen("111110101000nnnn1111dddd1001mmmm", // QDADD
three_reg_not_r15),
ThumbInstGen("111110101000nnnn1111dddd1011mmmm", // QDSUB
three_reg_not_r15),
ThumbInstGen("111110101110nnnn1111dddd0001mmmm", // QSAX
three_reg_not_r15),
ThumbInstGen("111110101000nnnn1111dddd1010mmmm", // QSUB
three_reg_not_r15),
ThumbInstGen("111110101100nnnn1111dddd0001mmmm", // QSUB8
three_reg_not_r15),
ThumbInstGen("111110101101nnnn1111dddd0001mmmm", // QSUB16
three_reg_not_r15),
ThumbInstGen("111110101001nnnn1111dddd1010mmmm", // RBIT
[](u32 inst) {
const auto d = mcl::bit::get_bits<8, 11>(inst);
const auto m = mcl::bit::get_bits<0, 3>(inst);
const auto n = mcl::bit::get_bits<16, 19>(inst);
return m == n && d != 15 && m != 15;
}),
ThumbInstGen("111110101001nnnn1111dddd1000mmmm", // REV
[](u32 inst) {
const auto d = mcl::bit::get_bits<8, 11>(inst);
const auto m = mcl::bit::get_bits<0, 3>(inst);
const auto n = mcl::bit::get_bits<16, 19>(inst);
return m == n && d != 15 && m != 15;
}),
ThumbInstGen("111110101001nnnn1111dddd1001mmmm", // REV16
[](u32 inst) {
const auto d = mcl::bit::get_bits<8, 11>(inst);
const auto m = mcl::bit::get_bits<0, 3>(inst);
const auto n = mcl::bit::get_bits<16, 19>(inst);
return m == n && d != 15 && m != 15;
}),
ThumbInstGen("111110101001nnnn1111dddd1011mmmm", // REVSH
[](u32 inst) {
const auto d = mcl::bit::get_bits<8, 11>(inst);
const auto m = mcl::bit::get_bits<0, 3>(inst);
const auto n = mcl::bit::get_bits<16, 19>(inst);
return m == n && d != 15 && m != 15;
}),
ThumbInstGen("111110101000nnnn1111dddd0000mmmm", // SADD8
three_reg_not_r15),
ThumbInstGen("111110101001nnnn1111dddd0000mmmm", // SADD16
three_reg_not_r15),
ThumbInstGen("111110101010nnnn1111dddd0000mmmm", // SASX
three_reg_not_r15),
ThumbInstGen("111110101010nnnn1111dddd1000mmmm", // SEL
three_reg_not_r15),
ThumbInstGen("111110101000nnnn1111dddd0010mmmm", // SHADD8
three_reg_not_r15),
ThumbInstGen("111110101001nnnn1111dddd0010mmmm", // SHADD16
three_reg_not_r15),
ThumbInstGen("111110101010nnnn1111dddd0010mmmm", // SHASX
three_reg_not_r15),
ThumbInstGen("111110101110nnnn1111dddd0010mmmm", // SHSAX
three_reg_not_r15),
ThumbInstGen("111110101100nnnn1111dddd0010mmmm", // SHSUB8
three_reg_not_r15),
ThumbInstGen("111110101101nnnn1111dddd0010mmmm", // SHSUB16
three_reg_not_r15),
ThumbInstGen("111110101110nnnn1111dddd0000mmmm", // SSAX
three_reg_not_r15),
ThumbInstGen("111110101100nnnn1111dddd0000mmmm", // SSUB8
three_reg_not_r15),
ThumbInstGen("111110101101nnnn1111dddd0000mmmm", // SSUB16
three_reg_not_r15),
ThumbInstGen("111110101000nnnn1111dddd0100mmmm", // UADD8
three_reg_not_r15),
ThumbInstGen("111110101001nnnn1111dddd0100mmmm", // UADD16
three_reg_not_r15),
ThumbInstGen("111110101010nnnn1111dddd0100mmmm", // UASX
three_reg_not_r15),
ThumbInstGen("111110101000nnnn1111dddd0110mmmm", // UHADD8
three_reg_not_r15),
ThumbInstGen("111110101001nnnn1111dddd0110mmmm", // UHADD16
three_reg_not_r15),
ThumbInstGen("111110101010nnnn1111dddd0110mmmm", // UHASX
three_reg_not_r15),
ThumbInstGen("111110101110nnnn1111dddd0110mmmm", // UHSAX
three_reg_not_r15),
ThumbInstGen("111110101100nnnn1111dddd0110mmmm", // UHSUB8
three_reg_not_r15),
ThumbInstGen("111110101101nnnn1111dddd0110mmmm", // UHSUB16
three_reg_not_r15),
ThumbInstGen("111110101000nnnn1111dddd0101mmmm", // UQADD8
three_reg_not_r15),
ThumbInstGen("111110101001nnnn1111dddd0101mmmm", // UQADD16
three_reg_not_r15),
ThumbInstGen("111110101010nnnn1111dddd0101mmmm", // UQASX
three_reg_not_r15),
ThumbInstGen("111110101110nnnn1111dddd0101mmmm", // UQSAX
three_reg_not_r15),
ThumbInstGen("111110101100nnnn1111dddd0101mmmm", // UQSUB8
three_reg_not_r15),
ThumbInstGen("111110101101nnnn1111dddd0101mmmm", // UQSUB16
three_reg_not_r15),
ThumbInstGen("111110101110nnnn1111dddd0100mmmm", // USAX
three_reg_not_r15),
ThumbInstGen("111110101100nnnn1111dddd0100mmmm", // USUB8
three_reg_not_r15),
ThumbInstGen("111110101101nnnn1111dddd0100mmmm", // USUB16
three_reg_not_r15),
};
const auto instruction_select = [&]() -> u32 {
const auto inst_index = RandInt<size_t>(0, instructions.size() - 1);
return instructions[inst_index].Generate32();
};
SECTION("single instructions") {
FuzzJitThumb32(1, 2, 10000, instruction_select);
}
SECTION("short blocks") {
FuzzJitThumb32(5, 6, 3000, instruction_select);
}
}
TEST_CASE("Verify fix for off by one error in MemoryRead32 worked", "[Thumb][Thumb16]") {
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 <catch2/catch_test_macros.hpp>
#include "dynarmic/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,704 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2016 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <catch2/catch_test_macros.hpp>
#include "./testenv.h"
#include "dynarmic/frontend/A32/a32_location_descriptor.h"
#include "dynarmic/interface/A32/a32.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();
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);
}
TEST_CASE("arm: Memory access (fastmem)", "[arm][A32]") {
constexpr size_t address_width = 12;
constexpr size_t memory_size = 1ull << address_width; // 4K
constexpr size_t page_size = 4 * 1024;
constexpr size_t buffer_size = 2 * page_size;
char buffer[buffer_size];
void* buffer_ptr = reinterpret_cast<void*>(buffer);
size_t buffer_size_nconst = buffer_size;
char* backing_memory = reinterpret_cast<char*>(std::align(page_size, memory_size, buffer_ptr, buffer_size_nconst));
A32FastmemTestEnv env{backing_memory};
Dynarmic::A32::UserConfig config{&env};
config.fastmem_pointer = reinterpret_cast<uintptr_t>(backing_memory);
config.recompile_on_fastmem_failure = false;
config.processor_id = 0;
Dynarmic::A32::Jit jit{config};
memset(backing_memory, 0, memory_size);
memcpy(backing_memory + 0x100, "Lorem ipsum dolor sit amet, consectetur adipiscing elit.", 57);
env.MemoryWrite32(0, 0xE5904000); // LDR R4, [R0]
env.MemoryWrite32(4, 0xE5814000); // STR R4, [R1]
env.MemoryWrite32(8, 0xEAFFFFFE); // B .
jit.Regs()[0] = 0x100;
jit.Regs()[1] = 0x1F0;
jit.Regs()[15] = 0; // PC = 0
jit.SetCpsr(0x000001d0); // User-mode
env.ticks_left = 3;
jit.Run();
REQUIRE(strncmp(backing_memory + 0x100, backing_memory + 0x1F0, 4) == 0);
}
TEST_CASE("arm: vmsr, vcmp, vmrs", "[arm][A32]") {
ArmTestEnv test_env;
A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0xeee10a10, // vmsr fpscr, r0
0xeeb48a4a, // vcmp.f32 s16, s20
0xeef1fa10, // vmrs apsr_nzcv, fpscr
0xe12fff1e, // bx lr
};
jit.ExtRegs()[16] = 0xFF7FFFFF;
jit.ExtRegs()[20] = 0xFF7FFFFF;
jit.Regs()[0] = 0x60000000;
jit.SetFpscr(0x3ee22ac0);
jit.SetCpsr(0x60000000); // User-mode
test_env.ticks_left = 4;
jit.Run();
}
TEST_CASE("arm: sdiv maximally", "[arm][A32]") {
ArmTestEnv test_env;
A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0xe712f011, // sdiv r2, r1, r0
0xeafffffe, // b +#0
};
jit.Regs()[1] = 0x80000000;
jit.Regs()[0] = 0xffffffff;
jit.SetCpsr(0x000001d0); // User-mode
test_env.ticks_left = 2;
jit.Run();
REQUIRE(jit.Regs()[2] == 0x80000000);
}
TEST_CASE("arm: tbl", "[arm][A32]") {
ArmTestEnv test_env;
A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem.emplace_back(0xf3f408a0); // vtbl.8 d16, {d20 }, d16
test_env.code_mem.emplace_back(0xf3f419a1); // vtbl.8 d17, {d20, d21 }, d17
test_env.code_mem.emplace_back(0xf3f42aa2); // vtbl.8 d18, {d20, d21, d22 }, d18
test_env.code_mem.emplace_back(0xf3f43ba3); // vtbl.8 d19, {d20, d21, d22, d23}, d19
test_env.code_mem.emplace_back(0xeafffffe); // b +#0
// Indices
jit.ExtRegs()[16 * 2 + 0] = 0x05'02'01'00;
jit.ExtRegs()[16 * 2 + 1] = 0x20'1F'10'0F;
jit.ExtRegs()[17 * 2 + 0] = 0x05'02'01'00;
jit.ExtRegs()[17 * 2 + 1] = 0x20'1F'10'0F;
jit.ExtRegs()[18 * 2 + 0] = 0x05'02'01'00;
jit.ExtRegs()[18 * 2 + 1] = 0x20'1F'10'0F;
jit.ExtRegs()[19 * 2 + 0] = 0x05'02'01'00;
jit.ExtRegs()[19 * 2 + 1] = 0x20'1F'10'0F;
// Table
jit.ExtRegs()[20 * 2 + 0] = 0x03'02'01'00;
jit.ExtRegs()[20 * 2 + 1] = 0x07'06'05'04;
jit.ExtRegs()[21 * 2 + 0] = 0x0B'0A'09'08;
jit.ExtRegs()[21 * 2 + 1] = 0x0F'0E'0D'0C;
jit.ExtRegs()[22 * 2 + 0] = 0x13'12'11'10;
jit.ExtRegs()[22 * 2 + 1] = 0x17'16'15'14;
jit.ExtRegs()[23 * 2 + 0] = 0x1B'1A'19'18;
jit.ExtRegs()[23 * 2 + 1] = 0x1F'1E'1D'1C;
test_env.ticks_left = 5;
jit.Run();
REQUIRE(jit.ExtRegs()[16 * 2 + 0] == 0x05'02'01'00);
REQUIRE(jit.ExtRegs()[16 * 2 + 1] == 0x00'00'00'00);
REQUIRE(jit.ExtRegs()[17 * 2 + 0] == 0x05'02'01'00);
REQUIRE(jit.ExtRegs()[17 * 2 + 1] == 0x00'00'00'0F);
REQUIRE(jit.ExtRegs()[18 * 2 + 0] == 0x05'02'01'00);
REQUIRE(jit.ExtRegs()[18 * 2 + 1] == 0x00'00'10'0F);
REQUIRE(jit.ExtRegs()[19 * 2 + 0] == 0x05'02'01'00);
REQUIRE(jit.ExtRegs()[19 * 2 + 1] == 0x00'1F'10'0F);
}
TEST_CASE("arm: tbx", "[arm][A32]") {
ArmTestEnv test_env;
A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem.emplace_back(0xf3f408e0); // vtbx.8 d16, {d20 }, d16
test_env.code_mem.emplace_back(0xf3f419e1); // vtbx.8 d17, {d20, d21 }, d17
test_env.code_mem.emplace_back(0xf3f42ae2); // vtbx.8 d18, {d20, d21, d22 }, d18
test_env.code_mem.emplace_back(0xf3f43be3); // vtbx.8 d19, {d20, d21, d22, d23}, d19
test_env.code_mem.emplace_back(0xeafffffe); // b +#0
// Indices
jit.ExtRegs()[16 * 2 + 0] = 0x05'02'01'00;
jit.ExtRegs()[16 * 2 + 1] = 0x20'1F'10'0F;
jit.ExtRegs()[17 * 2 + 0] = 0x05'02'01'00;
jit.ExtRegs()[17 * 2 + 1] = 0x20'1F'10'0F;
jit.ExtRegs()[18 * 2 + 0] = 0x05'02'01'00;
jit.ExtRegs()[18 * 2 + 1] = 0x20'1F'10'0F;
jit.ExtRegs()[19 * 2 + 0] = 0x05'02'01'00;
jit.ExtRegs()[19 * 2 + 1] = 0x20'1F'10'0F;
// Table
jit.ExtRegs()[20 * 2 + 0] = 0x03'02'01'00;
jit.ExtRegs()[20 * 2 + 1] = 0x07'06'05'04;
jit.ExtRegs()[21 * 2 + 0] = 0x0B'0A'09'08;
jit.ExtRegs()[21 * 2 + 1] = 0x0F'0E'0D'0C;
jit.ExtRegs()[22 * 2 + 0] = 0x13'12'11'10;
jit.ExtRegs()[22 * 2 + 1] = 0x17'16'15'14;
jit.ExtRegs()[23 * 2 + 0] = 0x1B'1A'19'18;
jit.ExtRegs()[23 * 2 + 1] = 0x1F'1E'1D'1C;
test_env.ticks_left = 5;
jit.Run();
REQUIRE(jit.ExtRegs()[16 * 2 + 0] == 0x05'02'01'00);
REQUIRE(jit.ExtRegs()[16 * 2 + 1] == 0x20'1F'10'0F);
REQUIRE(jit.ExtRegs()[17 * 2 + 0] == 0x05'02'01'00);
REQUIRE(jit.ExtRegs()[17 * 2 + 1] == 0x20'1F'10'0F);
REQUIRE(jit.ExtRegs()[18 * 2 + 0] == 0x05'02'01'00);
REQUIRE(jit.ExtRegs()[18 * 2 + 1] == 0x20'1F'10'0F);
REQUIRE(jit.ExtRegs()[19 * 2 + 0] == 0x05'02'01'00);
REQUIRE(jit.ExtRegs()[19 * 2 + 1] == 0x20'1F'10'0F);
}

View file

@ -0,0 +1,228 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2022 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <memory>
#include <catch2/catch_test_macros.hpp>
#include "./testenv.h"
#include "dynarmic/frontend/A32/a32_location_descriptor.h"
#include "dynarmic/interface/A32/a32.h"
#include "dynarmic/interface/A32/coprocessor.h"
using namespace Dynarmic;
struct CP15State {
u32 cp15_thread_uprw = 0;
u32 cp15_thread_uro = 0;
u32 cp15_flush_prefetch_buffer = 0; ///< dummy value
u32 cp15_data_sync_barrier = 0; ///< dummy value
u32 cp15_data_memory_barrier = 0; ///< dummy value
};
class TestCP15 final : public Dynarmic::A32::Coprocessor {
public:
using CoprocReg = Dynarmic::A32::CoprocReg;
explicit TestCP15(CP15State&);
~TestCP15() override;
std::optional<Callback> CompileInternalOperation(bool two, unsigned opc1, CoprocReg CRd, CoprocReg CRn, CoprocReg CRm, unsigned opc2) override;
CallbackOrAccessOneWord CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn, CoprocReg CRm, unsigned opc2) override;
CallbackOrAccessTwoWords CompileSendTwoWords(bool two, unsigned opc, CoprocReg CRm) override;
CallbackOrAccessOneWord CompileGetOneWord(bool two, unsigned opc1, CoprocReg CRn, CoprocReg CRm, unsigned opc2) override;
CallbackOrAccessTwoWords CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) override;
std::optional<Callback> CompileLoadWords(bool two, bool long_transfer, CoprocReg CRd, std::optional<u8> option) override;
std::optional<Callback> CompileStoreWords(bool two, bool long_transfer, CoprocReg CRd, std::optional<u8> option) override;
private:
CP15State& state;
};
using Callback = Dynarmic::A32::Coprocessor::Callback;
using CallbackOrAccessOneWord = Dynarmic::A32::Coprocessor::CallbackOrAccessOneWord;
using CallbackOrAccessTwoWords = Dynarmic::A32::Coprocessor::CallbackOrAccessTwoWords;
TestCP15::TestCP15(CP15State& state)
: state(state) {}
TestCP15::~TestCP15() = default;
std::optional<Callback> TestCP15::CompileInternalOperation([[maybe_unused]] bool two, [[maybe_unused]] unsigned opc1, [[maybe_unused]] CoprocReg CRd, [[maybe_unused]] CoprocReg CRn, [[maybe_unused]] CoprocReg CRm, [[maybe_unused]] unsigned opc2) {
return std::nullopt;
}
CallbackOrAccessOneWord TestCP15::CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn, CoprocReg CRm, unsigned opc2) {
if (!two && CRn == CoprocReg::C7 && opc1 == 0 && CRm == CoprocReg::C5 && opc2 == 4) {
return Callback{
[](void* user_arg, std::uint32_t, std::uint32_t) -> std::uint64_t {
CP15State& state = *reinterpret_cast<CP15State*>(user_arg);
state.cp15_flush_prefetch_buffer = 1;
return 0;
},
reinterpret_cast<void*>(&state),
};
}
if (!two && CRn == CoprocReg::C7 && opc1 == 0 && CRm == CoprocReg::C10) {
switch (opc2) {
case 4:
return Callback{
[](void* user_arg, std::uint32_t, std::uint32_t) -> std::uint64_t {
CP15State& state = *reinterpret_cast<CP15State*>(user_arg);
state.cp15_data_sync_barrier = 1;
return 0;
},
reinterpret_cast<void*>(&state),
};
case 5:
return Callback{
[](void* user_arg, std::uint32_t, std::uint32_t) -> std::uint64_t {
CP15State& state = *reinterpret_cast<CP15State*>(user_arg);
state.cp15_data_memory_barrier = 1;
return 0;
},
reinterpret_cast<void*>(&state),
};
default:
return std::monostate{};
}
}
if (!two && CRn == CoprocReg::C13 && opc1 == 0 && CRm == CoprocReg::C0 && opc2 == 2) {
return &state.cp15_thread_uprw;
}
return std::monostate{};
}
CallbackOrAccessTwoWords TestCP15::CompileSendTwoWords([[maybe_unused]] bool two, [[maybe_unused]] unsigned opc, [[maybe_unused]] CoprocReg CRm) {
return std::monostate{};
}
CallbackOrAccessOneWord TestCP15::CompileGetOneWord(bool two, unsigned opc1, CoprocReg CRn, CoprocReg CRm, unsigned opc2) {
// TODO(merry): Privileged CP15 registers
if (!two && CRn == CoprocReg::C13 && opc1 == 0 && CRm == CoprocReg::C0) {
switch (opc2) {
case 2:
return &state.cp15_thread_uprw;
case 3:
return &state.cp15_thread_uro;
default:
return std::monostate{};
}
}
return std::monostate{};
}
CallbackOrAccessTwoWords TestCP15::CompileGetTwoWords([[maybe_unused]] bool two, [[maybe_unused]] unsigned opc, [[maybe_unused]] CoprocReg CRm) {
return std::monostate{};
}
std::optional<Callback> TestCP15::CompileLoadWords([[maybe_unused]] bool two, [[maybe_unused]] bool long_transfer, [[maybe_unused]] CoprocReg CRd, [[maybe_unused]] std::optional<u8> option) {
return std::nullopt;
}
std::optional<Callback> TestCP15::CompileStoreWords([[maybe_unused]] bool two, [[maybe_unused]] bool long_transfer, [[maybe_unused]] CoprocReg CRd, [[maybe_unused]] std::optional<u8> option) {
return std::nullopt;
}
static A32::UserConfig GetUserConfig(ArmTestEnv* testenv, CP15State& cp15_state) {
A32::UserConfig user_config;
user_config.optimizations &= ~OptimizationFlag::FastDispatch;
user_config.callbacks = testenv;
user_config.coprocessors[15] = std::make_unique<TestCP15>(cp15_state);
return user_config;
}
TEST_CASE("arm: Test coprocessor (Read TPIDRURO)", "[arm][A32]") {
ArmTestEnv test_env;
CP15State cp15_state;
A32::Jit jit{GetUserConfig(&test_env, cp15_state)};
cp15_state.cp15_thread_uro = 0xf00d;
cp15_state.cp15_thread_uprw = 0xcafe;
jit.Regs()[0] = 0xaaaa;
test_env.code_mem = {
0xee1d1f70, // mrc p15, 0, r1, c13, c0, 3 (Read TPIDRURO into R1)
0xeafffffe, // b +#0
};
jit.SetCpsr(0x000001d0); // User-mode
test_env.ticks_left = 2;
jit.Run();
REQUIRE(jit.Regs()[1] == 0xf00d);
}
TEST_CASE("arm: Test coprocessor (Read TPIDRURW)", "[arm][A32]") {
ArmTestEnv test_env;
CP15State cp15_state;
A32::Jit jit{GetUserConfig(&test_env, cp15_state)};
cp15_state.cp15_thread_uro = 0xf00d;
cp15_state.cp15_thread_uprw = 0xcafe;
jit.Regs()[0] = 0xaaaa;
test_env.code_mem = {
0xee1d1f50, // mrc p15, 0, r1, c13, c0, 2 (Read TPIDRURW into R1)
0xeafffffe, // b +#0
};
jit.SetCpsr(0x000001d0); // User-mode
test_env.ticks_left = 2;
jit.Run();
REQUIRE(jit.Regs()[1] == 0xcafe);
}
TEST_CASE("arm: Test coprocessor (Write TPIDRURW)", "[arm][A32]") {
ArmTestEnv test_env;
CP15State cp15_state;
A32::Jit jit{GetUserConfig(&test_env, cp15_state)};
cp15_state.cp15_thread_uro = 0xf00d;
cp15_state.cp15_thread_uprw = 0xcafe;
jit.Regs()[0] = 0xaaaa;
test_env.code_mem = {
0xee0d0f50, // mcr p15, 0, r0, c13, c0, 2 (Write R0 into TPIDRURW)
0xeafffffe, // b +#0
};
jit.SetCpsr(0x000001d0); // User-mode
test_env.ticks_left = 2;
jit.Run();
REQUIRE(cp15_state.cp15_thread_uprw == 0xaaaa);
}
TEST_CASE("arm: Test coprocessor (DMB)", "[arm][A32]") {
ArmTestEnv test_env;
CP15State cp15_state;
A32::Jit jit{GetUserConfig(&test_env, cp15_state)};
cp15_state.cp15_thread_uro = 0xf00d;
cp15_state.cp15_thread_uprw = 0xcafe;
jit.Regs()[0] = 0xaaaa;
test_env.code_mem = {
0xee070fba, // mcr p15, 0, r0, c7, c10, 5 (Data Memory Barrier)
0xeafffffe, // b +#0
};
jit.SetCpsr(0x000001d0); // User-mode
test_env.ticks_left = 2;
jit.Run();
REQUIRE(cp15_state.cp15_data_memory_barrier == 1);
}

View file

@ -0,0 +1,39 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2022 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <optional>
#include <catch2/catch_test_macros.hpp>
#include "./testenv.h"
using namespace Dynarmic;
class ArmSvcTestEnv : public ArmTestEnv {
public:
std::optional<u32> svc_called = std::nullopt;
void CallSVC(u32 swi) override {
svc_called = swi;
}
};
TEST_CASE("arm: svc", "[arm][A32]") {
ArmSvcTestEnv test_env;
A32::Jit jit{A32::UserConfig{&test_env}};
test_env.code_mem = {
0xef0001ee, // svc #0x1ee
0xe30a0071, // mov r0, #41073
0xeafffffe, // b +#0
};
jit.SetCpsr(0x000001d0); // User-mode
test_env.ticks_left = 3;
jit.Run();
REQUIRE(test_env.svc_called == 0x1ee);
REQUIRE(jit.Regs()[15] == 0x00000008);
REQUIRE(jit.Regs()[0] == 41073);
}

View file

@ -0,0 +1,254 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2016 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <catch2/catch_test_macros.hpp>
#include <mcl/stdint.hpp>
#include "./testenv.h"
#include "dynarmic/interface/A32/a32.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
}
TEST_CASE("thumb: Opt Failure: Get/Set Elimination for Flags", "[thumb]") {
// This was a randomized test-case that was failing.
//
// Incorrect IR:
// Block: location={0000000100000000}
// cycles=6, entry_cond=al
// [0000556569455160] %0 = GetRegister r1 (uses: 1)
// [00005565694551c8] %1 = GetRegister r6 (uses: 1)
// [0000556569455230] %2 = Mul32 %1, %0 (uses: 1)
// [0000556569455298] SetRegister r6, %2 (uses: 0)
// [0000556569455300] Void (uses: 0)
// [00005565694553d0] Void (uses: 0)
// [0000556569455438] Void (uses: 0)
// [00005565694554a0] Void (uses: 0)
// [0000556569455508] Void (uses: 0)
// [00005565694555d8] %9 = GetCFlag (uses: 1)
// [0000556569455640] %10 = GetRegister r3 (uses: 2)
// [00005565694556a8] %11 = Identity %10 (uses: 1)
// [0000556569455710] %12 = Add32 %11, %10, %9 (uses: 2)
// [0000556569455778] SetRegister r3, %12 (uses: 0)
// [00005565694557e0] %14 = GetNZCVFromOp %12 (uses: 1)
// [0000556569455848] SetCpsrNZCV %14 (uses: 0)
// [00005565694558b0] %16 = GetRegister sp (uses: 1)
// [0000556569455918] %17 = Add32 %16, #0x2c4, #0 (uses: 1)
// [0000556569455980] %18 = GetRegister r4 (uses: 1)
// [00005565694559e8] WriteMemory32 #0x100000006, %17, %18, <unknown immediate type> (uses: 0)
// [0000556569455a50] %20 = GetRegister r2 (uses: 1)
// [0000556569455ab8] %21 = GetRegister r5 (uses: 1)
// [0000556569455b20] %22 = Add32 %21, %20, #0 (uses: 1)
// [0000556569455b88] SetRegister r5, %22 (uses: 0)
// terminal = LinkBlock{{000000010000000a}}
ThumbTestEnv test_env;
Dynarmic::A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0x434e, // muls r6, r1, r6
0x4557, // cmp r7, r10
0x415b, // adcs r3, r3
0x94b1, // str r4, [sp, #708]
0x4415, // add r5, r2
0xe7fe // b +#0
};
jit.Regs() = {0x2154abb5, 0xdbaa6333, 0xf8a7bc0e, 0x989f6096, 0x19cd7783, 0xe1cf5b7f, 0x9bb1aa6c, 0x6b700f5c,
0xc04f6cb2, 0xc8df07f0, 0x217d83de, 0xe77fdffa, 0x98bcceaf, 0xbfcab4f7, 0xdb9d5405, 0x00000000};
jit.SetCpsr(0x000001f0); // Thumb, User-mode
test_env.ticks_left = 7;
jit.Run();
REQUIRE(jit.Regs()[0] == 0x2154abb5);
REQUIRE(jit.Regs()[1] == 0xdbaa6333);
REQUIRE(jit.Regs()[2] == 0xf8a7bc0e);
REQUIRE(jit.Regs()[3] == 0x313ec12d);
REQUIRE(jit.Regs()[4] == 0x19cd7783);
REQUIRE(jit.Regs()[5] == 0xda77178d);
REQUIRE(jit.Regs()[6] == 0x4904b784);
REQUIRE(jit.Regs()[7] == 0x6b700f5c);
REQUIRE(jit.Regs()[8] == 0xc04f6cb2);
REQUIRE(jit.Regs()[9] == 0xc8df07f0);
REQUIRE(jit.Regs()[10] == 0x217d83de);
REQUIRE(jit.Regs()[11] == 0xe77fdffa);
REQUIRE(jit.Regs()[12] == 0x98bcceaf);
REQUIRE(jit.Regs()[13] == 0xbfcab4f7);
REQUIRE(jit.Regs()[14] == 0xdb9d5405);
REQUIRE(jit.Regs()[15] == 0x0000000a);
REQUIRE(jit.Cpsr() == 0x300001f0);
}
TEST_CASE("thumb: Opt Failure: Get/Set Elimination for Flags 2", "[thumb]") {
// This was a randomized test-case that was failing.
ThumbTestEnv test_env;
Dynarmic::A32::Jit jit{GetUserConfig(&test_env)};
test_env.code_mem = {
0x442a, // add r2, r5
0x065d, // lsls r5, r3, #25
0xbc64, // pop {r2, r5, r6}
0x2666, // movs r6, #102
0x7471, // strb r1, [r6, #17]
0xe7fe // b +#0
};
jit.Regs() = {0x954d53b0, 0x4caaad40, 0xa42325b8, 0x0da0cdb6, 0x0f43507e, 0x31d68ae1, 0x9c471808, 0x892a6888,
0x3b9ffb23, 0x0a92ef93, 0x38dee619, 0xc0e95e81, 0x6a448690, 0xc2d4d6ad, 0xe93600b9, 0x00000000};
jit.SetCpsr(0x000001f0); // Thumb, User-mode
test_env.ticks_left = 7;
jit.Run();
const std::array<u32, 16> expected = {0x954d53b0, 0x4caaad40, 0xb0afaead, 0x0da0cdb6, 0x0f43507e, 0xb4b3b2b1, 0x00000066, 0x892a6888,
0x3b9ffb23, 0x0a92ef93, 0x38dee619, 0xc0e95e81, 0x6a448690, 0xc2d4d6b9, 0xe93600b9, 0x0000000a};
REQUIRE(jit.Regs() == expected);
REQUIRE(jit.Cpsr() == 0x200001f0);
}

201
externals/dynarmic/tests/A32/testenv.h vendored Normal file
View file

@ -0,0 +1,201 @@
/* 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 <mcl/assert.hpp>
#include <mcl/stdint.hpp>
#include "dynarmic/interface/A32/a32.h"
template<typename InstructionType_, u32 infinite_loop_u32>
class A32TestEnv : public Dynarmic::A32::UserCallbacks {
public:
using InstructionType = InstructionType_;
using RegisterArray = std::array<u32, 16>;
using ExtRegsArray = std::array<u32, 64>;
#ifdef _MSC_VER
# pragma warning(push)
# pragma warning(disable : 4309) // C4309: 'static_cast': truncation of constant value
#endif
static constexpr InstructionType infinite_loop = static_cast<InstructionType>(infinite_loop_u32);
#ifdef _MSC_VER
# pragma warning(pop)
#endif
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;
void PadCodeMem() {
do {
code_mem.push_back(infinite_loop);
} while (code_mem.size() % 2 != 0);
}
bool IsInCodeMem(u32 vaddr) const {
return vaddr < sizeof(InstructionType) * code_mem.size();
}
std::optional<std::uint32_t> MemoryReadCode(u32 vaddr) override {
if (IsInCodeMem(vaddr)) {
u32 value;
std::memcpy(&value, &code_mem[vaddr / sizeof(InstructionType)], sizeof(u32));
return value;
}
return infinite_loop_u32; // B .
}
std::uint8_t MemoryRead8(u32 vaddr) override {
if (IsInCodeMem(vaddr)) {
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}) code = {:08x}", pc, *MemoryReadCode(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>;
class A32FastmemTestEnv final : public Dynarmic::A32::UserCallbacks {
public:
u64 ticks_left = 0;
char* backing_memory = nullptr;
explicit A32FastmemTestEnv(char* addr)
: backing_memory(addr) {}
template<typename T>
T read(std::uint32_t vaddr) {
T value;
memcpy(&value, backing_memory + vaddr, sizeof(T));
return value;
}
template<typename T>
void write(std::uint32_t vaddr, const T& value) {
memcpy(backing_memory + vaddr, &value, sizeof(T));
}
std::optional<std::uint32_t> MemoryReadCode(std::uint32_t vaddr) override {
return read<std::uint32_t>(vaddr);
}
std::uint8_t MemoryRead8(std::uint32_t vaddr) override {
return read<std::uint8_t>(vaddr);
}
std::uint16_t MemoryRead16(std::uint32_t vaddr) override {
return read<std::uint16_t>(vaddr);
}
std::uint32_t MemoryRead32(std::uint32_t vaddr) override {
return read<std::uint32_t>(vaddr);
}
std::uint64_t MemoryRead64(std::uint32_t vaddr) override {
return read<std::uint64_t>(vaddr);
}
void MemoryWrite8(std::uint32_t vaddr, std::uint8_t value) override {
write(vaddr, value);
}
void MemoryWrite16(std::uint32_t vaddr, std::uint16_t value) override {
write(vaddr, value);
}
void MemoryWrite32(std::uint32_t vaddr, std::uint32_t value) override {
write(vaddr, value);
}
void MemoryWrite64(std::uint32_t vaddr, std::uint64_t value) override {
write(vaddr, value);
}
bool MemoryWriteExclusive8(std::uint32_t vaddr, std::uint8_t value, [[maybe_unused]] std::uint8_t expected) override {
MemoryWrite8(vaddr, value);
return true;
}
bool MemoryWriteExclusive16(std::uint32_t vaddr, std::uint16_t value, [[maybe_unused]] std::uint16_t expected) override {
MemoryWrite16(vaddr, value);
return true;
}
bool MemoryWriteExclusive32(std::uint32_t vaddr, std::uint32_t value, [[maybe_unused]] std::uint32_t expected) override {
MemoryWrite32(vaddr, value);
return true;
}
bool MemoryWriteExclusive64(std::uint32_t vaddr, std::uint64_t value, [[maybe_unused]] std::uint64_t expected) override {
MemoryWrite64(vaddr, value);
return true;
}
void InterpreterFallback(std::uint32_t 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(std::uint32_t pc, Dynarmic::A32::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;
}
};

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

2156
externals/dynarmic/tests/A64/a64.cpp vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,168 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2023 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <array>
#include <exception>
#include <map>
#include <catch2/catch_test_macros.hpp>
#include <mcl/stdint.hpp>
#include <oaknut/oaknut.hpp>
#include "dynarmic/interface/A64/a64.h"
using namespace Dynarmic;
namespace {
class MyEnvironment final : public A64::UserCallbacks {
public:
u64 ticks_left = 0;
std::map<u64, u8> memory{};
u8 MemoryRead8(u64 vaddr) override {
return memory[vaddr];
}
u16 MemoryRead16(u64 vaddr) override {
return u16(MemoryRead8(vaddr)) | u16(MemoryRead8(vaddr + 1)) << 8;
}
u32 MemoryRead32(u64 vaddr) override {
return u32(MemoryRead16(vaddr)) | u32(MemoryRead16(vaddr + 2)) << 16;
}
u64 MemoryRead64(u64 vaddr) override {
return u64(MemoryRead32(vaddr)) | u64(MemoryRead32(vaddr + 4)) << 32;
}
std::array<u64, 2> MemoryRead128(u64 vaddr) override {
return {MemoryRead64(vaddr), MemoryRead64(vaddr + 8)};
}
void MemoryWrite8(u64 vaddr, u8 value) override {
memory[vaddr] = value;
}
void MemoryWrite16(u64 vaddr, u16 value) override {
MemoryWrite8(vaddr, u8(value));
MemoryWrite8(vaddr + 1, u8(value >> 8));
}
void MemoryWrite32(u64 vaddr, u32 value) override {
MemoryWrite16(vaddr, u16(value));
MemoryWrite16(vaddr + 2, u16(value >> 16));
}
void MemoryWrite64(u64 vaddr, u64 value) override {
MemoryWrite32(vaddr, u32(value));
MemoryWrite32(vaddr + 4, u32(value >> 32));
}
void MemoryWrite128(u64 vaddr, std::array<u64, 2> value) override {
MemoryWrite64(vaddr, value[0]);
MemoryWrite64(vaddr + 8, value[1]);
}
void InterpreterFallback(u64, size_t) override {
// This is never called in practice.
std::terminate();
}
void CallSVC(u32) override {
// Do something.
}
void ExceptionRaised(u64, A64::Exception) override {
cpu->HaltExecution();
}
void AddTicks(u64) override {
}
u64 GetTicksRemaining() override {
return 1000000000000;
}
std::uint64_t GetCNTPCT() override {
return 0;
}
A64::Jit* cpu;
};
} // namespace
TEST_CASE("A64: fibonacci", "[a64]") {
MyEnvironment env;
A64::UserConfig user_config;
user_config.callbacks = &env;
A64::Jit cpu{user_config};
env.cpu = &cpu;
std::vector<u32> instructions(1024);
oaknut::CodeGenerator code{instructions.data(), nullptr};
using namespace oaknut::util;
oaknut::Label start, end, zero, recurse;
code.l(start);
code.STP(X29, X30, SP, PRE_INDEXED, -32);
code.STP(X20, X19, SP, 16);
code.MOV(X29, SP);
code.MOV(W19, W0);
code.SUBS(W0, W0, 1);
code.B(LT, zero);
code.B(NE, recurse);
code.MOV(W0, 1);
code.B(end);
code.l(zero);
code.MOV(W0, WZR);
code.B(end);
code.l(recurse);
code.BL(start);
code.MOV(W20, W0);
code.SUB(W0, W19, 2);
code.BL(start);
code.ADD(W0, W0, W20);
code.l(end);
code.LDP(X20, X19, SP, 16);
code.LDP(X29, X30, SP, POST_INDEXED, 32);
code.RET();
for (size_t i = 0; i < 1024; i++) {
env.MemoryWrite32(i * 4, instructions[i]);
}
env.MemoryWrite32(8888, 0xd4200000);
cpu.SetRegister(30, 8888);
cpu.SetRegister(0, 10);
cpu.SetSP(0xffff0000);
cpu.SetPC(0);
cpu.Run();
REQUIRE(cpu.GetRegister(0) == 55);
cpu.SetRegister(0, 20);
cpu.SetSP(0xffff0000);
cpu.SetPC(0);
cpu.Run();
REQUIRE(cpu.GetRegister(0) == 6765);
cpu.SetRegister(0, 30);
cpu.SetSP(0xffff0000);
cpu.SetPC(0);
cpu.Run();
REQUIRE(cpu.GetRegister(0) == 832040);
}

View file

@ -0,0 +1,185 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2022 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <vector>
#include <catch2/catch_test_macros.hpp>
#include <mcl/stdint.hpp>
#include "./testenv.h"
using namespace Dynarmic;
namespace {
struct TestCase {
u32 a;
u32 b;
u32 fmax;
u32 fmaxnm;
u32 fmin;
u32 fminnm;
};
const std::vector test_cases{
// a b fmax fmaxnm fmin fminnm
TestCase{0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000}, // +0.0
TestCase{0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000, 0x80000000}, // -0.0
TestCase{0x3f800000, 0x3f800000, 0x3f800000, 0x3f800000, 0x3f800000, 0x3f800000}, // +1.0
TestCase{0xbf800000, 0xbf800000, 0xbf800000, 0xbf800000, 0xbf800000, 0xbf800000}, // -1.0
TestCase{0x7f800000, 0x7f800000, 0x7f800000, 0x7f800000, 0x7f800000, 0x7f800000}, // +Inf
TestCase{0xff800000, 0xff800000, 0xff800000, 0xff800000, 0xff800000, 0xff800000}, // -Inf
TestCase{0x7fc00041, 0x7fc00041, 0x7fc00041, 0x7fc00041, 0x7fc00041, 0x7fc00041}, // QNaN
TestCase{0x7f800042, 0x7f800042, 0x7fc00042, 0x7fc00042, 0x7fc00042, 0x7fc00042}, // SNaN
TestCase{0x00000000, 0x80000000, 0x00000000, 0x00000000, 0x80000000, 0x80000000}, // (+0.0, -0.0)
TestCase{0x3f800000, 0xbf800000, 0x3f800000, 0x3f800000, 0xbf800000, 0xbf800000}, // (+1.0, -1.0)
TestCase{0x3f800000, 0x7f800000, 0x7f800000, 0x7f800000, 0x3f800000, 0x3f800000}, // (+1.0, +Inf)
TestCase{0x3f800000, 0xff800000, 0x3f800000, 0x3f800000, 0xff800000, 0xff800000}, // (+1.0, -Inf)
TestCase{0x7f800000, 0xff800000, 0x7f800000, 0x7f800000, 0xff800000, 0xff800000}, // (+Inf, -Inf)
TestCase{0x3f800000, 0x7fc00041, 0x7fc00041, 0x3f800000, 0x7fc00041, 0x3f800000}, // (+1.0, QNaN)
TestCase{0x3f800000, 0x7f800042, 0x7fc00042, 0x7fc00042, 0x7fc00042, 0x7fc00042}, // (+1.0, SNaN)
TestCase{0x7f800000, 0x7fc00041, 0x7fc00041, 0x7f800000, 0x7fc00041, 0x7f800000}, // (+Inf, QNaN)
TestCase{0x7f800000, 0x7f800042, 0x7fc00042, 0x7fc00042, 0x7fc00042, 0x7fc00042}, // (+Inf, SNaN)
TestCase{0x7fc00041, 0x7f800042, 0x7fc00042, 0x7fc00042, 0x7fc00042, 0x7fc00042}, // (QNaN, SNaN)
TestCase{0xffa57454, 0xe343a6b3, 0xffe57454, 0xffe57454, 0xffe57454, 0xffe57454},
};
const std::vector unidirectional_test_cases{
TestCase{0x7fc00041, 0x7fc00043, 0x7fc00041, 0x7fc00041, 0x7fc00041, 0x7fc00041}, // (QNaN, QNaN)
TestCase{0x7f800042, 0x7f800044, 0x7fc00042, 0x7fc00042, 0x7fc00042, 0x7fc00042}, // (SNaN, SNaN)
};
constexpr u32 default_nan = 0x7fc00000;
bool is_nan(u32 value) {
return (value & 0x7f800000) == 0x7f800000 && (value & 0x007fffff) != 0;
}
u32 force_default_nan(u32 value) {
return is_nan(value) ? default_nan : value;
}
template<typename Fn>
void run_test(u32 instruction, Fn fn) {
A64TestEnv env;
A64::Jit jit{A64::UserConfig{&env}};
env.code_mem.emplace_back(instruction); // FMAX S0, S1, S2
env.code_mem.emplace_back(0x14000000); // B .
for (const auto base_fpcr : {0, 0x01000000}) {
for (const auto test_case : test_cases) {
INFO(test_case.a);
INFO(test_case.b);
jit.SetFpcr(base_fpcr);
jit.SetVector(0, {42, 0});
jit.SetVector(1, {test_case.a, 0});
jit.SetVector(2, {test_case.b, 0});
jit.SetPC(0);
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetVector(0)[0] == fn(test_case));
jit.SetVector(0, {42, 0});
jit.SetVector(1, {test_case.b, 0});
jit.SetVector(2, {test_case.a, 0});
jit.SetPC(0);
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetVector(0)[0] == fn(test_case));
jit.SetFpcr(base_fpcr | 0x02000000);
jit.SetVector(0, {42, 0});
jit.SetVector(1, {test_case.a, 0});
jit.SetVector(2, {test_case.b, 0});
jit.SetPC(0);
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetVector(0)[0] == force_default_nan(fn(test_case)));
jit.SetVector(0, {42, 0});
jit.SetVector(1, {test_case.b, 0});
jit.SetVector(2, {test_case.a, 0});
jit.SetPC(0);
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetVector(0)[0] == force_default_nan(fn(test_case)));
}
for (const auto test_case : unidirectional_test_cases) {
INFO(test_case.a);
INFO(test_case.b);
jit.SetFpcr(base_fpcr);
jit.SetVector(0, {42, 0});
jit.SetVector(1, {test_case.a, 0});
jit.SetVector(2, {test_case.b, 0});
jit.SetPC(0);
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetVector(0)[0] == fn(test_case));
jit.SetFpcr(base_fpcr | 0x02000000);
jit.SetVector(0, {42, 0});
jit.SetVector(1, {test_case.a, 0});
jit.SetVector(2, {test_case.b, 0});
jit.SetPC(0);
env.ticks_left = 2;
jit.Run();
REQUIRE(jit.GetVector(0)[0] == force_default_nan(fn(test_case)));
}
}
}
} // namespace
TEST_CASE("A64: FMAX (scalar)", "[a64]") {
run_test(0x1e224820, [](const TestCase& test_case) { return test_case.fmax; });
}
TEST_CASE("A64: FMIN (scalar)", "[a64]") {
run_test(0x1e225820, [](const TestCase& test_case) { return test_case.fmin; });
}
TEST_CASE("A64: FMAXNM (scalar)", "[a64]") {
run_test(0x1e226820, [](const TestCase& test_case) { return test_case.fmaxnm; });
}
TEST_CASE("A64: FMINNM (scalar)", "[a64]") {
run_test(0x1e227820, [](const TestCase& test_case) { return test_case.fminnm; });
}
TEST_CASE("A64: FMAX (vector)", "[a64]") {
run_test(0x4e22f420, [](const TestCase& test_case) { return test_case.fmax; });
}
TEST_CASE("A64: FMIN (vector)", "[a64]") {
run_test(0x4ea2f420, [](const TestCase& test_case) { return test_case.fmin; });
}
TEST_CASE("A64: FMAXNM (vector)", "[a64]") {
run_test(0x4e22c420, [](const TestCase& test_case) { return test_case.fmaxnm; });
}
TEST_CASE("A64: FMINNM (vector)", "[a64]") {
run_test(0x4ea2c420, [](const TestCase& test_case) { return test_case.fminnm; });
}

View file

@ -0,0 +1,519 @@
/* 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 <catch2/catch_test_macros.hpp>
#include <mcl/scope_exit.hpp>
#include <mcl/stdint.hpp>
#include "../fuzz_util.h"
#include "../rand_int.h"
#include "../unicorn_emu/a64_unicorn.h"
#include "./testenv.h"
#include "dynarmic/common/fp/fpcr.h"
#include "dynarmic/common/fp/fpsr.h"
#include "dynarmic/common/llvm_disassemble.h"
#include "dynarmic/frontend/A64/a64_location_descriptor.h"
#include "dynarmic/frontend/A64/a64_types.h"
#include "dynarmic/frontend/A64/decoder/a64.h"
#include "dynarmic/frontend/A64/translate/a64_translate.h"
#include "dynarmic/ir/basic_block.h"
#include "dynarmic/ir/opcodes.h"
#include "dynarmic/ir/opt/passes.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 "dynarmic/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 "dynarmic/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 (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", A64::RegToString(static_cast<A64::Reg>(i)), regs[i]);
}
for (size_t i = 0; i < vecs.size(); ++i) {
fmt::print("{:3s}: {:016x}{:016x}\n", A64::VecToString(static_cast<A64::Vec>(i)), vecs[i][1], vecs[i][0]);
}
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", A64::RegToString(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}: {:016x}{:016x} {:016x}{:016x} {}\n", A64::VecToString(static_cast<A64::Vec>(i)),
uni_vecs[i][1], uni_vecs[i][0],
jit.GetVectors()[i][1], jit.GetVectors()[i][0],
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));
Optimization::NamingPass(ir_block);
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");
jit.DumpDisassembly();
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 = [&] {
if (RandInt<size_t>(0, 1) == 0) {
return RandInt<u64>(0, 0xffffffff);
}
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,29 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <catch2/catch_test_macros.hpp>
#include "./testenv.h"
#include "dynarmic/interface/A64/a64.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.
}

View file

@ -0,0 +1,113 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <catch2/catch_test_macros.hpp>
#include "./testenv.h"
#include "dynarmic/interface/A64/a64.h"
using namespace Dynarmic;
TEST_CASE("ensure fast dispatch entry is cleared even when a block does not have any patching requirements", "[a64]") {
A64TestEnv env;
A64::UserConfig conf{&env};
A64::Jit jit{conf};
REQUIRE(conf.HasOptimization(OptimizationFlag::FastDispatch));
env.code_mem_start_address = 100;
env.code_mem.clear();
env.code_mem.emplace_back(0xd2800d80); // MOV X0, 108
env.code_mem.emplace_back(0xd61f0000); // BR X0
env.code_mem.emplace_back(0xd2800540); // MOV X0, 42
env.code_mem.emplace_back(0x14000000); // B .
jit.SetPC(100);
env.ticks_left = 4;
jit.Run();
REQUIRE(jit.GetRegister(0) == 42);
jit.SetPC(100);
env.ticks_left = 4;
jit.Run();
REQUIRE(jit.GetRegister(0) == 42);
jit.InvalidateCacheRange(108, 4);
jit.SetPC(100);
env.ticks_left = 4;
jit.Run();
REQUIRE(jit.GetRegister(0) == 42);
env.code_mem[2] = 0xd28008a0; // MOV X0, 69
jit.SetPC(100);
env.ticks_left = 4;
jit.Run();
REQUIRE(jit.GetRegister(0) == 42);
jit.InvalidateCacheRange(108, 4);
jit.SetPC(100);
env.ticks_left = 4;
jit.Run();
REQUIRE(jit.GetRegister(0) == 69);
jit.SetPC(100);
env.ticks_left = 4;
jit.Run();
REQUIRE(jit.GetRegister(0) == 69);
}
TEST_CASE("ensure fast dispatch entry is cleared even when a block does not have any patching requirements 2", "[a64]") {
A64TestEnv env;
A64::UserConfig conf{&env};
A64::Jit jit{conf};
REQUIRE(conf.HasOptimization(OptimizationFlag::FastDispatch));
env.code_mem.emplace_back(0xd2800100); // MOV X0, 8
env.code_mem.emplace_back(0xd61f0000); // BR X0
env.code_mem.emplace_back(0xd2800540); // MOV X0, 42
env.code_mem.emplace_back(0x14000000); // B .
jit.SetPC(0);
env.ticks_left = 4;
jit.Run();
REQUIRE(jit.GetRegister(0) == 42);
jit.SetPC(0);
env.ticks_left = 4;
jit.Run();
REQUIRE(jit.GetRegister(0) == 42);
jit.InvalidateCacheRange(8, 4);
jit.SetPC(0);
env.ticks_left = 4;
jit.Run();
REQUIRE(jit.GetRegister(0) == 42);
env.code_mem[2] = 0xd28008a0; // MOV X0, 69
jit.SetPC(0);
env.ticks_left = 4;
jit.Run();
REQUIRE(jit.GetRegister(0) == 42);
jit.InvalidateCacheRange(8, 4);
jit.SetPC(0);
env.ticks_left = 4;
jit.Run();
REQUIRE(jit.GetRegister(0) == 69);
jit.SetPC(0);
env.ticks_left = 4;
jit.Run();
REQUIRE(jit.GetRegister(0) == 69);
}

224
externals/dynarmic/tests/A64/testenv.h vendored Normal file
View file

@ -0,0 +1,224 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#pragma once
#include <array>
#include <map>
#include <mcl/assert.hpp>
#include <mcl/stdint.hpp>
#include "dynarmic/interface/A64/a64.h"
using Vector = Dynarmic::A64::Vector;
class A64TestEnv : 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::optional<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;
}
};
class A64FastmemTestEnv final : public Dynarmic::A64::UserCallbacks {
public:
u64 ticks_left = 0;
char* backing_memory = nullptr;
explicit A64FastmemTestEnv(char* addr)
: backing_memory(addr) {}
template<typename T>
T read(u64 vaddr) {
T value;
memcpy(&value, backing_memory + vaddr, sizeof(T));
return value;
}
template<typename T>
void write(u64 vaddr, const T& value) {
memcpy(backing_memory + vaddr, &value, sizeof(T));
}
std::optional<std::uint32_t> MemoryReadCode(u64 vaddr) override {
return read<std::uint32_t>(vaddr);
}
std::uint8_t MemoryRead8(u64 vaddr) override {
return read<std::uint8_t>(vaddr);
}
std::uint16_t MemoryRead16(u64 vaddr) override {
return read<std::uint16_t>(vaddr);
}
std::uint32_t MemoryRead32(u64 vaddr) override {
return read<std::uint32_t>(vaddr);
}
std::uint64_t MemoryRead64(u64 vaddr) override {
return read<std::uint64_t>(vaddr);
}
Vector MemoryRead128(u64 vaddr) override {
return read<Vector>(vaddr);
}
void MemoryWrite8(u64 vaddr, std::uint8_t value) override {
write(vaddr, value);
}
void MemoryWrite16(u64 vaddr, std::uint16_t value) override {
write(vaddr, value);
}
void MemoryWrite32(u64 vaddr, std::uint32_t value) override {
write(vaddr, value);
}
void MemoryWrite64(u64 vaddr, std::uint64_t value) override {
write(vaddr, value);
}
void MemoryWrite128(u64 vaddr, Vector value) override {
write(vaddr, value);
}
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) 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,80 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <array>
#include <catch2/catch_test_macros.hpp>
#include "../rand_int.h"
#include "../unicorn_emu/a64_unicorn.h"
#include "./testenv.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);
}

132
externals/dynarmic/tests/CMakeLists.txt vendored Normal file
View file

@ -0,0 +1,132 @@
include(TargetArchitectureSpecificSources)
add_executable(dynarmic_tests
fp/FPToFixed.cpp
fp/FPValue.cpp
fp/mantissa_util_tests.cpp
fp/unpacked_tests.cpp
rand_int.h
)
if ("A32" IN_LIST DYNARMIC_FRONTENDS)
target_sources(dynarmic_tests PRIVATE
A32/test_arm_disassembler.cpp
A32/test_arm_instructions.cpp
A32/test_coprocessor.cpp
A32/test_svc.cpp
A32/test_thumb_instructions.cpp
A32/testenv.h
decoder_tests.cpp
)
endif()
if ("A64" IN_LIST DYNARMIC_FRONTENDS)
target_link_libraries(dynarmic_tests PRIVATE merry::oaknut)
target_sources(dynarmic_tests PRIVATE
A64/a64.cpp
A64/fibonacci.cpp
A64/fp_min_max.cpp
A64/misaligned_page_table.cpp
A64/test_invalidation.cpp
A64/testenv.h
)
endif()
if (DYNARMIC_TESTS_USE_UNICORN)
target_link_libraries(dynarmic_tests PRIVATE Unicorn::Unicorn)
target_sources(dynarmic_tests PRIVATE
fuzz_util.cpp
fuzz_util.h
)
if ("A32" IN_LIST DYNARMIC_FRONTENDS)
target_sources(dynarmic_tests PRIVATE
A32/fuzz_arm.cpp
A32/fuzz_thumb.cpp
unicorn_emu/a32_unicorn.cpp
unicorn_emu/a32_unicorn.h
)
endif()
if ("A64" IN_LIST DYNARMIC_FRONTENDS)
target_sources(dynarmic_tests PRIVATE
A64/fuzz_with_unicorn.cpp
A64/verify_unicorn.cpp
unicorn_emu/a64_unicorn.cpp
unicorn_emu/a64_unicorn.h
)
endif()
endif()
if ("riscv" IN_LIST ARCHITECTURE)
target_link_libraries(dynarmic_tests PRIVATE biscuit::biscuit)
endif()
if ("x86_64" IN_LIST ARCHITECTURE)
target_link_libraries(dynarmic_tests PRIVATE xbyak::xbyak)
target_architecture_specific_sources(dynarmic_tests "x86_64"
x64_cpu_info.cpp
)
if (NOT MSVC AND NOT DYNARMIC_MULTIARCH_BUILD)
target_sources(dynarmic_tests PRIVATE
rsqrt_test.cpp
rsqrt_test_fn.s
)
endif()
endif()
include(CreateDirectoryGroups)
if (("A32" IN_LIST DYNARMIC_FRONTENDS) AND ("A64" IN_LIST DYNARMIC_FRONTENDS))
add_executable(dynarmic_print_info
print_info.cpp
)
create_target_directory_groups(dynarmic_print_info)
target_link_libraries(dynarmic_print_info PRIVATE dynarmic Boost::boost fmt::fmt merry::mcl)
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=1)
endif()
if (("A32" IN_LIST DYNARMIC_FRONTENDS) AND ("A64" IN_LIST DYNARMIC_FRONTENDS))
add_executable(dynarmic_test_generator
fuzz_util.cpp
fuzz_util.h
test_generator.cpp
)
create_target_directory_groups(dynarmic_test_generator)
target_link_libraries(dynarmic_test_generator PRIVATE dynarmic Boost::boost fmt::fmt merry::mcl)
target_include_directories(dynarmic_test_generator PRIVATE . ../src)
target_compile_options(dynarmic_test_generator PRIVATE ${DYNARMIC_CXX_FLAGS})
target_compile_definitions(dynarmic_test_generator PRIVATE FMT_USE_USER_DEFINED_LITERALS=1)
endif()
if (("A32" IN_LIST DYNARMIC_FRONTENDS) AND ("A64" IN_LIST DYNARMIC_FRONTENDS))
add_executable(dynarmic_test_reader
test_reader.cpp
)
create_target_directory_groups(dynarmic_test_reader)
target_link_libraries(dynarmic_test_reader PRIVATE dynarmic Boost::boost fmt::fmt merry::mcl)
target_include_directories(dynarmic_test_reader PRIVATE . ../src)
target_compile_options(dynarmic_test_reader PRIVATE ${DYNARMIC_CXX_FLAGS})
target_compile_definitions(dynarmic_test_reader PRIVATE FMT_USE_USER_DEFINED_LITERALS=1)
endif()
create_target_directory_groups(dynarmic_tests)
target_link_libraries(dynarmic_tests PRIVATE dynarmic Boost::boost Catch2::Catch2WithMain fmt::fmt merry::mcl)
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=1)
add_test(dynarmic_tests dynarmic_tests --durations yes)

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 <iomanip>
#include <iostream>
#include <catch2/catch_test_macros.hpp>
#include <mcl/assert.hpp>
#include "dynarmic/frontend/A32/decoder/asimd.h"
#include "dynarmic/frontend/A32/translate/impl/a32_translate_impl.h"
#include "dynarmic/interface/A32/config.h"
#include "dynarmic/ir/opcodes.h"
using namespace Dynarmic;
TEST_CASE("ASIMD Decoder: Ensure table order correctness", "[decode][a32][.]") {
const auto table = A32::GetASIMDDecodeTable<A32::TranslatorVisitor>();
const auto get_ir = [](const A32::ASIMDMatcher<A32::TranslatorVisitor>& matcher, u32 instruction) {
ASSERT(matcher.Matches(instruction));
const A32::LocationDescriptor location{0, {}, {}};
IR::Block block{location};
A32::TranslatorVisitor visitor{block, location, {}};
matcher.call(visitor, instruction);
return block;
};
const auto is_decode_error = [&get_ir](const A32::ASIMDMatcher<A32::TranslatorVisitor>& 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);
}
}

View file

@ -0,0 +1,57 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <tuple>
#include <vector>
#include <catch2/catch_test_macros.hpp>
#include <mcl/stdint.hpp>
#include "../rand_int.h"
#include "dynarmic/common/fp/fpcr.h"
#include "dynarmic/common/fp/fpsr.h"
#include "dynarmic/common/fp/op.h"
#include "dynarmic/common/fp/rounding_mode.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);
}
}
TEST_CASE("FPToFixed edge cases", "[fp]") {
const std::vector<std::tuple<u64, u64, bool, FP::RoundingMode>> test_cases{
{0x41dffffffffffffe, 0x7fffffff, false, FP::RoundingMode::ToNearest_TieEven},
{0x41dffffffffffffe, 0x7fffffff, false, FP::RoundingMode::TowardsPlusInfinity},
{0x41dffffffffffffe, 0x7fffffff, false, FP::RoundingMode::TowardsMinusInfinity},
{0x41dffffffffffffe, 0x7fffffff, false, FP::RoundingMode::TowardsZero},
{0x41dffffffffffffe, 0x7fffffff, false, FP::RoundingMode::ToNearest_TieAwayFromZero},
};
const FPCR fpcr;
FPSR fpsr;
for (auto [input, expected_output, unsigned_, rounding_mode] : test_cases) {
const u64 output = FPToFixed<u64>(32, input, 0, unsigned_, fpcr, rounding_mode, fpsr);
REQUIRE(output == expected_output);
}
}

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

@ -0,0 +1,15 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include "dynarmic/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 <catch2/catch_test_macros.hpp>
#include <mcl/stdint.hpp>
#include "../rand_int.h"
#include "dynarmic/common/fp/mantissa_util.h"
#include "dynarmic/common/safe_ops.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 = mcl::bit::sign_extend<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 <catch2/catch_test_macros.hpp>
#include <mcl/stdint.hpp>
#include "../rand_int.h"
#include "dynarmic/common/fp/fpcr.h"
#include "dynarmic/common/fp/fpsr.h"
#include "dynarmic/common/fp/unpacked.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);
}

67
externals/dynarmic/tests/fuzz_util.cpp vendored Normal file
View file

@ -0,0 +1,67 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include "./fuzz_util.h"
#include <cstring>
#include <fmt/format.h>
#include <fmt/ostream.h>
#include <mcl/assert.hpp>
#include "./rand_int.h"
#include "dynarmic/common/fp/fpcr.h"
#include "dynarmic/common/fp/rounding_mode.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) {
const size_t format_len = std::strlen(format);
ASSERT(format_len == 16 || format_len == 32);
if (format_len == 16) {
// Begin with 16 zeros
mask |= 0xFFFF0000;
}
for (size_t i = 0; i < format_len; i++) {
const u32 bit = 1u << (format_len - i - 1);
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 Normal 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 <mcl/stdint.hpp>
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;
};

342
externals/dynarmic/tests/print_info.cpp vendored Normal file
View file

@ -0,0 +1,342 @@
/* 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 <fmt/format.h>
#include <fmt/ostream.h>
#include <mcl/bit/swap.hpp>
#include <mcl/stdint.hpp>
#include "dynarmic/common/llvm_disassemble.h"
#include "dynarmic/frontend/A32/a32_location_descriptor.h"
#include "dynarmic/frontend/A32/decoder/arm.h"
#include "dynarmic/frontend/A32/decoder/asimd.h"
#include "dynarmic/frontend/A32/decoder/vfp.h"
#include "dynarmic/frontend/A32/translate/a32_translate.h"
#include "dynarmic/frontend/A32/translate/impl/a32_translate_impl.h"
#include "dynarmic/frontend/A64/a64_location_descriptor.h"
#include "dynarmic/frontend/A64/decoder/a64.h"
#include "dynarmic/frontend/A64/translate/a64_translate.h"
#include "dynarmic/frontend/A64/translate/impl/impl.h"
#include "dynarmic/interface/A32/a32.h"
#include "dynarmic/interface/A32/disassembler.h"
#include "dynarmic/ir/basic_block.h"
#include "dynarmic/ir/opt/passes.h"
using namespace Dynarmic;
const char* GetNameOfA32Instruction(u32 instruction) {
if (auto vfp_decoder = A32::DecodeVFP<A32::TranslatorVisitor>(instruction)) {
return vfp_decoder->get().GetName();
} else if (auto asimd_decoder = A32::DecodeASIMD<A32::TranslatorVisitor>(instruction)) {
return asimd_decoder->get().GetName();
} else if (auto decoder = A32::DecodeArm<A32::TranslatorVisitor>(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(false, 0, (u8*)&instruction, sizeof(instruction)));
fmt::print("Name: {}\n", GetNameOfA32Instruction(instruction));
const A32::LocationDescriptor location{0, {}, {}};
IR::Block ir_block{location};
const bool should_continue = A32::TranslateSingleInstruction(ir_block, location, instruction);
fmt::print("should_continue: {}\n\n", should_continue);
Optimization::NamingPass(ir_block);
fmt::print("IR:\n");
fmt::print("{}\n", IR::DumpBlock(ir_block));
Optimization::A32GetSetElimination(ir_block, {});
Optimization::DeadCodeElimination(ir_block);
Optimization::ConstantPropagation(ir_block);
Optimization::DeadCodeElimination(ir_block);
Optimization::IdentityRemovalPass(ir_block);
fmt::print("Optimized IR:\n");
fmt::print("{}\n", IR::DumpBlock(ir_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 ir_block{location};
const bool should_continue = A64::TranslateSingleInstruction(ir_block, location, instruction);
fmt::print("should_continue: {}\n\n", should_continue);
Optimization::NamingPass(ir_block);
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);
Optimization::IdentityRemovalPass(ir_block);
fmt::print("Optimized IR:\n");
fmt::print("{}\n", IR::DumpBlock(ir_block));
}
void PrintThumbInstruction(u32 instruction) {
const size_t inst_size = (instruction >> 16) == 0 ? 2 : 4;
if (inst_size == 4)
instruction = mcl::bit::swap_halves_32(instruction);
fmt::print("{:08x} {}\n", instruction, Common::DisassembleAArch32(true, 0, (u8*)&instruction, inst_size));
const A32::LocationDescriptor location{0, A32::PSR{0x1F0}, {}};
IR::Block ir_block{location};
const bool should_continue = A32::TranslateSingleInstruction(ir_block, location, instruction);
fmt::print("should_continue: {}\n\n", should_continue);
Optimization::NamingPass(ir_block);
fmt::print("IR:\n");
fmt::print("{}\n", IR::DumpBlock(ir_block));
Optimization::A32GetSetElimination(ir_block, {});
Optimization::DeadCodeElimination(ir_block);
Optimization::ConstantPropagation(ir_block);
Optimization::DeadCodeElimination(ir_block);
Optimization::IdentityRemovalPass(ir_block);
fmt::print("Optimized IR:\n");
fmt::print("{}\n", IR::DumpBlock(ir_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 std::nullopt;
char* endptr;
const u32 value = strtol(line.c_str(), &endptr, 16);
if (line.c_str() + line.length() != endptr)
return std::nullopt;
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/thumb> <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 if (strcmp(argv[1], "t32") == 0 || strcmp(argv[1], "t16") == 0 || strcmp(argv[1], "thumb") == 0) {
PrintThumbInstruction(instruction);
} else {
fmt::print("Invalid mode: {}\nValid values: a32, a64, thumb\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 {
fmt::print("Executing in this mode not currently supported\n");
return 1;
}
}
return 0;
}

27
externals/dynarmic/tests/rand_int.h vendored Normal file
View file

@ -0,0 +1,27 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2020 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#pragma once
#include <random>
#include <type_traits>
namespace detail {
inline std::mt19937 g_rand_int_generator = [] {
std::random_device rd;
std::mt19937 mt{rd()};
return mt;
}();
} // namespace detail
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.");
std::uniform_int_distribution<T> rand(min, max);
return rand(detail::g_rand_int_generator);
}

152
externals/dynarmic/tests/rsqrt_test.cpp vendored Normal file
View file

@ -0,0 +1,152 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2021 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <catch2/benchmark/catch_benchmark.hpp>
#include <catch2/catch_test_macros.hpp>
#include <fmt/printf.h>
#include <mcl/stdint.hpp>
#include "dynarmic/common/fp/fpcr.h"
#include "dynarmic/common/fp/fpsr.h"
#include "dynarmic/common/fp/op/FPRSqrtEstimate.h"
extern "C" u32 rsqrt_inaccurate(u32);
extern "C" u32 rsqrt_full(u32);
extern "C" u32 rsqrt_full_gpr(u32);
extern "C" u32 rsqrt_full_nb(u32);
extern "C" u32 rsqrt_full_nb2(u32);
extern "C" u32 rsqrt_full_nb_gpr(u32);
extern "C" u32 rsqrt_newton(u32);
extern "C" u32 rsqrt_hack(u32);
using namespace Dynarmic;
extern "C" u32 rsqrt_fallback(u32 value) {
FP::FPCR fpcr;
FP::FPSR fpsr;
return FP::FPRSqrtEstimate(value, fpcr, fpsr);
}
extern "C" u32 _rsqrt_fallback(u32 value) {
return rsqrt_fallback(value);
}
void Test(u32 value) {
FP::FPCR fpcr;
FP::FPSR fpsr;
const u32 expect = FP::FPRSqrtEstimate(value, fpcr, fpsr);
const u32 full = rsqrt_full(value);
const u32 full_gpr = rsqrt_full_gpr(value);
const u32 newton = rsqrt_newton(value);
const u32 hack = rsqrt_hack(value);
if (expect != full || expect != full_gpr || expect != newton || expect != hack) {
fmt::print("{:08x} = {:08x} : {:08x} : {:08x} : {:08x} : {:08x}\n", value, expect, full, full_gpr, newton, hack);
REQUIRE(expect == full);
REQUIRE(expect == full_gpr);
REQUIRE(expect == newton);
REQUIRE(expect == hack);
}
}
TEST_CASE("RSqrt Tests", "[fp][.]") {
Test(0x00000000);
Test(0x80000000);
Test(0x7f8b7201);
Test(0x7f800000);
Test(0x7fc00000);
Test(0xff800000);
Test(0xffc00000);
Test(0xff800001);
for (u64 i = 0; i < 0x1'0000'0000; i++) {
const u32 value = static_cast<u32>(i);
Test(value);
}
}
TEST_CASE("Benchmark RSqrt", "[fp][.]") {
BENCHMARK("Inaccurate") {
u64 total = 0;
for (u64 i = 0; i < 0x1'0000'0000; i += 0x1234) {
const u32 value = static_cast<u32>(i);
total += rsqrt_inaccurate(value);
}
return total;
};
BENCHMARK("Full divss") {
u64 total = 0;
for (u64 i = 0; i < 0x1'0000'0000; i += 0x1234) {
const u32 value = static_cast<u32>(i);
total += rsqrt_full(value);
}
return total;
};
BENCHMARK("Full divss (GPR)") {
u64 total = 0;
for (u64 i = 0; i < 0x1'0000'0000; i += 0x1234) {
const u32 value = static_cast<u32>(i);
total += rsqrt_full_gpr(value);
}
return total;
};
BENCHMARK("Full divss (NB)") {
u64 total = 0;
for (u64 i = 0; i < 0x1'0000'0000; i += 0x1234) {
const u32 value = static_cast<u32>(i);
total += rsqrt_full_nb(value);
}
return total;
};
BENCHMARK("Full divss (NB2)") {
u64 total = 0;
for (u64 i = 0; i < 0x1'0000'0000; i += 0x1234) {
const u32 value = static_cast<u32>(i);
total += rsqrt_full_nb2(value);
}
return total;
};
BENCHMARK("Full divss (NB + GPR)") {
u64 total = 0;
for (u64 i = 0; i < 0x1'0000'0000; i += 0x1234) {
const u32 value = static_cast<u32>(i);
total += rsqrt_full_nb_gpr(value);
}
return total;
};
BENCHMARK("One Newton iteration") {
u64 total = 0;
for (u64 i = 0; i < 0x1'0000'0000; i += 0x1234) {
const u32 value = static_cast<u32>(i);
total += rsqrt_newton(value);
}
return total;
};
BENCHMARK("Ugly Hack") {
u64 total = 0;
for (u64 i = 0; i < 0x1'0000'0000; i += 0x1234) {
const u32 value = static_cast<u32>(i);
total += rsqrt_hack(value);
}
return total;
};
BENCHMARK("Softfloat") {
u64 total = 0;
for (u64 i = 0; i < 0x1'0000'0000; i += 0x1234) {
const u32 value = static_cast<u32>(i);
total += rsqrt_fallback(value);
}
return total;
};
}

303
externals/dynarmic/tests/rsqrt_test_fn.s vendored Normal file
View file

@ -0,0 +1,303 @@
.global _rsqrt_inaccurate
.global rsqrt_inaccurate
.global _rsqrt_full
.global rsqrt_full
.global _rsqrt_full_gpr
.global rsqrt_full_gpr
.global _rsqrt_full_nb
.global rsqrt_full_nb
.global _rsqrt_full_nb2
.global rsqrt_full_nb2
.global _rsqrt_full_nb_gpr
.global rsqrt_full_nb_gpr
.global _rsqrt_newton
.global rsqrt_newton
.global _rsqrt_hack
.global rsqrt_hack
.global _rsqrt_fallback
.text
.intel_syntax noprefix
.align 16
min_pos_denorm:
.long 0x00800000,0,0,0
penultimate_bit:
.long 0x00008000,0,0,0
ultimate_bit:
.long 0x00004000,0,0,0
top_mask:
.long 0xFFFF8000,0,0,0
one:
.long 0x3f800000,0,0,0
half:
.long 0x3f000000,0,0,0
one_point_five:
.long 0x3fc00000,0,0,0
magic1:
.long 0x60000000,0,0,0
magic2:
.long 0x3c000000,0,0,0
magic3:
.long 0x000047ff,0,0,0
_rsqrt_inaccurate:
rsqrt_inaccurate:
movd xmm0, edi
rsqrtss xmm0, xmm0
movd eax, xmm0
ret
_rsqrt_full:
rsqrt_full:
movd xmm0, edi
pand xmm0, [rip + top_mask]
por xmm0, [rip + penultimate_bit]
vcmpngt_uqss xmm1, xmm0, [rip + min_pos_denorm]
ptest xmm1, xmm1
jnz rsqrt_full_bad
sqrtss xmm0, xmm0
movd xmm1, [rip + one]
divss xmm1, xmm0
paddd xmm1, [rip + ultimate_bit]
pand xmm1, [rip + top_mask]
movd eax, xmm1
ret
_rsqrt_full_gpr:
rsqrt_full_gpr:
movd eax, xmm0 # Emulate regalloc mov
mov eax, edi
and eax, 0xFFFF8000
or eax, 0x00008000
movd xmm0, eax
vcmpngt_uqss xmm1, xmm0, [rip + min_pos_denorm]
ptest xmm1, xmm1
jnz rsqrt_full_bad
sqrtss xmm0, xmm0
movd xmm1, [rip + one]
divss xmm1, xmm0
movd eax, xmm1
add eax, 0x00004000
and eax, 0xffff8000
movd xmm0, eax # Emulate regalloc mov
ret
_rsqrt_full_nb2:
rsqrt_full_nb2:
movd xmm0, edi
pand xmm0, [rip + top_mask]
por xmm0, [rip + penultimate_bit]
ucomiss xmm0, [rip + min_pos_denorm]
jna rsqrt_full_bad_new1
sqrtss xmm0, xmm0
movd xmm1, [rip + one]
divss xmm1, xmm0
paddd xmm1, [rip + ultimate_bit]
pand xmm1, [rip + top_mask]
movd eax, xmm1
ret
_rsqrt_full_nb:
rsqrt_full_nb:
movd xmm0, edi
pand xmm0, [rip + top_mask]
por xmm0, [rip + penultimate_bit]
vcmpngt_uqss xmm1, xmm0, [rip + min_pos_denorm]
ptest xmm1, xmm1
jnz rsqrt_full_bad_new1
sqrtss xmm0, xmm0
movd xmm1, [rip + one]
divss xmm1, xmm0
paddd xmm1, [rip + ultimate_bit]
pand xmm1, [rip + top_mask]
movd eax, xmm1
ret
rsqrt_full_bad_new1:
cmp edi, 0x00800000
jb rsqrt_full_bad_new_fallback1
movd xmm0, edi
rsqrtss xmm1, xmm0
ucomiss xmm1, xmm1
jp rsqrt_full_bad_new1_nan
movd eax, xmm1
ret
rsqrt_full_bad_new_fallback1:
call _rsqrt_fallback
ret
rsqrt_full_bad_new1_nan:
ucomiss xmm0, xmm0
jp rsqrt_full_bad_new1_nan_ret
mov eax, 0x7FC00000
ret
rsqrt_full_bad_new1_nan_ret:
ret
_rsqrt_full_nb_gpr:
rsqrt_full_nb_gpr:
movd eax, xmm0 # Emulate regalloc mov
mov eax, edi
and eax, 0xFFFF8000
or eax, 0x00008000
movd xmm0, eax
vcmpngt_uqss xmm1, xmm0, [rip + min_pos_denorm]
ptest xmm1, xmm1
jnz rsqrt_full_bad_new2
sqrtss xmm0, xmm0
movd xmm1, [rip + one]
divss xmm1, xmm0
movd eax, xmm1
add eax, 0x00004000
and eax, 0xffff8000
movd xmm0, eax # Emulate regalloc mov
ret
rsqrt_full_bad_new2:
cmp edi, 0x00800000
jb rsqrt_full_bad_new_fallback2
movd xmm0, edi
rsqrtss xmm1, xmm0
test edi, edi
js rsqrt_full_bad_new2_nan
movd eax, xmm1
ret
rsqrt_full_bad_new_fallback2:
call _rsqrt_fallback
ret
rsqrt_full_bad_new2_nan:
mov eax, 0x7FC00000
ret
rsqrt_full_bad:
xorps xmm1, xmm1
movd xmm0, edi
ucomiss xmm0, xmm1
jp rsqrt_full_nan
je rsqrt_full_zero
jc rsqrt_full_neg
cmp edi, 0x7F800000
je rsqrt_full_inf
# TODO: Full Denormal Implementation
call _rsqrt_fallback
ret
rsqrt_full_neg:
mov eax, 0x7FC00000
ret
rsqrt_full_inf:
xor eax, eax
ret
rsqrt_full_nan:
mov eax, edi
or eax, 0x00400000
ret
rsqrt_full_zero:
mov eax, edi
or eax, 0x7F800000
ret
_rsqrt_newton:
rsqrt_newton:
movd xmm0, edi
pand xmm0, [rip + top_mask]
por xmm0, [rip + penultimate_bit]
vcmpngt_uqss xmm1, xmm0, [rip + min_pos_denorm]
ptest xmm1, xmm1
jnz rsqrt_full_bad
rsqrtps xmm1, xmm0
mulss xmm0, [rip + half]
vmulss xmm2, xmm1, xmm1
mulss xmm2, xmm0
movaps xmm0, [rip + one_point_five]
subss xmm0, xmm2
mulss xmm0, xmm1
paddd xmm0, [rip + ultimate_bit]
pand xmm0, [rip + top_mask]
movd eax, xmm0
ret
_rsqrt_hack:
rsqrt_hack:
movd xmm9, edi
vpand xmm0, xmm9, [rip + top_mask]
por xmm0, [rip + penultimate_bit]
# detect NaNs, negatives, zeros, denormals and infinities
vcmpngt_uqss xmm1, xmm0, [rip + min_pos_denorm]
ptest xmm1, xmm1
jnz rsqrt_full_bad
# calculate x64 estimate
rsqrtps xmm0, xmm0
# calculate correction factor
vpslld xmm1, xmm9, 8
vpsrad xmm2, xmm1, 31
paddd xmm1, [rip + magic1]
pcmpgtd xmm1, [rip + magic2]
pxor xmm1, xmm2
movaps xmm2, [rip + magic3]
psubd xmm2, xmm1
# correct x64 estimate
paddd xmm0, xmm2
pand xmm0, [rip + top_mask]
movd eax, xmm0
ret

View file

@ -0,0 +1,700 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2022 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <algorithm>
#include <array>
#include <cstdio>
#include <cstdlib>
#include <functional>
#include <limits>
#include <optional>
#include <tuple>
#include <vector>
#include <mcl/bit/swap.hpp>
#include <mcl/macro/architecture.hpp>
#include <mcl/stdint.hpp>
#include "./A32/testenv.h"
#include "./A64/testenv.h"
#include "./fuzz_util.h"
#include "./rand_int.h"
#include "dynarmic/common/fp/fpcr.h"
#include "dynarmic/common/fp/fpsr.h"
#include "dynarmic/frontend/A32/ITState.h"
#include "dynarmic/frontend/A32/a32_location_descriptor.h"
#include "dynarmic/frontend/A32/a32_types.h"
#include "dynarmic/frontend/A32/translate/a32_translate.h"
#include "dynarmic/frontend/A64/a64_location_descriptor.h"
#include "dynarmic/frontend/A64/a64_types.h"
#include "dynarmic/frontend/A64/translate/a64_translate.h"
#include "dynarmic/interface/A32/a32.h"
#include "dynarmic/interface/A64/a64.h"
#include "dynarmic/ir/basic_block.h"
#include "dynarmic/ir/location_descriptor.h"
#include "dynarmic/ir/opcodes.h"
// Must be declared last for all necessary operator<< to be declared prior to this.
#include <fmt/format.h>
#include <fmt/ostream.h>
constexpr bool mask_fpsr_cum_bits = true;
namespace {
using namespace Dynarmic;
bool ShouldTestInst(IR::Block& block) {
if (auto terminal = block.GetTerminal(); boost::get<IR::Term::Interpret>(&terminal)) {
return false;
}
for (const auto& ir_inst : block) {
switch (ir_inst.GetOpcode()) {
// A32
case IR::Opcode::A32GetFpscr:
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:
// A64
case IR::Opcode::A64ExceptionRaised:
case IR::Opcode::A64CallSupervisor:
case IR::Opcode::A64DataCacheOperationRaised:
case IR::Opcode::A64GetCNTPCT:
// Unimplemented
case IR::Opcode::SignedSaturatedAdd8:
case IR::Opcode::SignedSaturatedAdd16:
case IR::Opcode::SignedSaturatedAdd32:
case IR::Opcode::SignedSaturatedAdd64:
case IR::Opcode::SignedSaturatedDoublingMultiplyReturnHigh16:
case IR::Opcode::SignedSaturatedDoublingMultiplyReturnHigh32:
case IR::Opcode::SignedSaturatedSub8:
case IR::Opcode::SignedSaturatedSub16:
case IR::Opcode::SignedSaturatedSub32:
case IR::Opcode::SignedSaturatedSub64:
case IR::Opcode::UnsignedSaturatedAdd8:
case IR::Opcode::UnsignedSaturatedAdd16:
case IR::Opcode::UnsignedSaturatedAdd32:
case IR::Opcode::UnsignedSaturatedAdd64:
case IR::Opcode::UnsignedSaturatedSub8:
case IR::Opcode::UnsignedSaturatedSub16:
case IR::Opcode::UnsignedSaturatedSub32:
case IR::Opcode::UnsignedSaturatedSub64:
case IR::Opcode::VectorMaxS64:
case IR::Opcode::VectorMaxU64:
case IR::Opcode::VectorMinS64:
case IR::Opcode::VectorMinU64:
case IR::Opcode::VectorMultiply64:
case IR::Opcode::SM4AccessSubstitutionBox:
// Half-prec conversions
case IR::Opcode::FPHalfToFixedS16:
case IR::Opcode::FPHalfToFixedS32:
case IR::Opcode::FPHalfToFixedS64:
case IR::Opcode::FPHalfToFixedU16:
case IR::Opcode::FPHalfToFixedU32:
case IR::Opcode::FPHalfToFixedU64:
// Half-precision
case IR::Opcode::FPAbs16:
case IR::Opcode::FPMulAdd16:
case IR::Opcode::FPMulSub16:
case IR::Opcode::FPNeg16:
case IR::Opcode::FPRecipEstimate16:
case IR::Opcode::FPRecipExponent16:
case IR::Opcode::FPRecipStepFused16:
case IR::Opcode::FPRoundInt16:
case IR::Opcode::FPRSqrtEstimate16:
case IR::Opcode::FPRSqrtStepFused16:
case IR::Opcode::FPVectorAbs16:
case IR::Opcode::FPVectorEqual16:
case IR::Opcode::FPVectorMulAdd16:
case IR::Opcode::FPVectorNeg16:
case IR::Opcode::FPVectorRecipEstimate16:
case IR::Opcode::FPVectorRecipStepFused16:
case IR::Opcode::FPVectorRoundInt16:
case IR::Opcode::FPVectorRSqrtEstimate16:
case IR::Opcode::FPVectorRSqrtStepFused16:
case IR::Opcode::FPVectorToSignedFixed16:
case IR::Opcode::FPVectorToUnsignedFixed16:
case IR::Opcode::FPVectorFromHalf32:
case IR::Opcode::FPVectorToHalf32:
return false;
default:
continue;
}
}
return true;
}
bool ShouldTestA32Inst(u32 instruction, u32 pc, bool is_thumb, bool is_last_inst, A32::ITState it_state = {}) {
const A32::LocationDescriptor location = A32::LocationDescriptor{pc, {}, {}}.SetTFlag(is_thumb).SetIT(it_state);
IR::Block block{location};
const bool should_continue = A32::TranslateSingleInstruction(block, location, instruction);
if (!should_continue && !is_last_inst) {
return false;
}
return ShouldTestInst(block);
}
bool ShouldTestA64Inst(u32 instruction, u64 pc, bool is_last_inst) {
const A64::LocationDescriptor location = A64::LocationDescriptor{pc, {}};
IR::Block block{location};
const bool should_continue = A64::TranslateSingleInstruction(block, location, instruction);
if (!should_continue && !is_last_inst) {
return false;
}
return ShouldTestInst(block);
}
u32 GenRandomArmInst(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 "dynarmic/frontend/A32/decoder/arm.inc"
#include "dynarmic/frontend/A32/decoder/asimd.inc"
#include "dynarmic/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",
// 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 (ShouldTestA32Inst(inst, pc, false, is_last_inst)) {
return inst;
}
}
}
std::vector<u16> GenRandomThumbInst(u32 pc, bool is_last_inst, A32::ITState it_state = {}) {
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 "dynarmic/frontend/A32/decoder/thumb16.inc"
#include "dynarmic/frontend/A32/decoder/thumb32.inc"
#undef INST
};
const std::vector<std::tuple<std::string, const char*>> vfp_list{
#define INST(fn, name, bitstring) {#fn, bitstring},
#include "dynarmic/frontend/A32/decoder/vfp.inc"
#undef INST
};
const std::vector<std::tuple<std::string, const char*>> asimd_list{
#define INST(fn, name, bitstring) {#fn, bitstring},
#include "dynarmic/frontend/A32/decoder/asimd.inc"
#undef INST
};
std::vector<InstructionGenerator> generators;
std::vector<InstructionGenerator> invalid;
// List of instructions not to test
static constexpr std::array do_not_test{
"thumb16_BKPT",
"thumb16_IT",
// Exclusive load/stores
"thumb32_LDREX",
"thumb32_LDREXB",
"thumb32_LDREXD",
"thumb32_LDREXH",
"thumb32_STREX",
"thumb32_STREXB",
"thumb32_STREXD",
"thumb32_STREXH",
// Coprocessor
"thumb32_CDP",
"thumb32_LDC",
"thumb32_MCR",
"thumb32_MCRR",
"thumb32_MRC",
"thumb32_MRRC",
"thumb32_STC",
};
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});
}
for (const auto& [fn, bs] : vfp_list) {
std::string bitstring = bs;
if (bitstring.substr(0, 4) == "cccc" || bitstring.substr(0, 4) == "----") {
bitstring.replace(0, 4, "1110");
}
if (std::find(do_not_test.begin(), do_not_test.end(), fn) != do_not_test.end()) {
invalid.emplace_back(InstructionGenerator{bitstring.c_str()});
continue;
}
generators.emplace_back(InstructionGenerator{bitstring.c_str()});
}
for (const auto& [fn, bs] : asimd_list) {
std::string bitstring = bs;
if (bitstring.substr(0, 7) == "1111001") {
const char U = bitstring[7];
bitstring.replace(0, 8, "111-1111");
bitstring[3] = U;
} else if (bitstring.substr(0, 8) == "11110100") {
bitstring.replace(0, 8, "11111001");
} else {
ASSERT_FALSE("Unhandled ASIMD instruction: {} {}", fn, bs);
}
if (std::find(do_not_test.begin(), do_not_test.end(), fn) != do_not_test.end()) {
invalid.emplace_back(InstructionGenerator{bitstring.c_str()});
continue;
}
generators.emplace_back(InstructionGenerator{bitstring.c_str()});
}
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();
const bool is_four_bytes = (inst >> 16) != 0;
if (ShouldTestA32Inst(is_four_bytes ? mcl::bit::swap_halves_32(inst) : inst, pc, true, is_last_inst, it_state)) {
if (is_four_bytes)
return {static_cast<u16>(inst >> 16), static_cast<u16>(inst)};
return {static_cast<u16>(inst)};
}
}
}
u32 GenRandomA64Inst(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 "dynarmic/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{
// 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 (ShouldTestA64Inst(inst, pc, is_last_inst)) {
return inst;
}
}
}
template<typename TestEnv>
Dynarmic::A32::UserConfig GetA32UserConfig(TestEnv& testenv, bool noopt) {
Dynarmic::A32::UserConfig user_config;
user_config.optimizations &= ~OptimizationFlag::FastDispatch;
user_config.callbacks = &testenv;
if (noopt) {
user_config.optimizations = no_optimizations;
}
return user_config;
}
template<size_t num_jit_reruns = 1, typename TestEnv>
void RunTestInstance(Dynarmic::A32::Jit& jit,
TestEnv& jit_env,
const std::array<u32, 16>& regs,
const std::array<u32, 64>& vecs,
const std::vector<typename TestEnv::InstructionType>& instructions,
const u32 cpsr,
const u32 fpscr,
const size_t ticks_left) {
const u32 initial_pc = regs[15];
const u32 num_words = initial_pc / sizeof(typename TestEnv::InstructionType);
const u32 code_mem_size = num_words + static_cast<u32>(instructions.size());
fmt::print("instructions:");
for (auto instruction : instructions) {
if constexpr (sizeof(decltype(instruction)) == 2) {
fmt::print(" {:04x}", instruction);
} else {
fmt::print(" {:08x}", instruction);
}
}
fmt::print("\n");
fmt::print("initial_regs:");
for (u32 i : regs) {
fmt::print(" {:08x}", i);
}
fmt::print("\n");
fmt::print("initial_vecs:");
for (u32 i : vecs) {
fmt::print(" {:08x}", i);
}
fmt::print("\n");
fmt::print("initial_cpsr: {:08x}\n", cpsr);
fmt::print("initial_fpcr: {:08x}\n", fpscr);
jit.ClearCache();
for (size_t jit_rerun_count = 0; jit_rerun_count < num_jit_reruns; ++jit_rerun_count) {
jit_env.code_mem.resize(code_mem_size);
std::fill(jit_env.code_mem.begin(), jit_env.code_mem.end(), TestEnv::infinite_loop);
std::copy(instructions.begin(), instructions.end(), jit_env.code_mem.begin() + num_words);
jit_env.PadCodeMem();
jit_env.modified_memory.clear();
jit_env.interrupts.clear();
jit.Regs() = regs;
jit.ExtRegs() = vecs;
jit.SetFpscr(fpscr);
jit.SetCpsr(cpsr);
jit_env.ticks_left = ticks_left;
jit.Run();
}
fmt::print("final_regs:");
for (u32 i : jit.Regs()) {
fmt::print(" {:08x}", i);
}
fmt::print("\n");
fmt::print("final_vecs:");
for (u32 i : jit.ExtRegs()) {
fmt::print(" {:08x}", i);
}
fmt::print("\n");
fmt::print("final_cpsr: {:08x}\n", jit.Cpsr());
fmt::print("final_fpsr: {:08x}\n", mask_fpsr_cum_bits ? jit.Fpscr() & 0xffffff00 : jit.Fpscr());
fmt::print("mod_mem: ");
for (auto [addr, value] : jit_env.modified_memory) {
fmt::print("{:08x}:{:02x} ", addr, value);
}
fmt::print("\n");
fmt::print("interrupts:\n");
for (const auto& i : jit_env.interrupts) {
std::puts(i.c_str());
}
fmt::print("===\n");
}
Dynarmic::A64::UserConfig GetA64UserConfig(A64TestEnv& jit_env, bool noopt) {
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;
if (noopt) {
jit_user_config.optimizations = no_optimizations;
}
return jit_user_config;
}
template<size_t num_jit_reruns = 2>
void RunTestInstance(Dynarmic::A64::Jit& jit,
A64TestEnv& jit_env,
const std::array<u64, 31>& regs,
const std::array<std::array<u64, 2>, 32>& vecs,
const std::vector<u32>& instructions,
const u32 pstate,
const u32 fpcr,
const u64 initial_sp,
const u64 start_address,
const size_t ticks_left) {
jit.ClearCache();
for (size_t jit_rerun_count = 0; jit_rerun_count < num_jit_reruns; ++jit_rerun_count) {
jit_env.code_mem = instructions;
jit_env.code_mem.emplace_back(0x14000000); // B .
jit_env.code_mem_start_address = start_address;
jit_env.modified_memory.clear();
jit_env.interrupts.clear();
jit.SetRegisters(regs);
jit.SetVectors(vecs);
jit.SetPC(start_address);
jit.SetSP(initial_sp);
jit.SetFpcr(fpcr);
jit.SetFpsr(0);
jit.SetPstate(pstate);
jit.ClearCache();
jit_env.ticks_left = ticks_left;
jit.Run();
}
fmt::print("instructions:");
for (u32 instruction : instructions) {
fmt::print(" {:08x}", instruction);
}
fmt::print("\n");
fmt::print("initial_regs:");
for (u64 i : regs) {
fmt::print(" {:016x}", i);
}
fmt::print("\n");
fmt::print("initial_vecs:");
for (auto i : vecs) {
fmt::print(" {:016x}:{:016x}", i[0], i[1]);
}
fmt::print("\n");
fmt::print("initial_sp: {:016x}\n", initial_sp);
fmt::print("initial_pstate: {:08x}\n", pstate);
fmt::print("initial_fpcr: {:08x}\n", fpcr);
fmt::print("final_regs:");
for (u64 i : jit.GetRegisters()) {
fmt::print(" {:016x}", i);
}
fmt::print("\n");
fmt::print("final_vecs:");
for (auto i : jit.GetVectors()) {
fmt::print(" {:016x}:{:016x}", i[0], i[1]);
}
fmt::print("\n");
fmt::print("final_sp: {:016x}\n", jit.GetSP());
fmt::print("final_pc: {:016x}\n", jit.GetPC());
fmt::print("final_pstate: {:08x}\n", jit.GetPstate());
fmt::print("final_fpcr: {:08x}\n", jit.GetFpcr());
fmt::print("final_qc : {}\n", FP::FPSR{jit.GetFpsr()}.QC());
fmt::print("mod_mem:");
for (auto [addr, value] : jit_env.modified_memory) {
fmt::print(" {:08x}:{:02x}", addr, value);
}
fmt::print("\n");
fmt::print("interrupts:\n");
for (const auto& i : jit_env.interrupts) {
std::puts(i.c_str());
}
fmt::print("===\n");
}
} // Anonymous namespace
void TestThumb(size_t num_instructions, size_t num_iterations, bool noopt) {
ThumbTestEnv jit_env{};
Dynarmic::A32::Jit jit{GetA32UserConfig(jit_env, noopt)};
std::array<u32, 16> regs;
std::array<u32, 64> ext_reg;
std::vector<u16> instructions;
for (size_t iteration = 0; iteration < num_iterations; ++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)); });
const u32 start_address = 100;
const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x1F0;
const u32 fpcr = RandomFpcr();
instructions.clear();
for (size_t i = 0; i < num_instructions; ++i) {
const auto inst = GenRandomThumbInst(static_cast<u32>(start_address + 2 * instructions.size()), i == num_instructions - 1);
instructions.insert(instructions.end(), inst.begin(), inst.end());
}
regs[15] = start_address;
RunTestInstance(jit, jit_env, regs, ext_reg, instructions, cpsr, fpcr, num_instructions);
}
}
void TestArm(size_t num_instructions, size_t num_iterations, bool noopt) {
ArmTestEnv jit_env{};
Dynarmic::A32::Jit jit{GetA32UserConfig(jit_env, noopt)};
std::array<u32, 16> regs;
std::array<u32, 64> ext_reg;
std::vector<u32> instructions;
for (size_t iteration = 0; iteration < num_iterations; ++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)); });
const u32 start_address = 100;
const u32 cpsr = (RandInt<u32>(0, 0xF) << 28);
const u32 fpcr = RandomFpcr();
instructions.clear();
for (size_t i = 0; i < num_instructions; ++i) {
instructions.emplace_back(GenRandomArmInst(static_cast<u32>(start_address + 4 * instructions.size()), i == num_instructions - 1));
}
regs[15] = start_address;
RunTestInstance(jit, jit_env, regs, ext_reg, instructions, cpsr, fpcr, num_instructions);
}
}
void TestA64(size_t num_instructions, size_t num_iterations, bool noopt) {
A64TestEnv jit_env{};
Dynarmic::A64::Jit jit{GetA64UserConfig(jit_env, noopt)};
std::array<u64, 31> regs;
std::array<std::array<u64, 2>, 32> vecs;
std::vector<u32> instructions;
for (size_t iteration = 0; iteration < num_iterations; ++iteration) {
std::generate(regs.begin(), regs.end(), [] { return RandInt<u64>(0, ~u64(0)); });
std::generate(vecs.begin(), vecs.end(), RandomVector);
const u32 start_address = 100;
const u32 pstate = (RandInt<u32>(0, 0xF) << 28);
const u32 fpcr = RandomFpcr();
const u64 initial_sp = RandInt<u64>(0x30'0000'0000, 0x40'0000'0000) * 4;
instructions.clear();
for (size_t i = 0; i < num_instructions; ++i) {
instructions.emplace_back(GenRandomA64Inst(static_cast<u32>(start_address + 4 * instructions.size()), i == num_instructions - 1));
}
RunTestInstance(jit, jit_env, regs, vecs, instructions, pstate, fpcr, initial_sp, start_address, num_instructions);
}
}
static std::optional<size_t> str2sz(char const* s) {
char* end = nullptr;
errno = 0;
const long l = std::strtol(s, &end, 10);
if (errno == ERANGE || l < 0) {
return std::nullopt;
}
if (*s == '\0' || *end != '\0') {
return std::nullopt;
}
return static_cast<size_t>(l);
}
int main(int argc, char* argv[]) {
if (argc < 5 || argc > 6) {
fmt::print("Usage: {} <thumb|arm|a64> <seed> <instruction_count> <iteration_count> [noopt]\n", argv[0]);
return 1;
}
const auto seed = str2sz(argv[2]);
const auto instruction_count = str2sz(argv[3]);
const auto iterator_count = str2sz(argv[4]);
const bool noopt = argc == 6 && (strcmp(argv[5], "noopt") == 0);
if (!seed || !instruction_count || !iterator_count) {
fmt::print("invalid numeric arguments\n");
return 1;
}
detail::g_rand_int_generator.seed(static_cast<std::mt19937::result_type>(*seed));
if (strcmp(argv[1], "thumb") == 0) {
TestThumb(*instruction_count, *iterator_count, noopt);
} else if (strcmp(argv[1], "arm") == 0) {
TestArm(*instruction_count, *iterator_count, noopt);
} else if (strcmp(argv[1], "a64") == 0) {
TestA64(*instruction_count, *iterator_count, noopt);
} else {
fmt::print("unrecognized instruction class\n");
return 1;
}
return 0;
}

427
externals/dynarmic/tests/test_reader.cpp vendored Normal file
View file

@ -0,0 +1,427 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2023 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <array>
#include <iostream>
#include <string>
#include <string_view>
#include <vector>
#include <fmt/format.h>
#include <mcl/stdint.hpp>
#include "./A32/testenv.h"
#include "./A64/testenv.h"
#include "dynarmic/common/fp/fpsr.h"
#include "dynarmic/interface/A32/a32.h"
#include "dynarmic/interface/A64/a64.h"
const bool mask_fpsr_cum_bits = true;
using namespace Dynarmic;
void SkipWhitespace(std::string_view& sv) {
auto nextpos{sv.find_first_not_of(' ')};
if (nextpos != std::string::npos) {
sv.remove_prefix(nextpos);
}
}
void SkipHeader(std::string_view& sv) {
sv.remove_prefix(sv.find_first_of(':') + 1);
SkipWhitespace(sv);
}
std::string_view NextToken(std::string_view& sv) {
auto nextpos{sv.find_first_of(' ')};
auto tok{sv.substr(0, nextpos)};
sv.remove_prefix(nextpos == std::string::npos ? sv.size() : nextpos);
SkipWhitespace(sv);
return tok;
}
u64 ParseHex(std::string_view hex) {
u64 result = 0;
while (!hex.empty()) {
result <<= 4;
if (hex.front() >= '0' && hex.front() <= '9') {
result += hex.front() - '0';
} else if (hex.front() >= 'a' && hex.front() <= 'f') {
result += hex.front() - 'a' + 0xA;
} else if (hex.front() >= 'A' && hex.front() <= 'F') {
result += hex.front() - 'A' + 0xA;
} else if (hex.front() == ':') {
return result;
} else {
fmt::print("Character {} is not a valid hex character\n", hex.front());
}
hex.remove_prefix(1);
}
return result;
}
template<typename TestEnv>
Dynarmic::A32::UserConfig GetA32UserConfig(TestEnv& testenv, bool noopt) {
Dynarmic::A32::UserConfig user_config;
user_config.optimizations &= ~OptimizationFlag::FastDispatch;
user_config.callbacks = &testenv;
user_config.very_verbose_debugging_output = true;
if (noopt) {
user_config.optimizations = no_optimizations;
}
return user_config;
}
template<size_t num_jit_reruns = 1, typename TestEnv>
void RunTestInstance(Dynarmic::A32::Jit& jit,
TestEnv& jit_env,
const std::array<u32, 16>& regs,
const std::array<u32, 64>& vecs,
const std::vector<typename TestEnv::InstructionType>& instructions,
const u32 cpsr,
const u32 fpscr,
const size_t ticks_left) {
const u32 initial_pc = regs[15];
const u32 num_words = initial_pc / sizeof(typename TestEnv::InstructionType);
const u32 code_mem_size = num_words + static_cast<u32>(instructions.size());
jit.ClearCache();
for (size_t jit_rerun_count = 0; jit_rerun_count < num_jit_reruns; ++jit_rerun_count) {
jit_env.code_mem.resize(code_mem_size);
std::fill(jit_env.code_mem.begin(), jit_env.code_mem.end(), TestEnv::infinite_loop);
std::copy(instructions.begin(), instructions.end(), jit_env.code_mem.begin() + num_words);
jit_env.PadCodeMem();
jit_env.modified_memory.clear();
jit_env.interrupts.clear();
jit.Regs() = regs;
jit.ExtRegs() = vecs;
jit.SetFpscr(fpscr);
jit.SetCpsr(cpsr);
jit_env.ticks_left = ticks_left;
jit.Run();
}
fmt::print("instructions:");
for (auto instruction : instructions) {
if constexpr (sizeof(decltype(instruction)) == 2) {
fmt::print(" {:04x}", instruction);
} else {
fmt::print(" {:08x}", instruction);
}
}
fmt::print("\n");
fmt::print("initial_regs:");
for (u32 i : regs) {
fmt::print(" {:08x}", i);
}
fmt::print("\n");
fmt::print("initial_vecs:");
for (u32 i : vecs) {
fmt::print(" {:08x}", i);
}
fmt::print("\n");
fmt::print("initial_cpsr: {:08x}\n", cpsr);
fmt::print("initial_fpcr: {:08x}\n", fpscr);
fmt::print("final_regs:");
for (u32 i : jit.Regs()) {
fmt::print(" {:08x}", i);
}
fmt::print("\n");
fmt::print("final_vecs:");
for (u32 i : jit.ExtRegs()) {
fmt::print(" {:08x}", i);
}
fmt::print("\n");
fmt::print("final_cpsr: {:08x}\n", jit.Cpsr());
fmt::print("final_fpsr: {:08x}\n", mask_fpsr_cum_bits ? jit.Fpscr() & 0xffffff00 : jit.Fpscr());
fmt::print("mod_mem: ");
for (auto [addr, value] : jit_env.modified_memory) {
fmt::print("{:08x}:{:02x} ", addr, value);
}
fmt::print("\n");
fmt::print("interrupts:\n");
for (const auto& i : jit_env.interrupts) {
std::puts(i.c_str());
}
fmt::print("===\n");
}
A64::UserConfig GetA64UserConfig(A64TestEnv& jit_env, bool noopt) {
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;
jit_user_config.very_verbose_debugging_output = true;
if (noopt) {
jit_user_config.optimizations = no_optimizations;
}
return jit_user_config;
}
template<size_t num_jit_reruns = 1>
void RunTestInstance(A64::Jit& jit,
A64TestEnv& jit_env,
const std::array<u64, 31>& regs,
const std::array<std::array<u64, 2>, 32>& vecs,
const std::vector<u32>& instructions,
const u32 pstate,
const u32 fpcr,
const u64 initial_sp,
const u64 start_address,
const size_t ticks_left) {
jit.ClearCache();
for (size_t jit_rerun_count = 0; jit_rerun_count < num_jit_reruns; ++jit_rerun_count) {
jit_env.code_mem = instructions;
jit_env.code_mem.emplace_back(0x14000000); // B .
jit_env.code_mem_start_address = start_address;
jit_env.modified_memory.clear();
jit_env.interrupts.clear();
jit.SetRegisters(regs);
jit.SetVectors(vecs);
jit.SetPC(start_address);
jit.SetSP(initial_sp);
jit.SetFpcr(fpcr);
jit.SetFpsr(0);
jit.SetPstate(pstate);
jit.ClearCache();
jit_env.ticks_left = ticks_left;
jit.Run();
}
fmt::print("instructions:");
for (u32 instruction : instructions) {
fmt::print(" {:08x}", instruction);
}
fmt::print("\n");
fmt::print("initial_regs:");
for (u64 i : regs) {
fmt::print(" {:016x}", i);
}
fmt::print("\n");
fmt::print("initial_vecs:");
for (auto i : vecs) {
fmt::print(" {:016x}:{:016x}", i[0], i[1]);
}
fmt::print("\n");
fmt::print("initial_sp: {:016x}\n", initial_sp);
fmt::print("initial_pstate: {:08x}\n", pstate);
fmt::print("initial_fpcr: {:08x}\n", fpcr);
fmt::print("final_regs:");
for (u64 i : jit.GetRegisters()) {
fmt::print(" {:016x}", i);
}
fmt::print("\n");
fmt::print("final_vecs:");
for (auto i : jit.GetVectors()) {
fmt::print(" {:016x}:{:016x}", i[0], i[1]);
}
fmt::print("\n");
fmt::print("final_sp: {:016x}\n", jit.GetSP());
fmt::print("final_pc: {:016x}\n", jit.GetPC());
fmt::print("final_pstate: {:08x}\n", jit.GetPstate());
fmt::print("final_fpcr: {:08x}\n", jit.GetFpcr());
fmt::print("final_qc : {}\n", FP::FPSR{jit.GetFpsr()}.QC());
fmt::print("mod_mem:");
for (auto [addr, value] : jit_env.modified_memory) {
fmt::print(" {:08x}:{:02x}", addr, value);
}
fmt::print("\n");
fmt::print("interrupts:\n");
for (const auto& i : jit_env.interrupts) {
std::puts(i.c_str());
}
fmt::print("===\n");
}
void RunThumb(bool noopt) {
std::array<u32, 16> initial_regs{};
std::array<u32, 64> initial_vecs{};
std::vector<u16> instructions{};
u32 initial_cpsr = 0;
u32 initial_fpcr = 0;
std::string line;
while (std::getline(std::cin, line)) {
std::string_view sv{line};
if (sv.starts_with("instructions:")) {
SkipHeader(sv);
while (!sv.empty()) {
instructions.emplace_back((u16)ParseHex(NextToken(sv)));
}
} else if (sv.starts_with("initial_regs:")) {
SkipHeader(sv);
for (size_t i = 0; i < initial_regs.size(); ++i) {
initial_regs[i] = (u32)ParseHex(NextToken(sv));
}
} else if (sv.starts_with("initial_vecs:")) {
SkipHeader(sv);
for (size_t i = 0; i < initial_vecs.size(); ++i) {
initial_vecs[i] = (u32)ParseHex(NextToken(sv));
}
} else if (sv.starts_with("initial_cpsr:")) {
SkipHeader(sv);
initial_cpsr = (u32)ParseHex(NextToken(sv));
} else if (sv.starts_with("initial_fpcr:")) {
SkipHeader(sv);
initial_fpcr = (u32)ParseHex(NextToken(sv));
}
}
ThumbTestEnv jit_env{};
A32::Jit jit{GetA32UserConfig(jit_env, noopt)};
RunTestInstance(jit,
jit_env,
initial_regs,
initial_vecs,
instructions,
initial_cpsr,
initial_fpcr,
instructions.size());
}
void RunArm(bool noopt) {
std::array<u32, 16> initial_regs{};
std::array<u32, 64> initial_vecs{};
std::vector<u32> instructions{};
u32 initial_cpsr = 0;
u32 initial_fpcr = 0;
std::string line;
while (std::getline(std::cin, line)) {
std::string_view sv{line};
if (sv.starts_with("instructions:")) {
SkipHeader(sv);
while (!sv.empty()) {
instructions.emplace_back((u32)ParseHex(NextToken(sv)));
}
} else if (sv.starts_with("initial_regs:")) {
SkipHeader(sv);
for (size_t i = 0; i < initial_regs.size(); ++i) {
initial_regs[i] = (u32)ParseHex(NextToken(sv));
}
} else if (sv.starts_with("initial_vecs:")) {
SkipHeader(sv);
for (size_t i = 0; i < initial_vecs.size(); ++i) {
initial_vecs[i] = (u32)ParseHex(NextToken(sv));
}
} else if (sv.starts_with("initial_cpsr:")) {
SkipHeader(sv);
initial_cpsr = (u32)ParseHex(NextToken(sv));
} else if (sv.starts_with("initial_fpcr:")) {
SkipHeader(sv);
initial_fpcr = (u32)ParseHex(NextToken(sv));
}
}
ArmTestEnv jit_env{};
A32::Jit jit{GetA32UserConfig(jit_env, noopt)};
RunTestInstance(jit,
jit_env,
initial_regs,
initial_vecs,
instructions,
initial_cpsr,
initial_fpcr,
instructions.size());
}
void RunA64(bool noopt) {
std::array<u64, 31> initial_regs{};
std::array<std::array<u64, 2>, 32> initial_vecs{};
std::vector<u32> instructions{};
u32 initial_pstate = 0;
u32 initial_fpcr = 0;
u64 initial_sp = 0;
u64 start_address = 100;
std::string line;
while (std::getline(std::cin, line)) {
std::string_view sv{line};
if (sv.starts_with("instructions:")) {
SkipHeader(sv);
while (!sv.empty()) {
instructions.emplace_back((u32)ParseHex(NextToken(sv)));
}
} else if (sv.starts_with("initial_regs:")) {
SkipHeader(sv);
for (size_t i = 0; i < initial_regs.size(); ++i) {
initial_regs[i] = ParseHex(NextToken(sv));
}
} else if (sv.starts_with("initial_vecs:")) {
SkipHeader(sv);
for (size_t i = 0; i < initial_vecs.size(); ++i) {
auto tok{NextToken(sv)};
initial_vecs[i][0] = ParseHex(tok);
tok.remove_prefix(tok.find_first_of(':') + 1);
initial_vecs[i][1] = ParseHex(tok);
}
} else if (sv.starts_with("initial_sp:")) {
SkipHeader(sv);
initial_sp = ParseHex(NextToken(sv));
} else if (sv.starts_with("initial_pstate:")) {
SkipHeader(sv);
initial_pstate = (u32)ParseHex(NextToken(sv));
} else if (sv.starts_with("initial_fpcr:")) {
SkipHeader(sv);
initial_fpcr = (u32)ParseHex(NextToken(sv));
}
}
A64TestEnv jit_env{};
A64::Jit jit{GetA64UserConfig(jit_env, noopt)};
RunTestInstance(jit,
jit_env,
initial_regs,
initial_vecs,
instructions,
initial_pstate,
initial_fpcr,
initial_sp,
start_address,
instructions.size());
}
int main(int argc, char** argv) {
if (argc < 2 || argc > 3) {
fmt::print("Usage: {} <thumb|arm|a64> [noopt]\n", argv[0]);
return 1;
}
const bool noopt = argc == 3 && (strcmp(argv[2], "noopt") == 0);
if (strcmp(argv[1], "thumb") == 0) {
RunThumb(noopt);
} else if (strcmp(argv[1], "arm") == 0) {
RunArm(noopt);
} else if (strcmp(argv[1], "a64") == 0) {
RunA64(noopt);
} else {
fmt::print("unrecognized instruction class\n");
return 1;
}
return 0;
}

View file

@ -0,0 +1,351 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2018 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include "./a32_unicorn.h"
#include <type_traits>
#include <mcl/assert.hpp>
#include <mcl/bit/bit_field.hpp>
#include "../A32/testenv.h"
#define CHECKED(expr) \
do { \
if (auto cerr_ = (expr)) { \
ASSERT_MSG(false, "Call " #expr " failed with error: {} ({})\n", static_cast<size_t>(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 (!testenv.IsInCodeMem(pc)) {
return;
}
if (auto cerr_ = uc_emu_start(uc, pc, END_ADDRESS, 0, 1)) {
fmt::print("uc_emu_start failed @ {:08x} (code = {:08x}) with error {} ({})", pc, *testenv.MemoryReadCode(pc), static_cast<size_t>(cerr_), uc_strerror(cerr_));
throw "A32Unicorn::Run() failure";
}
testenv.ticks_left--;
if (!testenv.interrupts.empty() || testenv.code_mem_modified_by_guest) {
return;
}
}
const bool T = mcl::bit::get_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 <mcl/stdint.hpp>
#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 "./a64_unicorn.h"
#include <mcl/assert.hpp>
#define CHECKED(expr) \
do { \
if (auto cerr_ = (expr)) { \
ASSERT_MSG(false, "Call " #expr " failed with error: {} ({})\n", static_cast<size_t>(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,82 @@
/* 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 <mcl/stdint.hpp>
#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;
};

View file

@ -0,0 +1,119 @@
/* This file is part of the dynarmic project.
* Copyright (c) 2020 MerryMage
* SPDX-License-Identifier: 0BSD
*/
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <utility>
#include <catch2/catch_test_macros.hpp>
#include <xbyak/xbyak_util.h>
TEST_CASE("Host CPU supports", "[a64]") {
using Cpu = Xbyak::util::Cpu;
Cpu cpu_info;
std::array<std::uint32_t, 4> cpu_name;
for (std::uint32_t i = 2; i < 5; ++i) {
cpu_info.getCpuid(0x80000000 | i, cpu_name.data());
std::printf("%.16s", reinterpret_cast<const char*>(cpu_name.data()));
}
std::putchar('\n');
cpu_info.putFamily();
const std::array types{
#define X(NAME) std::make_pair(Cpu::Type{Cpu::NAME}, &#NAME[1])
X(t3DN),
X(tADX),
X(tAESNI),
X(tAMD),
X(tAMX_BF16),
X(tAMX_INT8),
X(tAMX_TILE),
X(tAVX),
X(tAVX2),
X(tAVX512_4FMAPS),
X(tAVX512_4VNNIW),
X(tAVX512_BF16),
X(tAVX512_BITALG),
X(tAVX512_FP16),
X(tAVX512_IFMA),
X(tAVX512_VBMI),
X(tAVX512_VBMI2),
X(tAVX512_VNNI),
X(tAVX512_VP2INTERSECT),
X(tAVX512_VPOPCNTDQ),
X(tAVX512BW),
X(tAVX512CD),
X(tAVX512DQ),
X(tAVX512ER),
X(tAVX512F),
X(tAVX512IFMA),
X(tAVX512PF),
X(tAVX512VBMI),
X(tAVX512VL),
X(tAVX_VNNI),
X(tBMI1),
X(tBMI2),
X(tCLDEMOTE),
X(tCLFLUSHOPT),
X(tCLZERO),
X(tCMOV),
X(tE3DN),
X(tENHANCED_REP),
X(tF16C),
X(tFMA),
X(tGFNI),
X(tHLE),
X(tINTEL),
X(tLZCNT),
X(tMMX),
X(tMMX2),
X(tMOVBE),
X(tMOVDIR64B),
X(tMOVDIRI),
X(tMPX),
X(tOSXSAVE),
X(tPCLMULQDQ),
X(tPOPCNT),
X(tPREFETCHW),
X(tPREFETCHWT1),
X(tRDRAND),
X(tRDSEED),
X(tRDTSCP),
X(tRTM),
X(tSHA),
X(tSMAP),
X(tSSE),
X(tSSE2),
X(tSSE3),
X(tSSE41),
X(tSSE42),
X(tSSSE3),
X(tVAES),
X(tVPCLMULQDQ),
X(tWAITPKG),
#undef X
};
constexpr std::size_t line_max = 80;
std::size_t line_length = 0;
for (const auto& [type, name] : types) {
if (cpu_info.has(type)) {
const std::size_t name_length = std::strlen(name) + 1;
if ((line_length + name_length) >= line_max) {
line_length = name_length;
std::putchar('\n');
} else if (line_length) {
std::putchar(' ');
}
std::fputs(name, stdout);
line_length += name_length;
}
}
std::putchar('\n');
}