Skip to main content

cranelift_codegen/isa/aarch64/inst/
args.rs

1//! AArch64 ISA definitions: instruction arguments.
2
3use crate::ir::types::*;
4use crate::isa::aarch64::inst::*;
5
6//=============================================================================
7// Instruction sub-components: shift and extend descriptors
8
9/// A shift operator for a register or immediate.
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11#[repr(u8)]
12pub enum ShiftOp {
13    /// Logical shift left.
14    LSL = 0b00,
15    /// Logical shift right.
16    LSR = 0b01,
17    /// Arithmetic shift right.
18    ASR = 0b10,
19    /// Rotate right.
20    ROR = 0b11,
21}
22
23impl ShiftOp {
24    /// Get the encoding of this shift op.
25    pub fn bits(self) -> u8 {
26        self as u8
27    }
28}
29
30/// A shift operator amount.
31#[derive(Clone, Copy, Debug)]
32pub struct ShiftOpShiftImm(u8);
33
34impl ShiftOpShiftImm {
35    /// Maximum shift for shifted-register operands.
36    pub const MAX_SHIFT: u64 = 63;
37
38    /// Create a new shiftop shift amount, if possible.
39    pub fn maybe_from_shift(shift: u64) -> Option<ShiftOpShiftImm> {
40        if shift <= Self::MAX_SHIFT {
41            Some(ShiftOpShiftImm(shift as u8))
42        } else {
43            None
44        }
45    }
46
47    /// Return the shift amount.
48    pub fn value(self) -> u8 {
49        self.0
50    }
51
52    /// Mask down to a given number of bits.
53    pub fn mask(self, bits: u8) -> ShiftOpShiftImm {
54        ShiftOpShiftImm(self.0 & (bits - 1))
55    }
56}
57
58/// A shift operator with an amount, guaranteed to be within range.
59#[derive(Copy, Clone, Debug)]
60pub struct ShiftOpAndAmt {
61    /// The shift operator.
62    op: ShiftOp,
63    /// The shift operator amount.
64    shift: ShiftOpShiftImm,
65}
66
67impl ShiftOpAndAmt {
68    /// Create a new shift operator with an amount.
69    pub fn new(op: ShiftOp, shift: ShiftOpShiftImm) -> ShiftOpAndAmt {
70        ShiftOpAndAmt { op, shift }
71    }
72
73    /// Get the shift op.
74    pub fn op(&self) -> ShiftOp {
75        self.op
76    }
77
78    /// Get the shift amount.
79    pub fn amt(&self) -> ShiftOpShiftImm {
80        self.shift
81    }
82}
83
84/// An extend operator for a register.
85#[derive(Clone, Copy, Debug)]
86#[repr(u8)]
87pub enum ExtendOp {
88    /// Unsigned extend byte.
89    UXTB = 0b000,
90    /// Unsigned extend halfword.
91    UXTH = 0b001,
92    /// Unsigned extend word.
93    UXTW = 0b010,
94    /// Unsigned extend doubleword.
95    UXTX = 0b011,
96    /// Signed extend byte.
97    SXTB = 0b100,
98    /// Signed extend halfword.
99    SXTH = 0b101,
100    /// Signed extend word.
101    SXTW = 0b110,
102    /// Signed extend doubleword.
103    SXTX = 0b111,
104}
105
106impl ExtendOp {
107    /// Encoding of this op.
108    pub fn bits(self) -> u8 {
109        self as u8
110    }
111
112    /// The width in bits of the value being extended.
113    pub fn from_bits(self) -> u8 {
114        match self {
115            Self::UXTB | Self::SXTB => 8,
116            Self::UXTH | Self::SXTH => 16,
117            Self::UXTW | Self::SXTW => 32,
118            Self::UXTX | Self::SXTX => 64,
119        }
120    }
121}
122
123//=============================================================================
124// Instruction sub-components (memory addresses): definitions
125
126/// A reference to some memory address.
127#[derive(Clone, Debug)]
128pub enum MemLabel {
129    /// An address in the code, a constant pool or jumptable, with relative
130    /// offset from this instruction. This form must be used at emission time;
131    /// see `memlabel_finalize()` for how other forms are lowered to this one.
132    PCRel(i32),
133    /// An address that refers to a label within a `MachBuffer`, for example a
134    /// constant that lives in the pool at the end of the function.
135    Mach(MachLabel),
136}
137
138impl AMode {
139    /// Memory reference using an address in a register.
140    pub fn reg(reg: Reg) -> AMode {
141        // Use UnsignedOffset rather than Unscaled to use ldr rather than ldur.
142        // This also does not use PostIndexed / PreIndexed as they update the register.
143        AMode::UnsignedOffset {
144            rn: reg,
145            uimm12: UImm12Scaled::zero(I64),
146        }
147    }
148
149    /// Memory reference using `reg1 + sizeof(ty) * reg2` as an address, with `reg2` sign- or
150    /// zero-extended as per `op`.
151    pub fn reg_plus_reg_scaled_extended(reg1: Reg, reg2: Reg, op: ExtendOp) -> AMode {
152        AMode::RegScaledExtended {
153            rn: reg1,
154            rm: reg2,
155            extendop: op,
156        }
157    }
158}
159
160pub use crate::isa::aarch64::lower::isle::generated_code::PairAMode;
161
162//=============================================================================
163// Instruction sub-components (conditions, branches and branch targets):
164// definitions
165
166/// Condition for conditional branches.
167#[derive(Clone, Copy, Debug, PartialEq, Eq)]
168#[repr(u8)]
169pub enum Cond {
170    /// Equal.
171    Eq = 0,
172    /// Not equal.
173    Ne = 1,
174    /// Unsigned greater than or equal to.
175    Hs = 2,
176    /// Unsigned less than.
177    Lo = 3,
178    /// Minus, negative.
179    Mi = 4,
180    /// Positive or zero.
181    Pl = 5,
182    /// Signed overflow.
183    Vs = 6,
184    /// No signed overflow.
185    Vc = 7,
186    /// Unsigned greater than.
187    Hi = 8,
188    /// Unsigned less than or equal to.
189    Ls = 9,
190    /// Signed greater or equal to.
191    Ge = 10,
192    /// Signed less than.
193    Lt = 11,
194    /// Signed greater than.
195    Gt = 12,
196    /// Signed less than or equal.
197    Le = 13,
198    /// Always executed.
199    Al = 14,
200    /// Always executed.
201    Nv = 15,
202}
203
204impl Cond {
205    /// Return the inverted condition.
206    pub fn invert(self) -> Cond {
207        match self {
208            Cond::Eq => Cond::Ne,
209            Cond::Ne => Cond::Eq,
210
211            Cond::Hs => Cond::Lo,
212            Cond::Lo => Cond::Hs,
213
214            Cond::Mi => Cond::Pl,
215            Cond::Pl => Cond::Mi,
216
217            Cond::Vs => Cond::Vc,
218            Cond::Vc => Cond::Vs,
219
220            Cond::Hi => Cond::Ls,
221            Cond::Ls => Cond::Hi,
222
223            Cond::Ge => Cond::Lt,
224            Cond::Lt => Cond::Ge,
225
226            Cond::Gt => Cond::Le,
227            Cond::Le => Cond::Gt,
228
229            Cond::Al => Cond::Nv,
230            Cond::Nv => Cond::Al,
231        }
232    }
233
234    /// Return the machine encoding of this condition.
235    pub fn bits(self) -> u32 {
236        self as u32
237    }
238}
239
240/// The kind of conditional branch: the common-case-optimized "reg-is-zero" /
241/// "reg-is-nonzero" variants, or the generic one that tests the machine
242/// condition codes.
243#[derive(Clone, Copy, Debug)]
244pub enum CondBrKind {
245    /// Condition: given register is zero.
246    Zero(Reg, OperandSize),
247    /// Condition: given register is nonzero.
248    NotZero(Reg, OperandSize),
249    /// Condition: the given condition-code test is true.
250    Cond(Cond),
251}
252
253impl CondBrKind {
254    /// Return the inverted branch condition.
255    pub fn invert(self) -> CondBrKind {
256        match self {
257            CondBrKind::Zero(reg, size) => CondBrKind::NotZero(reg, size),
258            CondBrKind::NotZero(reg, size) => CondBrKind::Zero(reg, size),
259            CondBrKind::Cond(c) => CondBrKind::Cond(c.invert()),
260        }
261    }
262}
263
264/// A branch target. Either unresolved (basic-block index) or resolved (offset
265/// from end of current instruction).
266#[derive(Clone, Copy, Debug, PartialEq, Eq)]
267pub enum BranchTarget {
268    /// An unresolved reference to a Label, as passed into
269    /// `lower_branch_group()`.
270    Label(MachLabel),
271    /// A fixed PC offset.
272    ResolvedOffset(i32),
273}
274
275impl BranchTarget {
276    /// Return the target's label, if it is a label-based target.
277    pub fn as_label(self) -> Option<MachLabel> {
278        match self {
279            BranchTarget::Label(l) => Some(l),
280            _ => None,
281        }
282    }
283
284    /// Return the target's offset, if specified, or zero if label-based.
285    pub fn as_offset14_or_zero(self) -> u32 {
286        self.as_offset_bounded(14)
287    }
288
289    /// Return the target's offset, if specified, or zero if label-based.
290    pub fn as_offset19_or_zero(self) -> u32 {
291        self.as_offset_bounded(19)
292    }
293
294    /// Return the target's offset, if specified, or zero if label-based.
295    pub fn as_offset26_or_zero(self) -> u32 {
296        self.as_offset_bounded(26)
297    }
298
299    fn as_offset_bounded(self, bits: u32) -> u32 {
300        let off = match self {
301            BranchTarget::ResolvedOffset(off) => off >> 2,
302            _ => 0,
303        };
304        let hi = (1 << (bits - 1)) - 1;
305        let lo = -(1 << bits - 1);
306        assert!(off <= hi);
307        assert!(off >= lo);
308        (off as u32) & ((1 << bits) - 1)
309    }
310}
311
312impl PrettyPrint for ShiftOpAndAmt {
313    fn pretty_print(&self, _: u8) -> String {
314        format!("{:?} {}", self.op(), self.amt().value())
315    }
316}
317
318impl PrettyPrint for ExtendOp {
319    fn pretty_print(&self, _: u8) -> String {
320        format!("{self:?}")
321    }
322}
323
324impl PrettyPrint for MemLabel {
325    fn pretty_print(&self, _: u8) -> String {
326        match self {
327            MemLabel::PCRel(off) => format!("pc+{off}"),
328            MemLabel::Mach(off) => format!("label({})", off.as_u32()),
329        }
330    }
331}
332
333fn shift_for_type(size_bytes: u8) -> usize {
334    match size_bytes {
335        1 => 0,
336        2 => 1,
337        4 => 2,
338        8 => 3,
339        16 => 4,
340        _ => panic!("unknown type size: {size_bytes}"),
341    }
342}
343
344impl PrettyPrint for AMode {
345    fn pretty_print(&self, size_bytes: u8) -> String {
346        debug_assert!(size_bytes != 0);
347        match self {
348            &AMode::Unscaled { rn, simm9 } => {
349                let reg = pretty_print_reg(rn);
350                if simm9.value != 0 {
351                    let simm9 = simm9.pretty_print(8);
352                    format!("[{reg}, {simm9}]")
353                } else {
354                    format!("[{reg}]")
355                }
356            }
357            &AMode::UnsignedOffset { rn, uimm12 } => {
358                let reg = pretty_print_reg(rn);
359                if uimm12.value() != 0 {
360                    let uimm12 = uimm12.pretty_print(8);
361                    format!("[{reg}, {uimm12}]")
362                } else {
363                    format!("[{reg}]")
364                }
365            }
366            &AMode::RegReg { rn, rm } => {
367                let r1 = pretty_print_reg(rn);
368                let r2 = pretty_print_reg(rm);
369                format!("[{r1}, {r2}]")
370            }
371            &AMode::RegScaled { rn, rm } => {
372                let r1 = pretty_print_reg(rn);
373                let r2 = pretty_print_reg(rm);
374                let shift = shift_for_type(size_bytes);
375                format!("[{r1}, {r2}, LSL #{shift}]")
376            }
377            &AMode::RegScaledExtended { rn, rm, extendop } => {
378                let shift = shift_for_type(size_bytes);
379                let size = match extendop {
380                    ExtendOp::SXTW | ExtendOp::UXTW => OperandSize::Size32,
381                    _ => OperandSize::Size64,
382                };
383                let r1 = pretty_print_reg(rn);
384                let r2 = pretty_print_ireg(rm, size);
385                let op = extendop.pretty_print(0);
386                format!("[{r1}, {r2}, {op} #{shift}]")
387            }
388            &AMode::RegExtended { rn, rm, extendop } => {
389                let size = match extendop {
390                    ExtendOp::SXTW | ExtendOp::UXTW => OperandSize::Size32,
391                    _ => OperandSize::Size64,
392                };
393                let r1 = pretty_print_reg(rn);
394                let r2 = pretty_print_ireg(rm, size);
395                let op = extendop.pretty_print(0);
396                format!("[{r1}, {r2}, {op}]")
397            }
398            &AMode::Label { ref label } => label.pretty_print(0),
399            &AMode::SPPreIndexed { simm9 } => {
400                let simm9 = simm9.pretty_print(8);
401                format!("[sp, {simm9}]!")
402            }
403            &AMode::SPPostIndexed { simm9 } => {
404                let simm9 = simm9.pretty_print(8);
405                format!("[sp], {simm9}")
406            }
407            AMode::Const { addr } => format!("[const({})]", addr.as_u32()),
408
409            // Eliminated by `mem_finalize()`.
410            &AMode::SPOffset { .. }
411            | &AMode::FPOffset { .. }
412            | &AMode::IncomingArg { .. }
413            | &AMode::SlotOffset { .. }
414            | &AMode::RegOffset { .. } => {
415                panic!("Unexpected pseudo mem-arg mode: {self:?}")
416            }
417        }
418    }
419}
420
421impl PrettyPrint for PairAMode {
422    fn pretty_print(&self, _: u8) -> String {
423        match self {
424            &PairAMode::SignedOffset { reg, simm7 } => {
425                let reg = pretty_print_reg(reg);
426                if simm7.value != 0 {
427                    let simm7 = simm7.pretty_print(8);
428                    format!("[{reg}, {simm7}]")
429                } else {
430                    format!("[{reg}]")
431                }
432            }
433            &PairAMode::SPPreIndexed { simm7 } => {
434                let simm7 = simm7.pretty_print(8);
435                format!("[sp, {simm7}]!")
436            }
437            &PairAMode::SPPostIndexed { simm7 } => {
438                let simm7 = simm7.pretty_print(8);
439                format!("[sp], {simm7}")
440            }
441        }
442    }
443}
444
445impl PrettyPrint for Cond {
446    fn pretty_print(&self, _: u8) -> String {
447        let mut s = format!("{self:?}");
448        s.make_ascii_lowercase();
449        s
450    }
451}
452
453impl PrettyPrint for BranchTarget {
454    fn pretty_print(&self, _: u8) -> String {
455        match self {
456            &BranchTarget::Label(label) => format!("label{:?}", label.as_u32()),
457            &BranchTarget::ResolvedOffset(off) => format!("{off}"),
458        }
459    }
460}
461
462/// Type used to communicate the operand size of a machine instruction, as AArch64 has 32- and
463/// 64-bit variants of many instructions (and integer registers).
464#[derive(Clone, Copy, Debug, PartialEq, Eq)]
465pub enum OperandSize {
466    /// 32-bit.
467    Size32,
468    /// 64-bit.
469    Size64,
470}
471
472impl OperandSize {
473    /// 32-bit case?
474    pub fn is32(self) -> bool {
475        self == OperandSize::Size32
476    }
477
478    /// 64-bit case?
479    pub fn is64(self) -> bool {
480        self == OperandSize::Size64
481    }
482
483    /// Convert from a needed width to the smallest size that fits.
484    pub fn from_bits<I: Into<usize>>(bits: I) -> OperandSize {
485        let bits: usize = bits.into();
486        assert!(bits <= 64);
487        if bits <= 32 {
488            OperandSize::Size32
489        } else {
490            OperandSize::Size64
491        }
492    }
493
494    /// Return the operand size in bits.
495    pub fn bits(&self) -> u8 {
496        match self {
497            OperandSize::Size32 => 32,
498            OperandSize::Size64 => 64,
499        }
500    }
501
502    /// Convert from an integer type into the smallest size that fits.
503    pub fn from_ty(ty: Type) -> OperandSize {
504        debug_assert!(!ty.is_vector());
505
506        Self::from_bits(ty_bits(ty))
507    }
508
509    /// Convert to I32, I64, or I128.
510    pub fn to_ty(self) -> Type {
511        match self {
512            OperandSize::Size32 => I32,
513            OperandSize::Size64 => I64,
514        }
515    }
516
517    /// Register interpretation bit.
518    /// When 0, the register is interpreted as the 32-bit version.
519    /// When 1, the register is interpreted as the 64-bit version.
520    pub fn sf_bit(&self) -> u32 {
521        match self {
522            OperandSize::Size32 => 0,
523            OperandSize::Size64 => 1,
524        }
525    }
526
527    /// The maximum unsigned value representable in a value of this size.
528    pub fn max_value(&self) -> u64 {
529        match self {
530            OperandSize::Size32 => u32::MAX as u64,
531            OperandSize::Size64 => u64::MAX,
532        }
533    }
534}
535
536/// Type used to communicate the size of a scalar SIMD & FP operand.
537#[derive(Clone, Copy, Debug, PartialEq, Eq)]
538pub enum ScalarSize {
539    /// 8-bit.
540    Size8,
541    /// 16-bit.
542    Size16,
543    /// 32-bit.
544    Size32,
545    /// 64-bit.
546    Size64,
547    /// 128-bit.
548    Size128,
549}
550
551impl ScalarSize {
552    /// Convert to an integer operand size.
553    pub fn operand_size(&self) -> OperandSize {
554        match self {
555            ScalarSize::Size8 | ScalarSize::Size16 | ScalarSize::Size32 => OperandSize::Size32,
556            ScalarSize::Size64 => OperandSize::Size64,
557            _ => panic!("Unexpected operand_size request for: {self:?}"),
558        }
559    }
560
561    /// Return the encoding bits that are used by some scalar FP instructions
562    /// for a particular operand size.
563    pub fn ftype(&self) -> u32 {
564        match self {
565            ScalarSize::Size16 => 0b11,
566            ScalarSize::Size32 => 0b00,
567            ScalarSize::Size64 => 0b01,
568            _ => panic!("Unexpected scalar FP operand size: {self:?}"),
569        }
570    }
571
572    /// Return the widened version of the scalar size.
573    pub fn widen(&self) -> ScalarSize {
574        match self {
575            ScalarSize::Size8 => ScalarSize::Size16,
576            ScalarSize::Size16 => ScalarSize::Size32,
577            ScalarSize::Size32 => ScalarSize::Size64,
578            ScalarSize::Size64 => ScalarSize::Size128,
579            ScalarSize::Size128 => panic!("can't widen 128-bits"),
580        }
581    }
582
583    /// Return the narrowed version of the scalar size.
584    pub fn narrow(&self) -> ScalarSize {
585        match self {
586            ScalarSize::Size8 => panic!("can't narrow 8-bits"),
587            ScalarSize::Size16 => ScalarSize::Size8,
588            ScalarSize::Size32 => ScalarSize::Size16,
589            ScalarSize::Size64 => ScalarSize::Size32,
590            ScalarSize::Size128 => ScalarSize::Size64,
591        }
592    }
593
594    /// Return a type with the same size as this scalar.
595    pub fn ty(&self) -> Type {
596        match self {
597            ScalarSize::Size8 => I8,
598            ScalarSize::Size16 => I16,
599            ScalarSize::Size32 => I32,
600            ScalarSize::Size64 => I64,
601            ScalarSize::Size128 => I128,
602        }
603    }
604}
605
606/// Type used to communicate the size of a vector operand.
607#[derive(Clone, Copy, Debug, PartialEq, Eq)]
608pub enum VectorSize {
609    /// 8-bit, 8 lanes.
610    Size8x8,
611    /// 8 bit, 16 lanes.
612    Size8x16,
613    /// 16-bit, 4 lanes.
614    Size16x4,
615    /// 16-bit, 8 lanes.
616    Size16x8,
617    /// 32-bit, 2 lanes.
618    Size32x2,
619    /// 32-bit, 4 lanes.
620    Size32x4,
621    /// 64-bit, 2 lanes.
622    Size64x2,
623}
624
625impl VectorSize {
626    /// Get the vector operand of the same size but with 8-bit lane size.
627    pub fn as_scalar8_vector(&self) -> VectorSize {
628        match self {
629            // 64-bit vector
630            VectorSize::Size8x8 | VectorSize::Size16x4 | VectorSize::Size32x2 => {
631                VectorSize::Size8x8
632            }
633            // 128-bit vector
634            VectorSize::Size8x16
635            | VectorSize::Size16x8
636            | VectorSize::Size32x4
637            | VectorSize::Size64x2 => VectorSize::Size8x16,
638        }
639    }
640
641    /// Get the vector operand size with the given scalar size as lane size.
642    pub fn from_lane_size(size: ScalarSize, is_128bit: bool) -> VectorSize {
643        match (size, is_128bit) {
644            (ScalarSize::Size8, false) => VectorSize::Size8x8,
645            (ScalarSize::Size8, true) => VectorSize::Size8x16,
646            (ScalarSize::Size16, false) => VectorSize::Size16x4,
647            (ScalarSize::Size16, true) => VectorSize::Size16x8,
648            (ScalarSize::Size32, false) => VectorSize::Size32x2,
649            (ScalarSize::Size32, true) => VectorSize::Size32x4,
650            (ScalarSize::Size64, true) => VectorSize::Size64x2,
651            _ => panic!("Unexpected scalar FP operand size: {size:?}"),
652        }
653    }
654
655    /// Get the integer operand size that corresponds to a lane of a vector with a certain size.
656    pub fn operand_size(&self) -> OperandSize {
657        match self {
658            VectorSize::Size64x2 => OperandSize::Size64,
659            _ => OperandSize::Size32,
660        }
661    }
662
663    /// Get the scalar operand size that corresponds to a lane of a vector with a certain size.
664    pub fn lane_size(&self) -> ScalarSize {
665        match self {
666            VectorSize::Size8x8 | VectorSize::Size8x16 => ScalarSize::Size8,
667            VectorSize::Size16x4 | VectorSize::Size16x8 => ScalarSize::Size16,
668            VectorSize::Size32x2 | VectorSize::Size32x4 => ScalarSize::Size32,
669            VectorSize::Size64x2 => ScalarSize::Size64,
670        }
671    }
672
673    /// Returns true if the VectorSize is 128-bits.
674    pub fn is_128bits(&self) -> bool {
675        match self {
676            VectorSize::Size8x8 => false,
677            VectorSize::Size8x16 => true,
678            VectorSize::Size16x4 => false,
679            VectorSize::Size16x8 => true,
680            VectorSize::Size32x2 => false,
681            VectorSize::Size32x4 => true,
682            VectorSize::Size64x2 => true,
683        }
684    }
685
686    /// Return the encoding bits that are used by some SIMD instructions
687    /// for a particular operand size.
688    pub fn enc_size(&self) -> (u32, u32) {
689        let q = self.is_128bits() as u32;
690        let size = match self.lane_size() {
691            ScalarSize::Size8 => 0b00,
692            ScalarSize::Size16 => 0b01,
693            ScalarSize::Size32 => 0b10,
694            ScalarSize::Size64 => 0b11,
695            _ => unreachable!(),
696        };
697
698        (q, size)
699    }
700
701    /// Return the encoding bit that is used by some floating-point SIMD
702    /// instructions for a particular operand size.
703    pub fn enc_float_size(&self) -> u32 {
704        match self.lane_size() {
705            ScalarSize::Size32 => 0b0,
706            ScalarSize::Size64 => 0b1,
707            size => panic!("Unsupported floating-point size for vector op: {size:?}"),
708        }
709    }
710}
711
712impl APIKey {
713    /// Returns the encoding of the `auti{key}` instruction used to decrypt the
714    /// `lr` register.
715    pub fn enc_auti_hint(&self) -> u32 {
716        let (crm, op2) = match self {
717            APIKey::AZ => (0b0011, 0b100),
718            APIKey::ASP => (0b0011, 0b101),
719            APIKey::BZ => (0b0011, 0b110),
720            APIKey::BSP => (0b0011, 0b111),
721        };
722        0xd503201f | (crm << 8) | (op2 << 5)
723    }
724}
725
726/// "Package" of the arguments for the instruction `AtomicCAS128` to avoid
727/// making the `Inst` enum massive.
728///
729/// `rd_lo`/`rd_hi` are really `rs`/`rs+1` in the encoded instruction (so
730/// `rd_lo == rs_lo` and `rd_hi == rs_hi`); they are separated here to have
731/// separate use and def vregs for regalloc.
732#[derive(Clone, Debug)]
733#[expect(missing_docs, reason = "self-describing fields")]
734pub struct AtomicCAS128Args {
735    pub rd_lo: Writable<Reg>,
736    pub rd_hi: Writable<Reg>,
737    pub rs_lo: Reg,
738    pub rs_hi: Reg,
739    pub rt_lo: Reg,
740    pub rt_hi: Reg,
741    pub rn: Reg,
742    pub flags: MemFlagsData,
743}
744
745pub use crate::isa::aarch64::lower::isle::generated_code::TestBitAndBranchKind;
746
747impl TestBitAndBranchKind {
748    /// Complements this branch condition to act on the opposite result.
749    pub fn complement(&self) -> TestBitAndBranchKind {
750        match self {
751            TestBitAndBranchKind::Z => TestBitAndBranchKind::NZ,
752            TestBitAndBranchKind::NZ => TestBitAndBranchKind::Z,
753        }
754    }
755}