use crate::ir;
use crate::ir::types;
use crate::ir::types::*;
use crate::ir::MemFlags;
use crate::ir::Opcode;
use crate::ir::{dynamic_to_fixed, ExternalName, LibCall, Signature};
use crate::isa;
use crate::isa::aarch64::{inst::EmitState, inst::*, settings as aarch64_settings};
use crate::isa::unwind::UnwindInst;
use crate::machinst::*;
use crate::settings;
use crate::{CodegenError, CodegenResult};
use alloc::boxed::Box;
use alloc::vec::Vec;
use regalloc2::{MachineEnv, PReg, PRegSet, VReg};
use smallvec::{smallvec, SmallVec};
use std::sync::OnceLock;
pub(crate) type AArch64Callee = Callee<AArch64MachineDeps>;
pub(crate) type AArch64CallSite = CallSite<AArch64MachineDeps>;
static STACK_ARG_RET_SIZE_LIMIT: u32 = 128 * 1024 * 1024;
impl Into<AMode> for StackAMode {
fn into(self) -> AMode {
match self {
StackAMode::FPOffset(off, ty) => AMode::FPOffset { off, ty },
StackAMode::NominalSPOffset(off, ty) => AMode::NominalSPOffset { off, ty },
StackAMode::SPOffset(off, ty) => AMode::SPOffset { off, ty },
}
}
}
fn compute_clobber_size(clobbered_callee_saves: &[Writable<RealReg>]) -> u32 {
let mut int_regs = 0;
let mut vec_regs = 0;
for ® in clobbered_callee_saves {
match reg.to_reg().class() {
RegClass::Int => {
int_regs += 1;
}
RegClass::Float => {
vec_regs += 1;
}
RegClass::Vector => unreachable!(),
}
}
let int_save_bytes = (int_regs + (int_regs & 1)) * 8;
let vec_reg_size = 8;
let vec_save_padding = vec_regs & 1;
let vec_save_bytes = (vec_regs + vec_save_padding) * vec_reg_size;
int_save_bytes + vec_save_bytes
}
pub struct AArch64MachineDeps;
impl IsaFlags for aarch64_settings::Flags {
fn is_forward_edge_cfi_enabled(&self) -> bool {
self.use_bti()
}
}
impl ABIMachineSpec for AArch64MachineDeps {
type I = Inst;
type F = aarch64_settings::Flags;
fn word_bits() -> u32 {
64
}
fn stack_align(_call_conv: isa::CallConv) -> u32 {
16
}
fn compute_arg_locs<'a, I>(
call_conv: isa::CallConv,
_flags: &settings::Flags,
params: I,
args_or_rets: ArgsOrRets,
add_ret_area_ptr: bool,
mut args: ArgsAccumulator<'_>,
) -> CodegenResult<(u32, Option<usize>)>
where
I: IntoIterator<Item = &'a ir::AbiParam>,
{
if call_conv == isa::CallConv::Tail {
return compute_arg_locs_tail(params, add_ret_area_ptr, args);
}
let is_apple_cc = call_conv.extends_apple_aarch64();
let mut next_xreg = 0;
let mut next_vreg = 0;
let mut next_stack: u32 = 0;
let (max_per_class_reg_vals, mut remaining_reg_vals) = match args_or_rets {
ArgsOrRets::Args => (8, 16),
ArgsOrRets::Rets => {
(8, 16) }
};
for param in params {
assert!(
legal_type_for_machine(param.value_type),
"Invalid type for AArch64: {:?}",
param.value_type
);
let (rcs, reg_types) = Inst::rc_for_type(param.value_type)?;
if let ir::ArgumentPurpose::StructArgument(size) = param.purpose {
assert_eq!(args_or_rets, ArgsOrRets::Args);
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 {
pointer: None,
offset,
size: size as u64,
purpose: param.purpose,
});
continue;
}
if let ir::ArgumentPurpose::StructReturn = param.purpose {
assert!(
param.value_type == types::I64,
"StructReturn must be a pointer sized integer"
);
args.push(ABIArg::Slots {
slots: smallvec![ABIArgSlot::Reg {
reg: xreg(8).to_real_reg().unwrap(),
ty: types::I64,
extension: param.extension,
},],
purpose: ir::ArgumentPurpose::StructReturn,
});
continue;
}
let is_multi_reg = rcs.len() >= 2;
if is_multi_reg {
assert!(
rcs.len() == 2,
"Unable to handle multi reg params with more than 2 regs"
);
assert!(
rcs == &[RegClass::Int, RegClass::Int],
"Unable to handle non i64 regs"
);
let reg_class_space = max_per_class_reg_vals - next_xreg;
let reg_space = remaining_reg_vals;
if reg_space >= 2 && reg_class_space >= 2 {
if !is_apple_cc && next_xreg % 2 != 0 {
next_xreg += 1;
}
let lower_reg = xreg(next_xreg);
let upper_reg = xreg(next_xreg + 1);
args.push(ABIArg::Slots {
slots: smallvec![
ABIArgSlot::Reg {
reg: lower_reg.to_real_reg().unwrap(),
ty: reg_types[0],
extension: param.extension,
},
ABIArgSlot::Reg {
reg: upper_reg.to_real_reg().unwrap(),
ty: reg_types[1],
extension: param.extension,
},
],
purpose: param.purpose,
});
next_xreg += 2;
remaining_reg_vals -= 2;
continue;
}
} else {
let rc = rcs[0];
let next_reg = match rc {
RegClass::Int => &mut next_xreg,
RegClass::Float => &mut next_vreg,
RegClass::Vector => unreachable!(),
};
if *next_reg < max_per_class_reg_vals && remaining_reg_vals > 0 {
let reg = match rc {
RegClass::Int => xreg(*next_reg),
RegClass::Float => vreg(*next_reg),
RegClass::Vector => unreachable!(),
};
let ty = if param.value_type.is_dynamic_vector() {
dynamic_to_fixed(param.value_type)
} else {
param.value_type
};
args.push(ABIArg::reg(
reg.to_real_reg().unwrap(),
ty,
param.extension,
param.purpose,
));
*next_reg += 1;
remaining_reg_vals -= 1;
continue;
}
}
let size = (ty_bits(param.value_type) / 8) as u32;
let size = if is_apple_cc {
size
} else {
std::cmp::max(size, 8)
};
debug_assert!(size.is_power_of_two());
next_stack = align_to(next_stack, size);
let slots = reg_types
.iter()
.copied()
.scan(next_stack, |next_stack, ty| {
let slot_offset = *next_stack as i64;
*next_stack += (ty_bits(ty) / 8) as u32;
Some((ty, slot_offset))
})
.map(|(ty, offset)| ABIArgSlot::Stack {
offset,
ty,
extension: param.extension,
})
.collect();
args.push(ABIArg::Slots {
slots,
purpose: param.purpose,
});
next_stack += size;
}
let extra_arg = if add_ret_area_ptr {
debug_assert!(args_or_rets == ArgsOrRets::Args);
if next_xreg < max_per_class_reg_vals && remaining_reg_vals > 0 {
args.push_non_formal(ABIArg::reg(
xreg(next_xreg).to_real_reg().unwrap(),
I64,
ir::ArgumentExtension::None,
ir::ArgumentPurpose::Normal,
));
} else {
args.push_non_formal(ABIArg::stack(
next_stack as i64,
I64,
ir::ArgumentExtension::None,
ir::ArgumentPurpose::Normal,
));
next_stack += 8;
}
Some(args.args().len() - 1)
} else {
None
};
next_stack = align_to(next_stack, 16);
if next_stack > STACK_ARG_RET_SIZE_LIMIT {
return Err(CodegenError::ImplLimitExceeded);
}
Ok((next_stack, extra_arg))
}
fn fp_to_arg_offset(_call_conv: isa::CallConv, _flags: &settings::Flags) -> i64 {
16 }
fn gen_load_stack(mem: StackAMode, into_reg: Writable<Reg>, ty: Type) -> Inst {
Inst::gen_load(into_reg, mem.into(), ty, MemFlags::trusted())
}
fn gen_store_stack(mem: StackAMode, from_reg: Reg, ty: Type) -> Inst {
Inst::gen_store(mem.into(), from_reg, ty, MemFlags::trusted())
}
fn gen_move(to_reg: Writable<Reg>, from_reg: Reg, ty: Type) -> Inst {
Inst::gen_move(to_reg, from_reg, ty)
}
fn gen_extend(
to_reg: Writable<Reg>,
from_reg: Reg,
signed: bool,
from_bits: u8,
to_bits: u8,
) -> Inst {
assert!(from_bits < to_bits);
Inst::Extend {
rd: to_reg,
rn: from_reg,
signed,
from_bits,
to_bits,
}
}
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<Inst> {
let imm = imm as u64;
let mut insts = SmallVec::new();
if let Some(imm12) = Imm12::maybe_from_u64(imm) {
insts.push(Inst::AluRRImm12 {
alu_op: ALUOp::Add,
size: OperandSize::Size64,
rd: into_reg,
rn: from_reg,
imm12,
});
} else {
let scratch2 = writable_tmp2_reg();
assert_ne!(scratch2.to_reg(), from_reg);
insts.extend(Inst::load_constant(scratch2, imm.into(), &mut |_| scratch2));
insts.push(Inst::AluRRRExtend {
alu_op: ALUOp::Add,
size: OperandSize::Size64,
rd: into_reg,
rn: from_reg,
rm: scratch2.to_reg(),
extendop: ExtendOp::UXTX,
});
}
insts
}
fn gen_stack_lower_bound_trap(limit_reg: Reg) -> SmallInstVec<Inst> {
let mut insts = SmallVec::new();
insts.push(Inst::AluRRRExtend {
alu_op: ALUOp::SubS,
size: OperandSize::Size64,
rd: writable_zero_reg(),
rn: stack_reg(),
rm: limit_reg,
extendop: ExtendOp::UXTX,
});
insts.push(Inst::TrapIf {
trap_code: ir::TrapCode::StackOverflow,
kind: CondBrKind::Cond(Cond::Lo),
});
insts
}
fn gen_get_stack_addr(mem: StackAMode, into_reg: Writable<Reg>, _ty: Type) -> Inst {
let mem = mem.into();
Inst::LoadAddr { rd: into_reg, mem }
}
fn get_stacklimit_reg(_call_conv: isa::CallConv) -> Reg {
spilltmp_reg()
}
fn gen_load_base_offset(into_reg: Writable<Reg>, base: Reg, offset: i32, ty: Type) -> Inst {
let mem = AMode::RegOffset {
rn: base,
off: offset as i64,
ty,
};
Inst::gen_load(into_reg, mem, ty, MemFlags::trusted())
}
fn gen_store_base_offset(base: Reg, offset: i32, from_reg: Reg, ty: Type) -> Inst {
let mem = AMode::RegOffset {
rn: base,
off: offset as i64,
ty,
};
Inst::gen_store(mem, from_reg, ty, MemFlags::trusted())
}
fn gen_sp_reg_adjust(amount: i32) -> SmallInstVec<Inst> {
if amount == 0 {
return SmallVec::new();
}
let (amount, is_sub) = if amount > 0 {
(amount as u64, false)
} else {
(-amount as u64, true)
};
let alu_op = if is_sub { ALUOp::Sub } else { ALUOp::Add };
let mut ret = SmallVec::new();
if let Some(imm12) = Imm12::maybe_from_u64(amount) {
let adj_inst = Inst::AluRRImm12 {
alu_op,
size: OperandSize::Size64,
rd: writable_stack_reg(),
rn: stack_reg(),
imm12,
};
ret.push(adj_inst);
} else {
let tmp = writable_spilltmp_reg();
let const_inst = Inst::load_constant(tmp, amount, &mut |_| tmp);
let adj_inst = Inst::AluRRRExtend {
alu_op,
size: OperandSize::Size64,
rd: writable_stack_reg(),
rn: stack_reg(),
rm: tmp.to_reg(),
extendop: ExtendOp::UXTX,
};
ret.extend(const_inst);
ret.push(adj_inst);
}
ret
}
fn gen_nominal_sp_adj(offset: i32) -> Inst {
Inst::VirtualSPOffsetAdj {
offset: offset as i64,
}
}
fn gen_prologue_frame_setup(
call_conv: isa::CallConv,
flags: &settings::Flags,
isa_flags: &aarch64_settings::Flags,
frame_layout: &FrameLayout,
) -> SmallInstVec<Inst> {
let setup_frame = frame_layout.setup_area_size > 0;
let mut insts = SmallVec::new();
match select_api_key(isa_flags, call_conv, setup_frame) {
Some(key) => {
insts.push(Inst::Paci { key });
if flags.unwind_info() {
insts.push(Inst::Unwind {
inst: UnwindInst::Aarch64SetPointerAuth {
return_addresses: true,
},
});
}
}
None => {
if isa_flags.use_bti() {
insts.push(Inst::Bti {
targets: BranchTargetType::C,
});
}
if flags.unwind_info() && call_conv.extends_apple_aarch64() {
insts.push(Inst::Unwind {
inst: UnwindInst::Aarch64SetPointerAuth {
return_addresses: false,
},
});
}
}
}
if setup_frame {
insts.push(Inst::StoreP64 {
rt: fp_reg(),
rt2: link_reg(),
mem: PairAMode::SPPreIndexed {
simm7: SImm7Scaled::maybe_from_i64(-16, types::I64).unwrap(),
},
flags: MemFlags::trusted(),
});
if flags.unwind_info() {
insts.push(Inst::Unwind {
inst: UnwindInst::PushFrameRegs {
offset_upward_to_caller_sp: frame_layout.setup_area_size,
},
});
}
insts.push(Inst::AluRRImm12 {
alu_op: ALUOp::Add,
size: OperandSize::Size64,
rd: writable_fp_reg(),
rn: stack_reg(),
imm12: Imm12 {
bits: 0,
shift12: false,
},
});
}
insts
}
fn gen_epilogue_frame_restore(
call_conv: isa::CallConv,
_flags: &settings::Flags,
isa_flags: &aarch64_settings::Flags,
frame_layout: &FrameLayout,
) -> SmallInstVec<Inst> {
let setup_frame = frame_layout.setup_area_size > 0;
let mut insts = SmallVec::new();
if setup_frame {
insts.push(Inst::LoadP64 {
rt: writable_fp_reg(),
rt2: writable_link_reg(),
mem: PairAMode::SPPostIndexed {
simm7: SImm7Scaled::maybe_from_i64(16, types::I64).unwrap(),
},
flags: MemFlags::trusted(),
});
}
if call_conv == isa::CallConv::Tail && frame_layout.stack_args_size > 0 {
insts.extend(Self::gen_sp_reg_adjust(
frame_layout.stack_args_size.try_into().unwrap(),
));
}
match select_api_key(isa_flags, call_conv, setup_frame) {
Some(key) => {
insts.push(Inst::AuthenticatedRet {
key,
is_hint: !isa_flags.has_pauth(),
});
}
None => {
insts.push(Inst::Ret {});
}
}
insts
}
fn gen_probestack(_insts: &mut SmallInstVec<Self::I>, _: u32) {
unimplemented!("Stack probing is unimplemented on AArch64");
}
fn gen_inline_probestack(
insts: &mut SmallInstVec<Self::I>,
_call_conv: isa::CallConv,
frame_size: u32,
guard_size: u32,
) {
const PROBE_MAX_UNROLL: u32 = 3;
let probe_count = align_to(frame_size, guard_size) / guard_size;
if probe_count <= PROBE_MAX_UNROLL {
Self::gen_probestack_unroll(insts, guard_size, probe_count)
} else {
Self::gen_probestack_loop(insts, frame_size, guard_size)
}
}
fn gen_clobber_save(
_call_conv: isa::CallConv,
flags: &settings::Flags,
frame_layout: &FrameLayout,
) -> SmallVec<[Inst; 16]> {
let mut clobbered_int = vec![];
let mut clobbered_vec = vec![];
for ® in frame_layout.clobbered_callee_saves.iter() {
match reg.to_reg().class() {
RegClass::Int => clobbered_int.push(reg),
RegClass::Float => clobbered_vec.push(reg),
RegClass::Vector => unreachable!(),
}
}
let mut insts = SmallVec::new();
if flags.unwind_info() && frame_layout.setup_area_size > 0 {
insts.push(Inst::Unwind {
inst: UnwindInst::DefineNewFrame {
offset_downward_to_clobbers: frame_layout.clobber_size,
offset_upward_to_caller_sp: frame_layout.setup_area_size,
},
});
}
let mut clobber_offset = frame_layout.clobber_size;
let clobber_offset_change = 16;
let iter = clobbered_int.chunks_exact(2);
if let [rd] = iter.remainder() {
let rd: Reg = rd.to_reg().into();
debug_assert_eq!(rd.class(), RegClass::Int);
insts.push(Inst::Store64 {
rd,
mem: AMode::SPPreIndexed {
simm9: SImm9::maybe_from_i64(-clobber_offset_change).unwrap(),
},
flags: MemFlags::trusted(),
});
if flags.unwind_info() {
clobber_offset -= clobber_offset_change as u32;
insts.push(Inst::Unwind {
inst: UnwindInst::SaveReg {
clobber_offset,
reg: rd.to_real_reg().unwrap(),
},
});
}
}
let mut iter = iter.rev();
while let Some([rt, rt2]) = iter.next() {
let rt: Reg = rt.to_reg().into();
let rt2: Reg = rt2.to_reg().into();
debug_assert!(rt.class() == RegClass::Int);
debug_assert!(rt2.class() == RegClass::Int);
insts.push(Inst::StoreP64 {
rt,
rt2,
mem: PairAMode::SPPreIndexed {
simm7: SImm7Scaled::maybe_from_i64(-clobber_offset_change, types::I64).unwrap(),
},
flags: MemFlags::trusted(),
});
if flags.unwind_info() {
clobber_offset -= clobber_offset_change as u32;
insts.push(Inst::Unwind {
inst: UnwindInst::SaveReg {
clobber_offset,
reg: rt.to_real_reg().unwrap(),
},
});
insts.push(Inst::Unwind {
inst: UnwindInst::SaveReg {
clobber_offset: clobber_offset + (clobber_offset_change / 2) as u32,
reg: rt2.to_real_reg().unwrap(),
},
});
}
}
let store_vec_reg = |rd| Inst::FpuStore64 {
rd,
mem: AMode::SPPreIndexed {
simm9: SImm9::maybe_from_i64(-clobber_offset_change).unwrap(),
},
flags: MemFlags::trusted(),
};
let iter = clobbered_vec.chunks_exact(2);
if let [rd] = iter.remainder() {
let rd: Reg = rd.to_reg().into();
debug_assert_eq!(rd.class(), RegClass::Float);
insts.push(store_vec_reg(rd));
if flags.unwind_info() {
clobber_offset -= clobber_offset_change as u32;
insts.push(Inst::Unwind {
inst: UnwindInst::SaveReg {
clobber_offset,
reg: rd.to_real_reg().unwrap(),
},
});
}
}
let store_vec_reg_pair = |rt, rt2| {
let clobber_offset_change = 16;
(
Inst::FpuStoreP64 {
rt,
rt2,
mem: PairAMode::SPPreIndexed {
simm7: SImm7Scaled::maybe_from_i64(-clobber_offset_change, F64).unwrap(),
},
flags: MemFlags::trusted(),
},
clobber_offset_change as u32,
)
};
let mut iter = iter.rev();
while let Some([rt, rt2]) = iter.next() {
let rt: Reg = rt.to_reg().into();
let rt2: Reg = rt2.to_reg().into();
debug_assert_eq!(rt.class(), RegClass::Float);
debug_assert_eq!(rt2.class(), RegClass::Float);
let (inst, clobber_offset_change) = store_vec_reg_pair(rt, rt2);
insts.push(inst);
if flags.unwind_info() {
clobber_offset -= clobber_offset_change;
insts.push(Inst::Unwind {
inst: UnwindInst::SaveReg {
clobber_offset,
reg: rt.to_real_reg().unwrap(),
},
});
insts.push(Inst::Unwind {
inst: UnwindInst::SaveReg {
clobber_offset: clobber_offset + clobber_offset_change / 2,
reg: rt2.to_real_reg().unwrap(),
},
});
}
}
if frame_layout.fixed_frame_storage_size > 0 {
insts.extend(Self::gen_sp_reg_adjust(
-(frame_layout.fixed_frame_storage_size as i32),
));
}
insts
}
fn gen_clobber_restore(
_call_conv: isa::CallConv,
_flags: &settings::Flags,
frame_layout: &FrameLayout,
) -> SmallVec<[Inst; 16]> {
let mut insts = SmallVec::new();
let mut clobbered_int = vec![];
let mut clobbered_vec = vec![];
for ® in frame_layout.clobbered_callee_saves.iter() {
match reg.to_reg().class() {
RegClass::Int => clobbered_int.push(reg),
RegClass::Float => clobbered_vec.push(reg),
RegClass::Vector => unreachable!(),
}
}
if frame_layout.fixed_frame_storage_size > 0 {
insts.extend(Self::gen_sp_reg_adjust(
frame_layout.fixed_frame_storage_size as i32,
));
}
let load_vec_reg = |rd| Inst::FpuLoad64 {
rd,
mem: AMode::SPPostIndexed {
simm9: SImm9::maybe_from_i64(16).unwrap(),
},
flags: MemFlags::trusted(),
};
let load_vec_reg_pair = |rt, rt2| Inst::FpuLoadP64 {
rt,
rt2,
mem: PairAMode::SPPostIndexed {
simm7: SImm7Scaled::maybe_from_i64(16, F64).unwrap(),
},
flags: MemFlags::trusted(),
};
let mut iter = clobbered_vec.chunks_exact(2);
while let Some([rt, rt2]) = iter.next() {
let rt: Writable<Reg> = rt.map(|r| r.into());
let rt2: Writable<Reg> = rt2.map(|r| r.into());
debug_assert_eq!(rt.to_reg().class(), RegClass::Float);
debug_assert_eq!(rt2.to_reg().class(), RegClass::Float);
insts.push(load_vec_reg_pair(rt, rt2));
}
debug_assert!(iter.remainder().len() <= 1);
if let [rd] = iter.remainder() {
let rd: Writable<Reg> = rd.map(|r| r.into());
debug_assert_eq!(rd.to_reg().class(), RegClass::Float);
insts.push(load_vec_reg(rd));
}
let mut iter = clobbered_int.chunks_exact(2);
while let Some([rt, rt2]) = iter.next() {
let rt: Writable<Reg> = rt.map(|r| r.into());
let rt2: Writable<Reg> = rt2.map(|r| r.into());
debug_assert_eq!(rt.to_reg().class(), RegClass::Int);
debug_assert_eq!(rt2.to_reg().class(), RegClass::Int);
insts.push(Inst::LoadP64 {
rt,
rt2,
mem: PairAMode::SPPostIndexed {
simm7: SImm7Scaled::maybe_from_i64(16, I64).unwrap(),
},
flags: MemFlags::trusted(),
});
}
debug_assert!(iter.remainder().len() <= 1);
if let [rd] = iter.remainder() {
let rd: Writable<Reg> = rd.map(|r| r.into());
debug_assert_eq!(rd.to_reg().class(), RegClass::Int);
insts.push(Inst::ULoad64 {
rd,
mem: AMode::SPPostIndexed {
simm9: SImm9::maybe_from_i64(16).unwrap(),
},
flags: MemFlags::trusted(),
});
}
insts
}
fn gen_call(
dest: &CallDest,
uses: CallArgList,
defs: CallRetList,
clobbers: PRegSet,
opcode: ir::Opcode,
tmp: Writable<Reg>,
callee_conv: isa::CallConv,
caller_conv: isa::CallConv,
callee_pop_size: u32,
) -> SmallVec<[Inst; 2]> {
let mut insts = SmallVec::new();
match &dest {
&CallDest::ExtName(ref name, RelocDistance::Near) => insts.push(Inst::Call {
info: Box::new(CallInfo {
dest: name.clone(),
uses,
defs,
clobbers,
opcode,
caller_callconv: caller_conv,
callee_callconv: callee_conv,
callee_pop_size,
}),
}),
&CallDest::ExtName(ref name, RelocDistance::Far) => {
insts.push(Inst::LoadExtName {
rd: tmp,
name: Box::new(name.clone()),
offset: 0,
});
insts.push(Inst::CallInd {
info: Box::new(CallIndInfo {
rn: tmp.to_reg(),
uses,
defs,
clobbers,
opcode,
caller_callconv: caller_conv,
callee_callconv: callee_conv,
callee_pop_size,
}),
});
}
&CallDest::Reg(reg) => insts.push(Inst::CallInd {
info: Box::new(CallIndInfo {
rn: *reg,
uses,
defs,
clobbers,
opcode,
caller_callconv: caller_conv,
callee_callconv: callee_conv,
callee_pop_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 = writable_xreg(0);
let arg1 = writable_xreg(1);
let arg2 = writable_xreg(2);
let tmp = alloc_tmp(Self::word_type());
insts.extend(Inst::load_constant(tmp, size as u64, &mut alloc_tmp));
insts.push(Inst::Call {
info: Box::new(CallInfo {
dest: ExternalName::LibCall(LibCall::Memcpy),
uses: smallvec![
CallArgPair {
vreg: dst,
preg: arg0.to_reg()
},
CallArgPair {
vreg: src,
preg: arg1.to_reg()
},
CallArgPair {
vreg: tmp.to_reg(),
preg: arg2.to_reg()
}
],
defs: smallvec![],
clobbers: Self::get_regs_clobbered_by_call(call_conv),
opcode: Opcode::Call,
caller_callconv: call_conv,
callee_callconv: call_conv,
callee_pop_size: 0,
}),
});
insts
}
fn get_number_of_spillslots_for_value(
rc: RegClass,
vector_size: u32,
_isa_flags: &Self::F,
) -> u32 {
assert_eq!(vector_size % 8, 0);
match rc {
RegClass::Int => 1,
RegClass::Float => vector_size / 8,
RegClass::Vector => unreachable!(),
}
}
fn get_virtual_sp_offset_from_state(s: &EmitState) -> i64 {
s.virtual_sp_offset
}
fn get_nominal_sp_to_fp(s: &EmitState) -> i64 {
s.nominal_sp_to_fp
}
fn get_machine_env(flags: &settings::Flags, _call_conv: isa::CallConv) -> &MachineEnv {
if flags.enable_pinned_reg() {
static MACHINE_ENV: OnceLock<MachineEnv> = OnceLock::new();
MACHINE_ENV.get_or_init(|| create_reg_env(true))
} else {
static MACHINE_ENV: OnceLock<MachineEnv> = OnceLock::new();
MACHINE_ENV.get_or_init(|| create_reg_env(false))
}
}
fn get_regs_clobbered_by_call(call_conv_of_callee: isa::CallConv) -> PRegSet {
if call_conv_of_callee == isa::CallConv::Tail {
TAIL_CLOBBERS
} else {
DEFAULT_AAPCS_CLOBBERS
}
}
fn get_ext_mode(
call_conv: isa::CallConv,
specified: ir::ArgumentExtension,
) -> ir::ArgumentExtension {
if call_conv == isa::CallConv::AppleAarch64 {
specified
} else {
ir::ArgumentExtension::None
}
}
fn compute_frame_layout(
call_conv: isa::CallConv,
flags: &settings::Flags,
sig: &Signature,
regs: &[Writable<RealReg>],
is_leaf: bool,
stack_args_size: u32,
fixed_frame_storage_size: u32,
outgoing_args_size: u32,
) -> FrameLayout {
let mut regs: Vec<Writable<RealReg>> = regs
.iter()
.cloned()
.filter(|r| {
is_reg_saved_in_prologue(call_conv, flags.enable_pinned_reg(), sig, r.to_reg())
})
.collect();
regs.sort_unstable_by_key(|r| VReg::from(r.to_reg()).vreg());
let clobber_size = compute_clobber_size(®s);
let setup_area_size = if flags.preserve_frame_pointers()
|| !is_leaf
|| stack_args_size > 0
|| clobber_size > 0
|| fixed_frame_storage_size > 0
{
16 } else {
0
};
FrameLayout {
stack_args_size,
setup_area_size,
clobber_size,
fixed_frame_storage_size,
outgoing_args_size,
clobbered_callee_saves: regs,
}
}
}
impl AArch64MachineDeps {
fn gen_probestack_unroll(insts: &mut SmallInstVec<Inst>, guard_size: u32, probe_count: u32) {
for _ in 0..probe_count {
insts.extend(Self::gen_sp_reg_adjust(-(guard_size as i32)));
insts.push(Self::gen_store_stack(
StackAMode::SPOffset(0, I8),
zero_reg(),
I32,
));
}
insts.extend(Self::gen_sp_reg_adjust((guard_size * probe_count) as i32));
}
fn gen_probestack_loop(insts: &mut SmallInstVec<Inst>, frame_size: u32, guard_size: u32) {
let start = writable_spilltmp_reg();
let end = writable_tmp2_reg();
insts.extend(Inst::load_constant(start, 0, &mut |_| start));
insts.extend(Inst::load_constant(end, frame_size.into(), &mut |_| end));
insts.push(Inst::StackProbeLoop {
start,
end: end.to_reg(),
step: Imm12::maybe_from_u64(guard_size.into()).unwrap(),
});
}
}
fn select_api_key(
isa_flags: &aarch64_settings::Flags,
call_conv: isa::CallConv,
setup_frame: bool,
) -> Option<APIKey> {
if isa_flags.sign_return_address() && (setup_frame || isa_flags.sign_return_address_all()) {
Some(if isa_flags.sign_return_address_with_bkey() {
match call_conv {
isa::CallConv::Tail => APIKey::BZ,
_ => APIKey::BSP,
}
} else {
match call_conv {
isa::CallConv::Tail => APIKey::AZ,
_ => APIKey::ASP,
}
})
} else {
None
}
}
impl AArch64CallSite {
pub fn emit_return_call(
mut self,
ctx: &mut Lower<Inst>,
args: isle::ValueSlice,
isa_flags: &aarch64_settings::Flags,
) {
let (new_stack_arg_size, old_stack_arg_size) =
self.emit_temporary_tail_call_frame(ctx, args);
let dest = self.dest().clone();
let opcode = self.opcode();
let uses = self.take_uses();
let info = Box::new(ReturnCallInfo {
uses,
opcode,
old_stack_arg_size,
new_stack_arg_size,
key: select_api_key(isa_flags, isa::CallConv::Tail, true),
});
match dest {
CallDest::ExtName(callee, RelocDistance::Near) => {
let callee = Box::new(callee);
ctx.emit(Inst::ReturnCall { callee, info });
}
CallDest::ExtName(name, RelocDistance::Far) => {
let callee = ctx.alloc_tmp(types::I64).only_reg().unwrap();
ctx.emit(Inst::LoadExtName {
rd: callee,
name: Box::new(name),
offset: 0,
});
ctx.emit(Inst::ReturnCallInd {
callee: callee.to_reg(),
info,
});
}
CallDest::Reg(callee) => ctx.emit(Inst::ReturnCallInd { callee, info }),
}
}
}
fn compute_arg_locs_tail<'a, I>(
params: I,
add_ret_area_ptr: bool,
mut args: ArgsAccumulator<'_>,
) -> CodegenResult<(u32, Option<usize>)>
where
I: IntoIterator<Item = &'a ir::AbiParam>,
{
let mut xregs = TAIL_CLOBBERS
.into_iter()
.filter(|r| r.class() == RegClass::Int)
.skip(2);
let mut vregs = TAIL_CLOBBERS
.into_iter()
.filter(|r| r.class() == RegClass::Float);
let mut next_stack: u32 = 0;
let stack = |next_stack: &mut u32, ty: ir::Type| {
*next_stack = align_to(*next_stack, ty.bytes());
let offset = i64::from(*next_stack);
*next_stack += ty.bytes();
ABIArgSlot::Stack {
offset,
ty,
extension: ir::ArgumentExtension::None,
}
};
let mut xreg = |next_stack: &mut u32, ty| {
xregs
.next()
.map(|reg| ABIArgSlot::Reg {
reg: reg.into(),
ty,
extension: ir::ArgumentExtension::None,
})
.unwrap_or_else(|| stack(next_stack, ty))
};
let mut vreg = |next_stack: &mut u32, ty| {
vregs
.next()
.map(|reg| ABIArgSlot::Reg {
reg: reg.into(),
ty,
extension: ir::ArgumentExtension::None,
})
.unwrap_or_else(|| stack(next_stack, ty))
};
for param in params {
assert!(
legal_type_for_machine(param.value_type),
"Invalid type for AArch64: {:?}",
param.value_type
);
match param.purpose {
ir::ArgumentPurpose::Normal | ir::ArgumentPurpose::VMContext => {}
ir::ArgumentPurpose::StructArgument(_)
| ir::ArgumentPurpose::StructReturn
| ir::ArgumentPurpose::StackLimit => unimplemented!(
"support for {:?} parameters is not implemented for the `tail` \
calling convention yet",
param.purpose,
),
}
let (reg_classes, reg_types) = Inst::rc_for_type(param.value_type)?;
args.push(ABIArg::Slots {
slots: reg_classes
.iter()
.zip(reg_types)
.map(|(cls, ty)| match cls {
RegClass::Int => xreg(&mut next_stack, *ty),
RegClass::Float => vreg(&mut next_stack, *ty),
RegClass::Vector => unreachable!(),
})
.collect(),
purpose: param.purpose,
});
}
let ret_ptr = if add_ret_area_ptr {
let idx = args.args().len();
args.push(ABIArg::reg(
xreg_preg(0).into(),
types::I64,
ir::ArgumentExtension::None,
ir::ArgumentPurpose::Normal,
));
Some(idx)
} else {
None
};
next_stack = align_to(next_stack, 16);
if next_stack > STACK_ARG_RET_SIZE_LIMIT {
return Err(CodegenError::ImplLimitExceeded);
}
Ok((next_stack, ret_ptr))
}
fn legal_type_for_machine(ty: Type) -> bool {
match ty {
R32 => false,
_ => true,
}
}
fn is_reg_saved_in_prologue(
call_conv: isa::CallConv,
enable_pinned_reg: bool,
sig: &Signature,
r: RealReg,
) -> bool {
if call_conv == isa::CallConv::Tail {
return false;
}
let save_z_regs = sig
.params
.iter()
.filter(|p| p.value_type.is_dynamic_vector())
.count()
!= 0;
match r.class() {
RegClass::Int => {
if enable_pinned_reg && r.hw_enc() == PINNED_REG {
false
} else {
r.hw_enc() >= 19 && r.hw_enc() <= 28
}
}
RegClass::Float => {
if save_z_regs {
r.hw_enc() >= 8 && r.hw_enc() <= 23
} else {
r.hw_enc() >= 8 && r.hw_enc() <= 15
}
}
RegClass::Vector => unreachable!(),
}
}
const fn default_aapcs_clobbers() -> PRegSet {
PRegSet::empty()
.with(xreg_preg(0))
.with(xreg_preg(1))
.with(xreg_preg(2))
.with(xreg_preg(3))
.with(xreg_preg(4))
.with(xreg_preg(5))
.with(xreg_preg(6))
.with(xreg_preg(7))
.with(xreg_preg(8))
.with(xreg_preg(9))
.with(xreg_preg(10))
.with(xreg_preg(11))
.with(xreg_preg(12))
.with(xreg_preg(13))
.with(xreg_preg(14))
.with(xreg_preg(15))
.with(xreg_preg(16))
.with(xreg_preg(17))
.with(vreg_preg(0))
.with(vreg_preg(1))
.with(vreg_preg(2))
.with(vreg_preg(3))
.with(vreg_preg(4))
.with(vreg_preg(5))
.with(vreg_preg(6))
.with(vreg_preg(7))
.with(vreg_preg(8))
.with(vreg_preg(9))
.with(vreg_preg(10))
.with(vreg_preg(11))
.with(vreg_preg(12))
.with(vreg_preg(13))
.with(vreg_preg(14))
.with(vreg_preg(15))
.with(vreg_preg(16))
.with(vreg_preg(17))
.with(vreg_preg(18))
.with(vreg_preg(19))
.with(vreg_preg(20))
.with(vreg_preg(21))
.with(vreg_preg(22))
.with(vreg_preg(23))
.with(vreg_preg(24))
.with(vreg_preg(25))
.with(vreg_preg(26))
.with(vreg_preg(27))
.with(vreg_preg(28))
.with(vreg_preg(29))
.with(vreg_preg(30))
.with(vreg_preg(31))
}
const DEFAULT_AAPCS_CLOBBERS: PRegSet = default_aapcs_clobbers();
const TAIL_CLOBBERS: PRegSet = PRegSet::empty()
.with(xreg_preg(0))
.with(xreg_preg(1))
.with(xreg_preg(2))
.with(xreg_preg(3))
.with(xreg_preg(4))
.with(xreg_preg(5))
.with(xreg_preg(6))
.with(xreg_preg(7))
.with(xreg_preg(8))
.with(xreg_preg(9))
.with(xreg_preg(10))
.with(xreg_preg(11))
.with(xreg_preg(12))
.with(xreg_preg(13))
.with(xreg_preg(14))
.with(xreg_preg(15))
.with(xreg_preg(19))
.with(xreg_preg(20))
.with(xreg_preg(21))
.with(xreg_preg(22))
.with(xreg_preg(23))
.with(xreg_preg(24))
.with(xreg_preg(25))
.with(xreg_preg(26))
.with(xreg_preg(27))
.with(xreg_preg(28))
.with(vreg_preg(0))
.with(vreg_preg(1))
.with(vreg_preg(2))
.with(vreg_preg(3))
.with(vreg_preg(4))
.with(vreg_preg(5))
.with(vreg_preg(6))
.with(vreg_preg(7))
.with(vreg_preg(8))
.with(vreg_preg(9))
.with(vreg_preg(10))
.with(vreg_preg(11))
.with(vreg_preg(12))
.with(vreg_preg(13))
.with(vreg_preg(14))
.with(vreg_preg(15))
.with(vreg_preg(16))
.with(vreg_preg(17))
.with(vreg_preg(18))
.with(vreg_preg(19))
.with(vreg_preg(20))
.with(vreg_preg(21))
.with(vreg_preg(22))
.with(vreg_preg(23))
.with(vreg_preg(24))
.with(vreg_preg(25))
.with(vreg_preg(26))
.with(vreg_preg(27))
.with(vreg_preg(28))
.with(vreg_preg(29))
.with(vreg_preg(30))
.with(vreg_preg(31));
fn create_reg_env(enable_pinned_reg: bool) -> MachineEnv {
fn preg(r: Reg) -> PReg {
r.to_real_reg().unwrap().into()
}
let mut env = MachineEnv {
preferred_regs_by_class: [
vec![
preg(xreg(0)),
preg(xreg(1)),
preg(xreg(2)),
preg(xreg(3)),
preg(xreg(4)),
preg(xreg(5)),
preg(xreg(6)),
preg(xreg(7)),
preg(xreg(8)),
preg(xreg(9)),
preg(xreg(10)),
preg(xreg(11)),
preg(xreg(12)),
preg(xreg(13)),
preg(xreg(14)),
preg(xreg(15)),
],
vec![
preg(vreg(0)),
preg(vreg(1)),
preg(vreg(2)),
preg(vreg(3)),
preg(vreg(4)),
preg(vreg(5)),
preg(vreg(6)),
preg(vreg(7)),
preg(vreg(16)),
preg(vreg(17)),
preg(vreg(18)),
preg(vreg(19)),
preg(vreg(20)),
preg(vreg(21)),
preg(vreg(22)),
preg(vreg(23)),
preg(vreg(24)),
preg(vreg(25)),
preg(vreg(26)),
preg(vreg(27)),
preg(vreg(28)),
preg(vreg(29)),
preg(vreg(30)),
preg(vreg(31)),
],
vec![],
],
non_preferred_regs_by_class: [
vec![
preg(xreg(19)),
preg(xreg(20)),
preg(xreg(22)),
preg(xreg(23)),
preg(xreg(24)),
preg(xreg(25)),
preg(xreg(26)),
preg(xreg(27)),
preg(xreg(28)),
],
vec![
preg(vreg(8)),
preg(vreg(9)),
preg(vreg(10)),
preg(vreg(11)),
preg(vreg(12)),
preg(vreg(13)),
preg(vreg(14)),
preg(vreg(15)),
],
vec![],
],
fixed_stack_slots: vec![],
scratch_by_class: [None, None, None],
};
if !enable_pinned_reg {
debug_assert_eq!(PINNED_REG, 21); env.non_preferred_regs_by_class[0].push(preg(xreg(PINNED_REG)));
}
env
}