Skip to content

Commit

Permalink
Auto merge of #80838 - nagisa:nagisa/stack-probe-type, r=cuviper
Browse files Browse the repository at this point in the history
Target stack-probe support configurable finely

This adds capability to configure the target's stack probe support in a
more precise manner than just on/off. In particular now we allow
choosing between always inline-asm, always call or either one of those
depending on the LLVM version.

Note that this removes the ability to turn off the generation of the
stack-probe attribute. This is valid to replace it with inline-asm for all targets because
`probe-stack="inline-asm"` will not generate any machine code on targets
that do not currently support stack probes. This makes support for stack
probes on targets that don't have any right now automatic with LLVM
upgrades in the future.

(This is valid to do based on the fact that clang unconditionally sets
this attribute when `-fstack-clash-protection` is used, AFAICT)

cc #77885
r? `@cuviper`
  • Loading branch information
bors committed Jan 24, 2021
2 parents 85e355e + 1b15ec6 commit 72c7b70
Show file tree
Hide file tree
Showing 35 changed files with 205 additions and 87 deletions.
45 changes: 26 additions & 19 deletions compiler/rustc_codegen_llvm/src/attributes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use rustc_middle::ty::query::Providers;
use rustc_middle::ty::{self, TyCtxt};
use rustc_session::config::{OptLevel, SanitizerSet};
use rustc_session::Session;
use rustc_target::spec::StackProbeType;

use crate::attributes;
use crate::llvm::AttributePlace::Function;
Expand Down Expand Up @@ -98,12 +99,6 @@ fn set_instrument_function(cx: &CodegenCx<'ll, '_>, llfn: &'ll Value) {
}

fn set_probestack(cx: &CodegenCx<'ll, '_>, llfn: &'ll Value) {
// Only use stack probes if the target specification indicates that we
// should be using stack probes
if !cx.sess().target.stack_probes {
return;
}

// Currently stack probes seem somewhat incompatible with the address
// sanitizer and thread sanitizer. With asan we're already protected from
// stack overflow anyway so we don't really need stack probes regardless.
Expand All @@ -127,19 +122,31 @@ fn set_probestack(cx: &CodegenCx<'ll, '_>, llfn: &'ll Value) {
return;
}

llvm::AddFunctionAttrStringValue(
llfn,
llvm::AttributePlace::Function,
const_cstr!("probe-stack"),
if llvm_util::get_version() < (11, 0, 1) {
// Flag our internal `__rust_probestack` function as the stack probe symbol.
// This is defined in the `compiler-builtins` crate for each architecture.
const_cstr!("__rust_probestack")
} else {
// On LLVM 11+, emit inline asm for stack probes instead of a function call.
const_cstr!("inline-asm")
},
);
let attr_value = match cx.sess().target.stack_probes {
StackProbeType::None => None,
// Request LLVM to generate the probes inline. If the given LLVM version does not support
// this, no probe is generated at all (even if the attribute is specified).
StackProbeType::Inline => Some(const_cstr!("inline-asm")),
// Flag our internal `__rust_probestack` function as the stack probe symbol.
// This is defined in the `compiler-builtins` crate for each architecture.
StackProbeType::Call => Some(const_cstr!("__rust_probestack")),
// Pick from the two above based on the LLVM version.
StackProbeType::InlineOrCall { min_llvm_version_for_inline } => {
if llvm_util::get_version() < min_llvm_version_for_inline {
Some(const_cstr!("__rust_probestack"))
} else {
Some(const_cstr!("inline-asm"))
}
}
};
if let Some(attr_value) = attr_value {
llvm::AddFunctionAttrStringValue(
llfn,
llvm::AttributePlace::Function,
const_cstr!("probe-stack"),
attr_value,
);
}
}

