use crate::CodegenResult;
use crate::ir::MemFlagsData;
use crate::ir::{self, LibCall, Signature, TrapCode, types};
use crate::ir::{ExternalName, types::*};
use crate::isa;
use crate::isa::winch;
use crate::isa::{CallConv, unwind::UnwindInst, x64::inst::*, x64::settings as x64_settings};
use crate::machinst::*;
use crate::settings;
use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::vec::Vec;
use args::*;
use cranelift_assembler_x64 as asm;
use regalloc2::{MachineEnv, PReg, PRegSet};
use smallvec::{SmallVec, smallvec};
pub(crate) type X64Callee = Callee<X64ABIMachineSpec>;
pub struct X64ABIMachineSpec;
impl X64ABIMachineSpec {
fn gen_probestack_unroll(insts: &mut SmallInstVec<Inst>, guard_size: u32, probe_count: u32) {
insts.reserve(probe_count as usize);
for _ in 0..probe_count {
insts.extend(Self::gen_sp_reg_adjust(-(guard_size as i32)));
insts.push(Inst::External {
inst: asm::inst::movl_mi::new(Amode::imm_reg(0, regs::rsp()), 0i32.cast_unsigned())
.into(),
});
}
insts.extend(Self::gen_sp_reg_adjust((guard_size * probe_count) as i32));
}
fn gen_probestack_loop(
insts: &mut SmallInstVec<Inst>,
_call_conv: isa::CallConv,
frame_size: u32,
guard_size: u32,
) {
let tmp = regs::r11();
debug_assert!({
let real_reg = tmp.to_real_reg().unwrap();
!is_callee_save_systemv(real_reg, false) && !is_callee_save_fastcall(real_reg, false)
});
insts.push(Inst::StackProbeLoop {
tmp: Writable::from_reg(tmp),
frame_size,
guard_size,
});
}
}
impl IsaFlags for x64_settings::Flags {}
impl ABIMachineSpec for X64ABIMachineSpec {
type I = Inst;
type F = x64_settings::Flags;
const STACK_ARG_RET_SIZE_LIMIT: u32 = 128 * 1024 * 1024;
fn word_bits() -> u32 {
64
}
fn stack_align(_call_conv: isa::CallConv) -> u32 {
16
}
fn 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>)> {
let is_fastcall = call_conv == CallConv::WindowsFastcall;
let is_tail = call_conv == CallConv::Tail;
let mut next_gpr = 0;
let mut next_vreg = 0;
let mut next_stack: u32 = 0;
let mut next_param_idx = 0;
if args_or_rets == ArgsOrRets::Args && is_fastcall {
next_stack = 32;
}
let ret_area_ptr = if add_ret_area_ptr {
debug_assert_eq!(args_or_rets, ArgsOrRets::Args);
next_gpr += 1;
next_param_idx += 1;
Some(ABIArg::reg(
get_intreg_for_arg(call_conv, 0, 0)
.unwrap()
.to_real_reg()
.unwrap(),
types::I64,
ir::ArgumentExtension::None,
ir::ArgumentPurpose::Normal,
))
} else {
None
};
let uses_extension = params.iter().any(|p| {
p.extension != ir::ArgumentExtension::None
|| p.value_type == types::F16
|| p.value_type == types::I8X2
});
for (ix, param) in params.iter().enumerate() {
let last_param = ix == params.len() - 1;
if let ir::ArgumentPurpose::StructArgument(size) = param.purpose {
let offset = next_stack as i64;
let size = size;
assert!(size % 8 == 0, "StructArgument size is not properly aligned");
next_stack += size;
args.push(ABIArg::StructArg {
offset,
size: size as u64,
purpose: param.purpose,
});
continue;
}
let (rcs, reg_tys) = Inst::rc_for_type(param.value_type)?;
if param.value_type.bits() > 64
&& !(param.value_type.is_vector() || param.value_type.is_float())
&& !flags.enable_llvm_abi_extensions()
&& !is_tail
{
panic!(
"i128 args/return values not supported unless LLVM ABI extensions are enabled"
);
}
if matches!(param.value_type, types::F16 | types::F128)
&& is_fastcall
&& !flags.enable_llvm_abi_extensions()
{
panic!(
"f16/f128 args/return values not supported for windows_fastcall unless LLVM ABI extensions are enabled"
);
}
if (param.value_type.is_vector() || param.value_type.is_float())
&& param.value_type.bits() >= 128
&& args_or_rets == ArgsOrRets::Args
&& is_fastcall
{
let pointer = match get_intreg_for_arg(call_conv, next_gpr, next_param_idx) {
Some(reg) => {
next_gpr += 1;
ABIArgSlot::Reg {
reg: reg.to_real_reg().unwrap(),
ty: ir::types::I64,
extension: ir::ArgumentExtension::None,
}
}
None => {
next_stack = align_to(next_stack, 8) + 8;
ABIArgSlot::Stack {
offset: (next_stack - 8) as i64,
ty: ir::types::I64,
extension: param.extension,
}
}
};
next_param_idx += 1;
args.push(ABIArg::ImplicitPtrArg {
offset: 0,
pointer,
ty: param.value_type,
purpose: param.purpose,
});
continue;
}
if param.value_type == types::I128
&& args_or_rets == ArgsOrRets::Args
&& call_conv == CallConv::SystemV
{
let mut slots = ABIArgSlotVec::new();
match (
get_intreg_for_arg(CallConv::SystemV, next_gpr, next_param_idx),
get_intreg_for_arg(CallConv::SystemV, next_gpr + 1, next_param_idx + 1),
) {
(Some(reg1), Some(reg2)) => {
slots.push(ABIArgSlot::Reg {
reg: reg1.to_real_reg().unwrap(),
ty: ir::types::I64,
extension: ir::ArgumentExtension::None,
});
slots.push(ABIArgSlot::Reg {
reg: reg2.to_real_reg().unwrap(),
ty: ir::types::I64,
extension: ir::ArgumentExtension::None,
});
}
_ => {
let size = 16;
next_stack = align_to(next_stack, size);
slots.push(ABIArgSlot::Stack {
offset: next_stack as i64,
ty: ir::types::I64,
extension: param.extension,
});
slots.push(ABIArgSlot::Stack {
offset: next_stack as i64 + 8,
ty: ir::types::I64,
extension: param.extension,
});
next_stack += size;
}
};
next_gpr += 2;
next_param_idx += 2;
args.push(ABIArg::Slots {
slots,
purpose: param.purpose,
});
continue;
}
let mut slots = ABIArgSlotVec::new();
for (ix, (rc, reg_ty)) in rcs.iter().zip(reg_tys.iter()).enumerate() {
let last_slot = last_param && ix == rcs.len() - 1;
let intreg = *rc == RegClass::Int;
let nextreg = if intreg {
match args_or_rets {
ArgsOrRets::Args => get_intreg_for_arg(call_conv, next_gpr, next_param_idx),
ArgsOrRets::Rets => {
get_intreg_for_retval(call_conv, flags, next_gpr, last_slot)
}
}
} else {
match args_or_rets {
ArgsOrRets::Args => {
get_fltreg_for_arg(call_conv, next_vreg, next_param_idx)
}
ArgsOrRets::Rets => get_fltreg_for_retval(call_conv, next_vreg, last_slot),
}
};
next_param_idx += 1;
if let Some(reg) = nextreg {
if intreg {
next_gpr += 1;
} else {
next_vreg += 1;
}
slots.push(ABIArgSlot::Reg {
reg: reg.to_real_reg().unwrap(),
ty: *reg_ty,
extension: param.extension,
});
} else {
if args_or_rets == ArgsOrRets::Rets && !flags.enable_multi_ret_implicit_sret() {
return Err(crate::CodegenError::Unsupported(
"Too many return values to fit in registers. \
Use a StructReturn argument instead. (#9510)"
.to_owned(),
));
}
let size = reg_ty.bytes();
let size = if call_conv == CallConv::Winch
&& args_or_rets == ArgsOrRets::Rets
&& !uses_extension
{
size
} else {
let size = core::cmp::max(size, 8);
debug_assert!(size.is_power_of_two());
next_stack = align_to(next_stack, size);
size
};
slots.push(ABIArgSlot::Stack {
offset: next_stack as i64,
ty: *reg_ty,
extension: param.extension,
});
next_stack += size;
}
}
args.push(ABIArg::Slots {
slots,
purpose: param.purpose,
});
}
if args_or_rets == ArgsOrRets::Args && is_fastcall {
for arg in args.args_mut() {
if let ABIArg::ImplicitPtrArg { offset, .. } = arg {
assert_eq!(*offset, 0);
next_stack = align_to(next_stack, 16);
*offset = next_stack as i64;
next_stack += 16;
}
}
}
let extra_arg_idx = if let Some(ret_area_ptr) = ret_area_ptr {
args.push_non_formal(ret_area_ptr);
Some(args.args().len() - 1)
} else {
None
};
if call_conv == CallConv::Winch && args_or_rets == ArgsOrRets::Rets {
winch::reverse_stack(args, next_stack, uses_extension);
}
next_stack = align_to(next_stack, 16);
Ok((next_stack, extra_arg_idx))
}
fn gen_load_stack(mem: StackAMode, into_reg: Writable<Reg>, ty: Type) -> Self::I {
let ty = match ty {
types::I8 | types::I16 | types::I32 => types::I64,
types::F16 | types::I8X2 => types::F32,
_ => ty,
};
Inst::load(ty, mem, into_reg, ExtKind::None)
}
fn gen_store_stack(mem: StackAMode, from_reg: Reg, ty: Type) -> Self::I {
let ty = match ty {
types::F16 | types::I8X2 => types::F32,
_ => ty,
};
Inst::store(ty, from_reg, mem)
}
fn gen_move(to_reg: Writable<Reg>, from_reg: Reg, ty: Type) -> Self::I {
Inst::gen_move(to_reg, from_reg, ty)
}
fn gen_extend(
to_reg: Writable<Reg>,
from_reg: Reg,
is_signed: bool,
from_bits: u8,
to_bits: u8,
) -> Self::I {
let ext_mode = ExtMode::new(from_bits as u16, to_bits as u16)
.unwrap_or_else(|| panic!("invalid extension: {from_bits} -> {to_bits}"));
if is_signed {
Inst::movsx_rm_r(ext_mode, RegMem::reg(from_reg), to_reg)
} else {
Inst::movzx_rm_r(ext_mode, RegMem::reg(from_reg), to_reg)
}
}
fn gen_args(args: Vec<ArgPair>) -> Inst {
Inst::Args { args }
}
fn gen_rets(rets: Vec<RetPair>) -> Inst {
Inst::Rets { rets }
}
fn gen_add_imm(
_call_conv: isa::CallConv,
into_reg: Writable<Reg>,
from_reg: Reg,
imm: u32,
) -> SmallInstVec<Self::I> {
let mut ret = SmallVec::new();
if from_reg != into_reg.to_reg() {
ret.push(Inst::gen_move(into_reg, from_reg, I64));
}
let imm = i32::try_from(imm).expect("`imm` is too large to fit in a 32-bit immediate");
ret.push(Inst::addq_mi(into_reg, imm));
ret
}
fn gen_stack_lower_bound_trap(limit_reg: Reg) -> SmallInstVec<Self::I> {
smallvec![
Inst::External {
inst: asm::inst::cmpq_rm::new(Gpr::unwrap_new(limit_reg), Gpr::RSP,).into(),
},
Inst::TrapIf {
cc: CC::NBE,
trap_code: TrapCode::STACK_OVERFLOW,
},
]
}
fn gen_get_stack_addr(mem: StackAMode, into_reg: Writable<Reg>) -> Self::I {
let mem: SyntheticAmode = mem.into();
Inst::External {
inst: asm::inst::leaq_rm::new(into_reg, mem).into(),
}
}
fn get_stacklimit_reg(_call_conv: isa::CallConv) -> Reg {
debug_assert!(!is_callee_save_systemv(
regs::r10().to_real_reg().unwrap(),
false
));
regs::r10()
}
fn gen_load_base_offset(into_reg: Writable<Reg>, base: Reg, offset: i32, ty: Type) -> Self::I {
assert!(ty == I64 || ty.is_vector() || ty == F128);
let mem = Amode::imm_reg(offset, base);
Inst::load(ty, mem, into_reg, ExtKind::None)
}
fn gen_store_base_offset(base: Reg, offset: i32, from_reg: Reg, ty: Type) -> Self::I {
let ty = match ty {
types::F16 | types::I8X2 => types::F32,
_ => ty,
};
let mem = Amode::imm_reg(offset, base);
Inst::store(ty, from_reg, mem)
}
fn gen_sp_reg_adjust(amount: i32) -> SmallInstVec<Self::I> {
let rsp = Writable::from_reg(regs::rsp());
let inst = if amount >= 0 {
Inst::addq_mi(rsp, amount)
} else {
Inst::subq_mi(rsp, -amount)
};
smallvec![inst]
}
fn gen_prologue_frame_setup(
_call_conv: isa::CallConv,
flags: &settings::Flags,
_isa_flags: &x64_settings::Flags,
frame_layout: &FrameLayout,
) -> SmallInstVec<Self::I> {
let r_rsp = Gpr::RSP;
let r_rbp = Gpr::RBP;
let w_rbp = Writable::from_reg(r_rbp);
let mut insts = SmallVec::new();
insts.push(Inst::External {
inst: asm::inst::pushq_o::new(r_rbp).into(),
});
if flags.unwind_info() {
insts.push(Inst::Unwind {
inst: UnwindInst::PushFrameRegs {
offset_upward_to_caller_sp: frame_layout.setup_area_size,
},
});
}
insts.push(Inst::External {
inst: asm::inst::movq_mr::new(w_rbp, r_rsp).into(),
});
insts
}
fn gen_epilogue_frame_restore(
_call_conv: isa::CallConv,
_flags: &settings::Flags,
_isa_flags: &x64_settings::Flags,
_frame_layout: &FrameLayout,
) -> SmallInstVec<Self::I> {
let rbp = Gpr::RBP;
let rsp = Gpr::RSP;
let mut insts = SmallVec::new();
insts.push(Inst::External {
inst: asm::inst::movq_mr::new(Writable::from_reg(rsp), rbp).into(),
});
insts.push(Inst::External {
inst: asm::inst::popq_o::new(Writable::from_reg(rbp)).into(),
});
insts
}
fn gen_return(
call_conv: CallConv,
_isa_flags: &x64_settings::Flags,
frame_layout: &FrameLayout,
) -> SmallInstVec<Self::I> {
let stack_bytes_to_pop = if call_conv == CallConv::Tail {
frame_layout.tail_args_size
} else {
0
};
let inst = if stack_bytes_to_pop == 0 {
asm::inst::retq_zo::new().into()
} else {
let stack_bytes_to_pop = u16::try_from(stack_bytes_to_pop).unwrap();
asm::inst::retq_i::new(stack_bytes_to_pop).into()
};
smallvec![Inst::External { inst }]
}
fn gen_probestack(insts: &mut SmallInstVec<Self::I>, frame_size: u32) {
insts.push(Inst::imm(
OperandSize::Size32,
frame_size as u64,
Writable::from_reg(regs::rax()),
));
insts.push(Inst::CallKnown {
info: Box::new(CallInfo::empty(
ExternalName::LibCall(LibCall::Probestack),
CallConv::Probestack,
)),
});
}
fn gen_inline_probestack(
insts: &mut SmallInstVec<Self::I>,
call_conv: isa::CallConv,
frame_size: u32,
guard_size: u32,
) {
const PROBE_MAX_UNROLL: u32 = 4;
let probe_count = frame_size / guard_size;
if probe_count == 0 {
} else if probe_count <= PROBE_MAX_UNROLL {
Self::gen_probestack_unroll(insts, guard_size, probe_count)
} else {
Self::gen_probestack_loop(insts, call_conv, frame_size, guard_size)
}
}
fn gen_clobber_save(
_call_conv: isa::CallConv,
flags: &settings::Flags,
frame_layout: &FrameLayout,
) -> SmallVec<[Self::I; 16]> {
let mut insts = SmallVec::new();
let incoming_args_diff = frame_layout.tail_args_size - frame_layout.incoming_args_size;
if incoming_args_diff > 0 {
let rsp = Writable::from_reg(regs::rsp());
insts.push(Inst::subq_mi(
rsp,
i32::try_from(incoming_args_diff)
.expect("`incoming_args_diff` is too large to fit in a 32-bit immediate"),
));
let rbp = Gpr::RBP;
let rsp = Gpr::RSP;
insts.push(Inst::External {
inst: asm::inst::movq_mr::new(Writable::from_reg(rbp), rsp).into(),
});
let incoming_args_diff = i32::try_from(incoming_args_diff).unwrap();
let addr = Amode::imm_reg(incoming_args_diff, regs::rsp());
let r11 = Writable::from_reg(Gpr::R11);
let inst = asm::inst::movq_rm::new(r11, addr).into();
insts.push(Inst::External { inst });
let inst = asm::inst::movq_mr::new(Amode::imm_reg(0, regs::rsp()), r11.to_reg()).into();
insts.push(Inst::External { inst });
let addr = Amode::imm_reg(incoming_args_diff + 8, regs::rsp());
let inst = asm::inst::movq_rm::new(r11, addr).into();
insts.push(Inst::External { inst });
let inst = asm::inst::movq_mr::new(Amode::imm_reg(8, regs::rsp()), r11.to_reg()).into();
insts.push(Inst::External { inst });
}
let offset_upward_to_caller_sp = frame_layout.setup_area_size + incoming_args_diff;
if flags.unwind_info() && offset_upward_to_caller_sp > 0 {
insts.push(Inst::Unwind {
inst: UnwindInst::DefineNewFrame {
offset_downward_to_clobbers: frame_layout.clobber_size,
offset_upward_to_caller_sp,
},
});
}
let stack_size = frame_layout.fixed_frame_storage_size
+ frame_layout.clobber_size
+ frame_layout.outgoing_args_size;
if stack_size > 0 {
let rsp = Writable::from_reg(regs::rsp());
let stack_size = i32::try_from(stack_size)
.expect("`stack_size` is too large to fit in a 32-bit immediate");
insts.push(Inst::subq_mi(rsp, stack_size));
}
let clobber_offset =
frame_layout.fixed_frame_storage_size + frame_layout.outgoing_args_size;
let mut cur_offset = 0;
for reg in &frame_layout.clobbered_callee_saves {
let r_reg = reg.to_reg();
let ty = match r_reg.class() {
RegClass::Int => types::I64,
RegClass::Float => types::I8X16,
RegClass::Vector => unreachable!(),
};
cur_offset = align_to(cur_offset, ty.bytes());
let off = cur_offset;
cur_offset += ty.bytes();
insts.push(Inst::store(
ty,
r_reg.into(),
Amode::imm_reg(i32::try_from(off + clobber_offset).unwrap(), regs::rsp()),
));
if flags.unwind_info() {
insts.push(Inst::Unwind {
inst: UnwindInst::SaveReg {
clobber_offset: off,
reg: r_reg,
},
});
}
}
insts
}
fn gen_clobber_restore(
_call_conv: isa::CallConv,
_flags: &settings::Flags,
frame_layout: &FrameLayout,
) -> SmallVec<[Self::I; 16]> {
let mut insts = SmallVec::new();
let mut cur_offset =
frame_layout.fixed_frame_storage_size + frame_layout.outgoing_args_size;
for reg in &frame_layout.clobbered_callee_saves {
let rreg = reg.to_reg();
let ty = match rreg.class() {
RegClass::Int => types::I64,
RegClass::Float => types::I8X16,
RegClass::Vector => unreachable!(),
};
cur_offset = align_to(cur_offset, ty.bytes());
insts.push(Inst::load(
ty,
Amode::imm_reg(cur_offset.try_into().unwrap(), regs::rsp()),
Writable::from_reg(rreg.into()),
ExtKind::None,
));
cur_offset += ty.bytes();
}
let stack_size = frame_layout.fixed_frame_storage_size
+ frame_layout.clobber_size
+ frame_layout.outgoing_args_size;
if stack_size > 0 {
let rsp = Writable::from_reg(regs::rsp());
let stack_size = i32::try_from(stack_size)
.expect("`stack_size` is too large to fit in a 32-bit immediate");
insts.push(Inst::addq_mi(rsp, stack_size));
}
insts
}
fn 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]> {
let mut insts = SmallVec::new();
let arg0 = get_intreg_for_arg(call_conv, 0, 0).unwrap();
let arg1 = get_intreg_for_arg(call_conv, 1, 1).unwrap();
let arg2 = get_intreg_for_arg(call_conv, 2, 2).unwrap();
let temp = alloc_tmp(Self::word_type());
let temp2 = alloc_tmp(Self::word_type());
insts.push(Inst::imm(OperandSize::Size64, size as u64, temp));
insts.push(Inst::LoadExtName {
dst: temp2.map(Gpr::unwrap_new),
name: Box::new(ExternalName::LibCall(LibCall::Memcpy)),
offset: 0,
distance: RelocDistance::Far,
});
let callee_pop_size = 0;
insts.push(Inst::call_unknown(Box::new(CallInfo {
dest: RegMem::reg(temp2.to_reg()),
uses: smallvec![
CallArgPair {
vreg: dst,
preg: arg0
},
CallArgPair {
vreg: src,
preg: arg1
},
CallArgPair {
vreg: temp.to_reg(),
preg: arg2
},
],
defs: smallvec![],
clobbers: Self::get_regs_clobbered_by_call(call_conv, false),
callee_pop_size,
callee_conv: call_conv,
caller_conv: call_conv,
try_call_info: None,
patchable: false,
})));
insts
}
fn get_number_of_spillslots_for_value(
rc: RegClass,
vector_scale: u32,
_isa_flags: &Self::F,
) -> u32 {
match rc {
RegClass::Int => 1,
RegClass::Float => vector_scale / 8,
RegClass::Vector => unreachable!(),
}
}
fn get_machine_env(flags: &settings::Flags, _call_conv: isa::CallConv) -> &MachineEnv {
if flags.enable_pinned_reg() {
static MACHINE_ENV: MachineEnv = create_reg_env_systemv(true);
&MACHINE_ENV
} else {
static MACHINE_ENV: MachineEnv = create_reg_env_systemv(false);
&MACHINE_ENV
}
}
fn get_regs_clobbered_by_call(
call_conv_of_callee: isa::CallConv,
is_exception: bool,
) -> PRegSet {
match (call_conv_of_callee, is_exception) {
(isa::CallConv::Tail, true) => ALL_CLOBBERS,
(isa::CallConv::PreserveAll, true) => ALL_CLOBBERS,
(isa::CallConv::Winch, _) => ALL_CLOBBERS,
(isa::CallConv::SystemV, _) => SYSV_CLOBBERS,
(isa::CallConv::WindowsFastcall, false) => WINDOWS_CLOBBERS,
(isa::CallConv::PreserveAll, _) => NO_CLOBBERS,
(_, false) => SYSV_CLOBBERS,
(call_conv, true) => panic!("unimplemented clobbers for exn abi of {call_conv:?}"),
}
}
fn get_ext_mode(
_call_conv: isa::CallConv,
specified: ir::ArgumentExtension,
) -> ir::ArgumentExtension {
specified
}
fn 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,
) -> FrameLayout {
debug_assert!(tail_args_size >= incoming_args_size);
let mut regs: Vec<Writable<RealReg>> = match call_conv {
CallConv::Winch => vec![],
CallConv::Fast | CallConv::SystemV | CallConv::Tail => regs
.iter()
.cloned()
.filter(|r| is_callee_save_systemv(r.to_reg(), flags.enable_pinned_reg()))
.collect(),
CallConv::WindowsFastcall => regs
.iter()
.cloned()
.filter(|r| is_callee_save_fastcall(r.to_reg(), flags.enable_pinned_reg()))
.collect(),
CallConv::PreserveAll => regs.iter().cloned().collect(),
CallConv::Probestack => todo!("probestack?"),
CallConv::AppleAarch64 => unreachable!(),
};
regs.sort_unstable();
let clobber_size = compute_clobber_size(®s);
let setup_area_size = 16;
FrameLayout {
word_bytes: 8,
incoming_args_size,
tail_args_size: align_to(tail_args_size, 16),
setup_area_size,
clobber_size,
fixed_frame_storage_size,
stackslots_size,
outgoing_args_size,
clobbered_callee_saves: regs,
function_calls,
}
}
fn retval_temp_reg(_call_conv_of_callee: isa::CallConv) -> Writable<Reg> {
Writable::from_reg(regs::r11())
}
fn exception_payload_regs(call_conv: isa::CallConv) -> &'static [Reg] {
const PAYLOAD_REGS: &'static [Reg] = &[regs::rax(), regs::rdx()];
match call_conv {
isa::CallConv::SystemV | isa::CallConv::Tail | isa::CallConv::PreserveAll => {
PAYLOAD_REGS
}
_ => &[],
}
}
}
impl From<StackAMode> for SyntheticAmode {
fn from(amode: StackAMode) -> Self {
match amode {
StackAMode::IncomingArg(off, stack_args_size) => {
let offset = u32::try_from(off).expect(
"Offset in IncomingArg is greater than 4GB; should hit impl limit first",
);
SyntheticAmode::IncomingArg {
offset: stack_args_size - offset,
}
}
StackAMode::Slot(off) => {
let off = i32::try_from(off)
.expect("Offset in Slot is greater than 2GB; should hit impl limit first");
SyntheticAmode::slot_offset(off)
}
StackAMode::OutgoingArg(off) => {
let off = i32::try_from(off).expect(
"Offset in OutgoingArg is greater than 2GB; should hit impl limit first",
);
SyntheticAmode::Real(Amode::ImmReg {
simm32: off,
base: regs::rsp(),
flags: MemFlagsData::trusted(),
})
}
}
}
}
fn get_intreg_for_arg(call_conv: CallConv, idx: usize, arg_idx: usize) -> Option<Reg> {
let is_fastcall = call_conv == CallConv::WindowsFastcall;
let i = if is_fastcall { arg_idx } else { idx };
match (i, is_fastcall) {
(0, false) => Some(regs::rdi()),
(1, false) => Some(regs::rsi()),
(2, false) => Some(regs::rdx()),
(3, false) => Some(regs::rcx()),
(4, false) => Some(regs::r8()),
(5, false) => Some(regs::r9()),
(0, true) => Some(regs::rcx()),
(1, true) => Some(regs::rdx()),
(2, true) => Some(regs::r8()),
(3, true) => Some(regs::r9()),
_ => None,
}
}
fn get_fltreg_for_arg(call_conv: CallConv, idx: usize, arg_idx: usize) -> Option<Reg> {
let is_fastcall = call_conv == CallConv::WindowsFastcall;
let i = if is_fastcall { arg_idx } else { idx };
match (i, is_fastcall) {
(0, false) => Some(regs::xmm0()),
(1, false) => Some(regs::xmm1()),
(2, false) => Some(regs::xmm2()),
(3, false) => Some(regs::xmm3()),
(4, false) => Some(regs::xmm4()),
(5, false) => Some(regs::xmm5()),
(6, false) => Some(regs::xmm6()),
(7, false) => Some(regs::xmm7()),
(0, true) => Some(regs::xmm0()),
(1, true) => Some(regs::xmm1()),
(2, true) => Some(regs::xmm2()),
(3, true) => Some(regs::xmm3()),
_ => None,
}
}
fn get_intreg_for_retval(
call_conv: CallConv,
flags: &settings::Flags,
intreg_idx: usize,
is_last: bool,
) -> Option<Reg> {
match call_conv {
CallConv::Tail => match intreg_idx {
0 => Some(regs::rax()),
1 => Some(regs::rcx()),
2 => Some(regs::rdx()),
3 => Some(regs::rsi()),
4 => Some(regs::rdi()),
5 => Some(regs::r8()),
6 => Some(regs::r9()),
7 => Some(regs::r10()),
_ => None,
},
CallConv::Fast | CallConv::SystemV | CallConv::PreserveAll => match intreg_idx {
0 => Some(regs::rax()),
1 => Some(regs::rdx()),
2 if flags.enable_llvm_abi_extensions() => Some(regs::rcx()),
_ => None,
},
CallConv::WindowsFastcall => match intreg_idx {
0 => Some(regs::rax()),
1 => Some(regs::rdx()), _ => None,
},
CallConv::Winch => is_last.then(|| regs::rax()),
CallConv::Probestack => todo!(),
CallConv::AppleAarch64 => unreachable!(),
}
}
fn get_fltreg_for_retval(call_conv: CallConv, fltreg_idx: usize, is_last: bool) -> Option<Reg> {
match call_conv {
CallConv::Tail => match fltreg_idx {
0 => Some(regs::xmm0()),
1 => Some(regs::xmm1()),
2 => Some(regs::xmm2()),
3 => Some(regs::xmm3()),
4 => Some(regs::xmm4()),
5 => Some(regs::xmm5()),
6 => Some(regs::xmm6()),
7 => Some(regs::xmm7()),
_ => None,
},
CallConv::Fast | CallConv::SystemV | CallConv::PreserveAll => match fltreg_idx {
0 => Some(regs::xmm0()),
1 => Some(regs::xmm1()),
_ => None,
},
CallConv::WindowsFastcall => match fltreg_idx {
0 => Some(regs::xmm0()),
_ => None,
},
CallConv::Winch => is_last.then(|| regs::xmm0()),
CallConv::Probestack => todo!(),
CallConv::AppleAarch64 => unreachable!(),
}
}
fn is_callee_save_systemv(r: RealReg, enable_pinned_reg: bool) -> bool {
use asm::gpr::enc::*;
match r.class() {
RegClass::Int => match r.hw_enc() {
RBX | RBP | R12 | R13 | R14 => true,
R15 => !enable_pinned_reg,
_ => false,
},
RegClass::Float => false,
RegClass::Vector => unreachable!(),
}
}
fn is_callee_save_fastcall(r: RealReg, enable_pinned_reg: bool) -> bool {
use asm::gpr::enc::*;
use asm::xmm::enc::*;
match r.class() {
RegClass::Int => match r.hw_enc() {
RBX | RBP | RSI | RDI | R12 | R13 | R14 => true,
R15 => !enable_pinned_reg,
_ => false,
},
RegClass::Float => match r.hw_enc() {
XMM6 | XMM7 | XMM8 | XMM9 | XMM10 | XMM11 | XMM12 | XMM13 | XMM14 | XMM15 => true,
_ => false,
},
RegClass::Vector => unreachable!(),
}
}
fn compute_clobber_size(clobbers: &[Writable<RealReg>]) -> u32 {
let mut clobbered_size = 0;
for reg in clobbers {
match reg.to_reg().class() {
RegClass::Int => {
clobbered_size += 8;
}
RegClass::Float => {
clobbered_size = align_to(clobbered_size, 16);
clobbered_size += 16;
}
RegClass::Vector => unreachable!(),
}
}
align_to(clobbered_size, 16)
}
const WINDOWS_CLOBBERS: PRegSet = windows_clobbers();
const SYSV_CLOBBERS: PRegSet = sysv_clobbers();
pub(crate) const ALL_CLOBBERS: PRegSet = all_clobbers();
const NO_CLOBBERS: PRegSet = PRegSet::empty();
const fn windows_clobbers() -> PRegSet {
use asm::gpr::enc::*;
use asm::xmm::enc::*;
PRegSet::empty()
.with(regs::gpr_preg(RAX))
.with(regs::gpr_preg(RCX))
.with(regs::gpr_preg(RDX))
.with(regs::gpr_preg(R8))
.with(regs::gpr_preg(R9))
.with(regs::gpr_preg(R10))
.with(regs::gpr_preg(R11))
.with(regs::fpr_preg(XMM0))
.with(regs::fpr_preg(XMM1))
.with(regs::fpr_preg(XMM2))
.with(regs::fpr_preg(XMM3))
.with(regs::fpr_preg(XMM4))
.with(regs::fpr_preg(XMM5))
}
const fn sysv_clobbers() -> PRegSet {
use asm::gpr::enc::*;
use asm::xmm::enc::*;
PRegSet::empty()
.with(regs::gpr_preg(RAX))
.with(regs::gpr_preg(RCX))
.with(regs::gpr_preg(RDX))
.with(regs::gpr_preg(RSI))
.with(regs::gpr_preg(RDI))
.with(regs::gpr_preg(R8))
.with(regs::gpr_preg(R9))
.with(regs::gpr_preg(R10))
.with(regs::gpr_preg(R11))
.with(regs::fpr_preg(XMM0))
.with(regs::fpr_preg(XMM1))
.with(regs::fpr_preg(XMM2))
.with(regs::fpr_preg(XMM3))
.with(regs::fpr_preg(XMM4))
.with(regs::fpr_preg(XMM5))
.with(regs::fpr_preg(XMM6))
.with(regs::fpr_preg(XMM7))
.with(regs::fpr_preg(XMM8))
.with(regs::fpr_preg(XMM9))
.with(regs::fpr_preg(XMM10))
.with(regs::fpr_preg(XMM11))
.with(regs::fpr_preg(XMM12))
.with(regs::fpr_preg(XMM13))
.with(regs::fpr_preg(XMM14))
.with(regs::fpr_preg(XMM15))
}
const fn all_clobbers() -> PRegSet {
use asm::gpr::enc::*;
use asm::xmm::enc::*;
PRegSet::empty()
.with(regs::gpr_preg(RAX))
.with(regs::gpr_preg(RCX))
.with(regs::gpr_preg(RDX))
.with(regs::gpr_preg(RBX))
.with(regs::gpr_preg(RSI))
.with(regs::gpr_preg(RDI))
.with(regs::gpr_preg(R8))
.with(regs::gpr_preg(R9))
.with(regs::gpr_preg(R10))
.with(regs::gpr_preg(R11))
.with(regs::gpr_preg(R12))
.with(regs::gpr_preg(R13))
.with(regs::gpr_preg(R14))
.with(regs::gpr_preg(R15))
.with(regs::fpr_preg(XMM0))
.with(regs::fpr_preg(XMM1))
.with(regs::fpr_preg(XMM2))
.with(regs::fpr_preg(XMM3))
.with(regs::fpr_preg(XMM4))
.with(regs::fpr_preg(XMM5))
.with(regs::fpr_preg(XMM6))
.with(regs::fpr_preg(XMM7))
.with(regs::fpr_preg(XMM8))
.with(regs::fpr_preg(XMM9))
.with(regs::fpr_preg(XMM10))
.with(regs::fpr_preg(XMM11))
.with(regs::fpr_preg(XMM12))
.with(regs::fpr_preg(XMM13))
.with(regs::fpr_preg(XMM14))
.with(regs::fpr_preg(XMM15))
}
const fn create_reg_env_systemv(enable_pinned_reg: bool) -> MachineEnv {
const fn preg(r: Reg) -> PReg {
r.to_real_reg().unwrap().preg()
}
let mut env = MachineEnv {
preferred_regs_by_class: [
PRegSet::empty()
.with(preg(regs::rsi()))
.with(preg(regs::rdi()))
.with(preg(regs::rax()))
.with(preg(regs::rcx()))
.with(preg(regs::rdx()))
.with(preg(regs::r8()))
.with(preg(regs::r9()))
.with(preg(regs::r10()))
.with(preg(regs::r11())),
PRegSet::empty()
.with(preg(regs::xmm0()))
.with(preg(regs::xmm1()))
.with(preg(regs::xmm2()))
.with(preg(regs::xmm3()))
.with(preg(regs::xmm4()))
.with(preg(regs::xmm5()))
.with(preg(regs::xmm6()))
.with(preg(regs::xmm7())),
PRegSet::empty(),
],
non_preferred_regs_by_class: [
PRegSet::empty()
.with(preg(regs::rbx()))
.with(preg(regs::r12()))
.with(preg(regs::r13()))
.with(preg(regs::r14())),
PRegSet::empty()
.with(preg(regs::xmm8()))
.with(preg(regs::xmm9()))
.with(preg(regs::xmm10()))
.with(preg(regs::xmm11()))
.with(preg(regs::xmm12()))
.with(preg(regs::xmm13()))
.with(preg(regs::xmm14()))
.with(preg(regs::xmm15())),
PRegSet::empty(),
],
fixed_stack_slots: vec![],
scratch_by_class: [None, None, None],
};
debug_assert!(regs::PINNED_REG == cranelift_assembler_x64::gpr::enc::R15);
if !enable_pinned_reg {
env.non_preferred_regs_by_class[0] =
env.non_preferred_regs_by_class[0].with(preg(regs::r15()));
}
env
}
#[cfg(test)]
mod tests {
use super::*;
use crate::machinst::abi::Callee;
use alloc::vec::Vec;
fn make_frame_layout(total_relevant_fields_sum: u32) -> FrameLayout {
FrameLayout {
word_bytes: 8,
incoming_args_size: 0,
tail_args_size: 0,
setup_area_size: 0,
clobber_size: 0,
fixed_frame_storage_size: total_relevant_fields_sum,
stackslots_size: 0,
outgoing_args_size: 0,
clobbered_callee_saves: Vec::new(),
function_calls: crate::machinst::FunctionCalls::None,
}
}
const ONE_GIB: u32 = 1 << 30;
#[test]
fn frame_layout_under_limit_is_accepted() {
let layout = make_frame_layout(ONE_GIB - 1);
assert!(!Callee::<X64ABIMachineSpec>::frame_layout_exceeds_limit(
&layout, ONE_GIB
));
}
#[test]
fn frame_layout_at_exact_limit_is_accepted() {
let layout = make_frame_layout(ONE_GIB);
assert!(!Callee::<X64ABIMachineSpec>::frame_layout_exceeds_limit(
&layout, ONE_GIB
));
}
#[test]
fn frame_layout_over_limit_is_rejected() {
let layout = make_frame_layout(ONE_GIB + 1);
assert!(Callee::<X64ABIMachineSpec>::frame_layout_exceeds_limit(
&layout, ONE_GIB
));
}
}