• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Traits used to represent [lattices] for use as the domain of a dataflow analysis.
2 //!
3 //! # Overview
4 //!
5 //! The most common lattice is a powerset of some set `S`, ordered by [set inclusion]. The [Hasse
6 //! diagram] for the powerset of a set with two elements (`X` and `Y`) is shown below. Note that
7 //! distinct elements at the same height in a Hasse diagram (e.g. `{X}` and `{Y}`) are
8 //! *incomparable*, not equal.
9 //!
10 //! ```text
11 //!      {X, Y}    <- top
12 //!       /  \
13 //!    {X}    {Y}
14 //!       \  /
15 //!        {}      <- bottom
16 //!
17 //! ```
18 //!
19 //! The defining characteristic of a lattice—the one that differentiates it from a [partially
20 //! ordered set][poset]—is the existence of a *unique* least upper and greatest lower bound for
21 //! every pair of elements. The lattice join operator (`∨`) returns the least upper bound, and the
22 //! lattice meet operator (`∧`) returns the greatest lower bound. Types that implement one operator
23 //! but not the other are known as semilattices. Dataflow analysis only uses the join operator and
24 //! will work with any join-semilattice, but both should be specified when possible.
25 //!
26 //! ## `PartialOrd`
27 //!
28 //! Given that they represent partially ordered sets, you may be surprised that [`JoinSemiLattice`]
29 //! and [`MeetSemiLattice`] do not have [`PartialOrd`] as a supertrait. This
30 //! is because most standard library types use lexicographic ordering instead of set inclusion for
31 //! their `PartialOrd` impl. Since we do not actually need to compare lattice elements to run a
32 //! dataflow analysis, there's no need for a newtype wrapper with a custom `PartialOrd` impl. The
33 //! only benefit would be the ability to check that the least upper (or greatest lower) bound
34 //! returned by the lattice join (or meet) operator was in fact greater (or lower) than the inputs.
35 //!
36 //! [lattices]: https://en.wikipedia.org/wiki/Lattice_(order)
37 //! [set inclusion]: https://en.wikipedia.org/wiki/Subset
38 //! [Hasse diagram]: https://en.wikipedia.org/wiki/Hasse_diagram
39 //! [poset]: https://en.wikipedia.org/wiki/Partially_ordered_set
40 
41 use crate::framework::BitSetExt;
42 use rustc_index::bit_set::{BitSet, ChunkedBitSet, HybridBitSet};
43 use rustc_index::{Idx, IndexVec};
44 use std::iter;
45 
46 /// A [partially ordered set][poset] that has a [least upper bound][lub] for any pair of elements
47 /// in the set.
48 ///
49 /// [lub]: https://en.wikipedia.org/wiki/Infimum_and_supremum
50 /// [poset]: https://en.wikipedia.org/wiki/Partially_ordered_set
51 pub trait JoinSemiLattice: Eq {
52     /// Computes the least upper bound of two elements, storing the result in `self` and returning
53     /// `true` if `self` has changed.
54     ///
55     /// The lattice join operator is abbreviated as `∨`.
join(&mut self, other: &Self) -> bool56     fn join(&mut self, other: &Self) -> bool;
57 }
58 
59 /// A [partially ordered set][poset] that has a [greatest lower bound][glb] for any pair of
60 /// elements in the set.
61 ///
62 /// Dataflow analyses only require that their domains implement [`JoinSemiLattice`], not
63 /// `MeetSemiLattice`. However, types that will be used as dataflow domains should implement both
64 /// so that they can be used with [`Dual`].
65 ///
66 /// [glb]: https://en.wikipedia.org/wiki/Infimum_and_supremum
67 /// [poset]: https://en.wikipedia.org/wiki/Partially_ordered_set
68 pub trait MeetSemiLattice: Eq {
69     /// Computes the greatest lower bound of two elements, storing the result in `self` and
70     /// returning `true` if `self` has changed.
71     ///
72     /// The lattice meet operator is abbreviated as `∧`.
meet(&mut self, other: &Self) -> bool73     fn meet(&mut self, other: &Self) -> bool;
74 }
75 
76 /// A set that has a "bottom" element, which is less than or equal to any other element.
77 pub trait HasBottom {
78     const BOTTOM: Self;
79 }
80 
81 /// A set that has a "top" element, which is greater than or equal to any other element.
82 pub trait HasTop {
83     const TOP: Self;
84 }
85 
86 /// A `bool` is a "two-point" lattice with `true` as the top element and `false` as the bottom:
87 ///
88 /// ```text
89 ///      true
90 ///        |
91 ///      false
92 /// ```
93 impl JoinSemiLattice for bool {
join(&mut self, other: &Self) -> bool94     fn join(&mut self, other: &Self) -> bool {
95         if let (false, true) = (*self, *other) {
96             *self = true;
97             return true;
98         }
99 
100         false
101     }
102 }
103 
104 impl MeetSemiLattice for bool {
meet(&mut self, other: &Self) -> bool105     fn meet(&mut self, other: &Self) -> bool {
106         if let (true, false) = (*self, *other) {
107             *self = false;
108             return true;
109         }
110 
111         false
112     }
113 }
114 
115 impl HasBottom for bool {
116     const BOTTOM: Self = false;
117 }
118 
119 impl HasTop for bool {
120     const TOP: Self = true;
121 }
122 
123 /// A tuple (or list) of lattices is itself a lattice whose least upper bound is the concatenation
124 /// of the least upper bounds of each element of the tuple (or list).
125 ///
126 /// In other words:
127 ///     (A₀, A₁, ..., Aₙ) ∨ (B₀, B₁, ..., Bₙ) = (A₀∨B₀, A₁∨B₁, ..., Aₙ∨Bₙ)
128 impl<I: Idx, T: JoinSemiLattice> JoinSemiLattice for IndexVec<I, T> {
join(&mut self, other: &Self) -> bool129     fn join(&mut self, other: &Self) -> bool {
130         assert_eq!(self.len(), other.len());
131 
132         let mut changed = false;
133         for (a, b) in iter::zip(self, other) {
134             changed |= a.join(b);
135         }
136         changed
137     }
138 }
139 
140 impl<I: Idx, T: MeetSemiLattice> MeetSemiLattice for IndexVec<I, T> {
meet(&mut self, other: &Self) -> bool141     fn meet(&mut self, other: &Self) -> bool {
142         assert_eq!(self.len(), other.len());
143 
144         let mut changed = false;
145         for (a, b) in iter::zip(self, other) {
146             changed |= a.meet(b);
147         }
148         changed
149     }
150 }
151 
152 /// A `BitSet` represents the lattice formed by the powerset of all possible values of
153 /// the index type `T` ordered by inclusion. Equivalently, it is a tuple of "two-point" lattices,
154 /// one for each possible value of `T`.
155 impl<T: Idx> JoinSemiLattice for BitSet<T> {
join(&mut self, other: &Self) -> bool156     fn join(&mut self, other: &Self) -> bool {
157         self.union(other)
158     }
159 }
160 
161 impl<T: Idx> MeetSemiLattice for BitSet<T> {
meet(&mut self, other: &Self) -> bool162     fn meet(&mut self, other: &Self) -> bool {
163         self.intersect(other)
164     }
165 }
166 
167 impl<T: Idx> JoinSemiLattice for ChunkedBitSet<T> {
join(&mut self, other: &Self) -> bool168     fn join(&mut self, other: &Self) -> bool {
169         self.union(other)
170     }
171 }
172 
173 impl<T: Idx> MeetSemiLattice for ChunkedBitSet<T> {
meet(&mut self, other: &Self) -> bool174     fn meet(&mut self, other: &Self) -> bool {
175         self.intersect(other)
176     }
177 }
178 
179 /// The counterpart of a given semilattice `T` using the [inverse order].
180 ///
181 /// The dual of a join-semilattice is a meet-semilattice and vice versa. For example, the dual of a
182 /// powerset has the empty set as its top element and the full set as its bottom element and uses
183 /// set *intersection* as its join operator.
184 ///
185 /// [inverse order]: https://en.wikipedia.org/wiki/Duality_(order_theory)
186 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
187 pub struct Dual<T>(pub T);
188 
189 impl<T: Idx> BitSetExt<T> for Dual<BitSet<T>> {
domain_size(&self) -> usize190     fn domain_size(&self) -> usize {
191         self.0.domain_size()
192     }
193 
contains(&self, elem: T) -> bool194     fn contains(&self, elem: T) -> bool {
195         self.0.contains(elem)
196     }
197 
union(&mut self, other: &HybridBitSet<T>)198     fn union(&mut self, other: &HybridBitSet<T>) {
199         self.0.union(other);
200     }
201 
subtract(&mut self, other: &HybridBitSet<T>)202     fn subtract(&mut self, other: &HybridBitSet<T>) {
203         self.0.subtract(other);
204     }
205 }
206 
207 impl<T: MeetSemiLattice> JoinSemiLattice for Dual<T> {
join(&mut self, other: &Self) -> bool208     fn join(&mut self, other: &Self) -> bool {
209         self.0.meet(&other.0)
210     }
211 }
212 
213 impl<T: JoinSemiLattice> MeetSemiLattice for Dual<T> {
meet(&mut self, other: &Self) -> bool214     fn meet(&mut self, other: &Self) -> bool {
215         self.0.join(&other.0)
216     }
217 }
218 
219 /// Extends a type `T` with top and bottom elements to make it a partially ordered set in which no
220 /// value of `T` is comparable with any other.
221 ///
222 /// A flat set has the following [Hasse diagram]:
223 ///
224 /// ```text
225 ///          top
226 ///  / ... / /  \ \ ... \
227 /// all possible values of `T`
228 ///  \ ... \ \  / / ... /
229 ///         bottom
230 /// ```
231 ///
232 /// [Hasse diagram]: https://en.wikipedia.org/wiki/Hasse_diagram
233 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
234 pub enum FlatSet<T> {
235     Bottom,
236     Elem(T),
237     Top,
238 }
239 
240 impl<T: Clone + Eq> JoinSemiLattice for FlatSet<T> {
join(&mut self, other: &Self) -> bool241     fn join(&mut self, other: &Self) -> bool {
242         let result = match (&*self, other) {
243             (Self::Top, _) | (_, Self::Bottom) => return false,
244             (Self::Elem(a), Self::Elem(b)) if a == b => return false,
245 
246             (Self::Bottom, Self::Elem(x)) => Self::Elem(x.clone()),
247 
248             _ => Self::Top,
249         };
250 
251         *self = result;
252         true
253     }
254 }
255 
256 impl<T: Clone + Eq> MeetSemiLattice for FlatSet<T> {
meet(&mut self, other: &Self) -> bool257     fn meet(&mut self, other: &Self) -> bool {
258         let result = match (&*self, other) {
259             (Self::Bottom, _) | (_, Self::Top) => return false,
260             (Self::Elem(ref a), Self::Elem(ref b)) if a == b => return false,
261 
262             (Self::Top, Self::Elem(ref x)) => Self::Elem(x.clone()),
263 
264             _ => Self::Bottom,
265         };
266 
267         *self = result;
268         true
269     }
270 }
271 
272 impl<T> HasBottom for FlatSet<T> {
273     const BOTTOM: Self = Self::Bottom;
274 }
275 
276 impl<T> HasTop for FlatSet<T> {
277     const TOP: Self = Self::Top;
278 }
279