use crate::CodegenResult;
use crate::ir;
use crate::ir::MemFlags;
use crate::ir::Signature;
use crate::ir::Type;
use crate::ir::condcodes::IntCC;
use crate::ir::types;
use crate::isa;
use crate::isa::s390x::{inst::*, settings as s390x_settings};
use crate::isa::unwind::UnwindInst;
use crate::machinst::*;
use crate::settings;
use alloc::borrow::ToOwned;
use alloc::vec::Vec;
use regalloc2::{MachineEnv, PRegSet};
use smallvec::{SmallVec, smallvec};
pub type S390xCallee = Callee<S390xMachineDeps>;
fn in_int_reg(ty: Type) -> bool {
match ty {
types::I8 | types::I16 | types::I32 | types::I64 => true,
_ => false,
}
}
fn in_flt_reg(ty: Type) -> bool {
match ty {
types::F16 | types::F32 | types::F64 => true,
_ => false,
}
}
fn in_vec_reg(ty: Type) -> bool {
ty.is_vector() && ty.bits() == 128
}
fn get_intreg_for_arg(call_conv: isa::CallConv, idx: usize) -> Option<Reg> {
match idx {
0 => Some(regs::gpr(2)),
1 => Some(regs::gpr(3)),
2 => Some(regs::gpr(4)),
3 => Some(regs::gpr(5)),
4 => Some(regs::gpr(6)),
5 if call_conv == isa::CallConv::Tail => Some(regs::gpr(7)),
_ => None,
}
}
fn get_fltreg_for_arg(idx: usize) -> Option<Reg> {
match idx {
0 => Some(regs::vr(0)),
1 => Some(regs::vr(2)),
2 => Some(regs::vr(4)),
3 => Some(regs::vr(6)),
_ => None,
}
}
fn get_vecreg_for_arg(idx: usize) -> Option<Reg> {
match idx {
0 => Some(regs::vr(24)),
1 => Some(regs::vr(25)),
2 => Some(regs::vr(26)),
3 => Some(regs::vr(27)),
4 => Some(regs::vr(28)),
5 => Some(regs::vr(29)),
6 => Some(regs::vr(30)),
7 => Some(regs::vr(31)),
_ => None,
}
}
fn get_intreg_for_ret(call_conv: isa::CallConv, idx: usize) -> Option<Reg> {
match idx {
0 => Some(regs::gpr(2)),
1 => Some(regs::gpr(3)),
2 => Some(regs::gpr(4)),
3 => Some(regs::gpr(5)),
4 if call_conv == isa::CallConv::Tail => Some(regs::gpr(6)),
5 if call_conv == isa::CallConv::Tail => Some(regs::gpr(7)),
_ => None,
}
}
fn get_fltreg_for_ret(idx: usize) -> Option<Reg> {
match idx {
0 => Some(regs::vr(0)),
1 => Some(regs::vr(2)),
2 => Some(regs::vr(4)),
3 => Some(regs::vr(6)),
_ => None,
}
}
fn get_vecreg_for_ret(idx: usize) -> Option<Reg> {
match idx {
0 => Some(regs::vr(24)),
1 => Some(regs::vr(25)),
2 => Some(regs::vr(26)),
3 => Some(regs::vr(27)),
4 => Some(regs::vr(28)),
5 => Some(regs::vr(29)),
6 => Some(regs::vr(30)),
7 => Some(regs::vr(31)),
_ => None,
}
}
pub static REG_SAVE_AREA_SIZE: u32 = 160;
impl From<StackAMode> for MemArg {
fn from(stack: StackAMode) -> MemArg {
match stack {
StackAMode::IncomingArg(off, stack_args_size) => MemArg::IncomingArgOffset {
off: off - stack_args_size as i64,
},
StackAMode::Slot(off) => MemArg::SlotOffset { off },
StackAMode::OutgoingArg(off) => MemArg::OutgoingArgOffset { off },
}
}
}
impl From<isa::CallConv> for LaneOrder {
fn from(call_conv: isa::CallConv) -> Self {
match call_conv {
isa::CallConv::Tail => LaneOrder::LittleEndian,
_ => LaneOrder::BigEndian,
}
}
}
pub struct S390xMachineDeps;
impl IsaFlags for s390x_settings::Flags {}
impl ABIMachineSpec for S390xMachineDeps {
type I = Inst;
type F = s390x_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 {
8
}
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>)> {
assert_ne!(
call_conv,
isa::CallConv::Winch,
"s390x does not support the 'winch' calling convention yet"
);
let mut next_gpr = 0;
let mut next_fpr = 0;
let mut next_vr = 0;
let mut next_stack: u32 = 0;
let ret_area_ptr = if add_ret_area_ptr {
debug_assert_eq!(args_or_rets, ArgsOrRets::Args);
next_gpr += 1;
Some(ABIArg::reg(
get_intreg_for_arg(call_conv, 0)
.unwrap()
.to_real_reg()
.unwrap(),
types::I64,
ir::ArgumentExtension::None,
ir::ArgumentPurpose::Normal,
))
} else {
None
};
for mut param in params.into_iter().copied() {
if let ir::ArgumentPurpose::StructArgument(_) = param.purpose {
panic!(
"StructArgument parameters are not supported on s390x. \
Use regular pointer arguments instead."
);
}
let intreg = in_int_reg(param.value_type);
let fltreg = in_flt_reg(param.value_type);
let vecreg = in_vec_reg(param.value_type);
debug_assert!(intreg as i32 + fltreg as i32 + vecreg as i32 <= 1);
let (next_reg, candidate, implicit_ref) = if intreg {
let candidate = match args_or_rets {
ArgsOrRets::Args => get_intreg_for_arg(call_conv, next_gpr),
ArgsOrRets::Rets => get_intreg_for_ret(call_conv, next_gpr),
};
(&mut next_gpr, candidate, None)
} else if fltreg {
let candidate = match args_or_rets {
ArgsOrRets::Args => get_fltreg_for_arg(next_fpr),
ArgsOrRets::Rets => get_fltreg_for_ret(next_fpr),
};
(&mut next_fpr, candidate, None)
} else if vecreg {
let candidate = match args_or_rets {
ArgsOrRets::Args => get_vecreg_for_arg(next_vr),
ArgsOrRets::Rets => get_vecreg_for_ret(next_vr),
};
(&mut next_vr, candidate, None)
} else {
if args_or_rets == ArgsOrRets::Rets {
(&mut next_gpr, None, None)
} else {
let implicit_ref = Some(param.value_type);
param = ir::AbiParam::new(types::I64);
let candidate = get_intreg_for_arg(call_conv, next_gpr);
(&mut next_gpr, candidate, implicit_ref)
}
};
let slot = if let Some(reg) = candidate {
*next_reg += 1;
ABIArgSlot::Reg {
reg: reg.to_real_reg().unwrap(),
ty: param.value_type,
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 = (ty_bits(param.value_type) / 8) as u32;
let slot_size = core::cmp::max(size, 8);
debug_assert!(slot_size.is_power_of_two());
let slot_align = core::cmp::min(slot_size, 8);
next_stack = align_to(next_stack, slot_align);
let offset = if size < slot_size && param.extension == ir::ArgumentExtension::None {
slot_size - size
} else {
0
};
let offset = (next_stack + offset) as i64;
next_stack += slot_size;
ABIArgSlot::Stack {
offset,
ty: param.value_type,
extension: param.extension,
}
};
if let Some(ty) = implicit_ref {
assert!(
(ty_bits(ty) / 8) % 8 == 0,
"implicit argument size is not properly aligned"
);
args.push(ABIArg::ImplicitPtrArg {
pointer: slot,
offset: 0, ty,
purpose: param.purpose,
});
} else {
args.push(ABIArg::Slots {
slots: smallvec![slot],
purpose: param.purpose,
});
}
}
next_stack = align_to(next_stack, 8);
let extra_arg = if let Some(ret_area_ptr) = ret_area_ptr {
args.push_non_formal(ret_area_ptr);
Some(args.args().len() - 1)
} else {
None
};
for arg in args.args_mut() {
match arg {
ABIArg::StructArg { .. } => unreachable!(),
ABIArg::ImplicitPtrArg { offset, ty, .. } => {
*offset = next_stack as i64;
next_stack += (ty_bits(*ty) / 8) as u32;
}
_ => {}
}
}
if call_conv == isa::CallConv::Tail && args_or_rets == ArgsOrRets::Args && next_stack != 0 {
next_stack += REG_SAVE_AREA_SIZE;
}
Ok((next_stack, extra_arg))
}
fn gen_load_stack(mem: StackAMode, into_reg: Writable<Reg>, ty: Type) -> Inst {
Inst::gen_load(into_reg, mem.into(), ty)
}
fn gen_store_stack(mem: StackAMode, from_reg: Reg, ty: Type) -> Inst {
Inst::gen_store(mem.into(), from_reg, ty)
}
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 mut insts = SmallVec::new();
if let Some(imm) = UImm12::maybe_from_u64(imm as u64) {
insts.push(Inst::LoadAddr {
rd: into_reg,
mem: MemArg::BXD12 {
base: from_reg,
index: zero_reg(),
disp: imm,
flags: MemFlags::trusted(),
},
});
} else if let Some(imm) = SImm20::maybe_from_i64(imm as i64) {
insts.push(Inst::LoadAddr {
rd: into_reg,
mem: MemArg::BXD20 {
base: from_reg,
index: zero_reg(),
disp: imm,
flags: MemFlags::trusted(),
},
});
} else {
if from_reg != into_reg.to_reg() {
insts.push(Inst::mov64(into_reg, from_reg));
}
insts.push(Inst::AluRUImm32 {
alu_op: ALUOp::AddLogical64,
rd: into_reg,
ri: into_reg.to_reg(),
imm,
});
}
insts
}
fn gen_stack_lower_bound_trap(limit_reg: Reg) -> SmallInstVec<Inst> {
let mut insts = SmallVec::new();
insts.push(Inst::CmpTrapRR {
op: CmpOp::CmpL64,
rn: stack_reg(),
rm: limit_reg,
cond: Cond::from_intcc(IntCC::UnsignedLessThanOrEqual),
trap_code: ir::TrapCode::STACK_OVERFLOW,
});
insts
}
fn gen_get_stack_addr(mem: StackAMode, into_reg: Writable<Reg>) -> 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 = MemArg::reg_plus_off(base, offset.into(), MemFlags::trusted());
Inst::gen_load(into_reg, mem, ty)
}
fn gen_store_base_offset(base: Reg, offset: i32, from_reg: Reg, ty: Type) -> Inst {
let mem = MemArg::reg_plus_off(base, offset.into(), MemFlags::trusted());
Inst::gen_store(mem, from_reg, ty)
}
fn gen_sp_reg_adjust(imm: i32) -> SmallInstVec<Inst> {
if imm == 0 {
return SmallVec::new();
}
let mut insts = SmallVec::new();
if let Ok(imm) = i16::try_from(imm) {
insts.push(Inst::AluRSImm16 {
alu_op: ALUOp::Add64,
rd: writable_stack_reg(),
ri: stack_reg(),
imm,
});
} else {
insts.push(Inst::AluRSImm32 {
alu_op: ALUOp::Add64,
rd: writable_stack_reg(),
ri: stack_reg(),
imm,
});
}
insts
}
fn gen_prologue_frame_setup(
_call_conv: isa::CallConv,
_flags: &settings::Flags,
_isa_flags: &s390x_settings::Flags,
_frame_layout: &FrameLayout,
) -> SmallInstVec<Inst> {
SmallVec::new()
}
fn gen_epilogue_frame_restore(
_call_conv: isa::CallConv,
_flags: &settings::Flags,
_isa_flags: &s390x_settings::Flags,
_frame_layout: &FrameLayout,
) -> SmallInstVec<Inst> {
SmallVec::new()
}
fn gen_return(
_call_conv: isa::CallConv,
_isa_flags: &s390x_settings::Flags,
_frame_layout: &FrameLayout,
) -> SmallInstVec<Inst> {
smallvec![Inst::Ret { link: gpr(14) }]
}
fn gen_probestack(_insts: &mut SmallInstVec<Self::I>, _: u32) {
unimplemented!("Stack probing is unimplemented on S390x");
}
fn gen_inline_probestack(
insts: &mut SmallInstVec<Self::I>,
_call_conv: isa::CallConv,
frame_size: u32,
guard_size: u32,
) {
const PROBE_MAX_UNROLL: u32 = 2;
let probe_count = frame_size / guard_size;
if probe_count == 0 {
} else if probe_count <= PROBE_MAX_UNROLL {
for _ in 0..probe_count {
insts.extend(Self::gen_sp_reg_adjust(-(guard_size as i32)));
insts.push(Inst::StoreImm8 {
imm: 0,
mem: MemArg::reg(stack_reg(), MemFlags::trusted()),
});
}
} else {
let probe_count_reg = writable_spilltmp_reg();
if let Ok(probe_count) = i16::try_from(probe_count) {
insts.push(Inst::Mov32SImm16 {
rd: probe_count_reg,
imm: probe_count,
});
} else {
insts.push(Inst::Mov32Imm {
rd: probe_count_reg,
imm: probe_count,
});
}
insts.push(Inst::StackProbeLoop {
probe_count: probe_count_reg,
guard_size: i16::try_from(guard_size).unwrap(),
});
}
insts.extend(Self::gen_sp_reg_adjust((probe_count * guard_size) as i32));
}
fn gen_clobber_save(
call_conv: isa::CallConv,
flags: &settings::Flags,
frame_layout: &FrameLayout,
) -> SmallVec<[Inst; 16]> {
let mut insts = SmallVec::new();
let incoming_tail_args_size = if call_conv == isa::CallConv::Tail {
frame_layout.incoming_args_size
} else {
0
};
if flags.unwind_info() {
insts.push(Inst::Unwind {
inst: UnwindInst::DefineNewFrame {
offset_upward_to_caller_sp: REG_SAVE_AREA_SIZE + incoming_tail_args_size,
offset_downward_to_clobbers: frame_layout.clobber_size
- incoming_tail_args_size,
},
});
}
if let Some((first_clobbered_gpr, _)) = get_clobbered_gprs(frame_layout) {
let mut last_clobbered_gpr = 15;
let offset = 8 * first_clobbered_gpr as i64 + incoming_tail_args_size as i64;
insts.push(Inst::StoreMultiple64 {
rt: gpr(first_clobbered_gpr),
rt2: gpr(last_clobbered_gpr),
mem: MemArg::reg_plus_off(stack_reg(), offset, MemFlags::trusted()),
});
if flags.unwind_info() {
if incoming_tail_args_size != 0 {
insts.push(Inst::Unwind {
inst: UnwindInst::RegStackOffset {
clobber_offset: frame_layout.clobber_size,
reg: gpr(last_clobbered_gpr).to_real_reg().unwrap(),
},
});
last_clobbered_gpr = last_clobbered_gpr - 1;
}
for i in first_clobbered_gpr..(last_clobbered_gpr + 1) {
insts.push(Inst::Unwind {
inst: UnwindInst::SaveReg {
clobber_offset: frame_layout.clobber_size + (i * 8) as u32,
reg: gpr(i).to_real_reg().unwrap(),
},
});
}
}
}
if flags.preserve_frame_pointers() {
if incoming_tail_args_size == 0 {
insts.push(Inst::mov64(writable_gpr(1), stack_reg()));
} else {
insts.extend(Self::gen_add_imm(
call_conv,
writable_gpr(1),
stack_reg(),
incoming_tail_args_size,
));
}
}
let stack_size = frame_layout.outgoing_args_size as i32
+ frame_layout.clobber_size as i32
+ frame_layout.fixed_frame_storage_size as i32
- incoming_tail_args_size as i32;
insts.extend(Self::gen_sp_reg_adjust(-stack_size));
if flags.unwind_info() {
insts.push(Inst::Unwind {
inst: UnwindInst::StackAlloc {
size: stack_size as u32,
},
});
}
if flags.preserve_frame_pointers() {
insts.push(Inst::Store64 {
rd: gpr(1),
mem: MemArg::reg_plus_off(stack_reg(), 0, MemFlags::trusted()),
});
}
match call_conv {
isa::CallConv::PreserveAll => {
for i in 0..32 {
insts.push(Inst::VecStore {
rd: regs::vr(i),
mem: MemArg::reg_plus_off(
stack_reg(),
6 * 8
+ (i as i64) * 16
+ frame_layout.outgoing_args_size as i64
+ frame_layout.fixed_frame_storage_size as i64,
MemFlags::trusted(),
),
});
}
insts.push(Inst::StoreMultiple64 {
rt: regs::gpr(0),
rt2: regs::gpr(5),
mem: MemArg::reg_plus_off(
stack_reg(),
frame_layout.outgoing_args_size as i64
+ frame_layout.fixed_frame_storage_size as i64,
MemFlags::trusted(),
),
});
}
_ => {
for (i, reg) in get_clobbered_fprs(frame_layout).iter().enumerate() {
insts.push(Inst::VecStoreLane {
size: 64,
rd: reg.to_reg().into(),
mem: MemArg::reg_plus_off(
stack_reg(),
(i * 8) as i64
+ frame_layout.outgoing_args_size as i64
+ frame_layout.fixed_frame_storage_size as i64,
MemFlags::trusted(),
),
lane_imm: 0,
});
if flags.unwind_info() {
insts.push(Inst::Unwind {
inst: UnwindInst::SaveReg {
clobber_offset: (i * 8) as u32,
reg: reg.to_reg(),
},
});
}
}
}
}
insts
}
fn gen_clobber_restore(
call_conv: isa::CallConv,
_flags: &settings::Flags,
frame_layout: &FrameLayout,
) -> SmallVec<[Inst; 16]> {
let mut insts = SmallVec::new();
match call_conv {
isa::CallConv::PreserveAll => {
insts.extend(gen_restore_patchable(frame_layout));
}
_ => {
insts.extend(gen_restore_fprs(frame_layout));
}
}
insts.extend(gen_restore_gprs(call_conv, frame_layout, 0));
insts
}
fn gen_memcpy<F: FnMut(Type) -> Writable<Reg>>(
_call_conv: isa::CallConv,
_dst: Reg,
_src: Reg,
_size: usize,
_alloc: F,
) -> SmallVec<[Self::I; 8]> {
unimplemented!("StructArgs not implemented for S390X yet");
}
fn get_number_of_spillslots_for_value(
rc: RegClass,
_vector_scale: u32,
_isa_flags: &Self::F,
) -> u32 {
match rc {
RegClass::Int => 1,
RegClass::Float => 2,
RegClass::Vector => unreachable!(),
}
}
fn get_machine_env(_flags: &settings::Flags, call_conv: isa::CallConv) -> &MachineEnv {
match call_conv {
isa::CallConv::Tail => {
static TAIL_MACHINE_ENV: MachineEnv = tail_create_machine_env();
&TAIL_MACHINE_ENV
}
_ => {
static SYSV_MACHINE_ENV: MachineEnv = sysv_create_machine_env();
&SYSV_MACHINE_ENV
}
}
}
fn get_regs_clobbered_by_call(
call_conv_of_callee: isa::CallConv,
is_exception: bool,
) -> PRegSet {
match call_conv_of_callee {
isa::CallConv::Tail if is_exception => ALL_CLOBBERS,
isa::CallConv::PreserveAll if is_exception => ALL_CLOBBERS,
isa::CallConv::Tail => TAIL_CLOBBERS,
isa::CallConv::PreserveAll => NO_CLOBBERS,
_ => SYSV_CLOBBERS,
}
}
fn get_ext_mode(
_call_conv: isa::CallConv,
specified: ir::ArgumentExtension,
) -> ir::ArgumentExtension {
specified
}
fn compute_frame_layout(
call_conv: isa::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,
mut outgoing_args_size: u32,
) -> FrameLayout {
assert!(
!flags.enable_pinned_reg(),
"Pinned register not supported on s390x"
);
let mut regs: Vec<Writable<RealReg>> = regs
.iter()
.cloned()
.filter(|r| is_reg_saved_in_prologue(call_conv, r.to_reg()))
.collect();
if flags.preserve_frame_pointers() {
if outgoing_args_size < REG_SAVE_AREA_SIZE {
outgoing_args_size = REG_SAVE_AREA_SIZE;
}
}
if outgoing_args_size > 0 {
let link_reg = Writable::from_reg(RealReg::from(gpr_preg(14)));
if !regs.contains(&link_reg) {
regs.push(link_reg);
}
}
regs.sort_unstable();
let mut clobber_size = match call_conv {
isa::CallConv::PreserveAll => 8 * 6 + 16 * 32,
_ => {
let mut clobber_size = 0;
for reg in ®s {
match reg.to_reg().class() {
RegClass::Int => {}
RegClass::Float => {
clobber_size += 8;
}
RegClass::Vector => unreachable!(),
}
}
clobber_size
}
};
if call_conv == isa::CallConv::Tail {
clobber_size += tail_args_size;
}
FrameLayout {
word_bytes: 8,
incoming_args_size,
tail_args_size: incoming_args_size,
setup_area_size: 0,
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> {
panic!("Should not be called");
}
fn exception_payload_regs(call_conv: isa::CallConv) -> &'static [Reg] {
const PAYLOAD_REGS: &'static [Reg] = &[gpr(6), gpr(7)];
match call_conv {
isa::CallConv::SystemV | isa::CallConv::Tail | isa::CallConv::PreserveAll => {
PAYLOAD_REGS
}
_ => &[],
}
}
}
impl S390xMachineDeps {
pub fn gen_tail_epilogue(
frame_layout: &FrameLayout,
callee_pop_size: u32,
dest: &CallInstDest,
) -> (SmallVec<[Inst; 16]>, Option<Reg>) {
let mut insts = SmallVec::new();
let call_conv = isa::CallConv::Tail;
insts.extend(gen_restore_fprs(frame_layout));
let temp_dest = match dest {
CallInstDest::Indirect { reg }
if reg.to_real_reg().is_some()
&& is_reg_saved_in_prologue(call_conv, reg.to_real_reg().unwrap()) =>
{
insts.push(Inst::Mov64 {
rd: writable_gpr(1),
rm: *reg,
});
Some(gpr(1))
}
_ => None,
};
insts.extend(gen_restore_gprs(call_conv, frame_layout, callee_pop_size));
(insts, temp_dest)
}
pub fn gen_retval_loads(info: &CallInfo<CallInstDest>) -> SmallInstVec<Inst> {
let mut insts = SmallVec::new();
let lane_swap_if_needed = |insts: &mut SmallInstVec<Inst>, vreg, ty: Type| {
if LaneOrder::from(info.caller_conv) != LaneOrder::from(info.callee_conv) {
if ty.is_vector() && ty.lane_count() >= 2 {
insts.push(Inst::VecEltRev {
lane_count: ty.lane_count(),
rd: vreg,
rn: vreg.to_reg(),
});
}
}
};
let temp_reg = |ty| match Inst::rc_for_type(ty).unwrap() {
(&[RegClass::Int], _) => writable_gpr(0),
(&[RegClass::Float], _) => writable_vr(1),
_ => unreachable!(),
};
for CallRetPair { vreg, location } in &info.defs {
match location {
RetLocation::Reg(preg, ty) => {
debug_assert!(*preg != temp_reg(*ty).to_reg());
}
RetLocation::Stack(amode, ty) => {
if let Some(spillslot) = vreg.to_reg().to_spillslot() {
let temp = temp_reg(*ty);
insts.push(Inst::gen_load(temp, (*amode).into(), *ty));
lane_swap_if_needed(&mut insts, temp, *ty);
insts.push(Inst::gen_store(
MemArg::SpillOffset {
off: 8 * (spillslot.index() as i64),
},
temp.to_reg(),
Inst::canonical_type_for_rc(temp.to_reg().class()),
));
}
}
}
}
for CallRetPair { vreg, location } in &info.defs {
match location {
RetLocation::Reg(preg, ty) => {
lane_swap_if_needed(&mut insts, Writable::from_reg(*preg), *ty);
}
RetLocation::Stack(amode, ty) => {
if vreg.to_reg().to_spillslot().is_none() {
insts.push(Inst::gen_load(*vreg, (*amode).into(), *ty));
lane_swap_if_needed(&mut insts, *vreg, *ty);
}
}
}
}
insts
}
}
fn is_reg_saved_in_prologue(call_conv: isa::CallConv, r: RealReg) -> bool {
match (call_conv, r.class()) {
(isa::CallConv::Tail, RegClass::Int) => {
r.hw_enc() >= 8 && r.hw_enc() <= 14
}
(isa::CallConv::PreserveAll, _) => true,
(_, RegClass::Int) => {
r.hw_enc() >= 6 && r.hw_enc() <= 15
}
(_, RegClass::Float) => {
r.hw_enc() >= 8 && r.hw_enc() <= 15
}
(_, RegClass::Vector) => unreachable!(),
}
}
fn get_clobbered_gprs(frame_layout: &FrameLayout) -> Option<(u8, u8)> {
let (clobbered_gpr, _) = frame_layout.clobbered_callee_saves_by_class();
if clobbered_gpr.is_empty() {
return None;
}
let first = clobbered_gpr.first().unwrap().to_reg().hw_enc();
let last = clobbered_gpr.last().unwrap().to_reg().hw_enc();
debug_assert!(clobbered_gpr.iter().all(|r| r.to_reg().hw_enc() >= first));
debug_assert!(clobbered_gpr.iter().all(|r| r.to_reg().hw_enc() <= last));
let first = core::cmp::max(first, 6);
Some((first, last))
}
fn get_clobbered_fprs(frame_layout: &FrameLayout) -> &[Writable<RealReg>] {
let (_, clobbered_fpr) = frame_layout.clobbered_callee_saves_by_class();
clobbered_fpr
}
fn gen_restore_gprs(
call_conv: isa::CallConv,
frame_layout: &FrameLayout,
callee_pop_size: u32,
) -> SmallVec<[Inst; 16]> {
let mut insts = SmallVec::new();
let clobbered_gpr = get_clobbered_gprs(frame_layout);
let stack_size = frame_layout.outgoing_args_size as i32
+ frame_layout.clobber_size as i32
+ frame_layout.fixed_frame_storage_size as i32;
let implicit_sp_restore = callee_pop_size == 0
&& (call_conv != isa::CallConv::Tail || frame_layout.incoming_args_size == 0)
&& clobbered_gpr.map_or(false, |(first, _)| {
SImm20::maybe_from_i64(8 * first as i64 + stack_size as i64).is_some()
});
if !implicit_sp_restore {
insts.extend(S390xMachineDeps::gen_sp_reg_adjust(
stack_size - callee_pop_size as i32,
));
}
if let Some((first, mut last)) = clobbered_gpr {
let mut reg = stack_reg();
let mut offset = callee_pop_size as i64 + 8 * first as i64;
if implicit_sp_restore {
offset += stack_size as i64 - callee_pop_size as i64;
last = 15;
}
if SImm20::maybe_from_i64(offset).is_none() {
insts.extend(S390xMachineDeps::gen_add_imm(
call_conv,
writable_gpr(first),
stack_reg(),
offset as u32,
));
reg = gpr(first);
offset = 0;
}
insts.push(Inst::LoadMultiple64 {
rt: writable_gpr(first),
rt2: writable_gpr(last),
mem: MemArg::reg_plus_off(reg, offset, MemFlags::trusted()),
});
}
insts
}
fn gen_restore_fprs(frame_layout: &FrameLayout) -> SmallVec<[Inst; 16]> {
let mut insts = SmallVec::new();
let clobbered_fpr = get_clobbered_fprs(frame_layout);
for (i, reg) in clobbered_fpr.iter().enumerate() {
insts.push(Inst::VecLoadLaneUndef {
size: 64,
rd: Writable::from_reg(reg.to_reg().into()),
mem: MemArg::reg_plus_off(
stack_reg(),
(i * 8) as i64
+ frame_layout.outgoing_args_size as i64
+ frame_layout.fixed_frame_storage_size as i64,
MemFlags::trusted(),
),
lane_imm: 0,
});
}
insts
}
fn gen_restore_patchable(frame_layout: &FrameLayout) -> SmallVec<[Inst; 16]> {
let mut insts = SmallVec::new();
for i in 0..32 {
insts.push(Inst::VecLoad {
rd: regs::writable_vr(i),
mem: MemArg::reg_plus_off(
stack_reg(),
6 * 8
+ (i as i64) * 16
+ frame_layout.outgoing_args_size as i64
+ frame_layout.fixed_frame_storage_size as i64,
MemFlags::trusted(),
),
});
}
insts.push(Inst::LoadMultiple64 {
rt: regs::writable_gpr(0),
rt2: regs::writable_gpr(5),
mem: MemArg::reg_plus_off(
stack_reg(),
frame_layout.outgoing_args_size as i64 + frame_layout.fixed_frame_storage_size as i64,
MemFlags::trusted(),
),
});
insts
}
const fn sysv_clobbers() -> PRegSet {
PRegSet::empty()
.with(gpr_preg(0))
.with(gpr_preg(1))
.with(gpr_preg(2))
.with(gpr_preg(3))
.with(gpr_preg(4))
.with(gpr_preg(5))
.with(vr_preg(0))
.with(vr_preg(1))
.with(vr_preg(2))
.with(vr_preg(3))
.with(vr_preg(4))
.with(vr_preg(5))
.with(vr_preg(6))
.with(vr_preg(7))
.with(vr_preg(8))
.with(vr_preg(9))
.with(vr_preg(10))
.with(vr_preg(11))
.with(vr_preg(12))
.with(vr_preg(13))
.with(vr_preg(14))
.with(vr_preg(15))
.with(vr_preg(16))
.with(vr_preg(17))
.with(vr_preg(18))
.with(vr_preg(19))
.with(vr_preg(20))
.with(vr_preg(21))
.with(vr_preg(22))
.with(vr_preg(23))
.with(vr_preg(24))
.with(vr_preg(25))
.with(vr_preg(26))
.with(vr_preg(27))
.with(vr_preg(28))
.with(vr_preg(29))
.with(vr_preg(30))
.with(vr_preg(31))
}
const SYSV_CLOBBERS: PRegSet = sysv_clobbers();
const fn tail_clobbers() -> PRegSet {
PRegSet::empty()
.with(gpr_preg(0))
.with(gpr_preg(1))
.with(gpr_preg(2))
.with(gpr_preg(3))
.with(gpr_preg(4))
.with(gpr_preg(5))
.with(gpr_preg(6))
.with(gpr_preg(7))
.with(vr_preg(0))
.with(vr_preg(1))
.with(vr_preg(2))
.with(vr_preg(3))
.with(vr_preg(4))
.with(vr_preg(5))
.with(vr_preg(6))
.with(vr_preg(7))
.with(vr_preg(8))
.with(vr_preg(9))
.with(vr_preg(10))
.with(vr_preg(11))
.with(vr_preg(12))
.with(vr_preg(13))
.with(vr_preg(14))
.with(vr_preg(15))
.with(vr_preg(16))
.with(vr_preg(17))
.with(vr_preg(18))
.with(vr_preg(19))
.with(vr_preg(20))
.with(vr_preg(21))
.with(vr_preg(22))
.with(vr_preg(23))
.with(vr_preg(24))
.with(vr_preg(25))
.with(vr_preg(26))
.with(vr_preg(27))
.with(vr_preg(28))
.with(vr_preg(29))
.with(vr_preg(30))
.with(vr_preg(31))
}
const TAIL_CLOBBERS: PRegSet = tail_clobbers();
const fn all_clobbers() -> PRegSet {
PRegSet::empty()
.with(gpr_preg(0))
.with(gpr_preg(1))
.with(gpr_preg(2))
.with(gpr_preg(3))
.with(gpr_preg(4))
.with(gpr_preg(5))
.with(gpr_preg(6))
.with(gpr_preg(7))
.with(gpr_preg(8))
.with(gpr_preg(9))
.with(gpr_preg(10))
.with(gpr_preg(11))
.with(gpr_preg(12))
.with(gpr_preg(13))
.with(gpr_preg(14))
.with(gpr_preg(15))
.with(vr_preg(0))
.with(vr_preg(1))
.with(vr_preg(2))
.with(vr_preg(3))
.with(vr_preg(4))
.with(vr_preg(5))
.with(vr_preg(6))
.with(vr_preg(7))
.with(vr_preg(8))
.with(vr_preg(9))
.with(vr_preg(10))
.with(vr_preg(11))
.with(vr_preg(12))
.with(vr_preg(13))
.with(vr_preg(14))
.with(vr_preg(15))
.with(vr_preg(16))
.with(vr_preg(17))
.with(vr_preg(18))
.with(vr_preg(19))
.with(vr_preg(20))
.with(vr_preg(21))
.with(vr_preg(22))
.with(vr_preg(23))
.with(vr_preg(24))
.with(vr_preg(25))
.with(vr_preg(26))
.with(vr_preg(27))
.with(vr_preg(28))
.with(vr_preg(29))
.with(vr_preg(30))
.with(vr_preg(31))
}
const ALL_CLOBBERS: PRegSet = all_clobbers();
const NO_CLOBBERS: PRegSet = PRegSet::empty();
const fn sysv_create_machine_env() -> MachineEnv {
MachineEnv {
preferred_regs_by_class: [
PRegSet::empty()
.with(gpr_preg(2))
.with(gpr_preg(3))
.with(gpr_preg(4))
.with(gpr_preg(5)),
PRegSet::empty()
.with(vr_preg(0))
.with(vr_preg(1))
.with(vr_preg(2))
.with(vr_preg(3))
.with(vr_preg(4))
.with(vr_preg(5))
.with(vr_preg(6))
.with(vr_preg(7))
.with(vr_preg(16))
.with(vr_preg(17))
.with(vr_preg(18))
.with(vr_preg(19))
.with(vr_preg(20))
.with(vr_preg(21))
.with(vr_preg(22))
.with(vr_preg(23))
.with(vr_preg(24))
.with(vr_preg(25))
.with(vr_preg(26))
.with(vr_preg(27))
.with(vr_preg(28))
.with(vr_preg(29))
.with(vr_preg(30))
.with(vr_preg(31)),
PRegSet::empty(),
],
non_preferred_regs_by_class: [
PRegSet::empty()
.with(gpr_preg(6))
.with(gpr_preg(7))
.with(gpr_preg(8))
.with(gpr_preg(9))
.with(gpr_preg(10))
.with(gpr_preg(11))
.with(gpr_preg(12))
.with(gpr_preg(13))
.with(gpr_preg(14)),
PRegSet::empty()
.with(vr_preg(8))
.with(vr_preg(9))
.with(vr_preg(10))
.with(vr_preg(11))
.with(vr_preg(12))
.with(vr_preg(13))
.with(vr_preg(14))
.with(vr_preg(15)),
PRegSet::empty(),
],
fixed_stack_slots: vec![],
scratch_by_class: [None, None, None],
}
}
const fn tail_create_machine_env() -> MachineEnv {
MachineEnv {
preferred_regs_by_class: [
PRegSet::empty()
.with(gpr_preg(2))
.with(gpr_preg(3))
.with(gpr_preg(4))
.with(gpr_preg(5))
.with(gpr_preg(6))
.with(gpr_preg(7)),
PRegSet::empty()
.with(vr_preg(0))
.with(vr_preg(1))
.with(vr_preg(2))
.with(vr_preg(3))
.with(vr_preg(4))
.with(vr_preg(5))
.with(vr_preg(6))
.with(vr_preg(7))
.with(vr_preg(16))
.with(vr_preg(17))
.with(vr_preg(18))
.with(vr_preg(19))
.with(vr_preg(20))
.with(vr_preg(21))
.with(vr_preg(22))
.with(vr_preg(23))
.with(vr_preg(24))
.with(vr_preg(25))
.with(vr_preg(26))
.with(vr_preg(27))
.with(vr_preg(28))
.with(vr_preg(29))
.with(vr_preg(30))
.with(vr_preg(31)),
PRegSet::empty(),
],
non_preferred_regs_by_class: [
PRegSet::empty()
.with(gpr_preg(8))
.with(gpr_preg(9))
.with(gpr_preg(10))
.with(gpr_preg(11))
.with(gpr_preg(12))
.with(gpr_preg(13))
.with(gpr_preg(14)),
PRegSet::empty()
.with(vr_preg(8))
.with(vr_preg(9))
.with(vr_preg(10))
.with(vr_preg(11))
.with(vr_preg(12))
.with(vr_preg(13))
.with(vr_preg(14))
.with(vr_preg(15)),
PRegSet::empty(),
],
fixed_stack_slots: vec![],
scratch_by_class: [None, None, None],
}
}