use core::fmt;
use address_space::{Vram, VramOffset};
use crate::abi::Abi;
use crate::display_flags::InstructionDisplayFlags;
use crate::encoded_field_mask::EncodedFieldMask;
use crate::instr::{InstrField, InstructionDisplay, InstructionFlags, MnemonicDisplay};
#[cfg(any(
feature = "RSP",
feature = "R3000GTE",
feature = "R4000ALLEGREX",
feature = "R5900EE",
))]
use crate::isa::IsaExtension;
use crate::isa::IsaVersion;
use crate::opcodes::{Opcode, OpcodeCategory, OpcodeDecoder};
use crate::operands::{Operand, OperandIterator, ValuedOperandIterator};
use crate::registers::*;
use crate::registers_meta::Register;
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Instruction {
word: u32,
vram: Vram,
opcode_decoder: OpcodeDecoder,
flags: InstructionFlags,
}
impl Instruction {
#[must_use]
pub const fn new(word: u32, vram: Vram, flags: InstructionFlags) -> Self {
let opcode_decoder = OpcodeDecoder::decode(
word,
flags.decoding_flags(),
flags.isa_version(),
flags.isa_extension(),
);
Self {
word,
vram,
opcode_decoder,
flags,
}
}
#[must_use]
pub fn new_checked(word: u32, vram: Vram, flags: InstructionFlags) -> Option<Self> {
let instr = Self::new(word, vram, flags);
instr.is_valid().then_some(instr)
}
#[cfg(feature = "encoder")]
#[must_use]
pub(crate) const fn from_raw_parts(
word: u32,
vram: Vram,
opcode_decoder: OpcodeDecoder,
flags: InstructionFlags,
) -> Self {
Self {
word,
vram,
opcode_decoder,
flags,
}
}
}
impl Instruction {
#[must_use]
pub const fn word(&self) -> u32 {
self.word
}
#[must_use]
pub const fn vram(&self) -> Vram {
self.vram
}
#[must_use]
pub const fn isa_version(&self) -> IsaVersion {
self.flags.isa_version()
}
#[cfg(any(
feature = "RSP",
feature = "R3000GTE",
feature = "R4000ALLEGREX",
feature = "R5900EE",
))]
#[must_use]
pub const fn isa_extension(&self) -> Option<IsaExtension> {
self.flags.isa_extension()
}
#[must_use]
pub const fn opcode(&self) -> Opcode {
self.opcode_decoder.opcode()
}
#[must_use]
pub const fn opcode_category(&self) -> OpcodeCategory {
self.opcode_decoder.opcode_category()
}
#[must_use]
pub const fn flags(&self) -> &InstructionFlags {
&self.flags
}
#[must_use]
pub const fn abi(&self) -> Abi {
self.flags.abi()
}
}
impl Instruction {
pub const fn display<'ins, 'flg, T>(
&'ins self,
display_flags: &'flg InstructionDisplayFlags,
imm_override: Option<T>,
extra_ljust: i32,
) -> InstructionDisplay<'ins, 'flg, T>
where
T: fmt::Display,
{
InstructionDisplay::new(self, display_flags, imm_override, extra_ljust)
}
#[must_use]
#[doc(alias = "opcode_display")]
pub const fn mnemonic_display<'ins, 'flg>(
&'ins self,
display_flags: &'flg InstructionDisplayFlags,
) -> MnemonicDisplay<'ins, 'flg> {
MnemonicDisplay::new(self, display_flags)
}
}
impl Instruction {
#[must_use]
pub const fn is_nop(&self) -> bool {
OpcodeDecoder::is_nop(self.word)
}
#[must_use]
pub fn valid_bits(&self) -> EncodedFieldMask {
self.opcode_decoder.mandatory_bits() | self.opcode().valid_bits()
}
#[must_use]
pub fn is_valid(&self) -> bool {
if !self.opcode_decoder.is_valid(self.flags.decoding_flags()) {
return false;
}
let valid_bits = self.valid_bits().bits();
((!valid_bits) & self.word()) == 0
}
#[must_use]
pub fn is_unconditional_branch(&self) -> bool {
match self.opcode() {
Opcode::core_b => true,
Opcode::core_beq => {
self.field().rt_impl() == Gpr::zero && self.field().rs_impl() == Gpr::zero
}
Opcode::core_j => self.flags().j_as_branch(),
_ => false,
}
}
#[must_use]
pub fn is_function_call(&self) -> bool {
match self.opcode() {
Opcode::core_j => !self.flags().j_as_branch(),
opcode => opcode.does_link(),
}
}
#[must_use]
pub fn is_return(&self) -> bool {
match self.opcode() {
Opcode::core_jr => self.field().rs_impl().holds_return_address(self.abi()),
_ => false,
}
}
#[must_use]
pub fn is_jumptable_jump(&self) -> bool {
match self.opcode() {
Opcode::core_jr => !self.field().rs_impl().holds_return_address(self.abi()),
_ => false,
}
}
#[must_use]
pub fn is_likely_handwritten(&self) -> bool {
if self.opcode_category().handwritten_category() {
return true;
}
if self.opcode().not_emitted_by_compilers() {
return true;
}
if let Some(reg) = self.field().rs() {
if reg.is_kernel(self.abi()) {
return true;
}
}
if let Some(reg) = self.field().rt() {
if reg.is_kernel(self.abi()) {
return true;
}
}
if let Some(reg) = self.field().rd() {
if reg.is_kernel(self.abi()) {
return true;
}
}
false
}
}
impl Instruction {
#[must_use]
pub fn valued_operands_iter(&self) -> ValuedOperandIterator<'_> {
ValuedOperandIterator::new(self)
}
#[must_use]
pub fn operands_iter(&self) -> OperandIterator<'_> {
self.opcode().operands_iter()
}
#[must_use]
pub const fn field(&self) -> InstrField<'_> {
InstrField::new(self)
}
}
impl Instruction {
#[must_use]
#[inline(always)]
const fn vram_from_instr_index(&self, instr_index: u32) -> Vram {
let vram_raw = instr_index << 2;
let upper_bits = (self.vram.inner() + 4) & 0xF0000000;
Vram::new(upper_bits | vram_raw)
}
#[must_use]
pub fn get_instr_index_as_vram(&self) -> Option<Vram> {
self.field()
.instr_index()
.map(|instr_index| self.vram_from_instr_index(instr_index))
}
#[must_use]
pub(crate) const fn get_instr_index_as_vram_impl(&self) -> Vram {
self.vram_from_instr_index(self.field().instr_index_impl())
}
#[must_use]
pub fn get_branch_offset(&self) -> Option<VramOffset> {
if self
.opcode()
.has_operand_alias(Operand::core_branch_target_label)
{
Some(self.get_branch_offset_impl())
} else {
None
}
}
#[must_use]
pub(crate) fn get_branch_offset_impl(&self) -> VramOffset {
let imm: i32 = self.field().imm_i16_impl().into();
VramOffset::new((imm + 1) << 2)
}
#[must_use]
pub fn get_branch_offset_generic(&self) -> Option<VramOffset> {
if let Some(offset) = self.get_branch_offset() {
Some(offset)
} else if self.flags.j_as_branch() && self.opcode() == Opcode::core_j {
self.get_instr_index_as_vram().map(|vram| vram - self.vram)
} else {
None
}
}
#[must_use]
pub fn get_branch_vram_generic(&self) -> Option<Vram> {
if let Some(offset) = self.get_branch_offset() {
Some(offset + self.vram)
} else if self.flags.j_as_branch() && self.opcode() == Opcode::core_j {
self.get_instr_index_as_vram()
} else {
None
}
}
#[must_use]
pub fn get_destination_gpr(&self) -> Option<Gpr> {
if self.opcode().modifies_rd() {
Some(self.field().rd_impl())
} else if self.opcode().modifies_rt() {
Some(self.field().rt_impl())
} else if self.opcode().modifies_rs() {
Some(self.field().rs_impl())
} else {
None
}
}
#[must_use]
pub fn outputs_to_gpr_zero(&self) -> bool {
self.get_destination_gpr() == Some(Gpr::zero)
}
}
impl Instruction {
#[must_use]
pub fn clear_operand(&self, operand: Operand) -> Option<Self> {
if self.opcode().has_operand_alias(operand) {
let mut new_instr = *self;
new_instr.word = self.word() & !operand.mask().bits();
Some(new_instr)
} else {
None
}
}
pub fn clear_operand_self(&mut self, operand: Operand) -> bool {
if let Some(new_instr) = self.clear_operand(operand) {
*self = new_instr;
true
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::isa::*;
#[test]
fn check_j() {
let instr = Instruction::new(
0x08000004,
Vram::new(0x80000000),
InstructionFlags::new(IsaVersion::MIPS_I),
);
assert!(instr.is_valid());
assert_eq!(instr.opcode_category(), OpcodeCategory::CORE_NORMAL);
assert_eq!(instr.opcode(), Opcode::core_j);
assert!(instr.opcode().is_jump());
assert_eq!(
Instruction::new(
0x08000000,
Vram::new(0x80000000),
InstructionFlags::new(IsaVersion::MIPS_I),
)
.opcode(),
Opcode::core_j
);
}
#[test]
fn check_jal() {
let instr = Instruction::new(
0x0C000004,
Vram::new(0x80000000),
InstructionFlags::new(IsaVersion::MIPS_I),
);
assert!(instr.is_valid());
assert_eq!(instr.opcode(), Opcode::core_jal);
}
#[cfg(feature = "MIPS_III")]
#[test]
fn check_lwu() {
let flags = InstructionFlags::new(IsaVersion::MIPS_III);
let instr = Instruction::new(0x9C000000, Vram::new(0x80000000), flags);
assert!(instr.is_valid());
assert_eq!(instr.opcode(), Opcode::core_lwu);
let instr = Instruction::new(
0x9C000000,
Vram::new(0x80000000),
flags.with_isa_version(IsaVersion::MIPS_II),
);
assert!(!instr.is_valid());
assert_eq!(instr.opcode(), Opcode::ALL_INVALID);
}
#[cfg(feature = "MIPS_III")]
#[test]
fn check_invalid() {
let instr = Instruction::new(
0x0000072E,
Vram::new(0x80000000),
InstructionFlags::new(IsaVersion::MIPS_III),
);
assert!(!instr.is_valid());
assert_eq!(instr.opcode(), Opcode::core_dsub);
}
#[cfg(feature = "MIPS_III")]
#[test]
fn check_jal_is_not_branch() {
let instr = Instruction::new(
0x0C00E2F6,
Vram::new(0x80000000),
InstructionFlags::new(IsaVersion::MIPS_III),
);
assert!(instr.get_branch_vram_generic().is_none());
assert!(instr.get_instr_index_as_vram().is_some());
}
#[test]
fn check_clearing() {
let mut instr = Instruction::new(
0x0C00E2F6,
Vram::new(0x80000000),
InstructionFlags::new(IsaVersion::MIPS_I),
);
assert_eq!(
instr.clear_operand(Operand::core_imm_i16).map(|x| x.word()),
None,
"jal doesn't have an immediate field, so this should return the same word"
);
assert_eq!(
instr
.clear_operand(Operand::core_branch_target_label)
.map(|x| x.word()),
None,
"jal is not a branch, so this should return the same word"
);
assert_eq!(
instr.clear_operand(Operand::core_label).map(|x| x.word()),
Some(0x0C000000),
"jal has a label field, so clearing it should zero out the lower 26 bits"
);
assert_eq!(
instr.word(),
0x0C00E2F6,
"original instruction should remain unchanged"
);
let changed = instr.clear_operand_self(Operand::core_label);
assert!(changed);
assert_eq!(instr.word(), 0x0C000000);
}
#[test]
fn test_clear_operand_self_no_effect() {
let mut instr = Instruction::new(
0x0C00E2F6,
Vram::new(0x80000000),
InstructionFlags::new(IsaVersion::MIPS_I),
);
let changed = instr.clear_operand_self(Operand::core_imm_i16);
assert!(!changed);
assert_eq!(instr.word(), 0x0C00E2F6);
}
#[test]
fn test_clear_operand_on_branch_instruction() {
let instr = Instruction::new(
0x10000004,
Vram::new(0x80000000),
InstructionFlags::new(IsaVersion::MIPS_I),
);
let cleared = instr
.clear_operand(Operand::core_branch_target_label)
.unwrap();
assert_eq!(cleared.word(), 0x10000000);
}
#[test]
fn test_clear_operand_on_register_field() {
let instr = Instruction::new(
0x012A4020,
Vram::new(0x80000000),
InstructionFlags::new(IsaVersion::MIPS_I),
);
let cleared = instr.clear_operand(Operand::core_rd).unwrap();
assert_eq!(cleared.word(), 0x012A0020);
}
#[test]
fn test_clear_operand_multiple_fields() {
let instr = Instruction::new(
0x35281234,
Vram::new(0x80000000),
InstructionFlags::new(IsaVersion::MIPS_I),
);
let cleared_imm = instr.clear_operand(Operand::core_imm_u16).unwrap();
assert_eq!(cleared_imm.word(), 0x35280000);
let cleared_rt = instr.clear_operand(Operand::core_rt).unwrap();
assert_eq!(cleared_rt.word(), 0x35201234);
let cleared_rs = instr.clear_operand(Operand::core_rs).unwrap();
assert_eq!(cleared_rs.word(), 0x34081234);
assert_eq!(
instr.word(),
0x35281234,
"original instruction should remain unchanged"
);
let new_instr = instr
.clear_operand(Operand::core_imm_u16)
.and_then(|x| x.clear_operand(Operand::core_rt))
.and_then(|x| x.clear_operand(Operand::core_rs));
assert_eq!(
new_instr.map(|x| x.word()),
Some(0x34000000),
"should clear all three fields"
);
}
}