Skip to content

Commit

Permalink
[RFC] support panic and target_has_atomic predicates
Browse files Browse the repository at this point in the history
There are a couple of new predicates in Rust 1.60: `target_has_atomic`
and `panic`. Here's a first cut at supporting them.

The panic predicate depends both on native platform support (default
behavior) and on a potential override through RUSTFLAGS. We should
somehow expose this I guess, but I'm not sure how.
  • Loading branch information
sunshowers committed May 18, 2022
1 parent 7cf31c0 commit 3826447
Show file tree
Hide file tree
Showing 7 changed files with 778 additions and 63 deletions.
30 changes: 24 additions & 6 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub enum Reason {
InvalidInteger,
/// The root cfg() may only contain a single predicate
MultipleRootPredicates,
/// A `target_has_atomic` predicate didn't correctly parse.
InvalidHasAtomic,
/// An element was not part of the builtin information in rustc
UnknownBuiltin,
}
Expand Down Expand Up @@ -72,9 +74,9 @@ impl fmt::Display for ParseError {
impl fmt::Display for Reason {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use Reason::{
Empty, InvalidCharacters, InvalidInteger, InvalidNot, MultipleRootPredicates,
UnclosedParens, UnclosedQuotes, Unexpected, UnknownBuiltin, UnopenedParens,
UnopenedQuotes,
Empty, InvalidCharacters, InvalidHasAtomic, InvalidInteger, InvalidNot,
MultipleRootPredicates, UnclosedParens, UnclosedQuotes, Unexpected, UnknownBuiltin,
UnopenedParens, UnopenedQuotes,
};

match self {
Expand All @@ -101,6 +103,7 @@ impl fmt::Display for Reason {
InvalidNot(np) => f.write_fmt(format_args!("not() takes 1 predicate, found {}", np)),
InvalidInteger => f.write_str("invalid integer"),
MultipleRootPredicates => f.write_str("multiple root predicates"),
InvalidHasAtomic => f.write_str("expected integer or \"ptr\""),
UnknownBuiltin => f.write_str("unknown built-in"),
}
}
Expand All @@ -109,9 +112,9 @@ impl fmt::Display for Reason {
impl Error for ParseError {
fn description(&self) -> &str {
use Reason::{
Empty, InvalidCharacters, InvalidInteger, InvalidNot, MultipleRootPredicates,
UnclosedParens, UnclosedQuotes, Unexpected, UnknownBuiltin, UnopenedParens,
UnopenedQuotes,
Empty, InvalidCharacters, InvalidHasAtomic, InvalidInteger, InvalidNot,
MultipleRootPredicates, UnclosedParens, UnclosedQuotes, Unexpected, UnknownBuiltin,
UnopenedParens, UnopenedQuotes,
};

match self.reason {
Expand All @@ -125,7 +128,22 @@ impl Error for ParseError {
InvalidNot(_) => "not() takes 1 predicate",
InvalidInteger => "invalid integer",
MultipleRootPredicates => "multiple root predicates",
InvalidHasAtomic => "expected integer or \"ptr\"",
UnknownBuiltin => "unknown built-in",
}
}
}

/// Error parsing a `target_has_atomic` predicate.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct HasAtomicParseError {
pub(crate) input: String,
}

impl fmt::Display for HasAtomicParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "expected integer or \"ptr\", found {}", self.input)
}
}

