1use 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
20pub(crate) type X64Callee = Callee<X64ABIMachineSpec>;
22
23pub 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 insts.extend(Self::gen_sp_reg_adjust(-(guard_size as i32)));
34
35 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 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 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 const STACK_ARG_RET_SIZE_LIMIT: u32 = 128 * 1024 * 1024;
83
84 fn word_bits() -> u32 {
85 64
86 }
87
88 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; if args_or_rets == ArgsOrRets::Args && is_fastcall {
110 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 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 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 let (rcs, reg_tys) = Inst::rc_for_type(¶m.value_type)?;
176
177 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 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 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 offset: 0,
254 pointer,
255 ty: param.value_type,
256 purpose: param.purpose,
257 });
258 continue;
259 }
260
261 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 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 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 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 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 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 let ty = match ty {
430 types::I8 | types::I16 | types::I32 => types::I64,
431 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 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 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 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 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 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 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 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 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 insts.push(Inst::External {
595 inst: asm::inst::movq_mr::new(Writable::from_reg(rsp), rbp).into(),
596 });
597 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 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 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 const PROBE_MAX_UNROLL: u32 = 4;
651
652 let probe_count = frame_size / guard_size;
655 if probe_count == 0 {
656 } else if probe_count <= PROBE_MAX_UNROLL || call_conv == isa::CallConv::PreserveAll {
658 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 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 let incoming_args_diff = frame_layout.tail_args_size - frame_layout.incoming_args_size;
698 if incoming_args_diff > 0 {
699 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 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 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 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 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 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 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 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 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 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 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 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 (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 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 CallConv::PreserveAll => regs.iter().cloned().collect(),
963 CallConv::Probestack => todo!("probestack?"),
964 CallConv::AppleAarch64 => unreachable!(),
965 };
966 regs.sort_unstable();
969
970 let clobber_size = compute_clobber_size(®s);
972
973 let setup_area_size = 16; 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 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 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 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 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 _ => 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()), _ => 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()), _ => 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 => !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 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
1263const 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 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 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 PRegSet::empty(),
1332 ],
1333 non_preferred_regs_by_class: [
1334 PRegSet::empty()
1336 .with(preg(regs::rbx()))
1337 .with(preg(regs::r12()))
1338 .with(preg(regs::r13()))
1339 .with(preg(regs::r14())),
1340 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 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}