Skip to main content

rucc_ir/
attrs.rs

1//! What is true of a whole function rather than of one instruction in it.
2//!
3//! Design: `spec/08-ir.md` section 8.4.
4//!
5//! A flag in [`crate::Flags`] is a licence over one instruction. An attribute here is a fact
6//! about a function, and the difference matters because the two are used at different times: the
7//! optimizer reads a flag when it is about to rewrite the instruction carrying it, and it reads
8//! an attribute when it is looking at a call and has no other way to find out what the callee
9//! does.
10//!
11//! That is the test for whether something belongs here. `noreturn` is an attribute because the
12//! block after a call to `exit` is unreachable and the only way to know that is to ask about
13//! `exit`. `nsw` is not, because the instruction it licenses is right there.
14//!
15//! Nearly every one of these comes from something a person wrote. `_Noreturn` and the GNU
16//! attributes it shares a spelling with, `inline`, `__attribute__((const))` and `((pure))` and
17//! `((cold))` and `((naked))`, and the command line for the rest. None of them is inferred yet;
18//! inferring them from a function body is an interprocedural analysis and belongs to the
19//! optimizer, which will set them on the same fields.
20
21use std::fmt;
22
23/// Everything true of a whole function.
24///
25/// Two parts, because most of these are either so or not so and one of them has three answers.
26/// A default set is a function nobody has promised anything about, which is what a function
27/// fresh from [`crate::Func::new`] is.
28#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, Hash)]
29pub struct Attrs {
30    /// The ones that are either set or not.
31    pub set: AttrSet,
32    /// How far the code generator may fuse a multiply and an addition.
33    pub fp_contract: FpContract,
34}
35
36impl Attrs {
37    /// Nothing promised.
38    pub const NONE: Self = Self { set: AttrSet::NONE, fp_contract: FpContract::Off };
39
40    /// Whether nothing has been promised, which is when the printer writes nothing at all.
41    #[must_use]
42    pub const fn is_default(self) -> bool {
43        self.set.is_empty() && matches!(self.fp_contract, FpContract::Off)
44    }
45
46    /// Two attributes that are set and contradict each other, if there are any.
47    ///
48    /// The verifier asks, because a function that is both `always_inline` and `noinline` is one
49    /// where two parts of the frontend disagreed, and the answer the optimizer picks would be
50    /// whichever branch it happens to test first.
51    #[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    /// The form the textual IR uses, `attrs(nounwind, fp_contract=on)`, and nothing at all when
62    /// nothing has been promised.
63    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/// The attributes that are either set or not.
87///
88/// A bitset for the same reason [`crate::Flags`] is one, though the pressure is lower here since
89/// there is one of these per function rather than one per instruction.
90#[derive(Clone, Copy, PartialEq, Eq, Default, Hash)]
91pub struct AttrSet(u32);
92
93impl AttrSet {
94    /// Nothing set.
95    pub const NONE: Self = Self(0);
96
97    /// No exception unwinds out of this function. Every C function compiled without
98    /// `-fexceptions` is one, and it is what lets a call be moved and lets the caller skip the
99    /// landing pad.
100    pub const NOUNWIND: Self = Self(1 << 0);
101    /// Control never comes back. `_Noreturn`, and `__attribute__((noreturn))` for the same
102    /// thing under the older spelling. The instruction after a call to one is unreachable.
103    pub const NORETURN: Self = Self(1 << 1);
104    /// Control may come back twice from one call. `setjmp` and `vfork`, under
105    /// `__attribute__((returns_twice))`. Every value live across a call to one has to be in
106    /// memory, so this switches off a large amount of the optimizer for the caller.
107    pub const RETURNS_TWICE: Self = Self(1 << 2);
108    /// Control comes back, eventually. This is what says an empty infinite loop is not in here,
109    /// and without it a call cannot be deleted even when nothing uses its result.
110    pub const WILLRETURN: Self = Self(1 << 3);
111
112    /// Rarely called, from `__attribute__((cold))`. Its code goes in the cold section and the
113    /// path leading to a call to it is the unlikely one.
114    pub const COLD: Self = Self(1 << 4);
115    /// Often called, from `__attribute__((hot))`.
116    pub const HOT: Self = Self(1 << 5);
117
118    /// The programmer wrote `inline`, which in C is a hint about linkage and about inlining and
119    /// which the inliner treats as a small nudge rather than as an instruction.
120    pub const INLINE_HINT: Self = Self(1 << 6);
121    /// `__attribute__((always_inline))`, which is not a hint. Failing to inline one of these is
122    /// an error, because the header that wrote it usually meant a target-specific builtin that
123    /// does not work any other way.
124    pub const ALWAYS_INLINE: Self = Self(1 << 7);
125    /// `__attribute__((noinline))`.
126    pub const NOINLINE: Self = Self(1 << 8);
127    /// `__attribute__((optimize("O0")))` and the pragma for it. Nothing in this function is
128    /// rewritten, which is what somebody debugging one function of a release build asks for.
129    pub const OPTNONE: Self = Self(1 << 9);
130
131    /// Reads no memory and writes none, so its result depends only on its arguments and two
132    /// calls with the same arguments are one call. `__attribute__((const))`.
133    pub const READNONE: Self = Self(1 << 10);
134    /// Writes no memory, though it may read it. `__attribute__((pure))`. Two calls with the
135    /// same arguments are one call only if nothing wrote memory in between.
136    pub const READONLY: Self = Self(1 << 11);
137    /// Touches no memory except through the pointers it was passed. This is what makes a call
138    /// stop clobbering everything the caller knew about its own locals.
139    pub const ARGMEM_ONLY: Self = Self(1 << 12);
140
141    /// `__attribute__((naked))`. No prologue and no epilogue are emitted, the body is inline
142    /// assembly, and the code generator does exactly what it is told.
143    pub const NAKED: Self = Self(1 << 13);
144    /// Keep it even if nothing refers to it. `__attribute__((used))`, which is how a section of
145    /// initializers survives a linker that garbage collects.
146    pub const USED: Self = Self(1 << 14);
147    /// Emit a stack protector for this frame, from `-fstack-protector` and the attribute.
148    pub const STACK_PROTECT: Self = Self(1 << 15);
149    /// Emit none, whatever the command line said.
150    /// `__attribute__((no_stack_protector))`, which the kernel needs on the functions that run
151    /// before the canary exists.
152    pub const NO_STACK_PROTECTOR: Self = Self(1 << 16);
153
154    /// The underlying bits, for the printer and for hashing.
155    #[must_use]
156    pub const fn bits(self) -> u32 {
157        self.0
158    }
159
160    /// Whether nothing is set.
161    #[must_use]
162    pub const fn is_empty(self) -> bool {
163        self.0 == 0
164    }
165
166    /// Whether every attribute in `other` is set here.
167    #[must_use]
168    pub const fn contains(self, other: Self) -> bool {
169        self.0 & other.0 == other.0
170    }
171
172    /// Both sets.
173    #[must_use]
174    pub const fn union(self, other: Self) -> Self {
175        Self(self.0 | other.0)
176    }
177
178    /// This set without the attributes in `other`.
179    #[must_use]
180    pub const fn without(self, other: Self) -> Self {
181        Self(self.0 & !other.0)
182    }
183
184    /// Every attribute that is set, with its name, in the order the printer writes them.
185    pub fn iter(self) -> impl Iterator<Item = (Self, &'static str)> {
186        NAMED.iter().copied().filter(move |&(attr, _)| self.contains(attr))
187    }
188
189    /// The attribute with that name, if there is one.
190    #[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
220/// Each attribute with its name, in printing order.
221static 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
241/// The pairs that cannot both be set, with their names for the message.
242static 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/// How far a multiply and an addition may be fused into one rounding.
252///
253/// [`crate::Flags::CONTRACT`] is the same question asked about one instruction, and it is the
254/// one the optimizer reads. This is the one the code generator reads, because by the time it
255/// runs the two operations it might fuse may have arrived from different expressions and the
256/// flags that were on them are gone. `off` is the default here rather than the one C says,
257/// because the frontend is what knows what the command line asked for and a licence nobody
258/// granted should not be assumed.
259#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
260pub enum FpContract {
261    /// Never. Every rounding the source asked for happens.
262    #[default]
263    Off,
264    /// Within one expression, which is what C's `FP_CONTRACT` pragma allows.
265    On,
266    /// Anywhere in the function, which is what `-ffp-contract=fast` means and what gcc does by
267    /// default.
268    Fast,
269}
270
271impl FpContract {
272    /// The textual form.
273    #[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    /// The setting with that name, if there is one.
283    #[must_use]
284    pub fn from_name(name: &str) -> Option<Self> {
285        Self::all().find(|contract| contract.name() == name)
286    }
287
288    /// Every setting, least permissive first.
289    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}