Skip to main content

cranelift_codegen/isa/aarch64/lower/
isle.rs

1//! ISLE integration glue code for aarch64 lowering.
2
3// Pull in the ISLE generated code.
4pub mod generated_code;
5use generated_code::{Context, ImmExtend};
6
7// Types that the generated ISLE code uses via `use super::*`.
8use super::{
9    ASIMDFPModImm, ASIMDMovModImm, BranchTarget, CallInfo, Cond, CondBrKind, ExtendOp, FPUOpRI,
10    FPUOpRIMod, FloatCC, Imm12, ImmLogic, ImmShift, Inst as MInst, IntCC, MachLabel, MemLabel,
11    MoveWideConst, MoveWideOp, NZCV, Opcode, OperandSize, Reg, SImm9, ScalarSize, ShiftOpAndAmt,
12    UImm5, UImm6, UImm12Scaled, VecMisc2, VectorSize, fp_reg, lower_condcode, stack_reg,
13    writable_link_reg, writable_zero_reg, zero_reg,
14};
15use crate::ir::{ArgumentExtension, condcodes};
16use crate::isa;
17use crate::isa::aarch64::AArch64Backend;
18use crate::isa::aarch64::inst::{FPULeftShiftImm, FPURightShiftImm, ReturnCallInfo};
19use crate::machinst::isle::*;
20use crate::{
21    binemit::CodeOffset,
22    ir::{
23        AtomicRmwOp, BlockCall, ExternalName, Inst, InstructionData, MemFlagsData, TrapCode, Value,
24        ValueList, immediates::*, types::*,
25    },
26    isa::aarch64::abi::AArch64MachineDeps,
27    isa::aarch64::inst::SImm7Scaled,
28    isa::aarch64::inst::args::{AtomicCAS128Args, ShiftOp, ShiftOpShiftImm},
29    machinst::{
30        CallArgList, CallRetList, InstOutput, MachInst, VCodeConstant, VCodeConstantData,
31        abi::ArgPair, ty_bits,
32    },
33};
34use alloc::boxed::Box;
35use alloc::vec::Vec;
36use regalloc2::PReg;
37
38type BoxCallInfo = Box<CallInfo<ExternalName>>;
39type BoxCallIndInfo = Box<CallInfo<Reg>>;
40type BoxReturnCallInfo = Box<ReturnCallInfo<ExternalName>>;
41type BoxReturnCallIndInfo = Box<ReturnCallInfo<Reg>>;
42type VecMachLabel = Vec<MachLabel>;
43type BoxExternalName = Box<ExternalName>;
44type VecArgPair = Vec<ArgPair>;
45type BoxAtomicCAS128Args = Box<AtomicCAS128Args>;
46
47/// The main entry point for lowering with ISLE.
48pub(crate) fn lower(
49    lower_ctx: &mut Lower<MInst>,
50    backend: &AArch64Backend,
51    inst: Inst,
52) -> Option<InstOutput> {
53    // TODO: reuse the ISLE context across lowerings so we can reuse its
54    // internal heap allocations.
55    let mut isle_ctx = IsleContext { lower_ctx, backend };
56    generated_code::constructor_lower(&mut isle_ctx, inst)
57}
58
59pub(crate) fn lower_branch(
60    lower_ctx: &mut Lower<MInst>,
61    backend: &AArch64Backend,
62    branch: Inst,
63    targets: &[MachLabel],
64) -> Option<()> {
65    // TODO: reuse the ISLE context across lowerings so we can reuse its
66    // internal heap allocations.
67    let mut isle_ctx = IsleContext { lower_ctx, backend };
68    generated_code::constructor_lower_branch(&mut isle_ctx, branch, targets)
69}
70
71pub struct ExtendedValue {
72    val: Value,
73    extend: ExtendOp,
74}
75
76impl Context for IsleContext<'_, '_, MInst, AArch64Backend> {
77    isle_lower_prelude_methods!();
78
79    fn gen_call_info(
80        &mut self,
81        sig: Sig,
82        dest: ExternalName,
83        uses: CallArgList,
84        defs: CallRetList,
85        try_call_info: Option<TryCallInfo>,
86        patchable: bool,
87    ) -> BoxCallInfo {
88        let stack_ret_space = self.lower_ctx.sigs()[sig].sized_stack_ret_space();
89        let stack_arg_space = self.lower_ctx.sigs()[sig].sized_stack_arg_space();
90        self.lower_ctx
91            .abi_mut()
92            .accumulate_outgoing_args_size(stack_ret_space + stack_arg_space);
93
94        Box::new(
95            self.lower_ctx
96                .gen_call_info(sig, dest, uses, defs, try_call_info, patchable),
97        )
98    }
99
100    fn gen_call_ind_info(
101        &mut self,
102        sig: Sig,
103        dest: Reg,
104        uses: CallArgList,
105        defs: CallRetList,
106        try_call_info: Option<TryCallInfo>,
107    ) -> BoxCallIndInfo {
108        let stack_ret_space = self.lower_ctx.sigs()[sig].sized_stack_ret_space();
109        let stack_arg_space = self.lower_ctx.sigs()[sig].sized_stack_arg_space();
110        self.lower_ctx
111            .abi_mut()
112            .accumulate_outgoing_args_size(stack_ret_space + stack_arg_space);
113
114        Box::new(
115            self.lower_ctx
116                .gen_call_info(sig, dest, uses, defs, try_call_info, false),
117        )
118    }
119
120    fn gen_return_call_info(
121        &mut self,
122        sig: Sig,
123        dest: ExternalName,
124        uses: CallArgList,
125    ) -> BoxReturnCallInfo {
126        let new_stack_arg_size = self.lower_ctx.sigs()[sig].sized_stack_arg_space();
127        self.lower_ctx
128            .abi_mut()
129            .accumulate_tail_args_size(new_stack_arg_size);
130
131        let key =
132            AArch64MachineDeps::select_api_key(&self.backend.isa_flags, isa::CallConv::Tail, true);
133
134        Box::new(ReturnCallInfo {
135            dest,
136            uses,
137            key,
138            sign_return_address_all: self.backend.isa_flags.sign_return_address_all(),
139            new_stack_arg_size,
140        })
141    }
142
143    fn gen_return_call_ind_info(
144        &mut self,
145        sig: Sig,
146        dest: Reg,
147        uses: CallArgList,
148    ) -> BoxReturnCallIndInfo {
149        let new_stack_arg_size = self.lower_ctx.sigs()[sig].sized_stack_arg_space();
150        self.lower_ctx
151            .abi_mut()
152            .accumulate_tail_args_size(new_stack_arg_size);
153
154        let key =
155            AArch64MachineDeps::select_api_key(&self.backend.isa_flags, isa::CallConv::Tail, true);
156
157        Box::new(ReturnCallInfo {
158            dest,
159            uses,
160            key,
161            sign_return_address_all: self.backend.isa_flags.sign_return_address_all(),
162            new_stack_arg_size,
163        })
164    }
165
166    fn sign_return_address_disabled(&mut self) -> Option<()> {
167        if self.backend.isa_flags.sign_return_address() {
168            None
169        } else {
170            Some(())
171        }
172    }
173
174    fn use_lse(&mut self, _: Inst) -> Option<()> {
175        if self.backend.isa_flags.has_lse() {
176            Some(())
177        } else {
178            None
179        }
180    }
181
182    fn atomic_cas_128_args(
183        &mut self,
184        rd_lo: WritableReg,
185        rd_hi: WritableReg,
186        rs_lo: Reg,
187        rs_hi: Reg,
188        rt_lo: Reg,
189        rt_hi: Reg,
190        rn: Reg,
191        flags: MemFlagsData,
192    ) -> BoxAtomicCAS128Args {
193        Box::new(AtomicCAS128Args {
194            rd_lo,
195            rd_hi,
196            rs_lo,
197            rs_hi,
198            rt_lo,
199            rt_hi,
200            rn,
201            flags,
202        })
203    }
204
205    fn use_dotprod(&mut self, _: Inst) -> Option<()> {
206        if self.backend.isa_flags.has_dotprod() {
207            Some(())
208        } else {
209            None
210        }
211    }
212
213    fn use_i8mm(&mut self, _: Inst) -> Option<()> {
214        if self.backend.isa_flags.has_i8mm() {
215            Some(())
216        } else {
217            None
218        }
219    }
220
221    fn use_fp16(&mut self) -> bool {
222        self.backend.isa_flags.has_fp16()
223    }
224
225    fn use_csdb(&mut self) -> bool {
226        self.backend.isa_flags.use_csdb()
227    }
228
229    fn move_wide_const_from_u64(&mut self, ty: Type, n: u64) -> Option<MoveWideConst> {
230        let bits = ty.bits();
231        let n = if bits < 64 {
232            n & !(u64::MAX << bits)
233        } else {
234            n
235        };
236        MoveWideConst::maybe_from_u64(n)
237    }
238
239    fn move_wide_const_from_inverted_u64(&mut self, ty: Type, n: u64) -> Option<MoveWideConst> {
240        self.move_wide_const_from_u64(ty, !n)
241    }
242
243    fn imm_logic_from_u64(&mut self, ty: Type, n: u64) -> Option<ImmLogic> {
244        ImmLogic::maybe_from_u64(n, ty)
245    }
246
247    fn imm_size_from_type(&mut self, ty: Type) -> Option<u16> {
248        match ty {
249            I32 => Some(32),
250            I64 => Some(64),
251            _ => None,
252        }
253    }
254
255    fn imm_logic_from_imm64(&mut self, ty: Type, n: Imm64) -> Option<ImmLogic> {
256        let ty = if ty.bits() < 32 { I32 } else { ty };
257        self.imm_logic_from_u64(ty, n.bits() as u64)
258    }
259
260    fn imm12_from_u64(&mut self, n: u64) -> Option<Imm12> {
261        Imm12::maybe_from_u64(n)
262    }
263
264    fn imm_shift_from_u8(&mut self, n: u8) -> ImmShift {
265        ImmShift::maybe_from_u64(n.into()).unwrap()
266    }
267
268    /// Compute the `immr` value for an `sbfm` instruction,
269    /// derived by fusing an `ishl` by amount `a`, with an `sshr` by amount `b`.
270    fn bfm_immr(&mut self, ty: Type, a: u64, b: u64) -> UImm6 {
271        let w = ty.lane_bits() as u8;
272        debug_assert!(w <= 64);
273
274        let a = (a as u8) & (w - 1);
275        let b = (b as u8) & (w - 1);
276        let result = if a <= b { b - a } else { w - (a - b) };
277        UImm6::maybe_from_u8(result).expect("result is always less than 64")
278    }
279
280    /// Compute the `imms` value for an `sbfm` instruction,
281    /// derived by fusing an `ishl` by amount `a`, with an `sshr` by amount `b`.
282    fn bfm_imms(&mut self, ty: Type, a: u64, _b: u64) -> UImm6 {
283        let w = ty.lane_bits() as u8;
284        debug_assert!(w <= 64);
285
286        let a = (a as u8) & (w - 1);
287        let result = w - 1 - (a & (w - 1));
288        UImm6::maybe_from_u8(result).expect("result is always less than 64")
289    }
290
291    fn lshr_from_u64(&mut self, ty: Type, n: u64) -> Option<ShiftOpAndAmt> {
292        let shiftimm = ShiftOpShiftImm::maybe_from_shift(n)?;
293        if let Ok(bits) = u8::try_from(ty_bits(ty)) {
294            let shiftimm = shiftimm.mask(bits);
295            Some(ShiftOpAndAmt::new(ShiftOp::LSR, shiftimm))
296        } else {
297            None
298        }
299    }
300
301    fn lshl_from_imm64(&mut self, ty: Type, n: Imm64) -> Option<ShiftOpAndAmt> {
302        self.lshl_from_u64(ty, n.bits() as u64)
303    }
304
305    fn lshl_from_u64(&mut self, ty: Type, n: u64) -> Option<ShiftOpAndAmt> {
306        let shiftimm = ShiftOpShiftImm::maybe_from_shift(n)?;
307        let shiftee_bits = ty_bits(ty);
308        if shiftee_bits <= u8::MAX as usize {
309            let shiftimm = shiftimm.mask(shiftee_bits as u8);
310            Some(ShiftOpAndAmt::new(ShiftOp::LSL, shiftimm))
311        } else {
312            None
313        }
314    }
315
316    fn ashr_from_u64(&mut self, ty: Type, n: u64) -> Option<ShiftOpAndAmt> {
317        let shiftimm = ShiftOpShiftImm::maybe_from_shift(n)?;
318        let shiftee_bits = ty_bits(ty);
319        if shiftee_bits <= u8::MAX as usize {
320            let shiftimm = shiftimm.mask(shiftee_bits as u8);
321            Some(ShiftOpAndAmt::new(ShiftOp::ASR, shiftimm))
322        } else {
323            None
324        }
325    }
326
327    fn integral_ty(&mut self, ty: Type) -> Option<Type> {
328        match ty {
329            I8 | I16 | I32 | I64 => Some(ty),
330            _ => None,
331        }
332    }
333
334    fn is_zero_simm9(&mut self, imm: &SImm9) -> Option<()> {
335        if imm.value() == 0 { Some(()) } else { None }
336    }
337
338    fn is_zero_uimm12(&mut self, imm: &UImm12Scaled) -> Option<()> {
339        if imm.value() == 0 { Some(()) } else { None }
340    }
341
342    /// This is target-word-size dependent.  And it excludes booleans and reftypes.
343    fn valid_atomic_transaction(&mut self, ty: Type) -> Option<Type> {
344        match ty {
345            I8 | I16 | I32 | I64 => Some(ty),
346            _ => None,
347        }
348    }
349
350    /// This is the fallback case for loading a 64-bit integral constant into a
351    /// register.
352    ///
353    /// The logic here is nontrivial enough that it's not really worth porting
354    /// this over to ISLE.
355    fn load_constant_full(
356        &mut self,
357        ty: Type,
358        extend: &ImmExtend,
359        extend_to: &OperandSize,
360        value: u64,
361    ) -> Reg {
362        let bits = ty.bits();
363
364        let value = match (extend_to, *extend) {
365            (OperandSize::Size32, ImmExtend::Sign) if bits < 32 => {
366                let shift = 32 - bits;
367                let value = value as i32;
368
369                // we cast first to a u32 and then to a u64, to ensure that we are representing a
370                // i32 in a u64, and not a i64. This is important, otherwise value will not fit in
371                // 32 bits
372                ((value << shift) >> shift) as u32 as u64
373            }
374            (OperandSize::Size32, ImmExtend::Zero) if bits < 32 => {
375                value & !((u32::MAX as u64) << bits)
376            }
377            (OperandSize::Size64, ImmExtend::Sign) if bits < 64 => {
378                let shift = 64 - bits;
379                let value = value as i64;
380
381                ((value << shift) >> shift) as u64
382            }
383            (OperandSize::Size64, ImmExtend::Zero) if bits < 64 => value & !(u64::MAX << bits),
384            _ => value,
385        };
386
387        // Divide the value into 16-bit slices that we can manipulate using
388        // `movz`, `movn`, and `movk`.
389        fn get(value: u64, shift: u8) -> u16 {
390            (value >> (shift * 16)) as u16
391        }
392        fn replace(mut old: u64, new: u16, shift: u8) -> u64 {
393            let offset = shift * 16;
394            old &= !(0xffff << offset);
395            old |= u64::from(new) << offset;
396            old
397        }
398
399        // The 32-bit versions of `movz`/`movn`/`movk` will clear the upper 32
400        // bits, so if that's the outcome we want we might as well use them. For
401        // simplicity and ease of reading the disassembly, we use the same size
402        // for all instructions in the sequence.
403        let size = if value >> 32 == 0 {
404            OperandSize::Size32
405        } else {
406            OperandSize::Size64
407        };
408
409        // The `movz` instruction initially sets all bits to zero, while `movn`
410        // initially sets all bits to one. A good choice of initial value can
411        // reduce the number of `movk` instructions we need afterward, so we
412        // check both variants to determine which is closest to the constant
413        // we actually wanted. In case they're equally good, we prefer `movz`
414        // because the assembly listings are generally harder to read when the
415        // operands are negated.
416        let (mut running_value, op, first) =
417            [(MoveWideOp::MovZ, 0), (MoveWideOp::MovN, size.max_value())]
418                .into_iter()
419                .map(|(op, base)| {
420                    // Both `movz` and `movn` can overwrite one slice after setting
421                    // the initial value; we get to pick which one. 32-bit variants
422                    // can only modify the lower two slices.
423                    let first = (0..(size.bits() / 16))
424                        // Pick one slice that's different from the initial value
425                        .find(|&i| get(base ^ value, i) != 0)
426                        // If none are different, we still have to pick one
427                        .unwrap_or(0);
428                    // Compute the value we'll get from this `movz`/`movn`
429                    (replace(base, get(value, first), first), op, first)
430                })
431                // Count how many `movk` instructions we'll need.
432                .min_by_key(|(base, ..)| (0..4).filter(|&i| get(base ^ value, i) != 0).count())
433                // `variants` isn't empty so `min_by_key` always returns something.
434                .unwrap();
435
436        // Build the initial instruction we chose above, putting the result
437        // into a new temporary virtual register. Note that the encoding for the
438        // immediate operand is bitwise-inverted for `movn`.
439        let mut rd = self.temp_writable_reg(I64);
440        self.lower_ctx.emit(MInst::MovWide {
441            op,
442            rd,
443            imm: MoveWideConst {
444                bits: match op {
445                    MoveWideOp::MovZ => get(value, first),
446                    MoveWideOp::MovN => !get(value, first),
447                },
448                shift: first,
449            },
450            size,
451        });
452
453        // Emit a `movk` instruction for each remaining slice of the desired
454        // constant that does not match the initial value constructed above.
455        for shift in (first + 1)..(size.bits() / 16) {
456            let bits = get(value, shift);
457            if bits != get(running_value, shift) {
458                let rn = rd.to_reg();
459                rd = self.temp_writable_reg(I64);
460                self.lower_ctx.emit(MInst::MovK {
461                    rd,
462                    rn,
463                    imm: MoveWideConst { bits, shift },
464                    size,
465                });
466                running_value = replace(running_value, bits, shift);
467            }
468        }
469
470        debug_assert_eq!(value, running_value);
471        return rd.to_reg();
472    }
473
474    fn zero_reg(&mut self) -> Reg {
475        zero_reg()
476    }
477
478    fn stack_reg(&mut self) -> Reg {
479        stack_reg()
480    }
481
482    fn fp_reg(&mut self) -> Reg {
483        fp_reg()
484    }
485
486    fn writable_link_reg(&mut self) -> WritableReg {
487        writable_link_reg()
488    }
489
490    fn extended_value_from_value(&mut self, val: Value) -> Option<ExtendedValue> {
491        let (val, extend) = super::get_as_extended_value(self.lower_ctx, val)?;
492        Some(ExtendedValue { val, extend })
493    }
494
495    fn put_extended_in_reg(&mut self, reg: &ExtendedValue) -> Reg {
496        self.put_in_reg(reg.val)
497    }
498
499    fn get_extended_op(&mut self, reg: &ExtendedValue) -> ExtendOp {
500        reg.extend
501    }
502
503    fn emit(&mut self, inst: &MInst) -> Unit {
504        self.lower_ctx.emit(inst.clone());
505    }
506
507    fn cond_br_zero(&mut self, reg: Reg, size: &OperandSize) -> CondBrKind {
508        CondBrKind::Zero(reg, *size)
509    }
510
511    fn cond_br_not_zero(&mut self, reg: Reg, size: &OperandSize) -> CondBrKind {
512        CondBrKind::NotZero(reg, *size)
513    }
514
515    fn cond_br_cond(&mut self, cond: &Cond) -> CondBrKind {
516        CondBrKind::Cond(*cond)
517    }
518
519    fn nzcv(&mut self, n: bool, z: bool, c: bool, v: bool) -> NZCV {
520        NZCV::new(n, z, c, v)
521    }
522
523    fn u8_into_uimm5(&mut self, x: u8) -> UImm5 {
524        UImm5::maybe_from_u8(x).unwrap()
525    }
526
527    fn u8_into_imm12(&mut self, x: u8) -> Imm12 {
528        Imm12::maybe_from_u64(x.into()).unwrap()
529    }
530
531    fn writable_zero_reg(&mut self) -> WritableReg {
532        writable_zero_reg()
533    }
534
535    fn shift_mask(&mut self, ty: Type) -> ImmLogic {
536        debug_assert!(ty.lane_bits().is_power_of_two());
537
538        let mask = (ty.lane_bits() - 1) as u64;
539        ImmLogic::maybe_from_u64(mask, I32).unwrap()
540    }
541
542    fn imm_shift_from_imm64(&mut self, ty: Type, val: Imm64) -> Option<ImmShift> {
543        let imm_value = (val.bits() as u64) & ((ty.bits() - 1) as u64);
544        ImmShift::maybe_from_u64(imm_value)
545    }
546
547    fn u64_into_imm_logic(&mut self, ty: Type, val: u64) -> ImmLogic {
548        ImmLogic::maybe_from_u64(val, ty).unwrap()
549    }
550
551    fn negate_imm_shift(&mut self, ty: Type, mut imm: ImmShift) -> ImmShift {
552        let size = u8::try_from(ty.bits()).unwrap();
553        imm.imm = size.wrapping_sub(imm.value());
554        imm.imm &= size - 1;
555        imm
556    }
557
558    fn rotr_mask(&mut self, ty: Type) -> ImmLogic {
559        ImmLogic::maybe_from_u64((ty.bits() - 1) as u64, I32).unwrap()
560    }
561
562    fn rotr_opposite_amount(&mut self, ty: Type, val: ImmShift) -> ImmShift {
563        let amount = val.value() & u8::try_from(ty.bits() - 1).unwrap();
564        ImmShift::maybe_from_u64(u64::from(ty.bits()) - u64::from(amount)).unwrap()
565    }
566
567    fn icmp_zero_cond(&mut self, cond: &IntCC) -> Option<IntCC> {
568        match cond {
569            &IntCC::Equal
570            | &IntCC::SignedGreaterThanOrEqual
571            | &IntCC::SignedGreaterThan
572            | &IntCC::SignedLessThanOrEqual
573            | &IntCC::SignedLessThan => Some(*cond),
574            _ => None,
575        }
576    }
577
578    fn fcmp_zero_cond(&mut self, cond: &FloatCC) -> Option<FloatCC> {
579        match cond {
580            &FloatCC::Equal
581            | &FloatCC::GreaterThanOrEqual
582            | &FloatCC::GreaterThan
583            | &FloatCC::LessThanOrEqual
584            | &FloatCC::LessThan => Some(*cond),
585            _ => None,
586        }
587    }
588
589    fn fcmp_zero_cond_not_eq(&mut self, cond: &FloatCC) -> Option<FloatCC> {
590        match cond {
591            &FloatCC::NotEqual => Some(FloatCC::NotEqual),
592            _ => None,
593        }
594    }
595
596    fn icmp_zero_cond_not_eq(&mut self, cond: &IntCC) -> Option<IntCC> {
597        match cond {
598            &IntCC::NotEqual => Some(IntCC::NotEqual),
599            _ => None,
600        }
601    }
602
603    fn float_cc_cmp_zero_to_vec_misc_op(&mut self, cond: &FloatCC) -> VecMisc2 {
604        match cond {
605            &FloatCC::Equal => VecMisc2::Fcmeq0,
606            &FloatCC::GreaterThanOrEqual => VecMisc2::Fcmge0,
607            &FloatCC::LessThanOrEqual => VecMisc2::Fcmle0,
608            &FloatCC::GreaterThan => VecMisc2::Fcmgt0,
609            &FloatCC::LessThan => VecMisc2::Fcmlt0,
610            _ => panic!(),
611        }
612    }
613
614    fn int_cc_cmp_zero_to_vec_misc_op(&mut self, cond: &IntCC) -> VecMisc2 {
615        match cond {
616            &IntCC::Equal => VecMisc2::Cmeq0,
617            &IntCC::SignedGreaterThanOrEqual => VecMisc2::Cmge0,
618            &IntCC::SignedLessThanOrEqual => VecMisc2::Cmle0,
619            &IntCC::SignedGreaterThan => VecMisc2::Cmgt0,
620            &IntCC::SignedLessThan => VecMisc2::Cmlt0,
621            _ => panic!(),
622        }
623    }
624
625    fn float_cc_cmp_zero_to_vec_misc_op_swap(&mut self, cond: &FloatCC) -> VecMisc2 {
626        match cond {
627            &FloatCC::Equal => VecMisc2::Fcmeq0,
628            &FloatCC::GreaterThanOrEqual => VecMisc2::Fcmle0,
629            &FloatCC::LessThanOrEqual => VecMisc2::Fcmge0,
630            &FloatCC::GreaterThan => VecMisc2::Fcmlt0,
631            &FloatCC::LessThan => VecMisc2::Fcmgt0,
632            _ => panic!(),
633        }
634    }
635
636    fn int_cc_cmp_zero_to_vec_misc_op_swap(&mut self, cond: &IntCC) -> VecMisc2 {
637        match cond {
638            &IntCC::Equal => VecMisc2::Cmeq0,
639            &IntCC::SignedGreaterThanOrEqual => VecMisc2::Cmle0,
640            &IntCC::SignedLessThanOrEqual => VecMisc2::Cmge0,
641            &IntCC::SignedGreaterThan => VecMisc2::Cmlt0,
642            &IntCC::SignedLessThan => VecMisc2::Cmgt0,
643            _ => panic!(),
644        }
645    }
646
647    fn cond_code(&mut self, cc: &condcodes::IntCC) -> Cond {
648        lower_condcode(*cc)
649    }
650
651    fn invert_cond(&mut self, cond: &Cond) -> Cond {
652        (*cond).invert()
653    }
654    fn preg_sp(&mut self) -> PReg {
655        super::regs::stack_reg().to_real_reg().unwrap().into()
656    }
657
658    fn preg_fp(&mut self) -> PReg {
659        super::regs::fp_reg().to_real_reg().unwrap().into()
660    }
661
662    fn preg_link(&mut self) -> PReg {
663        super::regs::link_reg().to_real_reg().unwrap().into()
664    }
665
666    fn preg_pinned(&mut self) -> PReg {
667        super::regs::pinned_reg().to_real_reg().unwrap().into()
668    }
669
670    fn branch_target(&mut self, label: MachLabel) -> BranchTarget {
671        BranchTarget::Label(label)
672    }
673
674    fn targets_jt_space(&mut self, elements: &BoxVecMachLabel) -> CodeOffset {
675        // calculate the number of bytes needed for the jumptable sequence:
676        // 4 bytes per instruction, with 8 instructions base + the size of
677        // the jumptable more.
678        (4 * (8 + elements.len())).try_into().unwrap()
679    }
680
681    fn min_fp_value(&mut self, signed: bool, in_bits: u8, out_bits: u8) -> Reg {
682        if in_bits == 32 {
683            // From float32.
684            let min = match (signed, out_bits) {
685                (true, 8) => i8::MIN as f32 - 1.,
686                (true, 16) => i16::MIN as f32 - 1.,
687                (true, 32) => i32::MIN as f32, // I32_MIN - 1 isn't precisely representable as a f32.
688                (true, 64) => i64::MIN as f32, // I64_MIN - 1 isn't precisely representable as a f32.
689
690                (false, _) => -1.,
691                _ => unimplemented!(
692                    "unexpected {} output size of {} bits for 32-bit input",
693                    if signed { "signed" } else { "unsigned" },
694                    out_bits
695                ),
696            };
697
698            generated_code::constructor_constant_f32(self, min.to_bits())
699        } else if in_bits == 64 {
700            // From float64.
701            let min = match (signed, out_bits) {
702                (true, 8) => i8::MIN as f64 - 1.,
703                (true, 16) => i16::MIN as f64 - 1.,
704                (true, 32) => i32::MIN as f64 - 1.,
705                (true, 64) => i64::MIN as f64,
706
707                (false, _) => -1.,
708                _ => unimplemented!(
709                    "unexpected {} output size of {} bits for 64-bit input",
710                    if signed { "signed" } else { "unsigned" },
711                    out_bits
712                ),
713            };
714
715            generated_code::constructor_constant_f64(self, min.to_bits())
716        } else {
717            unimplemented!(
718                "unexpected input size for min_fp_value: {} (signed: {}, output size: {})",
719                in_bits,
720                signed,
721                out_bits
722            );
723        }
724    }
725
726    fn max_fp_value(&mut self, signed: bool, in_bits: u8, out_bits: u8) -> Reg {
727        if in_bits == 32 {
728            // From float32.
729            let max = match (signed, out_bits) {
730                (true, 8) => i8::MAX as f32 + 1.,
731                (true, 16) => i16::MAX as f32 + 1.,
732                (true, 32) => (i32::MAX as u64 + 1) as f32,
733                (true, 64) => (i64::MAX as u64 + 1) as f32,
734
735                (false, 8) => u8::MAX as f32 + 1.,
736                (false, 16) => u16::MAX as f32 + 1.,
737                (false, 32) => (u32::MAX as u64 + 1) as f32,
738                (false, 64) => (u64::MAX as u128 + 1) as f32,
739                _ => unimplemented!(
740                    "unexpected {} output size of {} bits for 32-bit input",
741                    if signed { "signed" } else { "unsigned" },
742                    out_bits
743                ),
744            };
745
746            generated_code::constructor_constant_f32(self, max.to_bits())
747        } else if in_bits == 64 {
748            // From float64.
749            let max = match (signed, out_bits) {
750                (true, 8) => i8::MAX as f64 + 1.,
751                (true, 16) => i16::MAX as f64 + 1.,
752                (true, 32) => i32::MAX as f64 + 1.,
753                (true, 64) => (i64::MAX as u64 + 1) as f64,
754
755                (false, 8) => u8::MAX as f64 + 1.,
756                (false, 16) => u16::MAX as f64 + 1.,
757                (false, 32) => u32::MAX as f64 + 1.,
758                (false, 64) => (u64::MAX as u128 + 1) as f64,
759                _ => unimplemented!(
760                    "unexpected {} output size of {} bits for 64-bit input",
761                    if signed { "signed" } else { "unsigned" },
762                    out_bits
763                ),
764            };
765
766            generated_code::constructor_constant_f64(self, max.to_bits())
767        } else {
768            unimplemented!(
769                "unexpected input size for max_fp_value: {} (signed: {}, output size: {})",
770                in_bits,
771                signed,
772                out_bits
773            );
774        }
775    }
776
777    fn fpu_op_ri_ushr(&mut self, ty_bits: u8, shift: u8) -> FPUOpRI {
778        if ty_bits == 32 {
779            FPUOpRI::UShr32(FPURightShiftImm::maybe_from_u8(shift, ty_bits).unwrap())
780        } else if ty_bits == 64 {
781            FPUOpRI::UShr64(FPURightShiftImm::maybe_from_u8(shift, ty_bits).unwrap())
782        } else {
783            unimplemented!(
784                "unexpected input size for fpu_op_ri_ushr: {} (shift: {})",
785                ty_bits,
786                shift
787            );
788        }
789    }
790
791    fn fpu_op_ri_sli(&mut self, ty_bits: u8, shift: u8) -> FPUOpRIMod {
792        if ty_bits == 32 {
793            FPUOpRIMod::Sli32(FPULeftShiftImm::maybe_from_u8(shift, ty_bits).unwrap())
794        } else if ty_bits == 64 {
795            FPUOpRIMod::Sli64(FPULeftShiftImm::maybe_from_u8(shift, ty_bits).unwrap())
796        } else {
797            unimplemented!(
798                "unexpected input size for fpu_op_ri_sli: {} (shift: {})",
799                ty_bits,
800                shift
801            );
802        }
803    }
804
805    fn vec_extract_imm4_from_immediate(&mut self, imm: Immediate) -> Option<u8> {
806        let bytes = self.lower_ctx.get_immediate_data(imm).as_slice();
807
808        if bytes.array_windows().all(|[a, b]| *a + 1 == *b) && bytes[0] < 16 {
809            Some(bytes[0])
810        } else {
811            None
812        }
813    }
814
815    fn shuffle_dup8_from_imm(&mut self, imm: Immediate) -> Option<u8> {
816        let bytes = self.lower_ctx.get_immediate_data(imm).as_slice();
817        if bytes.iter().all(|b| *b == bytes[0]) && bytes[0] < 16 {
818            Some(bytes[0])
819        } else {
820            None
821        }
822    }
823    fn shuffle_dup16_from_imm(&mut self, imm: Immediate) -> Option<u8> {
824        let (a, b, c, d, e, f, g, h) = self.shuffle16_from_imm(imm)?;
825        if a == b && b == c && c == d && d == e && e == f && f == g && g == h && a < 8 {
826            Some(a)
827        } else {
828            None
829        }
830    }
831    fn shuffle_dup32_from_imm(&mut self, imm: Immediate) -> Option<u8> {
832        let (a, b, c, d) = self.shuffle32_from_imm(imm)?;
833        if a == b && b == c && c == d && a < 4 {
834            Some(a)
835        } else {
836            None
837        }
838    }
839    fn shuffle_dup64_from_imm(&mut self, imm: Immediate) -> Option<u8> {
840        let (a, b) = self.shuffle64_from_imm(imm)?;
841        if a == b && a < 2 { Some(a) } else { None }
842    }
843
844    fn asimd_mov_mod_imm_zero(&mut self, size: &ScalarSize) -> ASIMDMovModImm {
845        ASIMDMovModImm::zero(*size)
846    }
847
848    fn asimd_mov_mod_imm_from_u64(
849        &mut self,
850        val: u64,
851        size: &ScalarSize,
852    ) -> Option<ASIMDMovModImm> {
853        ASIMDMovModImm::maybe_from_u64(val, *size)
854    }
855
856    fn asimd_fp_mod_imm_from_u64(&mut self, val: u64, size: &ScalarSize) -> Option<ASIMDFPModImm> {
857        ASIMDFPModImm::maybe_from_u64(val, *size)
858    }
859
860    fn u64_low32_bits_unset(&mut self, val: u64) -> Option<u64> {
861        if val & 0xffffffff == 0 {
862            Some(val)
863        } else {
864            None
865        }
866    }
867
868    fn shift_masked_imm(&mut self, ty: Type, imm: u64) -> u8 {
869        (imm as u8) & ((ty.lane_bits() - 1) as u8)
870    }
871
872    fn simm7_scaled_from_i64(&mut self, val: i64, ty: Type) -> Option<SImm7Scaled> {
873        SImm7Scaled::maybe_from_i64(val, ty)
874    }
875
876    fn simm9_from_i64(&mut self, val: i64) -> Option<SImm9> {
877        SImm9::maybe_from_i64(val)
878    }
879
880    fn uimm12_scaled_from_i64(&mut self, val: i64, ty: Type) -> Option<UImm12Scaled> {
881        UImm12Scaled::maybe_from_i64(val, ty)
882    }
883
884    /// Like `uimm12_scaled_from_i64`, but rejects a zero value so `base + index
885    /// + 0` keeps its single-instruction `RegExtended` amode.
886    fn uimm12_scaled_nonzero_from_i64(&mut self, val: i64, ty: Type) -> Option<UImm12Scaled> {
887        if val == 0 {
888            return None;
889        }
890        UImm12Scaled::maybe_from_i64(val, ty)
891    }
892
893    fn test_and_compare_bit_const(&mut self, ty: Type, n: u64) -> Option<u8> {
894        if n.count_ones() != 1 {
895            return None;
896        }
897        let bit = n.trailing_zeros();
898        if bit >= ty.bits() {
899            return None;
900        }
901        Some(bit as u8)
902    }
903
904    /// Use as a helper when generating `AluRRRShift` for `extr` instructions.
905    fn a64_extr_imm(&mut self, ty: Type, shift: ImmShift) -> ShiftOpAndAmt {
906        // The `ShiftOpAndAmt` immediate is used with `AluRRRShift` shape which
907        // requires `ShiftOpAndAmt` so the shift of `ty` and `shift` are
908        // translated into `ShiftOpAndAmt` here. The `ShiftOp` value here is
909        // only used for its encoding, not its logical meaning.
910        let (op, expected) = match ty {
911            types::I32 => (ShiftOp::LSL, 0b00),
912            types::I64 => (ShiftOp::LSR, 0b01),
913            _ => unreachable!(),
914        };
915        assert_eq!(op.bits(), expected);
916        ShiftOpAndAmt::new(
917            op,
918            ShiftOpShiftImm::maybe_from_shift(shift.value().into()).unwrap(),
919        )
920    }
921
922    fn is_pic(&mut self) -> bool {
923        self.backend.flags.is_pic()
924    }
925}