Merge pull request #2874 from uweigand/s390x-backend
Support IBM z/Architecture
This commit is contained in:
@@ -90,6 +90,9 @@ mod arm32;
|
||||
#[cfg(feature = "arm64")]
|
||||
pub(crate) mod aarch64;
|
||||
|
||||
#[cfg(feature = "s390x")]
|
||||
mod s390x;
|
||||
|
||||
pub mod unwind;
|
||||
|
||||
mod call_conv;
|
||||
@@ -159,6 +162,7 @@ pub fn lookup_variant(triple: Triple, variant: BackendVariant) -> Result<Builder
|
||||
}
|
||||
(Architecture::Arm { .. }, _) => isa_builder!(arm32, (feature = "arm32"), triple),
|
||||
(Architecture::Aarch64 { .. }, _) => isa_builder!(aarch64, (feature = "arm64"), triple),
|
||||
(Architecture::S390x { .. }, _) => isa_builder!(s390x, (feature = "s390x"), triple),
|
||||
_ => Err(LookupError::Unsupported),
|
||||
}
|
||||
}
|
||||
|
||||
770
cranelift/codegen/src/isa/s390x/abi.rs
Normal file
770
cranelift/codegen/src/isa/s390x/abi.rs
Normal file
@@ -0,0 +1,770 @@
|
||||
//! Implementation of a standard S390x ABI.
|
||||
//!
|
||||
//! This machine uses the "vanilla" ABI implementation from abi_impl.rs,
|
||||
//! however a few details are different from the description there:
|
||||
//!
|
||||
//! - On s390x, the caller must provide a "register save area" of 160
|
||||
//! bytes to any function it calls. The called function is free to use
|
||||
//! this space for any purpose; usually to save callee-saved GPRs.
|
||||
//! (Note that while this area is allocated by the caller, it is counted
|
||||
//! as part of the callee's stack frame; in particular, the callee's CFA
|
||||
//! is the top of the register save area, not the incoming SP value.)
|
||||
//!
|
||||
//! - Overflow arguments are passed on the stack starting immediately
|
||||
//! above the register save area. On s390x, this space is allocated
|
||||
//! only once directly in the prologue, using a size large enough to
|
||||
//! hold overflow arguments for every call in the function.
|
||||
//!
|
||||
//! - On s390x we do not use a frame pointer register; instead, every
|
||||
//! element of the stack frame is addressed via (constant) offsets
|
||||
//! from the stack pointer. Note that due to the above (and because
|
||||
//! there are no variable-sized stack allocations in cranelift), the
|
||||
//! value of the stack pointer register never changes after the
|
||||
//! initial allocation in the function prologue.
|
||||
//!
|
||||
//! Overall, the stack frame layout on s390x is as follows:
|
||||
//!
|
||||
//! ```plain
|
||||
//! (high address)
|
||||
//!
|
||||
//! +---------------------------+
|
||||
//! | ... |
|
||||
//! CFA -----> | stack args |
|
||||
//! +---------------------------+
|
||||
//! | ... |
|
||||
//! | 160 bytes reg save area |
|
||||
//! SP at function entry -----> | (used to save GPRs) |
|
||||
//! +---------------------------+
|
||||
//! | ... |
|
||||
//! | clobbered callee-saves |
|
||||
//! | (used to save FPRs) |
|
||||
//! unwind-frame base ----> | (alloc'd by prologue) |
|
||||
//! +---------------------------+
|
||||
//! | ... |
|
||||
//! | spill slots |
|
||||
//! | (accessed via nominal SP) |
|
||||
//! | ... |
|
||||
//! | stack slots |
|
||||
//! | (accessed via nominal SP) |
|
||||
//! nominal SP ---------------> | (alloc'd by prologue) |
|
||||
//! +---------------------------+
|
||||
//! | ... |
|
||||
//! | args for call |
|
||||
//! | outgoing reg save area |
|
||||
//! SP during function ------> | (alloc'd by prologue) |
|
||||
//! +---------------------------+
|
||||
//!
|
||||
//! (low address)
|
||||
//! ```
|
||||
|
||||
use crate::ir;
|
||||
use crate::ir::condcodes::IntCC;
|
||||
use crate::ir::types;
|
||||
use crate::ir::MemFlags;
|
||||
use crate::ir::Type;
|
||||
use crate::isa;
|
||||
use crate::isa::s390x::inst::*;
|
||||
use crate::isa::unwind::UnwindInst;
|
||||
use crate::machinst::*;
|
||||
use crate::settings;
|
||||
use crate::{CodegenError, CodegenResult};
|
||||
use alloc::boxed::Box;
|
||||
use alloc::vec::Vec;
|
||||
use regalloc::{RealReg, Reg, RegClass, Set, Writable};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
// We use a generic implementation that factors out ABI commonalities.
|
||||
|
||||
/// Support for the S390x ABI from the callee side (within a function body).
|
||||
pub type S390xABICallee = ABICalleeImpl<S390xMachineDeps>;
|
||||
|
||||
/// Support for the S390x ABI from the caller side (at a callsite).
|
||||
pub type S390xABICaller = ABICallerImpl<S390xMachineDeps>;
|
||||
|
||||
/// ABI Register usage
|
||||
|
||||
fn in_int_reg(ty: Type) -> bool {
|
||||
match ty {
|
||||
types::I8 | types::I16 | types::I32 | types::I64 | types::R64 => true,
|
||||
types::B1 | types::B8 | types::B16 | types::B32 | types::B64 => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn in_flt_reg(ty: Type) -> bool {
|
||||
match ty {
|
||||
types::F32 | types::F64 => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_intreg_for_arg(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)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_fltreg_for_arg(idx: usize) -> Option<Reg> {
|
||||
match idx {
|
||||
0 => Some(regs::fpr(0)),
|
||||
1 => Some(regs::fpr(2)),
|
||||
2 => Some(regs::fpr(4)),
|
||||
3 => Some(regs::fpr(6)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_intreg_for_ret(idx: usize) -> Option<Reg> {
|
||||
match idx {
|
||||
0 => Some(regs::gpr(2)),
|
||||
// ABI extension to support multi-value returns:
|
||||
1 => Some(regs::gpr(3)),
|
||||
2 => Some(regs::gpr(4)),
|
||||
3 => Some(regs::gpr(5)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_fltreg_for_ret(idx: usize) -> Option<Reg> {
|
||||
match idx {
|
||||
0 => Some(regs::fpr(0)),
|
||||
// ABI extension to support multi-value returns:
|
||||
1 => Some(regs::fpr(2)),
|
||||
2 => Some(regs::fpr(4)),
|
||||
3 => Some(regs::fpr(6)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// This is the limit for the size of argument and return-value areas on the
|
||||
/// stack. We place a reasonable limit here to avoid integer overflow issues
|
||||
/// with 32-bit arithmetic: for now, 128 MB.
|
||||
static STACK_ARG_RET_SIZE_LIMIT: u64 = 128 * 1024 * 1024;
|
||||
|
||||
impl Into<MemArg> for StackAMode {
|
||||
fn into(self) -> MemArg {
|
||||
match self {
|
||||
StackAMode::FPOffset(off, _ty) => MemArg::InitialSPOffset { off },
|
||||
StackAMode::NominalSPOffset(off, _ty) => MemArg::NominalSPOffset { off },
|
||||
StackAMode::SPOffset(off, _ty) => {
|
||||
MemArg::reg_plus_off(stack_reg(), off, MemFlags::trusted())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// S390x-specific ABI behavior. This struct just serves as an implementation
|
||||
/// point for the trait; it is never actually instantiated.
|
||||
pub struct S390xMachineDeps;
|
||||
|
||||
impl ABIMachineSpec for S390xMachineDeps {
|
||||
type I = Inst;
|
||||
|
||||
fn word_bits() -> u32 {
|
||||
64
|
||||
}
|
||||
|
||||
/// Return required stack alignment in bytes.
|
||||
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,
|
||||
) -> CodegenResult<(Vec<ABIArg>, i64, Option<usize>)> {
|
||||
let mut next_gpr = 0;
|
||||
let mut next_fpr = 0;
|
||||
let mut next_stack: u64 = 0;
|
||||
let mut ret = vec![];
|
||||
|
||||
if args_or_rets == ArgsOrRets::Args {
|
||||
next_stack = 160;
|
||||
}
|
||||
|
||||
for i in 0..params.len() {
|
||||
let param = ¶ms[i];
|
||||
|
||||
// Validate "purpose".
|
||||
match ¶m.purpose {
|
||||
&ir::ArgumentPurpose::VMContext
|
||||
| &ir::ArgumentPurpose::Normal
|
||||
| &ir::ArgumentPurpose::StackLimit
|
||||
| &ir::ArgumentPurpose::SignatureId => {}
|
||||
_ => panic!(
|
||||
"Unsupported argument purpose {:?} in signature: {:?}",
|
||||
param.purpose, params
|
||||
),
|
||||
}
|
||||
|
||||
let intreg = in_int_reg(param.value_type);
|
||||
let fltreg = in_flt_reg(param.value_type);
|
||||
debug_assert!(intreg || fltreg);
|
||||
debug_assert!(!(intreg && fltreg));
|
||||
|
||||
let (next_reg, candidate) = if intreg {
|
||||
let candidate = match args_or_rets {
|
||||
ArgsOrRets::Args => get_intreg_for_arg(next_gpr),
|
||||
ArgsOrRets::Rets => get_intreg_for_ret(next_gpr),
|
||||
};
|
||||
(&mut next_gpr, candidate)
|
||||
} else {
|
||||
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)
|
||||
};
|
||||
|
||||
// In the Wasmtime ABI only the first return value can be in a register.
|
||||
let candidate =
|
||||
if call_conv.extends_wasmtime() && args_or_rets == ArgsOrRets::Rets && i > 0 {
|
||||
None
|
||||
} else {
|
||||
candidate
|
||||
};
|
||||
|
||||
if let Some(reg) = candidate {
|
||||
ret.push(ABIArg::reg(
|
||||
reg.to_real_reg(),
|
||||
param.value_type,
|
||||
param.extension,
|
||||
param.purpose,
|
||||
));
|
||||
*next_reg += 1;
|
||||
} else {
|
||||
// Compute size. Every argument or return value takes a slot of
|
||||
// at least 8 bytes, except for return values in the Wasmtime ABI.
|
||||
let size = (ty_bits(param.value_type) / 8) as u64;
|
||||
let slot_size = if call_conv.extends_wasmtime() && args_or_rets == ArgsOrRets::Rets
|
||||
{
|
||||
size
|
||||
} else {
|
||||
std::cmp::max(size, 8)
|
||||
};
|
||||
|
||||
// Align the stack slot.
|
||||
debug_assert!(slot_size.is_power_of_two());
|
||||
next_stack = align_to(next_stack, slot_size);
|
||||
|
||||
// If the type is actually of smaller size (and the argument
|
||||
// was not extended), it is passed right-aligned.
|
||||
let offset = if size < slot_size && param.extension == ir::ArgumentExtension::None {
|
||||
slot_size - size
|
||||
} else {
|
||||
0
|
||||
};
|
||||
ret.push(ABIArg::stack(
|
||||
(next_stack + offset) as i64,
|
||||
param.value_type,
|
||||
param.extension,
|
||||
param.purpose,
|
||||
));
|
||||
next_stack += slot_size;
|
||||
}
|
||||
}
|
||||
|
||||
next_stack = align_to(next_stack, 8);
|
||||
|
||||
let extra_arg = if add_ret_area_ptr {
|
||||
debug_assert!(args_or_rets == ArgsOrRets::Args);
|
||||
if let Some(reg) = get_intreg_for_arg(next_gpr) {
|
||||
ret.push(ABIArg::reg(
|
||||
reg.to_real_reg(),
|
||||
types::I64,
|
||||
ir::ArgumentExtension::None,
|
||||
ir::ArgumentPurpose::Normal,
|
||||
));
|
||||
} else {
|
||||
ret.push(ABIArg::stack(
|
||||
next_stack as i64,
|
||||
types::I64,
|
||||
ir::ArgumentExtension::None,
|
||||
ir::ArgumentPurpose::Normal,
|
||||
));
|
||||
next_stack += 8;
|
||||
}
|
||||
Some(ret.len() - 1)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// To avoid overflow issues, limit the arg/return size to something
|
||||
// reasonable -- here, 128 MB.
|
||||
if next_stack > STACK_ARG_RET_SIZE_LIMIT {
|
||||
return Err(CodegenError::ImplLimitExceeded);
|
||||
}
|
||||
|
||||
Ok((ret, next_stack as i64, extra_arg))
|
||||
}
|
||||
|
||||
fn fp_to_arg_offset(_call_conv: isa::CallConv, _flags: &settings::Flags) -> i64 {
|
||||
0
|
||||
}
|
||||
|
||||
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_ret() -> Inst {
|
||||
Inst::Ret { link: gpr(14) }
|
||||
}
|
||||
|
||||
fn gen_add_imm(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::Add64,
|
||||
rd: into_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::StackOverflow,
|
||||
});
|
||||
insts
|
||||
}
|
||||
|
||||
fn gen_epilogue_placeholder() -> Inst {
|
||||
Inst::EpiloguePlaceholder
|
||||
}
|
||||
|
||||
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() -> 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(),
|
||||
imm,
|
||||
});
|
||||
} else {
|
||||
insts.push(Inst::AluRSImm32 {
|
||||
alu_op: ALUOp::Add64,
|
||||
rd: writable_stack_reg(),
|
||||
imm,
|
||||
});
|
||||
}
|
||||
insts
|
||||
}
|
||||
|
||||
fn gen_nominal_sp_adj(offset: i32) -> Inst {
|
||||
Inst::VirtualSPOffsetAdj {
|
||||
offset: offset.into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_prologue_frame_setup(_flags: &settings::Flags) -> SmallInstVec<Inst> {
|
||||
SmallVec::new()
|
||||
}
|
||||
|
||||
fn gen_epilogue_frame_restore(_flags: &settings::Flags) -> SmallInstVec<Inst> {
|
||||
SmallVec::new()
|
||||
}
|
||||
|
||||
fn gen_probestack(_: u32) -> SmallInstVec<Self::I> {
|
||||
// TODO: implement if we ever require stack probes on an s390x host
|
||||
// (unlikely unless Lucet is ported)
|
||||
smallvec![]
|
||||
}
|
||||
|
||||
// Returns stack bytes used as well as instructions. Does not adjust
|
||||
// nominal SP offset; abi_impl generic code will do that.
|
||||
fn gen_clobber_save(
|
||||
call_conv: isa::CallConv,
|
||||
flags: &settings::Flags,
|
||||
clobbers: &Set<Writable<RealReg>>,
|
||||
fixed_frame_storage_size: u32,
|
||||
outgoing_args_size: u32,
|
||||
) -> (u64, SmallVec<[Inst; 16]>) {
|
||||
let mut insts = SmallVec::new();
|
||||
|
||||
// Collect clobbered registers.
|
||||
let (clobbered_gpr, clobbered_fpr) = get_regs_saved_in_prologue(call_conv, clobbers);
|
||||
let mut first_clobbered_gpr = 16;
|
||||
for reg in clobbered_gpr {
|
||||
let enc = reg.to_reg().get_hw_encoding();
|
||||
if enc < first_clobbered_gpr {
|
||||
first_clobbered_gpr = enc;
|
||||
}
|
||||
}
|
||||
let clobber_size = clobbered_fpr.len() * 8;
|
||||
if flags.unwind_info() {
|
||||
insts.push(Inst::Unwind {
|
||||
inst: UnwindInst::DefineNewFrame {
|
||||
offset_upward_to_caller_sp: 160,
|
||||
offset_downward_to_clobbers: clobber_size as u32,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Use STMG to save clobbered GPRs into save area.
|
||||
if first_clobbered_gpr < 16 {
|
||||
let offset = 8 * first_clobbered_gpr as i64;
|
||||
insts.push(Inst::StoreMultiple64 {
|
||||
rt: gpr(first_clobbered_gpr as u8),
|
||||
rt2: gpr(15),
|
||||
addr_reg: stack_reg(),
|
||||
addr_off: SImm20::maybe_from_i64(offset).unwrap(),
|
||||
});
|
||||
}
|
||||
if flags.unwind_info() {
|
||||
for i in first_clobbered_gpr..16 {
|
||||
insts.push(Inst::Unwind {
|
||||
inst: UnwindInst::SaveReg {
|
||||
clobber_offset: clobber_size as u32 + (i * 8) as u32,
|
||||
reg: gpr(i as u8).to_real_reg(),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Decrement stack pointer.
|
||||
let stack_size =
|
||||
outgoing_args_size as i32 + clobber_size as i32 + fixed_frame_storage_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,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let sp_adj = outgoing_args_size as i32;
|
||||
if sp_adj > 0 {
|
||||
insts.push(Self::gen_nominal_sp_adj(sp_adj));
|
||||
}
|
||||
|
||||
// Save FPRs.
|
||||
for (i, reg) in clobbered_fpr.iter().enumerate() {
|
||||
insts.push(Inst::FpuStore64 {
|
||||
rd: reg.to_reg().to_reg(),
|
||||
mem: MemArg::reg_plus_off(
|
||||
stack_reg(),
|
||||
(i * 8) as i64 + outgoing_args_size as i64 + fixed_frame_storage_size as i64,
|
||||
MemFlags::trusted(),
|
||||
),
|
||||
});
|
||||
if flags.unwind_info() {
|
||||
insts.push(Inst::Unwind {
|
||||
inst: UnwindInst::SaveReg {
|
||||
clobber_offset: (i * 8) as u32,
|
||||
reg: reg.to_reg(),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
(clobber_size as u64, insts)
|
||||
}
|
||||
|
||||
fn gen_clobber_restore(
|
||||
call_conv: isa::CallConv,
|
||||
_: &settings::Flags,
|
||||
clobbers: &Set<Writable<RealReg>>,
|
||||
fixed_frame_storage_size: u32,
|
||||
outgoing_args_size: u32,
|
||||
) -> SmallVec<[Inst; 16]> {
|
||||
let mut insts = SmallVec::new();
|
||||
|
||||
// Collect clobbered registers.
|
||||
let (clobbered_gpr, clobbered_fpr) = get_regs_saved_in_prologue(call_conv, clobbers);
|
||||
let mut first_clobbered_gpr = 16;
|
||||
for reg in clobbered_gpr {
|
||||
let enc = reg.to_reg().get_hw_encoding();
|
||||
if enc < first_clobbered_gpr {
|
||||
first_clobbered_gpr = enc;
|
||||
}
|
||||
}
|
||||
let clobber_size = clobbered_fpr.len() * 8;
|
||||
|
||||
// Restore FPRs.
|
||||
for (i, reg) in clobbered_fpr.iter().enumerate() {
|
||||
insts.push(Inst::FpuLoad64 {
|
||||
rd: Writable::from_reg(reg.to_reg().to_reg()),
|
||||
mem: MemArg::reg_plus_off(
|
||||
stack_reg(),
|
||||
(i * 8) as i64 + outgoing_args_size as i64 + fixed_frame_storage_size as i64,
|
||||
MemFlags::trusted(),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
// Increment stack pointer unless it will be restored implicitly.
|
||||
let stack_size =
|
||||
outgoing_args_size as i32 + clobber_size as i32 + fixed_frame_storage_size as i32;
|
||||
let implicit_sp_restore = first_clobbered_gpr < 16
|
||||
&& SImm20::maybe_from_i64(8 * first_clobbered_gpr as i64 + stack_size as i64).is_some();
|
||||
if !implicit_sp_restore {
|
||||
insts.extend(Self::gen_sp_reg_adjust(stack_size));
|
||||
}
|
||||
|
||||
// Use LMG to restore clobbered GPRs from save area.
|
||||
if first_clobbered_gpr < 16 {
|
||||
let mut offset = 8 * first_clobbered_gpr as i64;
|
||||
if implicit_sp_restore {
|
||||
offset += stack_size as i64;
|
||||
}
|
||||
insts.push(Inst::LoadMultiple64 {
|
||||
rt: writable_gpr(first_clobbered_gpr as u8),
|
||||
rt2: writable_gpr(15),
|
||||
addr_reg: stack_reg(),
|
||||
addr_off: SImm20::maybe_from_i64(offset).unwrap(),
|
||||
});
|
||||
}
|
||||
|
||||
insts
|
||||
}
|
||||
|
||||
fn gen_call(
|
||||
dest: &CallDest,
|
||||
uses: Vec<Reg>,
|
||||
defs: Vec<Writable<Reg>>,
|
||||
opcode: ir::Opcode,
|
||||
tmp: Writable<Reg>,
|
||||
_callee_conv: isa::CallConv,
|
||||
_caller_conv: isa::CallConv,
|
||||
) -> SmallVec<[(InstIsSafepoint, Inst); 2]> {
|
||||
let mut insts = SmallVec::new();
|
||||
match &dest {
|
||||
&CallDest::ExtName(ref name, RelocDistance::Near) => insts.push((
|
||||
InstIsSafepoint::Yes,
|
||||
Inst::Call {
|
||||
link: writable_gpr(14),
|
||||
info: Box::new(CallInfo {
|
||||
dest: name.clone(),
|
||||
uses,
|
||||
defs,
|
||||
opcode,
|
||||
}),
|
||||
},
|
||||
)),
|
||||
&CallDest::ExtName(ref name, RelocDistance::Far) => {
|
||||
insts.push((
|
||||
InstIsSafepoint::No,
|
||||
Inst::LoadExtNameFar {
|
||||
rd: tmp,
|
||||
name: Box::new(name.clone()),
|
||||
offset: 0,
|
||||
},
|
||||
));
|
||||
insts.push((
|
||||
InstIsSafepoint::Yes,
|
||||
Inst::CallInd {
|
||||
link: writable_gpr(14),
|
||||
info: Box::new(CallIndInfo {
|
||||
rn: tmp.to_reg(),
|
||||
uses,
|
||||
defs,
|
||||
opcode,
|
||||
}),
|
||||
},
|
||||
));
|
||||
}
|
||||
&CallDest::Reg(reg) => insts.push((
|
||||
InstIsSafepoint::Yes,
|
||||
Inst::CallInd {
|
||||
link: writable_gpr(14),
|
||||
info: Box::new(CallIndInfo {
|
||||
rn: *reg,
|
||||
uses,
|
||||
defs,
|
||||
opcode,
|
||||
}),
|
||||
},
|
||||
)),
|
||||
}
|
||||
|
||||
insts
|
||||
}
|
||||
|
||||
fn gen_memcpy(
|
||||
_call_conv: isa::CallConv,
|
||||
_dst: Reg,
|
||||
_src: Reg,
|
||||
_size: usize,
|
||||
) -> SmallVec<[Self::I; 8]> {
|
||||
unimplemented!("StructArgs not implemented for S390X yet");
|
||||
}
|
||||
|
||||
fn get_number_of_spillslots_for_value(rc: RegClass, ty: Type) -> u32 {
|
||||
// We allocate in terms of 8-byte slots.
|
||||
match (rc, ty) {
|
||||
(RegClass::I64, _) => 1,
|
||||
(RegClass::F64, _) => 1,
|
||||
_ => panic!("Unexpected register class!"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the current virtual-SP offset from an instruction-emission state.
|
||||
fn get_virtual_sp_offset_from_state(s: &EmitState) -> i64 {
|
||||
s.virtual_sp_offset
|
||||
}
|
||||
|
||||
/// Get the nominal-SP-to-FP offset from an instruction-emission state.
|
||||
fn get_nominal_sp_to_fp(s: &EmitState) -> i64 {
|
||||
s.initial_sp_offset
|
||||
}
|
||||
|
||||
fn get_regs_clobbered_by_call(call_conv_of_callee: isa::CallConv) -> Vec<Writable<Reg>> {
|
||||
let mut caller_saved = Vec::new();
|
||||
for i in 0..15 {
|
||||
let x = writable_gpr(i);
|
||||
if is_reg_clobbered_by_call(call_conv_of_callee, x.to_reg().to_real_reg()) {
|
||||
caller_saved.push(x);
|
||||
}
|
||||
}
|
||||
for i in 0..15 {
|
||||
let v = writable_fpr(i);
|
||||
if is_reg_clobbered_by_call(call_conv_of_callee, v.to_reg().to_real_reg()) {
|
||||
caller_saved.push(v);
|
||||
}
|
||||
}
|
||||
caller_saved
|
||||
}
|
||||
|
||||
fn get_ext_mode(
|
||||
_call_conv: isa::CallConv,
|
||||
specified: ir::ArgumentExtension,
|
||||
) -> ir::ArgumentExtension {
|
||||
specified
|
||||
}
|
||||
}
|
||||
|
||||
fn is_reg_saved_in_prologue(_call_conv: isa::CallConv, r: RealReg) -> bool {
|
||||
match r.get_class() {
|
||||
RegClass::I64 => {
|
||||
// r6 - r15 inclusive are callee-saves.
|
||||
r.get_hw_encoding() >= 6 && r.get_hw_encoding() <= 15
|
||||
}
|
||||
RegClass::F64 => {
|
||||
// f8 - f15 inclusive are callee-saves.
|
||||
r.get_hw_encoding() >= 8 && r.get_hw_encoding() <= 15
|
||||
}
|
||||
_ => panic!("Unexpected RegClass"),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_regs_saved_in_prologue(
|
||||
call_conv: isa::CallConv,
|
||||
regs: &Set<Writable<RealReg>>,
|
||||
) -> (Vec<Writable<RealReg>>, Vec<Writable<RealReg>>) {
|
||||
let mut int_saves = vec![];
|
||||
let mut fpr_saves = vec![];
|
||||
for ® in regs.iter() {
|
||||
if is_reg_saved_in_prologue(call_conv, reg.to_reg()) {
|
||||
match reg.to_reg().get_class() {
|
||||
RegClass::I64 => int_saves.push(reg),
|
||||
RegClass::F64 => fpr_saves.push(reg),
|
||||
_ => panic!("Unexpected RegClass"),
|
||||
}
|
||||
}
|
||||
}
|
||||
// Sort registers for deterministic code output.
|
||||
int_saves.sort_by_key(|r| r.to_reg().get_index());
|
||||
fpr_saves.sort_by_key(|r| r.to_reg().get_index());
|
||||
(int_saves, fpr_saves)
|
||||
}
|
||||
|
||||
fn is_reg_clobbered_by_call(_call_conv: isa::CallConv, r: RealReg) -> bool {
|
||||
match r.get_class() {
|
||||
RegClass::I64 => {
|
||||
// r0 - r5 inclusive are caller-saves.
|
||||
r.get_hw_encoding() <= 5
|
||||
}
|
||||
RegClass::F64 => {
|
||||
// f0 - f7 inclusive are caller-saves.
|
||||
r.get_hw_encoding() <= 7
|
||||
}
|
||||
_ => panic!("Unexpected RegClass"),
|
||||
}
|
||||
}
|
||||
317
cranelift/codegen/src/isa/s390x/inst/args.rs
Normal file
317
cranelift/codegen/src/isa/s390x/inst/args.rs
Normal file
@@ -0,0 +1,317 @@
|
||||
//! S390x ISA definitions: instruction arguments.
|
||||
|
||||
// Some variants are never constructed, but we still want them as options in the future.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use crate::ir::condcodes::{FloatCC, IntCC};
|
||||
use crate::ir::MemFlags;
|
||||
use crate::isa::s390x::inst::*;
|
||||
use crate::machinst::MachLabel;
|
||||
|
||||
use regalloc::{PrettyPrint, RealRegUniverse, Reg};
|
||||
|
||||
use std::string::String;
|
||||
|
||||
//=============================================================================
|
||||
// Instruction sub-components (memory addresses): definitions
|
||||
|
||||
/// A memory argument to load/store, encapsulating the possible addressing modes.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum MemArg {
|
||||
//
|
||||
// Real IBM Z addressing modes:
|
||||
//
|
||||
/// Base register, index register, and 12-bit unsigned displacement.
|
||||
BXD12 {
|
||||
base: Reg,
|
||||
index: Reg,
|
||||
disp: UImm12,
|
||||
flags: MemFlags,
|
||||
},
|
||||
|
||||
/// Base register, index register, and 20-bit signed displacement.
|
||||
BXD20 {
|
||||
base: Reg,
|
||||
index: Reg,
|
||||
disp: SImm20,
|
||||
flags: MemFlags,
|
||||
},
|
||||
|
||||
/// PC-relative Reference to a label.
|
||||
Label { target: BranchTarget },
|
||||
|
||||
/// PC-relative Reference to a near symbol.
|
||||
Symbol {
|
||||
name: Box<ExternalName>,
|
||||
offset: i32,
|
||||
flags: MemFlags,
|
||||
},
|
||||
|
||||
//
|
||||
// Virtual addressing modes that are lowered at emission time:
|
||||
//
|
||||
/// Arbitrary offset from a register. Converted to generation of large
|
||||
/// offsets with multiple instructions as necessary during code emission.
|
||||
RegOffset { reg: Reg, off: i64, flags: MemFlags },
|
||||
|
||||
/// Offset from the stack pointer at function entry.
|
||||
InitialSPOffset { off: i64 },
|
||||
|
||||
/// Offset from the "nominal stack pointer", which is where the real SP is
|
||||
/// just after stack and spill slots are allocated in the function prologue.
|
||||
/// At emission time, this is converted to `SPOffset` with a fixup added to
|
||||
/// the offset constant. The fixup is a running value that is tracked as
|
||||
/// emission iterates through instructions in linear order, and can be
|
||||
/// adjusted up and down with [Inst::VirtualSPOffsetAdj].
|
||||
///
|
||||
/// The standard ABI is in charge of handling this (by emitting the
|
||||
/// adjustment meta-instructions). It maintains the invariant that "nominal
|
||||
/// SP" is where the actual SP is after the function prologue and before
|
||||
/// clobber pushes. See the diagram in the documentation for
|
||||
/// [crate::isa::s390x::abi](the ABI module) for more details.
|
||||
NominalSPOffset { off: i64 },
|
||||
}
|
||||
|
||||
impl MemArg {
|
||||
/// Memory reference using an address in a register.
|
||||
pub fn reg(reg: Reg, flags: MemFlags) -> MemArg {
|
||||
MemArg::BXD12 {
|
||||
base: reg,
|
||||
index: zero_reg(),
|
||||
disp: UImm12::zero(),
|
||||
flags,
|
||||
}
|
||||
}
|
||||
|
||||
/// Memory reference using the sum of two registers as an address.
|
||||
pub fn reg_plus_reg(reg1: Reg, reg2: Reg, flags: MemFlags) -> MemArg {
|
||||
MemArg::BXD12 {
|
||||
base: reg1,
|
||||
index: reg2,
|
||||
disp: UImm12::zero(),
|
||||
flags,
|
||||
}
|
||||
}
|
||||
|
||||
/// Memory reference using the sum of a register an an offset as address.
|
||||
pub fn reg_plus_off(reg: Reg, off: i64, flags: MemFlags) -> MemArg {
|
||||
MemArg::RegOffset { reg, off, flags }
|
||||
}
|
||||
|
||||
pub(crate) fn get_flags(&self) -> MemFlags {
|
||||
match self {
|
||||
MemArg::BXD12 { flags, .. } => *flags,
|
||||
MemArg::BXD20 { flags, .. } => *flags,
|
||||
MemArg::RegOffset { flags, .. } => *flags,
|
||||
MemArg::Label { .. } => MemFlags::trusted(),
|
||||
MemArg::Symbol { flags, .. } => *flags,
|
||||
MemArg::InitialSPOffset { .. } => MemFlags::trusted(),
|
||||
MemArg::NominalSPOffset { .. } => MemFlags::trusted(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn can_trap(&self) -> bool {
|
||||
!self.get_flags().notrap()
|
||||
}
|
||||
}
|
||||
|
||||
//=============================================================================
|
||||
// Instruction sub-components (conditions, branches and branch targets):
|
||||
// definitions
|
||||
|
||||
/// Condition for conditional branches.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct Cond {
|
||||
mask: u8,
|
||||
}
|
||||
|
||||
impl Cond {
|
||||
pub fn from_mask(mask: u8) -> Cond {
|
||||
assert!(mask >= 1 && mask <= 14);
|
||||
Cond { mask }
|
||||
}
|
||||
|
||||
pub fn from_intcc(cc: IntCC) -> Cond {
|
||||
let mask = match cc {
|
||||
IntCC::Equal => 8,
|
||||
IntCC::NotEqual => 4 | 2,
|
||||
IntCC::SignedGreaterThanOrEqual => 8 | 2,
|
||||
IntCC::SignedGreaterThan => 2,
|
||||
IntCC::SignedLessThanOrEqual => 8 | 4,
|
||||
IntCC::SignedLessThan => 4,
|
||||
IntCC::UnsignedGreaterThanOrEqual => 8 | 2,
|
||||
IntCC::UnsignedGreaterThan => 2,
|
||||
IntCC::UnsignedLessThanOrEqual => 8 | 4,
|
||||
IntCC::UnsignedLessThan => 4,
|
||||
IntCC::Overflow => 1,
|
||||
IntCC::NotOverflow => 8 | 4 | 2,
|
||||
};
|
||||
Cond { mask }
|
||||
}
|
||||
|
||||
pub fn from_floatcc(cc: FloatCC) -> Cond {
|
||||
let mask = match cc {
|
||||
FloatCC::Ordered => 8 | 4 | 2,
|
||||
FloatCC::Unordered => 1,
|
||||
FloatCC::Equal => 8,
|
||||
FloatCC::NotEqual => 4 | 2 | 1,
|
||||
FloatCC::OrderedNotEqual => 4 | 2,
|
||||
FloatCC::UnorderedOrEqual => 8 | 1,
|
||||
FloatCC::LessThan => 4,
|
||||
FloatCC::LessThanOrEqual => 8 | 4,
|
||||
FloatCC::GreaterThan => 2,
|
||||
FloatCC::GreaterThanOrEqual => 8 | 2,
|
||||
FloatCC::UnorderedOrLessThan => 4 | 1,
|
||||
FloatCC::UnorderedOrLessThanOrEqual => 8 | 4 | 1,
|
||||
FloatCC::UnorderedOrGreaterThan => 2 | 1,
|
||||
FloatCC::UnorderedOrGreaterThanOrEqual => 8 | 2 | 1,
|
||||
};
|
||||
Cond { mask }
|
||||
}
|
||||
|
||||
/// Return the inverted condition.
|
||||
pub fn invert(self) -> Cond {
|
||||
Cond {
|
||||
mask: !self.mask & 15,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the machine encoding of this condition.
|
||||
pub fn bits(self) -> u8 {
|
||||
self.mask
|
||||
}
|
||||
}
|
||||
|
||||
/// A branch target. Either unresolved (basic-block index) or resolved (offset
|
||||
/// from end of current instruction).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum BranchTarget {
|
||||
/// An unresolved reference to a Label, as passed into
|
||||
/// `lower_branch_group()`.
|
||||
Label(MachLabel),
|
||||
/// A fixed PC offset.
|
||||
ResolvedOffset(i32),
|
||||
}
|
||||
|
||||
impl BranchTarget {
|
||||
/// Return the target's label, if it is a label-based target.
|
||||
pub fn as_label(self) -> Option<MachLabel> {
|
||||
match self {
|
||||
BranchTarget::Label(l) => Some(l),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the target's offset, if specified, or zero if label-based.
|
||||
pub fn as_ri_offset_or_zero(self) -> u16 {
|
||||
let off = match self {
|
||||
BranchTarget::ResolvedOffset(off) => off >> 1,
|
||||
_ => 0,
|
||||
};
|
||||
assert!(off <= 0x7fff);
|
||||
assert!(off >= -0x8000);
|
||||
off as u16
|
||||
}
|
||||
|
||||
/// Return the target's offset, if specified, or zero if label-based.
|
||||
pub fn as_ril_offset_or_zero(self) -> u32 {
|
||||
let off = match self {
|
||||
BranchTarget::ResolvedOffset(off) => off >> 1,
|
||||
_ => 0,
|
||||
};
|
||||
off as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl PrettyPrint for MemArg {
|
||||
fn show_rru(&self, mb_rru: Option<&RealRegUniverse>) -> String {
|
||||
match self {
|
||||
&MemArg::BXD12 {
|
||||
base, index, disp, ..
|
||||
} => {
|
||||
if base != zero_reg() {
|
||||
if index != zero_reg() {
|
||||
format!(
|
||||
"{}({},{})",
|
||||
disp.show_rru(mb_rru),
|
||||
index.show_rru(mb_rru),
|
||||
base.show_rru(mb_rru)
|
||||
)
|
||||
} else {
|
||||
format!("{}({})", disp.show_rru(mb_rru), base.show_rru(mb_rru))
|
||||
}
|
||||
} else {
|
||||
if index != zero_reg() {
|
||||
format!("{}({},)", disp.show_rru(mb_rru), index.show_rru(mb_rru))
|
||||
} else {
|
||||
format!("{}", disp.show_rru(mb_rru))
|
||||
}
|
||||
}
|
||||
}
|
||||
&MemArg::BXD20 {
|
||||
base, index, disp, ..
|
||||
} => {
|
||||
if base != zero_reg() {
|
||||
if index != zero_reg() {
|
||||
format!(
|
||||
"{}({},{})",
|
||||
disp.show_rru(mb_rru),
|
||||
index.show_rru(mb_rru),
|
||||
base.show_rru(mb_rru)
|
||||
)
|
||||
} else {
|
||||
format!("{}({})", disp.show_rru(mb_rru), base.show_rru(mb_rru))
|
||||
}
|
||||
} else {
|
||||
if index != zero_reg() {
|
||||
format!("{}({},)", disp.show_rru(mb_rru), index.show_rru(mb_rru))
|
||||
} else {
|
||||
format!("{}", disp.show_rru(mb_rru))
|
||||
}
|
||||
}
|
||||
}
|
||||
&MemArg::Label { ref target } => target.show_rru(mb_rru),
|
||||
&MemArg::Symbol {
|
||||
ref name, offset, ..
|
||||
} => format!("{} + {}", name, offset),
|
||||
// Eliminated by `mem_finalize()`.
|
||||
&MemArg::InitialSPOffset { .. }
|
||||
| &MemArg::NominalSPOffset { .. }
|
||||
| &MemArg::RegOffset { .. } => {
|
||||
panic!("Unexpected pseudo mem-arg mode (stack-offset or generic reg-offset)!")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PrettyPrint for Cond {
|
||||
fn show_rru(&self, _mb_rru: Option<&RealRegUniverse>) -> String {
|
||||
let s = match self.mask {
|
||||
1 => "o",
|
||||
2 => "h",
|
||||
3 => "nle",
|
||||
4 => "l",
|
||||
5 => "nhe",
|
||||
6 => "lh",
|
||||
7 => "ne",
|
||||
8 => "e",
|
||||
9 => "nlh",
|
||||
10 => "he",
|
||||
11 => "nl",
|
||||
12 => "le",
|
||||
13 => "nh",
|
||||
14 => "no",
|
||||
_ => unreachable!(),
|
||||
};
|
||||
s.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl PrettyPrint for BranchTarget {
|
||||
fn show_rru(&self, _mb_rru: Option<&RealRegUniverse>) -> String {
|
||||
match self {
|
||||
&BranchTarget::Label(label) => format!("label{:?}", label.get()),
|
||||
&BranchTarget::ResolvedOffset(off) => format!("{}", off),
|
||||
}
|
||||
}
|
||||
}
|
||||
1965
cranelift/codegen/src/isa/s390x/inst/emit.rs
Normal file
1965
cranelift/codegen/src/isa/s390x/inst/emit.rs
Normal file
File diff suppressed because it is too large
Load Diff
7140
cranelift/codegen/src/isa/s390x/inst/emit_tests.rs
Normal file
7140
cranelift/codegen/src/isa/s390x/inst/emit_tests.rs
Normal file
File diff suppressed because it is too large
Load Diff
231
cranelift/codegen/src/isa/s390x/inst/imms.rs
Normal file
231
cranelift/codegen/src/isa/s390x/inst/imms.rs
Normal file
@@ -0,0 +1,231 @@
|
||||
//! S390x ISA definitions: immediate constants.
|
||||
|
||||
use regalloc::{PrettyPrint, RealRegUniverse};
|
||||
use std::string::String;
|
||||
|
||||
/// An unsigned 12-bit immediate.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct UImm12 {
|
||||
/// The value.
|
||||
value: u16,
|
||||
}
|
||||
|
||||
impl UImm12 {
|
||||
pub fn maybe_from_u64(value: u64) -> Option<UImm12> {
|
||||
if value < 4096 {
|
||||
Some(UImm12 {
|
||||
value: value as u16,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a zero immediate of this format.
|
||||
pub fn zero() -> UImm12 {
|
||||
UImm12 { value: 0 }
|
||||
}
|
||||
|
||||
/// Bits for encoding.
|
||||
pub fn bits(&self) -> u32 {
|
||||
u32::from(self.value)
|
||||
}
|
||||
}
|
||||
|
||||
/// A signed 20-bit immediate.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct SImm20 {
|
||||
/// The value.
|
||||
value: i32,
|
||||
}
|
||||
|
||||
impl SImm20 {
|
||||
pub fn maybe_from_i64(value: i64) -> Option<SImm20> {
|
||||
if value >= -524288 && value < 524288 {
|
||||
Some(SImm20 {
|
||||
value: value as i32,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_uimm12(value: UImm12) -> SImm20 {
|
||||
SImm20 {
|
||||
value: value.bits() as i32,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a zero immediate of this format.
|
||||
pub fn zero() -> SImm20 {
|
||||
SImm20 { value: 0 }
|
||||
}
|
||||
|
||||
/// Bits for encoding.
|
||||
pub fn bits(&self) -> u32 {
|
||||
let encoded: u32 = self.value as u32;
|
||||
encoded & 0xfffff
|
||||
}
|
||||
}
|
||||
|
||||
/// A 16-bit immediate with a {0,16,32,48}-bit shift.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct UImm16Shifted {
|
||||
/// The value.
|
||||
pub bits: u16,
|
||||
/// Result is `bits` shifted 16*shift bits to the left.
|
||||
pub shift: u8,
|
||||
}
|
||||
|
||||
impl UImm16Shifted {
|
||||
/// Construct a UImm16Shifted from an arbitrary 64-bit constant if possible.
|
||||
pub fn maybe_from_u64(value: u64) -> Option<UImm16Shifted> {
|
||||
let mask0 = 0x0000_0000_0000_ffffu64;
|
||||
let mask1 = 0x0000_0000_ffff_0000u64;
|
||||
let mask2 = 0x0000_ffff_0000_0000u64;
|
||||
let mask3 = 0xffff_0000_0000_0000u64;
|
||||
|
||||
if value == (value & mask0) {
|
||||
return Some(UImm16Shifted {
|
||||
bits: (value & mask0) as u16,
|
||||
shift: 0,
|
||||
});
|
||||
}
|
||||
if value == (value & mask1) {
|
||||
return Some(UImm16Shifted {
|
||||
bits: ((value >> 16) & mask0) as u16,
|
||||
shift: 1,
|
||||
});
|
||||
}
|
||||
if value == (value & mask2) {
|
||||
return Some(UImm16Shifted {
|
||||
bits: ((value >> 32) & mask0) as u16,
|
||||
shift: 2,
|
||||
});
|
||||
}
|
||||
if value == (value & mask3) {
|
||||
return Some(UImm16Shifted {
|
||||
bits: ((value >> 48) & mask0) as u16,
|
||||
shift: 3,
|
||||
});
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn maybe_with_shift(imm: u16, shift: u8) -> Option<UImm16Shifted> {
|
||||
let shift_enc = shift / 16;
|
||||
if shift_enc > 3 {
|
||||
None
|
||||
} else {
|
||||
Some(UImm16Shifted {
|
||||
bits: imm,
|
||||
shift: shift_enc,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn negate_bits(&self) -> UImm16Shifted {
|
||||
UImm16Shifted {
|
||||
bits: !self.bits,
|
||||
shift: self.shift,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value that this constant represents.
|
||||
pub fn value(&self) -> u64 {
|
||||
(self.bits as u64) << (16 * self.shift)
|
||||
}
|
||||
}
|
||||
|
||||
/// A 32-bit immediate with a {0,32}-bit shift.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct UImm32Shifted {
|
||||
/// The value.
|
||||
pub bits: u32,
|
||||
/// Result is `bits` shifted 32*shift bits to the left.
|
||||
pub shift: u8,
|
||||
}
|
||||
|
||||
impl UImm32Shifted {
|
||||
/// Construct a UImm32Shifted from an arbitrary 64-bit constant if possible.
|
||||
pub fn maybe_from_u64(value: u64) -> Option<UImm32Shifted> {
|
||||
let mask0 = 0x0000_0000_ffff_ffffu64;
|
||||
let mask1 = 0xffff_ffff_0000_0000u64;
|
||||
|
||||
if value == (value & mask0) {
|
||||
return Some(UImm32Shifted {
|
||||
bits: (value & mask0) as u32,
|
||||
shift: 0,
|
||||
});
|
||||
}
|
||||
if value == (value & mask1) {
|
||||
return Some(UImm32Shifted {
|
||||
bits: ((value >> 32) & mask0) as u32,
|
||||
shift: 1,
|
||||
});
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn maybe_with_shift(imm: u32, shift: u8) -> Option<UImm32Shifted> {
|
||||
let shift_enc = shift / 32;
|
||||
if shift_enc > 3 {
|
||||
None
|
||||
} else {
|
||||
Some(UImm32Shifted {
|
||||
bits: imm,
|
||||
shift: shift_enc,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_uimm16shifted(value: UImm16Shifted) -> UImm32Shifted {
|
||||
if value.shift % 2 == 0 {
|
||||
UImm32Shifted {
|
||||
bits: value.bits as u32,
|
||||
shift: value.shift / 2,
|
||||
}
|
||||
} else {
|
||||
UImm32Shifted {
|
||||
bits: (value.bits as u32) << 16,
|
||||
shift: value.shift / 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn negate_bits(&self) -> UImm32Shifted {
|
||||
UImm32Shifted {
|
||||
bits: !self.bits,
|
||||
shift: self.shift,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the value that this constant represents.
|
||||
pub fn value(&self) -> u64 {
|
||||
(self.bits as u64) << (32 * self.shift)
|
||||
}
|
||||
}
|
||||
|
||||
impl PrettyPrint for UImm12 {
|
||||
fn show_rru(&self, _mb_rru: Option<&RealRegUniverse>) -> String {
|
||||
format!("{}", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl PrettyPrint for SImm20 {
|
||||
fn show_rru(&self, _mb_rru: Option<&RealRegUniverse>) -> String {
|
||||
format!("{}", self.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl PrettyPrint for UImm16Shifted {
|
||||
fn show_rru(&self, _mb_rru: Option<&RealRegUniverse>) -> String {
|
||||
format!("{}", self.bits)
|
||||
}
|
||||
}
|
||||
|
||||
impl PrettyPrint for UImm32Shifted {
|
||||
fn show_rru(&self, _mb_rru: Option<&RealRegUniverse>) -> String {
|
||||
format!("{}", self.bits)
|
||||
}
|
||||
}
|
||||
3411
cranelift/codegen/src/isa/s390x/inst/mod.rs
Normal file
3411
cranelift/codegen/src/isa/s390x/inst/mod.rs
Normal file
File diff suppressed because it is too large
Load Diff
168
cranelift/codegen/src/isa/s390x/inst/regs.rs
Normal file
168
cranelift/codegen/src/isa/s390x/inst/regs.rs
Normal file
@@ -0,0 +1,168 @@
|
||||
//! S390x ISA definitions: registers.
|
||||
|
||||
use crate::settings;
|
||||
use regalloc::{RealRegUniverse, Reg, RegClass, RegClassInfo, Writable, NUM_REG_CLASSES};
|
||||
|
||||
//=============================================================================
|
||||
// Registers, the Universe thereof, and printing
|
||||
|
||||
#[rustfmt::skip]
|
||||
const GPR_INDICES: [u8; 16] = [
|
||||
// r0 and r1 reserved
|
||||
30, 31,
|
||||
// r2 - r5 call-clobbered
|
||||
16, 17, 18, 19,
|
||||
// r6 - r14 call-saved (order reversed)
|
||||
28, 27, 26, 25, 24, 23, 22, 21, 20,
|
||||
// r15 (SP)
|
||||
29,
|
||||
];
|
||||
|
||||
#[rustfmt::skip]
|
||||
const FPR_INDICES: [u8; 16] = [
|
||||
// f0 - f7 as pairs
|
||||
0, 4, 1, 5, 2, 6, 3, 7,
|
||||
// f8 - f15 as pairs
|
||||
8, 12, 9, 13, 10, 14, 11, 15,
|
||||
];
|
||||
|
||||
/// Get a reference to a GPR (integer register).
|
||||
pub fn gpr(num: u8) -> Reg {
|
||||
assert!(num < 16);
|
||||
Reg::new_real(
|
||||
RegClass::I64,
|
||||
/* enc = */ num,
|
||||
/* index = */ GPR_INDICES[num as usize],
|
||||
)
|
||||
}
|
||||
|
||||
/// Get a writable reference to a GPR.
|
||||
pub fn writable_gpr(num: u8) -> Writable<Reg> {
|
||||
Writable::from_reg(gpr(num))
|
||||
}
|
||||
|
||||
/// Get a reference to a FPR (floating-point register).
|
||||
pub fn fpr(num: u8) -> Reg {
|
||||
assert!(num < 16);
|
||||
Reg::new_real(
|
||||
RegClass::F64,
|
||||
/* enc = */ num,
|
||||
/* index = */ FPR_INDICES[num as usize],
|
||||
)
|
||||
}
|
||||
|
||||
/// Get a writable reference to a V-register.
|
||||
pub fn writable_fpr(num: u8) -> Writable<Reg> {
|
||||
Writable::from_reg(fpr(num))
|
||||
}
|
||||
|
||||
/// Get a reference to the stack-pointer register.
|
||||
pub fn stack_reg() -> Reg {
|
||||
gpr(15)
|
||||
}
|
||||
|
||||
/// Get a writable reference to the stack-pointer register.
|
||||
pub fn writable_stack_reg() -> Writable<Reg> {
|
||||
Writable::from_reg(stack_reg())
|
||||
}
|
||||
|
||||
/// Get a reference to the first temporary, sometimes "spill temporary", register. This register is
|
||||
/// used to compute the address of a spill slot when a direct offset addressing mode from FP is not
|
||||
/// sufficient (+/- 2^11 words). We exclude this register from regalloc and reserve it for this
|
||||
/// purpose for simplicity; otherwise we need a multi-stage analysis where we first determine how
|
||||
/// many spill slots we have, then perhaps remove the reg from the pool and recompute regalloc.
|
||||
///
|
||||
/// We use r1 for this because it's a scratch register but is slightly special (used for linker
|
||||
/// veneers). We're free to use it as long as we don't expect it to live through call instructions.
|
||||
pub fn spilltmp_reg() -> Reg {
|
||||
gpr(1)
|
||||
}
|
||||
|
||||
/// Get a writable reference to the spilltmp reg.
|
||||
pub fn writable_spilltmp_reg() -> Writable<Reg> {
|
||||
Writable::from_reg(spilltmp_reg())
|
||||
}
|
||||
|
||||
pub fn zero_reg() -> Reg {
|
||||
gpr(0)
|
||||
}
|
||||
|
||||
/// Create the register universe for AArch64.
|
||||
pub fn create_reg_universe(_flags: &settings::Flags) -> RealRegUniverse {
|
||||
let mut regs = vec![];
|
||||
let mut allocable_by_class = [None; NUM_REG_CLASSES];
|
||||
|
||||
// Numbering Scheme: we put FPRs first, then GPRs. The GPRs exclude several registers:
|
||||
// r0 (we cannot use this for addressing // FIXME regalloc)
|
||||
// r1 (spilltmp)
|
||||
// r15 (stack pointer)
|
||||
|
||||
// FPRs.
|
||||
let mut base = regs.len();
|
||||
regs.push((fpr(0).to_real_reg(), "%f0".into()));
|
||||
regs.push((fpr(2).to_real_reg(), "%f2".into()));
|
||||
regs.push((fpr(4).to_real_reg(), "%f4".into()));
|
||||
regs.push((fpr(6).to_real_reg(), "%f6".into()));
|
||||
regs.push((fpr(1).to_real_reg(), "%f1".into()));
|
||||
regs.push((fpr(3).to_real_reg(), "%f3".into()));
|
||||
regs.push((fpr(5).to_real_reg(), "%f5".into()));
|
||||
regs.push((fpr(7).to_real_reg(), "%f7".into()));
|
||||
regs.push((fpr(8).to_real_reg(), "%f8".into()));
|
||||
regs.push((fpr(10).to_real_reg(), "%f10".into()));
|
||||
regs.push((fpr(12).to_real_reg(), "%f12".into()));
|
||||
regs.push((fpr(14).to_real_reg(), "%f14".into()));
|
||||
regs.push((fpr(9).to_real_reg(), "%f9".into()));
|
||||
regs.push((fpr(11).to_real_reg(), "%f11".into()));
|
||||
regs.push((fpr(13).to_real_reg(), "%f13".into()));
|
||||
regs.push((fpr(15).to_real_reg(), "%f15".into()));
|
||||
|
||||
allocable_by_class[RegClass::F64.rc_to_usize()] = Some(RegClassInfo {
|
||||
first: base,
|
||||
last: regs.len() - 1,
|
||||
suggested_scratch: Some(fpr(1).get_index()),
|
||||
});
|
||||
|
||||
// Caller-saved GPRs in the SystemV s390x ABI.
|
||||
base = regs.len();
|
||||
regs.push((gpr(2).to_real_reg(), "%r2".into()));
|
||||
regs.push((gpr(3).to_real_reg(), "%r3".into()));
|
||||
regs.push((gpr(4).to_real_reg(), "%r4".into()));
|
||||
regs.push((gpr(5).to_real_reg(), "%r5".into()));
|
||||
|
||||
// Callee-saved GPRs in the SystemV s390x ABI.
|
||||
// We start from r14 downwards in an attempt to allow the
|
||||
// prolog to use as short a STMG as possible.
|
||||
regs.push((gpr(14).to_real_reg(), "%r14".into()));
|
||||
regs.push((gpr(13).to_real_reg(), "%r13".into()));
|
||||
regs.push((gpr(12).to_real_reg(), "%r12".into()));
|
||||
regs.push((gpr(11).to_real_reg(), "%r11".into()));
|
||||
regs.push((gpr(10).to_real_reg(), "%r10".into()));
|
||||
regs.push((gpr(9).to_real_reg(), "%r9".into()));
|
||||
regs.push((gpr(8).to_real_reg(), "%r8".into()));
|
||||
regs.push((gpr(7).to_real_reg(), "%r7".into()));
|
||||
regs.push((gpr(6).to_real_reg(), "%r6".into()));
|
||||
|
||||
allocable_by_class[RegClass::I64.rc_to_usize()] = Some(RegClassInfo {
|
||||
first: base,
|
||||
last: regs.len() - 1,
|
||||
suggested_scratch: Some(gpr(13).get_index()),
|
||||
});
|
||||
|
||||
// Other regs, not available to the allocator.
|
||||
let allocable = regs.len();
|
||||
regs.push((gpr(15).to_real_reg(), "%r15".into()));
|
||||
regs.push((gpr(0).to_real_reg(), "%r0".into()));
|
||||
regs.push((gpr(1).to_real_reg(), "%r1".into()));
|
||||
|
||||
// Assert sanity: the indices in the register structs must match their
|
||||
// actual indices in the array.
|
||||
for (i, reg) in regs.iter().enumerate() {
|
||||
assert_eq!(i, reg.0.get_index());
|
||||
}
|
||||
|
||||
RealRegUniverse {
|
||||
regs,
|
||||
allocable,
|
||||
allocable_by_class,
|
||||
}
|
||||
}
|
||||
2
cranelift/codegen/src/isa/s390x/inst/unwind.rs
Normal file
2
cranelift/codegen/src/isa/s390x/inst/unwind.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
#[cfg(feature = "unwind")]
|
||||
pub(crate) mod systemv;
|
||||
197
cranelift/codegen/src/isa/s390x/inst/unwind/systemv.rs
Normal file
197
cranelift/codegen/src/isa/s390x/inst/unwind/systemv.rs
Normal file
@@ -0,0 +1,197 @@
|
||||
//! Unwind information for System V ABI (s390x).
|
||||
|
||||
use crate::isa::unwind::systemv::RegisterMappingError;
|
||||
use gimli::{write::CommonInformationEntry, Encoding, Format, Register};
|
||||
use regalloc::{Reg, RegClass};
|
||||
|
||||
/// Creates a new s390x common information entry (CIE).
|
||||
pub fn create_cie() -> CommonInformationEntry {
|
||||
use gimli::write::CallFrameInstruction;
|
||||
|
||||
let mut entry = CommonInformationEntry::new(
|
||||
Encoding {
|
||||
address_size: 8,
|
||||
format: Format::Dwarf32,
|
||||
version: 1,
|
||||
},
|
||||
1, // Code alignment factor
|
||||
-8, // Data alignment factor
|
||||
Register(14), // Return address column - register %r14
|
||||
);
|
||||
|
||||
// Every frame will start with the call frame address (CFA) at %r15 + 160.
|
||||
entry.add_instruction(CallFrameInstruction::Cfa(Register(15), 160));
|
||||
|
||||
entry
|
||||
}
|
||||
|
||||
/// Map Cranelift registers to their corresponding Gimli registers.
|
||||
pub fn map_reg(reg: Reg) -> Result<Register, RegisterMappingError> {
|
||||
const GPR_MAP: [gimli::Register; 16] = [
|
||||
Register(0),
|
||||
Register(1),
|
||||
Register(2),
|
||||
Register(3),
|
||||
Register(4),
|
||||
Register(5),
|
||||
Register(6),
|
||||
Register(7),
|
||||
Register(8),
|
||||
Register(9),
|
||||
Register(10),
|
||||
Register(11),
|
||||
Register(12),
|
||||
Register(13),
|
||||
Register(14),
|
||||
Register(15),
|
||||
];
|
||||
const FPR_MAP: [gimli::Register; 16] = [
|
||||
Register(16),
|
||||
Register(20),
|
||||
Register(17),
|
||||
Register(21),
|
||||
Register(18),
|
||||
Register(22),
|
||||
Register(19),
|
||||
Register(23),
|
||||
Register(24),
|
||||
Register(28),
|
||||
Register(25),
|
||||
Register(29),
|
||||
Register(26),
|
||||
Register(30),
|
||||
Register(27),
|
||||
Register(31),
|
||||
];
|
||||
|
||||
match reg.get_class() {
|
||||
RegClass::I64 => Ok(GPR_MAP[reg.get_hw_encoding() as usize]),
|
||||
RegClass::F64 => Ok(FPR_MAP[reg.get_hw_encoding() as usize]),
|
||||
_ => Err(RegisterMappingError::UnsupportedRegisterBank("class?")),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct RegisterMapper;
|
||||
|
||||
impl crate::isa::unwind::systemv::RegisterMapper<Reg> for RegisterMapper {
|
||||
fn map(&self, reg: Reg) -> Result<u16, RegisterMappingError> {
|
||||
Ok(map_reg(reg)?.0)
|
||||
}
|
||||
fn sp(&self) -> u16 {
|
||||
Register(15).0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::cursor::{Cursor, FuncCursor};
|
||||
use crate::ir::{
|
||||
types, AbiParam, ExternalName, Function, InstBuilder, Signature, StackSlotData,
|
||||
StackSlotKind,
|
||||
};
|
||||
use crate::isa::{lookup, CallConv};
|
||||
use crate::settings::{builder, Flags};
|
||||
use crate::Context;
|
||||
use gimli::write::Address;
|
||||
use std::str::FromStr;
|
||||
use target_lexicon::triple;
|
||||
|
||||
#[test]
|
||||
fn test_simple_func() {
|
||||
let isa = lookup(triple!("s390x"))
|
||||
.expect("expect s390x ISA")
|
||||
.finish(Flags::new(builder()));
|
||||
|
||||
let mut context = Context::for_function(create_function(
|
||||
CallConv::SystemV,
|
||||
Some(StackSlotData::new(StackSlotKind::ExplicitSlot, 64)),
|
||||
));
|
||||
|
||||
context.compile(&*isa).expect("expected compilation");
|
||||
|
||||
let fde = match context
|
||||
.create_unwind_info(isa.as_ref())
|
||||
.expect("can create unwind info")
|
||||
{
|
||||
Some(crate::isa::unwind::UnwindInfo::SystemV(info)) => {
|
||||
info.to_fde(Address::Constant(1234))
|
||||
}
|
||||
_ => panic!("expected unwind information"),
|
||||
};
|
||||
|
||||
assert_eq!(format!("{:?}", fde), "FrameDescriptionEntry { address: Constant(1234), length: 10, lsda: None, instructions: [(4, CfaOffset(224))] }");
|
||||
}
|
||||
|
||||
fn create_function(call_conv: CallConv, stack_slot: Option<StackSlotData>) -> Function {
|
||||
let mut func =
|
||||
Function::with_name_signature(ExternalName::user(0, 0), Signature::new(call_conv));
|
||||
|
||||
let block0 = func.dfg.make_block();
|
||||
let mut pos = FuncCursor::new(&mut func);
|
||||
pos.insert_block(block0);
|
||||
pos.ins().return_(&[]);
|
||||
|
||||
if let Some(stack_slot) = stack_slot {
|
||||
func.stack_slots.push(stack_slot);
|
||||
}
|
||||
|
||||
func
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multi_return_func() {
|
||||
let isa = lookup(triple!("s390x"))
|
||||
.expect("expect s390x ISA")
|
||||
.finish(Flags::new(builder()));
|
||||
|
||||
let mut context = Context::for_function(create_multi_return_function(
|
||||
CallConv::SystemV,
|
||||
Some(StackSlotData::new(StackSlotKind::ExplicitSlot, 64)),
|
||||
));
|
||||
|
||||
context.compile(&*isa).expect("expected compilation");
|
||||
|
||||
let fde = match context
|
||||
.create_unwind_info(isa.as_ref())
|
||||
.expect("can create unwind info")
|
||||
{
|
||||
Some(crate::isa::unwind::UnwindInfo::SystemV(info)) => {
|
||||
info.to_fde(Address::Constant(4321))
|
||||
}
|
||||
_ => panic!("expected unwind information"),
|
||||
};
|
||||
|
||||
assert_eq!(format!("{:?}", fde), "FrameDescriptionEntry { address: Constant(4321), length: 26, lsda: None, instructions: [(4, CfaOffset(224))] }");
|
||||
}
|
||||
|
||||
fn create_multi_return_function(
|
||||
call_conv: CallConv,
|
||||
stack_slot: Option<StackSlotData>,
|
||||
) -> Function {
|
||||
let mut sig = Signature::new(call_conv);
|
||||
sig.params.push(AbiParam::new(types::I32));
|
||||
let mut func = Function::with_name_signature(ExternalName::user(0, 0), sig);
|
||||
|
||||
let block0 = func.dfg.make_block();
|
||||
let v0 = func.dfg.append_block_param(block0, types::I32);
|
||||
let block1 = func.dfg.make_block();
|
||||
let block2 = func.dfg.make_block();
|
||||
|
||||
let mut pos = FuncCursor::new(&mut func);
|
||||
pos.insert_block(block0);
|
||||
pos.ins().brnz(v0, block2, &[]);
|
||||
pos.ins().jump(block1, &[]);
|
||||
|
||||
pos.insert_block(block1);
|
||||
pos.ins().return_(&[]);
|
||||
|
||||
pos.insert_block(block2);
|
||||
pos.ins().return_(&[]);
|
||||
|
||||
if let Some(stack_slot) = stack_slot {
|
||||
func.stack_slots.push(stack_slot);
|
||||
}
|
||||
|
||||
func
|
||||
}
|
||||
}
|
||||
2839
cranelift/codegen/src/isa/s390x/lower.rs
Normal file
2839
cranelift/codegen/src/isa/s390x/lower.rs
Normal file
File diff suppressed because it is too large
Load Diff
296
cranelift/codegen/src/isa/s390x/mod.rs
Normal file
296
cranelift/codegen/src/isa/s390x/mod.rs
Normal file
@@ -0,0 +1,296 @@
|
||||
//! IBM Z 64-bit Instruction Set Architecture.
|
||||
|
||||
use crate::ir::condcodes::IntCC;
|
||||
use crate::ir::Function;
|
||||
use crate::isa::s390x::settings as s390x_settings;
|
||||
use crate::isa::unwind::systemv::RegisterMappingError;
|
||||
use crate::isa::Builder as IsaBuilder;
|
||||
use crate::machinst::{compile, MachBackend, MachCompileResult, TargetIsaAdapter, VCode};
|
||||
use crate::result::CodegenResult;
|
||||
use crate::settings as shared_settings;
|
||||
|
||||
use alloc::{boxed::Box, vec::Vec};
|
||||
use core::hash::{Hash, Hasher};
|
||||
|
||||
use regalloc::{PrettyPrint, RealRegUniverse, Reg};
|
||||
use target_lexicon::{Architecture, Triple};
|
||||
|
||||
// New backend:
|
||||
mod abi;
|
||||
pub(crate) mod inst;
|
||||
mod lower;
|
||||
mod settings;
|
||||
|
||||
use inst::create_reg_universe;
|
||||
|
||||
use self::inst::EmitInfo;
|
||||
|
||||
/// A IBM Z backend.
|
||||
pub struct S390xBackend {
|
||||
triple: Triple,
|
||||
flags: shared_settings::Flags,
|
||||
isa_flags: s390x_settings::Flags,
|
||||
reg_universe: RealRegUniverse,
|
||||
}
|
||||
|
||||
impl S390xBackend {
|
||||
/// Create a new IBM Z backend with the given (shared) flags.
|
||||
pub fn new_with_flags(
|
||||
triple: Triple,
|
||||
flags: shared_settings::Flags,
|
||||
isa_flags: s390x_settings::Flags,
|
||||
) -> S390xBackend {
|
||||
let reg_universe = create_reg_universe(&flags);
|
||||
S390xBackend {
|
||||
triple,
|
||||
flags,
|
||||
isa_flags,
|
||||
reg_universe,
|
||||
}
|
||||
}
|
||||
|
||||
/// This performs lowering to VCode, register-allocates the code, computes block layout and
|
||||
/// finalizes branches. The result is ready for binary emission.
|
||||
fn compile_vcode(
|
||||
&self,
|
||||
func: &Function,
|
||||
flags: shared_settings::Flags,
|
||||
) -> CodegenResult<VCode<inst::Inst>> {
|
||||
let emit_info = EmitInfo::new(flags.clone());
|
||||
let abi = Box::new(abi::S390xABICallee::new(func, flags)?);
|
||||
compile::compile::<S390xBackend>(func, self, abi, emit_info)
|
||||
}
|
||||
}
|
||||
|
||||
impl MachBackend for S390xBackend {
|
||||
fn compile_function(
|
||||
&self,
|
||||
func: &Function,
|
||||
want_disasm: bool,
|
||||
) -> CodegenResult<MachCompileResult> {
|
||||
let flags = self.flags();
|
||||
let vcode = self.compile_vcode(func, flags.clone())?;
|
||||
let buffer = vcode.emit();
|
||||
let frame_size = vcode.frame_size();
|
||||
let value_labels_ranges = vcode.value_labels_ranges();
|
||||
let stackslot_offsets = vcode.stackslot_offsets().clone();
|
||||
|
||||
let disasm = if want_disasm {
|
||||
Some(vcode.show_rru(Some(&create_reg_universe(flags))))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let buffer = buffer.finish();
|
||||
|
||||
Ok(MachCompileResult {
|
||||
buffer,
|
||||
frame_size,
|
||||
disasm,
|
||||
value_labels_ranges,
|
||||
stackslot_offsets,
|
||||
})
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"s390x"
|
||||
}
|
||||
|
||||
fn triple(&self) -> Triple {
|
||||
self.triple.clone()
|
||||
}
|
||||
|
||||
fn flags(&self) -> &shared_settings::Flags {
|
||||
&self.flags
|
||||
}
|
||||
|
||||
fn isa_flags(&self) -> Vec<shared_settings::Value> {
|
||||
self.isa_flags.iter().collect()
|
||||
}
|
||||
|
||||
fn hash_all_flags(&self, mut hasher: &mut dyn Hasher) {
|
||||
self.flags.hash(&mut hasher);
|
||||
self.isa_flags.hash(&mut hasher);
|
||||
}
|
||||
|
||||
fn reg_universe(&self) -> &RealRegUniverse {
|
||||
&self.reg_universe
|
||||
}
|
||||
|
||||
fn unsigned_add_overflow_condition(&self) -> IntCC {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn unsigned_sub_overflow_condition(&self) -> IntCC {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg(feature = "unwind")]
|
||||
fn emit_unwind_info(
|
||||
&self,
|
||||
result: &MachCompileResult,
|
||||
kind: crate::machinst::UnwindInfoKind,
|
||||
) -> CodegenResult<Option<crate::isa::unwind::UnwindInfo>> {
|
||||
use crate::isa::unwind::UnwindInfo;
|
||||
use crate::machinst::UnwindInfoKind;
|
||||
Ok(match kind {
|
||||
UnwindInfoKind::SystemV => {
|
||||
let mapper = self::inst::unwind::systemv::RegisterMapper;
|
||||
Some(UnwindInfo::SystemV(
|
||||
crate::isa::unwind::systemv::create_unwind_info_from_insts(
|
||||
&result.buffer.unwind_info[..],
|
||||
result.buffer.data.len(),
|
||||
&mapper,
|
||||
)?,
|
||||
))
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(feature = "unwind")]
|
||||
fn create_systemv_cie(&self) -> Option<gimli::write::CommonInformationEntry> {
|
||||
Some(inst::unwind::systemv::create_cie())
|
||||
}
|
||||
|
||||
#[cfg(feature = "unwind")]
|
||||
fn map_reg_to_dwarf(&self, reg: Reg) -> Result<u16, RegisterMappingError> {
|
||||
inst::unwind::systemv::map_reg(reg).map(|reg| reg.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `isa::Builder`.
|
||||
pub fn isa_builder(triple: Triple) -> IsaBuilder {
|
||||
assert!(triple.architecture == Architecture::S390x);
|
||||
IsaBuilder {
|
||||
triple,
|
||||
setup: s390x_settings::builder(),
|
||||
constructor: |triple, shared_flags, builder| {
|
||||
let isa_flags = s390x_settings::Flags::new(&shared_flags, builder);
|
||||
let backend = S390xBackend::new_with_flags(triple, shared_flags, isa_flags);
|
||||
Box::new(TargetIsaAdapter::new(backend))
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::cursor::{Cursor, FuncCursor};
|
||||
use crate::ir::types::*;
|
||||
use crate::ir::{AbiParam, ExternalName, Function, InstBuilder, Signature};
|
||||
use crate::isa::CallConv;
|
||||
use crate::settings;
|
||||
use crate::settings::Configurable;
|
||||
use core::str::FromStr;
|
||||
use target_lexicon::Triple;
|
||||
|
||||
#[test]
|
||||
fn test_compile_function() {
|
||||
let name = ExternalName::testcase("test0");
|
||||
let mut sig = Signature::new(CallConv::SystemV);
|
||||
sig.params.push(AbiParam::new(I32));
|
||||
sig.returns.push(AbiParam::new(I32));
|
||||
let mut func = Function::with_name_signature(name, sig);
|
||||
|
||||
let bb0 = func.dfg.make_block();
|
||||
let arg0 = func.dfg.append_block_param(bb0, I32);
|
||||
|
||||
let mut pos = FuncCursor::new(&mut func);
|
||||
pos.insert_block(bb0);
|
||||
let v0 = pos.ins().iconst(I32, 0x1234);
|
||||
let v1 = pos.ins().iadd(arg0, v0);
|
||||
pos.ins().return_(&[v1]);
|
||||
|
||||
let mut shared_flags_builder = settings::builder();
|
||||
shared_flags_builder.set("opt_level", "none").unwrap();
|
||||
let shared_flags = settings::Flags::new(shared_flags_builder);
|
||||
let isa_flags = s390x_settings::Flags::new(&shared_flags, s390x_settings::builder());
|
||||
let backend = S390xBackend::new_with_flags(
|
||||
Triple::from_str("s390x").unwrap(),
|
||||
shared_flags,
|
||||
isa_flags,
|
||||
);
|
||||
let result = backend
|
||||
.compile_function(&mut func, /* want_disasm = */ false)
|
||||
.unwrap();
|
||||
let code = &result.buffer.data[..];
|
||||
|
||||
// ahi %r2, 0x1234
|
||||
// br %r14
|
||||
let golden = vec![0xa7, 0x2a, 0x12, 0x34, 0x07, 0xfe];
|
||||
|
||||
assert_eq!(code, &golden[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_branch_lowering() {
|
||||
let name = ExternalName::testcase("test0");
|
||||
let mut sig = Signature::new(CallConv::SystemV);
|
||||
sig.params.push(AbiParam::new(I32));
|
||||
sig.returns.push(AbiParam::new(I32));
|
||||
let mut func = Function::with_name_signature(name, sig);
|
||||
|
||||
let bb0 = func.dfg.make_block();
|
||||
let arg0 = func.dfg.append_block_param(bb0, I32);
|
||||
let bb1 = func.dfg.make_block();
|
||||
let bb2 = func.dfg.make_block();
|
||||
let bb3 = func.dfg.make_block();
|
||||
|
||||
let mut pos = FuncCursor::new(&mut func);
|
||||
pos.insert_block(bb0);
|
||||
let v0 = pos.ins().iconst(I32, 0x1234);
|
||||
let v1 = pos.ins().iadd(arg0, v0);
|
||||
pos.ins().brnz(v1, bb1, &[]);
|
||||
pos.ins().jump(bb2, &[]);
|
||||
pos.insert_block(bb1);
|
||||
pos.ins().brnz(v1, bb2, &[]);
|
||||
pos.ins().jump(bb3, &[]);
|
||||
pos.insert_block(bb2);
|
||||
let v2 = pos.ins().iadd(v1, v0);
|
||||
pos.ins().brnz(v2, bb2, &[]);
|
||||
pos.ins().jump(bb1, &[]);
|
||||
pos.insert_block(bb3);
|
||||
let v3 = pos.ins().isub(v1, v0);
|
||||
pos.ins().return_(&[v3]);
|
||||
|
||||
let mut shared_flags_builder = settings::builder();
|
||||
shared_flags_builder.set("opt_level", "none").unwrap();
|
||||
let shared_flags = settings::Flags::new(shared_flags_builder);
|
||||
let isa_flags = s390x_settings::Flags::new(&shared_flags, s390x_settings::builder());
|
||||
let backend = S390xBackend::new_with_flags(
|
||||
Triple::from_str("s390x").unwrap(),
|
||||
shared_flags,
|
||||
isa_flags,
|
||||
);
|
||||
let result = backend
|
||||
.compile_function(&mut func, /* want_disasm = */ false)
|
||||
.unwrap();
|
||||
let code = &result.buffer.data[..];
|
||||
|
||||
// FIXME: the branching logic should be optimized more
|
||||
|
||||
// ahi %r2, 4660
|
||||
// chi %r2, 0
|
||||
// jglh label1 ; jg label2
|
||||
// jg label6
|
||||
// jg label3
|
||||
// ahik %r3, %r2, 4660
|
||||
// chi %r3, 0
|
||||
// jglh label4 ; jg label5
|
||||
// jg label3
|
||||
// jg label6
|
||||
// chi %r2, 0
|
||||
// jglh label7 ; jg label8
|
||||
// jg label3
|
||||
// ahi %r2, -4660
|
||||
// br %r14
|
||||
let golden = vec![
|
||||
167, 42, 18, 52, 167, 46, 0, 0, 192, 100, 0, 0, 0, 11, 236, 50, 18, 52, 0, 216, 167,
|
||||
62, 0, 0, 192, 100, 255, 255, 255, 251, 167, 46, 0, 0, 192, 100, 255, 255, 255, 246,
|
||||
167, 42, 237, 204, 7, 254,
|
||||
];
|
||||
|
||||
assert_eq!(code, &golden[..]);
|
||||
}
|
||||
}
|
||||
9
cranelift/codegen/src/isa/s390x/settings.rs
Normal file
9
cranelift/codegen/src/isa/s390x/settings.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
//! S390X Settings.
|
||||
|
||||
use crate::settings::{self, detail, Builder, Value};
|
||||
use core::fmt;
|
||||
|
||||
// Include code generated by `cranelift-codegen/meta/src/gen_settings.rs:`. This file contains a
|
||||
// public `Flags` struct with an impl for all of the settings defined in
|
||||
// `cranelift-codegen/meta/src/isa/s390x/settings.rs`.
|
||||
include!(concat!(env!("OUT_DIR"), "/settings-s390x.rs"));
|
||||
Reference in New Issue
Block a user