diff --git a/dcb/src/game/exe.rs b/dcb/src/game/exe.rs index 93d4c71..d83c51b 100644 --- a/dcb/src/game/exe.rs +++ b/dcb/src/game/exe.rs @@ -6,10 +6,12 @@ // Modules pub mod error; pub mod header; +pub mod instruction; // Exports pub use error::DeserializeError; pub use header::Header; +pub use instruction::Instruction; // Imports use crate::{io::address::Data, GameFile}; diff --git a/dcb/src/game/exe/instruction.rs b/dcb/src/game/exe/instruction.rs new file mode 100644 index 0000000..e02fbf0 --- /dev/null +++ b/dcb/src/game/exe/instruction.rs @@ -0,0 +1,125 @@ +//! Psx cpu instructions + +// Modules +pub mod directive; +pub mod pos; +pub mod pseudo; +pub mod raw; +pub mod reg; +pub mod simple; + +// Exports +pub use directive::Directive; +pub use pos::Pos; +pub use pseudo::PseudoInstruction; +pub use raw::{FromRawIter, Raw}; +pub use reg::Register; +pub use simple::SimpleInstruction; + +/// An assembler instruction +#[derive(PartialEq, Eq, Clone, Debug)] +#[derive(derive_more::Display)] +pub enum Instruction { + /// A simple instruction + Simple(SimpleInstruction), + + /// A pseudo instruction + Pseudo(PseudoInstruction), + + /// A directive + Directive(Directive), +} + +impl Instruction { + /// End of the code itself in the executable. + pub const CODE_END: Pos = Pos(0x8006dd3c); + /// Start of the code itself in the executable. + pub const CODE_START: Pos = Pos(0x80013e4c); +} + +/// Iterator adaptor for converting [`RawInstruction`]s into [`Instruction`]s. +pub struct Iter + Clone> { + /// Underlying iterator + iter: I, + + /// Remaining items from last iterator + remaining: Option>>, +} + +impl + Clone> Iter { + /// Helper function to try to decode without consuming the iterator + fn try_decode(iter: &I) -> (I, T::Decoded) { + let mut cloned_iter = iter.clone(); + let decoded = T::decode(&mut cloned_iter); + (cloned_iter, decoded) + } + + /// Helper function to try to get instructions from `T`. + fn try_next_from(&mut self, to_instruction: fn(T) -> Instruction) -> Option<(Pos, Instruction)> { + // Try to decode it and get all instructions + let (iter, instructions) = Self::try_decode::(&self.iter); + + // Map the instructions to be an iterator over `Instruction` and peekable + let mut instructions = instructions + .into_iter() + .map(move |(pos, decoded)| (pos, to_instruction(decoded))) + .peekable(); + + // Then check if we got any from the decode + match instructions.next() { + // If we did, set our iter, set any remaining instructions and return the instruction + Some(instruction) => { + self.iter = iter; + // If there are any instructions left, set remaining, else just leave it + if instructions.peek().is_some() { + self.remaining = Some(Box::new(instructions)); + } + Some(instruction) + }, + + // Else we didn't get anything, don't update the iterator. + None => None, + } + } + + /// Returns the current position of the iterator + fn cur_pos(&self) -> Option { + self.iter.clone().next().map(|raw| raw.pos) + } +} + +impl + Clone> Iterator for Iter { + type Item = (Pos, Instruction); + + fn next(&mut self) -> Option { + // If we have remaining instruction, supply them + if let Some(remaining) = self.remaining.as_mut() { + if let Some(instruction) = remaining.next() { + return Some(instruction); + } else { + // Note: We set it to none in case `next` is expensive to check. + self.remaining = None; + } + } + + // Else get the current position + let cur_pos = self.cur_pos()?; + + // If we're before the code start, just read directives + if cur_pos < Instruction::CODE_START || cur_pos >= Instruction::CODE_END { + return self.try_next_from(Instruction::Directive); + } + + // Else try to decode it as a pseudo, simple or directive, in that order. + self.try_next_from(Instruction::Pseudo) + .or_else(|| self.try_next_from(Instruction::Simple)) + .or_else(|| self.try_next_from(Instruction::Directive)) + } +} + +impl Instruction { + /// Adapts an iterator over raw words to an instruction iterator + pub fn new_iter + Clone>(iter: I) -> Iter { + Iter { iter, remaining: None } + } +} diff --git a/dcb/src/game/exe/instruction/directive.rs b/dcb/src/game/exe/instruction/directive.rs new file mode 100644 index 0000000..b873759 --- /dev/null +++ b/dcb/src/game/exe/instruction/directive.rs @@ -0,0 +1,137 @@ +//! Directives + +// Imports +use super::{FromRawIter, Pos, Raw}; +use ascii::{AsciiChar, AsciiStr, AsciiString}; +use AsciiChar::Null; + +/// A directive +#[derive(PartialEq, Eq, Clone, Debug)] +#[derive(derive_more::Display)] +pub enum Directive { + /// Write word + #[display(fmt = "dw {_0:#x}")] + Dw(u32), + + /// Repeated words + #[display(fmt = "dw {value:#x}, {len}")] + DwRepeated { + /// Value being repeated + value: u32, + + /// Times the value was repeated + len: usize, + }, + + /// Ascii string + #[display(fmt = ".ascii {_0:?}")] + Ascii(AsciiString), +} + + +/// Helper function to check if a string has null and if everything after the first +/// null is also null (or if there were no nulls). +fn check_nulls>(s: S) -> (S, usize, bool) { + let null_idx = s + .as_ref() + .as_slice() + .iter() + .position(|&ch| ch == Null) + .unwrap_or_else(|| s.as_ref().len()); + #[allow(clippy::indexing_slicing)] // `null_idx <= len` + let uniform_null = s.as_ref()[null_idx..].chars().all(|ch| ch == Null); + (s, null_idx, uniform_null) +} + + +impl FromRawIter for Directive { + type Decoded = Option<(Pos, Self)>; + + fn decode + Clone>(iter: &mut I) -> Self::Decoded { + // Get the first raw + let raw = iter.next()?; + + // Try to get an ascii string from the raw and check for nulls + #[allow(clippy::wildcard_enum_match_arm)] // Option won't get more variants + match AsciiString::from_ascii(raw.repr.to_ne_bytes()).map(check_nulls) { + // If we got a string with at least 1 non-null, but + // at least 1 null and uniformly null, return just it + Ok((mut ascii_string, null_idx @ 1..=3, true)) => { + ascii_string.truncate(null_idx); + Some((raw.pos, Self::Ascii(ascii_string))) + }, + + // If we got a string without any nulls, keep + // filling the string until we find one. + Ok((mut ascii_string, 4, true)) => { + let ascii_string = loop { + let mut cur_iter = iter.clone(); + match cur_iter.next() { + // If we don't have a next character, return the string as-is + // Note: No need to update the iterator, it returned `None`. + None => break ascii_string, + + // Else try to get it as a string and check for nulls + Some(next_raw) => match AsciiStr::from_ascii(&next_raw.repr.to_ne_bytes()).map(check_nulls) { + // If we got it and it wasn't null, update the iterator, add it and continue + Ok((new_ascii_str, 4, _)) => { + *iter = cur_iter; + ascii_string.push_str(new_ascii_str); + }, + + // If we got it, but there was a uniform null, update the iterator, + // add the non-null parts and return. + #[allow(clippy::indexing_slicing)] // `null_idx < len` + Ok((new_ascii_str, null_idx, true)) => { + *iter = cur_iter; + ascii_string.push_str(&new_ascii_str[..null_idx]); + break ascii_string; + }, + + // If we didn't get it or it was a non-uniform null, return the string we have so far + // Note: We don't update the iterator, as we want to leave + // the next value to `dw`. + Err(_) | Ok((_, _, false)) => break ascii_string, + }, + } + }; + + Some((raw.pos, Self::Ascii(ascii_string))) + }, + + // Else if it was full null, non-uniformly null or non-ascii, + // try to get a dw table + _ => { + let mut times_repeated = 0; + + // Keep getting values until either eof or a different one + loop { + let mut cur_iter = iter.clone(); + match cur_iter.next().map(|next_raw| next_raw.repr == raw.repr) { + // If we got a different value, keep fetching values until they're different + Some(true) => { + *iter = cur_iter; + times_repeated += 1; + }, + + // If we didn't get it or we got a different value, exit + // Note: No need t update the iterator, as it either returned `None` or + // a different raw. + None | Some(false) => match times_repeated { + // If the value didn't repeat, use a single `dw` + 0 => break Some((raw.pos, Self::Dw(raw.repr))), + + // Else return the table + _ => { + break Some((raw.pos, Self::DwRepeated { + value: raw.repr, + len: times_repeated + 1, + })) + }, + }, + } + } + }, + } + } +} diff --git a/dcb/src/game/exe/instruction/pos.rs b/dcb/src/game/exe/instruction/pos.rs new file mode 100644 index 0000000..394d082 --- /dev/null +++ b/dcb/src/game/exe/instruction/pos.rs @@ -0,0 +1,36 @@ +//! Instruction position +// TODO: More implementations for `Pos` + +// Imports +use std::{fmt, ops}; + +/// An instruction position +#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Debug)] +#[derive(ref_cast::RefCast)] +#[repr(transparent)] +pub struct Pos(pub u32); + +impl fmt::LowerHex for Pos { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + ::fmt(&self.0, f) + } +} + +impl ops::Sub for Pos { + type Output = Self; + + fn sub(self, rhs: u32) -> Self::Output { + Self(self.0 - rhs) + } +} + +impl<'a, T> ops::Sub for &'_ Pos +where + Pos: ops::Sub, +{ + type Output = Pos; + + fn sub(self, rhs: T) -> Self::Output { + >::sub(Pos(self.0), rhs) + } +} diff --git a/dcb/src/game/exe/instruction/pseudo.rs b/dcb/src/game/exe/instruction/pseudo.rs new file mode 100644 index 0000000..54551ff --- /dev/null +++ b/dcb/src/game/exe/instruction/pseudo.rs @@ -0,0 +1,385 @@ +//! Pseudo instructions + +// Imports +use super::{FromRawIter, Pos, Raw, Register, SimpleInstruction}; +use crate::util::SignedHex; +use int_conv::{Join, SignExtended, Signed, ZeroExtended}; + +/// A pseudo instruction +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[derive(derive_more::Display)] +#[allow(clippy::missing_docs_in_private_items)] // Mostly just register names and immediates. +pub enum PseudoInstruction { + /// No-op + /// alias for `sll $zr,$zr,0` + #[display(fmt = "nop")] + Nop, + + /// Move register + /// Alias for `{addu|addiu|or} $rd, $rs, $zr` + #[display(fmt = "move {rd}, {rs}")] + MovReg { rd: Register, rs: Register }, + + /// Load byte immediate + /// Alias for `lui $rx, {offset-hi} / lb $rx, {offset-lo}($rx)` + #[display(fmt = "lb {rx}, {offset:#x}")] + LbImm { rx: Register, offset: u32 }, + + /// Load byte unsigned immediate + /// Alias for `lui $rx, {offset-hi} / lbu $rx, {offset-lo}($rx)` + #[display(fmt = "lbu {rx}, {offset:#x}")] + LbuImm { rx: Register, offset: u32 }, + + /// Load half-word immediate + /// Alias for `lui $rx, {offset-hi} / lh $rx, {offset-lo}($rx)` + #[display(fmt = "lh {rx}, {offset:#x}")] + LhImm { rx: Register, offset: u32 }, + + /// Load half-word unsigned immediate + /// Alias for `lui $rx, {offset-hi} / lhu $rx, {offset-lo}($rx)` + #[display(fmt = "lh {rx}, {offset:#x}")] + LhuImm { rx: Register, offset: u32 }, + + /// Load left word immediate + /// Alias for `lui $rx, {offset-hi} / lwl $rx, {offset-lo}($rx)` + #[display(fmt = "lwl {rx}, {offset:#x}")] + LwlImm { rx: Register, offset: u32 }, + + /// Load word immediate + /// Alias for `lui $rx, {offset-hi} / lw $rx, {offset-lo}($rx)` + #[display(fmt = "lw {rx}, {offset:#x}")] + LwImm { rx: Register, offset: u32 }, + + /// Load right word immediate + /// Alias for `lui $rx, {offset-hi} / lwr $rx, {offset-lo}($rx)` + #[display(fmt = "lwr {rx}, {offset:#x}")] + LwrImm { rx: Register, offset: u32 }, + + /// Store byte immediate + /// Alias for `lui $at, {offset-hi} / sb $rx, {offset-lo}($at)` + #[display(fmt = "sb {rx}, {offset:#x}")] + SbImm { rx: Register, offset: u32 }, + + /// Store half-word immediate + /// Alias for `lui $at, {offset-hi} / sh $rx, {offset-lo}($at)` + #[display(fmt = "sh {rx}, {offset:#x}")] + ShImm { rx: Register, offset: u32 }, + + /// Store left word immediate + /// Alias for `lui $at, {offset-hi} / swl $rx, {offset-lo}($at)` + #[display(fmt = "swl {rx}, {offset:#x}")] + SwlImm { rx: Register, offset: u32 }, + + /// Store word immediate + /// Alias for `lui $at, {offset-hi} / sw $rx, {offset-lo}($at)` + #[display(fmt = "sw {rx}, {offset:#x}")] + SwImm { rx: Register, offset: u32 }, + + /// Store right word immediate + /// Alias for `lui $at, {offset-hi} / swr $rx, {offset-lo}($at)` + #[display(fmt = "swr {rx}, {offset:#x}")] + SwrImm { rx: Register, offset: u32 }, + + /// Load address + /// Alias for `lui $rx, {target-hi} / addiu $rx, $rx, {target-lo}` + #[display(fmt = "la {rx}, {target:#x}")] + La { rx: Register, target: u32 }, + + /// Load immediate 32-bit + /// Alias for `lui $rx, {imm-hi} / ori $rx, $rx, {imm-lo}` + #[display(fmt = "li {rx}, {imm:#x}")] + Li32 { rx: Register, imm: u32 }, + + /// Load immediate 16-bit + /// Alias for `ori $rx, $zr, imm` + #[display(fmt = "li {rx}, {imm:#x}")] + Li16 { rx: Register, imm: u16 }, + + /// Load immediate negative 15-bit + /// Alias for `addiu $rx, $zr, imm`, with imm in `-0x8000 .. -0x1` + #[display(fmt = "li {rx}, {:#x}", "SignedHex(imm)")] + LiNeg15 { rx: Register, imm: i16 }, + + /// Load immediate upper 16-bits + /// Alias for `lui 0x1000 * imm` + #[display(fmt = "li {rx}, {:#x}", "imm.zero_extended::() << 16")] + LiUpper16 { rx: Register, imm: u16 }, + + /// Add assign + /// Alias for `add $rx, $rx, $rt` + #[display(fmt = "add {rx}, {rt}")] + AddAssign { rx: Register, rt: Register }, + + /// Add unsigned assign + /// Alias for `addu $rx, $rx, $rt` + #[display(fmt = "addu {rx}, {rt}")] + AdduAssign { rx: Register, rt: Register }, + + /// Sub assign + /// Alias for `sub $rx, $rx, $rt` + #[display(fmt = "sub {rx}, {rt}")] + SubAssign { rx: Register, rt: Register }, + + /// Sub unsigned assign + /// Alias for `subu $rx, $rx, $rt` + #[display(fmt = "subu {rx}, {rt}")] + SubuAssign { rx: Register, rt: Register }, + + /// And assign + /// Alias for `and $rx, $rx, $rt` + #[display(fmt = "and {rx}, {rt}")] + AndAssign { rx: Register, rt: Register }, + + /// Or assign + /// Alias for `or $rx, $rx, $rt` + #[display(fmt = "or {rx}, {rt}")] + OrAssign { rx: Register, rt: Register }, + + /// Xor assign + /// Alias for `xor $rx, $rx, $rt` + #[display(fmt = "xor {rx}, {rt}")] + XorAssign { rx: Register, rt: Register }, + + /// Nor assign + /// Alias for `nor $rx, $rx, $rt` + #[display(fmt = "nor {rx}, {rt}")] + NorAssign { rx: Register, rt: Register }, + + /// And immediate assign + /// Alias for `andi $rx, $rx, imm` + #[display(fmt = "andi {rx}, {imm:#x}")] + AndiAssign { rx: Register, imm: u16 }, + + /// Or immediate assign + /// Alias for `ori $rx, $rx, imm` + #[display(fmt = "ori {rx}, {imm:#x}")] + OriAssign { rx: Register, imm: u16 }, + + /// Xor immediate assign + /// Alias for `xori $rx, $rx, imm` + #[display(fmt = "xori {rx}, {imm:#x}")] + XoriAssign { rx: Register, imm: u16 }, + + /// Shift left logical variable assign + /// Alias for `sllv $rx, $rx, $rs` + #[display(fmt = "sllv {rx} {rs}")] + SllvAssign { rx: Register, rs: Register }, + + /// Shift right logical variable assign + /// Alias for `srlv $rx, $rx, $rs` + #[display(fmt = "srlv {rx} {rs}")] + SrlvAssign { rx: Register, rs: Register }, + + /// Shift right arithmetic variable assign + /// Alias for `srav $rx, $rx, $rs` + #[display(fmt = "srav {rx} {rs}")] + SravAssign { rx: Register, rs: Register }, + + /// Shift left logical assign + /// Alias for `sll $rx, $rx, imm` + #[display(fmt = "sll {rx} {imm:#x}")] + SllAssign { rx: Register, imm: u8 }, + + /// Shift right logical assign + /// Alias for `srl $rx, $rx, imm` + #[display(fmt = "srl {rx} {imm:#x}")] + SrlAssign { rx: Register, imm: u8 }, + + /// Shift right arithmetic assign + /// Alias for `sla $rx, $rx, imm` + #[display(fmt = "sra {rx} {imm:#x}")] + SraAssign { rx: Register, imm: u8 }, + + /// Jump and link with return address + /// Alias for `jalr $ra, $rx` + #[display(fmt = "jalr {rx}")] + JalrRa { rx: Register }, + + /// Subtract immediate + /// Alias for `addi $rt, $rs, imm` for negative `imm`s + #[display(fmt = "subi {rt}, {rs}, {:#x}", "SignedHex(imm)")] + Subi { rt: Register, rs: Register, imm: i16 }, + + /// Subtract immediate sign-extended + /// Alias for `addiu $rt, $rs, imm` for negative `imm`s + #[display(fmt = "subiu {rt}, {rs}, {:#x}", "SignedHex(imm)")] + Subiu { rt: Register, rs: Register, imm: i16 }, + + /// Subtract immediate assign + /// Alias for `subi $rx, $rx, imm` + #[display(fmt = "subi {rx}, {:#x}", "SignedHex(imm)")] + SubiAssign { rx: Register, imm: i16 }, + + /// Subtract immediate sign-extended assign + /// Alias for `subiu $rx, $rx, imm` + #[display(fmt = "subiu {rx}, {:#x}", "SignedHex(imm)")] + SubiuAssign { rx: Register, imm: i16 }, + + /// Branch if equal to zero + /// Alias for `beq $rx, $zr, target` + #[display(fmt = "beqz {rx}, {target:#x}")] + Beqz { rx: Register, target: u32 }, + + /// Branch if different from zero + /// Alias for `bne $rx, $zr, target` + #[display(fmt = "bnez {rx}, {target:#x}")] + Bnez { rx: Register, target: u32 }, + + /// Jump relative + /// Alias for `beq $zr, $zr, target` + #[display(fmt = "b {target:#x}")] + B { target: u32 }, + // TODO: Push / Pop +} + +impl FromRawIter for PseudoInstruction { + type Decoded = Option<(Pos, Self)>; + + #[allow(clippy::wildcard_enum_match_arm)] // New entries won't affect this function, it can only act on entries it knows. + #[allow(clippy::similar_names)] // With register names, this happens too much + #[allow(clippy::too_many_lines, clippy::clippy::cognitive_complexity)] // We can't separate this into several functions, it's just 1 big match + fn decode + Clone>(iter: &mut I) -> Self::Decoded { + use Register::{At, Ra, Zr}; + use SimpleInstruction::{ + Add, Addi, Addiu, Addu, And, Andi, Beq, Bne, Jalr, Lb, Lbu, Lh, Lhu, Lui, Lw, Lwl, Lwr, Nor, Or, Ori, Sb, Sh, Sll, Sllv, Sra, Srav, Srl, + Srlv, Sub, Subu, Sw, Swl, Swr, Xor, Xori, + }; + + // Get the first instruction + let (pos, instruction) = SimpleInstruction::decode(iter)?; + let pseudo = match instruction { + Lui { imm: imm_hi, rt: prev_rt } => { + let iter_before = iter.clone(); + match SimpleInstruction::decode(iter)?.1 { + Addiu { imm: imm_lo, rt, rs } if rt == prev_rt && rs == prev_rt => Some(Self::La { + rx: prev_rt, + // Note: `imm_lo` is signed + target: (u32::join(0, imm_hi).as_signed() + imm_lo.sign_extended::()).as_unsigned(), + }), + Ori { imm: imm_lo, rt, rs } if rt == prev_rt && rs == prev_rt => Some(Self::Li32 { + rx: prev_rt, + imm: u32::join(imm_lo, imm_hi), + }), + + Lb { offset: imm_lo, rt, rs } if rt == prev_rt && rs == prev_rt => Some(Self::LbImm { + rx: prev_rt, + offset: u32::join(imm_lo, imm_hi), + }), + Lbu { offset: imm_lo, rt, rs } if rt == prev_rt && rs == prev_rt => Some(Self::LbuImm { + rx: prev_rt, + offset: u32::join(imm_lo, imm_hi), + }), + Lh { offset: imm_lo, rt, rs } if rt == prev_rt && rs == prev_rt => Some(Self::LhImm { + rx: prev_rt, + offset: u32::join(imm_lo, imm_hi), + }), + Lhu { offset: imm_lo, rt, rs } if rt == prev_rt && rs == prev_rt => Some(Self::LhuImm { + rx: prev_rt, + offset: u32::join(imm_lo, imm_hi), + }), + Lwl { offset: imm_lo, rt, rs } if rt == prev_rt && rs == prev_rt => Some(Self::LwlImm { + rx: prev_rt, + offset: u32::join(imm_lo, imm_hi), + }), + Lw { offset: imm_lo, rt, rs } if rt == prev_rt && rs == prev_rt => Some(Self::LwImm { + rx: prev_rt, + offset: u32::join(imm_lo, imm_hi), + }), + Lwr { offset: imm_lo, rt, rs } if rt == prev_rt && rs == prev_rt => Some(Self::LwrImm { + rx: prev_rt, + offset: u32::join(imm_lo, imm_hi), + }), + + Sb { offset: imm_lo, rt, rs } if prev_rt == At && rs == At => Some(Self::SbImm { + rx: rt, + offset: u32::join(imm_lo, imm_hi), + }), + Sh { offset: imm_lo, rt, rs } if prev_rt == At && rs == At => Some(Self::ShImm { + rx: rt, + offset: u32::join(imm_lo, imm_hi), + }), + Swl { offset: imm_lo, rt, rs } if prev_rt == At && rs == At => Some(Self::SwlImm { + rx: rt, + offset: u32::join(imm_lo, imm_hi), + }), + Sw { offset: imm_lo, rt, rs } if prev_rt == At && rs == At => Some(Self::SwImm { + rx: rt, + offset: u32::join(imm_lo, imm_hi), + }), + Swr { offset: imm_lo, rt, rs } if prev_rt == At && rs == At => Some(Self::SwrImm { + rx: rt, + offset: u32::join(imm_lo, imm_hi), + }), + // Since we don't use the value, reset the iterator to it's previous value. + _ => { + *iter = iter_before; + Some(Self::LiUpper16 { rx: prev_rt, imm: imm_hi }) + }, + } + }, + + Sll { rd: Zr, rt: Zr, imm: 0 } => Some(Self::Nop), + + Addu { rd, rs, rt: Zr } | Addiu { rt: rd, rs, imm: 0 } | Or { rd, rs, rt: Zr } => Some(Self::MovReg { rd, rs }), + + Ori { rt, rs: Zr, imm } => Some(Self::Li16 { rx: rt, imm }), + + Add { rd, rs, rt } if rd == rs => Some(Self::AddAssign { rx: rd, rt }), + Addu { rd, rs, rt } if rd == rs => Some(Self::AdduAssign { rx: rd, rt }), + Sub { rd, rs, rt } if rd == rs => Some(Self::SubAssign { rx: rd, rt }), + Subu { rd, rs, rt } if rd == rs => Some(Self::SubuAssign { rx: rd, rt }), + + And { rd, rs, rt } if rd == rs => Some(Self::AndAssign { rx: rd, rt }), + Or { rd, rs, rt } if rd == rs => Some(Self::OrAssign { rx: rd, rt }), + Xor { rd, rs, rt } if rd == rs => Some(Self::XorAssign { rx: rd, rt }), + Nor { rd, rs, rt } if rd == rs => Some(Self::NorAssign { rx: rd, rt }), + + Andi { rt, rs, imm } if rt == rs => Some(Self::AndiAssign { rx: rt, imm }), + Ori { rt, rs, imm } if rt == rs => Some(Self::OriAssign { rx: rt, imm }), + Xori { rt, rs, imm } if rt == rs => Some(Self::XoriAssign { rx: rt, imm }), + + Sllv { rd, rt, rs } if rd == rt => Some(Self::SllvAssign { rx: rd, rs }), + Srlv { rd, rt, rs } if rd == rt => Some(Self::SrlvAssign { rx: rd, rs }), + Srav { rd, rt, rs } if rd == rt => Some(Self::SravAssign { rx: rd, rs }), + + Sll { rd, rt, imm } if rd == rt => Some(Self::SllAssign { rx: rd, imm }), + Srl { rd, rt, imm } if rd == rt => Some(Self::SrlAssign { rx: rd, imm }), + Sra { rd, rt, imm } if rd == rt => Some(Self::SraAssign { rx: rd, imm }), + + Jalr { rd: Ra, rs: rx } => Some(Self::JalrRa { rx }), + + Addi { + rt, + rs, + imm: imm @ i16::MIN..0, + } => match rt == rs { + true => Some(Self::SubiAssign { rx: rt, imm }), + false => Some(Self::Subi { rt, rs, imm }), + }, + + Addiu { + rt: rx, + rs: Zr, + imm: imm @ i16::MIN..0, + } => Some(Self::LiNeg15 { rx, imm }), + + Addiu { + rt, + rs, + imm: imm @ i16::MIN..0, + } => match rt == rs { + true => Some(Self::SubiuAssign { rx: rt, imm }), + false => Some(Self::Subiu { rt, rs, imm }), + }, + + Beq { rs: Zr, rt: Zr, target } => Some(Self::B { target }), + Beq { rs: rx, rt: Zr, target } => Some(Self::Beqz { rx, target }), + Bne { rs: rx, rt: Zr, target } => Some(Self::Bnez { rx, target }), + + // Note: No need to reset iterator, it returned `None`. + _ => None, + }; + + pseudo.map(|pseudo_instruction| (pos, pseudo_instruction)) + } +} diff --git a/dcb/src/game/exe/instruction/raw.rs b/dcb/src/game/exe/instruction/raw.rs new file mode 100644 index 0000000..1e04478 --- /dev/null +++ b/dcb/src/game/exe/instruction/raw.rs @@ -0,0 +1,27 @@ +//! Raw instructions + +// Imports +use super::Pos; + +/// A raw instruction +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct Raw { + /// The raw encoding of the instruction + pub repr: u32, + + /// The position of this instruction + pub pos: Pos, +} + +/// Raw instruction decoding +/// +/// Implementors should be atomic about the consumed and +/// returned iterator, that is, consume the least possible +/// input in order to produce an atomic part of themselves. +pub trait FromRawIter: Sized { + /// Returned iterator from [`decode`] + type Decoded: IntoIterator; + + /// Attempts to decode an instruction from an iterator of raw instructions + fn decode + Clone>(iter: &mut I) -> Self::Decoded; +} diff --git a/dcb/src/game/exe/instruction/reg.rs b/dcb/src/game/exe/instruction/reg.rs new file mode 100644 index 0000000..80e260b --- /dev/null +++ b/dcb/src/game/exe/instruction/reg.rs @@ -0,0 +1,135 @@ +//! Cpu registers + +// Macro to generate `Register` +macro_rules! generate_register { + ( + pub enum Register { + $( + $( #[doc = $doc:literal] )? + #[display(fmt = $fmt:literal)] + $variant:ident = $value:expr + ),+ $(,)? + } + ) => { + #[derive(PartialEq, Eq, Clone, Copy, Debug)] + #[derive(derive_more::Display)] + pub enum Register { + $( + $( #[doc = $doc] )? + #[display(fmt = $fmt)] + $variant = $value, + )* + } + + impl Register { + /// Array containing all registers + pub const ALL_REGISTERS: [Self; 32] = [ + $( + Self::$variant, + )* + ]; + + /// Creates a new register index from a `u8`. + #[must_use] + pub const fn new(idx: u8) -> Option { + match idx { + $( + $value => Some( Self::$variant ), + )* + + _ => None, + } + } + } + + impl From for usize { + fn from(idx: Register) -> Self { + match idx { + $( + Register::$variant => $value, + )* + } + } + } + + impl std::str::FromStr for Register { + type Err = (); + + fn from_str(s: &str) -> Result { + match s.trim() { + $( + $fmt => Ok(Self::$variant), + )* + + _ => Err(()) + } + } + } + } +} + +generate_register! { + pub enum Register { + /// Zero register + #[display(fmt = "$zr")] + Zr = 0, + + /// Assembler temporary + #[display(fmt = "$at")] + At = 1, + + // Return values + #[display(fmt = "$v0")] V0 = 2, + #[display(fmt = "$v1")] V1 = 3, + + // Arguments + #[display(fmt = "$a0")] A0 = 4, + #[display(fmt = "$a1")] A1 = 5, + #[display(fmt = "$a2")] A2 = 6, + #[display(fmt = "$a3")] A3 = 7, + + // Temporaries + #[display(fmt = "$t0")] T0 = 8, + #[display(fmt = "$t1")] T1 = 9, + #[display(fmt = "$t2")] T2 = 10, + #[display(fmt = "$t3")] T3 = 11, + #[display(fmt = "$t4")] T4 = 12, + #[display(fmt = "$t5")] T5 = 13, + #[display(fmt = "$t6")] T6 = 14, + #[display(fmt = "$t7")] T7 = 15, + + // Static variables + #[display(fmt = "$s0")] S0 = 16, + #[display(fmt = "$s1")] S1 = 17, + #[display(fmt = "$s2")] S2 = 18, + #[display(fmt = "$s3")] S3 = 19, + #[display(fmt = "$s4")] S4 = 20, + #[display(fmt = "$s5")] S5 = 21, + #[display(fmt = "$s6")] S6 = 22, + #[display(fmt = "$s7")] S7 = 23, + + // Temporaries + #[display(fmt = "$t8")] T8 = 24, + #[display(fmt = "$t9")] T9 = 25, + + // Kernel + #[display(fmt = "$k0")] K0 = 26, + #[display(fmt = "$k1")] K1 = 27, + + /// Global pointer + #[display(fmt = "$gp")] + Gp = 28, + + /// Stack pointer + #[display(fmt = "$sp")] + Sp = 29, + + /// Frame pointer + #[display(fmt = "$fp")] + Fp = 30, + + /// Return address + #[display(fmt = "$ra")] + Ra = 31, + } +} diff --git a/dcb/src/game/exe/instruction/simple.rs b/dcb/src/game/exe/instruction/simple.rs new file mode 100644 index 0000000..e264af0 --- /dev/null +++ b/dcb/src/game/exe/instruction/simple.rs @@ -0,0 +1,829 @@ +//! Raw instructions + +// Lints +// #[allow(clippy::similar_names)] + +// Modules +pub mod repr; + +// Exports +pub use repr::RawRepr; + +// Imports +use super::{FromRawIter, Pos, Raw, Register}; +use crate::util::SignedHex; +use int_conv::{SignExtended, Signed}; + +/// Macro to declare all instructions +macro_rules! decl_instructions { + ( + $( + $( #[doc = $doc:literal] )* + #[display(fmt = $fmt:literal $( , $fmt_args:expr )* $(,)?)] + $split:pat $( if $cond:expr )? => $variant:ident { + $( $field_name:ident : $field_type:ty $( = $field_expr: expr )? ),* $(,)? + } + ),+ $(,)? + ) => { + /// A raw instruction + #[derive(PartialEq, Eq, Clone, Copy, Debug)] + #[derive(derive_more::Display)] + pub enum SimpleInstruction { + $( + $( #[doc = $doc] )* + #[display(fmt = $fmt, $( $fmt_args, )*)] + $variant { + $( + $field_name: $field_type, + )* + }, + )+ + } + + impl FromRawIter for SimpleInstruction { + type Decoded = Option<(Pos, Self)>; + + #[allow(clippy::redundant_field_names)] // For uniform initialization + fn decode + Clone>(iter: &mut I) -> Self::Decoded { + let raw = iter.next()?; + let split = RawRepr::new(raw); + + let instruction = match split { + $( + $split $( if $cond )? => Some( Self::$variant { + $( + $field_name $( : $field_expr )?, + )* + } ), + )* + + _ => None, + }; + + instruction.map(|instruction| (raw.pos, instruction)) + } + } + } +} + +decl_instructions! { + /// Store byte + #[display(fmt = "sb {rt}, {offset:#x}({rs})")] + RawRepr { + op: 0x28, + rs, rt, imm16, + .. + } => Sb { + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + offset: u16 = imm16, + }, + + /// Store half-word + #[display(fmt = "sh {rt}, {offset:#x}({rs})")] + RawRepr { + op: 0x29, + rs, rt, imm16, + .. + } => Sh { + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + offset: u16 = imm16, + }, + + /// Store left word + #[display(fmt = "swl {rt}, {offset:#x}({rs})")] + RawRepr { + op: 0x2a, + rs, rt, imm16, + .. + } => Swl { + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + offset: u16 = imm16, + }, + + /// Store word + #[display(fmt = "sw {rt}, {offset:#x}({rs})")] + RawRepr { + op: 0x2b, + rs, rt, imm16, + .. + } => Sw { + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + offset: u16 = imm16, + }, + + /// Store right word + #[display(fmt = "swr {rt}, {offset:#x}({rs})")] + RawRepr { + op: 0x2e, + rs, rt, imm16, + .. + } => Swr { + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + offset: u16 = imm16, + }, + + + + /// Load byte + #[display(fmt = "lb {rt}, {offset:#x}({rs})")] + RawRepr { + op: 0x20, + rs, rt, imm16, + .. + } => Lb { + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + offset: u16 = imm16, + }, + + /// Load byte unsigned + #[display(fmt = "lbu {rt}, {offset:#x}({rs})")] + RawRepr { + op: 0x24, + rs, rt, imm16, + .. + } => Lbu { + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + offset: u16 = imm16, + }, + + /// Load half-word + #[display(fmt = "lh {rt}, {offset:#x}({rs})")] + RawRepr { + op: 0x21, + rs, rt, imm16, + .. + } => Lh { + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + offset: u16 = imm16, + }, + + /// Load half-word unsigned + #[display(fmt = "lhu {rt}, {offset:#x}({rs})")] + RawRepr { + op: 0x25, + rs, rt, imm16, + .. + } => Lhu { + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + offset: u16 = imm16, + }, + + /// Load left word + #[display(fmt = "lwl {rt}, {offset:#x}({rs})")] + RawRepr { + op: 0x22, + rs, rt, imm16, + .. + } => Lwl { + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + offset: u16 = imm16, + }, + + /// Load word + #[display(fmt = "lw {rt}, {offset:#x}({rs})")] + RawRepr { + op: 0x23, + rs, rt, imm16, + .. + } => Lw { + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + offset: u16 = imm16, + }, + + /// Load right word + #[display(fmt = "lwr {rt}, {offset:#x}({rs})")] + RawRepr { + op: 0x26, + rs, rt, imm16, + .. + } => Lwr { + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + offset: u16 = imm16, + }, + + /// Add + #[display(fmt = "add {rd}, {rs}, {rt}")] + RawRepr { + op: 0x00, op2: 0x20, + rd, rs, rt, + .. + } => Add { + rd: Register = Register::new(rd)?, + rs: Register = Register::new(rs)?, + rt: Register = Register::new(rt)?, + }, + + /// Add unsigned + #[display(fmt = "addu {rd}, {rs}, {rt}")] + RawRepr { + op: 0x00, op2: 0x21, + rd, rs, rt, + .. + } => Addu { + rd: Register = Register::new(rd)?, + rs: Register = Register::new(rs)?, + rt: Register = Register::new(rt)?, + }, + + /// Sub + #[display(fmt = "sub {rd}, {rs}, {rt}")] + RawRepr { + op: 0x00, op2: 0x22, + rd, rs, rt, + .. + } => Sub { + rd: Register = Register::new(rd)?, + rs: Register = Register::new(rs)?, + rt: Register = Register::new(rt)?, + }, + + /// Sub unsigned + #[display(fmt = "subu {rd}, {rs}, {rt}")] + RawRepr { + op: 0x00, op2: 0x23, + rd, rs, rt, + .. + } => Subu { + rd: Register = Register::new(rd)?, + rs: Register = Register::new(rs)?, + rt: Register = Register::new(rt)?, + }, + + /// Add immediate + #[display(fmt = "addi {rt}, {rs}, {:#x}", "SignedHex(imm)")] + RawRepr { + op: 0x08, + rt, rs, imm16, + .. + } => Addi { + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + imm: i16 = imm16.as_signed(), + }, + + /// Add immediate sign-extended + /// Note: _NOT_ Unsigned. + #[display(fmt = "addiu {rt}, {rs}, {:#x}", "SignedHex(imm)")] + RawRepr { + op: 0x09, + rt, rs, imm16, + .. + } => Addiu { + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + imm: i16 = imm16.as_signed(), + }, + + /// Set less than + #[display(fmt = "slt {rd}, {rs}, {rt}")] + RawRepr { + op: 0x00, op2: 0x2a, + rd, rs, rt, + .. + } => Slt { + rd: Register = Register::new(rd)?, + rs: Register = Register::new(rs)?, + rt: Register = Register::new(rt)?, + }, + + /// Set less than unsigned + #[display(fmt = "sltu {rd}, {rs}, {rt}")] + RawRepr { + op: 0x00, op2: 0x2b, + rd, rs, rt, + .. + } => Sltu { + rd: Register = Register::new(rd)?, + rs: Register = Register::new(rs)?, + rt: Register = Register::new(rt)?, + }, + + /// Set less than immediate + #[display(fmt = "slti {rt}, {rs}, {:#x}", "SignedHex(imm)")] + RawRepr { + op: 0x0a, + rt, rs, imm16, + .. + } => Slti { + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + imm: i16 = imm16.as_signed(), + }, + + /// Set less than immediate unsigned + #[display(fmt = "sltiu {rt}, {rs}, {imm:#x}")] + RawRepr { + op: 0x0b, + rt, rs, imm16, + .. + } => Sltiu { + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + imm: u16 = imm16, + }, + + /// And + #[display(fmt = "and {rd}, {rs}, {rt}")] + RawRepr { + op: 0x00, op2: 0x24, + rd, rs, rt, + .. + } => And { + rd: Register = Register::new(rd)?, + rs: Register = Register::new(rs)?, + rt: Register = Register::new(rt)?, + }, + + /// Or + #[display(fmt = "or {rd}, {rs}, {rt}")] + RawRepr { + op: 0x00, op2: 0x25, + rd, rs, rt, + .. + } => Or { + rd: Register = Register::new(rd)?, + rs: Register = Register::new(rs)?, + rt: Register = Register::new(rt)?, + }, + + /// Xor + #[display(fmt = "xor {rd}, {rs}, {rt}")] + RawRepr { + op: 0x00, op2: 0x26, + rd, rs, rt, + .. + } => Xor { + rd: Register = Register::new(rd)?, + rs: Register = Register::new(rs)?, + rt: Register = Register::new(rt)?, + }, + + /// Nor + #[display(fmt = "nor {rd}, {rs}, {rt}")] + RawRepr { + op: 0x00, op2: 0x27, + rd, rs, rt, + .. + } => Nor { + rd: Register = Register::new(rd)?, + rs: Register = Register::new(rs)?, + rt: Register = Register::new(rt)?, + }, + + /// And immediate + #[display(fmt = "andi {rt}, {rs}, {imm:#x}")] + RawRepr { + op: 0x0c, + rt, rs, imm16, + .. + } => Andi { + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + imm: u16 = imm16, + }, + + /// Or immediate + #[display(fmt = "ori {rt}, {rs}, {imm:#x}")] + RawRepr { + op: 0x0d, + rt, rs, imm16, + .. + } => Ori { + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + imm: u16 = imm16, + }, + + /// Xor immediate + #[display(fmt = "xori {rt}, {rs}, {imm:#x}")] + RawRepr { + op: 0x0e, + rt, rs, imm16, + .. + } => Xori { + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + imm: u16 = imm16, + }, + + /// Shift left logical variable + #[display(fmt = "sllv {rd}, {rt}, {rs}")] + RawRepr { + op: 0x00, op2: 0x04, + rd, rs, rt, + .. + } => Sllv { + rd: Register = Register::new(rd)?, + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + }, + + /// Shift right logical variable + #[display(fmt = "srlv {rd}, {rt}, {rs}")] + RawRepr { + op: 0x00, op2: 0x06, + rd, rs, rt, + .. + } => Srlv { + rd: Register = Register::new(rd)?, + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + }, + + /// Shift right arithmetic variable + #[display(fmt = "srav {rd}, {rt}, {rs}")] + RawRepr { + op: 0x00, op2: 0x07, + rd, rs, rt, + .. + } => Srav { + rd: Register = Register::new(rd)?, + rt: Register = Register::new(rt)?, + rs: Register = Register::new(rs)?, + }, + + /// Shift left logical + #[display(fmt = "sll {rd}, {rt}, {imm:#x}")] + RawRepr { + op: 0x00, op2: 0x00, + rd, rt, imm5, + .. + } => Sll { + rd: Register = Register::new(rd)?, + rt: Register = Register::new(rt)?, + imm: u8 = imm5, + }, + + /// Shift right logical + #[display(fmt = "srl {rd}, {rt}, {imm:#x}")] + RawRepr { + op: 0x00, op2: 0x02, + rd, rt, imm5, + .. + } => Srl { + rd: Register = Register::new(rd)?, + rt: Register = Register::new(rt)?, + imm: u8 = imm5, + }, + + /// Shift right arithmetic + #[display(fmt = "sra {rd}, {rt}, {imm:#x}")] + RawRepr { + op: 0x00, op2: 0x03, + rd, rt, imm5, + .. + } => Sra { + rd: Register = Register::new(rd)?, + rt: Register = Register::new(rt)?, + imm: u8 = imm5, + }, + + /// Load upper immediate + #[display(fmt = "lui {rt}, {imm:#x}")] + RawRepr { + op: 0x0f, + rt, imm16, + .. + } => Lui { + rt: Register = Register::new(rt)?, + imm: u16 = imm16, + }, + + /// Multiply + #[display(fmt = "mult {rs}, {rt}")] + RawRepr { + op: 0x00, op2: 0x18, + rs, rt, + .. + } => Mult { + rs: Register = Register::new(rs)?, + rt: Register = Register::new(rt)?, + }, + + /// Multiply unsigned + #[display(fmt = "multu {rs}, {rt}")] + RawRepr { + op: 0x00, op2: 0x19, + rs, rt, + .. + } => Multu { + rs: Register = Register::new(rs)?, + rt: Register = Register::new(rt)?, + }, + + /// Divide + #[display(fmt = "div {rs}, {rt}")] + RawRepr { + op: 0x00, op2: 0x1a, + rs, rt, + .. + } => Div { + rs: Register = Register::new(rs)?, + rt: Register = Register::new(rt)?, + }, + + /// Multiply unsigned + #[display(fmt = "divu {rs}, {rt}")] + RawRepr { + op: 0x00, op2: 0x1b, + rs, rt, + .. + } => Divu { + rs: Register = Register::new(rs)?, + rt: Register = Register::new(rt)?, + }, + + /// Move from hi + #[display(fmt = "mfhi {rd}")] + RawRepr { + op: 0x00, op2: 0x10, + rd, + .. + } => Mfhi { + rd: Register = Register::new(rd)?, + }, + + /// Move from lo + #[display(fmt = "mflo {rd}")] + RawRepr { + op: 0x00, op2: 0x11, + rd, + .. + } => Mflo { + rd: Register = Register::new(rd)?, + }, + + /// Move to hi + #[display(fmt = "mthi {rd}")] + RawRepr { + op: 0x00, op2: 0x12, + rd, + .. + } => Mthi { + rd: Register = Register::new(rd)?, + }, + + /// Move to lo + #[display(fmt = "mtlo {rd}")] + RawRepr { + op: 0x00, op2: 0x13, + rd, + .. + } => Mtlo { + rd: Register = Register::new(rd)?, + }, + + /// Jump + #[display(fmt = "j {target:#x}")] + RawRepr { + op: 0x02, + imm26, pos, + .. + } => J { + target: u32 = i32::as_unsigned(u32::as_signed(pos & 0xF000_0000) + imm26.as_signed() * 4), + }, + + /// Jump and link + #[display(fmt = "jal {target:#x}")] + RawRepr { + op: 0x03, + imm26, pos, + .. + } => Jal { + target: u32 = i32::as_unsigned(u32::as_signed(pos & 0xF000_0000) + imm26.as_signed() * 4), + }, + + /// Jump register + #[display(fmt = "jr {rs}")] + RawRepr { + op: 0x00, op2: 0x08, + rs, + .. + } => Jr { + rs: Register = Register::new(rs)?, + }, + + /// Jump and link register + #[display(fmt = "jalr {rd}, {rs}")] + RawRepr { + op: 0x00, op2: 0x09, + rd, rs, + .. + } => Jalr { + rd: Register = Register::new(rd)?, + rs: Register = Register::new(rs)?, + }, + + /// Branch if equal + #[display(fmt = "beq {rs}, {rt}, {target:#x}")] + RawRepr { + op: 0x04, + rs, rt, imm16, pos, + .. + } => Beq { + rs: Register = Register::new(rs)?, + rt: Register = Register::new(rt)?, + target: u32 = i32::as_unsigned(pos.as_signed() + 4 + imm16.as_signed().sign_extended::() * 4), + }, + + /// Branch if not equal + #[display(fmt = "bne {rs}, {rt}, {target:#x}")] + RawRepr { + op: 0x05, + rs, rt, imm16, pos, + .. + } => Bne { + rs: Register = Register::new(rs)?, + rt: Register = Register::new(rt)?, + target: u32 = i32::as_unsigned(pos.as_signed() + 4 + imm16.as_signed().sign_extended::() * 4), + }, + + /// Branch if less than zero + #[display(fmt = "bltz {rs}, {target:#x}")] + RawRepr { + op: 0x01, rt: 0x00, + rs, imm16, pos, + .. + } => Bltz { + rs: Register = Register::new(rs)?, + target: u32 = i32::as_unsigned(pos.as_signed() + 4 + imm16.as_signed().sign_extended::() * 4) + }, + + /// Branch if greater or equal to zero + #[display(fmt = "bgez {rs}, {target:#x}")] + RawRepr { + op: 0x01, rt: 0x01, + rs, imm16, pos, + .. + } => Bgez { + rs: Register = Register::new(rs)?, + target: u32 = i32::as_unsigned(pos.as_signed() + 4 + imm16.as_signed().sign_extended::() * 4) + }, + + /// Branch if greater than zero + #[display(fmt = "bgtz {rs}, {target:#x}")] + RawRepr { + op: 0x07, + rs, imm16, pos, + .. + } => Bgtz { + rs: Register = Register::new(rs)?, + target: u32 = i32::as_unsigned(pos.as_signed() + 4 + imm16.as_signed().sign_extended::() * 4) + }, + + /// Branch if less or equal to zero + #[display(fmt = "blez {rs}, {target:#x}")] + RawRepr { + op: 0x06, + rs, imm16, pos, + .. + } => Blez { + rs: Register = Register::new(rs)?, + target: u32 = i32::as_unsigned(pos.as_signed() + 4 + imm16.as_signed().sign_extended::() * 4) + }, + + /// Branch if less than zero and link + #[display(fmt = "bltzal {rs}, {target:#x}")] + RawRepr { + op: 0x01, rt: 0x10, + rs, imm16, pos, + .. + } => Bltzal { + rs: Register = Register::new(rs)?, + target: u32 = i32::as_unsigned(pos.as_signed() + imm16.as_signed().sign_extended::() * 4) + }, + + /// Branch if greater or equal to zero and link + #[display(fmt = "bgezal {rs}, {target:#x}")] + RawRepr { + op: 0x01, rt: 0x11, + rs, imm16, pos, + .. + } => Bgezal { + rs: Register = Register::new(rs)?, + target: u32 = i32::as_unsigned(pos.as_signed() + imm16.as_signed().sign_extended::() * 4) + }, + + /// Save co-processor data registers + #[display(fmt = "mfc{n} {rt}, {rd}")] + RawRepr { + co_op: 0b0100, co_rs0: 0, co_rs1: 0b0000, + co_n, rt, rd, + .. + } => MfcN { + n : u8 = co_n, + rt: Register = Register::new(rt)?, + rd: Register = Register::new(rd)?, + }, + + /// Save co-processor control registers + #[display(fmt = "cfc{n} {rt}, {rd}")] + RawRepr { + co_op: 0b0100, co_rs0: 0, co_rs1: 0b0010, + co_n, rt, rd, + .. + } => CfcN { + n : u8 = co_n, + rt: Register = Register::new(rt)?, + rd: Register = Register::new(rd)?, + }, + + /// Load co-processor data registers + #[display(fmt = "mtc{n} {rt}, {rd}")] + RawRepr { + co_op: 0b0100, co_rs0: 0, co_rs1: 0b0100, + co_n, rt, rd, + .. + } => MtcN { + n : u8 = co_n, + rt: Register = Register::new(rt)?, + rd: Register = Register::new(rd)?, + }, + + /// Load co-processor control registers + #[display(fmt = "ctc{n} {rt}, {rd}")] + RawRepr { + co_op: 0b0100, co_rs0: 0, co_rs1: 0b0110, + co_n, rt, rd, + .. + } => CtcN { + n : u8 = co_n, + rt: Register = Register::new(rt)?, + rd: Register = Register::new(rd)?, + }, + + // TODO: Check how to calculate actual targets for these jumps + // Docs say `$+disp`, not sure if a typo or what, no 4 + // multiple either, are co-processor instructions 1 byte? + + /// Branch co-processor if false + #[display(fmt = "bc{n}f {target:#x} # Raw target")] + RawRepr { + co_op: 0b0100, co_rs0: 0, co_rs1: 0b1000, rt: 0b00000, + co_n, imm16, + .. + } => BcNf { + n: u8 = co_n, + target: u16 = imm16, + }, + + /// Branch co-processor if true + #[display(fmt = "bc{n}t {target:#x} # Raw target")] + RawRepr { + co_op: 0b0100, co_rs0: 0, co_rs1: 0b1000, rt: 0b00001, + co_n, imm16, + .. + } => BcNt { + n: u8 = co_n, + target: u16 = imm16, + }, + + /// Exec immediate co-processor + #[display(fmt = "cop{n} {imm:#x}")] + RawRepr { + co_op: 0b0100, co_rs0: 1, + co_n, imm25, + .. + } => CopN { + n: u8 = co_n, + imm: u32 = imm25, + }, + + /// Load word co-processor + #[display(fmt = "lwc{n} {rt}, {imm:#x}({rs})")] + RawRepr { + co_op: 0b1100, + co_n, rs, rt, imm16, + .. + } => LwcN { + n: u8 = co_n, + rs: Register = Register::new(rs)?, + rt: Register = Register::new(rt)?, + imm: u16 = imm16, + }, + + /// Store word co-processor + #[display(fmt = "swc{n} {rt}, {imm:#x}({rs})")] + RawRepr { + co_op: 0b1110, + co_n, rs, rt, imm16, + .. + } => SwcN { + n: u8 = co_n, + rs: Register = Register::new(rs)?, + rt: Register = Register::new(rt)?, + imm: u16 = imm16, + }, +} diff --git a/dcb/src/game/exe/instruction/simple/repr.rs b/dcb/src/game/exe/instruction/simple/repr.rs new file mode 100644 index 0000000..daf48b3 --- /dev/null +++ b/dcb/src/game/exe/instruction/simple/repr.rs @@ -0,0 +1,61 @@ +//! Raw instruction representation + +// Imports +use super::Raw; +use int_conv::Truncate; + +/// An instruction's raw representation, including +/// it's current address. +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[allow(clippy::missing_docs_in_private_items)] +pub struct RawRepr { + pub op: u8, + pub rs: u8, + pub rt: u8, + pub rd: u8, + pub imm5: u8, + pub op2: u8, + pub imm16: u16, + pub imm25: u32, + pub imm26: u32, + + /// Co-processor opcode + pub co_op: u8, + + /// Co-processor number + pub co_n: u8, + + /// Co-processor highest `rs` bit. + pub co_rs0: u8, + + /// Co-processor lowest `rs` bits. + pub co_rs1: u8, + + /// Position of the instruction + pub pos: u32, +} + +#[allow(clippy::inconsistent_digit_grouping)] // We're grouping 6-5-5-5-5-6 as per docs. +impl RawRepr { + /// Creates a new split instruction + #[must_use] + #[rustfmt::skip] + pub fn new(Raw {repr, pos}: Raw) -> Self { + Self { + op : ((repr & 0b111111_00000_00000_00000_00000_000000) >> 26).truncate(), + rs : ((repr & 0b000000_11111_00000_00000_00000_000000) >> 21).truncate(), + rt : ((repr & 0b000000_00000_11111_00000_00000_000000) >> 16).truncate(), + rd : ((repr & 0b000000_00000_00000_11111_00000_000000) >> 11).truncate(), + imm5 : ((repr & 0b000000_00000_00000_00000_11111_000000) >> 6 ).truncate(), + op2 : ((repr & 0b000000_00000_00000_00000_00000_111111) >> 0 ).truncate(), + imm16 : ((repr & 0b000000_00000_00000_11111_11111_111111) >> 0 ).truncate(), + imm25 : ((repr & 0b000000_01111_11111_11111_11111_111111) >> 0 ), + imm26 : ((repr & 0b000000_11111_11111_11111_11111_111111) >> 0 ), + co_op : ((repr & 0b111100_00000_00000_00000_00000_000000) >> 28).truncate(), + co_rs0: ((repr & 0b000000_10000_00000_00000_00000_000000) >> 25).truncate(), + co_rs1: ((repr & 0b000000_01111_00000_00000_00000_000000) >> 21).truncate(), + co_n : ((repr & 0b000011_00000_00000_00000_00000_000000) >> 26).truncate(), + pos: pos.0, + } + } +} diff --git a/dcb/src/lib.rs b/dcb/src/lib.rs index 96f8782..5b78d77 100644 --- a/dcb/src/lib.rs +++ b/dcb/src/lib.rs @@ -47,7 +47,9 @@ array_map, const_mut_refs, core_intrinsics, - const_assume + const_assume, + bindings_after_at, + array_value_iter )] // Lints #![warn(clippy::restriction, clippy::pedantic, clippy::nursery)] @@ -98,6 +100,9 @@ #![allow(clippy::if_not_else)] // This lint triggers when using `assert`s and `todo`s, which is unsuitable for this project #![allow(clippy::panic_in_result_fn)] +// A `match Option / Result / Bool` can sometimes look cleaner than a `if let / else` +#![allow(clippy::single_match_else, clippy::match_bool)] + // Modules pub mod ascii_str_arr;