Skip to main content

asmkit/core/
inst.rs

1//! Generic instruction representation.
2//!
3//! [`Inst`] is the asmkit equivalent of AsmJit's `BaseInst` + trailing operand array: an
4//! architecture-namespaced instruction id, options, an optional extra register, and up to
5//! [`MAX_OP_COUNT`] operands stored inline. It is the node type recorded by a Builder
6//! (see `core::builder`) and consumed by `query_rw_info`-style APIs, while the per-mnemonic
7//! emitter methods remain the ergonomic way to construct one.
8
9use super::arch_traits::Arch;
10use super::globals::{InstOptions, MAX_OP_COUNT};
11use super::operand::Operand;
12use crate::AsmError;
13
14/// A generic instruction: id + options + extra register + inline operand array.
15///
16/// The id is architecture-namespaced (`x86::InstId`, `aarch64::InstId`, `riscv::Opcode`,
17/// all cast to `u32`); architectures may pack modifiers into high bits of the id (AArch64
18/// packs a [`super::globals::CondCode`]). `extra_reg` carries the REP register or AVX-512
19/// `{k}` selector where applicable and is [`Operand::new()`] (none) otherwise.
20#[derive(Clone, Copy, PartialEq, Eq, Debug)]
21pub struct Inst {
22    arch: Arch,
23    /// Instruction id with modifiers (architecture-namespaced).
24    pub(crate) id: u32,
25    /// Instruction options.
26    pub(crate) options: InstOptions,
27    /// Extra register used by the instruction (REP register or AVX-512 mask selector).
28    pub(crate) extra_reg: Operand,
29    /// Number of valid entries in `operands`.
30    op_count: u8,
31    /// Operands; only the first `op_count` entries are meaningful.
32    operands: [Operand; MAX_OP_COUNT],
33}
34
35impl Inst {
36    /// Creates a host-tagged instruction with the given `id` and no operands.
37    #[cfg(test)]
38    #[allow(dead_code)]
39    pub(crate) const fn new(id: u32) -> Self {
40        Self::new_for(Arch::HOST, id)
41    }
42
43    /// Creates an instruction for a supported assembler architecture.
44    pub(crate) const fn new_for(arch: Arch, id: u32) -> Self {
45        Self {
46            arch,
47            id,
48            options: InstOptions::NONE,
49            extra_reg: Operand::new(),
50            op_count: 0,
51            operands: [Operand::new(); MAX_OP_COUNT],
52        }
53    }
54
55    /// Creates a host-tagged instruction with the given `id` and operands.
56    #[cfg(test)]
57    #[allow(dead_code)]
58    pub(crate) fn with_operands(id: u32, ops: &[Operand]) -> Self {
59        Self::with_arch_operands(Arch::HOST, id, ops)
60            .expect("instruction operands must fit the inline array")
61    }
62
63    /// Creates an architecture-tagged instruction without operands.
64    pub fn for_arch(arch: Arch, id: u32) -> Result<Self, AsmError> {
65        Self::with_arch_operands(arch, id, &[])
66    }
67
68    /// Creates an architecture-tagged instruction without dropping operands.
69    pub fn with_arch_operands(arch: Arch, id: u32, ops: &[Operand]) -> Result<Self, AsmError> {
70        if !matches!(
71            arch,
72            Arch::X86 | Arch::X64 | Arch::AArch64 | Arch::RISCV32 | Arch::RISCV64
73        ) {
74            return Err(AsmError::InvalidArch);
75        }
76        let mut inst = Self::new_for(arch, id);
77        inst.set_operands(ops)?;
78        Ok(inst)
79    }
80
81    /// Architecture this instruction belongs to.
82    pub const fn arch(&self) -> Arch {
83        self.arch
84    }
85
86    /// Instruction id with architecture-specific modifiers.
87    pub const fn id(&self) -> u32 {
88        self.id
89    }
90
91    /// Encoding options retained by deferred replay.
92    pub const fn options(&self) -> InstOptions {
93        self.options
94    }
95
96    /// Optional extra register retained by deferred replay.
97    pub const fn extra_reg(&self) -> Operand {
98        self.extra_reg
99    }
100
101    /// Sets encoding options retained by deferred replay.
102    pub fn set_options(&mut self, options: InstOptions) {
103        self.options = options;
104    }
105
106    /// Sets the optional extra register retained by deferred replay.
107    pub fn set_extra_reg(&mut self, extra_reg: Operand) {
108        self.extra_reg = extra_reg;
109    }
110
111    /// Returns the number of operands.
112    pub const fn op_count(&self) -> usize {
113        self.op_count as usize
114    }
115
116    /// Returns the operands as a slice.
117    pub fn operands(&self) -> &[Operand] {
118        self.operands.split_at(self.op_count as usize).0
119    }
120
121    /// Returns the operands as a mutable slice.
122    pub fn operands_mut(&mut self) -> &mut [Operand] {
123        self.operands.split_at_mut(self.op_count as usize).0
124    }
125
126    /// Returns the operand at `index`, or `None` if out of range.
127    pub fn operand(&self, index: usize) -> Option<&Operand> {
128        self.operands().get(index)
129    }
130
131    /// Sets the operand at `index`. `index` must be less than `op_count`.
132    pub fn set_operand(&mut self, index: usize, op: Operand) -> Result<(), AsmError> {
133        let Some(slot) = self.operands_mut().get_mut(index) else {
134            return Err(AsmError::InvalidArgument);
135        };
136        *slot = op;
137        Ok(())
138    }
139
140    /// Appends an operand, or returns [`AsmError::InvalidState`] if the operand array is full.
141    pub fn add_operand(&mut self, op: Operand) -> Result<(), AsmError> {
142        if self.op_count() >= MAX_OP_COUNT {
143            return Err(AsmError::InvalidState);
144        }
145        self.operands[self.op_count()] = op;
146        self.op_count += 1;
147        Ok(())
148    }
149
150    /// Sets all operands from `ops`, replacing the current operand list.
151    ///
152    /// Returns [`AsmError::InvalidState`] if `ops` doesn't fit into the operand array.
153    pub fn set_operands(&mut self, ops: &[Operand]) -> Result<(), AsmError> {
154        if ops.len() > MAX_OP_COUNT {
155            return Err(AsmError::InvalidState);
156        }
157        self.operands[..ops.len()].copy_from_slice(ops);
158        self.operands[ops.len()..].fill(Operand::new());
159        self.op_count = ops.len() as u8;
160        Ok(())
161    }
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167
168    #[test]
169    fn build_and_mutate() {
170        let mut inst = Inst::new_for(Arch::X64, 42);
171        assert_eq!(inst.id(), 42);
172        assert_eq!(inst.op_count(), 0);
173        assert!(inst.operands().is_empty());
174
175        inst.add_operand(Operand::new()).unwrap();
176        inst.add_operand(Operand::new()).unwrap();
177        assert_eq!(inst.op_count(), 2);
178
179        inst.set_operands(&[Operand::new(); 3]).unwrap();
180        assert_eq!(inst.op_count(), 3);
181
182        for _ in 0..3 {
183            inst.add_operand(Operand::new()).unwrap();
184        }
185        assert_eq!(inst.op_count(), MAX_OP_COUNT);
186        assert!(inst.add_operand(Operand::new()).is_err());
187
188        assert_eq!(
189            inst.set_operand(MAX_OP_COUNT, Operand::new()),
190            Err(AsmError::InvalidArgument)
191        );
192        assert_eq!(
193            Inst::with_arch_operands(Arch::Unknown, 42, &[]),
194            Err(AsmError::InvalidArch)
195        );
196
197        inst.set_options(InstOptions::X86_ZMASK);
198        inst.set_extra_reg(Operand::new());
199        assert_eq!(inst.options(), InstOptions::X86_ZMASK);
200    }
201}