Skip to main content

cranelift_codegen/isa/aarch64/
abi.rs

1//! Implementation of a standard AArch64 ABI.
2
3use core::cmp::Reverse;
4
5use crate::CodegenResult;
6use crate::FxHashSet;
7use crate::ir;
8use crate::ir::MemFlagsData;
9use crate::ir::types;
10use crate::ir::types::*;
11use crate::ir::{ExternalName, LibCall, Signature, dynamic_to_fixed};
12use crate::isa;
13use crate::isa::aarch64::{inst::*, settings as aarch64_settings};
14use crate::isa::unwind::UnwindInst;
15use crate::isa::winch;
16use crate::machinst::*;
17use crate::settings;
18use alloc::borrow::ToOwned;
19use alloc::boxed::Box;
20use alloc::vec::Vec;
21use regalloc2::{MachineEnv, PReg, PRegSet};
22use smallvec::{SmallVec, smallvec};
23
24// We use a generic implementation that factors out AArch64 and x64 ABI commonalities, because
25// these ABIs are very similar.
26
27/// Support for the AArch64 ABI from the callee side (within a function body).
28pub(crate) type AArch64Callee = Callee<AArch64MachineDeps>;
29
30impl From<StackAMode> for AMode {
31    fn from(stack: StackAMode) -> AMode {
32        match stack {
33            StackAMode::IncomingArg(off, stack_args_size) => AMode::IncomingArg {
34                off: i64::from(stack_args_size) - off,
35            },
36            StackAMode::Slot(off) => AMode::SlotOffset { off },
37            StackAMode::OutgoingArg(off) => AMode::SPOffset { off },
38        }
39    }
40}
41
42// Returns the size of stack space needed to store the
43// `clobbered_callee_saved` registers.
44fn compute_clobber_size(
45    call_conv: isa::CallConv,
46    clobbered_callee_saves: &[Writable<RealReg>],
47) -> u32 {
48    let mut int_regs = 0;
49    let mut vec_regs = 0;
50    for &reg in clobbered_callee_saves {
51        match reg.to_reg().class() {
52            RegClass::Int => {
53                int_regs += 1;
54            }
55            RegClass::Float => {
56                vec_regs += 1;
57            }
58            RegClass::Vector => unreachable!(),
59        }
60    }
61
62    // Round up to multiple of 2, to keep 16-byte stack alignment.
63    let int_save_bytes = (int_regs + (int_regs & 1)) * 8;
64    let vec_save_bytes = if call_conv == isa::CallConv::PreserveAll {
65        // In the PreserveAll ABI, we save the entire vector register,
66        // i.e., all 128 bits.
67        vec_regs * 16
68    } else {
69        // The Procedure Call Standard for the Arm 64-bit Architecture
70        // (AAPCS64, including several related ABIs such as the one used by
71        // Windows) mandates saving only the bottom 8 bytes of the vector
72        // registers, so we round up the number of registers to ensure
73        // proper stack alignment (similarly to the situation with
74        // `int_reg`).
75        let vec_reg_size = 8;
76        let vec_save_padding = vec_regs & 1;
77        // FIXME: SVE: ABI is different to Neon, so do we treat all vec regs as Z-regs?
78        (vec_regs + vec_save_padding) * vec_reg_size
79    };
80
81    int_save_bytes + vec_save_bytes
82}
83
84/// The compact unwinding encoding can only represent pushes and pops of adjacent register pairs,
85/// so unused callee-saved registers may also need to be pushed.
86fn add_macho_compact_unwind_paired_regs(regs: &mut Vec<Writable<RealReg>>) {
87    let int_regs: FxHashSet<_> = regs
88        .iter()
89        .filter_map(|r| {
90            if r.to_reg().class() == RegClass::Int {
91                Some(r.to_reg().hw_enc())
92            } else {
93                None
94            }
95        })
96        .collect();
97    for (a, b) in [(19, 20), (21, 22), (23, 24), (25, 26), (27, 28)] {
98        if int_regs.contains(&a) && !int_regs.contains(&b) {
99            regs.push(Writable::from_reg(xreg(b).to_real_reg().unwrap()))
100        } else if int_regs.contains(&b) && !int_regs.contains(&a) {
101            regs.push(Writable::from_reg(xreg(a).to_real_reg().unwrap()))
102        }
103    }
104
105    let fp_regs: FxHashSet<_> = regs
106        .iter()
107        .filter_map(|r| {
108            if r.to_reg().class() == RegClass::Float {
109                Some(r.to_reg().hw_enc())
110            } else {
111                None
112            }
113        })
114        .collect();
115    for (a, b) in [(8, 9), (10, 11), (12, 13), (14, 15)] {
116        if fp_regs.contains(&a) && !fp_regs.contains(&b) {
117            regs.push(Writable::from_reg(vreg(b).to_real_reg().unwrap()))
118        } else if fp_regs.contains(&b) && !fp_regs.contains(&a) {
119            regs.push(Writable::from_reg(vreg(a).to_real_reg().unwrap()))
120        }
121    }
122}
123
124/// AArch64-specific ABI behavior. This struct just serves as an implementation
125/// point for the trait; it is never actually instantiated.
126pub struct AArch64MachineDeps;
127
128impl IsaFlags for aarch64_settings::Flags {
129    fn is_forward_edge_cfi_enabled(&self) -> bool {
130        self.use_bti()
131    }
132}
133
134impl ABIMachineSpec for AArch64MachineDeps {
135    type I = Inst;
136
137    type F = aarch64_settings::Flags;
138
139    /// This is the limit for the size of argument and return-value areas on the
140    /// stack. We place a reasonable limit here to avoid integer overflow issues
141    /// with 32-bit arithmetic: for now, 128 MB.
142    const STACK_ARG_RET_SIZE_LIMIT: u32 = 128 * 1024 * 1024;
143
144    fn word_bits() -> u32 {
145        64
146    }
147
148    /// Return required stack alignment in bytes.
149    fn stack_align(_call_conv: isa::CallConv) -> u32 {
150        16
151    }
152
153    fn compute_arg_locs(
154        call_conv: isa::CallConv,
155        flags: &settings::Flags,
156        params: &[ir::AbiParam],
157        args_or_rets: ArgsOrRets,
158        add_ret_area_ptr: bool,
159        mut args: ArgsAccumulator,
160    ) -> CodegenResult<(u32, Option<usize>)> {
161        let is_apple_cc = call_conv == isa::CallConv::AppleAarch64;
162        let is_winch_return = call_conv == isa::CallConv::Winch && args_or_rets == ArgsOrRets::Rets;
163
164        // See AArch64 ABI (https://github.com/ARM-software/abi-aa/blob/2021Q1/aapcs64/aapcs64.rst#64parameter-passing), sections 6.4.
165        //
166        // MacOS aarch64 is slightly different, see also
167        // https://developer.apple.com/documentation/xcode/writing_arm64_code_for_apple_platforms.
168        // We are diverging from the MacOS aarch64 implementation in the
169        // following ways:
170        // - sign- and zero- extensions of data types less than 32 bits are not
171        // implemented yet.
172        // - we align the arguments stack space to a 16-bytes boundary, while
173        // the MacOS allows aligning only on 8 bytes. In practice it means we're
174        // slightly overallocating when calling, which is fine, and doesn't
175        // break our other invariants that the stack is always allocated in
176        // 16-bytes chunks.
177
178        let mut next_xreg = if call_conv == isa::CallConv::Tail {
179            // We reserve `x0` for the return area pointer. For simplicity, we
180            // reserve it even when there is no return area pointer needed. This
181            // also means that identity functions don't have to shuffle arguments to
182            // different return registers because we shifted all argument register
183            // numbers down by one to make space for the return area pointer.
184            //
185            // Also, we cannot use all allocatable GPRs as arguments because we need
186            // at least one allocatable register for holding the callee address in
187            // indirect calls. So skip `x1` also, reserving it for that role.
188            2
189        } else {
190            0
191        };
192        let mut next_vreg = 0;
193        let mut next_stack: u32 = 0;
194
195        // Note on return values: on the regular ABI, we may return values
196        // in 8 registers for V128 and I64 registers independently of the
197        // number of register values returned in the other class. That is,
198        // we can return values in up to 8 integer and
199        // 8 vector registers at once.
200        let max_per_class_reg_vals = 8; // x0-x7 and v0-v7
201        let mut remaining_reg_vals = 16;
202
203        let ret_area_ptr = if add_ret_area_ptr {
204            debug_assert_eq!(args_or_rets, ArgsOrRets::Args);
205            if call_conv != isa::CallConv::Winch {
206                // In the AAPCS64 calling convention the return area pointer is
207                // stored in x8.
208                Some(ABIArg::reg(
209                    xreg(8).to_real_reg().unwrap(),
210                    I64,
211                    ir::ArgumentExtension::None,
212                    ir::ArgumentPurpose::Normal,
213                ))
214            } else {
215                // Use x0 for the return area pointer in the Winch calling convention
216                // to simplify the ABI handling code in Winch by avoiding an AArch64
217                // special case to assign it to x8.
218                next_xreg += 1;
219                Some(ABIArg::reg(
220                    xreg(0).to_real_reg().unwrap(),
221                    I64,
222                    ir::ArgumentExtension::None,
223                    ir::ArgumentPurpose::Normal,
224                ))
225            }
226        } else {
227            None
228        };
229
230        for (i, param) in params.into_iter().enumerate() {
231            if is_apple_cc && param.value_type == types::F128 && !flags.enable_llvm_abi_extensions()
232            {
233                panic!(
234                    "f128 args/return values not supported for apple_aarch64 unless LLVM ABI extensions are enabled"
235                );
236            }
237
238            let (rcs, reg_types) = Inst::rc_for_type(param.value_type)?;
239
240            if let ir::ArgumentPurpose::StructReturn = param.purpose {
241                assert!(
242                    call_conv != isa::CallConv::Tail,
243                    "support for StructReturn parameters is not implemented for the `tail` \
244                    calling convention yet",
245                );
246            }
247
248            if let ir::ArgumentPurpose::StructArgument(_) = param.purpose {
249                panic!(
250                    "StructArgument parameters are not supported on arm64. \
251                    Use regular pointer arguments instead."
252                );
253            }
254
255            if let ir::ArgumentPurpose::StructReturn = param.purpose {
256                // FIXME add assert_eq!(args_or_rets, ArgsOrRets::Args); once
257                // ensure_struct_return_ptr_is_returned is gone.
258                assert!(
259                    param.value_type == types::I64,
260                    "StructReturn must be a pointer sized integer"
261                );
262                args.push(ABIArg::Slots {
263                    slots: smallvec![ABIArgSlot::Reg {
264                        reg: xreg(8).to_real_reg().unwrap(),
265                        ty: types::I64,
266                        extension: param.extension,
267                    },],
268                    purpose: ir::ArgumentPurpose::StructReturn,
269                });
270                continue;
271            }
272
273            // Handle multi register params
274            //
275            // See AArch64 ABI (https://github.com/ARM-software/abi-aa/blob/2021Q1/aapcs64/aapcs64.rst#642parameter-passing-rules), (Section 6.4.2 Stage C).
276            //
277            // For arguments with alignment of 16 we round up the register number
278            // to the next even value. So we can never allocate for example an i128
279            // to X1 and X2, we have to skip one register and do X2, X3
280            // (Stage C.8)
281            // Note: The Apple ABI deviates a bit here. They don't respect Stage C.8
282            // and will happily allocate a i128 to X1 and X2
283            //
284            // For integer types with alignment of 16 we also have the additional
285            // restriction of passing the lower half in Xn and the upper half in Xn+1
286            // (Stage C.9)
287            //
288            // For examples of how LLVM handles this: https://godbolt.org/z/bhd3vvEfh
289            //
290            // On the Apple ABI it is unspecified if we can spill half the value into the stack
291            // i.e load the lower half into x7 and the upper half into the stack
292            // LLVM does not seem to do this, so we are going to replicate that behaviour
293            let is_multi_reg = rcs.len() >= 2;
294            if is_multi_reg {
295                assert!(
296                    rcs.len() == 2,
297                    "Unable to handle multi reg params with more than 2 regs"
298                );
299                assert!(
300                    rcs == &[RegClass::Int, RegClass::Int],
301                    "Unable to handle non i64 regs"
302                );
303
304                let reg_class_space = max_per_class_reg_vals - next_xreg;
305                let reg_space = remaining_reg_vals;
306
307                if reg_space >= 2 && reg_class_space >= 2 {
308                    // The aarch64 ABI does not allow us to start a split argument
309                    // at an odd numbered register. So we need to skip one register
310                    //
311                    // TODO: The Fast ABI should probably not skip the register
312                    if !is_apple_cc && next_xreg % 2 != 0 {
313                        next_xreg += 1;
314                    }
315
316                    let lower_reg = xreg(next_xreg);
317                    let upper_reg = xreg(next_xreg + 1);
318
319                    args.push(ABIArg::Slots {
320                        slots: smallvec![
321                            ABIArgSlot::Reg {
322                                reg: lower_reg.to_real_reg().unwrap(),
323                                ty: reg_types[0],
324                                extension: param.extension,
325                            },
326                            ABIArgSlot::Reg {
327                                reg: upper_reg.to_real_reg().unwrap(),
328                                ty: reg_types[1],
329                                extension: param.extension,
330                            },
331                        ],
332                        purpose: param.purpose,
333                    });
334
335                    next_xreg += 2;
336                    remaining_reg_vals -= 2;
337                    continue;
338                }
339            } else {
340                // Single Register parameters
341                let rc = rcs[0];
342                let next_reg = match rc {
343                    RegClass::Int => &mut next_xreg,
344                    RegClass::Float => &mut next_vreg,
345                    RegClass::Vector => unreachable!(),
346                };
347
348                let push_to_reg = if is_winch_return {
349                    // Winch uses the first register to return the last result
350                    i == params.len() - 1
351                } else {
352                    // Use max_per_class_reg_vals & remaining_reg_vals otherwise
353                    *next_reg < max_per_class_reg_vals && remaining_reg_vals > 0
354                };
355
356                if push_to_reg {
357                    let reg = match rc {
358                        RegClass::Int => xreg(*next_reg),
359                        RegClass::Float => vreg(*next_reg),
360                        RegClass::Vector => unreachable!(),
361                    };
362                    // Overlay Z-regs on V-regs for parameter passing.
363                    let ty = if param.value_type.is_dynamic_vector() {
364                        dynamic_to_fixed(param.value_type)
365                    } else {
366                        param.value_type
367                    };
368                    args.push(ABIArg::reg(
369                        reg.to_real_reg().unwrap(),
370                        ty,
371                        param.extension,
372                        param.purpose,
373                    ));
374                    *next_reg += 1;
375                    remaining_reg_vals -= 1;
376                    continue;
377                }
378            }
379
380            // Spill to the stack
381
382            if args_or_rets == ArgsOrRets::Rets && !flags.enable_multi_ret_implicit_sret() {
383                return Err(crate::CodegenError::Unsupported(
384                    "Too many return values to fit in registers. \
385                    Use a StructReturn argument instead. (#9510)"
386                        .to_owned(),
387                ));
388            }
389
390            // Compute the stack slot's size.
391            let size = (ty_bits(param.value_type) / 8) as u32;
392
393            // MacOS and Winch aarch64 allows stack slots with sizes less than 8
394            // bytes. They still need to be properly aligned on their natural
395            // data alignment, though, and this additionally is only applicable
396            // for arguments or when there's no argument extension in play.
397            // Stack slots for return values with argument extension get their
398            // full machine-word-width loaded or stored.
399            //
400            // Otherwise every arg takes a minimum slot of 8 bytes. (16-byte
401            // stack alignment happens separately after all args.)
402            let size = if (is_apple_cc || is_winch_return)
403                && (args_or_rets == ArgsOrRets::Args
404                    || param.extension == ir::ArgumentExtension::None)
405            {
406                size
407            } else {
408                core::cmp::max(size, 8)
409            };
410
411            if !is_winch_return {
412                // Align the stack slot.
413                debug_assert!(size.is_power_of_two());
414                next_stack = align_to(next_stack, size);
415            }
416
417            let slots = reg_types
418                .iter()
419                .copied()
420                // Build the stack locations from each slot
421                .scan(next_stack, |next_stack, ty| {
422                    let slot_offset = *next_stack as i64;
423                    *next_stack += (ty_bits(ty) / 8) as u32;
424
425                    Some((ty, slot_offset))
426                })
427                .map(|(ty, offset)| ABIArgSlot::Stack {
428                    offset,
429                    ty,
430                    extension: param.extension,
431                })
432                .collect();
433
434            args.push(ABIArg::Slots {
435                slots,
436                purpose: param.purpose,
437            });
438
439            next_stack += size;
440        }
441
442        let extra_arg = if let Some(ret_area_ptr) = ret_area_ptr {
443            args.push_non_formal(ret_area_ptr);
444            Some(args.args().len() - 1)
445        } else {
446            None
447        };
448
449        if is_winch_return {
450            winch::reverse_stack(args, next_stack, false);
451        }
452
453        next_stack = align_to(next_stack, 16);
454
455        Ok((next_stack, extra_arg))
456    }
457
458    fn gen_load_stack(mem: StackAMode, into_reg: Writable<Reg>, ty: Type) -> Inst {
459        Inst::gen_load(into_reg, mem.into(), ty, MemFlagsData::trusted())
460    }
461
462    fn gen_store_stack(mem: StackAMode, from_reg: Reg, ty: Type) -> Inst {
463        Inst::gen_store(mem.into(), from_reg, ty, MemFlagsData::trusted())
464    }
465
466    fn gen_move(to_reg: Writable<Reg>, from_reg: Reg, ty: Type) -> Inst {
467        Inst::gen_move(to_reg, from_reg, ty)
468    }
469
470    fn gen_extend(
471        to_reg: Writable<Reg>,
472        from_reg: Reg,
473        signed: bool,
474        from_bits: u8,
475        to_bits: u8,
476    ) -> Inst {
477        assert!(from_bits < to_bits);
478        Inst::Extend {
479            rd: to_reg,
480            rn: from_reg,
481            signed,
482            from_bits,
483            to_bits,
484        }
485    }
486
487    fn gen_args(args: Vec<ArgPair>) -> Inst {
488        Inst::Args { args }
489    }
490
491    fn gen_rets(rets: Vec<RetPair>) -> Inst {
492        Inst::Rets { rets }
493    }
494
495    fn gen_add_imm(
496        _call_conv: isa::CallConv,
497        into_reg: Writable<Reg>,
498        from_reg: Reg,
499        imm: u32,
500    ) -> SmallInstVec<Inst> {
501        let imm = imm as u64;
502        let mut insts = SmallVec::new();
503        if let Some(imm12) = Imm12::maybe_from_u64(imm) {
504            insts.push(Inst::AluRRImm12 {
505                alu_op: ALUOp::Add,
506                size: OperandSize::Size64,
507                rd: into_reg,
508                rn: from_reg,
509                imm12,
510            });
511        } else {
512            let scratch2 = writable_tmp2_reg();
513            assert_ne!(scratch2.to_reg(), from_reg);
514            // `gen_add_imm` is only ever called after register allocation has taken place, and as a
515            // result it's ok to reuse the scratch2 register here. If that changes, we'll need to
516            // plumb through a way to allocate temporary virtual registers
517            insts.extend(Inst::load_constant(scratch2, imm));
518            insts.push(Inst::AluRRRExtend {
519                alu_op: ALUOp::Add,
520                size: OperandSize::Size64,
521                rd: into_reg,
522                rn: from_reg,
523                rm: scratch2.to_reg(),
524                extendop: ExtendOp::UXTX,
525            });
526        }
527        insts
528    }
529
530    fn gen_stack_lower_bound_trap(limit_reg: Reg) -> SmallInstVec<Inst> {
531        let mut insts = SmallVec::new();
532        insts.push(Inst::AluRRRExtend {
533            alu_op: ALUOp::SubS,
534            size: OperandSize::Size64,
535            rd: writable_zero_reg(),
536            rn: stack_reg(),
537            rm: limit_reg,
538            extendop: ExtendOp::UXTX,
539        });
540        insts.push(Inst::TrapIf {
541            trap_code: ir::TrapCode::STACK_OVERFLOW,
542            // Here `Lo` == "less than" when interpreting the two
543            // operands as unsigned integers.
544            kind: CondBrKind::Cond(Cond::Lo),
545        });
546        insts
547    }
548
549    fn gen_get_stack_addr(mem: StackAMode, into_reg: Writable<Reg>) -> Inst {
550        // FIXME: Do something different for dynamic types?
551        let mem = mem.into();
552        Inst::LoadAddr { rd: into_reg, mem }
553    }
554
555    fn get_stacklimit_reg(_call_conv: isa::CallConv) -> Reg {
556        spilltmp_reg()
557    }
558
559    fn gen_load_base_offset(into_reg: Writable<Reg>, base: Reg, offset: i32, ty: Type) -> Inst {
560        let mem = AMode::RegOffset {
561            rn: base,
562            off: offset as i64,
563        };
564        Inst::gen_load(into_reg, mem, ty, MemFlagsData::trusted())
565    }
566
567    fn gen_store_base_offset(base: Reg, offset: i32, from_reg: Reg, ty: Type) -> Inst {
568        let mem = AMode::RegOffset {
569            rn: base,
570            off: offset as i64,
571        };
572        Inst::gen_store(mem, from_reg, ty, MemFlagsData::trusted())
573    }
574
575    fn gen_sp_reg_adjust(amount: i32) -> SmallInstVec<Inst> {
576        if amount == 0 {
577            return SmallVec::new();
578        }
579
580        let (amount, is_sub) = if amount > 0 {
581            (amount as u64, false)
582        } else {
583            (-amount as u64, true)
584        };
585
586        let alu_op = if is_sub { ALUOp::Sub } else { ALUOp::Add };
587
588        let mut ret = SmallVec::new();
589        if let Some(imm12) = Imm12::maybe_from_u64(amount) {
590            let adj_inst = Inst::AluRRImm12 {
591                alu_op,
592                size: OperandSize::Size64,
593                rd: writable_stack_reg(),
594                rn: stack_reg(),
595                imm12,
596            };
597            ret.push(adj_inst);
598        } else {
599            let tmp = writable_spilltmp_reg();
600            // `gen_sp_reg_adjust` is called after regalloc2, so it's acceptable to reuse `tmp` for
601            // intermediates in `load_constant`.
602            let const_inst = Inst::load_constant(tmp, amount);
603            let adj_inst = Inst::AluRRRExtend {
604                alu_op,
605                size: OperandSize::Size64,
606                rd: writable_stack_reg(),
607                rn: stack_reg(),
608                rm: tmp.to_reg(),
609                extendop: ExtendOp::UXTX,
610            };
611            ret.extend(const_inst);
612            ret.push(adj_inst);
613        }
614        ret
615    }
616
617    fn gen_prologue_frame_setup(
618        call_conv: isa::CallConv,
619        flags: &settings::Flags,
620        isa_flags: &aarch64_settings::Flags,
621        frame_layout: &FrameLayout,
622    ) -> SmallInstVec<Inst> {
623        let setup_frame = frame_layout.setup_area_size > 0;
624        let mut insts = SmallVec::new();
625
626        match Self::select_api_key(isa_flags, call_conv, setup_frame) {
627            Some(key) => {
628                insts.push(Inst::Paci { key });
629                if flags.unwind_info() {
630                    insts.push(Inst::Unwind {
631                        inst: UnwindInst::Aarch64SetPointerAuth {
632                            return_addresses: true,
633                        },
634                    });
635                }
636            }
637            None => {
638                if isa_flags.use_bti() {
639                    insts.push(Inst::Bti {
640                        targets: BranchTargetType::C,
641                    });
642                }
643
644                if flags.unwind_info() && call_conv == isa::CallConv::AppleAarch64 {
645                    // The macOS unwinder seems to require this.
646                    insts.push(Inst::Unwind {
647                        inst: UnwindInst::Aarch64SetPointerAuth {
648                            return_addresses: false,
649                        },
650                    });
651                }
652            }
653        }
654
655        if setup_frame {
656            // stp fp (x29), lr (x30), [sp, #-16]!
657            insts.push(Inst::StoreP64 {
658                rt: fp_reg(),
659                rt2: link_reg(),
660                mem: PairAMode::SPPreIndexed {
661                    simm7: SImm7Scaled::maybe_from_i64(-16, types::I64).unwrap(),
662                },
663                flags: MemFlagsData::trusted(),
664            });
665
666            if flags.unwind_info() {
667                insts.push(Inst::Unwind {
668                    inst: UnwindInst::PushFrameRegs {
669                        offset_upward_to_caller_sp: frame_layout.setup_area_size,
670                    },
671                });
672            }
673
674            // mov fp (x29), sp. This uses the ADDI rd, rs, 0 form of `MOV` because
675            // the usual encoding (`ORR`) does not work with SP.
676            insts.push(Inst::AluRRImm12 {
677                alu_op: ALUOp::Add,
678                size: OperandSize::Size64,
679                rd: writable_fp_reg(),
680                rn: stack_reg(),
681                imm12: Imm12 {
682                    bits: 0,
683                    shift12: false,
684                },
685            });
686        }
687
688        insts
689    }
690
691    fn gen_epilogue_frame_restore(
692        call_conv: isa::CallConv,
693        _flags: &settings::Flags,
694        _isa_flags: &aarch64_settings::Flags,
695        frame_layout: &FrameLayout,
696    ) -> SmallInstVec<Inst> {
697        let setup_frame = frame_layout.setup_area_size > 0;
698        let mut insts = SmallVec::new();
699
700        if setup_frame {
701            // N.B.: sp is already adjusted to the appropriate place by the
702            // clobber-restore code (which also frees the fixed frame). Hence, there
703            // is no need for the usual `mov sp, fp` here.
704
705            // `ldp fp, lr, [sp], #16`
706            insts.push(Inst::LoadP64 {
707                rt: writable_fp_reg(),
708                rt2: writable_link_reg(),
709                mem: PairAMode::SPPostIndexed {
710                    simm7: SImm7Scaled::maybe_from_i64(16, types::I64).unwrap(),
711                },
712                flags: MemFlagsData::trusted(),
713            });
714        }
715
716        if call_conv == isa::CallConv::Tail && frame_layout.tail_args_size > 0 {
717            insts.extend(Self::gen_sp_reg_adjust(
718                frame_layout.tail_args_size.try_into().unwrap(),
719            ));
720        }
721
722        insts
723    }
724
725    fn gen_return(
726        call_conv: isa::CallConv,
727        isa_flags: &aarch64_settings::Flags,
728        frame_layout: &FrameLayout,
729    ) -> SmallInstVec<Inst> {
730        let setup_frame = frame_layout.setup_area_size > 0;
731
732        match Self::select_api_key(isa_flags, call_conv, setup_frame) {
733            Some(key) => {
734                smallvec![Inst::AuthenticatedRet {
735                    key,
736                    is_hint: !isa_flags.has_pauth(),
737                }]
738            }
739            None => {
740                smallvec![Inst::Ret {}]
741            }
742        }
743    }
744
745    fn gen_probestack(_insts: &mut SmallInstVec<Self::I>, _: u32) {
746        // TODO: implement if we ever require stack probes on an AArch64 host
747        // (unlikely unless Lucet is ported)
748        unimplemented!("Stack probing is unimplemented on AArch64");
749    }
750
751    fn gen_inline_probestack(
752        insts: &mut SmallInstVec<Self::I>,
753        _call_conv: isa::CallConv,
754        frame_size: u32,
755        guard_size: u32,
756    ) {
757        // The stack probe loop currently takes 6 instructions and each inline
758        // probe takes 2 (ish, these numbers sort of depend on the constants).
759        // Set this to 3 to keep the max size of the probe to 6 instructions.
760        const PROBE_MAX_UNROLL: u32 = 3;
761
762        // Calculate how many probes we need to perform. Round down, as we only
763        // need to probe whole guard_size regions we'd otherwise skip over.
764        let probe_count = frame_size / guard_size;
765        if probe_count == 0 {
766            // No probe necessary
767        } else if probe_count <= PROBE_MAX_UNROLL {
768            Self::gen_probestack_unroll(insts, guard_size, probe_count)
769        } else {
770            Self::gen_probestack_loop(insts, frame_size, guard_size)
771        }
772    }
773
774    fn gen_clobber_save(
775        call_conv: isa::CallConv,
776        flags: &settings::Flags,
777        frame_layout: &FrameLayout,
778    ) -> SmallVec<[Inst; 16]> {
779        let (clobbered_int, clobbered_vec) = frame_layout.clobbered_callee_saves_by_class();
780
781        let mut insts = SmallVec::new();
782        let setup_frame = frame_layout.setup_area_size > 0;
783
784        // When a return_call within this function required more stack arguments than we have
785        // present, resize the incoming argument area of the frame to accommodate those arguments.
786        let incoming_args_diff = frame_layout.tail_args_size - frame_layout.incoming_args_size;
787        if incoming_args_diff > 0 {
788            // Decrement SP to account for the additional space required by a tail call.
789            insts.extend(Self::gen_sp_reg_adjust(-(incoming_args_diff as i32)));
790            if flags.unwind_info() {
791                insts.push(Inst::Unwind {
792                    inst: UnwindInst::StackAlloc {
793                        size: incoming_args_diff,
794                    },
795                });
796            }
797
798            // Move fp and lr down.
799            if setup_frame {
800                // Reload the frame pointer from the stack.
801                insts.push(Inst::ULoad64 {
802                    rd: regs::writable_fp_reg(),
803                    mem: AMode::SPOffset {
804                        off: i64::from(incoming_args_diff),
805                    },
806                    flags: MemFlagsData::trusted(),
807                });
808
809                // Store the frame pointer and link register again at the new SP
810                insts.push(Inst::StoreP64 {
811                    rt: fp_reg(),
812                    rt2: link_reg(),
813                    mem: PairAMode::SignedOffset {
814                        reg: regs::stack_reg(),
815                        simm7: SImm7Scaled::maybe_from_i64(0, types::I64).unwrap(),
816                    },
817                    flags: MemFlagsData::trusted(),
818                });
819
820                // Keep the frame pointer in sync
821                insts.push(Self::gen_move(
822                    regs::writable_fp_reg(),
823                    regs::stack_reg(),
824                    types::I64,
825                ));
826            }
827        }
828
829        if flags.unwind_info() && setup_frame {
830            // The *unwind* frame (but not the actual frame) starts at the
831            // clobbers, just below the saved FP/LR pair.
832            insts.push(Inst::Unwind {
833                inst: UnwindInst::DefineNewFrame {
834                    offset_downward_to_clobbers: frame_layout.clobber_size,
835                    offset_upward_to_caller_sp: frame_layout.setup_area_size,
836                },
837            });
838        }
839
840        // We use pre-indexed addressing modes here, rather than the possibly
841        // more efficient "subtract sp once then used fixed offsets" scheme,
842        // because (i) we cannot necessarily guarantee that the offset of a
843        // clobber-save slot will be within a SImm7Scaled (+504-byte) offset
844        // range of the whole frame including other slots, it is more complex to
845        // conditionally generate a two-stage SP adjustment (clobbers then fixed
846        // frame) otherwise, and generally we just want to maintain simplicity
847        // here for maintainability.  Because clobbers are at the top of the
848        // frame, just below FP, all that is necessary is to use the pre-indexed
849        // "push" `[sp, #-16]!` addressing mode.
850        //
851        // `frame_offset` tracks offset above start-of-clobbers for unwind-info
852        // purposes.
853        let mut clobber_offset = frame_layout.clobber_size;
854        let clobber_offset_change = 16;
855        let iter = clobbered_int.chunks_exact(2);
856
857        if let [rd] = iter.remainder() {
858            let rd: Reg = rd.to_reg().into();
859
860            debug_assert_eq!(rd.class(), RegClass::Int);
861            // str rd, [sp, #-16]!
862            insts.push(Inst::Store64 {
863                rd,
864                mem: AMode::SPPreIndexed {
865                    simm9: SImm9::maybe_from_i64(-clobber_offset_change).unwrap(),
866                },
867                flags: MemFlagsData::trusted(),
868            });
869
870            if flags.unwind_info() {
871                clobber_offset -= clobber_offset_change as u32;
872                insts.push(Inst::Unwind {
873                    inst: UnwindInst::SaveReg {
874                        clobber_offset,
875                        reg: rd.to_real_reg().unwrap(),
876                    },
877                });
878            }
879        }
880
881        let mut iter = iter.rev();
882
883        while let Some([rt, rt2]) = iter.next() {
884            // .to_reg().into(): Writable<RealReg> --> RealReg --> Reg
885            let rt: Reg = rt.to_reg().into();
886            let rt2: Reg = rt2.to_reg().into();
887
888            debug_assert!(rt.class() == RegClass::Int);
889            debug_assert!(rt2.class() == RegClass::Int);
890
891            // stp rt, rt2, [sp, #-16]!
892            insts.push(Inst::StoreP64 {
893                rt,
894                rt2,
895                mem: PairAMode::SPPreIndexed {
896                    simm7: SImm7Scaled::maybe_from_i64(-clobber_offset_change, types::I64).unwrap(),
897                },
898                flags: MemFlagsData::trusted(),
899            });
900
901            if flags.unwind_info() {
902                clobber_offset -= clobber_offset_change as u32;
903                insts.push(Inst::Unwind {
904                    inst: UnwindInst::SaveReg {
905                        clobber_offset,
906                        reg: rt.to_real_reg().unwrap(),
907                    },
908                });
909                insts.push(Inst::Unwind {
910                    inst: UnwindInst::SaveReg {
911                        clobber_offset: clobber_offset + (clobber_offset_change / 2) as u32,
912                        reg: rt2.to_real_reg().unwrap(),
913                    },
914                });
915            }
916        }
917
918        if call_conv == isa::CallConv::PreserveAll {
919            // Store full vector registers in PreserveAll convention.
920            for reg in clobbered_vec.iter().rev() {
921                let inst = Inst::FpuStore128 {
922                    rd: reg.to_reg().into(),
923                    mem: AMode::SPPreIndexed {
924                        simm9: SImm9::maybe_from_i64(-clobber_offset_change).unwrap(),
925                    },
926                    flags: MemFlagsData::trusted(),
927                };
928                insts.push(inst);
929                // N.B.: no unwind info: we don't have a way to
930                // represent "full register" anyway.
931            }
932        } else {
933            let store_vec_reg_half = |rd| Inst::FpuStore64 {
934                rd,
935                mem: AMode::SPPreIndexed {
936                    simm9: SImm9::maybe_from_i64(-clobber_offset_change).unwrap(),
937                },
938                flags: MemFlagsData::trusted(),
939            };
940            let iter = clobbered_vec.chunks_exact(2);
941
942            if let [rd] = iter.remainder() {
943                let rd: Reg = rd.to_reg().into();
944
945                debug_assert_eq!(rd.class(), RegClass::Float);
946                insts.push(store_vec_reg_half(rd));
947
948                if flags.unwind_info() {
949                    clobber_offset -= clobber_offset_change as u32;
950                    insts.push(Inst::Unwind {
951                        inst: UnwindInst::SaveReg {
952                            clobber_offset,
953                            reg: rd.to_real_reg().unwrap(),
954                        },
955                    });
956                }
957            }
958
959            let store_vec_reg_half_pair = |rt, rt2| {
960                let clobber_offset_change = 16;
961
962                (
963                    Inst::FpuStoreP64 {
964                        rt,
965                        rt2,
966                        mem: PairAMode::SPPreIndexed {
967                            simm7: SImm7Scaled::maybe_from_i64(-clobber_offset_change, F64)
968                                .unwrap(),
969                        },
970                        flags: MemFlagsData::trusted(),
971                    },
972                    clobber_offset_change as u32,
973                )
974            };
975            let mut iter = iter.rev();
976
977            while let Some([rt, rt2]) = iter.next() {
978                let rt: Reg = rt.to_reg().into();
979                let rt2: Reg = rt2.to_reg().into();
980
981                debug_assert_eq!(rt.class(), RegClass::Float);
982                debug_assert_eq!(rt2.class(), RegClass::Float);
983
984                let (inst, clobber_offset_change) = store_vec_reg_half_pair(rt, rt2);
985
986                insts.push(inst);
987
988                if flags.unwind_info() {
989                    clobber_offset -= clobber_offset_change;
990                    insts.push(Inst::Unwind {
991                        inst: UnwindInst::SaveReg {
992                            clobber_offset,
993                            reg: rt.to_real_reg().unwrap(),
994                        },
995                    });
996                    insts.push(Inst::Unwind {
997                        inst: UnwindInst::SaveReg {
998                            clobber_offset: clobber_offset + clobber_offset_change / 2,
999                            reg: rt2.to_real_reg().unwrap(),
1000                        },
1001                    });
1002                }
1003            }
1004        }
1005
1006        // Allocate the fixed frame below the clobbers if necessary.
1007        let stack_size = frame_layout.fixed_frame_storage_size + frame_layout.outgoing_args_size;
1008        if stack_size > 0 {
1009            insts.extend(Self::gen_sp_reg_adjust(-(stack_size as i32)));
1010            if flags.unwind_info() {
1011                insts.push(Inst::Unwind {
1012                    inst: UnwindInst::StackAlloc { size: stack_size },
1013                });
1014            }
1015        }
1016
1017        insts
1018    }
1019
1020    fn gen_clobber_restore(
1021        call_conv: isa::CallConv,
1022        _flags: &settings::Flags,
1023        frame_layout: &FrameLayout,
1024    ) -> SmallVec<[Inst; 16]> {
1025        let mut insts = SmallVec::new();
1026        let (clobbered_int, clobbered_vec) = frame_layout.clobbered_callee_saves_by_class();
1027
1028        // Free the fixed frame if necessary.
1029        let stack_size = frame_layout.fixed_frame_storage_size + frame_layout.outgoing_args_size;
1030        if stack_size > 0 {
1031            insts.extend(Self::gen_sp_reg_adjust(stack_size as i32));
1032        }
1033
1034        if call_conv == isa::CallConv::PreserveAll {
1035            for reg in clobbered_vec.iter() {
1036                let inst = Inst::FpuLoad128 {
1037                    rd: reg.map(|r| r.into()),
1038                    mem: AMode::SPPostIndexed {
1039                        simm9: SImm9::maybe_from_i64(16).unwrap(),
1040                    },
1041                    flags: MemFlagsData::trusted(),
1042                };
1043                insts.push(inst);
1044                // N.B.: no unwind info; we don't have a way to
1045                // represent "full vector register saved" anyway.
1046            }
1047        } else {
1048            let load_vec_reg_half = |rd| Inst::FpuLoad64 {
1049                rd,
1050                mem: AMode::SPPostIndexed {
1051                    simm9: SImm9::maybe_from_i64(16).unwrap(),
1052                },
1053                flags: MemFlagsData::trusted(),
1054            };
1055            let load_vec_reg_half_pair = |rt, rt2| Inst::FpuLoadP64 {
1056                rt,
1057                rt2,
1058                mem: PairAMode::SPPostIndexed {
1059                    simm7: SImm7Scaled::maybe_from_i64(16, F64).unwrap(),
1060                },
1061                flags: MemFlagsData::trusted(),
1062            };
1063
1064            let mut iter = clobbered_vec.chunks_exact(2);
1065
1066            while let Some([rt, rt2]) = iter.next() {
1067                let rt: Writable<Reg> = rt.map(|r| r.into());
1068                let rt2: Writable<Reg> = rt2.map(|r| r.into());
1069
1070                debug_assert_eq!(rt.to_reg().class(), RegClass::Float);
1071                debug_assert_eq!(rt2.to_reg().class(), RegClass::Float);
1072                insts.push(load_vec_reg_half_pair(rt, rt2));
1073            }
1074
1075            debug_assert!(iter.remainder().len() <= 1);
1076
1077            if let [rd] = iter.remainder() {
1078                let rd: Writable<Reg> = rd.map(|r| r.into());
1079
1080                debug_assert_eq!(rd.to_reg().class(), RegClass::Float);
1081                insts.push(load_vec_reg_half(rd));
1082            }
1083        }
1084
1085        let mut iter = clobbered_int.chunks_exact(2);
1086
1087        while let Some([rt, rt2]) = iter.next() {
1088            let rt: Writable<Reg> = rt.map(|r| r.into());
1089            let rt2: Writable<Reg> = rt2.map(|r| r.into());
1090
1091            debug_assert_eq!(rt.to_reg().class(), RegClass::Int);
1092            debug_assert_eq!(rt2.to_reg().class(), RegClass::Int);
1093            // ldp rt, rt2, [sp], #16
1094            insts.push(Inst::LoadP64 {
1095                rt,
1096                rt2,
1097                mem: PairAMode::SPPostIndexed {
1098                    simm7: SImm7Scaled::maybe_from_i64(16, I64).unwrap(),
1099                },
1100                flags: MemFlagsData::trusted(),
1101            });
1102        }
1103
1104        debug_assert!(iter.remainder().len() <= 1);
1105
1106        if let [rd] = iter.remainder() {
1107            let rd: Writable<Reg> = rd.map(|r| r.into());
1108
1109            debug_assert_eq!(rd.to_reg().class(), RegClass::Int);
1110            // ldr rd, [sp], #16
1111            insts.push(Inst::ULoad64 {
1112                rd,
1113                mem: AMode::SPPostIndexed {
1114                    simm9: SImm9::maybe_from_i64(16).unwrap(),
1115                },
1116                flags: MemFlagsData::trusted(),
1117            });
1118        }
1119
1120        insts
1121    }
1122
1123    fn gen_memcpy<F: FnMut(Type) -> Writable<Reg>>(
1124        call_conv: isa::CallConv,
1125        dst: Reg,
1126        src: Reg,
1127        size: usize,
1128        mut alloc_tmp: F,
1129    ) -> SmallVec<[Self::I; 8]> {
1130        let mut insts = SmallVec::new();
1131        let arg0 = writable_xreg(0);
1132        let arg1 = writable_xreg(1);
1133        let arg2 = writable_xreg(2);
1134        let tmp = alloc_tmp(Self::word_type());
1135        insts.extend(Inst::load_constant(tmp, size as u64));
1136        insts.push(Inst::Call {
1137            info: Box::new(CallInfo {
1138                dest: ExternalName::LibCall(LibCall::Memcpy),
1139                uses: smallvec![
1140                    CallArgPair {
1141                        vreg: dst,
1142                        preg: arg0.to_reg()
1143                    },
1144                    CallArgPair {
1145                        vreg: src,
1146                        preg: arg1.to_reg()
1147                    },
1148                    CallArgPair {
1149                        vreg: tmp.to_reg(),
1150                        preg: arg2.to_reg()
1151                    }
1152                ],
1153                defs: smallvec![],
1154                clobbers: Self::get_regs_clobbered_by_call(call_conv, false),
1155                caller_conv: call_conv,
1156                callee_conv: call_conv,
1157                callee_pop_size: 0,
1158                try_call_info: None,
1159                patchable: false,
1160            }),
1161        });
1162        insts
1163    }
1164
1165    fn get_number_of_spillslots_for_value(
1166        rc: RegClass,
1167        vector_size: u32,
1168        _isa_flags: &Self::F,
1169    ) -> u32 {
1170        assert_eq!(vector_size % 8, 0);
1171        // We allocate in terms of 8-byte slots.
1172        match rc {
1173            RegClass::Int => 1,
1174            RegClass::Float => vector_size / 8,
1175            RegClass::Vector => unreachable!(),
1176        }
1177    }
1178
1179    fn get_machine_env(flags: &settings::Flags, _call_conv: isa::CallConv) -> &MachineEnv {
1180        if flags.enable_pinned_reg() {
1181            static MACHINE_ENV: MachineEnv = create_reg_env(true);
1182            &MACHINE_ENV
1183        } else {
1184            static MACHINE_ENV: MachineEnv = create_reg_env(false);
1185            &MACHINE_ENV
1186        }
1187    }
1188
1189    fn get_regs_clobbered_by_call(call_conv: isa::CallConv, is_exception: bool) -> PRegSet {
1190        match (call_conv, is_exception) {
1191            (isa::CallConv::Tail, true) => ALL_CLOBBERS,
1192            (isa::CallConv::Winch, true) => ALL_CLOBBERS,
1193            (isa::CallConv::Winch, false) => WINCH_CLOBBERS,
1194            // Note that "PreserveAll" actually preserves nothing at
1195            // the callsite if used for a `try_call`, because the
1196            // unwinder ABI for `try_call`s is still "no clobbered
1197            // register restores" for this ABI (so as to work with
1198            // Wasmtime).
1199            (isa::CallConv::PreserveAll, true) => ALL_CLOBBERS,
1200            (isa::CallConv::SystemV, _) => DEFAULT_AAPCS_CLOBBERS,
1201            // On Mach-O, the compact unwind info properly describes how are callee-save
1202            // registers restored during unwinding.
1203            (isa::CallConv::AppleAarch64, true) => DEFAULT_AAPCS_CLOBBERS,
1204            (isa::CallConv::PreserveAll, _) => NO_CLOBBERS,
1205            (_, false) => DEFAULT_AAPCS_CLOBBERS,
1206            (_, true) => panic!("unimplemented clobbers for exn abi of {call_conv:?}"),
1207        }
1208    }
1209
1210    fn get_ext_mode(
1211        call_conv: isa::CallConv,
1212        specified: ir::ArgumentExtension,
1213    ) -> ir::ArgumentExtension {
1214        if call_conv == isa::CallConv::AppleAarch64 {
1215            specified
1216        } else {
1217            ir::ArgumentExtension::None
1218        }
1219    }
1220
1221    fn compute_frame_layout(
1222        call_conv: isa::CallConv,
1223        flags: &settings::Flags,
1224        sig: &Signature,
1225        regs: &[Writable<RealReg>],
1226        function_calls: FunctionCalls,
1227        incoming_args_size: u32,
1228        tail_args_size: u32,
1229        stackslots_size: u32,
1230        fixed_frame_storage_size: u32,
1231        outgoing_args_size: u32,
1232    ) -> FrameLayout {
1233        let mut regs: Vec<Writable<RealReg>> = regs
1234            .iter()
1235            .cloned()
1236            .filter(|r| {
1237                is_reg_saved_in_prologue(call_conv, flags.enable_pinned_reg(), sig, r.to_reg())
1238            })
1239            .collect();
1240
1241        if call_conv == isa::CallConv::AppleAarch64 && flags.enable_compact_unwind_abi() {
1242            add_macho_compact_unwind_paired_regs(&mut regs);
1243            // For Mach-O compact unwind, these pushes/pops must be emitted in
1244            // the fixed expected order. The encoding specifies only which
1245            // callee-saved register pairs are preserved; the order is mandatory.
1246            regs.sort_unstable_by_key(|r| {
1247                let reg = r.to_reg();
1248                (reg.class(), Reverse(reg.hw_enc()))
1249            });
1250        } else {
1251            // Sort registers for deterministic code output. We can do an unstable
1252            // sort because the registers will be unique (there are no dups).
1253            regs.sort_unstable();
1254        }
1255
1256        // Compute clobber size.
1257        let clobber_size = compute_clobber_size(call_conv, &regs);
1258
1259        let needs_linkage_frame = flags.preserve_frame_pointers()
1260                // The function arguments that are passed on the stack are addressed
1261                // relative to the Frame Pointer.
1262                || incoming_args_size > 0
1263                || tail_args_size > incoming_args_size
1264                || clobber_size > 0
1265                || fixed_frame_storage_size > 0
1266                || outgoing_args_size > 0;
1267
1268        // Compute linkage frame size.
1269        let setup_area_size = if needs_linkage_frame || function_calls == FunctionCalls::Regular {
1270            16 // FP, LR
1271        } else {
1272            0
1273        };
1274
1275        // Return FrameLayout structure.
1276        FrameLayout {
1277            word_bytes: 8,
1278            incoming_args_size,
1279            tail_args_size,
1280            setup_area_size,
1281            clobber_size,
1282            fixed_frame_storage_size,
1283            stackslots_size,
1284            outgoing_args_size,
1285            clobbered_callee_saves: regs,
1286            function_calls,
1287        }
1288    }
1289
1290    fn retval_temp_reg(_call_conv_of_callee: isa::CallConv) -> Writable<Reg> {
1291        // Use x9 as a temp if needed: clobbered, not a
1292        // retval.
1293        regs::writable_xreg(9)
1294    }
1295
1296    fn exception_payload_regs(call_conv: isa::CallConv) -> &'static [Reg] {
1297        const PAYLOAD_REGS: &'static [Reg] = &[regs::xreg(0), regs::xreg(1)];
1298        match call_conv {
1299            isa::CallConv::SystemV
1300            | isa::CallConv::Tail
1301            | isa::CallConv::PreserveAll
1302            | isa::CallConv::AppleAarch64 => PAYLOAD_REGS,
1303            _ => &[],
1304        }
1305    }
1306}
1307
1308impl AArch64MachineDeps {
1309    fn gen_probestack_unroll(insts: &mut SmallInstVec<Inst>, guard_size: u32, probe_count: u32) {
1310        // When manually unrolling adjust the stack pointer and then write a zero
1311        // to the stack at that offset. This generates something like
1312        // `sub sp, sp, #1, lsl #12` followed by `stur wzr, [sp]`.
1313        //
1314        // We do this because valgrind expects us to never write beyond the stack
1315        // pointer and associated redzone.
1316        // See: https://github.com/bytecodealliance/wasmtime/issues/7454
1317        for _ in 0..probe_count {
1318            insts.extend(Self::gen_sp_reg_adjust(-(guard_size as i32)));
1319
1320            insts.push(Inst::gen_store(
1321                AMode::SPOffset { off: 0 },
1322                zero_reg(),
1323                I32,
1324                MemFlagsData::trusted(),
1325            ));
1326        }
1327
1328        // Restore the stack pointer to its original value
1329        insts.extend(Self::gen_sp_reg_adjust((guard_size * probe_count) as i32));
1330    }
1331
1332    fn gen_probestack_loop(insts: &mut SmallInstVec<Inst>, frame_size: u32, guard_size: u32) {
1333        // The non-unrolled version uses two temporary registers. The
1334        // `start` contains the current offset from sp and counts downwards
1335        // during the loop by increments of `guard_size`. The `end` is
1336        // the size of the frame and where we stop.
1337        //
1338        // Note that this emission is all post-regalloc so it should be ok
1339        // to use the temporary registers here as input/output as the loop
1340        // itself is not allowed to use the registers.
1341        let start = writable_spilltmp_reg();
1342        let end = writable_tmp2_reg();
1343        // `gen_inline_probestack` is called after regalloc2, so it's acceptable to reuse
1344        // `start` and `end` as temporaries in load_constant.
1345        insts.extend(Inst::load_constant(start, 0));
1346        insts.extend(Inst::load_constant(end, frame_size.into()));
1347        insts.push(Inst::StackProbeLoop {
1348            start,
1349            end: end.to_reg(),
1350            step: Imm12::maybe_from_u64(guard_size.into()).unwrap(),
1351        });
1352    }
1353
1354    pub fn select_api_key(
1355        isa_flags: &aarch64_settings::Flags,
1356        call_conv: isa::CallConv,
1357        setup_frame: bool,
1358    ) -> Option<APIKey> {
1359        if isa_flags.sign_return_address() && (setup_frame || isa_flags.sign_return_address_all()) {
1360            // The `tail` calling convention uses a zero modifier rather than SP
1361            // because tail calls may happen with a different stack pointer than
1362            // when the function was entered, meaning that it won't be the same when
1363            // the return address is decrypted.
1364            Some(if isa_flags.sign_return_address_with_bkey() {
1365                match call_conv {
1366                    isa::CallConv::Tail => APIKey::BZ,
1367                    _ => APIKey::BSP,
1368                }
1369            } else {
1370                match call_conv {
1371                    isa::CallConv::Tail => APIKey::AZ,
1372                    _ => APIKey::ASP,
1373                }
1374            })
1375        } else {
1376            None
1377        }
1378    }
1379}
1380
1381/// Is the given register saved in the prologue if clobbered, i.e., is it a
1382/// callee-save?
1383fn is_reg_saved_in_prologue(
1384    call_conv: isa::CallConv,
1385    enable_pinned_reg: bool,
1386    sig: &Signature,
1387    r: RealReg,
1388) -> bool {
1389    if call_conv == isa::CallConv::PreserveAll {
1390        return true;
1391    }
1392
1393    // FIXME: We need to inspect whether a function is returning Z or P regs too.
1394    let save_z_regs = sig
1395        .params
1396        .iter()
1397        .filter(|p| p.value_type.is_dynamic_vector())
1398        .count()
1399        != 0;
1400
1401    match r.class() {
1402        RegClass::Int => {
1403            // x19 - x28 inclusive are callee-saves.
1404            // However, x21 is the pinned reg if `enable_pinned_reg`
1405            // is set, and is implicitly globally-allocated, hence not
1406            // callee-saved in prologues.
1407            if enable_pinned_reg && r.hw_enc() == PINNED_REG {
1408                false
1409            } else {
1410                r.hw_enc() >= 19 && r.hw_enc() <= 28
1411            }
1412        }
1413        RegClass::Float => {
1414            // If a subroutine takes at least one argument in scalable vector registers
1415            // or scalable predicate registers, or if it is a function that returns
1416            // results in such registers, it must ensure that the entire contents of
1417            // z8-z23 are preserved across the call. In other cases it need only
1418            // preserve the low 64 bits of z8-z15.
1419            if save_z_regs {
1420                r.hw_enc() >= 8 && r.hw_enc() <= 23
1421            } else {
1422                // v8 - v15 inclusive are callee-saves.
1423                r.hw_enc() >= 8 && r.hw_enc() <= 15
1424            }
1425        }
1426        RegClass::Vector => unreachable!(),
1427    }
1428}
1429
1430const fn default_aapcs_clobbers() -> PRegSet {
1431    PRegSet::empty()
1432        // x0 - x17 inclusive are caller-saves.
1433        .with(xreg_preg(0))
1434        .with(xreg_preg(1))
1435        .with(xreg_preg(2))
1436        .with(xreg_preg(3))
1437        .with(xreg_preg(4))
1438        .with(xreg_preg(5))
1439        .with(xreg_preg(6))
1440        .with(xreg_preg(7))
1441        .with(xreg_preg(8))
1442        .with(xreg_preg(9))
1443        .with(xreg_preg(10))
1444        .with(xreg_preg(11))
1445        .with(xreg_preg(12))
1446        .with(xreg_preg(13))
1447        .with(xreg_preg(14))
1448        .with(xreg_preg(15))
1449        .with(xreg_preg(16))
1450        .with(xreg_preg(17))
1451        // v0 - v7 inclusive and v16 - v31 inclusive are
1452        // caller-saves. The upper 64 bits of v8 - v15 inclusive are
1453        // also caller-saves.  However, because we cannot currently
1454        // represent partial registers to regalloc2, we indicate here
1455        // that every vector register is caller-save. Because this
1456        // function is used at *callsites*, approximating in this
1457        // direction (save more than necessary) is conservative and
1458        // thus safe.
1459        //
1460        // Note that we exclude clobbers from a call instruction when
1461        // a call instruction's callee has the same ABI as the caller
1462        // (the current function body); this is safe (anything
1463        // clobbered by callee can be clobbered by caller as well) and
1464        // avoids unnecessary saves of v8-v15 in the prologue even
1465        // though we include them as defs here.
1466        .with(vreg_preg(0))
1467        .with(vreg_preg(1))
1468        .with(vreg_preg(2))
1469        .with(vreg_preg(3))
1470        .with(vreg_preg(4))
1471        .with(vreg_preg(5))
1472        .with(vreg_preg(6))
1473        .with(vreg_preg(7))
1474        .with(vreg_preg(8))
1475        .with(vreg_preg(9))
1476        .with(vreg_preg(10))
1477        .with(vreg_preg(11))
1478        .with(vreg_preg(12))
1479        .with(vreg_preg(13))
1480        .with(vreg_preg(14))
1481        .with(vreg_preg(15))
1482        .with(vreg_preg(16))
1483        .with(vreg_preg(17))
1484        .with(vreg_preg(18))
1485        .with(vreg_preg(19))
1486        .with(vreg_preg(20))
1487        .with(vreg_preg(21))
1488        .with(vreg_preg(22))
1489        .with(vreg_preg(23))
1490        .with(vreg_preg(24))
1491        .with(vreg_preg(25))
1492        .with(vreg_preg(26))
1493        .with(vreg_preg(27))
1494        .with(vreg_preg(28))
1495        .with(vreg_preg(29))
1496        .with(vreg_preg(30))
1497        .with(vreg_preg(31))
1498}
1499
1500const fn winch_clobbers() -> PRegSet {
1501    PRegSet::empty()
1502        .with(xreg_preg(0))
1503        .with(xreg_preg(1))
1504        .with(xreg_preg(2))
1505        .with(xreg_preg(3))
1506        .with(xreg_preg(4))
1507        .with(xreg_preg(5))
1508        .with(xreg_preg(6))
1509        .with(xreg_preg(7))
1510        .with(xreg_preg(8))
1511        .with(xreg_preg(9))
1512        .with(xreg_preg(10))
1513        .with(xreg_preg(11))
1514        .with(xreg_preg(12))
1515        .with(xreg_preg(13))
1516        .with(xreg_preg(14))
1517        .with(xreg_preg(15))
1518        .with(xreg_preg(16))
1519        .with(xreg_preg(17))
1520        // x18 is used to carry platform state and is not allocatable by Winch.
1521        //
1522        // x19 - x27 are considered caller-saved in Winch's calling convention.
1523        .with(xreg_preg(19))
1524        .with(xreg_preg(20))
1525        .with(xreg_preg(21))
1526        .with(xreg_preg(22))
1527        .with(xreg_preg(23))
1528        .with(xreg_preg(24))
1529        .with(xreg_preg(25))
1530        .with(xreg_preg(26))
1531        .with(xreg_preg(27))
1532        // x28 is used as the shadow stack pointer and is considered
1533        // callee-saved.
1534        //
1535        // All vregs are considered caller-saved.
1536        .with(vreg_preg(0))
1537        .with(vreg_preg(1))
1538        .with(vreg_preg(2))
1539        .with(vreg_preg(3))
1540        .with(vreg_preg(4))
1541        .with(vreg_preg(5))
1542        .with(vreg_preg(6))
1543        .with(vreg_preg(7))
1544        .with(vreg_preg(8))
1545        .with(vreg_preg(9))
1546        .with(vreg_preg(10))
1547        .with(vreg_preg(11))
1548        .with(vreg_preg(12))
1549        .with(vreg_preg(13))
1550        .with(vreg_preg(14))
1551        .with(vreg_preg(15))
1552        .with(vreg_preg(16))
1553        .with(vreg_preg(17))
1554        .with(vreg_preg(18))
1555        .with(vreg_preg(19))
1556        .with(vreg_preg(20))
1557        .with(vreg_preg(21))
1558        .with(vreg_preg(22))
1559        .with(vreg_preg(23))
1560        .with(vreg_preg(24))
1561        .with(vreg_preg(25))
1562        .with(vreg_preg(26))
1563        .with(vreg_preg(27))
1564        .with(vreg_preg(28))
1565        .with(vreg_preg(29))
1566        .with(vreg_preg(30))
1567        .with(vreg_preg(31))
1568}
1569
1570const fn all_clobbers() -> PRegSet {
1571    PRegSet::empty()
1572        // integer registers: x0 to x28 inclusive. (x29 is FP, x30 is
1573        // LR, x31 is SP/ZR.)
1574        .with(xreg_preg(0))
1575        .with(xreg_preg(1))
1576        .with(xreg_preg(2))
1577        .with(xreg_preg(3))
1578        .with(xreg_preg(4))
1579        .with(xreg_preg(5))
1580        .with(xreg_preg(6))
1581        .with(xreg_preg(7))
1582        .with(xreg_preg(8))
1583        .with(xreg_preg(9))
1584        .with(xreg_preg(10))
1585        .with(xreg_preg(11))
1586        .with(xreg_preg(12))
1587        .with(xreg_preg(13))
1588        .with(xreg_preg(14))
1589        .with(xreg_preg(15))
1590        .with(xreg_preg(16))
1591        .with(xreg_preg(17))
1592        .with(xreg_preg(18))
1593        .with(xreg_preg(19))
1594        .with(xreg_preg(20))
1595        .with(xreg_preg(21))
1596        .with(xreg_preg(22))
1597        .with(xreg_preg(23))
1598        .with(xreg_preg(24))
1599        .with(xreg_preg(25))
1600        .with(xreg_preg(26))
1601        .with(xreg_preg(27))
1602        .with(xreg_preg(28))
1603        // vector registers: v0 to v31 inclusive.
1604        .with(vreg_preg(0))
1605        .with(vreg_preg(1))
1606        .with(vreg_preg(2))
1607        .with(vreg_preg(3))
1608        .with(vreg_preg(4))
1609        .with(vreg_preg(5))
1610        .with(vreg_preg(6))
1611        .with(vreg_preg(7))
1612        .with(vreg_preg(8))
1613        .with(vreg_preg(9))
1614        .with(vreg_preg(10))
1615        .with(vreg_preg(11))
1616        .with(vreg_preg(12))
1617        .with(vreg_preg(13))
1618        .with(vreg_preg(14))
1619        .with(vreg_preg(15))
1620        .with(vreg_preg(16))
1621        .with(vreg_preg(17))
1622        .with(vreg_preg(18))
1623        .with(vreg_preg(19))
1624        .with(vreg_preg(20))
1625        .with(vreg_preg(21))
1626        .with(vreg_preg(22))
1627        .with(vreg_preg(23))
1628        .with(vreg_preg(24))
1629        .with(vreg_preg(25))
1630        .with(vreg_preg(26))
1631        .with(vreg_preg(27))
1632        .with(vreg_preg(28))
1633        .with(vreg_preg(29))
1634        .with(vreg_preg(30))
1635        .with(vreg_preg(31))
1636}
1637
1638const DEFAULT_AAPCS_CLOBBERS: PRegSet = default_aapcs_clobbers();
1639const WINCH_CLOBBERS: PRegSet = winch_clobbers();
1640const ALL_CLOBBERS: PRegSet = all_clobbers();
1641const NO_CLOBBERS: PRegSet = PRegSet::empty();
1642
1643const fn create_reg_env(enable_pinned_reg: bool) -> MachineEnv {
1644    const fn preg(r: Reg) -> PReg {
1645        r.to_real_reg().unwrap().preg()
1646    }
1647
1648    let mut env = MachineEnv {
1649        preferred_regs_by_class: [
1650            PRegSet::empty()
1651                .with(preg(xreg(0)))
1652                .with(preg(xreg(1)))
1653                .with(preg(xreg(2)))
1654                .with(preg(xreg(3)))
1655                .with(preg(xreg(4)))
1656                .with(preg(xreg(5)))
1657                .with(preg(xreg(6)))
1658                .with(preg(xreg(7)))
1659                .with(preg(xreg(8)))
1660                .with(preg(xreg(9)))
1661                .with(preg(xreg(10)))
1662                .with(preg(xreg(11)))
1663                .with(preg(xreg(12)))
1664                .with(preg(xreg(13)))
1665                .with(preg(xreg(14)))
1666                .with(preg(xreg(15))),
1667            // x16 and x17 are spilltmp and tmp2 (see above).
1668            // x18 could be used by the platform to carry inter-procedural state;
1669            // conservatively assume so and make it not allocatable.
1670            // x19-28 are callee-saved and so not preferred.
1671            // x21 is the pinned register (if enabled) and not allocatable if so.
1672            // x29 is FP, x30 is LR, x31 is SP/ZR.
1673            PRegSet::empty()
1674                .with(preg(vreg(0)))
1675                .with(preg(vreg(1)))
1676                .with(preg(vreg(2)))
1677                .with(preg(vreg(3)))
1678                .with(preg(vreg(4)))
1679                .with(preg(vreg(5)))
1680                .with(preg(vreg(6)))
1681                .with(preg(vreg(7)))
1682                // v8-15 are callee-saved and so not preferred.
1683                .with(preg(vreg(16)))
1684                .with(preg(vreg(17)))
1685                .with(preg(vreg(18)))
1686                .with(preg(vreg(19)))
1687                .with(preg(vreg(20)))
1688                .with(preg(vreg(21)))
1689                .with(preg(vreg(22)))
1690                .with(preg(vreg(23)))
1691                .with(preg(vreg(24)))
1692                .with(preg(vreg(25)))
1693                .with(preg(vreg(26)))
1694                .with(preg(vreg(27)))
1695                .with(preg(vreg(28)))
1696                .with(preg(vreg(29)))
1697                .with(preg(vreg(30)))
1698                .with(preg(vreg(31))),
1699            // Vector Regclass is unused
1700            PRegSet::empty(),
1701        ],
1702        non_preferred_regs_by_class: [
1703            PRegSet::empty()
1704                .with(preg(xreg(19)))
1705                .with(preg(xreg(20)))
1706                // x21 is pinned reg if enabled; we add to this list below if not.
1707                .with(preg(xreg(22)))
1708                .with(preg(xreg(23)))
1709                .with(preg(xreg(24)))
1710                .with(preg(xreg(25)))
1711                .with(preg(xreg(26)))
1712                .with(preg(xreg(27)))
1713                .with(preg(xreg(28))),
1714            PRegSet::empty()
1715                .with(preg(vreg(8)))
1716                .with(preg(vreg(9)))
1717                .with(preg(vreg(10)))
1718                .with(preg(vreg(11)))
1719                .with(preg(vreg(12)))
1720                .with(preg(vreg(13)))
1721                .with(preg(vreg(14)))
1722                .with(preg(vreg(15))),
1723            // Vector Regclass is unused
1724            PRegSet::empty(),
1725        ],
1726        fixed_stack_slots: vec![],
1727        scratch_by_class: [None, None, None],
1728    };
1729
1730    if !enable_pinned_reg {
1731        debug_assert!(PINNED_REG == 21);
1732        env.non_preferred_regs_by_class[0].add(preg(xreg(PINNED_REG)));
1733    }
1734
1735    env
1736}