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:
Ulrich Weigand
2022-07-21 19:09:16 +02:00
committed by GitHub
parent 4ce329d1eb
commit fd639dd044
3 changed files with 87 additions and 15 deletions

View File

@@ -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;
} }

View 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

View File

@@ -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