#![allow(clippy::uninlined_format_args)]
use rabbitizer::display_flags::InstructionDisplayFlags;
use rabbitizer::instr::{Instruction, InstructionFlags};
#[cfg(any(feature = "RSP", feature = "R4000ALLEGREX"))]
use rabbitizer::isa::IsaExtension;
use rabbitizer::opcodes::Opcode;
use rabbitizer::operands::OPERAND_COUNT_MAX;
use rabbitizer::vram::Vram;
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct TestEntry {
pub instr: Instruction,
pub imm_override: Option<&'static str>,
pub display_flags: InstructionDisplayFlags,
pub valid: bool,
pub expected: &'static str,
pub expected_opcode: Opcode,
pub opcode_str: &'static str,
pub operands_str: [Option<&'static str>; OPERAND_COUNT_MAX],
pub test_encoder: bool,
}
impl TestEntry {
#[allow(dead_code)]
const fn new_impl(
word: u32,
vram: Vram,
flags: InstructionFlags,
expected: &'static str,
expected_opcode: Opcode,
opcode_str: &'static str,
operands_str: [Option<&'static str>; OPERAND_COUNT_MAX],
) -> Self {
Self {
instr: Instruction::new(word, vram, flags),
imm_override: None,
display_flags: InstructionDisplayFlags::new(),
valid: true,
expected,
expected_opcode,
opcode_str,
operands_str,
test_encoder: true,
}
}
#[allow(dead_code)]
pub const fn new(
word: u32,
flags: InstructionFlags,
expected: &'static str,
expected_opcode: Opcode,
opcode_str: &'static str,
operands_str: [Option<&'static str>; OPERAND_COUNT_MAX],
) -> Self {
Self::new_impl(
word,
Vram::new(0x80000000),
flags,
expected,
expected_opcode,
opcode_str,
operands_str,
)
}
#[cfg(feature = "RSP")]
#[allow(dead_code)]
pub const fn new_rsp(
word: u32,
flags: InstructionFlags,
expected: &'static str,
expected_opcode: Opcode,
opcode_str: &'static str,
operands_str: [Option<&'static str>; OPERAND_COUNT_MAX],
) -> Self {
Self::new_impl(
word,
Vram::new(0xA4000000),
flags,
expected,
expected_opcode,
opcode_str,
operands_str,
)
}
#[cfg(feature = "RSP")]
#[allow(dead_code)]
pub const fn new_rsp_invalid(
word: u32,
flags: InstructionFlags,
expected: &'static str,
) -> Self {
Self {
instr: Instruction::new(
word,
Vram::new(0xA4000000),
flags.with_isa_extension(Some(IsaExtension::RSP)),
),
imm_override: None,
display_flags: InstructionDisplayFlags::default(),
valid: false,
expected,
expected_opcode: Opcode::ALL_INVALID,
opcode_str: "INVALID",
operands_str: [None, None, None, None, None],
test_encoder: false,
}
}
#[cfg(feature = "R4000ALLEGREX")]
#[allow(dead_code)]
pub const fn new_r4000allegrex(
word: u32,
expected: &'static str,
expected_opcode: Opcode,
opcode_str: &'static str,
operands_str: [Option<&'static str>; OPERAND_COUNT_MAX],
) -> Self {
use rabbitizer::abi::Abi;
Self::new(
word,
InstructionFlags::new_extension(IsaExtension::R4000ALLEGREX).with_abi(Abi::EABI64),
expected,
expected_opcode,
opcode_str,
operands_str,
)
}
#[allow(dead_code)]
pub const fn new_full_invalid(
word: u32,
flags: InstructionFlags,
expected: &'static str,
) -> Self {
Self {
instr: Instruction::new(word, Vram::new(0x80000000), flags),
imm_override: None,
display_flags: InstructionDisplayFlags::default(),
valid: false,
expected,
expected_opcode: Opcode::ALL_INVALID,
opcode_str: "INVALID",
operands_str: [None, None, None, None, None],
test_encoder: false,
}
}
#[allow(dead_code)]
pub const fn new_semi_invalid(
word: u32,
flags: InstructionFlags,
expected: &'static str,
expected_opcode: Opcode,
opcode_str: &'static str,
operands_str: [Option<&'static str>; OPERAND_COUNT_MAX],
) -> Self {
Self {
instr: Instruction::new(word, Vram::new(0x80000000), flags),
imm_override: None,
display_flags: InstructionDisplayFlags::default(),
valid: false,
expected,
expected_opcode,
opcode_str,
operands_str,
test_encoder: false,
}
}
#[allow(dead_code)]
pub const fn with_display_flags(self, display_flags: InstructionDisplayFlags) -> Self {
Self {
display_flags,
..self
}
}
pub fn compare_source_info(&self, other: &Self) -> bool {
self.instr.word() == other.instr.word()
&& self.instr.flags() == other.instr.flags()
&& self.imm_override == other.imm_override
&& self.display_flags == other.display_flags
&& self.valid == other.valid
}
pub fn check_validity(&self) -> u32 {
let mut errors = 0;
if self.instr.is_valid() != self.valid {
println!(
"'{}' ({:08X}) has incorrect validity. Expected '{}', got '{}'",
self.opcode_str,
self.instr.word(),
self.valid,
self.instr.is_valid()
);
errors += 1;
}
if self.instr.opcode() != self.expected_opcode {
println!(
"'{}' ({:08X}) has incorrect decoded opcode. Expected '{:?}', got '{:?}'",
self.opcode_str,
self.instr.word(),
self.expected_opcode,
self.instr.opcode()
);
errors += 1;
}
let generated_mnemonic = self.instr.mnemonic_display(&self.display_flags).to_string();
if generated_mnemonic != self.opcode_str {
println!(
"'{}' ({:08X}) has incorrect mnemonic. Expected '{}', got '{}'",
self.opcode_str,
self.instr.word(),
self.opcode_str,
generated_mnemonic,
);
errors += 1;
}
if self.instr.opcode().is_branch() {
if self.instr.get_branch_offset_generic().is_none() {
println!(
"'{}' ({:08X}) is a branch opcode but `get_branch_offset_generic` returned `None`.",
self.opcode_str,
self.instr.word()
);
errors += 1;
}
if self.instr.get_branch_vram_generic().is_none() {
println!(
"'{}' ({:08X}) is a branch but `get_branch_vram_generic` returned `None`.",
self.opcode_str,
self.instr.word()
);
errors += 1;
}
}
if self.instr.opcode() == Opcode::core_j {
if self.instr.flags().j_as_branch() {
if self.instr.get_branch_offset_generic().is_none() {
println!(
"'{}' ({:08X}) is the `j` opcode but `get_branch_offset_generic` returned `None`.",
self.opcode_str,
self.instr.word()
);
errors += 1;
}
if self.instr.get_branch_vram_generic().is_none() {
println!(
"'{}' ({:08X}) is the `j` but `get_branch_vram_generic` returned `None`.",
self.opcode_str,
self.instr.word()
);
errors += 1;
}
} else {
if self.instr.get_branch_offset_generic().is_some() {
println!(
"'{}' ({:08X}) is the `j` opcode but `get_branch_offset_generic` returned `Some` when `j_as_branch` is turned off.",
self.opcode_str,
self.instr.word()
);
errors += 1;
}
if self.instr.get_branch_vram_generic().is_some() {
println!(
"'{}' ({:08X}) is the `j` opcode but `get_branch_vram_generic` returned `Some` when `j_as_branch` is turned off.",
self.opcode_str,
self.instr.word()
);
errors += 1;
}
}
}
errors
}
pub fn check_disassembly(&self) -> u32 {
let mut errors = 0;
let display_flags = self.display_flags.with_debug_word_comment_info(true);
let disasm = self
.instr
.display(&display_flags, self.imm_override, 0)
.to_string();
if disasm != self.expected {
println!(
"'{}' ({:08X}) did not match the expected string.",
self.opcode_str,
self.instr.word(),
);
println!(" Expected: '{}'", self.expected,);
println!(" Got: '{}'", disasm,);
errors += 1;
}
{
let mut j = 0;
for (i, operand) in self.instr.operands_iter().enumerate() {
let operand_str = operand
.display(&self.instr, &display_flags, self.imm_override)
.to_string();
let maybe_expected_str = self.operands_str[i];
if let Some(expected_str) = maybe_expected_str {
if operand_str != expected_str {
println!(
"'{}' ({:08X}) has incorrect disassembled operand. Expected '{}', got '{}'",
self.opcode_str,
self.instr.word(),
expected_str,
operand_str
);
errors += 1;
}
} else {
println!(
"'{}' ({:08X}) has an unexpected operand at index {}. Got: '{}'",
self.opcode_str,
self.instr.word(),
i,
operand_str,
);
errors += 1;
}
j = i;
}
if !self.operands_str[j + 1..].iter().all(|x| x.is_none()) {
println!(
"'{}' ({:08X}) has unhandled expected operands. Values: '{:?}'",
self.opcode_str,
self.instr.word(),
&self.operands_str[j..]
);
errors += 1;
}
}
errors
}
#[cfg(feature = "encoder")]
pub fn check_encoding(&self) -> u32 {
use rabbitizer::encoder::{EncoderFlags, EncoderIterator};
if !self.valid {
return 0;
}
if self.expected.starts_with(".word") {
return 0;
}
if self.imm_override.is_some() {
return 0;
}
let mut encoder_flags = EncoderFlags::new(*self.instr.flags());
if !self.display_flags.use_dollar() {
*encoder_flags.allow_dollarless_mut() = true;
}
#[cfg(feature = "R5900EE")]
{
*encoder_flags.r5900ee_prodg_sn_as_inverted_regs_mut() =
self.display_flags.r5900ee_prodg_sn_as_inverted_regs();
}
let mut errors = 0;
let mut encoder = EncoderIterator::new(self.expected, self.instr.vram(), encoder_flags);
let display_flags = self.display_flags.with_debug_word_comment_info(true);
match encoder.next() {
None => {
println!("Unable to encode? '{}'", self.expected);
errors += 1;
}
Some(Err(e)) => {
println!(
"Unable to encode '{}' due to error:\n {}",
self.expected, e
);
errors += 1;
}
Some(Ok((instr, raw))) => {
let disasm = instr
.display(&display_flags, self.imm_override, 0)
.to_string();
if !instr.is_valid() {
println!(
"Encoded instruction '{}' (encoded '{}') is not valid?",
self.expected, disasm
);
errors += 1;
}
if instr.word() != self.instr.word() {
println!("Encoded instruction does not match the expected word.");
println!(
" Expected: '0x{:08X}' '{}'",
self.instr.word(),
self.expected
);
println!(" Got: '0x{:08X}' '{}'", instr.word(), disasm);
errors += 1;
}
if raw.trim() != self.expected.trim() {
println!("The returned raw range of the instruction '{}' does not match the expected text '{}'", raw, self.expected);
errors += 1;
}
}
}
match encoder.next() {
None => {}
Some(_) => {
println!(
"What? A second instruction was tried to be encoded while encoding '{}'?",
self.expected
);
errors += 1;
}
}
errors
}
}
pub fn entries_sanity_check(entries: &[TestEntry]) {
for (i, x) in entries.iter().enumerate() {
for y in entries[i + 1..].iter() {
assert!(
!x.compare_source_info(y),
"Duplicated entry. Word: '0x{:08X}'. imm_override: '{:?}'",
x.instr.word(),
x.imm_override
);
}
}
}
pub fn check_test_entries(entries: &[TestEntry]) -> (u32, u32) {
let mut instructions_with_errors = 0;
let mut individual_errors = 0;
entries_sanity_check(entries);
for entry in entries {
let mut errors = entry.check_validity();
errors += entry.check_disassembly();
#[cfg(feature = "encoder")]
if entry.test_encoder {
errors += entry.check_encoding();
}
individual_errors += errors;
if errors != 0 {
instructions_with_errors += 1;
println!();
}
}
(instructions_with_errors, individual_errors)
}