Skip to main content

rustc_mir_dataflow/framework/
lattice.rs

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 it represents a partially ordered set, you may be surprised that [`JoinSemiLattice`]
29//! does 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
41use rustc_index::Idx;
42use rustc_index::bit_set::{DenseBitSet, MixedBitSet};
43
44use crate::framework::BitSetExt;
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
51pub 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 `∨`.
56    fn join(&mut self, other: &Self) -> bool;
57}
58
59/// A set that has a "bottom" element, which is less than or equal to any other element.
60pub trait HasBottom {
61    const BOTTOM: Self;
62
63    fn is_bottom(&self) -> bool;
64}
65
66/// A set that has a "top" element, which is greater than or equal to any other element.
67pub trait HasTop {
68    const TOP: Self;
69}
70
71/// A `DenseBitSet` represents the lattice formed by the powerset of all possible values of the
72/// index type `T` ordered by inclusion. Equivalently, it is a tuple of "two-point" lattices, one
73/// for each possible value of `T`.
74impl<T: Idx> JoinSemiLattice for DenseBitSet<T> {
75    fn join(&mut self, other: &Self) -> bool {
76        self.union(other)
77    }
78}
79
80impl<T: Idx> JoinSemiLattice for MixedBitSet<T> {
81    fn join(&mut self, other: &Self) -> bool {
82        self.union(other)
83    }
84}
85
86/// Extends a type `T` with top and bottom elements to make it a partially ordered set in which no
87/// value of `T` is comparable with any other.
88///
89/// A flat set has the following [Hasse diagram]:
90///
91/// ```text
92///          top
93///  / ... / /  \ \ ... \
94/// all possible values of `T`
95///  \ ... \ \  / / ... /
96///         bottom
97/// ```
98///
99/// [Hasse diagram]: https://en.wikipedia.org/wiki/Hasse_diagram
100#[derive(#[automatically_derived]
impl<T: ::core::clone::Clone> ::core::clone::Clone for FlatSet<T> {
    #[inline]
    fn clone(&self) -> Self {
        match self {
            Self::Bottom => FlatSet::Bottom,
            Self::Elem(__self_0) =>
                FlatSet::Elem(::core::clone::Clone::clone(__self_0)),
            Self::Top => FlatSet::Top,
        }
    }
}Clone, #[automatically_derived]
impl<T: ::core::marker::Copy> ::core::marker::Copy for FlatSet<T> { }Copy, #[automatically_derived]
impl<T: ::core::fmt::Debug> ::core::fmt::Debug for FlatSet<T> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            Self::Bottom => ::core::fmt::Formatter::write_str(f, "Bottom"),
            Self::Elem(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f, "Elem",
                    &__self_0),
            Self::Top => ::core::fmt::Formatter::write_str(f, "Top"),
        }
    }
}Debug, #[automatically_derived]
impl<T: ::core::cmp::PartialEq> ::core::marker::StructuralPartialEq for
    FlatSet<T> {
}
#[automatically_derived]
impl<T: ::core::cmp::PartialEq> ::core::cmp::PartialEq for FlatSet<T> {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
                ::core::intrinsics::discriminant_value(other) &&
            match (self, other) {
                (Self::Elem(__self_0), Self::Elem(__arg1_0)) =>
                    __self_0 == __arg1_0,
                _ => true,
            }
    }
}PartialEq, #[automatically_derived]
impl<T: ::core::cmp::Eq> ::core::cmp::Eq for FlatSet<T> {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) { let _: ::core::cmp::AssertParamIsEq<T>; }
}Eq)]
101pub enum FlatSet<T> {
102    Bottom,
103    Elem(T),
104    Top,
105}
106
107impl<T> FlatSet<T> {
108    #[inline]
109    pub fn and_then<U>(self, f: impl FnOnce(T) -> FlatSet<U>) -> FlatSet<U> {
110        match self {
111            FlatSet::Elem(elem) => f(elem),
112            FlatSet::Bottom => FlatSet::Bottom,
113            FlatSet::Top => FlatSet::Top,
114        }
115    }
116}
117
118impl<T: Clone + Eq> JoinSemiLattice for FlatSet<T> {
119    fn join(&mut self, other: &Self) -> bool {
120        let result = match (&*self, other) {
121            (Self::Top, _) | (_, Self::Bottom) => return false,
122            (Self::Elem(a), Self::Elem(b)) if a == b => return false,
123
124            (Self::Bottom, Self::Elem(x)) => Self::Elem(x.clone()),
125
126            _ => Self::Top,
127        };
128
129        *self = result;
130        true
131    }
132}
133
134impl<T> HasBottom for FlatSet<T> {
135    const BOTTOM: Self = Self::Bottom;
136
137    fn is_bottom(&self) -> bool {
138        #[allow(non_exhaustive_omitted_patterns)] match self {
    Self::Bottom => true,
    _ => false,
}matches!(self, Self::Bottom)
139    }
140}
141
142impl<T> HasTop for FlatSet<T> {
143    const TOP: Self = Self::Top;
144}
145
146/// Extend a lattice with a bottom value to represent an unreachable execution.
147///
148/// The only useful action on an unreachable state is joining it with a reachable one to make it
149/// reachable. All other actions, gen/kill for instance, are no-ops.
150#[derive(#[automatically_derived]
impl<T: ::core::cmp::PartialEq> ::core::marker::StructuralPartialEq for
    MaybeReachable<T> {
}
#[automatically_derived]
impl<T: ::core::cmp::PartialEq> ::core::cmp::PartialEq for MaybeReachable<T> {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
                ::core::intrinsics::discriminant_value(other) &&
            match (self, other) {
                (Self::Reachable(__self_0), Self::Reachable(__arg1_0)) =>
                    __self_0 == __arg1_0,
                _ => true,
            }
    }
}PartialEq, #[automatically_derived]
impl<T: ::core::cmp::Eq> ::core::cmp::Eq for MaybeReachable<T> {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) { let _: ::core::cmp::AssertParamIsEq<T>; }
}Eq, #[automatically_derived]
impl<T: ::core::fmt::Debug> ::core::fmt::Debug for MaybeReachable<T> {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            Self::Unreachable =>
                ::core::fmt::Formatter::write_str(f, "Unreachable"),
            Self::Reachable(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "Reachable", &__self_0),
        }
    }
}Debug)]
151pub enum MaybeReachable<T> {
152    Unreachable,
153    Reachable(T),
154}
155
156impl<T> MaybeReachable<T> {
157    pub fn is_reachable(&self) -> bool {
158        #[allow(non_exhaustive_omitted_patterns)] match self {
    MaybeReachable::Reachable(_) => true,
    _ => false,
}matches!(self, MaybeReachable::Reachable(_))
159    }
160}
161
162impl<S> MaybeReachable<S> {
163    /// Return whether the current state contains the given element. If the state is unreachable,
164    /// it does no contain anything.
165    pub fn contains<T>(&self, elem: T) -> bool
166    where
167        S: BitSetExt<T>,
168    {
169        match self {
170            MaybeReachable::Unreachable => false,
171            MaybeReachable::Reachable(set) => set.contains(elem),
172        }
173    }
174}
175
176impl<T, S: BitSetExt<T>> BitSetExt<T> for MaybeReachable<S> {
177    fn contains(&self, elem: T) -> bool {
178        self.contains(elem)
179    }
180}
181
182impl<V: Clone> Clone for MaybeReachable<V> {
183    fn clone(&self) -> Self {
184        match self {
185            MaybeReachable::Reachable(x) => MaybeReachable::Reachable(x.clone()),
186            MaybeReachable::Unreachable => MaybeReachable::Unreachable,
187        }
188    }
189
190    fn clone_from(&mut self, source: &Self) {
191        match (&mut *self, source) {
192            (MaybeReachable::Reachable(x), MaybeReachable::Reachable(y)) => {
193                x.clone_from(y);
194            }
195            _ => *self = source.clone(),
196        }
197    }
198}
199
200impl<T: JoinSemiLattice + Clone> JoinSemiLattice for MaybeReachable<T> {
201    fn join(&mut self, other: &Self) -> bool {
202        // Unreachable acts as a bottom.
203        match (&mut *self, &other) {
204            (_, MaybeReachable::Unreachable) => false,
205            (MaybeReachable::Unreachable, _) => {
206                *self = other.clone();
207                true
208            }
209            (MaybeReachable::Reachable(this), MaybeReachable::Reachable(other)) => this.join(other),
210        }
211    }
212}