diff options
Diffstat (limited to 'externals/dynarmic/tests/A32/fuzz_arm.cpp')
-rw-r--r-- | externals/dynarmic/tests/A32/fuzz_arm.cpp | 687 |
1 files changed, 687 insertions, 0 deletions
diff --git a/externals/dynarmic/tests/A32/fuzz_arm.cpp b/externals/dynarmic/tests/A32/fuzz_arm.cpp new file mode 100644 index 0000000000..cda424e27d --- /dev/null +++ b/externals/dynarmic/tests/A32/fuzz_arm.cpp @@ -0,0 +1,687 @@ +/* This file is part of the dynarmic project. + * Copyright (c) 2016 MerryMage + * SPDX-License-Identifier: 0BSD + */ + +#include <algorithm> +#include <array> +#include <cstdio> +#include <functional> +#include <tuple> +#include <type_traits> +#include <vector> + +#include <catch2/catch_test_macros.hpp> +#include <mcl/bit/bit_count.hpp> +#include <mcl/bit/swap.hpp> +#include <mcl/scope_exit.hpp> +#include <mcl/stdint.hpp> + +#include "../fuzz_util.h" +#include "../rand_int.h" +#include "../unicorn_emu/a32_unicorn.h" +#include "./testenv.h" +#include "dynarmic/common/fp/fpcr.h" +#include "dynarmic/common/fp/fpsr.h" +#include "dynarmic/common/llvm_disassemble.h" +#include "dynarmic/common/variant_util.h" +#include "dynarmic/frontend/A32/ITState.h" +#include "dynarmic/frontend/A32/a32_location_descriptor.h" +#include "dynarmic/frontend/A32/a32_types.h" +#include "dynarmic/frontend/A32/translate/a32_translate.h" +#include "dynarmic/interface/A32/a32.h" +#include "dynarmic/ir/basic_block.h" +#include "dynarmic/ir/location_descriptor.h" +#include "dynarmic/ir/opcodes.h" + +// Must be declared last for all necessary operator<< to be declared prior to this. +#include <fmt/format.h> +#include <fmt/ostream.h> + +namespace { +using namespace Dynarmic; + +template<typename Fn> +bool AnyLocationDescriptorForTerminalHas(IR::Terminal terminal, Fn fn) { + return Common::VisitVariant<bool>(terminal, [&](auto t) -> bool { + using T = std::decay_t<decltype(t)>; + if constexpr (std::is_same_v<T, IR::Term::Invalid>) { + return false; + } else if constexpr (std::is_same_v<T, IR::Term::ReturnToDispatch>) { + return false; + } else if constexpr (std::is_same_v<T, IR::Term::LinkBlock>) { + return fn(t.next); + } else if constexpr (std::is_same_v<T, IR::Term::LinkBlockFast>) { + return fn(t.next); + } else if constexpr (std::is_same_v<T, IR::Term::PopRSBHint>) { + return false; + } else if constexpr (std::is_same_v<T, IR::Term::Interpret>) { + return fn(t.next); + } else if constexpr (std::is_same_v<T, IR::Term::FastDispatchHint>) { + return false; + } else if constexpr (std::is_same_v<T, IR::Term::If>) { + return AnyLocationDescriptorForTerminalHas(t.then_, fn) || AnyLocationDescriptorForTerminalHas(t.else_, fn); + } else if constexpr (std::is_same_v<T, IR::Term::CheckBit>) { + return AnyLocationDescriptorForTerminalHas(t.then_, fn) || AnyLocationDescriptorForTerminalHas(t.else_, fn); + } else if constexpr (std::is_same_v<T, IR::Term::CheckHalt>) { + return AnyLocationDescriptorForTerminalHas(t.else_, fn); + } else { + ASSERT_MSG(false, "Invalid terminal type"); + return false; + } + }); +} + +bool ShouldTestInst(u32 instruction, u32 pc, bool is_thumb, bool is_last_inst, A32::ITState it_state = {}) { + const A32::LocationDescriptor location = A32::LocationDescriptor{pc, {}, {}}.SetTFlag(is_thumb).SetIT(it_state); + IR::Block block{location}; + const bool should_continue = A32::TranslateSingleInstruction(block, location, instruction); + + if (!should_continue && !is_last_inst) { + return false; + } + + if (auto terminal = block.GetTerminal(); boost::get<IR::Term::Interpret>(&terminal)) { + return false; + } + + if (AnyLocationDescriptorForTerminalHas(block.GetTerminal(), [&](IR::LocationDescriptor ld) { return A32::LocationDescriptor{ld}.PC() <= pc; })) { + return false; + } + + for (const auto& ir_inst : block) { + switch (ir_inst.GetOpcode()) { + case IR::Opcode::A32ExceptionRaised: + case IR::Opcode::A32CallSupervisor: + case IR::Opcode::A32CoprocInternalOperation: + case IR::Opcode::A32CoprocSendOneWord: + case IR::Opcode::A32CoprocSendTwoWords: + case IR::Opcode::A32CoprocGetOneWord: + case IR::Opcode::A32CoprocGetTwoWords: + case IR::Opcode::A32CoprocLoadWords: + case IR::Opcode::A32CoprocStoreWords: + return false; + // Currently unimplemented in Unicorn + case IR::Opcode::FPVectorRecipEstimate16: + case IR::Opcode::FPVectorRSqrtEstimate16: + case IR::Opcode::VectorPolynomialMultiplyLong64: + return false; + default: + continue; + } + } + + return true; +} + +u32 GenRandomArmInst(u32 pc, bool is_last_inst) { + static const struct InstructionGeneratorInfo { + std::vector<InstructionGenerator> generators; + std::vector<InstructionGenerator> invalid; + } instructions = [] { + const std::vector<std::tuple<std::string, const char*>> list{ +#define INST(fn, name, bitstring) {#fn, bitstring}, +#include "dynarmic/frontend/A32/decoder/arm.inc" +#include "dynarmic/frontend/A32/decoder/asimd.inc" +#include "dynarmic/frontend/A32/decoder/vfp.inc" +#undef INST + }; + + std::vector<InstructionGenerator> generators; + std::vector<InstructionGenerator> invalid; + + // List of instructions not to test + static constexpr std::array do_not_test{ + // Translating load/stores + "arm_LDRBT", "arm_LDRBT", "arm_LDRHT", "arm_LDRHT", "arm_LDRSBT", "arm_LDRSBT", "arm_LDRSHT", "arm_LDRSHT", "arm_LDRT", "arm_LDRT", + "arm_STRBT", "arm_STRBT", "arm_STRHT", "arm_STRHT", "arm_STRT", "arm_STRT", + // Exclusive load/stores + "arm_LDREXB", "arm_LDREXD", "arm_LDREXH", "arm_LDREX", "arm_LDAEXB", "arm_LDAEXD", "arm_LDAEXH", "arm_LDAEX", + "arm_STREXB", "arm_STREXD", "arm_STREXH", "arm_STREX", "arm_STLEXB", "arm_STLEXD", "arm_STLEXH", "arm_STLEX", + "arm_SWP", "arm_SWPB", + // Elevated load/store multiple instructions. + "arm_LDM_eret", "arm_LDM_usr", + "arm_STM_usr", + // Hint instructions + "arm_NOP", "arm_PLD_imm", "arm_PLD_reg", "arm_SEV", + "arm_WFE", "arm_WFI", "arm_YIELD", + // E, T, J + "arm_BLX_reg", "arm_BLX_imm", "arm_BXJ", "arm_SETEND", + // Coprocessor + "arm_CDP", "arm_LDC", "arm_MCR", "arm_MCRR", "arm_MRC", "arm_MRRC", "arm_STC", + // System + "arm_CPS", "arm_RFE", "arm_SRS", + // Undefined + "arm_UDF", + // FPSCR is inaccurate + "vfp_VMRS", + // Incorrect Unicorn implementations + "asimd_VRECPS", // Unicorn does not fuse the multiply and subtraction, resulting in being off by 1ULP. + "asimd_VRSQRTS", // Unicorn does not fuse the multiply and subtraction, resulting in being off by 1ULP. + "vfp_VCVT_from_fixed", // Unicorn does not do round-to-nearest-even for this instruction correctly. + }; + + for (const auto& [fn, bitstring] : list) { + if (std::find(do_not_test.begin(), do_not_test.end(), fn) != do_not_test.end()) { + invalid.emplace_back(InstructionGenerator{bitstring}); + continue; + } + generators.emplace_back(InstructionGenerator{bitstring}); + } + return InstructionGeneratorInfo{generators, invalid}; + }(); + + while (true) { + const size_t index = RandInt<size_t>(0, instructions.generators.size() - 1); + const u32 inst = instructions.generators[index].Generate(); + + if ((instructions.generators[index].Mask() & 0xF0000000) == 0 && (inst & 0xF0000000) == 0xF0000000) { + continue; + } + + if (ShouldTestInst(inst, pc, false, is_last_inst)) { + return inst; + } + } +} + +std::vector<u16> GenRandomThumbInst(u32 pc, bool is_last_inst, A32::ITState it_state = {}) { + static const struct InstructionGeneratorInfo { + std::vector<InstructionGenerator> generators; + std::vector<InstructionGenerator> invalid; + } instructions = [] { + const std::vector<std::tuple<std::string, const char*>> list{ +#define INST(fn, name, bitstring) {#fn, bitstring}, +#include "dynarmic/frontend/A32/decoder/thumb16.inc" +#include "dynarmic/frontend/A32/decoder/thumb32.inc" +#undef INST + }; + + const std::vector<std::tuple<std::string, const char*>> vfp_list{ +#define INST(fn, name, bitstring) {#fn, bitstring}, +#include "dynarmic/frontend/A32/decoder/vfp.inc" +#undef INST + }; + + const std::vector<std::tuple<std::string, const char*>> asimd_list{ +#define INST(fn, name, bitstring) {#fn, bitstring}, +#include "dynarmic/frontend/A32/decoder/asimd.inc" +#undef INST + }; + + std::vector<InstructionGenerator> generators; + std::vector<InstructionGenerator> invalid; + + // List of instructions not to test + static constexpr std::array do_not_test{ + "thumb16_BKPT", + "thumb16_IT", + "thumb16_SETEND", + + // Exclusive load/stores + "thumb32_LDREX", + "thumb32_LDREXB", + "thumb32_LDREXD", + "thumb32_LDREXH", + "thumb32_STREX", + "thumb32_STREXB", + "thumb32_STREXD", + "thumb32_STREXH", + + // FPSCR is inaccurate + "vfp_VMRS", + + // Unicorn is incorrect? + "thumb32_MRS_reg", + "thumb32_MSR_reg", + + // Unicorn has incorrect implementation (incorrect rounding and unsets CPSR.T??) + "vfp_VCVT_to_fixed", + "vfp_VCVT_from_fixed", + "asimd_VRECPS", // Unicorn does not fuse the multiply and subtraction, resulting in being off by 1ULP. + "asimd_VRSQRTS", // Unicorn does not fuse the multiply and subtraction, resulting in being off by 1ULP. + + // Coprocessor + "thumb32_CDP", + "thumb32_LDC", + "thumb32_MCR", + "thumb32_MCRR", + "thumb32_MRC", + "thumb32_MRRC", + "thumb32_STC", + }; + + for (const auto& [fn, bitstring] : list) { + if (std::find(do_not_test.begin(), do_not_test.end(), fn) != do_not_test.end()) { + invalid.emplace_back(InstructionGenerator{bitstring}); + continue; + } + generators.emplace_back(InstructionGenerator{bitstring}); + } + for (const auto& [fn, bs] : vfp_list) { + std::string bitstring = bs; + if (bitstring.substr(0, 4) == "cccc" || bitstring.substr(0, 4) == "----") { + bitstring.replace(0, 4, "1110"); + } + if (std::find(do_not_test.begin(), do_not_test.end(), fn) != do_not_test.end()) { + invalid.emplace_back(InstructionGenerator{bitstring.c_str()}); + continue; + } + generators.emplace_back(InstructionGenerator{bitstring.c_str()}); + } + for (const auto& [fn, bs] : asimd_list) { + std::string bitstring = bs; + if (bitstring.substr(0, 7) == "1111001") { + const char U = bitstring[7]; + bitstring.replace(0, 8, "111-1111"); + bitstring[3] = U; + } else if (bitstring.substr(0, 8) == "11110100") { + bitstring.replace(0, 8, "11111001"); + } else { + ASSERT_FALSE("Unhandled ASIMD instruction: {} {}", fn, bs); + } + if (std::find(do_not_test.begin(), do_not_test.end(), fn) != do_not_test.end()) { + invalid.emplace_back(InstructionGenerator{bitstring.c_str()}); + continue; + } + generators.emplace_back(InstructionGenerator{bitstring.c_str()}); + } + return InstructionGeneratorInfo{generators, invalid}; + }(); + + while (true) { + const size_t index = RandInt<size_t>(0, instructions.generators.size() - 1); + const u32 inst = instructions.generators[index].Generate(); + const bool is_four_bytes = (inst >> 16) != 0; + + if (ShouldTestInst(is_four_bytes ? mcl::bit::swap_halves_32(inst) : inst, pc, true, is_last_inst, it_state)) { + if (is_four_bytes) + return {static_cast<u16>(inst >> 16), static_cast<u16>(inst)}; + return {static_cast<u16>(inst)}; + } + } +} + +template<typename TestEnv> +Dynarmic::A32::UserConfig GetUserConfig(TestEnv& testenv) { + Dynarmic::A32::UserConfig user_config; + user_config.optimizations &= ~OptimizationFlag::FastDispatch; + user_config.callbacks = &testenv; + user_config.always_little_endian = true; + return user_config; +} + +template<typename TestEnv> +static void RunTestInstance(Dynarmic::A32::Jit& jit, + A32Unicorn<TestEnv>& uni, + TestEnv& jit_env, + TestEnv& uni_env, + const typename A32Unicorn<TestEnv>::RegisterArray& regs, + const typename A32Unicorn<TestEnv>::ExtRegArray& vecs, + const std::vector<typename TestEnv::InstructionType>& instructions, + const u32 cpsr, + const u32 fpscr, + const size_t ticks_left) { + const u32 initial_pc = regs[15]; + const u32 num_words = initial_pc / sizeof(typename TestEnv::InstructionType); + const u32 code_mem_size = num_words + static_cast<u32>(instructions.size()); + const u32 expected_end_pc = code_mem_size * sizeof(typename TestEnv::InstructionType); + + jit_env.code_mem.resize(code_mem_size); + uni_env.code_mem.resize(code_mem_size); + std::fill(jit_env.code_mem.begin(), jit_env.code_mem.end(), TestEnv::infinite_loop); + std::fill(uni_env.code_mem.begin(), uni_env.code_mem.end(), TestEnv::infinite_loop); + + std::copy(instructions.begin(), instructions.end(), jit_env.code_mem.begin() + num_words); + std::copy(instructions.begin(), instructions.end(), uni_env.code_mem.begin() + num_words); + jit_env.PadCodeMem(); + uni_env.PadCodeMem(); + jit_env.modified_memory.clear(); + uni_env.modified_memory.clear(); + jit_env.interrupts.clear(); + uni_env.interrupts.clear(); + + jit.Regs() = regs; + jit.ExtRegs() = vecs; + jit.SetFpscr(fpscr); + jit.SetCpsr(cpsr); + jit.ClearCache(); + uni.SetRegisters(regs); + uni.SetExtRegs(vecs); + uni.SetFpscr(fpscr); + uni.EnableFloatingPointAccess(); + uni.SetCpsr(cpsr); + uni.ClearPageCache(); + + jit_env.ticks_left = ticks_left; + jit.Run(); + + uni_env.ticks_left = instructions.size(); // Unicorn counts thumb instructions weirdly. + uni.Run(); + + SCOPE_FAIL { + fmt::print("Instruction Listing:\n"); + fmt::print("{}\n", Common::DisassembleAArch32(std::is_same_v<TestEnv, ThumbTestEnv>, initial_pc, (const u8*)instructions.data(), instructions.size() * sizeof(instructions[0]))); + + fmt::print("Initial register listing:\n"); + for (size_t i = 0; i < regs.size(); ++i) { + fmt::print("{:3s}: {:08x}\n", static_cast<A32::Reg>(i), regs[i]); + } + for (size_t i = 0; i < vecs.size(); ++i) { + fmt::print("{:3s}: {:08x}\n", static_cast<A32::ExtReg>(i), vecs[i]); + } + fmt::print("cpsr {:08x}\n", cpsr); + fmt::print("fpcr {:08x}\n", fpscr); + fmt::print("fpcr.AHP {}\n", FP::FPCR{fpscr}.AHP()); + fmt::print("fpcr.DN {}\n", FP::FPCR{fpscr}.DN()); + fmt::print("fpcr.FZ {}\n", FP::FPCR{fpscr}.FZ()); + fmt::print("fpcr.RMode {}\n", static_cast<size_t>(FP::FPCR{fpscr}.RMode())); + fmt::print("fpcr.FZ16 {}\n", FP::FPCR{fpscr}.FZ16()); + fmt::print("\n"); + + fmt::print("Final register listing:\n"); + fmt::print(" unicorn dynarmic\n"); + const auto uni_regs = uni.GetRegisters(); + for (size_t i = 0; i < regs.size(); ++i) { + fmt::print("{:3s}: {:08x} {:08x} {}\n", static_cast<A32::Reg>(i), uni_regs[i], jit.Regs()[i], uni_regs[i] != jit.Regs()[i] ? "*" : ""); + } + const auto uni_ext_regs = uni.GetExtRegs(); + for (size_t i = 0; i < vecs.size(); ++i) { + fmt::print("s{:2d}: {:08x} {:08x} {}\n", static_cast<size_t>(i), uni_ext_regs[i], jit.ExtRegs()[i], uni_ext_regs[i] != jit.ExtRegs()[i] ? "*" : ""); + } + fmt::print("cpsr {:08x} {:08x} {}\n", uni.GetCpsr(), jit.Cpsr(), uni.GetCpsr() != jit.Cpsr() ? "*" : ""); + fmt::print("fpsr {:08x} {:08x} {}\n", uni.GetFpscr(), jit.Fpscr(), (uni.GetFpscr() & 0xF0000000) != (jit.Fpscr() & 0xF0000000) ? "*" : ""); + fmt::print("\n"); + + fmt::print("Modified memory:\n"); + fmt::print(" uni dyn\n"); + auto uni_iter = uni_env.modified_memory.begin(); + auto jit_iter = jit_env.modified_memory.begin(); + while (uni_iter != uni_env.modified_memory.end() || jit_iter != jit_env.modified_memory.end()) { + if (uni_iter == uni_env.modified_memory.end() || (jit_iter != jit_env.modified_memory.end() && uni_iter->first > jit_iter->first)) { + fmt::print("{:08x}: {:02x} *\n", jit_iter->first, jit_iter->second); + jit_iter++; + } else if (jit_iter == jit_env.modified_memory.end() || jit_iter->first > uni_iter->first) { + fmt::print("{:08x}: {:02x} *\n", uni_iter->first, uni_iter->second); + uni_iter++; + } else if (uni_iter->first == jit_iter->first) { + fmt::print("{:08x}: {:02x} {:02x} {}\n", uni_iter->first, uni_iter->second, jit_iter->second, uni_iter->second != jit_iter->second ? "*" : ""); + uni_iter++; + jit_iter++; + } + } + fmt::print("\n"); + + fmt::print("x86_64:\n"); + jit.DumpDisassembly(); + + fmt::print("Interrupts:\n"); + for (const auto& i : uni_env.interrupts) { + std::puts(i.c_str()); + } + }; + + REQUIRE(uni_env.code_mem_modified_by_guest == jit_env.code_mem_modified_by_guest); + if (uni_env.code_mem_modified_by_guest) { + return; + } + + // Qemu doesn't do Thumb transitions?? + { + const u32 uni_pc = uni.GetPC(); + const bool is_thumb = (jit.Cpsr() & (1 << 5)) != 0; + const u32 new_uni_pc = uni_pc & (is_thumb ? 0xFFFFFFFE : 0xFFFFFFFC); + uni.SetPC(new_uni_pc); + } + + if (uni.GetRegisters()[15] > jit.Regs()[15]) { + int trials = 0; + while (jit.Regs()[15] >= initial_pc && jit.Regs()[15] < expected_end_pc && trials++ < 100 && uni.GetRegisters()[15] != jit.Regs()[15]) { + fmt::print("Warning: Possible unicorn overrrun, attempt recovery\n"); + jit.Step(); + } + } + + REQUIRE(uni.GetRegisters() == jit.Regs()); + REQUIRE(uni.GetExtRegs() == jit.ExtRegs()); + REQUIRE((uni.GetCpsr() & 0xFFFFFDDF) == (jit.Cpsr() & 0xFFFFFDDF)); + REQUIRE((uni.GetFpscr() & 0xF8000000) == (jit.Fpscr() & 0xF8000000)); + REQUIRE(uni_env.modified_memory == jit_env.modified_memory); + REQUIRE(uni_env.interrupts.empty()); +} +} // Anonymous namespace + +TEST_CASE("A32: Single random arm instruction", "[arm]") { + ArmTestEnv jit_env{}; + ArmTestEnv uni_env{}; + + Dynarmic::A32::Jit jit{GetUserConfig(jit_env)}; + A32Unicorn<ArmTestEnv> uni{uni_env}; + + A32Unicorn<ArmTestEnv>::RegisterArray regs; + A32Unicorn<ArmTestEnv>::ExtRegArray ext_reg; + std::vector<u32> instructions(1); + + for (size_t iteration = 0; iteration < 100000; ++iteration) { + std::generate(regs.begin(), regs.end(), [] { return RandInt<u32>(0, ~u32(0)); }); + std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt<u32>(0, ~u32(0)); }); + + const u32 start_address = 100; + const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x10; + const u32 fpcr = RandomFpcr(); + + instructions[0] = GenRandomArmInst(start_address, true); + + INFO("Instruction: 0x" << std::hex << instructions[0]); + + regs[15] = start_address; + RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr, 1); + } +} + +TEST_CASE("A32: Small random arm block", "[arm]") { + ArmTestEnv jit_env{}; + ArmTestEnv uni_env{}; + + Dynarmic::A32::Jit jit{GetUserConfig(jit_env)}; + A32Unicorn<ArmTestEnv> uni{uni_env}; + + A32Unicorn<ArmTestEnv>::RegisterArray regs; + A32Unicorn<ArmTestEnv>::ExtRegArray ext_reg; + std::vector<u32> instructions(5); + + for (size_t iteration = 0; iteration < 100000; ++iteration) { + std::generate(regs.begin(), regs.end(), [] { return RandInt<u32>(0, ~u32(0)); }); + std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt<u32>(0, ~u32(0)); }); + + const u32 start_address = 100; + const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x10; + const u32 fpcr = RandomFpcr(); + + instructions[0] = GenRandomArmInst(start_address + 0, false); + instructions[1] = GenRandomArmInst(start_address + 4, false); + instructions[2] = GenRandomArmInst(start_address + 8, false); + instructions[3] = GenRandomArmInst(start_address + 12, false); + instructions[4] = GenRandomArmInst(start_address + 16, true); + + INFO("Instruction 1: 0x" << std::hex << instructions[0]); + INFO("Instruction 2: 0x" << std::hex << instructions[1]); + INFO("Instruction 3: 0x" << std::hex << instructions[2]); + INFO("Instruction 4: 0x" << std::hex << instructions[3]); + INFO("Instruction 5: 0x" << std::hex << instructions[4]); + + regs[15] = start_address; + RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr, 5); + } +} + +TEST_CASE("A32: Large random arm block", "[arm]") { + ArmTestEnv jit_env{}; + ArmTestEnv uni_env{}; + + Dynarmic::A32::Jit jit{GetUserConfig(jit_env)}; + A32Unicorn<ArmTestEnv> uni{uni_env}; + + A32Unicorn<ArmTestEnv>::RegisterArray regs; + A32Unicorn<ArmTestEnv>::ExtRegArray ext_reg; + + constexpr size_t instruction_count = 100; + std::vector<u32> instructions(instruction_count); + + for (size_t iteration = 0; iteration < 10000; ++iteration) { + std::generate(regs.begin(), regs.end(), [] { return RandInt<u32>(0, ~u32(0)); }); + std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt<u32>(0, ~u32(0)); }); + + const u64 start_address = 100; + const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x10; + const u32 fpcr = RandomFpcr(); + + for (size_t j = 0; j < instruction_count; ++j) { + instructions[j] = GenRandomArmInst(start_address + j * 4, j == instruction_count - 1); + } + + regs[15] = start_address; + RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr, 100); + } +} + +TEST_CASE("A32: Single random thumb instruction", "[thumb]") { + ThumbTestEnv jit_env{}; + ThumbTestEnv uni_env{}; + + Dynarmic::A32::Jit jit{GetUserConfig(jit_env)}; + A32Unicorn<ThumbTestEnv> uni{uni_env}; + + A32Unicorn<ThumbTestEnv>::RegisterArray regs; + A32Unicorn<ThumbTestEnv>::ExtRegArray ext_reg; + std::vector<u16> instructions; + + for (size_t iteration = 0; iteration < 100000; ++iteration) { + std::generate(regs.begin(), regs.end(), [] { return RandInt<u32>(0, ~u32(0)); }); + std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt<u32>(0, ~u32(0)); }); + + const u32 start_address = 100; + const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x1F0; + const u32 fpcr = RandomFpcr(); + + instructions = GenRandomThumbInst(start_address, true); + + INFO("Instruction: 0x" << std::hex << instructions[0]); + + regs[15] = start_address; + RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr, 1); + } +} + +TEST_CASE("A32: Single random thumb instruction (offset)", "[thumb]") { + ThumbTestEnv jit_env{}; + ThumbTestEnv uni_env{}; + + Dynarmic::A32::Jit jit{GetUserConfig(jit_env)}; + A32Unicorn<ThumbTestEnv> uni{uni_env}; + + A32Unicorn<ThumbTestEnv>::RegisterArray regs; + A32Unicorn<ThumbTestEnv>::ExtRegArray ext_reg; + std::vector<u16> instructions; + + for (size_t iteration = 0; iteration < 100000; ++iteration) { + std::generate(regs.begin(), regs.end(), [] { return RandInt<u32>(0, ~u32(0)); }); + std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt<u32>(0, ~u32(0)); }); + + const u32 start_address = 100; + const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x1F0; + const u32 fpcr = RandomFpcr(); + + instructions.clear(); + instructions.push_back(0xbf00); // NOP + const std::vector<u16> inst = GenRandomThumbInst(start_address + 2, true); + instructions.insert(instructions.end(), inst.begin(), inst.end()); + + INFO("Instruction: 0x" << std::hex << inst[0]); + + regs[15] = start_address; + RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr, 2); + } +} + +TEST_CASE("A32: Small random thumb block", "[thumb]") { + ThumbTestEnv jit_env{}; + ThumbTestEnv uni_env{}; + + Dynarmic::A32::Jit jit{GetUserConfig(jit_env)}; + A32Unicorn<ThumbTestEnv> uni{uni_env}; + + A32Unicorn<ThumbTestEnv>::RegisterArray regs; + A32Unicorn<ThumbTestEnv>::ExtRegArray ext_reg; + std::vector<u16> instructions; + + for (size_t iteration = 0; iteration < 100000; ++iteration) { + std::generate(regs.begin(), regs.end(), [] { return RandInt<u32>(0, ~u32(0)); }); + std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt<u32>(0, ~u32(0)); }); + + const u32 start_address = 100; + const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x1F0; + const u32 fpcr = RandomFpcr(); + + instructions.clear(); + for (size_t i = 0; i < 5; i++) { + const std::vector<u16> inst = GenRandomThumbInst(start_address + instructions.size() * 2, i == 4); + instructions.insert(instructions.end(), inst.begin(), inst.end()); + } + + regs[15] = start_address; + RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr, 5); + } +} + +TEST_CASE("A32: Test thumb IT instruction", "[thumb]") { + ThumbTestEnv jit_env{}; + ThumbTestEnv uni_env{}; + + Dynarmic::A32::Jit jit{GetUserConfig(jit_env)}; + A32Unicorn<ThumbTestEnv> uni{uni_env}; + + A32Unicorn<ThumbTestEnv>::RegisterArray regs; + A32Unicorn<ThumbTestEnv>::ExtRegArray ext_reg; + std::vector<u16> instructions; + + for (size_t iteration = 0; iteration < 100000; ++iteration) { + std::generate(regs.begin(), regs.end(), [] { return RandInt<u32>(0, ~u32(0)); }); + std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt<u32>(0, ~u32(0)); }); + + const size_t pre_instructions = RandInt<size_t>(0, 3); + const size_t post_instructions = RandInt<size_t>(5, 8); + + const u32 start_address = 100; + const u32 cpsr = (RandInt<u32>(0, 0xF) << 28) | 0x1F0; + const u32 fpcr = RandomFpcr(); + + instructions.clear(); + + for (size_t i = 0; i < pre_instructions; i++) { + const std::vector<u16> inst = GenRandomThumbInst(start_address + instructions.size() * 2, false); + instructions.insert(instructions.end(), inst.begin(), inst.end()); + } + + // Emit IT instruction + A32::ITState it_state = [&] { + while (true) { + const u16 imm8 = RandInt<u16>(0, 0xFF); + if (mcl::bit::get_bits<0, 3>(imm8) == 0b0000 || mcl::bit::get_bits<4, 7>(imm8) == 0b1111 || (mcl::bit::get_bits<4, 7>(imm8) == 0b1110 && mcl::bit::count_ones(mcl::bit::get_bits<0, 3>(imm8)) != 1)) { + continue; + } + instructions.push_back(0b1011111100000000 | imm8); + return A32::ITState{static_cast<u8>(imm8)}; + } + }(); + + for (size_t i = 0; i < post_instructions; i++) { + const std::vector<u16> inst = GenRandomThumbInst(start_address + instructions.size() * 2, i == post_instructions - 1, it_state); + instructions.insert(instructions.end(), inst.begin(), inst.end()); + it_state = it_state.Advance(); + } + + regs[15] = start_address; + RunTestInstance(jit, uni, jit_env, uni_env, regs, ext_reg, instructions, cpsr, fpcr, pre_instructions + 1 + post_instructions); + } +} |