smt_scope/analysis/graph/analysis/
reconnect.rs

1use std::collections::hash_map::Entry;
2
3#[cfg(feature = "mem_dbg")]
4use mem_dbg::{MemDbg, MemSize};
5
6use fxhash::FxHashSet;
7
8use crate::{
9    analysis::{raw::Node, InstGraph, RawNodeIndex},
10    BoxSlice, FxHashMap, TiVec,
11};
12
13use super::run::TopoAnalysis;
14
15pub trait ReachKind: Copy + core::fmt::Debug + From<bool> + Into<bool> {
16    fn current(node: &Node) -> Self;
17    fn value(self) -> bool {
18        self.into()
19    }
20}
21
22macro_rules! reach_kind {
23    ($name:ident($node:ident => $e:expr)) => {
24        #[cfg_attr(feature = "mem_dbg", derive(MemSize, MemDbg))]
25        #[cfg_attr(feature = "mem_dbg", copy_type)]
26        #[derive(Debug, Clone, Copy)]
27        pub struct $name(bool);
28        impl ReachKind for $name {
29            fn current($node: &Node) -> Self {
30                Self($e)
31            }
32        }
33        impl From<bool> for $name {
34            fn from(b: bool) -> Self {
35                Self(b)
36            }
37        }
38        impl From<$name> for bool {
39            fn from(b: $name) -> bool {
40                b.0
41            }
42        }
43    };
44}
45
46reach_kind!(ReachVisible(node => node.visible()));
47reach_kind!(ReachNonDisabled(node => !node.disabled()));
48
49/// If `VIS` is true, then it checks if a visible node is reachable from this
50/// one, otherwise it checks if any non-disabled node is reachable.
51#[derive(Clone, Copy)]
52pub struct BwdReachableAnalysis<Kind: ReachKind>(core::marker::PhantomData<Kind>);
53impl<Kind: ReachKind> Default for BwdReachableAnalysis<Kind> {
54    fn default() -> Self {
55        Self(Default::default())
56    }
57}
58
59impl<Kind: ReachKind> TopoAnalysis<false, false> for BwdReachableAnalysis<Kind> {
60    type Value = Kind;
61
62    fn collect<'a, 'n, T: Iterator<Item = (RawNodeIndex, &'n Self::Value)>>(
63        &mut self,
64        _graph: &'a InstGraph,
65        _cidx: RawNodeIndex,
66        node: &'a Node,
67        from_all: impl Fn() -> T,
68    ) -> Self::Value
69    where
70        Self::Value: 'n,
71    {
72        let curr = Kind::current(node).value();
73        let reach = curr || from_all().any(|(_, &b)| b.value());
74        reach.into()
75    }
76}
77
78/// Looks for tuples of 4 indices:
79///  - `from`: a visible node
80///  - `from_child`: (optional) a non-visible child of `from`
81///  - `to_parent`: a non-visible node reachable from `from_child`
82///    but not reachable by any visible node also reachable from `from_child`
83///    (note that it's possible for `from_child == to_parent`)
84///  - `to`: a visible child of `to_parent`
85///
86/// The `to` is implicit in the index which we used to reach the
87/// `TopoAnalysis::Value`.
88pub struct ReconnectAnalysis(pub TiVec<RawNodeIndex, ReachVisible>);
89
90#[derive(Debug, Default)]
91pub struct ReconnectData {
92    /// Edges, along with a path if they have only a single one possible.
93    pub above: FxHashMap<ReconnectEdge, Option<BoxSlice<RawNodeIndex>>>,
94    /// These should not be added to `above` since they can be transitively
95    /// reached.
96    pub transitive: FxHashSet<ReconnectFrom>,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
100pub struct ReconnectFrom {
101    /// A visible node to reconnect from.
102    pub visible: RawNodeIndex,
103    /// A non-visible child of `from_v`, set only if
104    /// `from_v.reconnect_child()`.
105    pub hidden: Option<RawNodeIndex>,
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
109pub struct ReconnectEdge {
110    pub from: ReconnectFrom,
111    /// A non-visible parent of the current visible node, set only if
112    /// `to_v.reconnect_parents()`.
113    pub to_h: RawNodeIndex,
114    /// Did we walk through any `hidden` nodes on a path between `from_v` and
115    /// `to_v`?
116    pub indirect: bool,
117}
118
119impl ReconnectEdge {
120    /// Create a special `ReconnectEdge` representing a connection between two
121    /// visible nodes.
122    fn direct_visible(from: RawNodeIndex) -> Self {
123        Self {
124            from: ReconnectFrom {
125                visible: from,
126                hidden: None,
127            },
128            to_h: from,
129            indirect: false,
130        }
131    }
132
133    pub fn is_direct_visible(&self) -> bool {
134        self.from.visible == self.to_h
135    }
136
137    fn set_indirect(self, indirect: bool) -> Self {
138        Self {
139            indirect: indirect || self.indirect,
140            ..self
141        }
142    }
143
144    fn set_to_h(self, to_h: RawNodeIndex) -> Self {
145        debug_assert_eq!(self.from.visible, self.to_h);
146        debug_assert_ne!(self.to_h, to_h);
147        Self { to_h, ..self }
148    }
149}
150
151impl ReconnectData {
152    fn above(&self) -> impl Iterator<Item = (ReconnectEdge, Option<&[RawNodeIndex]>)> + '_ {
153        self.above
154            .iter()
155            .map(|(edge, path)| (*edge, path.as_deref()))
156    }
157
158    pub fn insert(
159        &mut self,
160        edge: ReconnectEdge,
161        path: Option<&[RawNodeIndex]>,
162        prev: Option<RawNodeIndex>,
163    ) {
164        match self.above.entry(edge) {
165            Entry::Occupied(o) => *o.into_mut() = None,
166            Entry::Vacant(v) => {
167                v.insert(path.map(|p| p.iter().copied().chain(prev).collect()));
168            }
169        }
170    }
171
172    pub fn extend(&mut self, other: &Self, prev: RawNodeIndex, hidden: bool) {
173        // Remove any that we've already added but should not have.
174        self.above
175            .retain(|above, _| !other.transitive.contains(&above.from));
176        // Add any that we can add, this is after the above line since `other`
177        // may contain nodes which it has itself forbidden.
178        for (edge, path) in other.above() {
179            if self.transitive.contains(&edge.from) {
180                continue;
181            }
182            self.insert(edge.set_indirect(hidden), path, Some(prev));
183        }
184        // Add the transitive ones which forbid adding.
185        self.transitive.extend(other.transitive.iter().copied());
186    }
187
188    pub fn reached_visible(&mut self, other: &Self, prev: RawNodeIndex) {
189        let transitive = other.above().map(|(above, _)| above.from);
190        self.transitive.extend(transitive);
191        for (edge, path) in other.above() {
192            self.insert(edge.set_to_h(prev), path, None);
193        }
194    }
195}
196
197impl TopoAnalysis<true, false> for ReconnectAnalysis {
198    type Value = ReconnectData;
199
200    fn collect<'a, 'n, T: Iterator<Item = (RawNodeIndex, &'n Self::Value)>>(
201        &mut self,
202        graph: &'a InstGraph,
203        cidx: RawNodeIndex,
204        node: &'a Node,
205        from_all: impl Fn() -> T,
206    ) -> Self::Value
207    where
208        Self::Value: 'n,
209    {
210        let mut data = Self::Value::default();
211        if !self.0[cidx].value() {
212            return data;
213        }
214
215        let visible = node.visible();
216        let hidden = node.hidden();
217        for (fidx, from_data) in from_all() {
218            let from = &graph.raw[fidx];
219            match (from.visible(), visible) {
220                (false, false) => {
221                    data.extend(from_data, fidx, hidden);
222                }
223                (true, false) => {
224                    let cidx = Some(cidx).filter(|_| from.kind().reconnect_child(node.kind()));
225                    let edge = ReconnectEdge {
226                        from: ReconnectFrom {
227                            visible: fidx,
228                            hidden: cidx,
229                        },
230                        to_h: fidx,
231                        indirect: hidden,
232                    };
233                    data.insert(edge, Some(&[]), None);
234                    data.transitive.extend(from_data.transitive.iter().copied());
235                }
236                (false, true) => {
237                    data.reached_visible(from_data, fidx);
238                }
239                (true, true) => {
240                    data.insert(ReconnectEdge::direct_visible(fidx), Some(&[]), None);
241                    data.transitive.extend(from_data.transitive.iter().copied());
242                }
243            }
244        }
245
246        data
247    }
248}