mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-05-01 12:58:59 +02:00
[cmake] refactor: Use CPM over submodules (#143)
Transfers the majority of submodules and large externals to CPM, using source archives rather than full Git clones. Not only does this save massive amounts of clone and configure time, but dependencies are grabbed on-demand rather than being required by default. Additionally, CPM will (generally) automatically search for system dependencies, though certain dependencies have options to control this. Testing shows gains ranging from 5x to 10x in terms of overall clone/configure time. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/143 Reviewed-by: CamilleLaVey <camillelavey99@gmail.com>
This commit is contained in:
parent
04e5e64538
commit
51b170b470
4035 changed files with 709 additions and 1033458 deletions
693
src/dynarmic/tests/A32/fuzz_arm.cpp
Normal file
693
src/dynarmic/tests/A32/fuzz_arm.cpp
Normal file
|
|
@ -0,0 +1,693 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* 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 "dynarmic/common/common_types.h"
|
||||
|
||||
#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();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Why the difference? QEMU what are you doing???
|
||||
jit.Regs()[15] = uni.GetRegisters()[15];
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
544
src/dynarmic/tests/A32/fuzz_thumb.cpp
Normal file
544
src/dynarmic/tests/A32/fuzz_thumb.cpp
Normal file
|
|
@ -0,0 +1,544 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* 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 "dynarmic/common/common_types.h"
|
||||
|
||||
#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);
|
||||
}
|
||||
381
src/dynarmic/tests/A32/test_arm_disassembler.cpp
Normal file
381
src/dynarmic/tests/A32/test_arm_disassembler.cpp
Normal 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}");
|
||||
}
|
||||
705
src/dynarmic/tests/A32/test_arm_instructions.cpp
Normal file
705
src/dynarmic/tests/A32/test_arm_instructions.cpp
Normal file
|
|
@ -0,0 +1,705 @@
|
|||
/* 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{};
|
||||
config.callbacks = &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);
|
||||
}
|
||||
228
src/dynarmic/tests/A32/test_coprocessor.cpp
Normal file
228
src/dynarmic/tests/A32/test_coprocessor.cpp
Normal 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);
|
||||
}
|
||||
39
src/dynarmic/tests/A32/test_svc.cpp
Normal file
39
src/dynarmic/tests/A32/test_svc.cpp
Normal 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);
|
||||
}
|
||||
257
src/dynarmic/tests/A32/test_thumb_instructions.cpp
Normal file
257
src/dynarmic/tests/A32/test_thumb_instructions.cpp
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
* Copyright (c) 2016 MerryMage
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
*/
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include "dynarmic/common/common_types.h"
|
||||
|
||||
#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);
|
||||
}
|
||||
204
src/dynarmic/tests/A32/testenv.h
Normal file
204
src/dynarmic/tests/A32/testenv.h
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
* Copyright (c) 2018 MerryMage
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "dynarmic/common/assert.h"
|
||||
#include "dynarmic/common/common_types.h"
|
||||
|
||||
#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;
|
||||
}
|
||||
};
|
||||
13456
src/dynarmic/tests/A32/vfp_vadd_f32.inc
Normal file
13456
src/dynarmic/tests/A32/vfp_vadd_f32.inc
Normal file
File diff suppressed because it is too large
Load diff
13456
src/dynarmic/tests/A32/vfp_vsub_f32.inc
Normal file
13456
src/dynarmic/tests/A32/vfp_vsub_f32.inc
Normal file
File diff suppressed because it is too large
Load diff
2387
src/dynarmic/tests/A64/a64.cpp
Normal file
2387
src/dynarmic/tests/A64/a64.cpp
Normal file
File diff suppressed because one or more lines are too long
171
src/dynarmic/tests/A64/fibonacci.cpp
Normal file
171
src/dynarmic/tests/A64/fibonacci.cpp
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
* Copyright (c) 2023 MerryMage
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
*/
|
||||
|
||||
#include <array>
|
||||
#include <exception>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include "dynarmic/common/common_types.h"
|
||||
#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::unordered_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);
|
||||
}
|
||||
190
src/dynarmic/tests/A64/fp_min_max.cpp
Normal file
190
src/dynarmic/tests/A64/fp_min_max.cpp
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* 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 "dynarmic/common/common_types.h"
|
||||
|
||||
#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::UserConfig jit_user_config{};
|
||||
jit_user_config.callbacks = &env;
|
||||
A64::Jit jit{jit_user_config};
|
||||
|
||||
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; });
|
||||
}
|
||||
523
src/dynarmic/tests/A64/fuzz_with_unicorn.cpp
Normal file
523
src/dynarmic/tests/A64/fuzz_with_unicorn.cpp
Normal file
|
|
@ -0,0 +1,523 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* 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 "dynarmic/common/common_types.h"
|
||||
|
||||
#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_user_config.callbacks = &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);
|
||||
}
|
||||
}
|
||||
30
src/dynarmic/tests/A64/misaligned_page_table.cpp
Normal file
30
src/dynarmic/tests/A64/misaligned_page_table.cpp
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/* 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{};
|
||||
conf.callbacks = &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.
|
||||
}
|
||||
102
src/dynarmic/tests/A64/real_world.cpp
Normal file
102
src/dynarmic/tests/A64/real_world.cpp
Normal file
File diff suppressed because one or more lines are too long
113
src/dynarmic/tests/A64/test_invalidation.cpp
Normal file
113
src/dynarmic/tests/A64/test_invalidation.cpp
Normal 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{};
|
||||
conf.callbacks = &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{};
|
||||
conf.callbacks = &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);
|
||||
}
|
||||
228
src/dynarmic/tests/A64/testenv.h
Normal file
228
src/dynarmic/tests/A64/testenv.h
Normal file
|
|
@ -0,0 +1,228 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
* Copyright (c) 2018 MerryMage
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "dynarmic/common/assert.h"
|
||||
#include "dynarmic/common/common_types.h"
|
||||
|
||||
#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::unordered_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;
|
||||
bool ignore_invalid_insn = false;
|
||||
|
||||
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(ignore_invalid_insn, "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;
|
||||
}
|
||||
};
|
||||
80
src/dynarmic/tests/A64/verify_unicorn.cpp
Normal file
80
src/dynarmic/tests/A64/verify_unicorn.cpp
Normal 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);
|
||||
}
|
||||
133
src/dynarmic/tests/CMakeLists.txt
Normal file
133
src/dynarmic/tests/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
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/real_world.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)
|
||||
78
src/dynarmic/tests/decoder_tests.cpp
Normal file
78
src/dynarmic/tests/decoder_tests.cpp
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* 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 "dynarmic/common/assert.h"
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
60
src/dynarmic/tests/fp/FPToFixed.cpp
Normal file
60
src/dynarmic/tests/fp/FPToFixed.cpp
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* 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 "dynarmic/common/common_types.h"
|
||||
|
||||
#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
src/dynarmic/tests/fp/FPValue.cpp
Normal file
15
src/dynarmic/tests/fp/FPValue.cpp
Normal 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);
|
||||
66
src/dynarmic/tests/fp/mantissa_util_tests.cpp
Normal file
66
src/dynarmic/tests/fp/mantissa_util_tests.cpp
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* 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 "dynarmic/common/common_types.h"
|
||||
|
||||
#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);
|
||||
}
|
||||
}
|
||||
98
src/dynarmic/tests/fp/unpacked_tests.cpp
Normal file
98
src/dynarmic/tests/fp/unpacked_tests.cpp
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* 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 "dynarmic/common/common_types.h"
|
||||
|
||||
#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);
|
||||
}
|
||||
70
src/dynarmic/tests/fuzz_util.cpp
Normal file
70
src/dynarmic/tests/fuzz_util.cpp
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* 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 "dynarmic/common/assert.h"
|
||||
|
||||
#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);
|
||||
}
|
||||
34
src/dynarmic/tests/fuzz_util.h
Normal file
34
src/dynarmic/tests/fuzz_util.h
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
* Copyright (c) 2018 MerryMage
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <iosfwd>
|
||||
|
||||
#include "dynarmic/common/common_types.h"
|
||||
|
||||
using Vector = std::array<u64, 2>;
|
||||
|
||||
std::ostream& operator<<(std::ostream& o, Vector vec);
|
||||
Vector RandomVector();
|
||||
u32 RandomFpcr();
|
||||
|
||||
struct InstructionGenerator final {
|
||||
public:
|
||||
explicit InstructionGenerator(const char* format);
|
||||
|
||||
u32 Generate() const;
|
||||
u32 Bits() const { return bits; }
|
||||
u32 Mask() const { return mask; }
|
||||
bool Match(u32 inst) const { return (inst & mask) == bits; }
|
||||
|
||||
private:
|
||||
u32 bits = 0;
|
||||
u32 mask = 0;
|
||||
};
|
||||
345
src/dynarmic/tests/print_info.cpp
Normal file
345
src/dynarmic/tests/print_info.cpp
Normal file
|
|
@ -0,0 +1,345 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* 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 "dynarmic/common/common_types.h"
|
||||
|
||||
#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 = [®s, &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)] = ®s[i];
|
||||
}
|
||||
for (size_t i = 0; i < ext_regs.size(); i++) {
|
||||
name_map[fmt::format("s{}", i)] = &ext_regs[i];
|
||||
}
|
||||
name_map["sp"] = ®s[13];
|
||||
name_map["lr"] = ®s[14];
|
||||
name_map["pc"] = ®s[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
src/dynarmic/tests/rand_int.h
Normal file
27
src/dynarmic/tests/rand_int.h
Normal 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);
|
||||
}
|
||||
155
src/dynarmic/tests/rsqrt_test.cpp
Normal file
155
src/dynarmic/tests/rsqrt_test.cpp
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* 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 "dynarmic/common/common_types.h"
|
||||
|
||||
#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
src/dynarmic/tests/rsqrt_test_fn.s
Normal file
303
src/dynarmic/tests/rsqrt_test_fn.s
Normal 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
|
||||
703
src/dynarmic/tests/test_generator.cpp
Normal file
703
src/dynarmic/tests/test_generator.cpp
Normal file
|
|
@ -0,0 +1,703 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* 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 "dynarmic/common/common_types.h"
|
||||
|
||||
#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/common/llvm_disassemble.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 bool show_disas) {
|
||||
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());
|
||||
|
||||
if (show_disas) {
|
||||
fmt::print("instructions:\n");
|
||||
auto current_pc = initial_pc;
|
||||
for (auto instruction : instructions) {
|
||||
if constexpr (sizeof(decltype(instruction)) == 2) {
|
||||
fmt::print("{:04x} ?\n", instruction);
|
||||
} else {
|
||||
fmt::print("{}", Dynarmic::Common::DisassembleAArch64(instruction, current_pc));
|
||||
}
|
||||
current_pc += sizeof(decltype(instruction));
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
if (show_disas) {
|
||||
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");
|
||||
jit.DumpDisassembly();
|
||||
}
|
||||
}
|
||||
|
||||
Dynarmic::A64::UserConfig GetA64UserConfig(A64TestEnv& jit_env, bool noopt) {
|
||||
Dynarmic::A64::UserConfig jit_user_config{};
|
||||
jit_user_config.callbacks = &jit_env;
|
||||
jit_user_config.optimizations = all_safe_optimizations;
|
||||
// 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,
|
||||
const bool show_disas) {
|
||||
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();
|
||||
}
|
||||
|
||||
if (show_disas) {
|
||||
fmt::print("instructions:\n");
|
||||
auto current_pc = start_address;
|
||||
for (u32 instruction : instructions) {
|
||||
fmt::print("{}", Dynarmic::Common::DisassembleAArch64(instruction, current_pc));
|
||||
current_pc += 4;
|
||||
}
|
||||
|
||||
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");
|
||||
jit.DumpDisassembly();
|
||||
}
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
void TestThumb(size_t num_instructions, size_t num_iterations, bool noopt, bool show_disas) {
|
||||
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, show_disas);
|
||||
}
|
||||
}
|
||||
|
||||
void TestArm(size_t num_instructions, size_t num_iterations, bool noopt, bool show_disas) {
|
||||
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, show_disas);
|
||||
}
|
||||
}
|
||||
|
||||
void TestA64(size_t num_instructions, size_t num_iterations, bool noopt, bool show_disas) {
|
||||
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, show_disas);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
const bool show_disas = argc == 6 && (strcmp(argv[5], "disas") == 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, show_disas);
|
||||
} else if (strcmp(argv[1], "arm") == 0) {
|
||||
TestArm(*instruction_count, *iterator_count, noopt, show_disas);
|
||||
} else if (strcmp(argv[1], "a64") == 0) {
|
||||
TestA64(*instruction_count, *iterator_count, noopt, show_disas);
|
||||
} else {
|
||||
fmt::print("unrecognized instruction class\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
431
src/dynarmic/tests/test_reader.cpp
Normal file
431
src/dynarmic/tests/test_reader.cpp
Normal file
|
|
@ -0,0 +1,431 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* 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 "dynarmic/common/common_types.h"
|
||||
|
||||
#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_user_config.callbacks = &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;
|
||||
}
|
||||
354
src/dynarmic/tests/unicorn_emu/a32_unicorn.cpp
Normal file
354
src/dynarmic/tests/unicorn_emu/a32_unicorn.cpp
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
* Copyright (c) 2018 MerryMage
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
*/
|
||||
|
||||
#include "./a32_unicorn.h"
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "dynarmic/common/assert.h"
|
||||
#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] = ®s[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, ®ions, &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>;
|
||||
91
src/dynarmic/tests/unicorn_emu/a32_unicorn.h
Normal file
91
src/dynarmic/tests/unicorn_emu/a32_unicorn.h
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* 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 "dynarmic/common/common_types.h"
|
||||
|
||||
#include "../A32/testenv.h"
|
||||
|
||||
namespace Unicorn::A32 {
|
||||
static constexpr size_t num_gprs = 16;
|
||||
static constexpr size_t num_ext_regs = 64;
|
||||
|
||||
using ExtRegArray = std::array<u32, num_ext_regs>;
|
||||
using RegisterArray = std::array<u32, num_gprs>;
|
||||
using RegisterPtrArray = std::array<RegisterArray::pointer, num_gprs>;
|
||||
using RegisterConstPtrArray = std::array<RegisterArray::const_pointer, num_gprs>;
|
||||
} // namespace Unicorn::A32
|
||||
|
||||
template<class TestEnvironment>
|
||||
class A32Unicorn final {
|
||||
public:
|
||||
using ExtRegArray = Unicorn::A32::ExtRegArray;
|
||||
using RegisterArray = Unicorn::A32::RegisterArray;
|
||||
|
||||
explicit A32Unicorn(TestEnvironment& testenv);
|
||||
~A32Unicorn();
|
||||
|
||||
void Run();
|
||||
|
||||
u32 GetSP() const;
|
||||
void SetSP(u32 value);
|
||||
|
||||
u32 GetPC() const;
|
||||
void SetPC(u32 value);
|
||||
|
||||
RegisterArray GetRegisters() const;
|
||||
void SetRegisters(const RegisterArray& value);
|
||||
|
||||
ExtRegArray GetExtRegs() const;
|
||||
void SetExtRegs(const ExtRegArray& value);
|
||||
|
||||
u32 GetFpscr() const;
|
||||
void SetFpscr(u32 value);
|
||||
|
||||
u32 GetFpexc() const;
|
||||
void SetFpexc(u32 value);
|
||||
|
||||
u32 GetCpsr() const;
|
||||
void SetCpsr(u32 value);
|
||||
|
||||
void EnableFloatingPointAccess();
|
||||
|
||||
void ClearPageCache();
|
||||
|
||||
void DumpMemoryInformation();
|
||||
|
||||
private:
|
||||
static void InterruptHook(uc_engine* uc, u32 interrupt, void* user_data);
|
||||
static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u32 addr, int size, u64 value, void* user_data);
|
||||
static bool MemoryWriteHook(uc_engine* uc, uc_mem_type type, u32 addr, int size, u64 value, void* user_data);
|
||||
|
||||
struct Page {
|
||||
u32 address;
|
||||
std::array<u8, 4096> data;
|
||||
};
|
||||
|
||||
TestEnvironment& testenv;
|
||||
uc_engine* uc{};
|
||||
uc_hook intr_hook{};
|
||||
uc_hook mem_invalid_hook{};
|
||||
uc_hook mem_write_prot_hook{};
|
||||
|
||||
std::vector<std::unique_ptr<Page>> pages;
|
||||
};
|
||||
255
src/dynarmic/tests/unicorn_emu/a64_unicorn.cpp
Normal file
255
src/dynarmic/tests/unicorn_emu/a64_unicorn.cpp
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
* Copyright (c) 2018 MerryMage
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
*/
|
||||
|
||||
#include "./a64_unicorn.h"
|
||||
|
||||
#include "dynarmic/common/assert.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 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] = ®s[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, ®ions, &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_EL0, &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;
|
||||
}
|
||||
85
src/dynarmic/tests/unicorn_emu/a64_unicorn.h
Normal file
85
src/dynarmic/tests/unicorn_emu/a64_unicorn.h
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* 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 "dynarmic/common/common_types.h"
|
||||
|
||||
#include "../A64/testenv.h"
|
||||
|
||||
class A64Unicorn final {
|
||||
public:
|
||||
static constexpr size_t num_gprs = 31;
|
||||
using RegisterArray = std::array<u64, num_gprs>;
|
||||
using RegisterPtrArray = std::array<RegisterArray::pointer, num_gprs>;
|
||||
using RegisterConstPtrArray = std::array<RegisterArray::const_pointer, num_gprs>;
|
||||
|
||||
static constexpr size_t num_vecs = 32;
|
||||
using VectorArray = std::array<Vector, num_vecs>;
|
||||
using VectorPtrArray = std::array<VectorArray::pointer, num_vecs>;
|
||||
using VectorConstPtrArray = std::array<VectorArray::const_pointer, num_vecs>;
|
||||
|
||||
explicit A64Unicorn(A64TestEnv& testenv);
|
||||
~A64Unicorn();
|
||||
|
||||
void Run();
|
||||
|
||||
u64 GetSP() const;
|
||||
void SetSP(u64 value);
|
||||
|
||||
u64 GetPC() const;
|
||||
void SetPC(u64 value);
|
||||
|
||||
RegisterArray GetRegisters() const;
|
||||
void SetRegisters(const RegisterArray& value);
|
||||
|
||||
VectorArray GetVectors() const;
|
||||
void SetVectors(const VectorArray& value);
|
||||
|
||||
u32 GetFpcr() const;
|
||||
void SetFpcr(u32 value);
|
||||
|
||||
u32 GetFpsr() const;
|
||||
void SetFpsr(u32 value);
|
||||
|
||||
u32 GetPstate() const;
|
||||
void SetPstate(u32 value);
|
||||
|
||||
void ClearPageCache();
|
||||
|
||||
void DumpMemoryInformation();
|
||||
|
||||
private:
|
||||
static void InterruptHook(uc_engine* uc, u32 interrupt, void* user_data);
|
||||
static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int size, u64 value, void* user_data);
|
||||
static bool MemoryWriteHook(uc_engine* uc, uc_mem_type type, u64 addr, int size, u64 value, void* user_data);
|
||||
|
||||
struct Page {
|
||||
u64 address;
|
||||
std::array<u8, 4096> data;
|
||||
};
|
||||
|
||||
A64TestEnv& testenv;
|
||||
uc_engine* uc{};
|
||||
uc_hook intr_hook{};
|
||||
uc_hook mem_invalid_hook{};
|
||||
uc_hook mem_write_prot_hook{};
|
||||
|
||||
std::vector<std::unique_ptr<Page>> pages;
|
||||
};
|
||||
119
src/dynarmic/tests/x64_cpu_info.cpp
Normal file
119
src/dynarmic/tests/x64_cpu_info.cpp
Normal 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');
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue