about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--compiler/rustc_codegen_llvm/src/attributes.rs30
-rw-r--r--compiler/rustc_codegen_llvm/src/context.rs4
-rw-r--r--compiler/rustc_codegen_llvm/src/intrinsic.rs2
-rw-r--r--compiler/rustc_codegen_ssa/src/base.rs2
-rw-r--r--compiler/rustc_codegen_ssa/src/traits/misc.rs2
-rw-r--r--compiler/rustc_session/src/session.rs12
-rw-r--r--compiler/rustc_target/src/spec/aarch64_apple_darwin.rs8
-rw-r--r--compiler/rustc_target/src/spec/aarch64_apple_ios.rs3
-rw-r--r--compiler/rustc_target/src/spec/aarch64_apple_ios_macabi.rs3
-rw-r--r--compiler/rustc_target/src/spec/aarch64_apple_ios_sim.rs3
-rw-r--r--compiler/rustc_target/src/spec/aarch64_apple_tvos.rs3
-rw-r--r--compiler/rustc_target/src/spec/apple_base.rs4
-rw-r--r--compiler/rustc_target/src/spec/freebsd_base.rs4
-rw-r--r--compiler/rustc_target/src/spec/i686_apple_darwin.rs4
-rw-r--r--compiler/rustc_target/src/spec/i686_pc_windows_gnu.rs4
-rw-r--r--compiler/rustc_target/src/spec/i686_unknown_linux_musl.rs4
-rw-r--r--compiler/rustc_target/src/spec/i686_uwp_windows_gnu.rs4
-rw-r--r--compiler/rustc_target/src/spec/illumos_base.rs4
-rw-r--r--compiler/rustc_target/src/spec/linux_kernel_base.rs5
-rw-r--r--compiler/rustc_target/src/spec/mod.rs55
-rw-r--r--compiler/rustc_target/src/spec/openbsd_base.rs4
-rw-r--r--compiler/rustc_target/src/spec/thumb_base.rs5
-rw-r--r--compiler/rustc_target/src/spec/x86_64_apple_darwin.rs5
-rw-r--r--src/test/codegen/frame-pointer.rs35
24 files changed, 149 insertions, 60 deletions
diff --git a/compiler/rustc_codegen_llvm/src/attributes.rs b/compiler/rustc_codegen_llvm/src/attributes.rs
index 6a032b9be72..56b93f83466 100644
--- a/compiler/rustc_codegen_llvm/src/attributes.rs
+++ b/compiler/rustc_codegen_llvm/src/attributes.rs
@@ -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);
 
diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs
index 7415a570453..f662887abf8 100644
--- a/compiler/rustc_codegen_llvm/src/context.rs
+++ b/compiler/rustc_codegen_llvm/src/context.rs
@@ -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) {
diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs
index 22d513d66d1..1fb201eda6b 100644
--- a/compiler/rustc_codegen_llvm/src/intrinsic.rs
+++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs
@@ -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) };
diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs
index 31ae84f4ca1..2e9aae467f8 100644
--- a/compiler/rustc_codegen_ssa/src/base.rs
+++ b/compiler/rustc_codegen_ssa/src/base.rs
@@ -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");
diff --git a/compiler/rustc_codegen_ssa/src/traits/misc.rs b/compiler/rustc_codegen_ssa/src/traits/misc.rs
index 6fff64bfcb6..46f2adbe552 100644
--- a/compiler/rustc_codegen_ssa/src/traits/misc.rs
+++ b/compiler/rustc_codegen_ssa/src/traits/misc.rs
@@ -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.
diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs
index d8a58ee18cd..f792e319867 100644
--- a/compiler/rustc_session/src/session.rs
+++ b/compiler/rustc_session/src/session.rs
@@ -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.
diff --git a/compiler/rustc_target/src/spec/aarch64_apple_darwin.rs b/compiler/rustc_target/src/spec/aarch64_apple_darwin.rs
index feadd4e891c..bf3ec8f9160 100644
--- a/compiler/rustc_target/src/spec/aarch64_apple_darwin.rs
+++ b/compiler/rustc_target/src/spec/aarch64_apple_darwin.rs
@@ -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
+        },
     }
 }
diff --git a/compiler/rustc_target/src/spec/aarch64_apple_ios.rs b/compiler/rustc_target/src/spec/aarch64_apple_ios.rs
index 5682039b865..9fa8ef69d81 100644
--- a/compiler/rustc_target/src/spec/aarch64_apple_ios.rs
+++ b/compiler/rustc_target/src/spec/aarch64_apple_ios.rs
@@ -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.
diff --git a/compiler/rustc_target/src/spec/aarch64_apple_ios_macabi.rs b/compiler/rustc_target/src/spec/aarch64_apple_ios_macabi.rs
index 8a832546d09..a43eb99a1d7 100644
--- a/compiler/rustc_target/src/spec/aarch64_apple_ios_macabi.rs
+++ b/compiler/rustc_target/src/spec/aarch64_apple_ios_macabi.rs
@@ -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.
diff --git a/compiler/rustc_target/src/spec/aarch64_apple_ios_sim.rs b/compiler/rustc_target/src/spec/aarch64_apple_ios_sim.rs
index 2187015b627..586e4043d79 100644
--- a/compiler/rustc_target/src/spec/aarch64_apple_ios_sim.rs
+++ b/compiler/rustc_target/src/spec/aarch64_apple_ios_sim.rs
@@ -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.
diff --git a/compiler/rustc_target/src/spec/aarch64_apple_tvos.rs b/compiler/rustc_target/src/spec/aarch64_apple_tvos.rs
index cb6c06b3711..934f3370369 100644
--- a/compiler/rustc_target/src/spec/aarch64_apple_tvos.rs
+++ b/compiler/rustc_target/src/spec/aarch64_apple_tvos.rs
@@ -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
         },
     }
