mirror of
https://git.eden-emu.dev/eden-emu/eden
synced 2026-05-22 20:17:00 +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
Loading…
Add table
Add a link
Reference in a new issue