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