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