diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 05380ab3..8ed3bcc0 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -83,6 +83,21 @@ if (("A32" IN_LIST DYNARMIC_FRONTENDS) AND ("A64" IN_LIST DYNARMIC_FRONTENDS)) target_compile_definitions(dynarmic_print_info PRIVATE FMT_USE_USER_DEFINED_LITERALS=1) endif() +if ("A32" IN_LIST DYNARMIC_FRONTENDS) + add_executable(dynarmic_test_generator + fuzz_util.cpp + fuzz_util.h + test_generator.cpp + ) + + create_target_directory_groups(dynarmic_test_generator) + + target_link_libraries(dynarmic_test_generator PRIVATE dynarmic boost catch fmt) + target_include_directories(dynarmic_test_generator PRIVATE . ../src) + target_compile_options(dynarmic_test_generator PRIVATE ${DYNARMIC_CXX_FLAGS}) + target_compile_definitions(dynarmic_test_generator PRIVATE FMT_USE_USER_DEFINED_LITERALS=1) +endif() + create_target_directory_groups(dynarmic_tests) target_link_libraries(dynarmic_tests PRIVATE dynarmic boost catch fmt) diff --git a/tests/test_generator.cpp b/tests/test_generator.cpp new file mode 100644 index 00000000..872ec983 --- /dev/null +++ b/tests/test_generator.cpp @@ -0,0 +1,287 @@ +/* This file is part of the dynarmic project. + * Copyright (c) 2022 MerryMage + * SPDX-License-Identifier: 0BSD + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "./A32/testenv.h" +#include "./fuzz_util.h" +#include "./rand_int.h" +#include "dynarmic/common/fp/fpcr.h" +#include "dynarmic/common/fp/fpsr.h" +#include "dynarmic/frontend/A32/ITState.h" +#include "dynarmic/frontend/A32/a32_location_descriptor.h" +#include "dynarmic/frontend/A32/a32_types.h" +#include "dynarmic/frontend/A32/translate/a32_translate.h" +#include "dynarmic/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 +#include + +namespace { +using namespace Dynarmic; + +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(&terminal)) { + return false; + } + + for (const auto& ir_inst : block) { + switch (ir_inst.GetOpcode()) { + case IR::Opcode::A32ExceptionRaised: + case IR::Opcode::A32CallSupervisor: + case IR::Opcode::A32CoprocInternalOperation: + case IR::Opcode::A32CoprocSendOneWord: + case IR::Opcode::A32CoprocSendTwoWords: + case IR::Opcode::A32CoprocGetOneWord: + case IR::Opcode::A32CoprocGetTwoWords: + case IR::Opcode::A32CoprocLoadWords: + case IR::Opcode::A32CoprocStoreWords: + return false; + default: + continue; + } + } + + return true; +} + +std::vector GenRandomThumbInst(u32 pc, bool is_last_inst, A32::ITState it_state = {}) { + static const struct InstructionGeneratorInfo { + std::vector generators; + std::vector invalid; + } instructions = [] { + const std::vector> 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> vfp_list{ +#define INST(fn, name, bitstring) {#fn, bitstring}, +//#include "dynarmic/frontend/A32/decoder/vfp.inc" +#undef INST + }; + + const std::vector> asimd_list{ +#define INST(fn, name, bitstring) {#fn, bitstring}, +//#include "dynarmic/frontend/A32/decoder/asimd.inc" +#undef INST + }; + + std::vector generators; + std::vector invalid; + + // List of instructions not to test + static constexpr std::array do_not_test{ + "thumb16_BKPT", + "thumb16_IT", + + // Exclusive load/stores + "thumb32_LDREX", + "thumb32_LDREXB", + "thumb32_LDREXD", + "thumb32_LDREXH", + "thumb32_STREX", + "thumb32_STREXB", + "thumb32_STREXD", + "thumb32_STREXH", + + // Coprocessor + "thumb32_CDP", + "thumb32_LDC", + "thumb32_MCR", + "thumb32_MCRR", + "thumb32_MRC", + "thumb32_MRRC", + "thumb32_STC", + }; + + for (const auto& [fn, bitstring] : list) { + if (std::find(do_not_test.begin(), do_not_test.end(), fn) != do_not_test.end()) { + invalid.emplace_back(InstructionGenerator{bitstring}); + continue; + } + generators.emplace_back(InstructionGenerator{bitstring}); + } + for (const auto& [fn, bs] : vfp_list) { + std::string bitstring = bs; + if (bitstring.substr(0, 4) == "cccc" || bitstring.substr(0, 4) == "----") { + bitstring.replace(0, 4, "1110"); + } + if (std::find(do_not_test.begin(), do_not_test.end(), fn) != do_not_test.end()) { + invalid.emplace_back(InstructionGenerator{bitstring.c_str()}); + continue; + } + generators.emplace_back(InstructionGenerator{bitstring.c_str()}); + } + for (const auto& [fn, bs] : asimd_list) { + std::string bitstring = bs; + if (bitstring.substr(0, 7) == "1111001") { + const char U = bitstring[7]; + bitstring.replace(0, 8, "111-1111"); + bitstring[3] = U; + } else if (bitstring.substr(0, 8) == "11110100") { + bitstring.replace(0, 8, "11111001"); + } else { + ASSERT_FALSE("Unhandled ASIMD instruction: {} {}", fn, bs); + } + if (std::find(do_not_test.begin(), do_not_test.end(), fn) != do_not_test.end()) { + invalid.emplace_back(InstructionGenerator{bitstring.c_str()}); + continue; + } + generators.emplace_back(InstructionGenerator{bitstring.c_str()}); + } + return InstructionGeneratorInfo{generators, invalid}; + }(); + + while (true) { + const size_t index = RandInt(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(inst >> 16), static_cast(inst)}; + return {static_cast(inst)}; + } + } +} + +template +Dynarmic::A32::UserConfig GetUserConfig(TestEnv& testenv) { + Dynarmic::A32::UserConfig user_config; + user_config.optimizations &= ~OptimizationFlag::FastDispatch; + user_config.callbacks = &testenv; + return user_config; +} + +template +static void RunTestInstance(Dynarmic::A32::Jit& jit, + TestEnv& jit_env, + const std::array& regs, + const std::array& vecs, + const std::vector& 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(instructions.size()); + + jit_env.code_mem.resize(code_mem_size); + std::fill(jit_env.code_mem.begin(), jit_env.code_mem.end(), TestEnv::infinite_loop); + + std::copy(instructions.begin(), instructions.end(), jit_env.code_mem.begin() + num_words); + jit_env.PadCodeMem(); + jit_env.modified_memory.clear(); + jit_env.interrupts.clear(); + + jit.Regs() = regs; + jit.ExtRegs() = vecs; + jit.SetFpscr(fpscr); + jit.SetCpsr(cpsr); + jit.ClearCache(); + + jit_env.ticks_left = ticks_left; + jit.Run(); + + fmt::print("instructions: "); + for (auto instruction : instructions) { + if constexpr (sizeof(decltype(instruction)) == 2) { + fmt::print("{:04x}", instruction); + } else { + fmt::print("{:08x}", instruction); + } + } + fmt::print("\n"); + + fmt::print("initial_regs: "); + for (u32 i : regs) { + fmt::print("{:08x}", i); + } + fmt::print("\n"); + fmt::print("initial_vecs: "); + for (u32 i : vecs) { + fmt::print("{:08x}", i); + } + fmt::print("\n"); + fmt::print("initial_cpsr: {:08x}\n", cpsr); + fmt::print("initial_fpcr: {:08x}\n", fpscr); + + fmt::print("final_regs: "); + for (u32 i : jit.Regs()) { + fmt::print("{:08x}", i); + } + fmt::print("\n"); + fmt::print("final_vecs: "); + for (u32 i : jit.ExtRegs()) { + fmt::print("{:08x}", i); + } + fmt::print("\n"); + fmt::print("final_cpsr: {:08x}\n", jit.Cpsr()); + fmt::print("final_fpsr: {:08x}\n", jit.Fpscr()); + + fmt::print("mod_mem: "); + for (auto [addr, value] : jit_env.modified_memory) { + fmt::print("{:08x}:{:02x} ", addr, value); + } + fmt::print("\n"); + + fmt::print("interrupts:\n"); + for (const auto& i : jit_env.interrupts) { + std::puts(i.c_str()); + } + + fmt::print("===\n"); +} +} // Anonymous namespace + +int main(int, char*[]) { + detail::g_rand_int_generator.seed(42); + + ThumbTestEnv jit_env{}; + Dynarmic::A32::Jit jit{GetUserConfig(jit_env)}; + + std::array regs; + std::array ext_reg; + std::vector instructions; + + for (size_t iteration = 0; iteration < 5; ++iteration) { + std::generate(regs.begin(), regs.end(), [] { return RandInt(0, ~u32(0)); }); + std::generate(ext_reg.begin(), ext_reg.end(), [] { return RandInt(0, ~u32(0)); }); + + instructions = GenRandomThumbInst(0, true); + + const u32 start_address = 100; + const u32 cpsr = (RandInt(0, 0xF) << 28) | 0x1F0; + const u32 fpcr = RandomFpcr(); + + regs[15] = start_address; + RunTestInstance(jit, jit_env, regs, ext_reg, instructions, cpsr, fpcr, 1); + } + + return 0; +}