tests: Add test generator
This commit is contained in:
parent
52a46d841b
commit
3e5309bd96
2 changed files with 302 additions and 0 deletions
|
@ -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)
|
||||
|
|
287
tests/test_generator.cpp
Normal file
287
tests/test_generator.cpp
Normal file
|
@ -0,0 +1,287 @@
|
|||
/* This file is part of the dynarmic project.
|
||||
* Copyright (c) 2022 MerryMage
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <functional>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
|
||||
#include <mcl/bit/swap.hpp>
|
||||
#include <mcl/stdint.hpp>
|
||||
|
||||
#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 <fmt/format.h>
|
||||
#include <fmt/ostream.h>
|
||||
|
||||
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<IR::Term::Interpret>(&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<u16> GenRandomThumbInst(u32 pc, bool is_last_inst, A32::ITState it_state = {}) {
|
||||
static const struct InstructionGeneratorInfo {
|
||||
std::vector<InstructionGenerator> generators;
|
||||
std::vector<InstructionGenerator> invalid;
|
||||
} instructions = [] {
|
||||
const std::vector<std::tuple<std::string, const char*>> list{
|
||||
#define INST(fn, name, bitstring) {#fn, bitstring},
|
||||
#include "dynarmic/frontend/A32/decoder/thumb16.inc"
|
||||
//#include "dynarmic/frontend/A32/decoder/thumb32.inc"
|
||||
#undef INST
|
||||
};
|
||||
|
||||
const std::vector<std::tuple<std::string, const char*>> vfp_list{
|
||||
#define INST(fn, name, bitstring) {#fn, bitstring},
|
||||
//#include "dynarmic/frontend/A32/decoder/vfp.inc"
|
||||
#undef INST
|
||||
};
|
||||
|
||||
const std::vector<std::tuple<std::string, const char*>> asimd_list{
|
||||
#define INST(fn, name, bitstring) {#fn, bitstring},
|
||||
//#include "dynarmic/frontend/A32/decoder/asimd.inc"
|
||||
#undef INST
|
||||
};
|
||||
|
||||
std::vector<InstructionGenerator> generators;
|
||||
std::vector<InstructionGenerator> invalid;
|
||||
|
||||
// List of instructions not to test
|
||||
static constexpr std::array do_not_test{
|
||||
"thumb16_BKPT",
|
||||
"thumb16_IT",
|
||||
|
||||
// Exclusive load/stores
|
||||
"thumb32_LDREX",
|
||||
"thumb32_LDREXB",
|
||||
"thumb32_LDREXD",
|
||||
"thumb32_LDREXH",
|
||||
"thumb32_STREX",
|
||||
"thumb32_STREXB",
|
||||
"thumb32_STREXD",
|
||||
"thumb32_STREXH",
|
||||
|
||||
// Coprocessor
|
||||
"thumb32_CDP",
|
||||
"thumb32_LDC",
|
||||
"thumb32_MCR",
|
||||
"thumb32_MCRR",
|
||||
"thumb32_MRC",
|
||||
"thumb32_MRRC",
|
||||
"thumb32_STC",
|
||||
};
|
||||
|
||||
for (const auto& [fn, bitstring] : list) {
|
||||
if (std::find(do_not_test.begin(), do_not_test.end(), fn) != do_not_test.end()) {
|
||||
invalid.emplace_back(InstructionGenerator{bitstring});
|
||||
continue;
|
||||
}
|
||||
generators.emplace_back(InstructionGenerator{bitstring});
|
||||
}
|
||||
for (const auto& [fn, bs] : vfp_list) {
|
||||
std::string bitstring = bs;
|
||||
if (bitstring.substr(0, 4) == "cccc" || bitstring.substr(0, 4) == "----") {
|
||||
bitstring.replace(0, 4, "1110");
|
||||
}
|
||||
if (std::find(do_not_test.begin(), do_not_test.end(), fn) != do_not_test.end()) {
|
||||
invalid.emplace_back(InstructionGenerator{bitstring.c_str()});
|
||||
continue;
|
||||
}
|
||||
generators.emplace_back(InstructionGenerator{bitstring.c_str()});
|
||||
}
|
||||
for (const auto& [fn, bs] : asimd_list) {
|
||||
std::string bitstring = bs;
|
||||
if (bitstring.substr(0, 7) == "1111001") {
|
||||
const char U = bitstring[7];
|
||||
bitstring.replace(0, 8, "111-1111");
|
||||
bitstring[3] = U;
|
||||
} else if (bitstring.substr(0, 8) == "11110100") {
|
||||
bitstring.replace(0, 8, "11111001");
|
||||
} else {
|
||||
ASSERT_FALSE("Unhandled ASIMD instruction: {} {}", fn, bs);
|
||||
}
|
||||
if (std::find(do_not_test.begin(), do_not_test.end(), fn) != do_not_test.end()) {
|
||||
invalid.emplace_back(InstructionGenerator{bitstring.c_str()});
|
||||
continue;
|
||||
}
|
||||
generators.emplace_back(InstructionGenerator{bitstring.c_str()});
|
||||
}
|
||||
return InstructionGeneratorInfo{generators, invalid};
|
||||
}();
|
||||
|
||||
while (true) {
|
||||
const size_t index = RandInt<size_t>(0, instructions.generators.size() - 1);
|
||||
const u32 inst = instructions.generators[index].Generate();
|
||||
const bool is_four_bytes = (inst >> 16) != 0;
|
||||
|
||||
if (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;
|
||||
return user_config;
|
||||
}
|
||||
|
||||
template<typename TestEnv>
|
||||
static void RunTestInstance(Dynarmic::A32::Jit& jit,
|
||||
TestEnv& jit_env,
|
||||
const std::array<u32, 16>& regs,
|
||||
const std::array<u32, 64>& vecs,
|
||||
const std::vector<typename TestEnv::InstructionType>& instructions,
|
||||
const u32 cpsr,
|
||||
const u32 fpscr,
|
||||
const size_t ticks_left) {
|
||||
const u32 initial_pc = regs[15];
|
||||
const u32 num_words = initial_pc / sizeof(typename TestEnv::InstructionType);
|
||||
const u32 code_mem_size = num_words + static_cast<u32>(instructions.size());
|
||||
|
||||
jit_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<u32, 16> regs;
|
||||
std::array<u32, 64> ext_reg;
|
||||
std::vector<u16> instructions;
|
||||
|
||||
for (size_t iteration = 0; iteration < 5; ++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)); });
|
||||
|
||||
instructions = GenRandomThumbInst(0, true);
|
||||
|
||||
const u32 start_address = 100;
|
||||
const u32 cpsr = (RandInt<u32>(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;
|
||||
}
|
Loading…
Reference in a new issue