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        let callee_pop_size = match call_conv {
717            isa::CallConv::Tail => frame_layout.tail_args_size,
718            isa::CallConv::Winch => frame_layout.incoming_args_size,
719            _ => 0,
720        };
721        if callee_pop_size > 0 {
722            insts.extend(Self::gen_sp_reg_adjust(callee_pop_size.try_into().unwrap()));
723        }
724
725        insts
726    }
727
728    fn gen_return(
729        call_conv: isa::CallConv,
730        isa_flags: &aarch64_settings::Flags,
731        frame_layout: &FrameLayout,
732    ) -> SmallInstVec<Inst> {
733        let setup_frame = frame_layout.setup_area_size > 0;
734
735        match Self::select_api_key(isa_flags, call_conv, setup_frame) {
736            Some(key) => {
737                smallvec![Inst::AuthenticatedRet {
738                    key,
739                    is_hint: !isa_flags.has_pauth(),
740                }]
741            }
742            None => {
743                smallvec![Inst::Ret {}]
744            }
745        }
746    }
747
748    fn gen_probestack(_insts: &mut SmallInstVec<Self::I>, _: u32) {
749        // TODO: implement if we ever require stack probes on an AArch64 host
750        // (unlikely unless Lucet is ported)
751        unimplemented!("Stack probing is unimplemented on AArch64");
752    }
753
754    fn gen_inline_probestack(
755        insts: &mut SmallInstVec<Self::I>,
756        _call_conv: isa::CallConv,
757        frame_size: u32,
758        guard_size: u32,
759    ) {
760        // The stack probe loop currently takes 6 instructions and each inline
761        // probe takes 2 (ish, these numbers sort of depend on the constants).
762        // Set this to 3 to keep the max size of the probe to 6 instructions.
763        const PROBE_MAX_UNROLL: u32 = 3;
764
765        // Calculate how many probes we need to perform. Round down, as we only
766        // need to probe whole guard_size regions we'd otherwise skip over.
767        let probe_count = frame_size / guard_size;
768        if probe_count == 0 {
769            // No probe necessary
770        } else if probe_count <= PROBE_MAX_UNROLL {
771            Self::gen_probestack_unroll(insts, guard_size, probe_count)
772        } else {
773            Self::gen_probestack_loop(insts, frame_size, guard_size)
774        }
775    }
776
777    fn gen_clobber_save(
778        call_conv: isa::CallConv,
779        flags: &settings::Flags,
780        frame_layout: &FrameLayout,
781    ) -> SmallVec<[Inst; 16]> {
782        let (clobbered_int, clobbered_vec) = frame_layout.clobbered_callee_saves_by_class();
783
784        let mut insts = SmallVec::new();
785        let setup_frame = frame_layout.setup_area_size > 0;
786
787        // When a return_call within this function required more stack arguments than we have
788        // present, resize the incoming argument area of the frame to accommodate those arguments.
789        let incoming_args_diff = frame_layout.tail_args_size - frame_layout.incoming_args_size;
790        if incoming_args_diff > 0 {
791            // Decrement SP to account for the additional space required by a tail call.
792            insts.extend(Self::gen_sp_reg_adjust(-(incoming_args_diff as i32)));
793            if flags.unwind_info() {
794                insts.push(Inst::Unwind {
795                    inst: UnwindInst::StackAlloc {
796                        size: incoming_args_diff,
797                    },
798                });
799            }
800
801            // Move fp and lr down.
802            if setup_frame {
803                // Reload the frame pointer from the stack.
804                insts.push(Inst::ULoad64 {
805                    rd: regs::writable_fp_reg(),
806                    mem: AMode::SPOffset {
807                        off: i64::from(incoming_args_diff),
808                    },
809                    flags: MemFlagsData::trusted(),
810                });
811
812                // Store the frame pointer and link register again at the new SP
813                insts.push(Inst::StoreP64 {
814                    rt: fp_reg(),
815                    rt2: link_reg(),
816                    mem: PairAMode::SignedOffset {
817                        reg: regs::stack_reg(),
818                        simm7: SImm7Scaled::maybe_from_i64(0, types::I64).unwrap(),
819                    },
820                    flags: MemFlagsData::trusted(),
821                });
822
823                // Keep the frame pointer in sync
824                insts.push(Self::gen_move(
825                    regs::writable_fp_reg(),
826                    regs::stack_reg(),
827                    types::I64,
828                ));
829            }
830        }
831
832        if flags.unwind_info() && setup_frame {
833            // The *unwind* frame (but not the actual frame) starts at the
834            // clobbers, just below the saved FP/LR pair.
835            insts.push(Inst::Unwind {
836                inst: UnwindInst::DefineNewFrame {
837                    offset_downward_to_clobbers: frame_layout.clobber_size,
838                    offset_upward_to_caller_sp: frame_layout.setup_area_size,
839                },
840            });
841        }
842
843        // We use pre-indexed addressing modes here, rather than the possibly
844        // more efficient "subtract sp once then used fixed offsets" scheme,
845        // because (i) we cannot necessarily guarantee that the offset of a
846        // clobber-save slot will be within a SImm7Scaled (+504-byte) offset
847        // range of the whole frame including other slots, it is more complex to
848        // conditionally generate a two-stage SP adjustment (clobbers then fixed
849        // frame) otherwise, and generally we just want to maintain simplicity
850        // here for maintainability.  Because clobbers are at the top of the
851        // frame, just below FP, all that is necessary is to use the pre-indexed
852        // "push" `[sp, #-16]!` addressing mode.
853        //
854        // `frame_offset` tracks offset above start-of-clobbers for unwind-info
855        // purposes.
856        let mut clobber_offset = frame_layout.clobber_size;
857        let clobber_offset_change = 16;
858        let iter = clobbered_int.chunks_exact(2);
859
860        if let [rd] = iter.remainder() {
861            let rd: Reg = rd.to_reg().into();
862
863            debug_assert_eq!(rd.class(), RegClass::Int);
864            // str rd, [sp, #-16]!
865            insts.push(Inst::Store64 {
866                rd,
867                mem: AMode::SPPreIndexed {
868                    simm9: SImm9::maybe_from_i64(-clobber_offset_change).unwrap(),
869                },
870                flags: MemFlagsData::trusted(),
871            });
872
873            if flags.unwind_info() {
874                clobber_offset -= clobber_offset_change as u32;
875                insts.push(Inst::Unwind {
876                    inst: UnwindInst::SaveReg {
877                        clobber_offset,
878                        reg: rd.to_real_reg().unwrap(),
879                    },
880                });
881            }
882        }
883
884        let mut iter = iter.rev();
885
886        while let Some([rt, rt2]) = iter.next() {
887            // .to_reg().into(): Writable<RealReg> --> RealReg --> Reg
888            let rt: Reg = rt.to_reg().into();
889            let rt2: Reg = rt2.to_reg().into();
890
891            debug_assert!(rt.class() == RegClass::Int);
892            debug_assert!(rt2.class() == RegClass::Int);
893
894            // stp rt, rt2, [sp, #-16]!
895            insts.push(Inst::StoreP64 {
896                rt,
897                rt2,
898                mem: PairAMode::SPPreIndexed {
899                    simm7: SImm7Scaled::maybe_from_i64(-clobber_offset_change, types::I64).unwrap(),
900                },
901                flags: MemFlagsData::trusted(),
902            });
903
904            if flags.unwind_info() {
905                clobber_offset -= clobber_offset_change as u32;
906                insts.push(Inst::Unwind {
907                    inst: UnwindInst::SaveReg {
908                        clobber_offset,
909                        reg: rt.to_real_reg().unwrap(),
910                    },
911                });
912                insts.push(Inst::Unwind {
913                    inst: UnwindInst::SaveReg {
914                        clobber_offset: clobber_offset + (clobber_offset_change / 2) as u32,
915                        reg: rt2.to_real_reg().unwrap(),
916                    },
917                });
918            }
919        }
920
921        if call_conv == isa::CallConv::PreserveAll {
922            // Store full vector registers in PreserveAll convention.
923            for reg in clobbered_vec.iter().rev() {
924                let inst = Inst::FpuStore128 {
925                    rd: reg.to_reg().into(),
926                    mem: AMode::SPPreIndexed {
927                        simm9: SImm9::maybe_from_i64(-clobber_offset_change).unwrap(),
928                    },
929                    flags: MemFlagsData::trusted(),
930                };
931                insts.push(inst);
932                // N.B.: no unwind info: we don't have a way to
933                // represent "full register" anyway.
934            }
935        } else {
936            let store_vec_reg_half = |rd| Inst::FpuStore64 {
937                rd,
938                mem: AMode::SPPreIndexed {
939                    simm9: SImm9::maybe_from_i64(-clobber_offset_change).unwrap(),
940                },
941                flags: MemFlagsData::trusted(),
942            };
943            let iter = clobbered_vec.chunks_exact(2);
944
945            if let [rd] = iter.remainder() {
946                let rd: Reg = rd.to_reg().into();
947
948                debug_assert_eq!(rd.class(), RegClass::Float);
949                insts.push(store_vec_reg_half(rd));
950
951                if flags.unwind_info() {
952                    clobber_offset -= clobber_offset_change as u32;
953                    insts.push(Inst::Unwind {
954                        inst: UnwindInst::SaveReg {
955                            clobber_offset,
956                            reg: rd.to_real_reg().unwrap(),
957                        },
958                    });
959                }
960            }
961
962            let store_vec_reg_half_pair = |rt, rt2| {
963                let clobber_offset_change = 16;
964
965                (
966                    Inst::FpuStoreP64 {
967                        rt,
968                        rt2,
969                        mem: PairAMode::SPPreIndexed {
970                            simm7: SImm7Scaled::maybe_from_i64(-clobber_offset_change, F64)
971                                .unwrap(),
972                        },
973                        flags: MemFlagsData::trusted(),
974                    },
975                    clobber_offset_change as u32,
976                )
977            };
978            let mut iter = iter.rev();
979
980            while let Some([rt, rt2]) = iter.next() {
981                let rt: Reg = rt.to_reg().into();
982                let rt2: Reg = rt2.to_reg().into();
983
984                debug_assert_eq!(rt.class(), RegClass::Float);
985                debug_assert_eq!(rt2.class(), RegClass::Float);
986
987                let (inst, clobber_offset_change) = store_vec_reg_half_pair(rt, rt2);
988
989                insts.push(inst);
990
991                if flags.unwind_info() {
992                    clobber_offset -= clobber_offset_change;
993                    insts.push(Inst::Unwind {
994                        inst: UnwindInst::SaveReg {
995                            clobber_offset,
996                            reg: rt.to_real_reg().unwrap(),
997                        },
998                    });
999                    insts.push(Inst::Unwind {
1000                        inst: UnwindInst::SaveReg {
1001                            clobber_offset: clobber_offset + clobber_offset_change / 2,
1002                            reg: rt2.to_real_reg().unwrap(),
1003                        },
1004                    });
1005                }
1006            }
1007        }
1008
1009        // Allocate the fixed frame below the clobbers if necessary.
1010        let stack_size = frame_layout.fixed_frame_storage_size + frame_layout.outgoing_args_size;
1011        if stack_size > 0 {
1012            insts.extend(Self::gen_sp_reg_adjust(-(stack_size as i32)));
1013            if flags.unwind_info() {
1014                insts.push(Inst::Unwind {
1015                    inst: UnwindInst::StackAlloc { size: stack_size },
1016                });
1017            }
1018        }
1019
1020        insts
1021    }
1022
1023    fn gen_clobber_restore(
1024        call_conv: isa::CallConv,
1025        _flags: &settings::Flags,
1026        frame_layout: &FrameLayout,
1027    ) -> SmallVec<[Inst; 16]> {
1028        let mut insts = SmallVec::new();
1029        let (clobbered_int, clobbered_vec) = frame_layout.clobbered_callee_saves_by_class();
1030
1031        // Free the fixed frame if necessary.
1032        let stack_size = frame_layout.fixed_frame_storage_size + frame_layout.outgoing_args_size;
1033        if stack_size > 0 {
1034            insts.extend(Self::gen_sp_reg_adjust(stack_size as i32));
1035        }
1036
1037        if call_conv == isa::CallConv::PreserveAll {
1038            for reg in clobbered_vec.iter() {
1039                let inst = Inst::FpuLoad128 {
1040                    rd: reg.map(|r| r.into()),
1041                    mem: AMode::SPPostIndexed {
1042                        simm9: SImm9::maybe_from_i64(16).unwrap(),
1043                    },
1044                    flags: MemFlagsData::trusted(),
1045                };
1046                insts.push(inst);
1047                // N.B.: no unwind info; we don't have a way to
1048                // represent "full vector register saved" anyway.
1049            }
1050        } else {
1051            let load_vec_reg_half = |rd| Inst::FpuLoad64 {
1052                rd,
1053                mem: AMode::SPPostIndexed {
1054                    simm9: SImm9::maybe_from_i64(16).unwrap(),
1055                },
1056                flags: MemFlagsData::trusted(),
1057            };
1058            let load_vec_reg_half_pair = |rt, rt2| Inst::FpuLoadP64 {
1059                rt,
1060                rt2,
1061                mem: PairAMode::SPPostIndexed {
1062                    simm7: SImm7Scaled::maybe_from_i64(16, F64).unwrap(),
1063                },
1064                flags: MemFlagsData::trusted(),
1065            };
1066
1067            let mut iter = clobbered_vec.chunks_exact(2);
1068
1069            while let Some([rt, rt2]) = iter.next() {
1070                let rt: Writable<Reg> = rt.map(|r| r.into());
1071                let rt2: Writable<Reg> = rt2.map(|r| r.into());
1072
1073                debug_assert_eq!(rt.to_reg().class(), RegClass::Float);
1074                debug_assert_eq!(rt2.to_reg().class(), RegClass::Float);
1075                insts.push(load_vec_reg_half_pair(rt, rt2));
1076            }
1077
1078            debug_assert!(iter.remainder().len() <= 1);
1079
1080            if let [rd] = iter.remainder() {
1081                let rd: Writable<Reg> = rd.map(|r| r.into());
1082
1083                debug_assert_eq!(rd.to_reg().class(), RegClass::Float);
1084                insts.push(load_vec_reg_half(rd));
1085            }
1086        }
1087
1088        let mut iter = clobbered_int.chunks_exact(2);
1089
1090        while let Some([rt, rt2]) = iter.next() {
1091            let rt: Writable<Reg> = rt.map(|r| r.into());
1092            let rt2: Writable<Reg> = rt2.map(|r| r.into());
1093
1094            debug_assert_eq!(rt.to_reg().class(), RegClass::Int);
1095            debug_assert_eq!(rt2.to_reg().class(), RegClass::Int);
1096            // ldp rt, rt2, [sp], #16
1097            insts.push(Inst::LoadP64 {
1098                rt,
1099                rt2,
1100                mem: PairAMode::SPPostIndexed {
1101                    simm7: SImm7Scaled::maybe_from_i64(16, I64).unwrap(),
1102                },
1103                flags: MemFlagsData::trusted(),
1104            });
1105        }
1106
1107        debug_assert!(iter.remainder().len() <= 1);
1108
1109        if let [rd] = iter.remainder() {
1110            let rd: Writable<Reg> = rd.map(|r| r.into());
1111
1112            debug_assert_eq!(rd.to_reg().class(), RegClass::Int);
1113            // ldr rd, [sp], #16
1114            insts.push(Inst::ULoad64 {
1115                rd,
1116                mem: AMode::SPPostIndexed {
1117                    simm9: SImm9::maybe_from_i64(16).unwrap(),
1118                },
1119                flags: MemFlagsData::trusted(),
1120            });
1121        }
1122
1123        insts
1124    }
1125
1126    fn gen_memcpy<F: FnMut(Type) -> Writable<Reg>>(
1127        call_conv: isa::CallConv,
1128        dst: Reg,
1129        src: Reg,
1130        size: usize,
1131        mut alloc_tmp: F,
1132    ) -> SmallVec<[Self::I; 8]> {
1133        let mut insts = SmallVec::new();
1134        let arg0 = writable_xreg(0);
1135        let arg1 = writable_xreg(1);
1136        let arg2 = writable_xreg(2);
1137        let tmp = alloc_tmp(Self::word_type());
1138        insts.extend(Inst::load_constant(tmp, size as u64));
1139        insts.push(Inst::Call {
1140            info: Box::new(CallInfo {
1141                dest: ExternalName::LibCall(LibCall::Memcpy),
1142                uses: smallvec![
1143                    CallArgPair {
1144                        vreg: dst,
1145                        preg: arg0.to_reg()
1146                    },
1147                    CallArgPair {
1148                        vreg: src,
1149                        preg: arg1.to_reg()
1150                    },
1151                    CallArgPair {
1152                        vreg: tmp.to_reg(),
1153                        preg: arg2.to_reg()
1154                    }
1155                ],
1156                defs: smallvec![],
1157                clobbers: Self::get_regs_clobbered_by_call(call_conv, false),
1158                caller_conv: call_conv,
1159                callee_conv: call_conv,
1160                callee_pop_size: 0,
1161                try_call_info: None,
1162                patchable: false,
1163            }),
1164        });
1165        insts
1166    }
1167
1168    fn get_number_of_spillslots_for_value(
1169        rc: RegClass,
1170        vector_size: u32,
1171        _isa_flags: &Self::F,
1172    ) -> u32 {
1173        assert_eq!(vector_size % 8, 0);
1174        // We allocate in terms of 8-byte slots.
1175        match rc {
1176            RegClass::Int => 1,
1177            RegClass::Float => vector_size / 8,
1178            RegClass::Vector => unreachable!(),
1179        }
1180    }
1181
1182    fn get_machine_env(flags: &settings::Flags, _call_conv: isa::CallConv) -> &MachineEnv {
1183        if flags.enable_pinned_reg() {
1184            static MACHINE_ENV: MachineEnv = create_reg_env(true);
1185            &MACHINE_ENV
1186        } else {
1187            static MACHINE_ENV: MachineEnv = create_reg_env(false);
1188            &MACHINE_ENV
1189        }
1190    }
1191
1192    fn get_regs_clobbered_by_call(call_conv: isa::CallConv, is_exception: bool) -> PRegSet {
1193        match (call_conv, is_exception) {
1194            (isa::CallConv::Tail, true) => ALL_CLOBBERS,
1195            (isa::CallConv::Winch, true) => ALL_CLOBBERS,
1196            (isa::CallConv::Winch, false) => WINCH_CLOBBERS,
1197            // Note that "PreserveAll" actually preserves nothing at
1198            // the callsite if used for a `try_call`, because the
1199            // unwinder ABI for `try_call`s is still "no clobbered
1200            // register restores" for this ABI (so as to work with
1201            // Wasmtime).
1202            (isa::CallConv::PreserveAll, true) => ALL_CLOBBERS,
1203            (isa::CallConv::SystemV, _) => DEFAULT_AAPCS_CLOBBERS,
1204            // On Mach-O, the compact unwind info properly describes how are callee-save
1205            // registers restored during unwinding.
1206            (isa::CallConv::AppleAarch64, true) => DEFAULT_AAPCS_CLOBBERS,
1207            (isa::CallConv::PreserveAll, _) => NO_CLOBBERS,
1208            (_, false) => DEFAULT_AAPCS_CLOBBERS,
1209            (_, true) => panic!("unimplemented clobbers for exn abi of {call_conv:?}"),
1210        }
1211    }
1212
1213    fn get_ext_mode(
1214        call_conv: isa::CallConv,
1215        specified: ir::ArgumentExtension,
1216        location: ABIArgLocation,
1217    ) -> ir::ArgumentExtension {
1218        // Apple's AArch64 ABI only requires the caller to sign/zero-extend
1219        // arguments in registers; stack-passed arguments use their natural
1220        // (possibly sub-word) size and are not extended. See "Pass arguments
1221        // to functions correctly" in:
1222        // https://developer.apple.com/documentation/xcode/writing-arm64-code-for-apple-platforms
1223        if call_conv == isa::CallConv::AppleAarch64 && location == ABIArgLocation::Reg {
1224            specified
1225        } else {
1226            ir::ArgumentExtension::None
1227        }
1228    }
1229
1230    fn compute_frame_layout(
1231        call_conv: isa::CallConv,
1232        flags: &settings::Flags,
1233        sig: &Signature,
1234        regs: &[Writable<RealReg>],
1235        function_calls: FunctionCalls,
1236        incoming_args_size: u32,
1237        tail_args_size: u32,
1238        stackslots_size: u32,
1239        fixed_frame_storage_size: u32,
1240        outgoing_args_size: u32,
1241    ) -> FrameLayout {
1242        let mut regs: Vec<Writable<RealReg>> = regs
1243            .iter()
1244            .cloned()
1245            .filter(|r| {
1246                is_reg_saved_in_prologue(call_conv, flags.enable_pinned_reg(), sig, r.to_reg())
1247            })
1248            .collect();
1249
1250        if call_conv == isa::CallConv::AppleAarch64 && flags.enable_compact_unwind_abi() {
1251            add_macho_compact_unwind_paired_regs(&mut regs);
1252            // For Mach-O compact unwind, these pushes/pops must be emitted in
1253            // the fixed expected order. The encoding specifies only which
1254            // callee-saved register pairs are preserved; the order is mandatory.
1255            regs.sort_unstable_by_key(|r| {
1256                let reg = r.to_reg();
1257                (reg.class(), Reverse(reg.hw_enc()))
1258            });
1259        } else {
1260            // Sort registers for deterministic code output. We can do an unstable
1261            // sort because the registers will be unique (there are no dups).
1262            regs.sort_unstable();
1263        }
1264
1265        // Compute clobber size.
1266        let clobber_size = compute_clobber_size(call_conv, &regs);
1267
1268        let needs_linkage_frame = flags.preserve_frame_pointers()
1269                // The function arguments that are passed on the stack are addressed
1270                // relative to the Frame Pointer.
1271                || incoming_args_size > 0
1272                || tail_args_size > incoming_args_size
1273                || clobber_size > 0
1274                || fixed_frame_storage_size > 0
1275                || outgoing_args_size > 0;
1276
1277        // Compute linkage frame size.
1278        let setup_area_size = if needs_linkage_frame || function_calls == FunctionCalls::Regular {
1279            16 // FP, LR
1280        } else {
1281            0
1282        };
1283
1284        // Return FrameLayout structure.
1285        FrameLayout {
1286            word_bytes: 8,
1287            incoming_args_size,
1288            tail_args_size,
1289            setup_area_size,
1290            clobber_size,
1291            fixed_frame_storage_size,
1292            stackslots_size,
1293            outgoing_args_size,
1294            clobbered_callee_saves: regs,
1295            function_calls,
1296        }
1297    }
1298
1299    fn retval_temp_reg(_call_conv_of_callee: isa::CallConv) -> Writable<Reg> {
1300        // Use x9 as a temp if needed: clobbered, not a
1301        // retval.
1302        regs::writable_xreg(9)
1303    }
1304
1305    fn exception_payload_regs(call_conv: isa::CallConv) -> &'static [Reg] {
1306        const PAYLOAD_REGS: &'static [Reg] = &[regs::xreg(0), regs::xreg(1)];
1307        match call_conv {
1308            isa::CallConv::SystemV
1309            | isa::CallConv::Tail
1310            | isa::CallConv::PreserveAll
1311            | isa::CallConv::AppleAarch64 => PAYLOAD_REGS,
1312            _ => &[],
1313        }
1314    }
1315}
1316
1317impl AArch64MachineDeps {
1318    fn gen_probestack_unroll(insts: &mut SmallInstVec<Inst>, guard_size: u32, probe_count: u32) {
1319        // When manually unrolling adjust the stack pointer and then write a zero
1320        // to the stack at that offset. This generates something like
1321        // `sub sp, sp, #1, lsl #12` followed by `stur wzr, [sp]`.
1322        //
1323        // We do this because valgrind expects us to never write beyond the stack
1324        // pointer and associated redzone.
1325        // See: https://github.com/bytecodealliance/wasmtime/issues/7454
1326        for _ in 0..probe_count {
1327            insts.extend(Self::gen_sp_reg_adjust(-(guard_size as i32)));
1328
1329            insts.push(Inst::gen_store(
1330                AMode::SPOffset { off: 0 },
1331                zero_reg(),
1332                I32,
1333                MemFlagsData::trusted(),
1334            ));
1335        }
1336
1337        // Restore the stack pointer to its original value
1338        insts.extend(Self::gen_sp_reg_adjust((guard_size * probe_count) as i32));
1339    }
1340
1341    fn gen_probestack_loop(insts: &mut SmallInstVec<Inst>, frame_size: u32, guard_size: u32) {
1342        // The non-unrolled version uses two temporary registers. The
1343        // `start` contains the current offset from sp and counts downwards
1344        // during the loop by increments of `guard_size`. The `end` is
1345        // the size of the frame and where we stop.
1346        //
1347        // Note that this emission is all post-regalloc so it should be ok
1348        // to use the temporary registers here as input/output as the loop
1349        // itself is not allowed to use the registers.
1350        let start = writable_spilltmp_reg();
1351        let end = writable_tmp2_reg();
1352        // `gen_inline_probestack` is called after regalloc2, so it's acceptable to reuse
1353        // `start` and `end` as temporaries in load_constant.
1354        insts.extend(Inst::load_constant(start, 0));
1355        insts.extend(Inst::load_constant(end, frame_size.into()));
1356        insts.push(Inst::StackProbeLoop {
1357            start,
1358            end: end.to_reg(),
1359            step: Imm12::maybe_from_u64(guard_size.into()).unwrap(),
1360        });
1361    }
1362
1363    pub fn select_api_key(
1364        isa_flags: &aarch64_settings::Flags,
1365        call_conv: isa::CallConv,
1366        setup_frame: bool,
1367    ) -> Option<APIKey> {
1368        if isa_flags.sign_return_address() && (setup_frame || isa_flags.sign_return_address_all()) {
1369            // The `tail` calling convention uses a zero modifier rather than SP
1370            // because tail calls may happen with a different stack pointer than
1371            // when the function was entered, meaning that it won't be the same when
1372            // the return address is decrypted.
1373            Some(if isa_flags.sign_return_address_with_bkey() {
1374                match call_conv {
1375                    isa::CallConv::Tail => APIKey::BZ,
1376                    _ => APIKey::BSP,
1377                }
1378            } else {
1379                match call_conv {
1380                    isa::CallConv::Tail => APIKey::AZ,
1381                    _ => APIKey::ASP,
1382                }
1383            })
1384        } else {
1385            None
1386        }
1387    }
1388}
1389
1390/// Is the given register saved in the prologue if clobbered, i.e., is it a
1391/// callee-save?
1392fn is_reg_saved_in_prologue(
1393    call_conv: isa::CallConv,
1394    enable_pinned_reg: bool,
1395    sig: &Signature,
1396    r: RealReg,
1397) -> bool {
1398    if call_conv == isa::CallConv::PreserveAll {
1399        return true;
1400    }
1401
1402    // FIXME: We need to inspect whether a function is returning Z or P regs too.
1403    let save_z_regs = sig
1404        .params
1405        .iter()
1406        .filter(|p| p.value_type.is_dynamic_vector())
1407        .count()
1408        != 0;
1409
1410    match r.class() {
1411        RegClass::Int => {
1412            // x19 - x28 inclusive are callee-saves.
1413            // However, x21 is the pinned reg if `enable_pinned_reg`
1414            // is set, and is implicitly globally-allocated, hence not
1415            // callee-saved in prologues.
1416            if enable_pinned_reg && r.hw_enc() == PINNED_REG {
1417                false
1418            } else {
1419                r.hw_enc() >= 19 && r.hw_enc() <= 28
1420            }
1421        }
1422        RegClass::Float => {
1423            // If a subroutine takes at least one argument in scalable vector registers
1424            // or scalable predicate registers, or if it is a function that returns
1425            // results in such registers, it must ensure that the entire contents of
1426            // z8-z23 are preserved across the call. In other cases it need only
1427            // preserve the low 64 bits of z8-z15.
1428            if save_z_regs {
1429                r.hw_enc() >= 8 && r.hw_enc() <= 23
1430            } else {
1431                // v8 - v15 inclusive are callee-saves.
1432                r.hw_enc() >= 8 && r.hw_enc() <= 15
1433            }
1434        }
1435        RegClass::Vector => unreachable!(),
1436    }
1437}
1438
1439const fn default_aapcs_clobbers() -> PRegSet {
1440    PRegSet::empty()
1441        // x0 - x17 inclusive are caller-saves.
1442        .with(xreg_preg(0))
1443        .with(xreg_preg(1))
1444        .with(xreg_preg(2))
1445        .with(xreg_preg(3))
1446        .with(xreg_preg(4))
1447        .with(xreg_preg(5))
1448        .with(xreg_preg(6))
1449        .with(xreg_preg(7))
1450        .with(xreg_preg(8))
1451        .with(xreg_preg(9))
1452        .with(xreg_preg(10))
1453        .with(xreg_preg(11))
1454        .with(xreg_preg(12))
1455        .with(xreg_preg(13))
1456        .with(xreg_preg(14))
1457        .with(xreg_preg(15))
1458        .with(xreg_preg(16))
1459        .with(xreg_preg(17))
1460        // v0 - v7 inclusive and v16 - v31 inclusive are
1461        // caller-saves. The upper 64 bits of v8 - v15 inclusive are
1462        // also caller-saves.  However, because we cannot currently
1463        // represent partial registers to regalloc2, we indicate here
1464        // that every vector register is caller-save. Because this
1465        // function is used at *callsites*, approximating in this
1466        // direction (save more than necessary) is conservative and
1467        // thus safe.
1468        //
1469        // Note that we exclude clobbers from a call instruction when
1470        // a call instruction's callee has the same ABI as the caller
1471        // (the current function body); this is safe (anything
1472        // clobbered by callee can be clobbered by caller as well) and
1473        // avoids unnecessary saves of v8-v15 in the prologue even
1474        // though we include them as defs here.
1475        .with(vreg_preg(0))
1476        .with(vreg_preg(1))
1477        .with(vreg_preg(2))
1478        .with(vreg_preg(3))
1479        .with(vreg_preg(4))
1480        .with(vreg_preg(5))
1481        .with(vreg_preg(6))
1482        .with(vreg_preg(7))
1483        .with(vreg_preg(8))
1484        .with(vreg_preg(9))
1485        .with(vreg_preg(10))
1486        .with(vreg_preg(11))
1487        .with(vreg_preg(12))
1488        .with(vreg_preg(13))
1489        .with(vreg_preg(14))
1490        .with(vreg_preg(15))
1491        .with(vreg_preg(16))
1492        .with(vreg_preg(17))
1493        .with(vreg_preg(18))
1494        .with(vreg_preg(19))
1495        .with(vreg_preg(20))
1496        .with(vreg_preg(21))
1497        .with(vreg_preg(22))
1498        .with(vreg_preg(23))
1499        .with(vreg_preg(24))
1500        .with(vreg_preg(25))
1501        .with(vreg_preg(26))
1502        .with(vreg_preg(27))
1503        .with(vreg_preg(28))
1504        .with(vreg_preg(29))
1505        .with(vreg_preg(30))
1506        .with(vreg_preg(31))
1507}
1508
1509const fn winch_clobbers() -> PRegSet {
1510    PRegSet::empty()
1511        .with(xreg_preg(0))
1512        .with(xreg_preg(1))
1513        .with(xreg_preg(2))
1514        .with(xreg_preg(3))
1515        .with(xreg_preg(4))
1516        .with(xreg_preg(5))
1517        .with(xreg_preg(6))
1518        .with(xreg_preg(7))
1519        .with(xreg_preg(8))
1520        .with(xreg_preg(9))
1521        .with(xreg_preg(10))
1522        .with(xreg_preg(11))
1523        .with(xreg_preg(12))
1524        .with(xreg_preg(13))
1525        .with(xreg_preg(14))
1526        .with(xreg_preg(15))
1527        .with(xreg_preg(16))
1528        .with(xreg_preg(17))
1529        // x18 is used to carry platform state and is not allocatable by Winch.
1530        //
1531        // x19 - x27 are considered caller-saved in Winch's calling convention.
1532        .with(xreg_preg(19))
1533        .with(xreg_preg(20))
1534        .with(xreg_preg(21))
1535        .with(xreg_preg(22))
1536        .with(xreg_preg(23))
1537        .with(xreg_preg(24))
1538        .with(xreg_preg(25))
1539        .with(xreg_preg(26))
1540        .with(xreg_preg(27))
1541        // x28 is used as the shadow stack pointer and is considered
1542        // callee-saved.
1543        //
1544        // All vregs are considered caller-saved.
1545        .with(vreg_preg(0))
1546        .with(vreg_preg(1))
1547        .with(vreg_preg(2))
1548        .with(vreg_preg(3))
1549        .with(vreg_preg(4))
1550        .with(vreg_preg(5))
1551        .with(vreg_preg(6))
1552        .with(vreg_preg(7))
1553        .with(vreg_preg(8))
1554        .with(vreg_preg(9))
1555        .with(vreg_preg(10))
1556        .with(vreg_preg(11))
1557        .with(vreg_preg(12))
1558        .with(vreg_preg(13))
1559        .with(vreg_preg(14))
1560        .with(vreg_preg(15))
1561        .with(vreg_preg(16))
1562        .with(vreg_preg(17))
1563        .with(vreg_preg(18))
1564        .with(vreg_preg(19))
1565        .with(vreg_preg(20))
1566        .with(vreg_preg(21))
1567        .with(vreg_preg(22))
1568        .with(vreg_preg(23))
1569        .with(vreg_preg(24))
1570        .with(vreg_preg(25))
1571        .with(vreg_preg(26))
1572        .with(vreg_preg(27))
1573        .with(vreg_preg(28))
1574        .with(vreg_preg(29))
1575        .with(vreg_preg(30))
1576        .with(vreg_preg(31))
1577}
1578
1579const fn all_clobbers() -> PRegSet {
1580    PRegSet::empty()
1581        // integer registers: x0 to x28 inclusive. (x29 is FP, x30 is
1582        // LR, x31 is SP/ZR.)
1583        .with(xreg_preg(0))
1584        .with(xreg_preg(1))
1585        .with(xreg_preg(2))
1586        .with(xreg_preg(3))
1587        .with(xreg_preg(4))
1588        .with(xreg_preg(5))
1589        .with(xreg_preg(6))
1590        .with(xreg_preg(7))
1591        .with(xreg_preg(8))
1592        .with(xreg_preg(9))
1593        .with(xreg_preg(10))
1594        .with(xreg_preg(11))
1595        .with(xreg_preg(12))
1596        .with(xreg_preg(13))
1597        .with(xreg_preg(14))
1598        .with(xreg_preg(15))
1599        .with(xreg_preg(16))
1600        .with(xreg_preg(17))
1601        .with(xreg_preg(18))
1602        .with(xreg_preg(19))
1603        .with(xreg_preg(20))
1604        .with(xreg_preg(21))
1605        .with(xreg_preg(22))
1606        .with(xreg_preg(23))
1607        .with(xreg_preg(24))
1608        .with(xreg_preg(25))
1609        .with(xreg_preg(26))
1610        .with(xreg_preg(27))
1611        .with(xreg_preg(28))
1612        // vector registers: v0 to v31 inclusive.
1613        .with(vreg_preg(0))
1614        .with(vreg_preg(1))
1615        .with(vreg_preg(2))
1616        .with(vreg_preg(3))
1617        .with(vreg_preg(4))
1618        .with(vreg_preg(5))
1619        .with(vreg_preg(6))
1620        .with(vreg_preg(7))
1621        .with(vreg_preg(8))
1622        .with(vreg_preg(9))
1623        .with(vreg_preg(10))
1624        .with(vreg_preg(11))
1625        .with(vreg_preg(12))
1626        .with(vreg_preg(13))
1627        .with(vreg_preg(14))
1628        .with(vreg_preg(15))
1629        .with(vreg_preg(16))
1630        .with(vreg_preg(17))
1631        .with(vreg_preg(18))
1632        .with(vreg_preg(19))
1633        .with(vreg_preg(20))
1634        .with(vreg_preg(21))
1635        .with(vreg_preg(22))
1636        .with(vreg_preg(23))
1637        .with(vreg_preg(24))
1638        .with(vreg_preg(25))
1639        .with(vreg_preg(26))
1640        .with(vreg_preg(27))
1641        .with(vreg_preg(28))
1642        .with(vreg_preg(29))
1643        .with(vreg_preg(30))
1644        .with(vreg_preg(31))
1645}
1646
1647const DEFAULT_AAPCS_CLOBBERS: PRegSet = default_aapcs_clobbers();
1648const WINCH_CLOBBERS: PRegSet = winch_clobbers();
1649const ALL_CLOBBERS: PRegSet = all_clobbers();
1650const NO_CLOBBERS: PRegSet = PRegSet::empty();
1651
1652const fn create_reg_env(enable_pinned_reg: bool) -> MachineEnv {
1653    const fn preg(r: Reg) -> PReg {
1654        r.to_real_reg().unwrap().preg()
1655    }
1656
1657    let mut env = MachineEnv {
1658        preferred_regs_by_class: [
1659            PRegSet::empty()
1660                .with(preg(xreg(0)))
1661                .with(preg(xreg(1)))
1662                .with(preg(xreg(2)))
1663                .with(preg(xreg(3)))
1664                .with(preg(xreg(4)))
1665                .with(preg(xreg(5)))
1666                .with(preg(xreg(6)))
1667                .with(preg(xreg(7)))
1668                .with(preg(xreg(8)))
1669                .with(preg(xreg(9)))
1670                .with(preg(xreg(10)))
1671                .with(preg(xreg(11)))
1672                .with(preg(xreg(12)))
1673                .with(preg(xreg(13)))
1674                .with(preg(xreg(14)))
1675                .with(preg(xreg(15))),
1676            // x16 and x17 are spilltmp and tmp2 (see above).
1677            // x18 could be used by the platform to carry inter-procedural state;
1678            // conservatively assume so and make it not allocatable.
1679            // x19-28 are callee-saved and so not preferred.
1680            // x21 is the pinned register (if enabled) and not allocatable if so.
1681            // x29 is FP, x30 is LR, x31 is SP/ZR.
1682            PRegSet::empty()
1683                .with(preg(vreg(0)))
1684                .with(preg(vreg(1)))
1685                .with(preg(vreg(2)))
1686                .with(preg(vreg(3)))
1687                .with(preg(vreg(4)))
1688                .with(preg(vreg(5)))
1689                .with(preg(vreg(6)))
1690                .with(preg(vreg(7)))
1691                // v8-15 are callee-saved and so not preferred.
1692                .with(preg(vreg(16)))
1693                .with(preg(vreg(17)))
1694                .with(preg(vreg(18)))
1695                .with(preg(vreg(19)))
1696                .with(preg(vreg(20)))
1697                .with(preg(vreg(21)))
1698                .with(preg(vreg(22)))
1699                .with(preg(vreg(23)))
1700                .with(preg(vreg(24)))
1701                .with(preg(vreg(25)))
1702                .with(preg(vreg(26)))
1703                .with(preg(vreg(27)))
1704                .with(preg(vreg(28)))
1705                .with(preg(vreg(29)))
1706                .with(preg(vreg(30)))
1707                .with(preg(vreg(31))),
1708            // Vector Regclass is unused
1709            PRegSet::empty(),
1710        ],
1711        non_preferred_regs_by_class: [
1712            PRegSet::empty()
1713                .with(preg(xreg(19)))
1714                .with(preg(xreg(20)))
1715                // x21 is pinned reg if enabled; we add to this list below if not.
1716                .with(preg(xreg(22)))
1717                .with(preg(xreg(23)))
1718                .with(preg(xreg(24)))
1719                .with(preg(xreg(25)))
1720                .with(preg(xreg(26)))
1721                .with(preg(xreg(27)))
1722                .with(preg(xreg(28))),
1723            PRegSet::empty()
1724                .with(preg(vreg(8)))
1725                .with(preg(vreg(9)))
1726                .with(preg(vreg(10)))
1727                .with(preg(vreg(11)))
1728                .with(preg(vreg(12)))
1729                .with(preg(vreg(13)))
1730                .with(preg(vreg(14)))
1731                .with(preg(vreg(15))),
1732            // Vector Regclass is unused
1733            PRegSet::empty(),
1734        ],
1735        fixed_stack_slots: vec![],
1736        scratch_by_class: [None, None, None],
1737    };
1738
1739    if !enable_pinned_reg {
1740        debug_assert!(PINNED_REG == 21);
1741        env.non_preferred_regs_by_class[0].add(preg(xreg(PINNED_REG)));
1742    }
1743
1744    env
1745}