Skip to main content

cranelift_codegen/isa/x64/
abi.rs

1//! Implementation of the standard x64 ABI.
2
3use crate::CodegenResult;
4use crate::ir::MemFlagsData;
5use crate::ir::{self, LibCall, Signature, TrapCode, types};
6use crate::ir::{ExternalName, types::*};
7use crate::isa;
8use crate::isa::winch;
9use crate::isa::{CallConv, unwind::UnwindInst, x64::inst::*, x64::settings as x64_settings};
10use crate::machinst::*;
11use crate::settings;
12use alloc::borrow::ToOwned;
13use alloc::boxed::Box;
14use alloc::vec::Vec;
15use args::*;
16use cranelift_assembler_x64 as asm;
17use regalloc2::{MachineEnv, PReg, PRegSet};
18use smallvec::{SmallVec, smallvec};
19
20/// Support for the x64 ABI from the callee side (within a function body).
21pub(crate) type X64Callee = Callee<X64ABIMachineSpec>;
22
23/// Implementation of ABI primitives for x64.
24pub struct X64ABIMachineSpec;
25
26impl X64ABIMachineSpec {
27    fn gen_probestack_unroll(insts: &mut SmallInstVec<Inst>, guard_size: u32, probe_count: u32) {
28        insts.reserve(probe_count as usize);
29        for _ in 0..probe_count {
30            // "Allocate" stack space for the probe by decrementing the stack pointer before
31            // the write. This is required to make valgrind happy.
32            // See: https://github.com/bytecodealliance/wasmtime/issues/7454
33            insts.extend(Self::gen_sp_reg_adjust(-(guard_size as i32)));
34
35            // Touch the current page by storing an immediate zero.
36            // mov  [rsp], 0
37            insts.push(Inst::External {
38                inst: asm::inst::movl_mi::new(Amode::imm_reg(0, regs::rsp()), 0i32.cast_unsigned())
39                    .into(),
40            });
41        }
42
43        // Restore the stack pointer to its original value
44        insts.extend(Self::gen_sp_reg_adjust((guard_size * probe_count) as i32));
45    }
46
47    fn gen_probestack_loop(
48        insts: &mut SmallInstVec<Inst>,
49        _call_conv: isa::CallConv,
50        frame_size: u32,
51        guard_size: u32,
52    ) {
53        // We have to use a caller-saved register since clobbering only
54        // happens after stack probing.
55        // `r11` is caller saved on both Fastcall and SystemV, and not used
56        // for argument passing, so it's pretty much free. It is also not
57        // used by the stacklimit mechanism.
58        let tmp = regs::r11();
59        debug_assert!({
60            let real_reg = tmp.to_real_reg().unwrap();
61            !is_callee_save_systemv(real_reg, false) && !is_callee_save_fastcall(real_reg, false)
62        });
63
64        insts.push(Inst::StackProbeLoop {
65            tmp: Writable::from_reg(tmp),
66            frame_size,
67            guard_size,
68        });
69    }
70}
71
72impl IsaFlags for x64_settings::Flags {}
73
74impl ABIMachineSpec for X64ABIMachineSpec {
75    type I = Inst;
76
77    type F = x64_settings::Flags;
78
79    /// This is the limit for the size of argument and return-value areas on the
80    /// stack. We place a reasonable limit here to avoid integer overflow issues
81    /// with 32-bit arithmetic: for now, 128 MB.
82    const STACK_ARG_RET_SIZE_LIMIT: u32 = 128 * 1024 * 1024;
83
84    fn word_bits() -> u32 {
85        64
86    }
87
88    /// Return required stack alignment in bytes.
89    fn stack_align(_call_conv: isa::CallConv) -> u32 {
90        16
91    }
92
93    fn compute_arg_locs(
94        call_conv: isa::CallConv,
95        flags: &settings::Flags,
96        params: &[ir::AbiParam],
97        args_or_rets: ArgsOrRets,
98        add_ret_area_ptr: bool,
99        mut args: ArgsAccumulator,
100    ) -> CodegenResult<(u32, Option<usize>)> {
101        let is_fastcall = call_conv == CallConv::WindowsFastcall;
102        let is_tail = call_conv == CallConv::Tail;
103
104        let mut next_gpr = 0;
105        let mut next_vreg = 0;
106        let mut next_stack: u32 = 0;
107        let mut next_param_idx = 0; // Fastcall cares about overall param index
108
109        if args_or_rets == ArgsOrRets::Args && is_fastcall {
110            // Fastcall always reserves 32 bytes of shadow space corresponding to
111            // the four initial in-arg parameters.
112            //
113            // (See:
114            // https://learn.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-170)
115            next_stack = 32;
116        }
117
118        let ret_area_ptr = if add_ret_area_ptr {
119            debug_assert_eq!(args_or_rets, ArgsOrRets::Args);
120            next_gpr += 1;
121            next_param_idx += 1;
122            // In the SystemV and WindowsFastcall ABIs, the return area pointer is the first
123            // argument. For the Tail and Winch ABIs we do the same for simplicity sake.
124            Some(ABIArg::reg(
125                get_intreg_for_arg(call_conv, 0, 0)
126                    .unwrap()
127                    .to_real_reg()
128                    .unwrap(),
129                types::I64,
130                ir::ArgumentExtension::None,
131                ir::ArgumentPurpose::Normal,
132            ))
133        } else {
134            None
135        };
136
137        // If any param uses extension, the winch calling convention will not pack its results
138        // on the stack and will instead align them to 8-byte boundaries the same way that all the
139        // other calling conventions do. This isn't consistent with Winch itself, but is fine as
140        // Winch only uses this calling convention via trampolines, and those trampolines don't add
141        // extension annotations. Additionally, handling extension attributes this way allows clif
142        // functions that use them with the Winch calling convention to interact successfully with
143        // testing infrastructure.
144        // The results are also not packed if any of the types are `f16`. This is to simplify the
145        // implementation of `Inst::load`/`Inst::store` (which would otherwise require multiple
146        // instructions), and doesn't affect Winch itself as Winch doesn't support `f16` at all.
147        let uses_extension = params.iter().any(|p| {
148            p.extension != ir::ArgumentExtension::None
149                || p.value_type == types::F16
150                || p.value_type == types::I8X2
151        });
152
153        for (ix, param) in params.iter().enumerate() {
154            let last_param = ix == params.len() - 1;
155
156            if let ir::ArgumentPurpose::StructArgument(size) = param.purpose {
157                if is_tail {
158                    return Err(crate::CodegenError::Unsupported(
159                        "struct arguments (`sarg`) in `tail` signatures".to_owned(),
160                    ));
161                }
162                let offset = next_stack as i64;
163                let size = size;
164                assert!(size % 8 == 0, "StructArgument size is not properly aligned");
165                next_stack += size;
166                args.push(ABIArg::StructArg {
167                    offset,
168                    size: size as u64,
169                    purpose: param.purpose,
170                });
171                continue;
172            }
173
174            // Find regclass(es) of the register(s) used to store a value of this type.
175            let (rcs, reg_tys) = Inst::rc_for_type(&param.value_type)?;
176
177            // Now assign ABIArgSlots for each register-sized part.
178            //
179            // Note that the handling of `i128` values is unique here:
180            //
181            // - If `enable_llvm_abi_extensions` is set in the flags, each
182            //   `i128` is split into two `i64`s and assigned exactly as if it
183            //   were two consecutive 64-bit args, except that if one of the
184            //   two halves is forced onto the stack, the other half is too.
185            //   This is consistent with LLVM's behavior, and is needed for
186            //   some uses of Cranelift (e.g., the rustc backend).
187            //
188            // - Otherwise, if the calling convention is Tail, we behave as in
189            //   the previous case, even if `enable_llvm_abi_extensions` is not
190            //   set in the flags: This is a custom calling convention defined
191            //   by Cranelift, LLVM doesn't know about it.
192            //
193            // - Otherwise, both SysV and Fastcall specify behavior (use of
194            //   vector register, a register pair, or passing by reference
195            //   depending on the case), but for simplicity, we will just panic if
196            //   an i128 type appears in a signature and the LLVM extensions flag
197            //   is not set.
198            //
199            // For examples of how rustc compiles i128 args and return values on
200            // both SysV and Fastcall platforms, see:
201            // https://godbolt.org/z/PhG3ob
202
203            if param.value_type.bits() > 64
204                && !(param.value_type.is_vector() || param.value_type.is_float())
205                && !flags.enable_llvm_abi_extensions()
206                && !is_tail
207            {
208                panic!(
209                    "i128 args/return values not supported unless LLVM ABI extensions are enabled"
210                );
211            }
212            // As MSVC doesn't support f16/f128 there is no standard way to pass/return them with
213            // the Windows ABI. LLVM passes/returns them in XMM registers.
214            if matches!(param.value_type, types::F16 | types::F128)
215                && is_fastcall
216                && !flags.enable_llvm_abi_extensions()
217            {
218                panic!(
219                    "f16/f128 args/return values not supported for windows_fastcall unless LLVM ABI extensions are enabled"
220                );
221            }
222
223            // Windows fastcall dictates that `__m128i` and `f128` parameters to
224            // a function are passed indirectly as pointers, so handle that as a
225            // special case before the loop below.
226            if (param.value_type.is_vector() || param.value_type.is_float())
227                && param.value_type.bits() >= 128
228                && args_or_rets == ArgsOrRets::Args
229                && is_fastcall
230            {
231                let pointer = match get_intreg_for_arg(call_conv, next_gpr, next_param_idx) {
232                    Some(reg) => {
233                        next_gpr += 1;
234                        ABIArgSlot::Reg {
235                            reg: reg.to_real_reg().unwrap(),
236                            ty: ir::types::I64,
237                            extension: ir::ArgumentExtension::None,
238                        }
239                    }
240
241                    None => {
242                        next_stack = align_to(next_stack, 8) + 8;
243                        ABIArgSlot::Stack {
244                            offset: (next_stack - 8) as i64,
245                            ty: ir::types::I64,
246                            extension: param.extension,
247                        }
248                    }
249                };
250                next_param_idx += 1;
251                args.push(ABIArg::ImplicitPtrArg {
252                    // NB: this is filled in after this loop
253                    offset: 0,
254                    pointer,
255                    ty: param.value_type,
256                    purpose: param.purpose,
257                });
258                continue;
259            }
260
261            // SystemV dictates that 128bit int parameters are always either
262            // passed in two registers or on the stack, so handle that as a
263            // special case before the loop below.
264            if param.value_type == types::I128
265                && args_or_rets == ArgsOrRets::Args
266                && call_conv == CallConv::SystemV
267            {
268                let mut slots = ABIArgSlotVec::new();
269                match (
270                    get_intreg_for_arg(CallConv::SystemV, next_gpr, next_param_idx),
271                    get_intreg_for_arg(CallConv::SystemV, next_gpr + 1, next_param_idx + 1),
272                ) {
273                    (Some(reg1), Some(reg2)) => {
274                        slots.push(ABIArgSlot::Reg {
275                            reg: reg1.to_real_reg().unwrap(),
276                            ty: ir::types::I64,
277                            extension: ir::ArgumentExtension::None,
278                        });
279                        slots.push(ABIArgSlot::Reg {
280                            reg: reg2.to_real_reg().unwrap(),
281                            ty: ir::types::I64,
282                            extension: ir::ArgumentExtension::None,
283                        });
284                    }
285                    _ => {
286                        let size = 16;
287
288                        // Align.
289                        next_stack = align_to(next_stack, size);
290
291                        slots.push(ABIArgSlot::Stack {
292                            offset: next_stack as i64,
293                            ty: ir::types::I64,
294                            extension: param.extension,
295                        });
296                        slots.push(ABIArgSlot::Stack {
297                            offset: next_stack as i64 + 8,
298                            ty: ir::types::I64,
299                            extension: param.extension,
300                        });
301                        next_stack += size;
302                    }
303                };
304                // Unconditionally increment next_gpr even when storing the
305                // argument on the stack to prevent reusing a possibly
306                // remaining register for the next argument.
307                next_gpr += 2;
308                next_param_idx += 2;
309
310                args.push(ABIArg::Slots {
311                    slots,
312                    purpose: param.purpose,
313                });
314                continue;
315            }
316
317            let mut slots = ABIArgSlotVec::new();
318            for (ix, (rc, reg_ty)) in rcs.iter().zip(reg_tys.iter()).enumerate() {
319                let last_slot = last_param && ix == rcs.len() - 1;
320
321                let intreg = *rc == RegClass::Int;
322                let nextreg = if intreg {
323                    match args_or_rets {
324                        ArgsOrRets::Args => get_intreg_for_arg(call_conv, next_gpr, next_param_idx),
325                        ArgsOrRets::Rets => {
326                            get_intreg_for_retval(call_conv, flags, next_gpr, last_slot)
327                        }
328                    }
329                } else {
330                    match args_or_rets {
331                        ArgsOrRets::Args => {
332                            get_fltreg_for_arg(call_conv, next_vreg, next_param_idx)
333                        }
334                        ArgsOrRets::Rets => get_fltreg_for_retval(call_conv, next_vreg, last_slot),
335                    }
336                };
337                next_param_idx += 1;
338                if let Some(reg) = nextreg {
339                    if intreg {
340                        next_gpr += 1;
341                    } else {
342                        next_vreg += 1;
343                    }
344                    slots.push(ABIArgSlot::Reg {
345                        reg: reg.to_real_reg().unwrap(),
346                        ty: *reg_ty,
347                        extension: param.extension,
348                    });
349                } else {
350                    if args_or_rets == ArgsOrRets::Rets && !flags.enable_multi_ret_implicit_sret() {
351                        return Err(crate::CodegenError::Unsupported(
352                            "Too many return values to fit in registers. \
353                            Use a StructReturn argument instead. (#9510)"
354                                .to_owned(),
355                        ));
356                    }
357
358                    let size = reg_ty.bytes();
359                    let size = if call_conv == CallConv::Winch
360                        && args_or_rets == ArgsOrRets::Rets
361                        && !uses_extension
362                    {
363                        size
364                    } else {
365                        let size = core::cmp::max(size, 8);
366
367                        // Align.
368                        debug_assert!(size.is_power_of_two());
369                        next_stack = align_to(next_stack, size);
370                        size
371                    };
372
373                    slots.push(ABIArgSlot::Stack {
374                        offset: next_stack as i64,
375                        ty: *reg_ty,
376                        extension: param.extension,
377                    });
378                    next_stack += size;
379                }
380            }
381
382            args.push(ABIArg::Slots {
383                slots,
384                purpose: param.purpose,
385            });
386        }
387
388        // Fastcall's indirect 128+ bit vector arguments are all located on the
389        // stack, and stack space is reserved after all parameters are passed,
390        // so allocate from the space now.
391        if args_or_rets == ArgsOrRets::Args && is_fastcall {
392            for arg in args.args_mut() {
393                if let ABIArg::ImplicitPtrArg { offset, .. } = arg {
394                    assert_eq!(*offset, 0);
395                    next_stack = align_to(next_stack, 16);
396                    *offset = next_stack as i64;
397                    next_stack += 16;
398                }
399            }
400        }
401        let extra_arg_idx = if let Some(ret_area_ptr) = ret_area_ptr {
402            args.push_non_formal(ret_area_ptr);
403            Some(args.args().len() - 1)
404        } else {
405            None
406        };
407
408        // Winch writes the first result to the highest offset, so we need to iterate through the
409        // args and adjust the offsets down.
410        if call_conv == CallConv::Winch && args_or_rets == ArgsOrRets::Rets {
411            winch::reverse_stack(args, next_stack, uses_extension);
412        }
413
414        next_stack = align_to(next_stack, 16);
415
416        if args_or_rets == ArgsOrRets::Args
417            && matches!(call_conv, CallConv::Tail | CallConv::Winch)
418            && next_stack > u32::from(u16::MAX)
419        {
420            return Err(crate::CodegenError::ImplLimitExceeded);
421        }
422
423        Ok((next_stack, extra_arg_idx))
424    }
425
426    fn gen_load_stack(mem: StackAMode, into_reg: Writable<Reg>, ty: Type) -> Self::I {
427        // For integer-typed values, we always load a full 64 bits (and we always spill a full 64
428        // bits as well -- see `Inst::store()`).
429        let ty = match ty {
430            types::I8 | types::I16 | types::I32 => types::I64,
431            // Stack slots are always at least 8 bytes, so it's fine to load 4 bytes instead of only
432            // two.
433            types::F16 | types::I8X2 => types::F32,
434            _ => ty,
435        };
436        Inst::load(ty, mem, into_reg, ExtKind::None)
437    }
438
439    fn gen_store_stack(mem: StackAMode, from_reg: Reg, ty: Type) -> Self::I {
440        let ty = match ty {
441            // See `gen_load_stack`.
442            types::F16 | types::I8X2 => types::F32,
443            _ => ty,
444        };
445        Inst::store(ty, from_reg, mem)
446    }
447
448    fn gen_move(to_reg: Writable<Reg>, from_reg: Reg, ty: Type) -> Self::I {
449        Inst::gen_move(to_reg, from_reg, ty)
450    }
451
452    /// Generate an integer-extend operation.
453    fn gen_extend(
454        to_reg: Writable<Reg>,
455        from_reg: Reg,
456        is_signed: bool,
457        from_bits: u8,
458        to_bits: u8,
459    ) -> Self::I {
460        let ext_mode = ExtMode::new(from_bits as u16, to_bits as u16)
461            .unwrap_or_else(|| panic!("invalid extension: {from_bits} -> {to_bits}"));
462        if is_signed {
463            Inst::movsx_rm_r(ext_mode, RegMem::reg(from_reg), to_reg)
464        } else {
465            Inst::movzx_rm_r(ext_mode, RegMem::reg(from_reg), to_reg)
466        }
467    }
468
469    fn gen_args(args: Vec<ArgPair>) -> Inst {
470        Inst::Args { args }
471    }
472
473    fn gen_rets(rets: Vec<RetPair>) -> Inst {
474        Inst::Rets { rets }
475    }
476
477    fn gen_add_imm(
478        _call_conv: isa::CallConv,
479        into_reg: Writable<Reg>,
480        from_reg: Reg,
481        imm: u32,
482    ) -> SmallInstVec<Self::I> {
483        let mut ret = SmallVec::new();
484        if from_reg != into_reg.to_reg() {
485            ret.push(Inst::gen_move(into_reg, from_reg, I64));
486        }
487        let imm = i32::try_from(imm).expect("`imm` is too large to fit in a 32-bit immediate");
488        ret.push(Inst::addq_mi(into_reg, imm));
489        ret
490    }
491
492    fn gen_stack_lower_bound_trap(limit_reg: Reg) -> SmallInstVec<Self::I> {
493        smallvec![
494            Inst::External {
495                inst: asm::inst::cmpq_rm::new(Gpr::unwrap_new(limit_reg), Gpr::RSP,).into(),
496            },
497            Inst::TrapIf {
498                // NBE == "> unsigned"; args above are reversed; this tests limit_reg > rsp.
499                cc: CC::NBE,
500                trap_code: TrapCode::STACK_OVERFLOW,
501            },
502        ]
503    }
504
505    fn gen_get_stack_addr(mem: StackAMode, into_reg: Writable<Reg>) -> Self::I {
506        let mem: SyntheticAmode = mem.into();
507        Inst::External {
508            inst: asm::inst::leaq_rm::new(into_reg, mem).into(),
509        }
510    }
511
512    fn get_stacklimit_reg(_call_conv: isa::CallConv) -> Reg {
513        // As per comment on trait definition, we must return a caller-save
514        // register that is not used as an argument here.
515        debug_assert!(!is_callee_save_systemv(
516            regs::r10().to_real_reg().unwrap(),
517            false
518        ));
519        regs::r10()
520    }
521
522    fn gen_load_base_offset(into_reg: Writable<Reg>, base: Reg, offset: i32, ty: Type) -> Self::I {
523        // Only ever used for I64s, F128s and vectors; if that changes, see if
524        // the ExtKind below needs to be changed.
525        assert!(ty == I64 || ty.is_vector() || ty == F128);
526        let mem = Amode::imm_reg(offset, base);
527        Inst::load(ty, mem, into_reg, ExtKind::None)
528    }
529
530    fn gen_store_base_offset(base: Reg, offset: i32, from_reg: Reg, ty: Type) -> Self::I {
531        let ty = match ty {
532            // See `gen_load_stack`.
533            types::F16 | types::I8X2 => types::F32,
534            _ => ty,
535        };
536        let mem = Amode::imm_reg(offset, base);
537        Inst::store(ty, from_reg, mem)
538    }
539
540    fn gen_sp_reg_adjust(amount: i32) -> SmallInstVec<Self::I> {
541        let rsp = Writable::from_reg(regs::rsp());
542        let inst = if amount >= 0 {
543            Inst::addq_mi(rsp, amount)
544        } else {
545            Inst::subq_mi(rsp, -amount)
546        };
547        smallvec![inst]
548    }
549
550    fn gen_prologue_frame_setup(
551        _call_conv: isa::CallConv,
552        flags: &settings::Flags,
553        _isa_flags: &x64_settings::Flags,
554        frame_layout: &FrameLayout,
555    ) -> SmallInstVec<Self::I> {
556        let r_rsp = Gpr::RSP;
557        let r_rbp = Gpr::RBP;
558        let w_rbp = Writable::from_reg(r_rbp);
559        let mut insts = SmallVec::new();
560        // `push %rbp`
561        // RSP before the call will be 0 % 16.  So here, it is 8 % 16.
562        insts.push(Inst::External {
563            inst: asm::inst::pushq_o::new(r_rbp).into(),
564        });
565
566        if flags.unwind_info() {
567            insts.push(Inst::Unwind {
568                inst: UnwindInst::PushFrameRegs {
569                    offset_upward_to_caller_sp: frame_layout.setup_area_size,
570                },
571            });
572        }
573
574        // `mov %rsp, %rbp`
575        // RSP is now 0 % 16
576        insts.push(Inst::External {
577            inst: asm::inst::movq_mr::new(w_rbp, r_rsp).into(),
578        });
579
580        insts
581    }
582
583    fn gen_epilogue_frame_restore(
584        _call_conv: isa::CallConv,
585        _flags: &settings::Flags,
586        _isa_flags: &x64_settings::Flags,
587        _frame_layout: &FrameLayout,
588    ) -> SmallInstVec<Self::I> {
589        let rbp = Gpr::RBP;
590        let rsp = Gpr::RSP;
591
592        let mut insts = SmallVec::new();
593        // `mov %rbp, %rsp`
594        insts.push(Inst::External {
595            inst: asm::inst::movq_mr::new(Writable::from_reg(rsp), rbp).into(),
596        });
597        // `pop %rbp`
598        insts.push(Inst::External {
599            inst: asm::inst::popq_o::new(Writable::from_reg(rbp)).into(),
600        });
601        insts
602    }
603
604    fn gen_return(
605        call_conv: CallConv,
606        _isa_flags: &x64_settings::Flags,
607        frame_layout: &FrameLayout,
608    ) -> SmallInstVec<Self::I> {
609        // Emit return instruction.
610        let stack_bytes_to_pop = match call_conv {
611            CallConv::Tail => frame_layout.tail_args_size,
612            CallConv::Winch => frame_layout.incoming_args_size,
613            _ => 0,
614        };
615        let inst = if stack_bytes_to_pop == 0 {
616            asm::inst::retq_zo::new().into()
617        } else {
618            let stack_bytes_to_pop = u16::try_from(stack_bytes_to_pop).unwrap();
619            asm::inst::retq_i::new(stack_bytes_to_pop).into()
620        };
621        smallvec![Inst::External { inst }]
622    }
623
624    fn gen_probestack(insts: &mut SmallInstVec<Self::I>, frame_size: u32) {
625        insts.push(Inst::imm(
626            OperandSize::Size32,
627            frame_size as u64,
628            Writable::from_reg(regs::rax()),
629        ));
630        insts.push(Inst::CallKnown {
631            // No need to include arg here: we are post-regalloc
632            // so no constraints will be seen anyway.
633            info: Box::new(CallInfo::empty(
634                ExternalName::LibCall(LibCall::Probestack),
635                CallConv::Probestack,
636            )),
637        });
638    }
639
640    fn gen_inline_probestack(
641        insts: &mut SmallInstVec<Self::I>,
642        call_conv: isa::CallConv,
643        frame_size: u32,
644        guard_size: u32,
645    ) {
646        // Unroll at most n consecutive probes, before falling back to using a loop
647        //
648        // This was number was picked because the loop version is 38 bytes long. We can fit
649        // 4 inline probes in that space, so unroll if its beneficial in terms of code size.
650        const PROBE_MAX_UNROLL: u32 = 4;
651
652        // Calculate how many probes we need to perform. Round down, as we only
653        // need to probe whole guard_size regions we'd otherwise skip over.
654        let probe_count = frame_size / guard_size;
655        if probe_count == 0 {
656            // No probe necessary
657        } else if probe_count <= PROBE_MAX_UNROLL || call_conv == isa::CallConv::PreserveAll {
658            // The probe loop needs a temporary register, but probes run
659            // before any registers are saved, and a `preserve_all` function
660            // must not clobber any register. The unrolled probes don't use
661            // any registers, so always use them for `preserve_all`.
662            Self::gen_probestack_unroll(insts, guard_size, probe_count)
663        } else {
664            Self::gen_probestack_loop(insts, call_conv, frame_size, guard_size)
665        }
666    }
667
668    fn gen_clobber_save(
669        _call_conv: isa::CallConv,
670        flags: &settings::Flags,
671        frame_layout: &FrameLayout,
672    ) -> SmallVec<[Self::I; 16]> {
673        let mut insts = SmallVec::new();
674
675        // Emit unwind info: start the frame. The frame (from unwind consumers'
676        // point of view) starts at clobbers, just below the FP and return
677        // address. Spill slots and stack slots are part of our actual frame but
678        // do not concern the unwinder.
679        //
680        // This is emitted before the incoming argument area is resized below so
681        // that the CFA is defined in terms of RBP while the saved RBP and return
682        // address are being moved: RBP keeps pointing at the original copies
683        // until both have been moved, and then points at the new copies. In both
684        // cases the caller's frame is `setup_area_size` above RBP, and the
685        // clobbers are just below RBP.
686        if flags.unwind_info() && frame_layout.setup_area_size > 0 {
687            insts.push(Inst::Unwind {
688                inst: UnwindInst::DefineNewFrame {
689                    offset_downward_to_clobbers: frame_layout.clobber_size,
690                    offset_upward_to_caller_sp: frame_layout.setup_area_size,
691                },
692            });
693        }
694
695        // When a return_call within this function required more stack arguments than we have
696        // present, resize the incoming argument area of the frame to accommodate those arguments.
697        let incoming_args_diff = frame_layout.tail_args_size - frame_layout.incoming_args_size;
698        if incoming_args_diff > 0 {
699            // Decrement the stack pointer to make space for the new arguments.
700            let rsp = Writable::from_reg(regs::rsp());
701            insts.push(Inst::subq_mi(
702                rsp,
703                i32::try_from(incoming_args_diff)
704                    .expect("`incoming_args_diff` is too large to fit in a 32-bit immediate"),
705            ));
706
707            let incoming_args_diff = i32::try_from(incoming_args_diff).unwrap();
708
709            // Move the saved frame pointer down by `incoming_args_diff`.
710            let addr = Amode::imm_reg(incoming_args_diff, regs::rsp());
711            let r11 = Writable::from_reg(Gpr::R11);
712            let inst = asm::inst::movq_rm::new(r11, addr).into();
713            insts.push(Inst::External { inst });
714            let inst = asm::inst::movq_mr::new(Amode::imm_reg(0, regs::rsp()), r11.to_reg()).into();
715            insts.push(Inst::External { inst });
716
717            // Move the saved return address down by `incoming_args_diff`.
718            let addr = Amode::imm_reg(incoming_args_diff + 8, regs::rsp());
719            let inst = asm::inst::movq_rm::new(r11, addr).into();
720            insts.push(Inst::External { inst });
721            let inst = asm::inst::movq_mr::new(Amode::imm_reg(8, regs::rsp()), r11.to_reg()).into();
722            insts.push(Inst::External { inst });
723
724            // Now that the saved frame pointer and return address have been
725            // moved, point the frame pointer at the new frame record.
726            let rbp = Gpr::RBP;
727            let rsp = Gpr::RSP;
728            insts.push(Inst::External {
729                inst: asm::inst::movq_mr::new(Writable::from_reg(rbp), rsp).into(),
730            });
731        }
732
733        // Adjust the stack pointer downward for clobbers and the function fixed
734        // frame (spillslots, storage slots, and argument area).
735        let stack_size = frame_layout.fixed_frame_storage_size
736            + frame_layout.clobber_size
737            + frame_layout.outgoing_args_size;
738        if stack_size > 0 {
739            let rsp = Writable::from_reg(regs::rsp());
740            let stack_size = i32::try_from(stack_size)
741                .expect("`stack_size` is too large to fit in a 32-bit immediate");
742            insts.push(Inst::subq_mi(rsp, stack_size));
743        }
744
745        // Store each clobbered register in order at offsets from RSP,
746        // placing them above the fixed frame slots.
747        let clobber_offset =
748            frame_layout.fixed_frame_storage_size + frame_layout.outgoing_args_size;
749        let mut cur_offset = 0;
750        for reg in &frame_layout.clobbered_callee_saves {
751            let r_reg = reg.to_reg();
752            let ty = match r_reg.class() {
753                RegClass::Int => types::I64,
754                RegClass::Float => types::I8X16,
755                RegClass::Vector => unreachable!(),
756            };
757
758            // Align to 8 or 16 bytes as required by the storage type of the clobber.
759            cur_offset = align_to(cur_offset, ty.bytes());
760            let off = cur_offset;
761            cur_offset += ty.bytes();
762
763            insts.push(Inst::store(
764                ty,
765                r_reg.into(),
766                Amode::imm_reg(i32::try_from(off + clobber_offset).unwrap(), regs::rsp()),
767            ));
768
769            if flags.unwind_info() {
770                insts.push(Inst::Unwind {
771                    inst: UnwindInst::SaveReg {
772                        clobber_offset: off,
773                        reg: r_reg,
774                    },
775                });
776            }
777        }
778
779        insts
780    }
781
782    fn gen_clobber_restore(
783        _call_conv: isa::CallConv,
784        _flags: &settings::Flags,
785        frame_layout: &FrameLayout,
786    ) -> SmallVec<[Self::I; 16]> {
787        let mut insts = SmallVec::new();
788
789        // Restore regs by loading from offsets of RSP. We compute the offset from
790        // the same base as above in clobber_save, as RSP won't change between the
791        // prologue and epilogue.
792        let mut cur_offset =
793            frame_layout.fixed_frame_storage_size + frame_layout.outgoing_args_size;
794        for reg in &frame_layout.clobbered_callee_saves {
795            let rreg = reg.to_reg();
796            let ty = match rreg.class() {
797                RegClass::Int => types::I64,
798                RegClass::Float => types::I8X16,
799                RegClass::Vector => unreachable!(),
800            };
801
802            // Align to 8 or 16 bytes as required by the storage type of the clobber.
803            cur_offset = align_to(cur_offset, ty.bytes());
804
805            insts.push(Inst::load(
806                ty,
807                Amode::imm_reg(cur_offset.try_into().unwrap(), regs::rsp()),
808                Writable::from_reg(rreg.into()),
809                ExtKind::None,
810            ));
811
812            cur_offset += ty.bytes();
813        }
814
815        let stack_size = frame_layout.fixed_frame_storage_size
816            + frame_layout.clobber_size
817            + frame_layout.outgoing_args_size;
818
819        // Adjust RSP back upward.
820        if stack_size > 0 {
821            let rsp = Writable::from_reg(regs::rsp());
822            let stack_size = i32::try_from(stack_size)
823                .expect("`stack_size` is too large to fit in a 32-bit immediate");
824            insts.push(Inst::addq_mi(rsp, stack_size));
825        }
826
827        insts
828    }
829
830    fn gen_memcpy<F: FnMut(Type) -> Writable<Reg>>(
831        call_conv: isa::CallConv,
832        dst: Reg,
833        src: Reg,
834        size: usize,
835        mut alloc_tmp: F,
836    ) -> SmallVec<[Self::I; 8]> {
837        let mut insts = SmallVec::new();
838        let arg0 = get_intreg_for_arg(call_conv, 0, 0).unwrap();
839        let arg1 = get_intreg_for_arg(call_conv, 1, 1).unwrap();
840        let arg2 = get_intreg_for_arg(call_conv, 2, 2).unwrap();
841        let temp = alloc_tmp(Self::word_type());
842        let temp2 = alloc_tmp(Self::word_type());
843        insts.push(Inst::imm(OperandSize::Size64, size as u64, temp));
844        // We use an indirect call and a full LoadExtName because we do not have
845        // information about the libcall `RelocDistance` here, so we
846        // conservatively use the more flexible calling sequence.
847        insts.push(Inst::LoadExtName {
848            dst: temp2.map(Gpr::unwrap_new),
849            name: Box::new(ExternalName::LibCall(LibCall::Memcpy)),
850            offset: 0,
851            distance: RelocDistance::Far,
852        });
853        let callee_pop_size = 0;
854        insts.push(Inst::call_unknown(Box::new(CallInfo {
855            dest: RegMem::reg(temp2.to_reg()),
856            uses: smallvec![
857                CallArgPair {
858                    vreg: dst,
859                    preg: arg0
860                },
861                CallArgPair {
862                    vreg: src,
863                    preg: arg1
864                },
865                CallArgPair {
866                    vreg: temp.to_reg(),
867                    preg: arg2
868                },
869            ],
870            defs: smallvec![],
871            clobbers: Self::get_regs_clobbered_by_call(call_conv, false),
872            callee_pop_size,
873            callee_conv: call_conv,
874            caller_conv: call_conv,
875            try_call_info: None,
876            patchable: false,
877        })));
878        insts
879    }
880
881    fn get_number_of_spillslots_for_value(
882        rc: RegClass,
883        vector_scale: u32,
884        _isa_flags: &Self::F,
885    ) -> u32 {
886        // We allocate in terms of 8-byte slots.
887        match rc {
888            RegClass::Int => 1,
889            RegClass::Float => vector_scale / 8,
890            RegClass::Vector => unreachable!(),
891        }
892    }
893
894    fn get_machine_env(flags: &settings::Flags, _call_conv: isa::CallConv) -> &MachineEnv {
895        if flags.enable_pinned_reg() {
896            static MACHINE_ENV: MachineEnv = create_reg_env_systemv(true);
897            &MACHINE_ENV
898        } else {
899            static MACHINE_ENV: MachineEnv = create_reg_env_systemv(false);
900            &MACHINE_ENV
901        }
902    }
903
904    fn get_regs_clobbered_by_call(
905        call_conv_of_callee: isa::CallConv,
906        is_exception: bool,
907    ) -> PRegSet {
908        match (call_conv_of_callee, is_exception) {
909            (isa::CallConv::Tail, true) => ALL_CLOBBERS,
910            // Note that "PreserveAll" actually preserves nothing at
911            // the callsite if used for a `try_call`, because the
912            // unwinder ABI for `try_call`s is still "no clobbered
913            // register restores" for this ABI (so as to work with
914            // Wasmtime).
915            (isa::CallConv::PreserveAll, true) => ALL_CLOBBERS,
916            (isa::CallConv::Winch, _) => ALL_CLOBBERS,
917            (isa::CallConv::SystemV, _) => SYSV_CLOBBERS,
918            (isa::CallConv::WindowsFastcall, false) => WINDOWS_CLOBBERS,
919            (isa::CallConv::PreserveAll, _) => NO_CLOBBERS,
920            (_, false) => SYSV_CLOBBERS,
921            (call_conv, true) => panic!("unimplemented clobbers for exn abi of {call_conv:?}"),
922        }
923    }
924
925    fn get_ext_mode(
926        _call_conv: isa::CallConv,
927        specified: ir::ArgumentExtension,
928        _location: ABIArgLocation,
929    ) -> ir::ArgumentExtension {
930        specified
931    }
932
933    fn compute_frame_layout(
934        call_conv: CallConv,
935        flags: &settings::Flags,
936        _sig: &Signature,
937        regs: &[Writable<RealReg>],
938        function_calls: FunctionCalls,
939        incoming_args_size: u32,
940        tail_args_size: u32,
941        stackslots_size: u32,
942        fixed_frame_storage_size: u32,
943        outgoing_args_size: u32,
944    ) -> FrameLayout {
945        debug_assert!(tail_args_size >= incoming_args_size);
946
947        let mut regs: Vec<Writable<RealReg>> = match call_conv {
948            // The `winch` calling convention doesn't have any callee-save
949            // registers.
950            CallConv::Winch => vec![],
951            CallConv::Fast | CallConv::SystemV | CallConv::Tail => regs
952                .iter()
953                .cloned()
954                .filter(|r| is_callee_save_systemv(r.to_reg(), flags.enable_pinned_reg()))
955                .collect(),
956            CallConv::WindowsFastcall => regs
957                .iter()
958                .cloned()
959                .filter(|r| is_callee_save_fastcall(r.to_reg(), flags.enable_pinned_reg()))
960                .collect(),
961            // The `preserve_all` calling convention makes every reg a callee-save reg.
962            CallConv::PreserveAll => regs.iter().cloned().collect(),
963            CallConv::Probestack => todo!("probestack?"),
964            CallConv::AppleAarch64 => unreachable!(),
965        };
966        // Sort registers for deterministic code output. We can do an unstable sort because the
967        // registers will be unique (there are no dups).
968        regs.sort_unstable();
969
970        // Compute clobber size.
971        let clobber_size = compute_clobber_size(&regs);
972
973        // Compute setup area size.
974        let setup_area_size = 16; // RBP, return address
975
976        // Return FrameLayout structure.
977        FrameLayout {
978            word_bytes: 8,
979            incoming_args_size,
980            tail_args_size: align_to(tail_args_size, 16),
981            setup_area_size,
982            clobber_size,
983            fixed_frame_storage_size,
984            stackslots_size,
985            outgoing_args_size,
986            clobbered_callee_saves: regs,
987            function_calls,
988        }
989    }
990
991    fn retval_temp_reg(_call_conv_of_callee: isa::CallConv) -> Writable<Reg> {
992        // Use r11 as a temp: clobbered anyway, and
993        // not otherwise used as a return value in any of our
994        // supported calling conventions.
995        Writable::from_reg(regs::r11())
996    }
997
998    fn exception_payload_regs(call_conv: isa::CallConv) -> &'static [Reg] {
999        const PAYLOAD_REGS: &'static [Reg] = &[regs::rax(), regs::rdx()];
1000        match call_conv {
1001            isa::CallConv::SystemV | isa::CallConv::Tail | isa::CallConv::PreserveAll => {
1002                PAYLOAD_REGS
1003            }
1004            _ => &[],
1005        }
1006    }
1007}
1008
1009impl From<StackAMode> for SyntheticAmode {
1010    fn from(amode: StackAMode) -> Self {
1011        // We enforce a 128 MB stack-frame size limit above, so these
1012        // `expect()`s should never fail.
1013        match amode {
1014            StackAMode::IncomingArg(off, stack_args_size) => {
1015                let offset = u32::try_from(off).expect(
1016                    "Offset in IncomingArg is greater than 4GB; should hit impl limit first",
1017                );
1018                SyntheticAmode::IncomingArg {
1019                    offset: stack_args_size - offset,
1020                }
1021            }
1022            StackAMode::Slot(off) => {
1023                let off = i32::try_from(off)
1024                    .expect("Offset in Slot is greater than 2GB; should hit impl limit first");
1025                SyntheticAmode::slot_offset(off)
1026            }
1027            StackAMode::OutgoingArg(off) => {
1028                let off = i32::try_from(off).expect(
1029                    "Offset in OutgoingArg is greater than 2GB; should hit impl limit first",
1030                );
1031                SyntheticAmode::Real(Amode::ImmReg {
1032                    simm32: off,
1033                    base: regs::rsp(),
1034                    flags: MemFlagsData::trusted(),
1035                })
1036            }
1037        }
1038    }
1039}
1040
1041fn get_intreg_for_arg(call_conv: CallConv, idx: usize, arg_idx: usize) -> Option<Reg> {
1042    let is_fastcall = call_conv == CallConv::WindowsFastcall;
1043
1044    // Fastcall counts by absolute argument number; SysV counts by argument of
1045    // this (integer) class.
1046    let i = if is_fastcall { arg_idx } else { idx };
1047    match (i, is_fastcall) {
1048        (0, false) => Some(regs::rdi()),
1049        (1, false) => Some(regs::rsi()),
1050        (2, false) => Some(regs::rdx()),
1051        (3, false) => Some(regs::rcx()),
1052        (4, false) => Some(regs::r8()),
1053        (5, false) => Some(regs::r9()),
1054        (0, true) => Some(regs::rcx()),
1055        (1, true) => Some(regs::rdx()),
1056        (2, true) => Some(regs::r8()),
1057        (3, true) => Some(regs::r9()),
1058        _ => None,
1059    }
1060}
1061
1062fn get_fltreg_for_arg(call_conv: CallConv, idx: usize, arg_idx: usize) -> Option<Reg> {
1063    let is_fastcall = call_conv == CallConv::WindowsFastcall;
1064
1065    // Fastcall counts by absolute argument number; SysV counts by argument of
1066    // this (floating-point) class.
1067    let i = if is_fastcall { arg_idx } else { idx };
1068    match (i, is_fastcall) {
1069        (0, false) => Some(regs::xmm0()),
1070        (1, false) => Some(regs::xmm1()),
1071        (2, false) => Some(regs::xmm2()),
1072        (3, false) => Some(regs::xmm3()),
1073        (4, false) => Some(regs::xmm4()),
1074        (5, false) => Some(regs::xmm5()),
1075        (6, false) => Some(regs::xmm6()),
1076        (7, false) => Some(regs::xmm7()),
1077        (0, true) => Some(regs::xmm0()),
1078        (1, true) => Some(regs::xmm1()),
1079        (2, true) => Some(regs::xmm2()),
1080        (3, true) => Some(regs::xmm3()),
1081        _ => None,
1082    }
1083}
1084
1085fn get_intreg_for_retval(
1086    call_conv: CallConv,
1087    flags: &settings::Flags,
1088    intreg_idx: usize,
1089    is_last: bool,
1090) -> Option<Reg> {
1091    match call_conv {
1092        CallConv::Tail => match intreg_idx {
1093            0 => Some(regs::rax()),
1094            1 => Some(regs::rcx()),
1095            2 => Some(regs::rdx()),
1096            3 => Some(regs::rsi()),
1097            4 => Some(regs::rdi()),
1098            5 => Some(regs::r8()),
1099            6 => Some(regs::r9()),
1100            7 => Some(regs::r10()),
1101            // NB: `r11` is reserved as a scratch register that is
1102            // also part of the clobber set.
1103            // NB: `r15` is reserved as a scratch register.
1104            _ => None,
1105        },
1106        CallConv::Fast | CallConv::SystemV | CallConv::PreserveAll => match intreg_idx {
1107            0 => Some(regs::rax()),
1108            1 => Some(regs::rdx()),
1109            2 if flags.enable_llvm_abi_extensions() => Some(regs::rcx()),
1110            _ => None,
1111        },
1112        CallConv::WindowsFastcall => match intreg_idx {
1113            0 => Some(regs::rax()),
1114            1 => Some(regs::rdx()), // The Rust ABI for i128s needs this.
1115            _ => None,
1116        },
1117
1118        CallConv::Winch => is_last.then(|| regs::rax()),
1119        CallConv::Probestack => todo!(),
1120        CallConv::AppleAarch64 => unreachable!(),
1121    }
1122}
1123
1124fn get_fltreg_for_retval(call_conv: CallConv, fltreg_idx: usize, is_last: bool) -> Option<Reg> {
1125    match call_conv {
1126        CallConv::Tail => match fltreg_idx {
1127            0 => Some(regs::xmm0()),
1128            1 => Some(regs::xmm1()),
1129            2 => Some(regs::xmm2()),
1130            3 => Some(regs::xmm3()),
1131            4 => Some(regs::xmm4()),
1132            5 => Some(regs::xmm5()),
1133            6 => Some(regs::xmm6()),
1134            7 => Some(regs::xmm7()),
1135            _ => None,
1136        },
1137        CallConv::Fast | CallConv::SystemV | CallConv::PreserveAll => match fltreg_idx {
1138            0 => Some(regs::xmm0()),
1139            1 => Some(regs::xmm1()),
1140            _ => None,
1141        },
1142        CallConv::WindowsFastcall => match fltreg_idx {
1143            0 => Some(regs::xmm0()),
1144            1 => Some(regs::xmm1()), // The Rust ABI for float scalar pairs needs this.
1145            _ => None,
1146        },
1147        CallConv::Winch => is_last.then(|| regs::xmm0()),
1148        CallConv::Probestack => todo!(),
1149        CallConv::AppleAarch64 => unreachable!(),
1150    }
1151}
1152
1153fn is_callee_save_systemv(r: RealReg, enable_pinned_reg: bool) -> bool {
1154    use asm::gpr::enc::*;
1155
1156    match r.class() {
1157        RegClass::Int => match r.hw_enc() {
1158            RBX | RBP | R12 | R13 | R14 => true,
1159            // R15 is the pinned register; if we're using it that way,
1160            // it is effectively globally-allocated, and is not
1161            // callee-saved.
1162            R15 => !enable_pinned_reg,
1163            _ => false,
1164        },
1165        RegClass::Float => false,
1166        RegClass::Vector => unreachable!(),
1167    }
1168}
1169
1170fn is_callee_save_fastcall(r: RealReg, enable_pinned_reg: bool) -> bool {
1171    use asm::gpr::enc::*;
1172    use asm::xmm::enc::*;
1173
1174    match r.class() {
1175        RegClass::Int => match r.hw_enc() {
1176            RBX | RBP | RSI | RDI | R12 | R13 | R14 => true,
1177            // See above for SysV: we must treat the pinned reg specially.
1178            R15 => !enable_pinned_reg,
1179            _ => false,
1180        },
1181        RegClass::Float => match r.hw_enc() {
1182            XMM6 | XMM7 | XMM8 | XMM9 | XMM10 | XMM11 | XMM12 | XMM13 | XMM14 | XMM15 => true,
1183            _ => false,
1184        },
1185        RegClass::Vector => unreachable!(),
1186    }
1187}
1188
1189fn compute_clobber_size(clobbers: &[Writable<RealReg>]) -> u32 {
1190    let mut clobbered_size = 0;
1191    for reg in clobbers {
1192        match reg.to_reg().class() {
1193            RegClass::Int => {
1194                clobbered_size += 8;
1195            }
1196            RegClass::Float => {
1197                clobbered_size = align_to(clobbered_size, 16);
1198                clobbered_size += 16;
1199            }
1200            RegClass::Vector => unreachable!(),
1201        }
1202    }
1203    align_to(clobbered_size, 16)
1204}
1205
1206const WINDOWS_CLOBBERS: PRegSet = windows_clobbers();
1207const SYSV_CLOBBERS: PRegSet = sysv_clobbers();
1208pub(crate) const ALL_CLOBBERS: PRegSet = all_clobbers();
1209const NO_CLOBBERS: PRegSet = PRegSet::empty();
1210
1211const fn windows_clobbers() -> PRegSet {
1212    use asm::gpr::enc::*;
1213    use asm::xmm::enc::*;
1214
1215    PRegSet::empty()
1216        .with(regs::gpr_preg(RAX))
1217        .with(regs::gpr_preg(RCX))
1218        .with(regs::gpr_preg(RDX))
1219        .with(regs::gpr_preg(R8))
1220        .with(regs::gpr_preg(R9))
1221        .with(regs::gpr_preg(R10))
1222        .with(regs::gpr_preg(R11))
1223        .with(regs::fpr_preg(XMM0))
1224        .with(regs::fpr_preg(XMM1))
1225        .with(regs::fpr_preg(XMM2))
1226        .with(regs::fpr_preg(XMM3))
1227        .with(regs::fpr_preg(XMM4))
1228        .with(regs::fpr_preg(XMM5))
1229}
1230
1231const fn sysv_clobbers() -> PRegSet {
1232    use asm::gpr::enc::*;
1233    use asm::xmm::enc::*;
1234
1235    PRegSet::empty()
1236        .with(regs::gpr_preg(RAX))
1237        .with(regs::gpr_preg(RCX))
1238        .with(regs::gpr_preg(RDX))
1239        .with(regs::gpr_preg(RSI))
1240        .with(regs::gpr_preg(RDI))
1241        .with(regs::gpr_preg(R8))
1242        .with(regs::gpr_preg(R9))
1243        .with(regs::gpr_preg(R10))
1244        .with(regs::gpr_preg(R11))
1245        .with(regs::fpr_preg(XMM0))
1246        .with(regs::fpr_preg(XMM1))
1247        .with(regs::fpr_preg(XMM2))
1248        .with(regs::fpr_preg(XMM3))
1249        .with(regs::fpr_preg(XMM4))
1250        .with(regs::fpr_preg(XMM5))
1251        .with(regs::fpr_preg(XMM6))
1252        .with(regs::fpr_preg(XMM7))
1253        .with(regs::fpr_preg(XMM8))
1254        .with(regs::fpr_preg(XMM9))
1255        .with(regs::fpr_preg(XMM10))
1256        .with(regs::fpr_preg(XMM11))
1257        .with(regs::fpr_preg(XMM12))
1258        .with(regs::fpr_preg(XMM13))
1259        .with(regs::fpr_preg(XMM14))
1260        .with(regs::fpr_preg(XMM15))
1261}
1262
1263/// For calling conventions that clobber all registers.
1264const fn all_clobbers() -> PRegSet {
1265    use asm::gpr::enc::*;
1266    use asm::xmm::enc::*;
1267
1268    PRegSet::empty()
1269        .with(regs::gpr_preg(RAX))
1270        .with(regs::gpr_preg(RCX))
1271        .with(regs::gpr_preg(RDX))
1272        .with(regs::gpr_preg(RBX))
1273        .with(regs::gpr_preg(RSI))
1274        .with(regs::gpr_preg(RDI))
1275        .with(regs::gpr_preg(R8))
1276        .with(regs::gpr_preg(R9))
1277        .with(regs::gpr_preg(R10))
1278        .with(regs::gpr_preg(R11))
1279        .with(regs::gpr_preg(R12))
1280        .with(regs::gpr_preg(R13))
1281        .with(regs::gpr_preg(R14))
1282        .with(regs::gpr_preg(R15))
1283        .with(regs::fpr_preg(XMM0))
1284        .with(regs::fpr_preg(XMM1))
1285        .with(regs::fpr_preg(XMM2))
1286        .with(regs::fpr_preg(XMM3))
1287        .with(regs::fpr_preg(XMM4))
1288        .with(regs::fpr_preg(XMM5))
1289        .with(regs::fpr_preg(XMM6))
1290        .with(regs::fpr_preg(XMM7))
1291        .with(regs::fpr_preg(XMM8))
1292        .with(regs::fpr_preg(XMM9))
1293        .with(regs::fpr_preg(XMM10))
1294        .with(regs::fpr_preg(XMM11))
1295        .with(regs::fpr_preg(XMM12))
1296        .with(regs::fpr_preg(XMM13))
1297        .with(regs::fpr_preg(XMM14))
1298        .with(regs::fpr_preg(XMM15))
1299}
1300
1301const fn create_reg_env_systemv(enable_pinned_reg: bool) -> MachineEnv {
1302    const fn preg(r: Reg) -> PReg {
1303        r.to_real_reg().unwrap().preg()
1304    }
1305
1306    let mut env = MachineEnv {
1307        preferred_regs_by_class: [
1308            // Preferred GPRs: caller-saved in the SysV ABI.
1309            PRegSet::empty()
1310                .with(preg(regs::rsi()))
1311                .with(preg(regs::rdi()))
1312                .with(preg(regs::rax()))
1313                .with(preg(regs::rcx()))
1314                .with(preg(regs::rdx()))
1315                .with(preg(regs::r8()))
1316                .with(preg(regs::r9()))
1317                .with(preg(regs::r10()))
1318                .with(preg(regs::r11())),
1319            // Preferred XMMs: the first 8, which can have smaller encodings
1320            // with AVX instructions.
1321            PRegSet::empty()
1322                .with(preg(regs::xmm0()))
1323                .with(preg(regs::xmm1()))
1324                .with(preg(regs::xmm2()))
1325                .with(preg(regs::xmm3()))
1326                .with(preg(regs::xmm4()))
1327                .with(preg(regs::xmm5()))
1328                .with(preg(regs::xmm6()))
1329                .with(preg(regs::xmm7())),
1330            // The Vector Regclass is unused
1331            PRegSet::empty(),
1332        ],
1333        non_preferred_regs_by_class: [
1334            // Non-preferred GPRs: callee-saved in the SysV ABI.
1335            PRegSet::empty()
1336                .with(preg(regs::rbx()))
1337                .with(preg(regs::r12()))
1338                .with(preg(regs::r13()))
1339                .with(preg(regs::r14())),
1340            // Non-preferred XMMs: the last 8 registers, which can have larger
1341            // encodings with AVX instructions.
1342            PRegSet::empty()
1343                .with(preg(regs::xmm8()))
1344                .with(preg(regs::xmm9()))
1345                .with(preg(regs::xmm10()))
1346                .with(preg(regs::xmm11()))
1347                .with(preg(regs::xmm12()))
1348                .with(preg(regs::xmm13()))
1349                .with(preg(regs::xmm14()))
1350                .with(preg(regs::xmm15())),
1351            // The Vector Regclass is unused
1352            PRegSet::empty(),
1353        ],
1354        fixed_stack_slots: vec![],
1355        scratch_by_class: [None, None, None],
1356    };
1357
1358    debug_assert!(regs::PINNED_REG == cranelift_assembler_x64::gpr::enc::R15);
1359    if !enable_pinned_reg {
1360        env.non_preferred_regs_by_class[0] =
1361            env.non_preferred_regs_by_class[0].with(preg(regs::r15()));
1362    }
1363
1364    env
1365}
1366
1367#[cfg(test)]
1368mod tests {
1369    use super::*;
1370    use crate::machinst::abi::Callee;
1371    use alloc::vec::Vec;
1372
1373    fn make_frame_layout(total_relevant_fields_sum: u32) -> FrameLayout {
1374        FrameLayout {
1375            word_bytes: 8,
1376            incoming_args_size: 0,
1377            tail_args_size: 0,
1378            setup_area_size: 0,
1379            clobber_size: 0,
1380            fixed_frame_storage_size: total_relevant_fields_sum,
1381            stackslots_size: 0,
1382            outgoing_args_size: 0,
1383            clobbered_callee_saves: Vec::new(),
1384            function_calls: crate::machinst::FunctionCalls::None,
1385        }
1386    }
1387
1388    const ONE_GIB: u32 = 1 << 30;
1389
1390    #[test]
1391    fn frame_layout_under_limit_is_accepted() {
1392        let layout = make_frame_layout(ONE_GIB - 1);
1393        assert!(!Callee::<X64ABIMachineSpec>::frame_layout_exceeds_limit(
1394            &layout, ONE_GIB
1395        ));
1396    }
1397
1398    #[test]
1399    fn frame_layout_at_exact_limit_is_accepted() {
1400        let layout = make_frame_layout(ONE_GIB);
1401        assert!(!Callee::<X64ABIMachineSpec>::frame_layout_exceeds_limit(
1402            &layout, ONE_GIB
1403        ));
1404    }
1405
1406    #[test]
1407    fn frame_layout_over_limit_is_rejected() {
1408        let layout = make_frame_layout(ONE_GIB + 1);
1409        assert!(Callee::<X64ABIMachineSpec>::frame_layout_exceeds_limit(
1410            &layout, ONE_GIB
1411        ));
1412    }
1413}