s390x: Support preserve_frame_pointers flag (#4477)
On s390x, we do not have a frame pointer that can be used to chain stack frames for easy unwinding. Instead, our ABI defines a stack "backchain" mechanism that can be used to the same effect. This PR uses that backchain mechanism to implement the new preserve_frame_pointers flags introduced here: https://github.com/bytecodealliance/wasmtime/pull/4469
This commit is contained in:
@@ -22,6 +22,13 @@
|
|||||||
//! value of the stack pointer register never changes after the
|
//! value of the stack pointer register never changes after the
|
||||||
//! initial allocation in the function prologue.
|
//! initial allocation in the function prologue.
|
||||||
//!
|
//!
|
||||||
|
//! - If we are asked to "preserve frame pointers" to enable stack
|
||||||
|
//! unwinding, we use the stack backchain feature instead, which
|
||||||
|
//! is documented by the s390x ELF ABI, but marked as optional.
|
||||||
|
//! This ensures that at all times during execution of a function,
|
||||||
|
//! the lowest word on the stack (part of the register save area)
|
||||||
|
//! holds a copy of the stack pointer at function entry.
|
||||||
|
//!
|
||||||
//! Overall, the stack frame layout on s390x is as follows:
|
//! Overall, the stack frame layout on s390x is as follows:
|
||||||
//!
|
//!
|
||||||
//! ```plain
|
//! ```plain
|
||||||
@@ -33,7 +40,8 @@
|
|||||||
//! +---------------------------+
|
//! +---------------------------+
|
||||||
//! | ... |
|
//! | ... |
|
||||||
//! | 160 bytes reg save area |
|
//! | 160 bytes reg save area |
|
||||||
//! SP at function entry -----> | (used to save GPRs) |
|
//! | (used to save GPRs) |
|
||||||
|
//! SP at function entry -----> | (incl. caller's backchain)|
|
||||||
//! +---------------------------+
|
//! +---------------------------+
|
||||||
//! | ... |
|
//! | ... |
|
||||||
//! | clobbered callee-saves |
|
//! | clobbered callee-saves |
|
||||||
@@ -51,7 +59,8 @@
|
|||||||
//! | ... |
|
//! | ... |
|
||||||
//! | args for call |
|
//! | args for call |
|
||||||
//! | outgoing reg save area |
|
//! | outgoing reg save area |
|
||||||
//! SP during function ------> | (alloc'd by prologue) |
|
//! | (alloc'd by prologue) |
|
||||||
|
//! SP during function ------> | (incl. callee's backchain)|
|
||||||
//! +---------------------------+
|
//! +---------------------------+
|
||||||
//!
|
//!
|
||||||
//! (low address)
|
//! (low address)
|
||||||
@@ -178,6 +187,9 @@ fn get_vecreg_for_ret(idx: usize) -> Option<Reg> {
|
|||||||
/// with 32-bit arithmetic: for now, 128 MB.
|
/// with 32-bit arithmetic: for now, 128 MB.
|
||||||
static STACK_ARG_RET_SIZE_LIMIT: u64 = 128 * 1024 * 1024;
|
static STACK_ARG_RET_SIZE_LIMIT: u64 = 128 * 1024 * 1024;
|
||||||
|
|
||||||
|
/// The size of the register save area
|
||||||
|
static REG_SAVE_AREA_SIZE: u32 = 160;
|
||||||
|
|
||||||
impl Into<MemArg> for StackAMode {
|
impl Into<MemArg> for StackAMode {
|
||||||
fn into(self) -> MemArg {
|
fn into(self) -> MemArg {
|
||||||
match self {
|
match self {
|
||||||
@@ -220,7 +232,7 @@ impl ABIMachineSpec for S390xMachineDeps {
|
|||||||
let mut ret = vec![];
|
let mut ret = vec![];
|
||||||
|
|
||||||
if args_or_rets == ArgsOrRets::Args {
|
if args_or_rets == ArgsOrRets::Args {
|
||||||
next_stack = 160;
|
next_stack = REG_SAVE_AREA_SIZE as u64;
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in 0..params.len() {
|
for i in 0..params.len() {
|
||||||
@@ -506,19 +518,18 @@ impl ABIMachineSpec for S390xMachineDeps {
|
|||||||
flags: &settings::Flags,
|
flags: &settings::Flags,
|
||||||
clobbered_callee_saves: &[Writable<RealReg>],
|
clobbered_callee_saves: &[Writable<RealReg>],
|
||||||
fixed_frame_storage_size: u32,
|
fixed_frame_storage_size: u32,
|
||||||
outgoing_args_size: u32,
|
mut outgoing_args_size: u32,
|
||||||
) -> (u64, SmallVec<[Inst; 16]>) {
|
) -> (u64, SmallVec<[Inst; 16]>) {
|
||||||
let mut insts = SmallVec::new();
|
let mut insts = SmallVec::new();
|
||||||
|
|
||||||
// Collect clobbered registers.
|
// Collect clobbered registers.
|
||||||
let is_leaf = outgoing_args_size == 0;
|
|
||||||
let (first_clobbered_gpr, clobbered_fpr) =
|
let (first_clobbered_gpr, clobbered_fpr) =
|
||||||
get_clobbered_gpr_fpr(clobbered_callee_saves, is_leaf);
|
get_clobbered_gpr_fpr(flags, clobbered_callee_saves, &mut outgoing_args_size);
|
||||||
let clobber_size = clobbered_fpr.len() * 8;
|
let clobber_size = clobbered_fpr.len() * 8;
|
||||||
if flags.unwind_info() {
|
if flags.unwind_info() {
|
||||||
insts.push(Inst::Unwind {
|
insts.push(Inst::Unwind {
|
||||||
inst: UnwindInst::DefineNewFrame {
|
inst: UnwindInst::DefineNewFrame {
|
||||||
offset_upward_to_caller_sp: 160,
|
offset_upward_to_caller_sp: REG_SAVE_AREA_SIZE,
|
||||||
offset_downward_to_clobbers: clobber_size as u32,
|
offset_downward_to_clobbers: clobber_size as u32,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -544,6 +555,11 @@ impl ABIMachineSpec for S390xMachineDeps {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save current stack pointer value if we need to write the backchain.
|
||||||
|
if flags.preserve_frame_pointers() {
|
||||||
|
insts.push(Inst::mov64(writable_gpr(1), stack_reg()));
|
||||||
|
}
|
||||||
|
|
||||||
// Decrement stack pointer.
|
// Decrement stack pointer.
|
||||||
let stack_size =
|
let stack_size =
|
||||||
outgoing_args_size as i32 + clobber_size as i32 + fixed_frame_storage_size as i32;
|
outgoing_args_size as i32 + clobber_size as i32 + fixed_frame_storage_size as i32;
|
||||||
@@ -561,6 +577,14 @@ impl ABIMachineSpec for S390xMachineDeps {
|
|||||||
insts.push(Self::gen_nominal_sp_adj(sp_adj));
|
insts.push(Self::gen_nominal_sp_adj(sp_adj));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write the stack backchain if requested, using the value saved above.
|
||||||
|
if flags.preserve_frame_pointers() {
|
||||||
|
insts.push(Inst::Store64 {
|
||||||
|
rd: gpr(1),
|
||||||
|
mem: MemArg::reg_plus_off(stack_reg(), 0, MemFlags::trusted()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Save FPRs.
|
// Save FPRs.
|
||||||
for (i, reg) in clobbered_fpr.iter().enumerate() {
|
for (i, reg) in clobbered_fpr.iter().enumerate() {
|
||||||
insts.push(Inst::VecStoreLane {
|
insts.push(Inst::VecStoreLane {
|
||||||
@@ -592,16 +616,15 @@ impl ABIMachineSpec for S390xMachineDeps {
|
|||||||
flags: &settings::Flags,
|
flags: &settings::Flags,
|
||||||
clobbers: &[Writable<RealReg>],
|
clobbers: &[Writable<RealReg>],
|
||||||
fixed_frame_storage_size: u32,
|
fixed_frame_storage_size: u32,
|
||||||
outgoing_args_size: u32,
|
mut outgoing_args_size: u32,
|
||||||
) -> SmallVec<[Inst; 16]> {
|
) -> SmallVec<[Inst; 16]> {
|
||||||
let mut insts = SmallVec::new();
|
let mut insts = SmallVec::new();
|
||||||
let clobbered_callee_saves =
|
let clobbered_callee_saves =
|
||||||
Self::get_clobbered_callee_saves(call_conv, flags, sig, clobbers);
|
Self::get_clobbered_callee_saves(call_conv, flags, sig, clobbers);
|
||||||
|
|
||||||
// Collect clobbered registers.
|
// Collect clobbered registers.
|
||||||
let is_leaf = outgoing_args_size == 0;
|
|
||||||
let (first_clobbered_gpr, clobbered_fpr) =
|
let (first_clobbered_gpr, clobbered_fpr) =
|
||||||
get_clobbered_gpr_fpr(&clobbered_callee_saves, is_leaf);
|
get_clobbered_gpr_fpr(flags, &clobbered_callee_saves, &mut outgoing_args_size);
|
||||||
let clobber_size = clobbered_fpr.len() * 8;
|
let clobber_size = clobbered_fpr.len() * 8;
|
||||||
|
|
||||||
// Restore FPRs.
|
// Restore FPRs.
|
||||||
@@ -742,8 +765,9 @@ fn is_reg_saved_in_prologue(_call_conv: isa::CallConv, r: RealReg) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_clobbered_gpr_fpr(
|
fn get_clobbered_gpr_fpr(
|
||||||
|
flags: &settings::Flags,
|
||||||
clobbered_callee_saves: &[Writable<RealReg>],
|
clobbered_callee_saves: &[Writable<RealReg>],
|
||||||
is_leaf: bool,
|
outgoing_args_size: &mut u32,
|
||||||
) -> (u8, SmallVec<[Writable<RealReg>; 8]>) {
|
) -> (u8, SmallVec<[Writable<RealReg>; 8]>) {
|
||||||
// Collect clobbered registers. Note we save/restore GPR always as
|
// Collect clobbered registers. Note we save/restore GPR always as
|
||||||
// a block of registers using LOAD MULTIPLE / STORE MULTIPLE, starting
|
// a block of registers using LOAD MULTIPLE / STORE MULTIPLE, starting
|
||||||
@@ -752,11 +776,23 @@ fn get_clobbered_gpr_fpr(
|
|||||||
let mut clobbered_fpr = SmallVec::new();
|
let mut clobbered_fpr = SmallVec::new();
|
||||||
let mut first_clobbered_gpr = 16;
|
let mut first_clobbered_gpr = 16;
|
||||||
|
|
||||||
|
// If the front end asks to preserve frame pointers (which we do not
|
||||||
|
// really have in the s390x ABI), we use the stack backchain instead.
|
||||||
|
// For this to work in all cases, we must allocate a stack frame with
|
||||||
|
// at least the outgoing register save area even in leaf functions.
|
||||||
|
// Update out caller's outgoing_args_size to reflect this.
|
||||||
|
if flags.preserve_frame_pointers() {
|
||||||
|
if *outgoing_args_size < REG_SAVE_AREA_SIZE {
|
||||||
|
*outgoing_args_size = REG_SAVE_AREA_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We need to save/restore the link register in non-leaf functions.
|
// We need to save/restore the link register in non-leaf functions.
|
||||||
// FIXME: This should be included in the clobber list to begin with,
|
// This is not included in the clobber list because we have excluded
|
||||||
// but isn't because we have have excluded call instructions via the
|
// call instructions via the is_included_in_clobbers callback.
|
||||||
// is_included_in_clobbers callback.
|
// We also want to enforce saving the link register in leaf functions
|
||||||
if !is_leaf {
|
// for stack unwinding, if we're asked to preserve frame pointers.
|
||||||
|
if *outgoing_args_size > 0 {
|
||||||
first_clobbered_gpr = 14;
|
first_clobbered_gpr = 14;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
15
cranelift/filetests/filetests/isa/s390x/leaf.clif
Normal file
15
cranelift/filetests/filetests/isa/s390x/leaf.clif
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
;; Test compilation of leaf functions without preserving frame pointers.
|
||||||
|
|
||||||
|
test compile precise-output
|
||||||
|
set unwind_info=false
|
||||||
|
set preserve_frame_pointers=false
|
||||||
|
target s390x
|
||||||
|
|
||||||
|
function %leaf(i64) -> i64 {
|
||||||
|
block0(v0: i64):
|
||||||
|
return v0
|
||||||
|
}
|
||||||
|
|
||||||
|
; block0:
|
||||||
|
; br %r14
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
;; Test compilation of leaf functions while preserving frame pointers.
|
||||||
|
|
||||||
|
test compile precise-output
|
||||||
|
set unwind_info=false
|
||||||
|
set preserve_frame_pointers=true
|
||||||
|
target s390x
|
||||||
|
|
||||||
|
function %leaf(i64) -> i64 {
|
||||||
|
block0(v0: i64):
|
||||||
|
return v0
|
||||||
|
}
|
||||||
|
|
||||||
|
; stmg %r14, %r15, 112(%r15)
|
||||||
|
; lgr %r1, %r15
|
||||||
|
; aghi %r15, -160
|
||||||
|
; virtual_sp_offset_adjust 160
|
||||||
|
; stg %r1, 0(%r15)
|
||||||
|
; block0:
|
||||||
|
; lmg %r14, %r15, 272(%r15)
|
||||||
|
; br %r14
|
||||||
|
|
||||||
Reference in New Issue
Block a user