Skip to main content

fARM64/
instruction.rs

1//! The `Copy` value-type [`Instruction`] — the public projection of a decoded
2//! A64 instruction.
3//!
4//! A `Copy` value type holding an inline `[Operand; MAX_OPERANDS]` with no heap
5//! allocation and no internal pointers. Its size is dominated by that inline
6//! operand array (`5 * 16 = 80` bytes) plus a small header; the realized ceiling
7//! is asserted at `<= 112` bytes in `lib.rs`'s `static_asserts`. The fat
8//! internal decode representation never reaches this type.
9
10use crate::enums::{Condition, FlagEffect, FlowControl};
11use crate::mnemonic::{Code, Mnemonic};
12use crate::operand::{OpKind, Operand};
13use crate::register::Register;
14use crate::{INSN_LEN, MAX_OPERANDS};
15
16/// A fully decoded AArch64 instruction.
17///
18/// Construct via [`crate::Decoder::decode`] / [`crate::Decoder::decode_into`].
19/// All accessors are cheap; the type is `Copy` and safe to pass by value.
20///
21/// Derives `PartialEq` but not `Eq`/`Hash`, because its inline
22/// `[Operand; MAX_OPERANDS]` contains a floating-point ([`Operand::FpImm`])
23/// payload. Compare by [`Instruction::word`] + [`Instruction::ip`] if a total
24/// key is needed.
25///
26/// [`Operand::FpImm`]: crate::operand::Operand::FpImm
27#[derive(Debug, Clone, Copy, PartialEq)]
28pub struct Instruction {
29    /// The raw little-endian instruction word as decoded.
30    pub(crate) word: u32,
31    /// Address of this instruction (the `ip` it was decoded at).
32    pub(crate) ip: u64,
33    /// Encoding-level identity.
34    pub(crate) code: Code,
35    /// Resolved mnemonic (may be an alias when alias resolution is enabled).
36    pub(crate) mnemonic: Mnemonic,
37    /// Number of valid entries in `operands`.
38    pub(crate) op_count: u8,
39    /// Packed instruction flags. Bit 0 ([`Instruction::FLAG_MODIFIED`]) records
40    /// that an operand/`ip`/`code` mutator has run, so [`Instruction::word`] no
41    /// longer describes this value. Kept as a `u8` to hold `Instruction` small.
42    pub(crate) flags: u8,
43    /// Inline operand storage; only `op_count` entries are meaningful.
44    pub(crate) operands: [Operand; MAX_OPERANDS],
45}
46
47impl Instruction {
48    /// The encoding identity.
49    #[inline]
50    pub const fn code(&self) -> Code {
51        self.code
52    }
53
54    /// The (possibly alias-resolved) mnemonic.
55    #[inline]
56    pub const fn mnemonic(&self) -> Mnemonic {
57        self.mnemonic
58    }
59
60    /// Number of explicit operands (`0..=MAX_OPERANDS`).
61    #[inline]
62    pub const fn op_count(&self) -> usize {
63        self.op_count as usize
64    }
65
66    /// The [`OpKind`] discriminant of operand `n`. Out-of-range `n` yields
67    /// [`OpKind::None`].
68    #[inline]
69    pub fn op_kind(&self, n: usize) -> OpKind {
70        if n < self.op_count as usize {
71            self.operands[n].kind()
72        } else {
73            OpKind::None
74        }
75    }
76
77    /// The full rich [`Operand`] at slot `n`. Out-of-range `n` yields
78    /// [`Operand::None`].
79    #[inline]
80    pub fn op(&self, n: usize) -> Operand {
81        if n < self.op_count as usize {
82            self.operands[n]
83        } else {
84            Operand::None
85        }
86    }
87
88    /// Fast indexed accessor: the register of operand `n`, or [`Register::None`]
89    /// if it is not a plain register operand.
90    #[inline]
91    pub fn op_register(&self, n: usize) -> Register {
92        match self.op(n) {
93            Operand::Reg { reg, .. } => reg,
94            _ => Register::None,
95        }
96    }
97
98    /// Fast indexed accessor: the immediate value of operand `n` as `u64`
99    /// (signed immediates are reinterpreted via `as u64`). `0` if operand `n`
100    /// is not an immediate.
101    #[inline]
102    pub fn op_immediate(&self, n: usize) -> u64 {
103        match self.op(n) {
104            Operand::ImmUnsigned(v) | Operand::ImmLogical(v) => v,
105            Operand::ImmSigned(v) => v as u64,
106            Operand::Label(v) => v,
107            _ => 0,
108        }
109    }
110
111    /// Length of this instruction in bytes. A64 is fixed-width: always
112    /// [`INSN_LEN`] (4).
113    #[inline]
114    pub const fn len(&self) -> usize {
115        INSN_LEN
116    }
117
118    /// Always `false` — an A64 instruction is never zero-length. Present so
119    /// Clippy does not demand it alongside [`Instruction::len`].
120    #[inline]
121    pub const fn is_empty(&self) -> bool {
122        false
123    }
124
125    /// The address this instruction was decoded at.
126    #[inline]
127    pub const fn ip(&self) -> u64 {
128        self.ip
129    }
130
131    /// The address of the following instruction (`ip + 4`).
132    #[inline]
133    pub const fn next_ip(&self) -> u64 {
134        self.ip.wrapping_add(INSN_LEN as u64)
135    }
136
137    /// The raw little-endian instruction word this instruction was **decoded
138    /// from**.
139    ///
140    /// After any of the [editing](#editing-and-re-encoding) mutators has run,
141    /// this still returns the *original* word and [`Instruction::is_modified`]
142    /// returns `true`; call [`Instruction::encode`] for the word the edited
143    /// semantics encode to, or [`Instruction::re_encode`] to encode and store it
144    /// back here.
145    #[inline]
146    pub const fn word(&self) -> u32 {
147        self.word
148    }
149
150    /// Control-flow classification (derived from [`Code`]/[`Mnemonic`]).
151    ///
152    /// Classifies from the (alias-resolved) [`Mnemonic`], with the one
153    /// disambiguation that needs the encoding: `B` is a
154    /// [`FlowControl::ConditionalBranch`] only for the `B.<cond>` encoding
155    /// ([`Code::BCond`]) and otherwise a [`FlowControl::UnconditionalBranch`].
156    #[inline]
157    pub fn flow_control(&self) -> FlowControl {
158        use Mnemonic::*;
159        match self.mnemonic {
160            // Direct unconditional / conditional branches.
161            B => {
162                if matches!(self.code, Code::BCond) {
163                    FlowControl::ConditionalBranch
164                } else {
165                    FlowControl::UnconditionalBranch
166                }
167            }
168            // The FEAT_HBC hinted conditional branch `BC.<cond>` is the
169            // `bit4 == 1` sibling of `B.<cond>`; it carries its own mnemonic
170            // rather than reusing `B`, so it needs its own arm.
171            Bc => FlowControl::ConditionalBranch,
172            // Compare/test-and-branch are conditional direct branches.
173            Cbz | Cbnz | Tbz | Tbnz => FlowControl::ConditionalBranch,
174            // FEAT_CMPBR compare-and-branch (register / immediate) — also
175            // conditional direct branches.
176            Cbgt | Cbge | Cbhi | Cbhs | Cbeq | Cbne | Cblt | Cblo | Cbbgt | Cbbge | Cbbhi
177            | Cbbhs | Cbbeq | Cbbne | Cbhgt | Cbhge | Cbhhi | Cbhhs | Cbheq | Cbhne => {
178                FlowControl::ConditionalBranch
179            }
180            // Direct call (writes the link register).
181            Bl => FlowControl::Call,
182            // Indirect call via register (incl. pointer-authenticated forms).
183            Blr | Blraa | Blraaz | Blrab | Blrabz => FlowControl::IndirectCall,
184            // Indirect branch via register (incl. pointer-authenticated forms).
185            Br | Braa | Braaz | Brab | Brabz => FlowControl::IndirectBranch,
186            // Returns from subroutine / exception.
187            Ret | Retaa | Retab | Eret | Eretaa | Eretab | Drps => FlowControl::Return,
188            // FEAT_PAuth_LR authenticated returns (PC-relative `<label>` and
189            // register-modifier `<Xm>` forms) also return from a subroutine.
190            Retaasppc | Retabsppc | Retaasppcr | Retabsppcr => FlowControl::Return,
191            // Exception generation / system calls / debug-state entry.
192            Svc | Hvc | Smc | Brk | Hlt | Dcps1 | Dcps2 | Dcps3 => FlowControl::Exception,
193            // Everything else falls through linearly.
194            _ => FlowControl::Next,
195        }
196    }
197
198    /// NZCV write behaviour.
199    ///
200    /// Integer flag-setters (the `S`-suffixed ALU forms plus `CMP`/`CMN`/`TST`
201    /// and the conditional-compare `CCMP`/`CCMN`) report
202    /// [`FlagEffect::SetsNormal`]; the floating-point compares
203    /// (`FCMP`/`FCMPE`/`FCCMP`/`FCCMPE`) report [`FlagEffect::SetsFloat`]. All
204    /// other instructions report [`FlagEffect::None`].
205    #[inline]
206    pub fn set_flags(&self) -> FlagEffect {
207        use Mnemonic::*;
208        match self.mnemonic {
209            // Integer ALU forms that write NZCV.
210            Adds | Subs | Adcs | Sbcs | Ands | Bics | Negs | Ngcs | Cmp | Cmn | Tst | Ccmp
211            | Ccmn => FlagEffect::SetsNormal,
212            // Floating-point comparisons write NZCV from the FP compare path.
213            Fcmp | Fcmpe | Fccmp | Fccmpe => FlagEffect::SetsFloat,
214            // Flag manipulation: each writes a *subset* of NZCV directly rather
215            // than through the ALU or FP compare path, so it reports the generic
216            // `Sets`. `RMIF` inserts a rotated bitfield of `Xn` into the mask-
217            // selected flags; `SETF8`/`SETF16` set N/Z/V from the low bits of
218            // `Wn` (C is preserved); `CFINV` inverts C; `AXFLAG`/`XAFLAG`
219            // convert between the Arm and the alternate FP condition-flag
220            // encodings. The SVE `RDFFRS` writes NZCV from the FFR predicate.
221            Rmif | Setf8 | Setf16 | Cfinv | Axflag | Xaflag | Rdffrs => FlagEffect::Sets,
222            // ADC/SBC/CSEL/... read flags but do not write them.
223            _ => FlagEffect::None,
224        }
225    }
226
227    /// The resolved absolute target of a direct (PC-relative) branch.
228    ///
229    /// Returns the pre-resolved [`Operand::Label`] target (`ip + offset`) for the
230    /// direct branches — `B`, `BL`, `B.<cond>`/`BC.<cond>`, `CBZ`/`CBNZ`,
231    /// `TBZ`/`TBNZ`, and the FEAT_CMPBR `CB<cc>` compare-and-branch family — and
232    /// `0` for every other instruction. The indirect register branches
233    /// (`BR`/`BLR`/`RET`/...) encode no target and return `0`, as do non-branch
234    /// instructions that merely carry a label (`ADR`/`ADRP`, literal loads).
235    /// Mirrors iced-x86's `near_branch_target`.
236    #[inline]
237    pub fn near_branch_target(&self) -> u64 {
238        // Direct branches are exactly the conditional / unconditional / direct-call
239        // flow classes; each carries one resolved `Label` operand.
240        match self.flow_control() {
241            FlowControl::ConditionalBranch
242            | FlowControl::UnconditionalBranch
243            | FlowControl::Call => {
244                for op in &self.operands[..self.op_count as usize] {
245                    if let Operand::Label(target) = *op {
246                        return target;
247                    }
248                }
249                0
250            }
251            _ => 0,
252        }
253    }
254
255    /// The condition code governing this instruction, if it has one.
256    ///
257    /// Covers every conditional form that carries the condition as an explicit
258    /// [`Operand::Cond`] — the integer conditional-select / conditional-compare
259    /// family (`CSEL`/`CSINC`/`CSINV`/`CSNEG` and the `CSET*`/`CINC`/`CINV`/`CNEG`
260    /// aliases, `CCMP`/`CCMN`), the floating-point conditional forms
261    /// (`FCSEL`/`FCCMP`/`FCCMPE`), and the direct conditional branches
262    /// `B.<cond>`/`BC.<cond>` — and additionally recovers the condition fused into
263    /// the mnemonic of the FEAT_CMPBR `CB<cc>` compare-and-branch family (whose
264    /// `cc` is encoded in the [`Code`], not as an operand). Returns `None` for
265    /// instructions that have no condition.
266    #[inline]
267    pub fn condition(&self) -> Option<Condition> {
268        // The common case: the condition is an explicit operand.
269        for op in &self.operands[..self.op_count as usize] {
270            if let Operand::Cond(c) = *op {
271                return Some(c);
272            }
273        }
274        // The FEAT_CMPBR `CB<cc>` family fuses the condition into the mnemonic.
275        use Mnemonic::*;
276        let c = match self.mnemonic {
277            Cbgt | Cbbgt | Cbhgt => Condition::Gt,
278            Cbge | Cbbge | Cbhge => Condition::Ge,
279            Cbhi | Cbbhi | Cbhhi => Condition::Hi,
280            Cbhs | Cbbhs | Cbhhs => Condition::Cs,
281            Cbeq | Cbbeq | Cbheq => Condition::Eq,
282            Cbne | Cbbne | Cbhne => Condition::Ne,
283            Cblt => Condition::Lt,
284            Cblo => Condition::Cc,
285            _ => return None,
286        };
287        Some(c)
288    }
289
290    /// The base register of this instruction's memory operand, or
291    /// [`Register::None`] if it has no [`Operand::MemImm`] / [`Operand::MemExt`]
292    /// memory operand. Mirrors iced-x86's `memory_base`.
293    #[inline]
294    pub fn memory_base(&self) -> Register {
295        for op in &self.operands[..self.op_count as usize] {
296            if let Operand::MemImm { base, .. } | Operand::MemExt { base, .. } = *op {
297                return base;
298            }
299        }
300        Register::None
301    }
302
303    /// The index register of a register-offset memory operand
304    /// ([`Operand::MemExt`]), or [`Register::None`] otherwise. Immediate-offset
305    /// ([`Operand::MemImm`]) forms have no index. Mirrors iced-x86's
306    /// `memory_index`.
307    #[inline]
308    pub fn memory_index(&self) -> Register {
309        for op in &self.operands[..self.op_count as usize] {
310            if let Operand::MemExt { index, .. } = *op {
311                return index;
312            }
313        }
314        Register::None
315    }
316
317    /// The left-shift amount applied to the index register of a register-offset
318    /// memory operand ([`Operand::MemExt`]); the effective multiplier is
319    /// `1 << memory_index_scale()`. `0` when there is no register index.
320    ///
321    /// AArch64 encodes index scaling as a shift, so this returns the shift amount
322    /// rather than the multiplier (the name keeps the iced-x86 `_scale` lineage).
323    #[inline]
324    pub fn memory_index_scale(&self) -> u32 {
325        for op in &self.operands[..self.op_count as usize] {
326            if let Operand::MemExt { shift, .. } = *op {
327                // The decoder packs a formatter "show amount" flag into bit 7 of
328                // `shift`; the actual left-shift amount is the low 7 bits.
329                return (shift & 0x7f) as u32;
330            }
331        }
332        0
333    }
334
335    /// The immediate displacement of this instruction's memory operand, as a
336    /// signed byte offset. Returns the [`Operand::MemImm`] displacement; `0` for
337    /// register-offset ([`Operand::MemExt`]) forms and for instructions with no
338    /// memory operand. Mirrors iced-x86's `memory_displacement64`.
339    #[inline]
340    pub fn memory_displacement64(&self) -> i64 {
341        for op in &self.operands[..self.op_count as usize] {
342            if let Operand::MemImm { imm, .. } = *op {
343                return imm;
344            }
345        }
346        0
347    }
348
349    /// The registers this instruction touches **without naming them in an
350    /// operand** — the link register of a call/return, the `X30`/`SP`/`X16`/
351    /// `X17` of the pointer-authentication hints, the `Xt+1..Xt+7` of the
352    /// FEAT_LS64 64-byte transfers, the SVE `FFR`, the `PC` of PC-relative
353    /// address generation, and the `NZCV` flags.
354    ///
355    /// See [`crate::implicit`] for the full rule set. The *explicit* operands
356    /// are not included here; [`crate::info::instruction_info`] merges both into
357    /// one [`crate::info::InstructionInfo::used_registers`] list.
358    #[inline]
359    pub fn implicit_registers(&self) -> crate::implicit::ImplicitRegisters {
360        crate::implicit::implicit_registers(self)
361    }
362
363    /// `true` if this is the invalid sentinel ([`Code::Invalid`]); check
364    /// [`crate::Decoder::last_error`] for the reason.
365    #[inline]
366    pub fn is_invalid(&self) -> bool {
367        matches!(self.code, Code::Invalid)
368    }
369
370    /// Crate-internal: an invalid instruction that nonetheless remembers the
371    /// `word` and `ip` it was decoded from.
372    ///
373    /// Used by the hand-written decode tree to seed `out` before a group decoder
374    /// fills it in, so that reserved / unallocated encodings still report the
375    /// correct address and raw word via [`Instruction::word`] /
376    /// [`Instruction::ip`] while remaining [`Code::Invalid`].
377    #[inline]
378    pub(crate) const fn new_invalid(word: u32, ip: u64) -> Self {
379        Instruction {
380            word,
381            ip,
382            code: Code::Invalid,
383            mnemonic: Mnemonic::Invalid,
384            op_count: 0,
385            flags: 0,
386            operands: [Operand::None; MAX_OPERANDS],
387        }
388    }
389
390    /// Crate-internal: set the encoding `code` and its default [`Mnemonic`]
391    /// (`code.mnemonic()`), and reset the operand list to empty.
392    ///
393    /// Group decoders call this first, then [`Instruction::push_operand`] for
394    /// each operand, and optionally [`Instruction::set_alias`] to install a
395    /// preferred-disassembly alias while keeping `code` canonical.
396    #[inline]
397    pub(crate) fn set(&mut self, code: Code) {
398        self.code = code;
399        self.mnemonic = code.mnemonic();
400        self.op_count = 0;
401        self.operands = [Operand::None; MAX_OPERANDS];
402    }
403
404    /// Crate-internal: install a preferred-disassembly alias mnemonic while
405    /// leaving [`Instruction::code`] as the canonical encoding identity.
406    ///
407    /// Distinct from the public [`Instruction::set_mnemonic`] editor: this is
408    /// part of *decoding* and does not mark the instruction modified.
409    #[inline]
410    pub(crate) fn set_alias(&mut self, mnemonic: Mnemonic) {
411        self.mnemonic = mnemonic;
412    }
413
414    /// Crate-internal: append `op` to the operand list (saturating at
415    /// [`MAX_OPERANDS`]; excess operands are dropped, never panicking).
416    #[inline]
417    pub(crate) fn push_operand(&mut self, op: Operand) {
418        let i = self.op_count as usize;
419        if i < MAX_OPERANDS {
420            self.operands[i] = op;
421            self.op_count = (i + 1) as u8;
422        }
423    }
424}
425
426// ---------------------------------------------------------------------------
427// Editing and re-encoding.
428// ---------------------------------------------------------------------------
429
430/// `true` if `reg` is legal for the operand *shape* `op`, beyond the register
431/// class and width that [`Instruction::set_op_register`] already checks.
432///
433/// Most shapes name a whole register file and accept anything of the right
434/// class. [`Operand::PredCounter`] is the exception: the predicate-as-counter
435/// `PNg` field is three bits wide, so it can only name `P8`..`P15` even though
436/// `P0`..`P7` are the same class and width.
437#[inline]
438fn operand_accepts_register(op: &Operand, reg: Register) -> bool {
439    match op {
440        Operand::PredCounter { .. } => reg.number() >= 8,
441        _ => true,
442    }
443}
444
445/// # Editing and re-encoding
446///
447/// Every mutator below edits the instruction's *semantics* in place and marks
448/// it [modified](Instruction::is_modified). Because
449/// [`Instruction::encode`](crate::encode::encode) rebuilds the 32-bit word from
450/// semantics alone — it never reads [`Instruction::word`] — an edit followed by
451/// `encode()` yields the word for the *edited* instruction:
452///
453/// ```
454/// use fARM64::{Decoder, DecoderOptions, Register};
455///
456/// // `add x0, x1, x2`
457/// let bytes = 0x8B02_0020u32.to_le_bytes();
458/// let mut insn = Decoder::new(&bytes, 0, DecoderOptions::NONE).decode();
459///
460/// // Retarget the destination to x5 and the second source to x7.
461/// assert!(insn.set_op_register(0, Register::X5));
462/// assert!(insn.set_op_register(2, Register::X7));
463/// assert!(insn.is_modified());
464///
465/// // `add x5, x1, x7`
466/// assert_eq!(insn.encode(), Ok(0x8B07_0025));
467/// ```
468///
469/// ## Rules
470///
471/// * Every mutator is **total**: it returns `false` (or leaves the value
472///   untouched) instead of panicking when the edit does not apply — an
473///   out-of-range slot, a wrong operand shape, or a value that does not fit the
474///   operand variant.
475/// * Register replacement is **class- and width-checked** by default:
476///   [`set_op_register`](Instruction::set_op_register) refuses to put a `W`
477///   register where an `X` register was, or a `P` register where a `Z` register
478///   was, because [`Instruction::code`] — not the operand — carries the operand
479///   size. Use
480///   [`set_op_register_unchecked`](Instruction::set_op_register_unchecked) to
481///   override, or [`set_code`](Instruction::set_code) to move to the encoding
482///   that matches.
483/// * Operand **decorations are preserved**: replacing the register of
484///   `v0.4s[1]` keeps the arrangement and lane; replacing an immediate keeps
485///   the immediate's variant (and therefore how the encoder packs it).
486/// * The edit is not validated against the encoding. A value with no
487///   representation in the instruction's fields surfaces as an
488///   [`EncodeError`](crate::EncodeError) from `encode()`, not here.
489/// * [`Instruction::word`] keeps returning the word the instruction was decoded
490///   from. Use [`re_encode`](Instruction::re_encode) to encode and store the new
491///   word in its place.
492impl Instruction {
493    /// `flags` bit 0: an editing mutator has run, so [`Instruction::word`] no
494    /// longer describes this value.
495    pub(crate) const FLAG_MODIFIED: u8 = 1 << 0;
496
497    /// `true` if any editing mutator has run since this instruction was
498    /// decoded (or since the last [`re_encode`](Instruction::re_encode)).
499    ///
500    /// When this is `true`, [`Instruction::word`] is still the *original*
501    /// decoded word and can no longer be assumed to encode this instruction.
502    ///
503    /// It is an upper bound, not an exact answer: an operand edit sets it even
504    /// in the rare case where the encoding cannot tell the difference (swapping
505    /// `SP` for `XZR`, which both encode as register 31). The one case that is
506    /// exact is the address — [`set_ip`](Instruction::set_ip) and
507    /// [`relocate`](Instruction::relocate) set it only for an instruction whose
508    /// encoding actually depends on `ip`, i.e. one carrying an
509    /// [`Operand::Label`].
510    #[inline]
511    pub const fn is_modified(&self) -> bool {
512        self.flags & Self::FLAG_MODIFIED != 0
513    }
514
515    /// Mark the instruction edited.
516    #[inline]
517    fn mark_modified(&mut self) {
518        self.flags |= Self::FLAG_MODIFIED;
519    }
520
521    /// `true` if any operand carries a resolved PC-relative target, which is
522    /// exactly when the encoding depends on [`Instruction::ip`].
523    #[inline]
524    fn has_label(&self) -> bool {
525        self.operands[..self.op_count as usize]
526            .iter()
527            .any(|op| matches!(op, Operand::Label(_)))
528    }
529
530    // --- Identity -----------------------------------------------------------
531
532    /// Set the address this instruction sits at.
533    ///
534    /// Operands that carry a **resolved absolute** target ([`Operand::Label`])
535    /// are left alone, so moving an instruction keeps it pointing at the same
536    /// address and the encoder re-derives the PC-relative displacement. To move
537    /// an instruction and keep its *relative* displacement instead, use
538    /// [`relocate`](Instruction::relocate).
539    ///
540    /// Only a PC-relative instruction's *encoding* depends on `ip`, so this
541    /// marks the instruction [modified](Instruction::is_modified) only when it
542    /// carries a label. Moving an `ADD` leaves its word — and `is_modified()` —
543    /// untouched.
544    #[inline]
545    pub fn set_ip(&mut self, ip: u64) {
546        self.ip = ip;
547        if self.has_label() {
548            self.mark_modified();
549        }
550    }
551
552    /// Move this instruction to `ip`, keeping every PC-relative displacement
553    /// the same.
554    ///
555    /// Each [`Operand::Label`] is shifted by `ip - self.ip()`, so a branch that
556    /// jumped `+8` still jumps `+8` from the new address. This is the
557    /// "copy an instruction elsewhere and keep it self-relative" move;
558    /// [`set_ip`](Instruction::set_ip) is the "keep pointing at the same
559    /// absolute address" move.
560    ///
561    /// `ADRP` resolves to a 4 KiB **page**, so only a page-aligned `delta`
562    /// keeps it encodable; a finer one leaves a label `ADRP` cannot name and
563    /// `encode()` reports
564    /// [`EncodeError::InvalidImmediate`](crate::EncodeError::InvalidImmediate).
565    /// See [`set_label`](Instruction::set_label).
566    #[inline]
567    pub fn relocate(&mut self, ip: u64) {
568        let delta = ip.wrapping_sub(self.ip);
569        let mut moved = false;
570        for op in self.operands[..self.op_count as usize].iter_mut() {
571            if let Operand::Label(target) = op {
572                *target = target.wrapping_add(delta);
573                moved = true;
574            }
575        }
576        self.ip = ip;
577        // A label-less instruction encodes identically wherever it sits, so
578        // moving it does not make `word()` stale.
579        if moved {
580            self.mark_modified();
581        }
582    }
583
584    /// Set the encoding identity and reset the mnemonic to that encoding's
585    /// default ([`Code::mnemonic`]).
586    ///
587    /// The operand list is **kept**: this is how you move an instruction to a
588    /// sibling encoding that takes the same operand shape (`ADD` to `SUB`, the
589    /// 32-bit form to the 64-bit form). The encoder dispatches on
590    /// [`Instruction::code`], so the new code decides how the operands are
591    /// packed — and rejects them if they do not fit.
592    ///
593    /// ```
594    /// use fARM64::{Code, Decoder, DecoderOptions};
595    ///
596    /// // `add x0, x1, x2` -> `sub x0, x1, x2`
597    /// let bytes = 0x8B02_0020u32.to_le_bytes();
598    /// let mut insn = Decoder::new(&bytes, 0, DecoderOptions::NONE).decode();
599    /// insn.set_code(Code::SubShifted64);
600    /// assert_eq!(insn.encode(), Ok(0xCB02_0020));
601    /// ```
602    #[inline]
603    pub fn set_code(&mut self, code: Code) {
604        self.code = code;
605        self.mnemonic = code.mnemonic();
606        self.mark_modified();
607    }
608
609    /// Override the displayed mnemonic, leaving [`Instruction::code`] — the
610    /// canonical encoding identity the encoder dispatches on — unchanged.
611    ///
612    /// This selects between the aliases of one encoding (`SUBS XZR, Xn, Xm`
613    /// spelled as `CMP`, `ORR Xd, XZR, Xm` spelled as `MOV`). The encoder
614    /// inverts the alias it is given, so the mnemonic must match the operand
615    /// list actually present; to change the *encoding*, use
616    /// [`set_code`](Instruction::set_code).
617    #[inline]
618    pub fn set_mnemonic(&mut self, mnemonic: Mnemonic) {
619        self.mnemonic = mnemonic;
620        self.mark_modified();
621    }
622
623    // --- Operands -----------------------------------------------------------
624
625    /// Replace operand `n` wholesale.
626    ///
627    /// `n` may be any slot below [`MAX_OPERANDS`]; writing past the current
628    /// [`op_count`](Instruction::op_count) extends the operand list (the slots
629    /// in between become [`Operand::None`]). Returns `false` — changing
630    /// nothing — if `n >= MAX_OPERANDS`.
631    #[inline]
632    pub fn set_op(&mut self, n: usize, op: Operand) -> bool {
633        if n >= MAX_OPERANDS {
634            return false;
635        }
636        self.operands[n] = op;
637        if n >= self.op_count as usize {
638            self.op_count = (n + 1) as u8;
639        }
640        self.mark_modified();
641        true
642    }
643
644    /// Set the number of meaningful operands.
645    ///
646    /// Truncating clears the dropped slots to [`Operand::None`]; growing
647    /// exposes [`Operand::None`] slots for [`set_op`](Instruction::set_op) to
648    /// fill. Returns `false` if `count > MAX_OPERANDS`.
649    #[inline]
650    pub fn set_op_count(&mut self, count: usize) -> bool {
651        if count > MAX_OPERANDS {
652            return false;
653        }
654        for op in self.operands[count..].iter_mut() {
655            *op = Operand::None;
656        }
657        self.op_count = count as u8;
658        self.mark_modified();
659        true
660    }
661
662    /// Append `op` after the last operand.
663    ///
664    /// Returns `false` — changing nothing — if the operand list is already
665    /// [`MAX_OPERANDS`] long.
666    #[inline]
667    pub fn push_op(&mut self, op: Operand) -> bool {
668        let i = self.op_count as usize;
669        if i >= MAX_OPERANDS {
670            return false;
671        }
672        self.operands[i] = op;
673        self.op_count = (i + 1) as u8;
674        self.mark_modified();
675        true
676    }
677
678    /// Replace the register of operand `n`, keeping every decoration
679    /// (arrangement, lane, shift, extend, predicate qualifier).
680    ///
681    /// `reg` must be the same [register class](crate::RegClass) and
682    /// [width](Register::width_bits) as the register it replaces — `X5` for
683    /// `X1`, `V7` for `V0`, `P3` for `P0` — because the operand *size* lives in
684    /// [`Instruction::code`], not in the operand. Operand shapes that can only
685    /// name part of a register file are checked against that range too: the
686    /// predicate-as-counter [`Operand::PredCounter`] has a 3-bit `PNg` field and
687    /// accepts only `P8`..`P15`. A mismatch returns `false` and changes nothing;
688    /// use
689    /// [`set_op_register_unchecked`](Instruction::set_op_register_unchecked) if
690    /// you are deliberately changing class or width alongside
691    /// [`set_code`](Instruction::set_code).
692    ///
693    /// Covers the single-register operand shapes ([`Operand::Reg`],
694    /// [`Operand::RegBang`], [`Operand::IndexedElement`],
695    /// [`Operand::PredCounter`]). Register *lists* and pairs
696    /// ([`Operand::MultiReg`], [`Operand::RegPair`], [`Operand::SveVecGroup`])
697    /// are ambiguous and return `false` — rebuild them with
698    /// [`set_op`](Instruction::set_op). Memory bases and indices have their own
699    /// setters ([`set_memory_base`](Instruction::set_memory_base) /
700    /// [`set_memory_index`](Instruction::set_memory_index)).
701    #[inline]
702    pub fn set_op_register(&mut self, n: usize, reg: Register) -> bool {
703        let op = self.op(n);
704        let old = match self.op_register_component(n) {
705            Some(r) => r,
706            None => return false,
707        };
708        if old.class() != reg.class() || old.width_bits() != reg.width_bits() {
709            return false;
710        }
711        if !operand_accepts_register(&op, reg) {
712            return false;
713        }
714        self.set_op_register_unchecked(n, reg)
715    }
716
717    /// Replace the register of operand `n` **without** the class/width check of
718    /// [`set_op_register`](Instruction::set_op_register).
719    ///
720    /// Use this when the operand size is changing too — pair it with
721    /// [`set_code`](Instruction::set_code) so the encoding agrees. Still returns
722    /// `false` for operand shapes that carry no single replaceable register.
723    #[inline]
724    pub fn set_op_register_unchecked(&mut self, n: usize, reg: Register) -> bool {
725        if n >= self.op_count as usize {
726            return false;
727        }
728        let ok = match &mut self.operands[n] {
729            Operand::Reg { reg: r, .. } => {
730                *r = reg;
731                true
732            }
733            Operand::RegBang(r) => {
734                *r = reg;
735                true
736            }
737            Operand::IndexedElement { reg: r, .. } => {
738                *r = reg;
739                true
740            }
741            Operand::PredCounter { reg: r, .. } => {
742                *r = reg;
743                true
744            }
745            _ => false,
746        };
747        if ok {
748            self.mark_modified();
749        }
750        ok
751    }
752
753    /// The single register carried by operand `n`, for the shapes
754    /// [`set_op_register`](Instruction::set_op_register) can edit.
755    ///
756    /// See also [`operand_accepts_register`], which decides whether a *new*
757    /// register is legal for that shape.
758    #[inline]
759    fn op_register_component(&self, n: usize) -> Option<Register> {
760        match self.op(n) {
761            Operand::Reg { reg, .. }
762            | Operand::RegBang(reg)
763            | Operand::IndexedElement { reg, .. }
764            | Operand::PredCounter { reg, .. } => Some(reg),
765            _ => None,
766        }
767    }
768
769    /// Replace the value of the immediate at operand `n`, keeping its
770    /// [`Operand`] variant.
771    ///
772    /// The variant decides how the encoder packs the value (a bitmask immediate
773    /// is not packed like an unsigned one), so it is deliberately preserved:
774    /// [`Operand::ImmUnsigned`], [`Operand::ImmSigned`],
775    /// [`Operand::ImmLogical`], [`Operand::ImmSignedDec`],
776    /// [`Operand::ShiftAmount`], [`Operand::Label`] and the shifted-move
777    /// immediates all keep their kind. Signed variants reinterpret `value` via
778    /// `as i64`.
779    ///
780    /// Returns `false` — changing nothing — if operand `n` is not an immediate,
781    /// or if `value` does not fit the variant's payload (an
782    /// [`Operand::ShiftAmount`] above `255`, an
783    /// [`Operand::ImmShiftedMove`] above `0xFFFF`). To change the *kind* of an
784    /// operand, or to set an [`Operand::FpImm`], use
785    /// [`set_op`](Instruction::set_op).
786    #[inline]
787    pub fn set_op_immediate(&mut self, n: usize, value: u64) -> bool {
788        if n >= self.op_count as usize {
789            return false;
790        }
791        let ok = match &mut self.operands[n] {
792            Operand::ImmUnsigned(v) | Operand::ImmLogical(v) | Operand::Label(v) => {
793                *v = value;
794                true
795            }
796            Operand::ImmSigned(v) | Operand::ImmSignedDec(v) => {
797                *v = value as i64;
798                true
799            }
800            Operand::ShiftAmount(v) => match u8::try_from(value) {
801                Ok(x) => {
802                    *v = x;
803                    true
804                }
805                Err(_) => false,
806            },
807            Operand::ImmShiftedMove { imm, .. } | Operand::ImmShiftedMsl { imm, .. } => {
808                match u16::try_from(value) {
809                    Ok(x) => {
810                        *imm = x;
811                        true
812                    }
813                    Err(_) => false,
814                }
815            }
816            _ => false,
817        };
818        if ok {
819            self.mark_modified();
820        }
821        ok
822    }
823
824    /// Replace the condition code of the [`Operand::Cond`] operand.
825    ///
826    /// Returns `false` if this instruction carries no condition operand — which
827    /// includes the families that fuse the condition into the [`Code`] instead
828    /// (the FEAT_CMPBR `CB<cc>` compare-and-branch forms); change those with
829    /// [`set_code`](Instruction::set_code).
830    #[inline]
831    pub fn set_condition(&mut self, cond: Condition) -> bool {
832        for op in self.operands[..self.op_count as usize].iter_mut() {
833            if let Operand::Cond(c) = op {
834                *c = cond;
835                self.mark_modified();
836                return true;
837            }
838        }
839        false
840    }
841
842    /// Retarget a direct branch, as an **absolute** address.
843    ///
844    /// The setter counterpart of
845    /// [`near_branch_target`](Instruction::near_branch_target): it applies only
846    /// to the direct branch / direct call forms that carry a resolved target
847    /// (`B`, `BL`, `B.<cond>`, `CBZ`/`CBNZ`, `TBZ`/`TBNZ`, the FEAT_CMPBR
848    /// `CB<cc>` family), and returns `false` for everything else. The encoder
849    /// re-derives the PC-relative displacement from
850    /// [`ip`](Instruction::ip), and reports
851    /// [`EncodeError::InvalidImmediate`](crate::EncodeError::InvalidImmediate)
852    /// if the new target is out of range or misaligned for the encoding.
853    ///
854    /// Use [`set_label`](Instruction::set_label) for the non-branch labels of
855    /// `ADR`/`ADRP` and the PC-relative literal loads.
856    #[inline]
857    pub fn set_near_branch_target(&mut self, target: u64) -> bool {
858        match self.flow_control() {
859            FlowControl::ConditionalBranch
860            | FlowControl::UnconditionalBranch
861            | FlowControl::Call => self.set_label(target),
862            _ => false,
863        }
864    }
865
866    /// Retarget the [`Operand::Label`] of this instruction, as an **absolute**
867    /// address.
868    ///
869    /// Covers every label-bearing form, branches included: `ADR`/`ADRP`, the
870    /// PC-relative literal loads, and the direct branches. Returns `false` if
871    /// there is no label operand.
872    ///
873    /// The target must be representable at the encoding's granularity — `ADRP`
874    /// addresses a 4 KiB **page**, the branches are 4-byte aligned — and a
875    /// target that is not reports
876    /// [`EncodeError::InvalidImmediate`](crate::EncodeError::InvalidImmediate)
877    /// from [`encode`](Instruction::encode). A label that encodes at all always
878    /// encodes exactly; no form silently rounds the target it was given.
879    #[inline]
880    pub fn set_label(&mut self, target: u64) -> bool {
881        for op in self.operands[..self.op_count as usize].iter_mut() {
882            if let Operand::Label(t) = op {
883                *t = target;
884                self.mark_modified();
885                return true;
886            }
887        }
888        false
889    }
890
891    /// Replace the base register of this instruction's memory operand.
892    ///
893    /// Applies to the first [`Operand::MemImm`] / [`Operand::MemExt`] /
894    /// [`Operand::SveMem`] operand — the counterpart of
895    /// [`memory_base`](Instruction::memory_base) — keeping the addressing mode,
896    /// displacement, index and extend. Returns `false` if there is no memory
897    /// operand.
898    ///
899    /// Not class-checked: an A64 memory base is always `Xn|SP` (or, for the
900    /// SVE vector-base modes, a `Zn`), so pass the register the mode expects.
901    #[inline]
902    pub fn set_memory_base(&mut self, reg: Register) -> bool {
903        for op in self.operands[..self.op_count as usize].iter_mut() {
904            match op {
905                Operand::MemImm { base, .. }
906                | Operand::MemExt { base, .. }
907                | Operand::SveMem { base, .. } => {
908                    *base = reg;
909                    self.mark_modified();
910                    return true;
911                }
912                _ => {}
913            }
914        }
915        false
916    }
917
918    /// Replace the index register of a register-offset memory operand.
919    ///
920    /// Applies to the first [`Operand::MemExt`] (or the offset register of an
921    /// [`Operand::SveMem`]) — the counterpart of
922    /// [`memory_index`](Instruction::memory_index) — keeping the extend and
923    /// shift. Returns `false` for immediate-offset forms, which have no index.
924    #[inline]
925    pub fn set_memory_index(&mut self, reg: Register) -> bool {
926        for op in self.operands[..self.op_count as usize].iter_mut() {
927            match op {
928                Operand::MemExt { index, .. } | Operand::SveMem { offset: index, .. } => {
929                    *index = reg;
930                    self.mark_modified();
931                    return true;
932                }
933                _ => {}
934            }
935        }
936        false
937    }
938
939    /// Replace the immediate displacement of this instruction's memory operand.
940    ///
941    /// Applies to the first [`Operand::MemImm`] (or [`Operand::SveMem`], whose
942    /// displacement is 32-bit) — the counterpart of
943    /// [`memory_displacement64`](Instruction::memory_displacement64) — keeping
944    /// the addressing mode. Returns `false` if there is no immediate-offset
945    /// memory operand, or if `disp` does not fit an [`Operand::SveMem`]'s
946    /// 32-bit displacement.
947    ///
948    /// Scaling is the encoder's job: pass the **byte** displacement exactly as
949    /// [`memory_displacement64`](Instruction::memory_displacement64) reports
950    /// it. A displacement the encoding cannot represent surfaces as
951    /// [`EncodeError::InvalidImmediate`](crate::EncodeError::InvalidImmediate).
952    #[inline]
953    pub fn set_memory_displacement64(&mut self, disp: i64) -> bool {
954        for op in self.operands[..self.op_count as usize].iter_mut() {
955            match op {
956                Operand::MemImm { imm, .. } => {
957                    *imm = disp;
958                    self.mark_modified();
959                    return true;
960                }
961                Operand::SveMem { imm, .. } => match i32::try_from(disp) {
962                    Ok(v) => {
963                        *imm = v;
964                        self.mark_modified();
965                        return true;
966                    }
967                    Err(_) => return false,
968                },
969                _ => {}
970            }
971        }
972        false
973    }
974}
975
976impl Default for Instruction {
977    /// The invalid/empty instruction: [`Code::Invalid`], no operands, `ip == 0`.
978    #[inline]
979    fn default() -> Self {
980        Instruction {
981            word: 0,
982            ip: 0,
983            code: Code::Invalid,
984            mnemonic: Mnemonic::Invalid,
985            op_count: 0,
986            flags: 0,
987            operands: [Operand::None; MAX_OPERANDS],
988        }
989    }
990}
991
992#[cfg(test)]
993mod tests {
994    use super::*;
995
996    /// Build a minimal instruction with a chosen code + mnemonic for
997    /// classification tests (operands are irrelevant here).
998    fn insn(code: Code, mnemonic: Mnemonic) -> Instruction {
999        let mut i = Instruction::new_invalid(0, 0);
1000        i.code = code;
1001        i.mnemonic = mnemonic;
1002        i
1003    }
1004
1005    #[test]
1006    fn flow_control_branches() {
1007        // B.<cond> is conditional; plain B is unconditional.
1008        assert_eq!(
1009            insn(Code::BCond, Mnemonic::B).flow_control(),
1010            FlowControl::ConditionalBranch
1011        );
1012        assert_eq!(
1013            insn(Code::BUncond, Mnemonic::B).flow_control(),
1014            FlowControl::UnconditionalBranch
1015        );
1016        assert_eq!(
1017            insn(Code::Cbz64, Mnemonic::Cbz).flow_control(),
1018            FlowControl::ConditionalBranch
1019        );
1020        assert_eq!(
1021            insn(Code::Tbnz, Mnemonic::Tbnz).flow_control(),
1022            FlowControl::ConditionalBranch
1023        );
1024    }
1025
1026    #[test]
1027    fn flow_control_calls_and_returns() {
1028        assert_eq!(
1029            insn(Code::BlImm, Mnemonic::Bl).flow_control(),
1030            FlowControl::Call
1031        );
1032        assert_eq!(
1033            insn(Code::Blr, Mnemonic::Blr).flow_control(),
1034            FlowControl::IndirectCall
1035        );
1036        assert_eq!(
1037            insn(Code::Br, Mnemonic::Br).flow_control(),
1038            FlowControl::IndirectBranch
1039        );
1040        assert_eq!(
1041            insn(Code::Ret, Mnemonic::Ret).flow_control(),
1042            FlowControl::Return
1043        );
1044    }
1045
1046    #[test]
1047    fn flow_control_exceptions_and_next() {
1048        assert_eq!(
1049            insn(Code::Invalid, Mnemonic::Svc).flow_control(),
1050            FlowControl::Exception
1051        );
1052        assert_eq!(
1053            insn(Code::Invalid, Mnemonic::Brk).flow_control(),
1054            FlowControl::Exception
1055        );
1056        // A plain ALU op falls through.
1057        assert_eq!(
1058            insn(Code::Invalid, Mnemonic::Add).flow_control(),
1059            FlowControl::Next
1060        );
1061    }
1062
1063    #[test]
1064    fn set_flags_classification() {
1065        // S-suffixed and compare/test forms set NZCV via the integer path.
1066        for m in [
1067            Mnemonic::Adds,
1068            Mnemonic::Subs,
1069            Mnemonic::Adcs,
1070            Mnemonic::Sbcs,
1071            Mnemonic::Ands,
1072            Mnemonic::Bics,
1073            Mnemonic::Cmp,
1074            Mnemonic::Cmn,
1075            Mnemonic::Tst,
1076            Mnemonic::Ccmp,
1077            Mnemonic::Ccmn,
1078        ] {
1079            assert_eq!(
1080                insn(Code::Invalid, m).set_flags(),
1081                FlagEffect::SetsNormal,
1082                "{m:?} should set NZCV"
1083            );
1084        }
1085        // FP compares set NZCV via the FP path.
1086        for m in [
1087            Mnemonic::Fcmp,
1088            Mnemonic::Fcmpe,
1089            Mnemonic::Fccmp,
1090            Mnemonic::Fccmpe,
1091        ] {
1092            assert_eq!(insn(Code::Invalid, m).set_flags(), FlagEffect::SetsFloat);
1093        }
1094        // ADC reads flags but does not write them; ADD writes none.
1095        assert_eq!(
1096            insn(Code::Invalid, Mnemonic::Adc).set_flags(),
1097            FlagEffect::None
1098        );
1099        assert_eq!(
1100            insn(Code::Invalid, Mnemonic::Add).set_flags(),
1101            FlagEffect::None
1102        );
1103    }
1104
1105    /// Decode a single 32-bit word at `ip` through the public decoder (all
1106    /// features on). Encodings below are cross-checked against `llvm-mc`.
1107    fn decode(word: u32, ip: u64) -> Instruction {
1108        let bytes = word.to_le_bytes();
1109        let mut dec = crate::Decoder::new(&bytes, ip, crate::DecoderOptions::NONE);
1110        dec.decode()
1111    }
1112
1113    #[test]
1114    fn bcond_near_branch_and_condition() {
1115        // `b.eq #8` @ 0x1000 -> target 0x1008, condition EQ.
1116        let i = decode(0x5400_0040, 0x1000);
1117        assert_eq!(i.code(), Code::BCond);
1118        assert_eq!(i.mnemonic(), Mnemonic::B);
1119        assert_eq!(i.condition(), Some(Condition::Eq));
1120        assert_eq!(i.near_branch_target(), 0x1008);
1121        assert_eq!(i.flow_control(), FlowControl::ConditionalBranch);
1122    }
1123
1124    #[test]
1125    fn uncond_branch_targets() {
1126        // `b #4` @ 0.
1127        let b = decode(0x1400_0001, 0);
1128        assert_eq!(b.code(), Code::BUncond);
1129        assert_eq!(b.near_branch_target(), 4);
1130        assert_eq!(b.condition(), None);
1131        // `bl #4` @ 0 (direct call still has a near-branch target).
1132        let bl = decode(0x9400_0001, 0);
1133        assert_eq!(bl.mnemonic(), Mnemonic::Bl);
1134        assert_eq!(bl.near_branch_target(), 4);
1135        assert_eq!(bl.condition(), None);
1136    }
1137
1138    #[test]
1139    fn cbz_near_branch_no_condition() {
1140        // `cbz x0, #8` @ 0x2000.
1141        let i = decode(0xB400_0040, 0x2000);
1142        assert_eq!(i.mnemonic(), Mnemonic::Cbz);
1143        assert_eq!(i.near_branch_target(), 0x2008);
1144        // CBZ/CBNZ test against zero — they carry no condition code.
1145        assert_eq!(i.condition(), None);
1146    }
1147
1148    #[test]
1149    fn cmpbr_condition_recovered_from_mnemonic() {
1150        // `cbgt w2, w1, #4` @ 0 (FEAT_CMPBR register form): the cc is fused into
1151        // the Code/mnemonic, not an operand.
1152        let i = decode(0x7401_0022, 0);
1153        assert_eq!(i.mnemonic(), Mnemonic::Cbgt);
1154        assert_eq!(i.condition(), Some(Condition::Gt));
1155        assert_eq!(i.near_branch_target(), 4);
1156    }
1157
1158    #[test]
1159    fn csel_condition_operand() {
1160        // `csel x0, x1, x2, ne` — cc carried as an Operand::Cond.
1161        let i = decode(0x9A82_1020, 0);
1162        assert_eq!(i.mnemonic(), Mnemonic::Csel);
1163        assert_eq!(i.condition(), Some(Condition::Ne));
1164        assert_eq!(i.near_branch_target(), 0);
1165    }
1166
1167    #[test]
1168    fn memory_imm_offset_projection() {
1169        // `ldr x0, [x1, #8]`.
1170        let i = decode(0xF940_0420, 0);
1171        assert_eq!(i.mnemonic(), Mnemonic::Ldr);
1172        assert_eq!(i.memory_base(), Register::X1);
1173        assert_eq!(i.memory_displacement64(), 8);
1174        assert_eq!(i.memory_index(), Register::None);
1175        assert_eq!(i.memory_index_scale(), 0);
1176    }
1177
1178    #[test]
1179    fn memory_reg_offset_projection() {
1180        // `ldr x0, [x1, x2, lsl #3]`.
1181        let i = decode(0xF862_7820, 0);
1182        assert_eq!(i.mnemonic(), Mnemonic::Ldr);
1183        assert_eq!(i.memory_base(), Register::X1);
1184        assert_eq!(i.memory_index(), Register::X2);
1185        assert_eq!(i.memory_index_scale(), 3);
1186        // Register-offset forms have no immediate displacement.
1187        assert_eq!(i.memory_displacement64(), 0);
1188    }
1189
1190    #[test]
1191    fn non_memory_non_branch_projections_are_inert() {
1192        // `add x0, x1, x2` — no memory operand, no condition, no branch target.
1193        let i = decode(0x8B02_0020, 0);
1194        assert_eq!(i.memory_base(), Register::None);
1195        assert_eq!(i.memory_index(), Register::None);
1196        assert_eq!(i.memory_index_scale(), 0);
1197        assert_eq!(i.memory_displacement64(), 0);
1198        assert_eq!(i.condition(), None);
1199        assert_eq!(i.near_branch_target(), 0);
1200    }
1201}