cranelift_codegen/isa/x64/
abi.rs

1//! Implementation of the standard x64 ABI.
2
3use crate::ir::{self, types, LibCall, MemFlags, Opcode, Signature, TrapCode, Type};
4use crate::ir::{types::*, ExternalName};
5use crate::isa;
6use crate::isa::{unwind::UnwindInst, x64::inst::*, x64::settings as x64_settings, CallConv};
7use crate::machinst::abi::*;
8use crate::machinst::*;
9use crate::settings;
10use crate::{CodegenError, CodegenResult};
11use alloc::boxed::Box;
12use alloc::vec::Vec;
13use args::*;
14use regalloc2::{PRegSet, VReg};
15use smallvec::{smallvec, SmallVec};
16use std::convert::TryFrom;
17
18/// This is the limit for the size of argument and return-value areas on the
19/// stack. We place a reasonable limit here to avoid integer overflow issues
20/// with 32-bit arithmetic: for now, 128 MB.
21static STACK_ARG_RET_SIZE_LIMIT: u32 = 128 * 1024 * 1024;
22
23/// Support for the x64 ABI from the callee side (within a function body).
24pub(crate) type X64Callee = Callee<X64ABIMachineSpec>;
25
26/// Support for the x64 ABI from the caller side (at a callsite).
27pub(crate) type X64Caller = Caller<X64ABIMachineSpec>;
28
29/// Implementation of ABI primitives for x64.
30pub struct X64ABIMachineSpec;
31
32impl X64ABIMachineSpec {
33    fn gen_probestack_unroll(insts: &mut SmallInstVec<Inst>, guard_size: u32, probe_count: u32) {
34        insts.reserve(probe_count as usize);
35        for i in 0..probe_count {
36            let offset = (guard_size * (i + 1)) as i64;
37
38            // TODO: It would be nice if we could store the imm 0, but we don't have insts for those
39            // so store the stack pointer. Any register will do, since the stack is undefined at this point
40            insts.push(Self::gen_store_stack(
41                StackAMode::SPOffset(-offset, I8),
42                regs::rsp(),
43                I32,
44            ));
45        }
46    }
47    fn gen_probestack_loop(insts: &mut SmallInstVec<Inst>, frame_size: u32, guard_size: u32) {
48        // We have to use a caller saved register since clobbering only happens
49        // after stack probing.
50        //
51        // R11 is caller saved on both Fastcall and SystemV, and not used for argument
52        // passing, so it's pretty much free. It is also not used by the stacklimit mechanism.
53        let tmp = regs::r11();
54        debug_assert!({
55            let real_reg = tmp.to_real_reg().unwrap();
56            !is_callee_save_systemv(real_reg, false) && !is_callee_save_fastcall(real_reg, false)
57        });
58
59        insts.push(Inst::StackProbeLoop {
60            tmp: Writable::from_reg(tmp),
61            frame_size,
62            guard_size,
63        });
64    }
65}
66
67impl IsaFlags for x64_settings::Flags {}
68
69impl ABIMachineSpec for X64ABIMachineSpec {
70    type I = Inst;
71
72    type F = x64_settings::Flags;
73
74    fn word_bits() -> u32 {
75        64
76    }
77
78    /// Return required stack alignment in bytes.
79    fn stack_align(_call_conv: isa::CallConv) -> u32 {
80        16
81    }
82
83    fn compute_arg_locs<'a, I>(
84        call_conv: isa::CallConv,
85        flags: &settings::Flags,
86        params: I,
87        args_or_rets: ArgsOrRets,
88        add_ret_area_ptr: bool,
89        mut args: ArgsAccumulator<'_>,
90    ) -> CodegenResult<(u32, Option<usize>)>
91    where
92        I: IntoIterator<Item = &'a ir::AbiParam>,
93    {
94        let is_fastcall = call_conv.extends_windows_fastcall();
95
96        let mut next_gpr = 0;
97        let mut next_vreg = 0;
98        let mut next_stack: u32 = 0;
99        let mut next_param_idx = 0; // Fastcall cares about overall param index
100
101        if args_or_rets == ArgsOrRets::Args && is_fastcall {
102            // Fastcall always reserves 32 bytes of shadow space corresponding to
103            // the four initial in-arg parameters.
104            //
105            // (See:
106            // https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-160)
107            next_stack = 32;
108        }
109
110        for param in params {
111            if let ir::ArgumentPurpose::StructArgument(size) = param.purpose {
112                let offset = next_stack as i64;
113                let size = size;
114                assert!(size % 8 == 0, "StructArgument size is not properly aligned");
115                next_stack += size;
116                args.push(ABIArg::StructArg {
117                    pointer: None,
118                    offset,
119                    size: size as u64,
120                    purpose: param.purpose,
121                });
122                continue;
123            }
124
125            // Find regclass(es) of the register(s) used to store a value of this type.
126            let (rcs, reg_tys) = Inst::rc_for_type(param.value_type)?;
127
128            // Now assign ABIArgSlots for each register-sized part.
129            //
130            // Note that the handling of `i128` values is unique here:
131            //
132            // - If `enable_llvm_abi_extensions` is set in the flags, each
133            //   `i128` is split into two `i64`s and assigned exactly as if it
134            //   were two consecutive 64-bit args. This is consistent with LLVM's
135            //   behavior, and is needed for some uses of Cranelift (e.g., the
136            //   rustc backend).
137            //
138            // - Otherwise, both SysV and Fastcall specify behavior (use of
139            //   vector register, a register pair, or passing by reference
140            //   depending on the case), but for simplicity, we will just panic if
141            //   an i128 type appears in a signature and the LLVM extensions flag
142            //   is not set.
143            //
144            // For examples of how rustc compiles i128 args and return values on
145            // both SysV and Fastcall platforms, see:
146            // https://godbolt.org/z/PhG3ob
147
148            if param.value_type.bits() > 64
149                && !param.value_type.is_vector()
150                && !flags.enable_llvm_abi_extensions()
151            {
152                panic!(
153                    "i128 args/return values not supported unless LLVM ABI extensions are enabled"
154                );
155            }
156
157            let mut slots = ABIArgSlotVec::new();
158            for (rc, reg_ty) in rcs.iter().zip(reg_tys.iter()) {
159                let intreg = *rc == RegClass::Int;
160                let nextreg = if intreg {
161                    match args_or_rets {
162                        ArgsOrRets::Args => {
163                            get_intreg_for_arg(&call_conv, next_gpr, next_param_idx)
164                        }
165                        ArgsOrRets::Rets => {
166                            get_intreg_for_retval(&call_conv, next_gpr, next_param_idx)
167                        }
168                    }
169                } else {
170                    match args_or_rets {
171                        ArgsOrRets::Args => {
172                            get_fltreg_for_arg(&call_conv, next_vreg, next_param_idx)
173                        }
174                        ArgsOrRets::Rets => {
175                            get_fltreg_for_retval(&call_conv, next_vreg, next_param_idx)
176                        }
177                    }
178                };
179                next_param_idx += 1;
180                if let Some(reg) = nextreg {
181                    if intreg {
182                        next_gpr += 1;
183                    } else {
184                        next_vreg += 1;
185                    }
186                    slots.push(ABIArgSlot::Reg {
187                        reg: reg.to_real_reg().unwrap(),
188                        ty: *reg_ty,
189                        extension: param.extension,
190                    });
191                } else {
192                    // Compute size. For the wasmtime ABI it differs from native
193                    // ABIs in how multiple values are returned, so we take a
194                    // leaf out of arm64's book by not rounding everything up to
195                    // 8 bytes. For all ABI arguments, and other ABI returns,
196                    // though, each slot takes a minimum of 8 bytes.
197                    //
198                    // Note that in all cases 16-byte stack alignment happens
199                    // separately after all args.
200                    let size = reg_ty.bits() / 8;
201                    let size = if args_or_rets == ArgsOrRets::Rets && call_conv.extends_wasmtime() {
202                        size
203                    } else {
204                        std::cmp::max(size, 8)
205                    };
206                    // Align.
207                    debug_assert!(size.is_power_of_two());
208                    next_stack = align_to(next_stack, size);
209                    slots.push(ABIArgSlot::Stack {
210                        offset: next_stack as i64,
211                        ty: *reg_ty,
212                        extension: param.extension,
213                    });
214                    next_stack += size;
215                }
216            }
217
218            args.push(ABIArg::Slots {
219                slots,
220                purpose: param.purpose,
221            });
222        }
223
224        let extra_arg = if add_ret_area_ptr {
225            debug_assert!(args_or_rets == ArgsOrRets::Args);
226            if let Some(reg) = get_intreg_for_arg(&call_conv, next_gpr, next_param_idx) {
227                args.push(ABIArg::reg(
228                    reg.to_real_reg().unwrap(),
229                    types::I64,
230                    ir::ArgumentExtension::None,
231                    ir::ArgumentPurpose::Normal,
232                ));
233            } else {
234                args.push(ABIArg::stack(
235                    next_stack as i64,
236                    types::I64,
237                    ir::ArgumentExtension::None,
238                    ir::ArgumentPurpose::Normal,
239                ));
240                next_stack += 8;
241            }
242            Some(args.args().len() - 1)
243        } else {
244            None
245        };
246
247        next_stack = align_to(next_stack, 16);
248
249        // To avoid overflow issues, limit the arg/return size to something reasonable.
250        if next_stack > STACK_ARG_RET_SIZE_LIMIT {
251            return Err(CodegenError::ImplLimitExceeded);
252        }
253
254        Ok((next_stack, extra_arg))
255    }
256
257    fn fp_to_arg_offset(_call_conv: isa::CallConv, _flags: &settings::Flags) -> i64 {
258        16 // frame pointer + return address.
259    }
260
261    fn gen_load_stack(mem: StackAMode, into_reg: Writable<Reg>, ty: Type) -> Self::I {
262        // For integer-typed values, we always load a full 64 bits (and we always spill a full 64
263        // bits as well -- see `Inst::store()`).
264        let ty = match ty {
265            types::I8 | types::I16 | types::I32 => types::I64,
266            _ => ty,
267        };
268        Inst::load(ty, mem, into_reg, ExtKind::None)
269    }
270
271    fn gen_store_stack(mem: StackAMode, from_reg: Reg, ty: Type) -> Self::I {
272        Inst::store(ty, from_reg, mem)
273    }
274
275    fn gen_move(to_reg: Writable<Reg>, from_reg: Reg, ty: Type) -> Self::I {
276        Inst::gen_move(to_reg, from_reg, ty)
277    }
278
279    /// Generate an integer-extend operation.
280    fn gen_extend(
281        to_reg: Writable<Reg>,
282        from_reg: Reg,
283        is_signed: bool,
284        from_bits: u8,
285        to_bits: u8,
286    ) -> Self::I {
287        let ext_mode = ExtMode::new(from_bits as u16, to_bits as u16)
288            .unwrap_or_else(|| panic!("invalid extension: {} -> {}", from_bits, to_bits));
289        if is_signed {
290            Inst::movsx_rm_r(ext_mode, RegMem::reg(from_reg), to_reg)
291        } else {
292            Inst::movzx_rm_r(ext_mode, RegMem::reg(from_reg), to_reg)
293        }
294    }
295
296    fn gen_args(_isa_flags: &x64_settings::Flags, args: Vec<ArgPair>) -> Inst {
297        Inst::Args { args }
298    }
299
300    fn gen_ret(
301        _setup_frame: bool,
302        _isa_flags: &x64_settings::Flags,
303        rets: Vec<RetPair>,
304    ) -> Self::I {
305        Inst::ret(rets)
306    }
307
308    fn gen_add_imm(into_reg: Writable<Reg>, from_reg: Reg, imm: u32) -> SmallInstVec<Self::I> {
309        let mut ret = SmallVec::new();
310        if from_reg != into_reg.to_reg() {
311            ret.push(Inst::gen_move(into_reg, from_reg, I64));
312        }
313        ret.push(Inst::alu_rmi_r(
314            OperandSize::Size64,
315            AluRmiROpcode::Add,
316            RegMemImm::imm(imm),
317            into_reg,
318        ));
319        ret
320    }
321
322    fn gen_stack_lower_bound_trap(limit_reg: Reg) -> SmallInstVec<Self::I> {
323        smallvec![
324            Inst::cmp_rmi_r(OperandSize::Size64, RegMemImm::reg(regs::rsp()), limit_reg),
325            Inst::TrapIf {
326                // NBE == "> unsigned"; args above are reversed; this tests limit_reg > rsp.
327                cc: CC::NBE,
328                trap_code: TrapCode::StackOverflow,
329            },
330        ]
331    }
332
333    fn gen_get_stack_addr(mem: StackAMode, into_reg: Writable<Reg>, _ty: Type) -> Self::I {
334        let mem: SyntheticAmode = mem.into();
335        Inst::lea(mem, into_reg)
336    }
337
338    fn get_stacklimit_reg() -> Reg {
339        debug_assert!(!is_callee_save_systemv(
340            regs::r10().to_real_reg().unwrap(),
341            false
342        ));
343
344        // As per comment on trait definition, we must return a caller-save
345        // register here.
346        regs::r10()
347    }
348
349    fn gen_load_base_offset(into_reg: Writable<Reg>, base: Reg, offset: i32, ty: Type) -> Self::I {
350        // Only ever used for I64s; if that changes, see if the ExtKind below needs to be changed.
351        assert_eq!(ty, I64);
352        let simm32 = offset as u32;
353        let mem = Amode::imm_reg(simm32, base);
354        Inst::load(ty, mem, into_reg, ExtKind::None)
355    }
356
357    fn gen_store_base_offset(base: Reg, offset: i32, from_reg: Reg, ty: Type) -> Self::I {
358        let simm32 = offset as u32;
359        let mem = Amode::imm_reg(simm32, base);
360        Inst::store(ty, from_reg, mem)
361    }
362
363    fn gen_sp_reg_adjust(amount: i32) -> SmallInstVec<Self::I> {
364        let (alu_op, amount) = if amount >= 0 {
365            (AluRmiROpcode::Add, amount)
366        } else {
367            (AluRmiROpcode::Sub, -amount)
368        };
369
370        let amount = amount as u32;
371
372        smallvec![Inst::alu_rmi_r(
373            OperandSize::Size64,
374            alu_op,
375            RegMemImm::imm(amount),
376            Writable::from_reg(regs::rsp()),
377        )]
378    }
379
380    fn gen_nominal_sp_adj(offset: i32) -> Self::I {
381        Inst::VirtualSPOffsetAdj {
382            offset: offset as i64,
383        }
384    }
385
386    fn gen_prologue_frame_setup(flags: &settings::Flags) -> SmallInstVec<Self::I> {
387        let r_rsp = regs::rsp();
388        let r_rbp = regs::rbp();
389        let w_rbp = Writable::from_reg(r_rbp);
390        let mut insts = SmallVec::new();
391        // `push %rbp`
392        // RSP before the call will be 0 % 16.  So here, it is 8 % 16.
393        insts.push(Inst::push64(RegMemImm::reg(r_rbp)));
394
395        if flags.unwind_info() {
396            insts.push(Inst::Unwind {
397                inst: UnwindInst::PushFrameRegs {
398                    offset_upward_to_caller_sp: 16, // RBP, return address
399                },
400            });
401        }
402
403        // `mov %rsp, %rbp`
404        // RSP is now 0 % 16
405        insts.push(Inst::mov_r_r(OperandSize::Size64, r_rsp, w_rbp));
406        insts
407    }
408
409    fn gen_epilogue_frame_restore(_: &settings::Flags) -> SmallInstVec<Self::I> {
410        let mut insts = SmallVec::new();
411        // `mov %rbp, %rsp`
412        insts.push(Inst::mov_r_r(
413            OperandSize::Size64,
414            regs::rbp(),
415            Writable::from_reg(regs::rsp()),
416        ));
417        // `pop %rbp`
418        insts.push(Inst::pop64(Writable::from_reg(regs::rbp())));
419        insts
420    }
421
422    fn gen_probestack(insts: &mut SmallInstVec<Self::I>, frame_size: u32) {
423        insts.push(Inst::imm(
424            OperandSize::Size32,
425            frame_size as u64,
426            Writable::from_reg(regs::rax()),
427        ));
428        insts.push(Inst::CallKnown {
429            dest: ExternalName::LibCall(LibCall::Probestack),
430            info: Box::new(CallInfo {
431                // No need to include arg here: we are post-regalloc
432                // so no constraints will be seen anyway.
433                uses: smallvec![],
434                defs: smallvec![],
435                clobbers: PRegSet::empty(),
436                opcode: Opcode::Call,
437            }),
438        });
439    }
440
441    fn gen_inline_probestack(insts: &mut SmallInstVec<Self::I>, frame_size: u32, guard_size: u32) {
442        // Unroll at most n consecutive probes, before falling back to using a loop
443        //
444        // This was number was picked because the loop version is 38 bytes long. We can fit
445        // 5 inline probes in that space, so unroll if its beneficial in terms of code size.
446        const PROBE_MAX_UNROLL: u32 = 5;
447
448        // Number of probes that we need to perform
449        let probe_count = align_to(frame_size, guard_size) / guard_size;
450
451        if probe_count <= PROBE_MAX_UNROLL {
452            Self::gen_probestack_unroll(insts, guard_size, probe_count)
453        } else {
454            Self::gen_probestack_loop(insts, frame_size, guard_size)
455        }
456    }
457
458    fn gen_clobber_save(
459        _call_conv: isa::CallConv,
460        setup_frame: bool,
461        flags: &settings::Flags,
462        clobbered_callee_saves: &[Writable<RealReg>],
463        fixed_frame_storage_size: u32,
464        _outgoing_args_size: u32,
465    ) -> (u64, SmallVec<[Self::I; 16]>) {
466        let mut insts = SmallVec::new();
467        let clobbered_size = compute_clobber_size(&clobbered_callee_saves);
468
469        if flags.unwind_info() && setup_frame {
470            // Emit unwind info: start the frame. The frame (from unwind
471            // consumers' point of view) starts at clobbbers, just below
472            // the FP and return address. Spill slots and stack slots are
473            // part of our actual frame but do not concern the unwinder.
474            insts.push(Inst::Unwind {
475                inst: UnwindInst::DefineNewFrame {
476                    offset_downward_to_clobbers: clobbered_size,
477                    offset_upward_to_caller_sp: 16, // RBP, return address
478                },
479            });
480        }
481
482        // Adjust the stack pointer downward for clobbers and the function fixed
483        // frame (spillslots and storage slots).
484        let stack_size = fixed_frame_storage_size + clobbered_size;
485        if stack_size > 0 {
486            insts.push(Inst::alu_rmi_r(
487                OperandSize::Size64,
488                AluRmiROpcode::Sub,
489                RegMemImm::imm(stack_size),
490                Writable::from_reg(regs::rsp()),
491            ));
492        }
493        // Store each clobbered register in order at offsets from RSP,
494        // placing them above the fixed frame slots.
495        let mut cur_offset = fixed_frame_storage_size;
496        for reg in clobbered_callee_saves {
497            let r_reg = reg.to_reg();
498            let off = cur_offset;
499            match r_reg.class() {
500                RegClass::Int => {
501                    insts.push(Inst::store(
502                        types::I64,
503                        r_reg.into(),
504                        Amode::imm_reg(cur_offset, regs::rsp()),
505                    ));
506                    cur_offset += 8;
507                }
508                RegClass::Float => {
509                    cur_offset = align_to(cur_offset, 16);
510                    insts.push(Inst::store(
511                        types::I8X16,
512                        r_reg.into(),
513                        Amode::imm_reg(cur_offset, regs::rsp()),
514                    ));
515                    cur_offset += 16;
516                }
517            };
518            if flags.unwind_info() {
519                insts.push(Inst::Unwind {
520                    inst: UnwindInst::SaveReg {
521                        clobber_offset: off - fixed_frame_storage_size,
522                        reg: r_reg,
523                    },
524                });
525            }
526        }
527
528        (clobbered_size as u64, insts)
529    }
530
531    fn gen_clobber_restore(
532        call_conv: isa::CallConv,
533        sig: &Signature,
534        flags: &settings::Flags,
535        clobbers: &[Writable<RealReg>],
536        fixed_frame_storage_size: u32,
537        _outgoing_args_size: u32,
538    ) -> SmallVec<[Self::I; 16]> {
539        let mut insts = SmallVec::new();
540
541        let clobbered_callee_saves =
542            Self::get_clobbered_callee_saves(call_conv, flags, sig, clobbers);
543        let stack_size = fixed_frame_storage_size + compute_clobber_size(&clobbered_callee_saves);
544
545        // Restore regs by loading from offsets of RSP. RSP will be
546        // returned to nominal-RSP at this point, so we can use the
547        // same offsets that we used when saving clobbers above.
548        let mut cur_offset = fixed_frame_storage_size;
549        for reg in &clobbered_callee_saves {
550            let rreg = reg.to_reg();
551            match rreg.class() {
552                RegClass::Int => {
553                    insts.push(Inst::mov64_m_r(
554                        Amode::imm_reg(cur_offset, regs::rsp()),
555                        Writable::from_reg(rreg.into()),
556                    ));
557                    cur_offset += 8;
558                }
559                RegClass::Float => {
560                    cur_offset = align_to(cur_offset, 16);
561                    insts.push(Inst::load(
562                        types::I8X16,
563                        Amode::imm_reg(cur_offset, regs::rsp()),
564                        Writable::from_reg(rreg.into()),
565                        ExtKind::None,
566                    ));
567                    cur_offset += 16;
568                }
569            }
570        }
571        // Adjust RSP back upward.
572        if stack_size > 0 {
573            insts.push(Inst::alu_rmi_r(
574                OperandSize::Size64,
575                AluRmiROpcode::Add,
576                RegMemImm::imm(stack_size),
577                Writable::from_reg(regs::rsp()),
578            ));
579        }
580
581        insts
582    }
583
584    /// Generate a call instruction/sequence.
585    fn gen_call(
586        dest: &CallDest,
587        uses: CallArgList,
588        defs: CallRetList,
589        clobbers: PRegSet,
590        opcode: ir::Opcode,
591        tmp: Writable<Reg>,
592        _callee_conv: isa::CallConv,
593        _caller_conv: isa::CallConv,
594    ) -> SmallVec<[Self::I; 2]> {
595        let mut insts = SmallVec::new();
596        match dest {
597            &CallDest::ExtName(ref name, RelocDistance::Near) => {
598                insts.push(Inst::call_known(name.clone(), uses, defs, clobbers, opcode));
599            }
600            &CallDest::ExtName(ref name, RelocDistance::Far) => {
601                insts.push(Inst::LoadExtName {
602                    dst: tmp,
603                    name: Box::new(name.clone()),
604                    offset: 0,
605                });
606                insts.push(Inst::call_unknown(
607                    RegMem::reg(tmp.to_reg()),
608                    uses,
609                    defs,
610                    clobbers,
611                    opcode,
612                ));
613            }
614            &CallDest::Reg(reg) => {
615                insts.push(Inst::call_unknown(
616                    RegMem::reg(reg),
617                    uses,
618                    defs,
619                    clobbers,
620                    opcode,
621                ));
622            }
623        }
624        insts
625    }
626
627    fn gen_memcpy<F: FnMut(Type) -> Writable<Reg>>(
628        call_conv: isa::CallConv,
629        dst: Reg,
630        src: Reg,
631        size: usize,
632        mut alloc_tmp: F,
633    ) -> SmallVec<[Self::I; 8]> {
634        let mut insts = SmallVec::new();
635        let arg0 = get_intreg_for_arg(&call_conv, 0, 0).unwrap();
636        let arg1 = get_intreg_for_arg(&call_conv, 1, 1).unwrap();
637        let arg2 = get_intreg_for_arg(&call_conv, 2, 2).unwrap();
638        let temp = alloc_tmp(Self::word_type());
639        let temp2 = alloc_tmp(Self::word_type());
640        insts.push(Inst::imm(OperandSize::Size64, size as u64, temp));
641        // We use an indirect call and a full LoadExtName because we do not have
642        // information about the libcall `RelocDistance` here, so we
643        // conservatively use the more flexible calling sequence.
644        insts.push(Inst::LoadExtName {
645            dst: temp2,
646            name: Box::new(ExternalName::LibCall(LibCall::Memcpy)),
647            offset: 0,
648        });
649        insts.push(Inst::call_unknown(
650            RegMem::reg(temp2.to_reg()),
651            /* uses = */
652            smallvec![
653                CallArgPair {
654                    vreg: dst,
655                    preg: arg0
656                },
657                CallArgPair {
658                    vreg: src,
659                    preg: arg1
660                },
661                CallArgPair {
662                    vreg: temp.to_reg(),
663                    preg: arg2
664                },
665            ],
666            /* defs = */ smallvec![],
667            /* clobbers = */ Self::get_regs_clobbered_by_call(call_conv),
668            Opcode::Call,
669        ));
670        insts
671    }
672
673    fn get_number_of_spillslots_for_value(rc: RegClass, vector_scale: u32) -> u32 {
674        // We allocate in terms of 8-byte slots.
675        match rc {
676            RegClass::Int => 1,
677            RegClass::Float => vector_scale / 8,
678        }
679    }
680
681    fn get_virtual_sp_offset_from_state(s: &<Self::I as MachInstEmit>::State) -> i64 {
682        s.virtual_sp_offset
683    }
684
685    fn get_nominal_sp_to_fp(s: &<Self::I as MachInstEmit>::State) -> i64 {
686        s.nominal_sp_to_fp
687    }
688
689    fn get_regs_clobbered_by_call(call_conv_of_callee: isa::CallConv) -> PRegSet {
690        if call_conv_of_callee.extends_windows_fastcall() {
691            WINDOWS_CLOBBERS
692        } else {
693            SYSV_CLOBBERS
694        }
695    }
696
697    fn get_ext_mode(
698        _call_conv: isa::CallConv,
699        _specified: ir::ArgumentExtension,
700    ) -> ir::ArgumentExtension {
701        ir::ArgumentExtension::None
702    }
703
704    fn get_clobbered_callee_saves(
705        call_conv: CallConv,
706        flags: &settings::Flags,
707        _sig: &Signature,
708        regs: &[Writable<RealReg>],
709    ) -> Vec<Writable<RealReg>> {
710        let mut regs: Vec<Writable<RealReg>> = match call_conv {
711            CallConv::Tail => unimplemented!(),
712            CallConv::Fast | CallConv::Cold | CallConv::SystemV | CallConv::WasmtimeSystemV => regs
713                .iter()
714                .cloned()
715                .filter(|r| is_callee_save_systemv(r.to_reg(), flags.enable_pinned_reg()))
716                .collect(),
717            CallConv::WindowsFastcall | CallConv::WasmtimeFastcall => regs
718                .iter()
719                .cloned()
720                .filter(|r| is_callee_save_fastcall(r.to_reg(), flags.enable_pinned_reg()))
721                .collect(),
722            CallConv::Probestack => todo!("probestack?"),
723            CallConv::AppleAarch64 | CallConv::WasmtimeAppleAarch64 => unreachable!(),
724        };
725        // Sort registers for deterministic code output. We can do an unstable sort because the
726        // registers will be unique (there are no dups).
727        regs.sort_unstable_by_key(|r| VReg::from(r.to_reg()).vreg());
728        regs
729    }
730
731    fn is_frame_setup_needed(
732        _is_leaf: bool,
733        _stack_args_size: u32,
734        _num_clobbered_callee_saves: usize,
735        _frame_storage_size: u32,
736    ) -> bool {
737        true
738    }
739}
740
741impl From<StackAMode> for SyntheticAmode {
742    fn from(amode: StackAMode) -> Self {
743        // We enforce a 128 MB stack-frame size limit above, so these
744        // `expect()`s should never fail.
745        match amode {
746            StackAMode::FPOffset(off, _ty) => {
747                let off = i32::try_from(off)
748                    .expect("Offset in FPOffset is greater than 2GB; should hit impl limit first");
749                let simm32 = off as u32;
750                SyntheticAmode::Real(Amode::ImmReg {
751                    simm32,
752                    base: regs::rbp(),
753                    flags: MemFlags::trusted(),
754                })
755            }
756            StackAMode::NominalSPOffset(off, _ty) => {
757                let off = i32::try_from(off).expect(
758                    "Offset in NominalSPOffset is greater than 2GB; should hit impl limit first",
759                );
760                let simm32 = off as u32;
761                SyntheticAmode::nominal_sp_offset(simm32)
762            }
763            StackAMode::SPOffset(off, _ty) => {
764                let off = i32::try_from(off)
765                    .expect("Offset in SPOffset is greater than 2GB; should hit impl limit first");
766                let simm32 = off as u32;
767                SyntheticAmode::Real(Amode::ImmReg {
768                    simm32,
769                    base: regs::rsp(),
770                    flags: MemFlags::trusted(),
771                })
772            }
773        }
774    }
775}
776
777fn get_intreg_for_arg(call_conv: &CallConv, idx: usize, arg_idx: usize) -> Option<Reg> {
778    let is_fastcall = call_conv.extends_windows_fastcall();
779
780    // Fastcall counts by absolute argument number; SysV counts by argument of
781    // this (integer) class.
782    let i = if is_fastcall { arg_idx } else { idx };
783    match (i, is_fastcall) {
784        (0, false) => Some(regs::rdi()),
785        (1, false) => Some(regs::rsi()),
786        (2, false) => Some(regs::rdx()),
787        (3, false) => Some(regs::rcx()),
788        (4, false) => Some(regs::r8()),
789        (5, false) => Some(regs::r9()),
790        (0, true) => Some(regs::rcx()),
791        (1, true) => Some(regs::rdx()),
792        (2, true) => Some(regs::r8()),
793        (3, true) => Some(regs::r9()),
794        _ => None,
795    }
796}
797
798fn get_fltreg_for_arg(call_conv: &CallConv, idx: usize, arg_idx: usize) -> Option<Reg> {
799    let is_fastcall = call_conv.extends_windows_fastcall();
800
801    // Fastcall counts by absolute argument number; SysV counts by argument of
802    // this (floating-point) class.
803    let i = if is_fastcall { arg_idx } else { idx };
804    match (i, is_fastcall) {
805        (0, false) => Some(regs::xmm0()),
806        (1, false) => Some(regs::xmm1()),
807        (2, false) => Some(regs::xmm2()),
808        (3, false) => Some(regs::xmm3()),
809        (4, false) => Some(regs::xmm4()),
810        (5, false) => Some(regs::xmm5()),
811        (6, false) => Some(regs::xmm6()),
812        (7, false) => Some(regs::xmm7()),
813        (0, true) => Some(regs::xmm0()),
814        (1, true) => Some(regs::xmm1()),
815        (2, true) => Some(regs::xmm2()),
816        (3, true) => Some(regs::xmm3()),
817        _ => None,
818    }
819}
820
821fn get_intreg_for_retval(
822    call_conv: &CallConv,
823    intreg_idx: usize,
824    retval_idx: usize,
825) -> Option<Reg> {
826    match call_conv {
827        CallConv::Tail => unimplemented!(),
828        CallConv::Fast | CallConv::Cold | CallConv::SystemV => match intreg_idx {
829            0 => Some(regs::rax()),
830            1 => Some(regs::rdx()),
831            _ => None,
832        },
833        CallConv::WasmtimeSystemV | CallConv::WasmtimeFastcall => {
834            if intreg_idx == 0 && retval_idx == 0 {
835                Some(regs::rax())
836            } else {
837                None
838            }
839        }
840        CallConv::WindowsFastcall => match intreg_idx {
841            0 => Some(regs::rax()),
842            1 => Some(regs::rdx()), // The Rust ABI for i128s needs this.
843            _ => None,
844        },
845        CallConv::Probestack => todo!(),
846        CallConv::AppleAarch64 | CallConv::WasmtimeAppleAarch64 => unreachable!(),
847    }
848}
849
850fn get_fltreg_for_retval(
851    call_conv: &CallConv,
852    fltreg_idx: usize,
853    retval_idx: usize,
854) -> Option<Reg> {
855    match call_conv {
856        CallConv::Tail => unimplemented!(),
857        CallConv::Fast | CallConv::Cold | CallConv::SystemV => match fltreg_idx {
858            0 => Some(regs::xmm0()),
859            1 => Some(regs::xmm1()),
860            _ => None,
861        },
862        CallConv::WasmtimeFastcall | CallConv::WasmtimeSystemV => {
863            if fltreg_idx == 0 && retval_idx == 0 {
864                Some(regs::xmm0())
865            } else {
866                None
867            }
868        }
869        CallConv::WindowsFastcall => match fltreg_idx {
870            0 => Some(regs::xmm0()),
871            _ => None,
872        },
873        CallConv::Probestack => todo!(),
874        CallConv::AppleAarch64 | CallConv::WasmtimeAppleAarch64 => unreachable!(),
875    }
876}
877
878fn is_callee_save_systemv(r: RealReg, enable_pinned_reg: bool) -> bool {
879    use regs::*;
880    match r.class() {
881        RegClass::Int => match r.hw_enc() {
882            ENC_RBX | ENC_RBP | ENC_R12 | ENC_R13 | ENC_R14 => true,
883            // R15 is the pinned register; if we're using it that way,
884            // it is effectively globally-allocated, and is not
885            // callee-saved.
886            ENC_R15 => !enable_pinned_reg,
887            _ => false,
888        },
889        RegClass::Float => false,
890    }
891}
892
893fn is_callee_save_fastcall(r: RealReg, enable_pinned_reg: bool) -> bool {
894    use regs::*;
895    match r.class() {
896        RegClass::Int => match r.hw_enc() {
897            ENC_RBX | ENC_RBP | ENC_RSI | ENC_RDI | ENC_R12 | ENC_R13 | ENC_R14 => true,
898            // See above for SysV: we must treat the pinned reg specially.
899            ENC_R15 => !enable_pinned_reg,
900            _ => false,
901        },
902        RegClass::Float => match r.hw_enc() {
903            6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 => true,
904            _ => false,
905        },
906    }
907}
908
909fn compute_clobber_size(clobbers: &[Writable<RealReg>]) -> u32 {
910    let mut clobbered_size = 0;
911    for reg in clobbers {
912        match reg.to_reg().class() {
913            RegClass::Int => {
914                clobbered_size += 8;
915            }
916            RegClass::Float => {
917                clobbered_size = align_to(clobbered_size, 16);
918                clobbered_size += 16;
919            }
920        }
921    }
922    align_to(clobbered_size, 16)
923}
924
925const WINDOWS_CLOBBERS: PRegSet = windows_clobbers();
926const SYSV_CLOBBERS: PRegSet = sysv_clobbers();
927
928const fn windows_clobbers() -> PRegSet {
929    PRegSet::empty()
930        .with(regs::gpr_preg(regs::ENC_RAX))
931        .with(regs::gpr_preg(regs::ENC_RCX))
932        .with(regs::gpr_preg(regs::ENC_RDX))
933        .with(regs::gpr_preg(regs::ENC_R8))
934        .with(regs::gpr_preg(regs::ENC_R9))
935        .with(regs::gpr_preg(regs::ENC_R10))
936        .with(regs::gpr_preg(regs::ENC_R11))
937        .with(regs::fpr_preg(0))
938        .with(regs::fpr_preg(1))
939        .with(regs::fpr_preg(2))
940        .with(regs::fpr_preg(3))
941        .with(regs::fpr_preg(4))
942        .with(regs::fpr_preg(5))
943}
944
945const fn sysv_clobbers() -> PRegSet {
946    PRegSet::empty()
947        .with(regs::gpr_preg(regs::ENC_RAX))
948        .with(regs::gpr_preg(regs::ENC_RCX))
949        .with(regs::gpr_preg(regs::ENC_RDX))
950        .with(regs::gpr_preg(regs::ENC_RSI))
951        .with(regs::gpr_preg(regs::ENC_RDI))
952        .with(regs::gpr_preg(regs::ENC_R8))
953        .with(regs::gpr_preg(regs::ENC_R9))
954        .with(regs::gpr_preg(regs::ENC_R10))
955        .with(regs::gpr_preg(regs::ENC_R11))
956        .with(regs::fpr_preg(0))
957        .with(regs::fpr_preg(1))
958        .with(regs::fpr_preg(2))
959        .with(regs::fpr_preg(3))
960        .with(regs::fpr_preg(4))
961        .with(regs::fpr_preg(5))
962        .with(regs::fpr_preg(6))
963        .with(regs::fpr_preg(7))
964        .with(regs::fpr_preg(8))
965        .with(regs::fpr_preg(9))
966        .with(regs::fpr_preg(10))
967        .with(regs::fpr_preg(11))
968        .with(regs::fpr_preg(12))
969        .with(regs::fpr_preg(13))
970        .with(regs::fpr_preg(14))
971        .with(regs::fpr_preg(15))
972}