use super::arch_traits::Arch;
use super::globals::{InstOptions, MAX_OP_COUNT};
use super::operand::Operand;
use crate::AsmError;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct Inst {
arch: Arch,
pub(crate) id: u32,
pub(crate) options: InstOptions,
pub(crate) extra_reg: Operand,
op_count: u8,
operands: [Operand; MAX_OP_COUNT],
}
impl Inst {
#[cfg(test)]
#[allow(dead_code)]
pub(crate) const fn new(id: u32) -> Self {
Self::new_for(Arch::HOST, id)
}
pub(crate) const fn new_for(arch: Arch, id: u32) -> Self {
Self {
arch,
id,
options: InstOptions::NONE,
extra_reg: Operand::new(),
op_count: 0,
operands: [Operand::new(); MAX_OP_COUNT],
}
}
#[cfg(test)]
#[allow(dead_code)]
pub(crate) fn with_operands(id: u32, ops: &[Operand]) -> Self {
Self::with_arch_operands(Arch::HOST, id, ops)
.expect("instruction operands must fit the inline array")
}
pub fn for_arch(arch: Arch, id: u32) -> Result<Self, AsmError> {
Self::with_arch_operands(arch, id, &[])
}
pub fn with_arch_operands(arch: Arch, id: u32, ops: &[Operand]) -> Result<Self, AsmError> {
if !matches!(
arch,
Arch::X86 | Arch::X64 | Arch::AArch64 | Arch::RISCV32 | Arch::RISCV64
) {
return Err(AsmError::InvalidArch);
}
let mut inst = Self::new_for(arch, id);
inst.set_operands(ops)?;
Ok(inst)
}
pub const fn arch(&self) -> Arch {
self.arch
}
pub const fn id(&self) -> u32 {
self.id
}
pub const fn options(&self) -> InstOptions {
self.options
}
pub const fn extra_reg(&self) -> Operand {
self.extra_reg
}
pub fn set_options(&mut self, options: InstOptions) {
self.options = options;
}
pub fn set_extra_reg(&mut self, extra_reg: Operand) {
self.extra_reg = extra_reg;
}
pub const fn op_count(&self) -> usize {
self.op_count as usize
}
pub fn operands(&self) -> &[Operand] {
self.operands.split_at(self.op_count as usize).0
}
pub fn operands_mut(&mut self) -> &mut [Operand] {
self.operands.split_at_mut(self.op_count as usize).0
}
pub fn operand(&self, index: usize) -> Option<&Operand> {
self.operands().get(index)
}
pub fn set_operand(&mut self, index: usize, op: Operand) -> Result<(), AsmError> {
let Some(slot) = self.operands_mut().get_mut(index) else {
return Err(AsmError::InvalidArgument);
};
*slot = op;
Ok(())
}
pub fn add_operand(&mut self, op: Operand) -> Result<(), AsmError> {
if self.op_count() >= MAX_OP_COUNT {
return Err(AsmError::InvalidState);
}
self.operands[self.op_count()] = op;
self.op_count += 1;
Ok(())
}
pub fn set_operands(&mut self, ops: &[Operand]) -> Result<(), AsmError> {
if ops.len() > MAX_OP_COUNT {
return Err(AsmError::InvalidState);
}
self.operands[..ops.len()].copy_from_slice(ops);
self.operands[ops.len()..].fill(Operand::new());
self.op_count = ops.len() as u8;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_and_mutate() {
let mut inst = Inst::new_for(Arch::X64, 42);
assert_eq!(inst.id(), 42);
assert_eq!(inst.op_count(), 0);
assert!(inst.operands().is_empty());
inst.add_operand(Operand::new()).unwrap();
inst.add_operand(Operand::new()).unwrap();
assert_eq!(inst.op_count(), 2);
inst.set_operands(&[Operand::new(); 3]).unwrap();
assert_eq!(inst.op_count(), 3);
for _ in 0..3 {
inst.add_operand(Operand::new()).unwrap();
}
assert_eq!(inst.op_count(), MAX_OP_COUNT);
assert!(inst.add_operand(Operand::new()).is_err());
assert_eq!(
inst.set_operand(MAX_OP_COUNT, Operand::new()),
Err(AsmError::InvalidArgument)
);
assert_eq!(
Inst::with_arch_operands(Arch::Unknown, 42, &[]),
Err(AsmError::InvalidArch)
);
inst.set_options(InstOptions::X86_ZMASK);
inst.set_extra_reg(Operand::new());
assert_eq!(inst.options(), InstOptions::X86_ZMASK);
}
}