1use std::fmt;
22
23#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Hash)]
29pub struct Attrs {
30 pub set: AttrSet,
32 pub fp_contract: FpContract,
34}
35
36impl Attrs {
37 pub const NONE: Self = Self { set: AttrSet::NONE, fp_contract: FpContract::Off };
39
40 #[must_use]
42 pub const fn is_default(self) -> bool {
43 self.set.is_empty() && matches!(self.fp_contract, FpContract::Off)
44 }
45
46 #[must_use]
52 pub fn conflict(self) -> Option<(&'static str, &'static str)> {
53 CONFLICTS
54 .iter()
55 .find(|&&(one, other, _, _)| self.set.contains(one) && self.set.contains(other))
56 .map(|&(_, _, one, other)| (one, other))
57 }
58}
59
60impl fmt::Display for Attrs {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 if self.is_default() {
65 return Ok(());
66 }
67 f.write_str("attrs(")?;
68 let mut first = true;
69 for (_, name) in self.set.iter() {
70 if !first {
71 f.write_str(", ")?;
72 }
73 first = false;
74 f.write_str(name)?;
75 }
76 if self.fp_contract != FpContract::Off {
77 if !first {
78 f.write_str(", ")?;
79 }
80 write!(f, "fp_contract={}", self.fp_contract.name())?;
81 }
82 f.write_str(")")
83 }
84}
85
86#[derive(Clone, Copy, PartialEq, Eq, Default, Hash)]
91pub struct AttrSet(u32);
92
93impl AttrSet {
94 pub const NONE: Self = Self(0);
96
97 pub const NOUNWIND: Self = Self(1 << 0);
101 pub const NORETURN: Self = Self(1 << 1);
104 pub const RETURNS_TWICE: Self = Self(1 << 2);
108 pub const WILLRETURN: Self = Self(1 << 3);
111
112 pub const COLD: Self = Self(1 << 4);
115 pub const HOT: Self = Self(1 << 5);
117
118 pub const INLINE_HINT: Self = Self(1 << 6);
121 pub const ALWAYS_INLINE: Self = Self(1 << 7);
125 pub const NOINLINE: Self = Self(1 << 8);
127 pub const OPTNONE: Self = Self(1 << 9);
130
131 pub const READNONE: Self = Self(1 << 10);
134 pub const READONLY: Self = Self(1 << 11);
137 pub const ARGMEM_ONLY: Self = Self(1 << 12);
140
141 pub const NAKED: Self = Self(1 << 13);
144 pub const USED: Self = Self(1 << 14);
147 pub const STACK_PROTECT: Self = Self(1 << 15);
149 pub const NO_STACK_PROTECTOR: Self = Self(1 << 16);
153
154 #[must_use]
156 pub const fn bits(self) -> u32 {
157 self.0
158 }
159
160 #[must_use]
162 pub const fn is_empty(self) -> bool {
163 self.0 == 0
164 }
165
166 #[must_use]
168 pub const fn contains(self, other: Self) -> bool {
169 self.0 & other.0 == other.0
170 }
171
172 #[must_use]
174 pub const fn union(self, other: Self) -> Self {
175 Self(self.0 | other.0)
176 }
177
178 #[must_use]
180 pub const fn without(self, other: Self) -> Self {
181 Self(self.0 & !other.0)
182 }
183
184 pub fn iter(self) -> impl Iterator<Item = (Self, &'static str)> {
186 NAMED.iter().copied().filter(move |&(attr, _)| self.contains(attr))
187 }
188
189 #[must_use]
191 pub fn from_name(name: &str) -> Option<Self> {
192 NAMED.iter().find(|&&(_, named)| named == name).map(|&(attr, _)| attr)
193 }
194}
195
196impl std::ops::BitOr for AttrSet {
197 type Output = Self;
198
199 fn bitor(self, other: Self) -> Self {
200 self.union(other)
201 }
202}
203
204impl std::ops::BitOrAssign for AttrSet {
205 fn bitor_assign(&mut self, other: Self) {
206 *self = self.union(other);
207 }
208}
209
210impl fmt::Debug for AttrSet {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 if self.is_empty() {
213 return f.write_str("AttrSet::NONE");
214 }
215 let named: Vec<&str> = self.iter().map(|(_, name)| name).collect();
216 f.write_str(&named.join(" | "))
217 }
218}
219
220static NAMED: &[(AttrSet, &str)] = &[
222 (AttrSet::NOUNWIND, "nounwind"),
223 (AttrSet::NORETURN, "noreturn"),
224 (AttrSet::RETURNS_TWICE, "returns_twice"),
225 (AttrSet::WILLRETURN, "willreturn"),
226 (AttrSet::COLD, "cold"),
227 (AttrSet::HOT, "hot"),
228 (AttrSet::INLINE_HINT, "inline_hint"),
229 (AttrSet::ALWAYS_INLINE, "always_inline"),
230 (AttrSet::NOINLINE, "noinline"),
231 (AttrSet::OPTNONE, "optnone"),
232 (AttrSet::READNONE, "readnone"),
233 (AttrSet::READONLY, "readonly"),
234 (AttrSet::ARGMEM_ONLY, "argmem_only"),
235 (AttrSet::NAKED, "naked"),
236 (AttrSet::USED, "used"),
237 (AttrSet::STACK_PROTECT, "stack_protect"),
238 (AttrSet::NO_STACK_PROTECTOR, "no_stack_protector"),
239];
240
241static CONFLICTS: &[(AttrSet, AttrSet, &str, &str)] = &[
243 (AttrSet::ALWAYS_INLINE, AttrSet::NOINLINE, "always_inline", "noinline"),
244 (AttrSet::ALWAYS_INLINE, AttrSet::OPTNONE, "always_inline", "optnone"),
245 (AttrSet::COLD, AttrSet::HOT, "cold", "hot"),
246 (AttrSet::READNONE, AttrSet::READONLY, "readnone", "readonly"),
247 (AttrSet::NORETURN, AttrSet::WILLRETURN, "noreturn", "willreturn"),
248 (AttrSet::STACK_PROTECT, AttrSet::NO_STACK_PROTECTOR, "stack_protect", "no_stack_protector"),
249];
250
251#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
260pub enum FpContract {
261 #[default]
263 Off,
264 On,
266 Fast,
269}
270
271impl FpContract {
272 #[must_use]
274 pub const fn name(self) -> &'static str {
275 match self {
276 Self::Off => "off",
277 Self::On => "on",
278 Self::Fast => "fast",
279 }
280 }
281
282 #[must_use]
284 pub fn from_name(name: &str) -> Option<Self> {
285 Self::all().find(|contract| contract.name() == name)
286 }
287
288 pub fn all() -> impl Iterator<Item = Self> {
290 [Self::Off, Self::On, Self::Fast].into_iter()
291 }
292}
293
294impl fmt::Display for FpContract {
295 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
296 f.write_str(self.name())
297 }
298}
299
300#[cfg(test)]
301mod tests {
302 use super::*;
303
304 #[test]
305 fn nothing_promised_prints_as_nothing() {
306 assert!(Attrs::NONE.is_default());
307 assert_eq!(Attrs::default(), Attrs::NONE);
308 assert_eq!(Attrs::NONE.to_string(), "");
309 assert_eq!(Attrs::NONE.conflict(), None);
310 }
311
312 #[test]
313 fn the_spec_example_prints_the_way_the_spec_writes_it() {
314 let attrs = Attrs { set: AttrSet::NOUNWIND, fp_contract: FpContract::On };
315 assert_eq!(attrs.to_string(), "attrs(nounwind, fp_contract=on)");
316 }
317
318 #[test]
319 fn one_of_each_half_on_its_own() {
320 let set = Attrs { set: AttrSet::COLD, ..Attrs::NONE };
321 assert_eq!(set.to_string(), "attrs(cold)");
322 let keyed = Attrs { fp_contract: FpContract::Fast, ..Attrs::NONE };
323 assert_eq!(keyed.to_string(), "attrs(fp_contract=fast)");
324 }
325
326 #[test]
327 fn attributes_print_in_one_order_whatever_order_they_were_set_in() {
328 let one = Attrs { set: AttrSet::NOUNWIND | AttrSet::COLD, ..Attrs::NONE };
329 let other = Attrs { set: AttrSet::COLD | AttrSet::NOUNWIND, ..Attrs::NONE };
330 assert_eq!(one.to_string(), "attrs(nounwind, cold)");
331 assert_eq!(one, other);
332 }
333
334 #[test]
335 fn every_attribute_has_a_name_and_finds_it_again() {
336 for &(attr, name) in NAMED {
337 assert_eq!(AttrSet::from_name(name), Some(attr), "{name}");
338 }
339 assert_eq!(AttrSet::from_name("nsw"), None);
340 assert_eq!(AttrSet::from_name(""), None);
341 }
342
343 #[test]
344 fn no_two_attributes_share_a_bit() {
345 let mut seen = 0u32;
346 for &(attr, name) in NAMED {
347 assert_eq!(attr.bits().count_ones(), 1, "{name} is not one bit");
348 assert_eq!(seen & attr.bits(), 0, "{name} shares a bit");
349 seen |= attr.bits();
350 }
351 }
352
353 #[test]
354 fn a_function_cannot_be_told_to_inline_and_not_to() {
355 let attrs = Attrs { set: AttrSet::ALWAYS_INLINE | AttrSet::NOINLINE, ..Attrs::NONE };
356 assert_eq!(attrs.conflict(), Some(("always_inline", "noinline")));
357 let fine = Attrs { set: AttrSet::INLINE_HINT | AttrSet::NOINLINE, ..Attrs::NONE };
358 assert_eq!(fine.conflict(), None);
359 }
360
361 #[test]
362 fn both_halves_of_every_conflicting_pair_are_real_attributes() {
363 for &(one, other, one_name, other_name) in CONFLICTS {
364 assert_eq!(AttrSet::from_name(one_name), Some(one), "{one_name}");
365 assert_eq!(AttrSet::from_name(other_name), Some(other), "{other_name}");
366 }
367 }
368
369 #[test]
370 fn a_set_says_what_is_in_it_when_something_prints_it_for_debugging() {
371 assert_eq!(format!("{:?}", AttrSet::NONE), "AttrSet::NONE");
372 assert_eq!(format!("{:?}", AttrSet::COLD | AttrSet::NAKED), "cold | naked");
373 }
374
375 #[test]
376 fn combining_and_removing() {
377 let mut set = AttrSet::NOUNWIND;
378 set |= AttrSet::COLD;
379 assert!(set.contains(AttrSet::NOUNWIND));
380 assert!(set.contains(AttrSet::COLD));
381 assert!(!set.contains(AttrSet::HOT));
382 assert_eq!(set.without(AttrSet::COLD), AttrSet::NOUNWIND);
383 assert!(AttrSet::NONE.is_empty());
384 }
385
386 #[test]
387 fn every_contraction_setting_finds_its_name_again() {
388 for contract in FpContract::all() {
389 assert_eq!(FpContract::from_name(contract.name()), Some(contract));
390 }
391 assert_eq!(FpContract::from_name("maybe"), None);
392 assert_eq!(FpContract::default(), FpContract::Off);
393 }
394}