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:
Ulrich Weigand
2021-05-03 16:34:15 +02:00
parent 92e0b6b9e8
commit 89b5fc776d
43 changed files with 24276 additions and 2 deletions

View 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),
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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)
}
}

File diff suppressed because it is too large Load Diff

View 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,
}
}

View File

@@ -0,0 +1,2 @@
#[cfg(feature = "unwind")]
pub(crate) mod systemv;

View 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
}
}