Support IBM z/Architecture
This adds support for the IBM z/Architecture (s390x-ibm-linux). The status of the s390x backend in its current form is: - Wasmtime is fully functional and passes all tests on s390x. - All back-end features supported, with the exception of SIMD. - There is still a lot of potential for performance improvements. - Currently the only supported processor type is z15.
This commit is contained in:
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user