use std::fmt;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Hash)]
pub struct Attrs {
pub set: AttrSet,
pub fp_contract: FpContract,
}
impl Attrs {
pub const NONE: Self = Self { set: AttrSet::NONE, fp_contract: FpContract::Off };
#[must_use]
pub const fn is_default(self) -> bool {
self.set.is_empty() && matches!(self.fp_contract, FpContract::Off)
}
#[must_use]
pub fn conflict(self) -> Option<(&'static str, &'static str)> {
CONFLICTS
.iter()
.find(|&&(one, other, _, _)| self.set.contains(one) && self.set.contains(other))
.map(|&(_, _, one, other)| (one, other))
}
}
impl fmt::Display for Attrs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_default() {
return Ok(());
}
f.write_str("attrs(")?;
let mut first = true;
for (_, name) in self.set.iter() {
if !first {
f.write_str(", ")?;
}
first = false;
f.write_str(name)?;
}
if self.fp_contract != FpContract::Off {
if !first {
f.write_str(", ")?;
}
write!(f, "fp_contract={}", self.fp_contract.name())?;
}
f.write_str(")")
}
}
#[derive(Clone, Copy, PartialEq, Eq, Default, Hash)]
pub struct AttrSet(u32);
impl AttrSet {
pub const NONE: Self = Self(0);
pub const NOUNWIND: Self = Self(1 << 0);
pub const NORETURN: Self = Self(1 << 1);
pub const RETURNS_TWICE: Self = Self(1 << 2);
pub const WILLRETURN: Self = Self(1 << 3);
pub const COLD: Self = Self(1 << 4);
pub const HOT: Self = Self(1 << 5);
pub const INLINE_HINT: Self = Self(1 << 6);
pub const ALWAYS_INLINE: Self = Self(1 << 7);
pub const NOINLINE: Self = Self(1 << 8);
pub const OPTNONE: Self = Self(1 << 9);
pub const READNONE: Self = Self(1 << 10);
pub const READONLY: Self = Self(1 << 11);
pub const ARGMEM_ONLY: Self = Self(1 << 12);
pub const NAKED: Self = Self(1 << 13);
pub const USED: Self = Self(1 << 14);
pub const STACK_PROTECT: Self = Self(1 << 15);
pub const NO_STACK_PROTECTOR: Self = Self(1 << 16);
#[must_use]
pub const fn bits(self) -> u32 {
self.0
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.0 == 0
}
#[must_use]
pub const fn contains(self, other: Self) -> bool {
self.0 & other.0 == other.0
}
#[must_use]
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
#[must_use]
pub const fn without(self, other: Self) -> Self {
Self(self.0 & !other.0)
}
pub fn iter(self) -> impl Iterator<Item = (Self, &'static str)> {
NAMED.iter().copied().filter(move |&(attr, _)| self.contains(attr))
}
#[must_use]
pub fn from_name(name: &str) -> Option<Self> {
NAMED.iter().find(|&&(_, named)| named == name).map(|&(attr, _)| attr)
}
}
impl std::ops::BitOr for AttrSet {
type Output = Self;
fn bitor(self, other: Self) -> Self {
self.union(other)
}
}
impl std::ops::BitOrAssign for AttrSet {
fn bitor_assign(&mut self, other: Self) {
*self = self.union(other);
}
}
impl fmt::Debug for AttrSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_empty() {
return f.write_str("AttrSet::NONE");
}
let named: Vec<&str> = self.iter().map(|(_, name)| name).collect();
f.write_str(&named.join(" | "))
}
}
static NAMED: &[(AttrSet, &str)] = &[
(AttrSet::NOUNWIND, "nounwind"),
(AttrSet::NORETURN, "noreturn"),
(AttrSet::RETURNS_TWICE, "returns_twice"),
(AttrSet::WILLRETURN, "willreturn"),
(AttrSet::COLD, "cold"),
(AttrSet::HOT, "hot"),
(AttrSet::INLINE_HINT, "inline_hint"),
(AttrSet::ALWAYS_INLINE, "always_inline"),
(AttrSet::NOINLINE, "noinline"),
(AttrSet::OPTNONE, "optnone"),
(AttrSet::READNONE, "readnone"),
(AttrSet::READONLY, "readonly"),
(AttrSet::ARGMEM_ONLY, "argmem_only"),
(AttrSet::NAKED, "naked"),
(AttrSet::USED, "used"),
(AttrSet::STACK_PROTECT, "stack_protect"),
(AttrSet::NO_STACK_PROTECTOR, "no_stack_protector"),
];
static CONFLICTS: &[(AttrSet, AttrSet, &str, &str)] = &[
(AttrSet::ALWAYS_INLINE, AttrSet::NOINLINE, "always_inline", "noinline"),
(AttrSet::ALWAYS_INLINE, AttrSet::OPTNONE, "always_inline", "optnone"),
(AttrSet::COLD, AttrSet::HOT, "cold", "hot"),
(AttrSet::READNONE, AttrSet::READONLY, "readnone", "readonly"),
(AttrSet::NORETURN, AttrSet::WILLRETURN, "noreturn", "willreturn"),
(AttrSet::STACK_PROTECT, AttrSet::NO_STACK_PROTECTOR, "stack_protect", "no_stack_protector"),
];
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub enum FpContract {
#[default]
Off,
On,
Fast,
}
impl FpContract {
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Off => "off",
Self::On => "on",
Self::Fast => "fast",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<Self> {
Self::all().find(|contract| contract.name() == name)
}
pub fn all() -> impl Iterator<Item = Self> {
[Self::Off, Self::On, Self::Fast].into_iter()
}
}
impl fmt::Display for FpContract {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn nothing_promised_prints_as_nothing() {
assert!(Attrs::NONE.is_default());
assert_eq!(Attrs::default(), Attrs::NONE);
assert_eq!(Attrs::NONE.to_string(), "");
assert_eq!(Attrs::NONE.conflict(), None);
}
#[test]
fn the_spec_example_prints_the_way_the_spec_writes_it() {
let attrs = Attrs { set: AttrSet::NOUNWIND, fp_contract: FpContract::On };
assert_eq!(attrs.to_string(), "attrs(nounwind, fp_contract=on)");
}
#[test]
fn one_of_each_half_on_its_own() {
let set = Attrs { set: AttrSet::COLD, ..Attrs::NONE };
assert_eq!(set.to_string(), "attrs(cold)");
let keyed = Attrs { fp_contract: FpContract::Fast, ..Attrs::NONE };
assert_eq!(keyed.to_string(), "attrs(fp_contract=fast)");
}
#[test]
fn attributes_print_in_one_order_whatever_order_they_were_set_in() {
let one = Attrs { set: AttrSet::NOUNWIND | AttrSet::COLD, ..Attrs::NONE };
let other = Attrs { set: AttrSet::COLD | AttrSet::NOUNWIND, ..Attrs::NONE };
assert_eq!(one.to_string(), "attrs(nounwind, cold)");
assert_eq!(one, other);
}
#[test]
fn every_attribute_has_a_name_and_finds_it_again() {
for &(attr, name) in NAMED {
assert_eq!(AttrSet::from_name(name), Some(attr), "{name}");
}
assert_eq!(AttrSet::from_name("nsw"), None);
assert_eq!(AttrSet::from_name(""), None);
}
#[test]
fn no_two_attributes_share_a_bit() {
let mut seen = 0u32;
for &(attr, name) in NAMED {
assert_eq!(attr.bits().count_ones(), 1, "{name} is not one bit");
assert_eq!(seen & attr.bits(), 0, "{name} shares a bit");
seen |= attr.bits();
}
}
#[test]
fn a_function_cannot_be_told_to_inline_and_not_to() {
let attrs = Attrs { set: AttrSet::ALWAYS_INLINE | AttrSet::NOINLINE, ..Attrs::NONE };
assert_eq!(attrs.conflict(), Some(("always_inline", "noinline")));
let fine = Attrs { set: AttrSet::INLINE_HINT | AttrSet::NOINLINE, ..Attrs::NONE };
assert_eq!(fine.conflict(), None);
}
#[test]
fn both_halves_of_every_conflicting_pair_are_real_attributes() {
for &(one, other, one_name, other_name) in CONFLICTS {
assert_eq!(AttrSet::from_name(one_name), Some(one), "{one_name}");
assert_eq!(AttrSet::from_name(other_name), Some(other), "{other_name}");
}
}
#[test]
fn a_set_says_what_is_in_it_when_something_prints_it_for_debugging() {
assert_eq!(format!("{:?}", AttrSet::NONE), "AttrSet::NONE");
assert_eq!(format!("{:?}", AttrSet::COLD | AttrSet::NAKED), "cold | naked");
}
#[test]
fn combining_and_removing() {
let mut set = AttrSet::NOUNWIND;
set |= AttrSet::COLD;
assert!(set.contains(AttrSet::NOUNWIND));
assert!(set.contains(AttrSet::COLD));
assert!(!set.contains(AttrSet::HOT));
assert_eq!(set.without(AttrSet::COLD), AttrSet::NOUNWIND);
assert!(AttrSet::NONE.is_empty());
}
#[test]
fn every_contraction_setting_finds_its_name_again() {
for contract in FpContract::all() {
assert_eq!(FpContract::from_name(contract.name()), Some(contract));
}
assert_eq!(FpContract::from_name("maybe"), None);
assert_eq!(FpContract::default(), FpContract::Off);
}
}