From fd639dd0445032ce88215e7abfe13c114fce7955 Mon Sep 17 00:00:00 2001 From: Ulrich Weigand Date: Thu, 21 Jul 2022 19:09:16 +0200 Subject: [PATCH] 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 --- cranelift/codegen/src/isa/s390x/abi.rs | 66 ++++++++++++++----- .../filetests/filetests/isa/s390x/leaf.clif | 15 +++++ .../leaf_with_preserve_frame_pointers.clif | 21 ++++++ 3 files changed, 87 insertions(+), 15 deletions(-) create mode 100644 cranelift/filetests/filetests/isa/s390x/leaf.clif create mode 100644 cranelift/filetests/filetests/isa/s390x/leaf_with_preserve_frame_pointers.clif diff --git a/cranelift/codegen/src/isa/s390x/abi.rs b/cranelift/codegen/src/isa/s390x/abi.rs index 2f5bda046c..1a4afbdb41 100644 --- a/cranelift/codegen/src/isa/s390x/abi.rs +++ b/cranelift/codegen/src/isa/s390x/abi.rs @@ -22,6 +22,13 @@ //! value of the stack pointer register never changes after the //! 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: //! //! ```plain @@ -33,7 +40,8 @@ //! +---------------------------+ //! | ... | //! | 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 | @@ -51,7 +59,8 @@ //! | ... | //! | args for call | //! | outgoing reg save area | -//! SP during function ------> | (alloc'd by prologue) | +//! | (alloc'd by prologue) | +//! SP during function ------> | (incl. callee's backchain)| //! +---------------------------+ //! //! (low address) @@ -178,6 +187,9 @@ fn get_vecreg_for_ret(idx: usize) -> Option { /// with 32-bit arithmetic: for now, 128 MB. 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 for StackAMode { fn into(self) -> MemArg { match self { @@ -220,7 +232,7 @@ impl ABIMachineSpec for S390xMachineDeps { let mut ret = vec![]; if args_or_rets == ArgsOrRets::Args { - next_stack = 160; + next_stack = REG_SAVE_AREA_SIZE as u64; } for i in 0..params.len() { @@ -506,19 +518,18 @@ impl ABIMachineSpec for S390xMachineDeps { flags: &settings::Flags, clobbered_callee_saves: &[Writable], fixed_frame_storage_size: u32, - outgoing_args_size: u32, + mut outgoing_args_size: u32, ) -> (u64, SmallVec<[Inst; 16]>) { let mut insts = SmallVec::new(); // Collect clobbered registers. - let is_leaf = outgoing_args_size == 0; 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; if flags.unwind_info() { insts.push(Inst::Unwind { 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, }, }); @@ -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. let stack_size = 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)); } + // 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. for (i, reg) in clobbered_fpr.iter().enumerate() { insts.push(Inst::VecStoreLane { @@ -592,16 +616,15 @@ impl ABIMachineSpec for S390xMachineDeps { flags: &settings::Flags, clobbers: &[Writable], fixed_frame_storage_size: u32, - outgoing_args_size: u32, + mut outgoing_args_size: u32, ) -> SmallVec<[Inst; 16]> { let mut insts = SmallVec::new(); let clobbered_callee_saves = Self::get_clobbered_callee_saves(call_conv, flags, sig, clobbers); // Collect clobbered registers. - let is_leaf = outgoing_args_size == 0; 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; // Restore FPRs. @@ -742,8 +765,9 @@ fn is_reg_saved_in_prologue(_call_conv: isa::CallConv, r: RealReg) -> bool { } fn get_clobbered_gpr_fpr( + flags: &settings::Flags, clobbered_callee_saves: &[Writable], - is_leaf: bool, + outgoing_args_size: &mut u32, ) -> (u8, SmallVec<[Writable; 8]>) { // Collect clobbered registers. Note we save/restore GPR always as // 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 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. - // FIXME: This should be included in the clobber list to begin with, - // but isn't because we have have excluded call instructions via the - // is_included_in_clobbers callback. - if !is_leaf { + // This is not included in the clobber list because we have excluded + // call instructions via the is_included_in_clobbers callback. + // We also want to enforce saving the link register in leaf functions + // for stack unwinding, if we're asked to preserve frame pointers. + if *outgoing_args_size > 0 { first_clobbered_gpr = 14; } diff --git a/cranelift/filetests/filetests/isa/s390x/leaf.clif b/cranelift/filetests/filetests/isa/s390x/leaf.clif new file mode 100644 index 0000000000..b1e5786971 --- /dev/null +++ b/cranelift/filetests/filetests/isa/s390x/leaf.clif @@ -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 + diff --git a/cranelift/filetests/filetests/isa/s390x/leaf_with_preserve_frame_pointers.clif b/cranelift/filetests/filetests/isa/s390x/leaf_with_preserve_frame_pointers.clif new file mode 100644 index 0000000000..82834df6de --- /dev/null +++ b/cranelift/filetests/filetests/isa/s390x/leaf_with_preserve_frame_pointers.clif @@ -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 +