Add support for leaf fn frame pointer elimination

This PR adds ability for the target specifications to specify frame
pointer emission type that's not just “always” or “whatever cg decides”.

In particular there's a new mode that allows omission of the frame
pointer for leaf functions (those that don't call any other functions).

We then set this new mode for Aarch64-based Apple targets.

Fixes #86196
This commit is contained in:
Simonas Kazlauskas 2021-06-26 23:53:35 +03:00
parent 3ddb78a346
commit 9b67cba4f6
24 changed files with 149 additions and 60 deletions

View File

@ -12,7 +12,7 @@ use rustc_middle::ty::{self, TyCtxt};
use rustc_session::config::OptLevel;
use rustc_session::Session;
use rustc_target::spec::abi::Abi;
use rustc_target::spec::{SanitizerSet, StackProbeType};
use rustc_target::spec::{FramePointer, SanitizerSet, StackProbeType};
use crate::attributes;
use crate::llvm::AttributePlace::Function;
@ -69,15 +69,25 @@ fn naked(val: &'ll Value, is_naked: bool) {
Attribute::Naked.toggle_llfn(Function, val, is_naked);
}
pub fn set_frame_pointer_elimination(cx: &CodegenCx<'ll, '_>, llfn: &'ll Value) {
if cx.sess().must_not_eliminate_frame_pointers() {
llvm::AddFunctionAttrStringValue(
llfn,
llvm::AttributePlace::Function,
cstr!("frame-pointer"),
cstr!("all"),
);
pub fn set_frame_pointer_type(cx: &CodegenCx<'ll, '_>, llfn: &'ll Value) {
let mut fp = cx.sess().target.frame_pointer;
// "mcount" function relies on stack pointer.
// See <https://sourceware.org/binutils/docs/gprof/Implementation.html>.
if cx.sess().instrument_mcount() || matches!(cx.sess().opts.cg.force_frame_pointers, Some(true))
{
fp = FramePointer::Always;
}
let attr_value = match fp {
FramePointer::Always => cstr!("all"),
FramePointer::NonLeaf => cstr!("non-leaf"),
FramePointer::MayOmit => return,
};
llvm::AddFunctionAttrStringValue(
llfn,
llvm::AttributePlace::Function,
cstr!("frame-pointer"),
attr_value,
);
}
/// Tell LLVM what instrument function to insert.
@ -254,7 +264,7 @@ pub fn from_fn_attrs(cx: &CodegenCx<'ll, 'tcx>, llfn: &'ll Value, instance: ty::
}
// FIXME: none of these three functions interact with source level attributes.
set_frame_pointer_elimination(cx, llfn);
set_frame_pointer_type(cx, llfn);
set_instrument_function(cx, llfn);
set_probestack(cx, llfn);

View File

@ -410,8 +410,8 @@ impl MiscMethods<'tcx> for CodegenCx<'ll, 'tcx> {
&self.used_statics
}
fn set_frame_pointer_elimination(&self, llfn: &'ll Value) {
attributes::set_frame_pointer_elimination(self, llfn)
fn set_frame_pointer_type(&self, llfn: &'ll Value) {
attributes::set_frame_pointer_type(self, llfn)
}
fn apply_target_cpu_attr(&self, llfn: &'ll Value) {

View File

@ -674,7 +674,7 @@ fn gen_fn<'ll, 'tcx>(
) -> &'ll Value {
let fn_abi = FnAbi::of_fn_ptr(cx, rust_fn_sig, &[]);
let llfn = cx.declare_fn(name, &fn_abi);
cx.set_frame_pointer_elimination(llfn);
cx.set_frame_pointer_type(llfn);
cx.apply_target_cpu_attr(llfn);
// FIXME(eddyb) find a nicer way to do this.
unsafe { llvm::LLVMRustSetLinkage(llfn, llvm::Linkage::InternalLinkage) };

View File

@ -406,7 +406,7 @@ pub fn maybe_create_entry_wrapper<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
};
// `main` should respect same config for frame pointer elimination as rest of code
cx.set_frame_pointer_elimination(llfn);
cx.set_frame_pointer_type(llfn);
cx.apply_target_cpu_attr(llfn);
let llbb = Bx::append_block(&cx, llfn, "top");

View File

@ -16,7 +16,7 @@ pub trait MiscMethods<'tcx>: BackendTypes {
fn sess(&self) -> &Session;
fn codegen_unit(&self) -> &'tcx CodegenUnit<'tcx>;
fn used_statics(&self) -> &RefCell<Vec<Self::Value>>;
fn set_frame_pointer_elimination(&self, llfn: Self::Function);
fn set_frame_pointer_type(&self, llfn: Self::Function);
fn apply_target_cpu_attr(&self, llfn: Self::Function);
fn create_used_variable(&self);
/// Declares the extern "C" main function for the entry point. Returns None if the symbol already exists.

View File

@ -792,18 +792,6 @@ impl Session {
!self.target.is_like_windows && !self.target.is_like_osx
}
pub fn must_not_eliminate_frame_pointers(&self) -> bool {
// "mcount" function relies on stack pointer.
// See <https://sourceware.org/binutils/docs/gprof/Implementation.html>.
if self.instrument_mcount() {
true
} else if let Some(x) = self.opts.cg.force_frame_pointers {
x
} else {
!self.target.eliminate_frame_pointer
}
}
pub fn must_emit_unwind_tables(&self) -> bool {
// This is used to control the emission of the `uwtable` attribute on
// LLVM functions.

View File

@ -1,4 +1,4 @@
use crate::spec::{LinkerFlavor, SanitizerSet, Target, TargetOptions};
use crate::spec::{FramePointer, LinkerFlavor, SanitizerSet, Target, TargetOptions};
pub fn target() -> Target {
let mut base = super::apple_base::opts("macos");
@ -20,6 +20,10 @@ pub fn target() -> Target {
pointer_width: 64,
data_layout: "e-m:o-i64:64-i128:128-n32:64-S128".to_string(),
arch: arch.to_string(),
options: TargetOptions { mcount: "\u{1}mcount".to_string(), ..base },
options: TargetOptions {
mcount: "\u{1}mcount".to_string(),
frame_pointer: FramePointer::NonLeaf,
..base
},
}
}

View File

@ -1,5 +1,5 @@
use super::apple_sdk_base::{opts, Arch};
use crate::spec::{Target, TargetOptions};
use crate::spec::{FramePointer, Target, TargetOptions};
pub fn target() -> Target {
let base = opts("ios", Arch::Arm64);
@ -13,6 +13,7 @@ pub fn target() -> Target {
max_atomic_width: Some(128),
unsupported_abis: super::arm_base::unsupported_abis(),
forces_embed_bitcode: true,
frame_pointer: FramePointer::NonLeaf,
// Taken from a clang build on Xcode 11.4.1.
// These arguments are not actually invoked - they just have
// to look right to pass App Store validation.

View File

@ -1,5 +1,5 @@
use super::apple_sdk_base::{opts, Arch};
use crate::spec::{Target, TargetOptions};
use crate::spec::{FramePointer, Target, TargetOptions};
pub fn target() -> Target {
let base = opts("ios", Arch::Arm64_macabi);
@ -13,6 +13,7 @@ pub fn target() -> Target {
max_atomic_width: Some(128),
unsupported_abis: super::arm_base::unsupported_abis(),
forces_embed_bitcode: true,
frame_pointer: FramePointer::NonLeaf,
// Taken from a clang build on Xcode 11.4.1.
// These arguments are not actually invoked - they just have
// to look right to pass App Store validation.

View File

@ -1,5 +1,5 @@
use super::apple_sdk_base::{opts, Arch};
use crate::spec::{Target, TargetOptions};
use crate::spec::{FramePointer, Target, TargetOptions};
pub fn target() -> Target {
let base = opts("ios", Arch::Arm64_sim);
@ -21,6 +21,7 @@ pub fn target() -> Target {
max_atomic_width: Some(128),
unsupported_abis: super::arm_base::unsupported_abis(),
forces_embed_bitcode: true,
frame_pointer: FramePointer::NonLeaf,
// Taken from a clang build on Xcode 11.4.1.
// These arguments are not actually invoked - they just have
// to look right to pass App Store validation.

View File

@ -1,5 +1,5 @@
use super::apple_sdk_base::{opts, Arch};
use crate::spec::{Target, TargetOptions};
use crate::spec::{FramePointer, Target, TargetOptions};
pub fn target() -> Target {
let base = opts("tvos", Arch::Arm64);
@ -13,6 +13,7 @@ pub fn target() -> Target {
max_atomic_width: Some(128),
unsupported_abis: super::arm_base::unsupported_abis(),
forces_embed_bitcode: true,
frame_pointer: FramePointer::NonLeaf,
..base
},
}

View File

@ -1,6 +1,6 @@
use std::env;
use crate::spec::{SplitDebuginfo, TargetOptions};
use crate::spec::{FramePointer, SplitDebuginfo, TargetOptions};
pub fn opts(os: &str) -> TargetOptions {
// ELF TLS is only available in macOS 10.7+. If you try to compile for 10.6
@ -27,7 +27,7 @@ pub fn opts(os: &str) -> TargetOptions {
families: vec!["unix".to_string()],
is_like_osx: true,
dwarf_version: Some(2),
eliminate_frame_pointer: false,
frame_pointer: FramePointer::Always,
has_rpath: true,
dll_suffix: ".dylib".to_string(),
archive_format: "darwin".to_string(),

View File

@ -1,4 +1,4 @@
use crate::spec::{RelroLevel, TargetOptions};
use crate::spec::{FramePointer, RelroLevel, TargetOptions};
pub fn opts() -> TargetOptions {
TargetOptions {
@ -8,7 +8,7 @@ pub fn opts() -> TargetOptions {
families: vec!["unix".to_string()],
has_rpath: true,
position_independent_executables: true,
eliminate_frame_pointer: false, // FIXME 43575
frame_pointer: FramePointer::Always, // FIXME 43575: should be MayOmit...
relro_level: RelroLevel::Full,
abi_return_struct_as_int: true,
dwarf_version: Some(2),

View File

@ -1,4 +1,4 @@
use crate::spec::{LinkerFlavor, StackProbeType, Target, TargetOptions};
use crate::spec::{FramePointer, LinkerFlavor, StackProbeType, Target, TargetOptions};
pub fn target() -> Target {
let mut base = super::apple_base::opts("macos");
@ -8,7 +8,7 @@ pub fn target() -> Target {
base.link_env_remove.extend(super::apple_base::macos_link_env_remove());
// don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
base.stack_probes = StackProbeType::Call;
base.eliminate_frame_pointer = false;
base.frame_pointer = FramePointer::Always;
// Clang automatically chooses a more specific target based on
// MACOSX_DEPLOYMENT_TARGET. To enable cross-language LTO to work

View File

@ -1,4 +1,4 @@
use crate::spec::{LinkerFlavor, LldFlavor, Target};
use crate::spec::{FramePointer, LinkerFlavor, LldFlavor, Target};
pub fn target() -> Target {
let mut base = super::windows_gnu_base::opts();
@ -6,7 +6,7 @@ pub fn target() -> Target {
base.pre_link_args
.insert(LinkerFlavor::Lld(LldFlavor::Ld), vec!["-m".to_string(), "i386pe".to_string()]);
base.max_atomic_width = Some(64);
base.eliminate_frame_pointer = false; // Required for backtraces
base.frame_pointer = FramePointer::Always; // Required for backtraces
base.linker = Some("i686-w64-mingw32-gcc".to_string());
// Mark all dynamic libraries and executables as compatible with the larger 4GiB address

View File

@ -1,4 +1,4 @@
use crate::spec::{LinkerFlavor, StackProbeType, Target};
use crate::spec::{FramePointer, LinkerFlavor, StackProbeType, Target};
pub fn target() -> Target {
let mut base = super::linux_musl_base::opts();
@ -21,7 +21,7 @@ pub fn target() -> Target {
//
// This may or may not be related to this bug:
// https://llvm.org/bugs/show_bug.cgi?id=30879
base.eliminate_frame_pointer = false;
base.frame_pointer = FramePointer::Always;
Target {
llvm_target: "i686-unknown-linux-musl".to_string(),

View File

@ -1,4 +1,4 @@
use crate::spec::{LinkerFlavor, LldFlavor, Target};
use crate::spec::{FramePointer, LinkerFlavor, LldFlavor, Target};
pub fn target() -> Target {
let mut base = super::windows_uwp_gnu_base::opts();
@ -6,7 +6,7 @@ pub fn target() -> Target {
base.pre_link_args
.insert(LinkerFlavor::Lld(LldFlavor::Ld), vec!["-m".to_string(), "i386pe".to_string()]);
base.max_atomic_width = Some(64);
base.eliminate_frame_pointer = false; // Required for backtraces
base.frame_pointer = FramePointer::Always; // Required for backtraces
// Mark all dynamic libraries and executables as compatible with the larger 4GiB address
// space available to x86 Windows binaries on x86_64.

View File

@ -1,4 +1,4 @@
use crate::spec::{LinkArgs, LinkerFlavor, TargetOptions};
use crate::spec::{FramePointer, LinkArgs, LinkerFlavor, TargetOptions};
use std::default::Default;
pub fn opts() -> TargetOptions {
@ -35,7 +35,7 @@ pub fn opts() -> TargetOptions {
is_like_solaris: true,
linker_is_gnu: false,
limit_rdylib_exports: false, // Linker doesn't support this
eliminate_frame_pointer: false,
frame_pointer: FramePointer::Always,
eh_frame_header: false,
late_link_args,

View File

@ -1,4 +1,5 @@
use crate::spec::{PanicStrategy, RelocModel, RelroLevel, StackProbeType, TargetOptions};
use crate::spec::TargetOptions;
use crate::spec::{FramePointer, PanicStrategy, RelocModel, RelroLevel, StackProbeType};
pub fn opts() -> TargetOptions {
TargetOptions {
@ -7,7 +8,7 @@ pub fn opts() -> TargetOptions {
panic_strategy: PanicStrategy::Abort,
// don't use probe-stack=inline-asm until rust#83139 and rust#84667 are resolved
stack_probes: StackProbeType::Call,
eliminate_frame_pointer: false,
frame_pointer: FramePointer::Always,
position_independent_executables: true,
needs_plt: true,
relro_level: RelroLevel::Full,

View File

@ -671,6 +671,42 @@ impl ToJson for SanitizerSet {
}
}
#[derive(Clone, Copy, PartialEq, Hash, Debug)]
pub enum FramePointer {
/// Forces the machine code generator to always preserve the frame pointers.
Always,
/// Forces the machine code generator to preserve the frame pointers except for the leaf
/// functions (i.e. those that don't call other functions).
NonLeaf,
/// Allows the machine code generator to omit the frame pointers.
///
/// This option does not guarantee that the frame pointers will be omitted.
MayOmit,
}
impl FromStr for FramePointer {
type Err = ();
fn from_str(s: &str) -> Result<Self, ()> {
Ok(match s {
"always" => Self::Always,
"non-leaf" => Self::NonLeaf,
"may-omit" => Self::MayOmit,
_ => return Err(()),
})
}
}
impl ToJson for FramePointer {
fn to_json(&self) -> Json {
match *self {
Self::Always => "always",
Self::NonLeaf => "non-leaf",
Self::MayOmit => "may-omit",
}
.to_json()
}
}
macro_rules! supported_targets {
( $(($( $triple:literal, )+ $module:ident ),)+ ) => {
$(mod $module;)+
@ -1068,8 +1104,8 @@ pub struct TargetOptions {
pub tls_model: TlsModel,
/// Do not emit code that uses the "red zone", if the ABI has one. Defaults to false.
pub disable_redzone: bool,
/// Eliminate frame pointers from stack frames if possible. Defaults to true.
pub eliminate_frame_pointer: bool,
/// Frame pointer mode for this target. Defaults to `MayOmit`.
pub frame_pointer: FramePointer,
/// Emit each function in its own section. Defaults to true.
pub function_sections: bool,
/// String to prepend to the name of every dynamic library. Defaults to "lib".
@ -1330,7 +1366,7 @@ impl Default for TargetOptions {
code_model: None,
tls_model: TlsModel::GeneralDynamic,
disable_redzone: false,
eliminate_frame_pointer: true,
frame_pointer: FramePointer::MayOmit,
function_sections: true,
dll_prefix: "lib".to_string(),
dll_suffix: ".so".to_string(),
@ -1833,6 +1869,16 @@ impl Target {
}
}
if let Some(fp) = obj.remove_key("frame-pointer") {
if let Some(s) = Json::as_string(&fp) {
base.frame_pointer = s
.parse()
.map_err(|()| format!("'{}' is not a valid value for frame-pointer", s))?;
} else {
incorrect_type.push("frame-pointer".to_string())
}
}
key!(is_builtin, bool);
key!(c_int_width = "target-c-int-width");
key!(os);
@ -1864,7 +1910,6 @@ impl Target {
key!(code_model, CodeModel)?;
key!(tls_model, TlsModel)?;
key!(disable_redzone, bool);
key!(eliminate_frame_pointer, bool);
key!(function_sections, bool);
key!(dll_prefix);
key!(dll_suffix);
@ -2128,7 +2173,7 @@ impl ToJson for Target {
target_option_val!(code_model);
target_option_val!(tls_model);
target_option_val!(disable_redzone);
target_option_val!(eliminate_frame_pointer);
target_option_val!(frame_pointer);
target_option_val!(function_sections);
target_option_val!(dll_prefix);
target_option_val!(dll_suffix);

View File

@ -1,4 +1,4 @@
use crate::spec::{RelroLevel, TargetOptions};
use crate::spec::{FramePointer, RelroLevel, TargetOptions};
pub fn opts() -> TargetOptions {
TargetOptions {
@ -9,7 +9,7 @@ pub fn opts() -> TargetOptions {
has_rpath: true,
abi_return_struct_as_int: true,
position_independent_executables: true,
eliminate_frame_pointer: false, // FIXME 43575
frame_pointer: FramePointer::Always, // FIXME 43575: should be MayOmit...
relro_level: RelroLevel::Full,
dwarf_version: Some(2),
..Default::default()

View File

@ -27,7 +27,8 @@
// differentiate these targets from our other `arm(v7)-*-*-gnueabi(hf)` targets in the context of
// build scripts / gcc flags.
use crate::spec::{LinkerFlavor, LldFlavor, PanicStrategy, RelocModel, TargetOptions};
use crate::spec::TargetOptions;
use crate::spec::{FramePointer, LinkerFlavor, LldFlavor, PanicStrategy, RelocModel};
pub fn opts() -> TargetOptions {
// See rust-lang/rfcs#1645 for a discussion about these defaults
@ -52,7 +53,7 @@ pub fn opts() -> TargetOptions {
emit_debug_gdb_scripts: false,
// LLVM is eager to trash the link register when calling `noreturn` functions, which
// breaks debugging. Preserve LR by default to prevent that from happening.
eliminate_frame_pointer: false,
frame_pointer: FramePointer::Always,
..Default::default()
}
}

View File

@ -1,10 +1,11 @@
use crate::spec::{LinkerFlavor, SanitizerSet, StackProbeType, Target, TargetOptions};
use crate::spec::TargetOptions;
use crate::spec::{FramePointer, LinkerFlavor, SanitizerSet, StackProbeType, Target};
pub fn target() -> Target {
let mut base = super::apple_base::opts("macos");
base.cpu = "core2".to_string();
base.max_atomic_width = Some(128); // core2 support cmpxchg16b
base.eliminate_frame_pointer = false;
base.frame_pointer = FramePointer::Always;
base.pre_link_args.insert(
LinkerFlavor::Gcc,
vec!["-m64".to_string(), "-arch".to_string(), "x86_64".to_string()],

View File

@ -0,0 +1,35 @@
// compile-flags: --crate-type=rlib
// revisions: aarch64-apple aarch64-linux force x64-apple x64-linux
// [aarch64-apple] needs-llvm-components: aarch64
// [aarch64-apple] compile-flags: --target=aarch64-apple-darwin
// [aarch64-linux] needs-llvm-components: aarch64
// [aarch64-linux] compile-flags: --target=aarch64-unknown-linux-gnu
// [force] needs-llvm-components: x86
// [force] compile-flags: --target=x86_64-unknown-linux-gnu -Cforce-frame-pointers=yes
// [x64-apple] needs-llvm-components: x86
// [x64-apple] compile-flags: --target=x86_64-apple-darwin
// [x64-linux] needs-llvm-components: x86
// [x64-linux] compile-flags: --target=x86_64-unknown-linux-gnu
#![feature(no_core, lang_items)]
#![no_core]
#[lang="sized"]
trait Sized { }
#[lang="copy"]
trait Copy { }
// CHECK: define i32 @peach{{.*}}[[PEACH_ATTRS:\#[0-9]+]] {
#[no_mangle]
pub fn peach(x: u32) -> u32 {
x
}
// CHECK: attributes [[PEACH_ATTRS]] = {
// x64-linux-NOT: {{.*}}"frame-pointer"{{.*}}
// aarch64-linux-NOT: {{.*}}"frame-pointer"{{.*}}
// x64-apple-SAME: {{.*}}"frame-pointer"="all"
// force-SAME: {{.*}}"frame-pointer"="all"
// aarch64-apple-SAME: {{.*}}"frame-pointer"="non-leaf"
// CHECK-SAME: }