impl Error for HasAtomicParseError {}
31 changes: 29 additions & 2 deletions src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,12 @@ pub enum TargetPredicate {
/// This also applies to the bare [`unix` and `windows`](https://doc.rust-lang.org/reference/conditional-compilation.html#unix-and-windows)
/// predicates.
Family(targ::Family),
/// [target_has_atomic](https://doc.rust-lang.org/reference/conditional-compilation.html#target_has_atomic).
HasAtomic(targ::HasAtomic),
/// [target_os](https://doc.rust-lang.org/reference/conditional-compilation.html#target_os)
Os(targ::Os),
/// [panic](https://doc.rust-lang.org/reference/conditional-compilation.html#panic)
Panic(targ::Panic),
/// [target_pointer_width](https://doc.rust-lang.org/reference/conditional-compilation.html#target_pointer_width)
PointerWidth(u8),
/// [target_vendor](https://doc.rust-lang.org/reference/conditional-compilation.html#target_vendor)
Expand All @@ -54,7 +58,9 @@ pub trait TargetMatcher {

impl TargetMatcher for targ::TargetInfo {
fn matches(&self, tp: &TargetPredicate) -> bool {
use TargetPredicate::{Arch, Endian, Env, Family, Os, PointerWidth, Vendor};
use TargetPredicate::{
Arch, Endian, Env, Family, HasAtomic, Os, Panic, PointerWidth, Vendor,
};

match tp {
Arch(a) => a == &self.arch,
Expand All @@ -65,12 +71,16 @@ impl TargetMatcher for targ::TargetInfo {
None => env.0.is_empty(),
},
Family(fam) => self.families.contains(fam),
HasAtomic(has_atomic) => self.has_atomics.contains(*has_atomic),
Os(os) => Some(os) == self.os.as_ref(),
PointerWidth(w) => *w == self.pointer_width,
Vendor(ven) => match &self.vendor {
Some(v) => ven == v,
None => ven == &targ::Vendor::unknown,
},
Panic(_panic) => {
todo!("probably want to model 'panic' as having a default value + an override")
}
}
}
}
Expand All @@ -81,7 +91,9 @@ impl TargetMatcher for target_lexicon::Triple {
#[allow(clippy::match_same_arms)]
fn matches(&self, tp: &TargetPredicate) -> bool {
use target_lexicon::*;
use TargetPredicate::{Arch, Endian, Env, Family, Os, PointerWidth, Vendor};
use TargetPredicate::{
Arch, Endian, Env, Family, HasAtomic, Os, Panic, PointerWidth, Vendor,
};

match tp {
Arch(arch) => {
Expand Down Expand Up @@ -263,6 +275,11 @@ impl TargetMatcher for target_lexicon::Triple {
_ => false,
}
}
HasAtomic(_) => {
// atomic support depends on both the architecture and the OS. Assume false for
// this.
false
}
Os(os) => match os.0.parse::<OperatingSystem>() {
Ok(o) => match self.environment {
Environment::HermitKernel => os == &targ::Os::hermit,
Expand All @@ -282,6 +299,10 @@ impl TargetMatcher for target_lexicon::Triple {
}
}
},
Panic(_) => {
// panic support depends on the OS. Assume false for this.
false
}
Vendor(ven) => match ven.0.parse::<target_lexicon::Vendor>() {
Ok(v) => self.vendor == v,
Err(_) => false,
Expand Down Expand Up @@ -337,6 +358,8 @@ pub(crate) enum Which {
Env,
Family,
Os,
HasAtomic(targ::HasAtomic),
Panic,
PointerWidth(u8),
Vendor,
}
Expand Down Expand Up @@ -409,6 +432,10 @@ impl InnerPredicate {
s[it.span.clone().unwrap()].to_owned(),
))),
Which::Endian(end) => Target(TargetPredicate::Endian(*end)),
Which::HasAtomic(has_atomic) => Target(TargetPredicate::HasAtomic(*has_atomic)),
Which::Panic => Target(TargetPredicate::Panic(targ::Panic::new(
s[it.span.clone().unwrap()].to_owned(),
))),
Which::PointerWidth(pw) => Target(TargetPredicate::PointerWidth(*pw)),
},
IP::Test => Test,
Expand Down
13 changes: 13 additions & 0 deletions src/expr/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ impl Expression {
}
}
}
"panic" => InnerPredicate::Target(InnerTarget {
which: Which::Panic,
span: Some(span),
}),
target_key if key.starts_with("target_") => {
let (val, vspan) = match val {
None => {
Expand Down Expand Up @@ -154,6 +158,14 @@ impl Expression {
})?),
span: None,
},
"has_atomic" => InnerTarget {
which: Which::HasAtomic(val.parse().map_err(|_err| ParseError {
original: original.to_owned(),
span: vspan,
reason: Reason::InvalidHasAtomic,
})?),
span: None,
},
"pointer_width" => InnerTarget {
which: Which::PointerWidth(val.parse().map_err(|_err| ParseError {
original: original.to_owned(),
Expand All @@ -174,6 +186,7 @@ impl Expression {
"target_family",
"target_env",
"target_endian",
"target_has_atomic",
"target_pointer_width",
"target_vendor",
]),
Expand Down
111 changes: 109 additions & 2 deletions src/targets.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::error::Reason;
use crate::error::{HasAtomicParseError, Reason};
use std::{borrow::Cow, ops::Deref};

mod builtins;
Expand Down Expand Up @@ -35,6 +35,10 @@ pub struct Family(pub Cow<'static, str>);
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Env(pub Cow<'static, str>);

/// The panic strategy used on this target by default.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Panic(pub Cow<'static, str>);

macro_rules! field_impls {
($kind:ident) => {
impl $kind {
Expand Down Expand Up @@ -80,6 +84,44 @@ field_impls!(Vendor);
field_impls!(Os);
field_impls!(Family);
field_impls!(Env);
field_impls!(Panic);

/// Integer size and pointers for which there's support for atomic functions.
#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[non_exhaustive]
pub enum HasAtomic {
/// The platform supports atomics for the given integer size in bits (e.g. `AtomicU8` if
/// `HasAtomic::IntegerSize(8)`).
IntegerSize(u16),

/// The platform supports atomics for pointers (`AtomicPtr`).
Pointer,
}

impl std::str::FromStr for HasAtomic {
type Err = HasAtomicParseError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Ok(size) = s.parse::<u16>() {
Ok(Self::IntegerSize(size))
} else if s == "ptr" {
Ok(HasAtomic::Pointer)
} else {
Err(HasAtomicParseError {
input: s.to_owned(),
})
}
}
}

impl std::fmt::Display for HasAtomic {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::IntegerSize(size) => write!(f, "{}", size),
Self::Pointer => write!(f, "ptr"),
}
}
}

/// A set of families for a target.
///
Expand Down Expand Up @@ -145,6 +187,66 @@ impl std::fmt::Display for Families {
}
}

/// A set of [`HasAtomic`] instances a target.
#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct HasAtomics(Cow<'static, [HasAtomic]>);

impl HasAtomics {
/// Constructs a new instance.
///
/// If you have a `&'static [HasAtomic]`, prefer [`Self::new_const`].
#[inline]
pub fn new(val: impl IntoIterator<Item = HasAtomic>) -> Self {
let mut has_atomics: Vec<_> = val.into_iter().collect();
has_atomics.sort_unstable();
Self(Cow::Owned(has_atomics))
}

/// Constructs a new instance of this struct from a static slice of [`HasAtomic`].
///
/// `val` must be in sorted order: this constructor cannot check for that due to
/// limitations in current versions of Rust.
#[inline]
pub const fn new_const(val: &'static [HasAtomic]) -> Self {
// TODO: Check that val is sorted.
Self(Cow::Borrowed(val))
}

/// Returns true if this list of families contains a given family.
#[inline]
pub fn contains(&self, val: HasAtomic) -> bool {
self.0.contains(&val)
}
}

impl Deref for HasAtomics {
type Target = [HasAtomic];
fn deref(&self) -> &Self::Target {
&*self.0
}
}

impl AsRef<[HasAtomic]> for HasAtomics {
#[inline]
fn as_ref(&self) -> &[HasAtomic] {
&*self.0
}
}

impl std::fmt::Display for HasAtomics {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{{")?;
let len = self.0.len();
for (idx, has_atomic) in self.0.iter().enumerate() {
write!(f, "{}", has_atomic)?;
if idx + 1 < len {
write!(f, ", ")?;
}
}
write!(f, "}}")
}
}

macro_rules! target_enum {
(
$(#[$outer:meta])*
Expand Down Expand Up @@ -238,6 +340,11 @@ pub struct TargetInfo {
/// [target_endian](https://doc.rust-lang.org/reference/conditional-compilation.html#target_endian)
/// predicate.
pub endian: Endian,
/// The target's support for atomics. Used by the has_target_atomics predicate.
pub has_atomics: HasAtomics,
/// The panic strategy used on this target by default. Used by the
/// [panic](https://doc.rust-lang.org/beta/reference/conditional-compilation.html#panic) predicate.
pub panic: Panic,
}

/// Attempts to find the `TargetInfo` for the specified target triple
Expand All @@ -257,7 +364,7 @@ pub fn get_builtin_target_by_triple(triple: &str) -> Option<&'static TargetInfo>
/// versions.
///
/// ```
/// assert_eq!("1.59.0", cfg_expr::targets::rustc_version());
/// assert_eq!("1.60.0", cfg_expr::targets::rustc_version());
/// ```
pub fn rustc_version() -> &'static str {
builtins::RUSTC_VERSION
Expand Down
Loading

0 comments on commit 3826447

Please sign in to comment.