Skip to main content

rucc_opt/
fuel.rs

1//! How many transformations a pass is allowed before it stops transforming.
2//!
3//! Section 9.10 of `spec/09-optimizer.md` requires this of every pass, and the reason is
4//! bisection. When a program compiles wrongly at `-O2` and correctly at `-O0`, the question is
5//! which of the thousands of rewrites the optimizer performed is the wrong one. With fuel it is
6//! a binary search: give the suspect pass n transformations, run the program, and halve. The
7//! answer arrives in about twenty compilations of a file rather than by reading a diff of two
8//! assembly listings.
9//!
10//! The counter is per pass and per compilation rather than per function, because the site being
11//! searched for is one site in one file and numbering it per function would need the function to
12//! be identified first, which is the thing not yet known.
13
14use std::fmt;
15
16/// What a pass has left.
17///
18/// A pass asks [`Fuel::take`] immediately before each transformation and does nothing when the
19/// answer is no. A pass that asks after transforming, or that transforms without asking, is
20/// what the fuel test in [`crate::pipeline`] exists to catch.
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub struct Fuel {
23    /// How many transformations are still allowed, or `None` for as many as are wanted.
24    left: Option<u32>,
25    /// How many have been performed.
26    spent: u32,
27}
28
29impl Fuel {
30    /// Fuel that never runs out, which is what every pass gets unless `-fpass-fuel` says
31    /// otherwise.
32    #[must_use]
33    pub const fn unlimited() -> Self {
34        Self { left: None, spent: 0 }
35    }
36
37    /// Fuel for exactly this many transformations.
38    #[must_use]
39    pub const fn of(count: u32) -> Self {
40        Self { left: Some(count), spent: 0 }
41    }
42
43    /// Whether one more transformation is allowed, counting it when it is.
44    ///
45    /// The counting happens here rather than at the transformation because a pass that has to
46    /// remember to do two things does one of them.
47    pub fn take(&mut self) -> bool {
48        match &mut self.left {
49            Some(0) => false,
50            Some(left) => {
51                *left -= 1;
52                self.spent += 1;
53                true
54            }
55            None => {
56                self.spent += 1;
57                true
58            }
59        }
60    }
61
62    /// How many transformations have been taken.
63    #[must_use]
64    pub const fn spent(self) -> u32 {
65        self.spent
66    }
67
68    /// Whether the pass has run out, which is only ever true when a limit was set.
69    #[must_use]
70    pub const fn is_empty(self) -> bool {
71        matches!(self.left, Some(0))
72    }
73}
74
75impl Default for Fuel {
76    fn default() -> Self {
77        Self::unlimited()
78    }
79}
80
81impl fmt::Display for Fuel {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        match self.left {
84            None => write!(f, "{} of unlimited", self.spent),
85            Some(left) => write!(f, "{} of {}", self.spent, self.spent + left),
86        }
87    }
88}
89
90#[cfg(test)]
91mod tests {
92    use super::Fuel;
93
94    #[test]
95    fn unlimited_fuel_is_never_refused_and_still_counts() {
96        let mut fuel = Fuel::unlimited();
97        for _ in 0..1000 {
98            assert!(fuel.take());
99        }
100        assert_eq!(fuel.spent(), 1000);
101        assert!(!fuel.is_empty());
102    }
103
104    #[test]
105    fn a_limit_of_three_allows_three_and_then_stops_allowing_any() {
106        let mut fuel = Fuel::of(3);
107        assert!(fuel.take());
108        assert!(fuel.take());
109        assert!(fuel.take());
110        assert!(!fuel.take());
111        assert!(!fuel.take());
112        assert_eq!(fuel.spent(), 3);
113        assert!(fuel.is_empty());
114    }
115
116    #[test]
117    fn a_limit_of_zero_allows_nothing_at_all() {
118        let mut fuel = Fuel::of(0);
119        assert!(!fuel.take());
120        assert_eq!(fuel.spent(), 0);
121        assert!(fuel.is_empty());
122    }
123
124    #[test]
125    fn the_display_says_how_much_of_how_much() {
126        let mut fuel = Fuel::of(4);
127        assert!(fuel.take());
128        assert_eq!(fuel.to_string(), "1 of 4");
129        let mut open = Fuel::unlimited();
130        assert!(open.take());
131        assert_eq!(open.to_string(), "1 of unlimited");
132    }
133}