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}