pub fn llvm_target_features(sess: &Session) -> impl Iterator<Item = &str> {
Expand Down
8 changes: 6 additions & 2 deletions compiler/rustc_target/src/spec/i386_apple_ios.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::apple_sdk_base::{opts, Arch};
use crate::spec::{Target, TargetOptions};
use crate::spec::{StackProbeType, Target, TargetOptions};

pub fn target() -> Target {
let base = opts("ios", Arch::I386);
Expand All @@ -10,6 +10,10 @@ pub fn target() -> Target {
f64:32:64-f80:128-n8:16:32-S128"
.to_string(),
arch: "x86".to_string(),
options: TargetOptions { max_atomic_width: Some(64), stack_probes: true, ..base },
options: TargetOptions {
max_atomic_width: Some(64),
stack_probes: StackProbeType::InlineOrCall { min_llvm_version_for_inline: (11, 0, 1) },
..base
},
}
}
4 changes: 2 additions & 2 deletions compiler/rustc_target/src/spec/i686_apple_darwin.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use crate::spec::{LinkerFlavor, Target, TargetOptions};
use crate::spec::{LinkerFlavor, StackProbeType, Target, TargetOptions};

pub fn target() -> Target {
let mut base = super::apple_base::opts("macos");
base.cpu = "yonah".to_string();
base.max_atomic_width = Some(64);
base.pre_link_args.insert(LinkerFlavor::Gcc, vec!["-m32".to_string()]);
base.link_env_remove.extend(super::apple_base::macos_link_env_remove());
base.stack_probes = true;
base.stack_probes = StackProbeType::InlineOrCall { min_llvm_version_for_inline: (11, 0, 1) };
base.eliminate_frame_pointer = false;

// Clang automatically chooses a more specific target based on
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_target/src/spec/i686_linux_android.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::spec::Target;
use crate::spec::{StackProbeType, Target};

// See https://developer.android.com/ndk/guides/abis.html#x86
// for target ABI requirements.
Expand All @@ -11,7 +11,7 @@ pub fn target() -> Target {
// http://developer.android.com/ndk/guides/abis.html#x86
base.cpu = "pentiumpro".to_string();
base.features = "+mmx,+sse,+sse2,+sse3,+ssse3".to_string();
base.stack_probes = true;
base.stack_probes = StackProbeType::InlineOrCall { min_llvm_version_for_inline: (11, 0, 1) };

Target {
llvm_target: "i686-linux-android".to_string(),
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_target/src/spec/i686_unknown_freebsd.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::spec::{LinkerFlavor, Target};
use crate::spec::{LinkerFlavor, StackProbeType, Target};

pub fn target() -> Target {
let mut base = super::freebsd_base::opts();
Expand All @@ -7,7 +7,7 @@ pub fn target() -> Target {
let pre_link_args = base.pre_link_args.get_mut(&LinkerFlavor::Gcc).unwrap();
pre_link_args.push("-m32".to_string());
pre_link_args.push("-Wl,-znotext".to_string());
base.stack_probes = true;
base.stack_probes = StackProbeType::InlineOrCall { min_llvm_version_for_inline: (11, 0, 1) };

Target {
llvm_target: "i686-unknown-freebsd".to_string(),
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_target/src/spec/i686_unknown_haiku.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::spec::{LinkerFlavor, Target};
use crate::spec::{LinkerFlavor, StackProbeType, Target};

pub fn target() -> Target {
let mut base = super::haiku_base::opts();
base.cpu = "pentium4".to_string();
base.max_atomic_width = Some(64);
base.pre_link_args.insert(LinkerFlavor::Gcc, vec!["-m32".to_string()]);
base.stack_probes = true;
base.stack_probes = StackProbeType::InlineOrCall { min_llvm_version_for_inline: (11, 0, 1) };

Target {
llvm_target: "i686-unknown-haiku".to_string(),
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_target/src/spec/i686_unknown_linux_gnu.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::spec::{LinkerFlavor, Target};
use crate::spec::{LinkerFlavor, StackProbeType, Target};

pub fn target() -> Target {
let mut base = super::linux_gnu_base::opts();
base.cpu = "pentium4".to_string();
base.max_atomic_width = Some(64);
base.pre_link_args.get_mut(&LinkerFlavor::Gcc).unwrap().push("-m32".to_string());
base.stack_probes = true;
base.stack_probes = StackProbeType::InlineOrCall { min_llvm_version_for_inline: (11, 0, 1) };

Target {
llvm_target: "i686-unknown-linux-gnu".to_string(),
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_target/src/spec/i686_unknown_linux_musl.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use crate::spec::{LinkerFlavor, Target};
use crate::spec::{LinkerFlavor, StackProbeType, Target};

pub fn target() -> Target {
let mut base = super::linux_musl_base::opts();
base.cpu = "pentium4".to_string();
base.max_atomic_width = Some(64);
base.pre_link_args.get_mut(&LinkerFlavor::Gcc).unwrap().push("-m32".to_string());
base.pre_link_args.get_mut(&LinkerFlavor::Gcc).unwrap().push("-Wl,-melf_i386".to_string());
base.stack_probes = true;
base.stack_probes = StackProbeType::InlineOrCall { min_llvm_version_for_inline: (11, 0, 1) };

// The unwinder used by i686-unknown-linux-musl, the LLVM libunwind
// implementation, apparently relies on frame pointers existing... somehow.
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_target/src/spec/i686_unknown_netbsd.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::spec::{LinkerFlavor, Target, TargetOptions};
use crate::spec::{LinkerFlavor, StackProbeType, Target, TargetOptions};

pub fn target() -> Target {
let mut base = super::netbsd_base::opts();
base.cpu = "pentium4".to_string();
base.max_atomic_width = Some(64);
base.pre_link_args.get_mut(&LinkerFlavor::Gcc).unwrap().push("-m32".to_string());
base.stack_probes = true;
base.stack_probes = StackProbeType::InlineOrCall { min_llvm_version_for_inline: (11, 0, 1) };

Target {
llvm_target: "i686-unknown-netbsdelf".to_string(),
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_target/src/spec/i686_unknown_openbsd.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
use crate::spec::{LinkerFlavor, Target};
use crate::spec::{LinkerFlavor, StackProbeType, Target};

pub fn target() -> Target {
let mut base = super::openbsd_base::opts();
base.cpu = "pentium4".to_string();
base.max_atomic_width = Some(64);
base.pre_link_args.get_mut(&LinkerFlavor::Gcc).unwrap().push("-m32".to_string());
base.pre_link_args.get_mut(&LinkerFlavor::Gcc).unwrap().push("-fuse-ld=lld".to_string());
base.stack_probes = true;
base.stack_probes = StackProbeType::InlineOrCall { min_llvm_version_for_inline: (11, 0, 1) };

Target {
llvm_target: "i686-unknown-openbsd".to_string(),
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_target/src/spec/i686_wrs_vxworks.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::spec::{LinkerFlavor, Target};
use crate::spec::{LinkerFlavor, StackProbeType, Target};

pub fn target() -> Target {
let mut base = super::vxworks_base::opts();
base.cpu = "pentium4".to_string();
base.max_atomic_width = Some(64);
base.pre_link_args.get_mut(&LinkerFlavor::Gcc).unwrap().push("-m32".to_string());
base.stack_probes = true;
base.stack_probes = StackProbeType::InlineOrCall { min_llvm_version_for_inline: (11, 0, 1) };

Target {
llvm_target: "i686-unknown-linux-gnu".to_string(),
Expand Down
6 changes: 4 additions & 2 deletions compiler/rustc_target/src/spec/linux_kernel_base.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::spec::{LinkArgs, LinkerFlavor, PanicStrategy, RelocModel, RelroLevel, TargetOptions};
use crate::spec::{
LinkArgs, LinkerFlavor, PanicStrategy, RelocModel, RelroLevel, StackProbeType, TargetOptions,
};

pub fn opts() -> TargetOptions {
let mut pre_link_args = LinkArgs::new();
Expand All @@ -11,7 +13,7 @@ pub fn opts() -> TargetOptions {
env: "gnu".to_string(),
disable_redzone: true,
panic_strategy: PanicStrategy::Abort,
stack_probes: true,
stack_probes: StackProbeType::InlineOrCall { min_llvm_version_for_inline: (11, 0, 1) },
eliminate_frame_pointer: false,
linker_is_gnu: true,
position_independent_executables: true,
Expand Down
98 changes: 94 additions & 4 deletions compiler/rustc_target/src/spec/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ use crate::spec::crt_objects::{CrtObjects, CrtObjectsFallback};
use rustc_serialize::json::{Json, ToJson};
use rustc_span::symbol::{sym, Symbol};
use std::collections::BTreeMap;
use std::convert::TryFrom;
use std::ops::{Deref, DerefMut};
use std::path::{Path, PathBuf};
use std::str::FromStr;
Expand Down Expand Up @@ -479,6 +480,83 @@ macro_rules! supported_targets {
};
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum StackProbeType {
/// Don't emit any stack probes.
None,
/// It is harmless to use this option even on targets that do not have backend support for
/// stack probes as the failure mode is the same as if no stack-probe option was specified in
/// the first place.
Inline,
/// Call `__rust_probestack` whenever stack needs to be probed.
Call,
/// Use inline option for LLVM versions later than specified in `min_llvm_version_for_inline`
/// and call `__rust_probestack` otherwise.
InlineOrCall { min_llvm_version_for_inline: (u32, u32, u32) },
}

impl StackProbeType {
fn from_json(json: &Json) -> Result<Self, String> {
let object = json.as_object().ok_or_else(|| "expected a JSON object")?;
let kind = object
.get("kind")
.and_then(|o| o.as_string())
.ok_or_else(|| "expected `kind` to be a string")?;
match kind {
"none" => Ok(StackProbeType::None),
"inline" => Ok(StackProbeType::Inline),
"call" => Ok(StackProbeType::Call),
"inline-or-call" => {
let min_version = object
.get("min-llvm-version-for-inline")
.and_then(|o| o.as_array())
.ok_or_else(|| "expected `min-llvm-version-for-inline` to be an array")?;
let mut iter = min_version.into_iter().map(|v| {
let int = v.as_u64().ok_or_else(
|| "expected `min-llvm-version-for-inline` values to be integers",
)?;
u32::try_from(int)
.map_err(|_| "`min-llvm-version-for-inline` values don't convert to u32")
});
let min_llvm_version_for_inline = (
iter.next().unwrap_or(Ok(11))?,
iter.next().unwrap_or(Ok(0))?,
iter.next().unwrap_or(Ok(0))?,
);
Ok(StackProbeType::InlineOrCall { min_llvm_version_for_inline })
}
_ => Err(String::from(
"`kind` expected to be one of `inline-or-none`, `call` or `inline-or-call`",
)),
}
}
}

impl ToJson for StackProbeType {
fn to_json(&self) -> Json {
Json::Object(match self {
StackProbeType::None => {
vec![(String::from("kind"), "none".to_json())].into_iter().collect()
}
StackProbeType::Inline => {
vec![(String::from("kind"), "inline".to_json())].into_iter().collect()
}
StackProbeType::Call => {
vec![(String::from("kind"), "call".to_json())].into_iter().collect()
}
StackProbeType::InlineOrCall { min_llvm_version_for_inline } => vec![
(String::from("kind"), "inline-or-call".to_json()),
(
String::from("min-llvm-version-for-inline"),
min_llvm_version_for_inline.to_json(),
),
]
.into_iter()
.collect(),
})
}
}

supported_targets! {
("x86_64-unknown-linux-gnu", x86_64_unknown_linux_gnu),
("x86_64-unknown-linux-gnux32", x86_64_unknown_linux_gnux32),
Expand Down Expand Up @@ -926,8 +1004,8 @@ pub struct TargetOptions {
/// Whether or not crt-static is respected by the compiler (or is a no-op).
pub crt_static_respected: bool,

/// Whether or not stack probes (__rust_probestack) are enabled
pub stack_probes: bool,
/// The implementation of stack probes to use.
pub stack_probes: StackProbeType,

/// The minimum alignment for global symbols.
pub min_global_align: Option<u64>,
Expand Down Expand Up @@ -1085,7 +1163,7 @@ impl Default for TargetOptions {
crt_static_allows_dylibs: false,
crt_static_default: false,
crt_static_respected: false,
stack_probes: false,
stack_probes: StackProbeType::None,
min_global_align: None,
default_codegen_units: None,
trap_unreachable: true,
Expand Down Expand Up @@ -1361,6 +1439,18 @@ impl Target {
Some(Ok(()))
})).unwrap_or(Ok(()))
} );
($key_name:ident, StackProbeType) => ( {
let name = (stringify!($key_name)).replace("_", "-");
obj.find(&name[..]).and_then(|o| match StackProbeType::from_json(o) {
Ok(v) => {
base.$key_name = v;
Some(Ok(()))
},
Err(s) => Some(Err(
format!("`{:?}` is not a valid value for `{}`: {}", o, name, s)
)),
}).unwrap_or(Ok(()))
} );
($key_name:ident, crt_objects_fallback) => ( {
let name = (stringify!($key_name)).replace("_", "-");
obj.find(&name[..]).and_then(|o| o.as_string().and_then(|s| {
Expand Down Expand Up @@ -1516,7 +1606,7 @@ impl Target {
key!(crt_static_allows_dylibs, bool);
key!(crt_static_default, bool);
key!(crt_static_respected, bool);
key!(stack_probes, bool);
key!(stack_probes, StackProbeType)?;
key!(min_global_align, Option<u64>);
key!(default_codegen_units, Option<u64>);
key!(trap_unreachable, bool);
Expand Down
6 changes: 4 additions & 2 deletions compiler/rustc_target/src/spec/uefi_msvc_base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// the timer-interrupt. Device-drivers are required to use polling-based models. Furthermore, all
// code runs in the same environment, no process separation is supported.

use crate::spec::{LinkerFlavor, LldFlavor, PanicStrategy, TargetOptions};
use crate::spec::{LinkerFlavor, LldFlavor, PanicStrategy, StackProbeType, TargetOptions};

pub fn opts() -> TargetOptions {
let mut base = super::msvc_base::opts();
Expand Down Expand Up @@ -43,7 +43,9 @@ pub fn opts() -> TargetOptions {
exe_suffix: ".efi".to_string(),
allows_weak_linkage: false,
panic_strategy: PanicStrategy::Abort,
stack_probes: true,
// LLVM does not emit inline assembly because the LLVM target does not get considered as…
// "Windows".
stack_probes: StackProbeType::Call,
singlethread: true,
linker: Some("rust-lld".to_string()),
..base
Expand Down
Loading

0 comments on commit 72c7b70

Please sign in to comment.