diff --git a/compiler/rustc_target/src/spec/apple_base.rs b/compiler/rustc_target/src/spec/apple_base.rs
index 8530db179d9..0c8a89210ff 100644
--- a/compiler/rustc_target/src/spec/apple_base.rs
+++ b/compiler/rustc_target/src/spec/apple_base.rs
@@ -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(),
diff --git a/compiler/rustc_target/src/spec/freebsd_base.rs b/compiler/rustc_target/src/spec/freebsd_base.rs
index bef2fce7c83..998d6ffe0fc 100644
--- a/compiler/rustc_target/src/spec/freebsd_base.rs
+++ b/compiler/rustc_target/src/spec/freebsd_base.rs
@@ -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),
diff --git a/compiler/rustc_target/src/spec/i686_apple_darwin.rs b/compiler/rustc_target/src/spec/i686_apple_darwin.rs
index 06d71db4af2..f2635f0656d 100644
--- a/compiler/rustc_target/src/spec/i686_apple_darwin.rs
+++ b/compiler/rustc_target/src/spec/i686_apple_darwin.rs
@@ -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
diff --git a/compiler/rustc_target/src/spec/i686_pc_windows_gnu.rs b/compiler/rustc_target/src/spec/i686_pc_windows_gnu.rs
index 7002d88c512..92c3a1554ac 100644
--- a/compiler/rustc_target/src/spec/i686_pc_windows_gnu.rs
+++ b/compiler/rustc_target/src/spec/i686_pc_windows_gnu.rs
@@ -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
diff --git a/compiler/rustc_target/src/spec/i686_unknown_linux_musl.rs b/compiler/rustc_target/src/spec/i686_unknown_linux_musl.rs
index 228976779f0..d95cb6a82d5 100644
--- a/compiler/rustc_target/src/spec/i686_unknown_linux_musl.rs
+++ b/compiler/rustc_target/src/spec/i686_unknown_linux_musl.rs
@@ -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(),
diff --git a/compiler/rustc_target/src/spec/i686_uwp_windows_gnu.rs b/compiler/rustc_target/src/spec/i686_uwp_windows_gnu.rs
index 426df59882d..27a0ac585e3 100644
--- a/compiler/rustc_target/src/spec/i686_uwp_windows_gnu.rs
+++ b/compiler/rustc_target/src/spec/i686_uwp_windows_gnu.rs
@@ -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.
diff --git a/compiler/rustc_target/src/spec/illumos_base.rs b/compiler/rustc_target/src/spec/illumos_base.rs
index 9d9da50be7a..f598f0f38f3 100644
--- a/compiler/rustc_target/src/spec/illumos_base.rs
+++ b/compiler/rustc_target/src/spec/illumos_base.rs
@@ -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,
 
diff --git a/compiler/rustc_target/src/spec/linux_kernel_base.rs b/compiler/rustc_target/src/spec/linux_kernel_base.rs
index 145aa4a5894..a332e3b847a 100644
--- a/compiler/rustc_target/src/spec/linux_kernel_base.rs
+++ b/compiler/rustc_target/src/spec/linux_kernel_base.rs
@@ -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,
diff --git a/compiler/rustc_target/src/spec/mod.rs b/compiler/rustc_target/src/spec/mod.rs
index 0bf89c3f93b..cfdae9623f3 100644
--- a/compiler/rustc_target/src/spec/mod.rs
+++ b/compiler/rustc_target/src/spec/mod.rs
@@ -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);
diff --git a/compiler/rustc_target/src/spec/openbsd_base.rs b/compiler/rustc_target/src/spec/openbsd_base.rs
index 29b415e7726..70e9e4aed92 100644
--- a/compiler/rustc_target/src/spec/openbsd_base.rs
+++ b/compiler/rustc_target/src/spec/openbsd_base.rs
@@ -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()
diff --git a/compiler/rustc_target/src/spec/thumb_base.rs b/compiler/rustc_target/src/spec/thumb_base.rs
index ec24807fec4..fbe4dd5b501 100644
--- a/compiler/rustc_target/src/spec/thumb_base.rs
+++ b/compiler/rustc_target/src/spec/thumb_base.rs
@@ -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()
     }
 }
diff --git a/compiler/rustc_target/src/spec/x86_64_apple_darwin.rs b/compiler/rustc_target/src/spec/x86_64_apple_darwin.rs
index dc7597fe7b2..60fd42970c7 100644
--- a/compiler/rustc_target/src/spec/x86_64_apple_darwin.rs
+++ b/compiler/rustc_target/src/spec/x86_64_apple_darwin.rs
@@ -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()],
diff --git a/src/test/codegen/frame-pointer.rs b/src/test/codegen/frame-pointer.rs
new file mode 100644
index 00000000000..367591dcb96
--- /dev/null
+++ b/src/test/codegen/frame-pointer.rs
@@ -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: }