patronus/system/
analysis.rs

1// Copyright 2023-2024 The Regents of the University of California
2// Copyright 2024 Cornell University
3// released under BSD 3-Clause License
4// author: Kevin Laeufer <laeufer@cornell.edu>
5
6use super::{State, TransitionSystem};
7use crate::expr::*;
8use rustc_hash::FxHashSet;
9
10pub type UseCountInt = u16;
11
12pub fn count_expr_uses(ctx: &Context, sys: &TransitionSystem) -> Vec<UseCountInt> {
13    internal_count_expr_uses(ctx, sys, false)
14}
15
16fn internal_count_expr_uses(
17    ctx: &Context,
18    sys: &TransitionSystem,
19    ignore_init: bool,
20) -> Vec<UseCountInt> {
21    let mut use_count: DenseExprMetaData<UseCountInt> = DenseExprMetaData::default();
22    let states = sys.state_map();
23    // all observable outputs are our roots
24    let mut todo = sys.get_assert_assume_output_exprs();
25    // ensure that all roots start with count 1
26    for expr in todo.iter() {
27        use_count[*expr] = 1;
28    }
29
30    while let Some(expr) = todo.pop() {
31        if let Some(state) = states.get(&expr) {
32            // for states, we also want to mark the initial and the next expression as used
33            if let Some(init) = state.init {
34                if !ignore_init {
35                    let count = use_count[init];
36                    if count == 0 {
37                        use_count[init] = 1;
38                        todo.push(init);
39                    }
40                }
41            }
42            if let Some(next) = state.next {
43                let count = use_count[next];
44                if count == 0 {
45                    use_count[next] = 1;
46                    todo.push(next);
47                }
48            }
49        }
50
51        count_uses(ctx, expr, &mut use_count, &mut todo);
52    }
53
54    use_count.into_vec()
55}
56
57/// Generates a list of all inputs and states that can influence the `root` expression.
58pub fn cone_of_influence(ctx: &Context, sys: &TransitionSystem, root: ExprRef) -> Vec<ExprRef> {
59    // we need to follow next and init expressions for all states
60    cone_of_influence_impl(ctx, sys, root, true, true)
61}
62
63/// Generates a list of all inputs and states that can influence the `root` expression, while the system is being initialized.
64pub fn cone_of_influence_init(
65    ctx: &Context,
66    sys: &TransitionSystem,
67    root: ExprRef,
68) -> Vec<ExprRef> {
69    cone_of_influence_impl(ctx, sys, root, false, true)
70}
71
72/// Generates a list of all inputs and states that can influence the `root` expression, combinationally.
73pub fn cone_of_influence_comb(
74    ctx: &Context,
75    sys: &TransitionSystem,
76    root: ExprRef,
77) -> Vec<ExprRef> {
78    cone_of_influence_impl(ctx, sys, root, false, false)
79}
80
81/// Internal implementation which allows us to define how we follow states.
82fn cone_of_influence_impl(
83    ctx: &Context,
84    sys: &TransitionSystem,
85    root: ExprRef,
86    follow_next: bool,
87    follow_init: bool,
88) -> Vec<ExprRef> {
89    let mut out = vec![];
90    let mut todo = vec![root];
91    let mut visited = DenseExprSet::default();
92    let states = sys.state_map();
93    let inputs = sys.input_set();
94
95    while let Some(expr_ref) = todo.pop() {
96        if visited.contains(&expr_ref) {
97            continue;
98        }
99
100        // make sure children are visited
101        let expr = &ctx[expr_ref];
102        expr.for_each_child(|c| {
103            if !visited.contains(c) {
104                todo.push(*c);
105            }
106        });
107
108        // for states, we might want to follow the next and init expressions
109        if let Some(state) = states.get(&expr_ref) {
110            if follow_init {
111                if let Some(c) = state.init {
112                    if !visited.contains(&c) {
113                        todo.push(c);
114                    }
115                }
116            }
117            if follow_next {
118                if let Some(c) = state.next {
119                    if !visited.contains(&c) {
120                        todo.push(c);
121                    }
122                }
123            }
124        }
125
126        // check to see if this is a state or input
127        if expr.is_symbol() && (states.contains_key(&expr_ref) || inputs.contains(&expr_ref)) {
128            out.push(expr_ref);
129        }
130        visited.insert(expr_ref);
131    }
132
133    out
134}
135
136/// Counts how often expressions are used. This version _does not_ follow any state symbols.
137fn simple_count_expr_uses(ctx: &Context, roots: Vec<ExprRef>) -> impl ExprMap<UseCountInt> {
138    let mut use_count = SparseExprMap::default();
139    let mut todo = roots;
140
141    // ensure that all roots start with count 1
142    for expr in todo.iter() {
143        use_count[*expr] = 1;
144    }
145
146    while let Some(expr) = todo.pop() {
147        count_uses(ctx, expr, &mut use_count, &mut todo);
148    }
149
150    use_count
151}
152
153#[inline]
154fn count_uses(
155    ctx: &Context,
156    expr: ExprRef,
157    use_count: &mut impl ExprMap<UseCountInt>,
158    todo: &mut Vec<ExprRef>,
159) {
160    ctx[expr].for_each_child(|child| {
161        let count = use_count[*child];
162        let is_first_use = count == 0;
163        use_count[*child] = count.saturating_add(1);
164        if is_first_use {
165            todo.push(*child);
166        }
167    });
168}
169
170#[derive(Debug, Clone)]
171pub struct RootInfo {
172    pub expr: ExprRef,
173    pub name: Option<StringRef>,
174    pub uses: Uses,
175    pub kind: SerializeSignalKind,
176}
177
178impl RootInfo {
179    fn new(expr: ExprRef, name: Option<StringRef>, uses: Uses, kind: SerializeSignalKind) -> Self {
180        Self {
181            expr,
182            name,
183            uses,
184            kind,
185        }
186    }
187}
188
189#[derive(Debug, Clone, Copy, PartialEq, Eq)]
190pub enum SerializeSignalKind {
191    BadState,
192    Constraint,
193    Output,
194    Input,
195    StateInit,
196    StateNext,
197    None,
198}
199
200/// Indicates which context an expression is used in.
201#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)]
202pub struct Uses {
203    pub next: UseCountInt,
204    pub init: UseCountInt,
205    pub other: UseCountInt,
206}
207
208impl Uses {
209    #[inline]
210    pub fn is_used(&self) -> bool {
211        self.total() > 0
212    }
213    #[inline]
214    pub fn total(&self) -> UseCountInt {
215        self.next
216            .saturating_add(self.init)
217            .saturating_add(self.other)
218    }
219}
220
221/// analyzes expression uses to determine in what contexts the expression is used.
222fn determine_simples_uses(
223    ctx: &Context,
224    sys: &TransitionSystem,
225    include_output: bool,
226) -> impl ExprMap<Uses> {
227    let init = simple_count_expr_uses(ctx, sys.get_init_exprs());
228    let next = simple_count_expr_uses(ctx, sys.get_next_exprs());
229    let other = if include_output {
230        simple_count_expr_uses(ctx, sys.get_assert_assume_output_exprs())
231    } else {
232        simple_count_expr_uses(ctx, sys.get_assert_assume_exprs())
233    };
234
235    // collect all expressions
236    let mut all_expr = FxHashSet::from_iter(other.non_default_value_keys());
237    all_expr.extend(init.non_default_value_keys());
238    all_expr.extend(next.non_default_value_keys());
239
240    // generate combined map
241    let mut out = SparseExprMap::default();
242    for expr in all_expr.into_iter() {
243        out[expr] = Uses {
244            next: next[expr],
245            init: init[expr],
246            other: other[expr],
247        };
248    }
249    out
250}
251
252/// Meta-data that helps with serialization, no matter if serializing to btor, our custom
253/// "human-readable" format or SMTLib.
254#[derive(Debug, Default, Clone)]
255pub struct SerializeMeta {
256    pub signal_order: Vec<RootInfo>,
257}
258
259pub fn analyze_for_serialization(
260    ctx: &Context,
261    sys: &TransitionSystem,
262    include_outputs: bool,
263) -> SerializeMeta {
264    let uses = determine_simples_uses(ctx, sys, include_outputs);
265
266    // always add all inputs first to the signal order
267    let mut signal_order = Vec::new();
268    for &input in sys.inputs.iter() {
269        signal_order.push(RootInfo::new(
270            input,
271            Some(ctx[input].get_symbol_name_ref().unwrap()),
272            uses[input],
273            SerializeSignalKind::Input,
274        ));
275    }
276
277    // keep track of which signals have been processed
278    let mut visited = DenseExprSet::default();
279
280    // add all roots and give them a large other count
281    let mut todo: Vec<RootInfo> = vec![];
282    if include_outputs {
283        todo.extend(sys.outputs.iter().map(|o| {
284            RootInfo::new(
285                o.expr,
286                Some(o.name),
287                uses[o.expr],
288                SerializeSignalKind::Output,
289            )
290        }));
291    }
292    todo.extend(sys.constraints.iter().map(|&e| {
293        RootInfo::new(
294            e,
295            find_name(ctx, sys, e),
296            uses[e],
297            SerializeSignalKind::Constraint,
298        )
299    }));
300    todo.extend(sys.bad_states.iter().map(|&e| {
301        RootInfo::new(
302            e,
303            find_name(ctx, sys, e),
304            uses[e],
305            SerializeSignalKind::BadState,
306        )
307    }));
308
309    // add state root expressions
310    todo.extend(sys.get_init_exprs().iter().map(|&e| {
311        RootInfo::new(
312            e,
313            find_name(ctx, sys, e),
314            uses[e],
315            SerializeSignalKind::StateInit,
316        )
317    }));
318    todo.extend(sys.get_next_exprs().iter().map(|&e| {
319        RootInfo::new(
320            e,
321            find_name(ctx, sys, e),
322            uses[e],
323            SerializeSignalKind::StateNext,
324        )
325    }));
326
327    // visit roots in the order in which they were declared
328    todo.reverse();
329
330    // visit expressions
331    while let Some(info) = todo.pop() {
332        if visited.contains(&info.expr) {
333            continue;
334        }
335
336        let expr = &ctx[info.expr];
337
338        // check to see if all children are done
339        let mut all_done = true;
340        let mut num_children = 0;
341        expr.for_each_child(|c| {
342            if !visited.contains(c) {
343                if all_done {
344                    todo.push(info.clone()); // return expression to the todo list
345                }
346                all_done = false;
347                // we need to visit the child first
348                let child_info = RootInfo::new(*c, None, uses[*c], SerializeSignalKind::None);
349                todo.push(child_info);
350            }
351            num_children += 1;
352        });
353
354        if !all_done {
355            continue;
356        }
357
358        // add to signal order if applicable
359        let is_output_like = matches!(
360            info.kind,
361            SerializeSignalKind::Output
362                | SerializeSignalKind::Constraint
363                | SerializeSignalKind::BadState
364        );
365        let is_input = info.kind == SerializeSignalKind::Input;
366        if num_children > 0 || is_output_like || is_input {
367            let used_multiple_times = info.uses.total() > 1;
368            if is_output_like || used_multiple_times {
369                signal_order.push(info.clone());
370            }
371        }
372        visited.insert(info.expr);
373    }
374
375    SerializeMeta { signal_order }
376}
377
378#[inline]
379fn find_name(ctx: &Context, sys: &TransitionSystem, e: ExprRef) -> Option<StringRef> {
380    ctx[e].get_symbol_name_ref().or_else(|| sys.names[e])
381}
382
383impl ForEachChild<ExprRef> for State {
384    fn for_each_child(&self, mut visitor: impl FnMut(&ExprRef)) {
385        if let Some(next) = &self.next {
386            visitor(next);
387        }
388        if let Some(init) = &self.init {
389            visitor(init);
390        }
391    }
392
393    fn num_children(&self) -> usize {
394        self.next.is_some() as usize + self.init.is_some() as usize
395    }
396}
397
398#[cfg(test)]
399mod tests {
400    use super::*;
401    use crate::btor2;
402
403    fn format_symbol_list(ctx: &Context, symbols: &[ExprRef]) -> String {
404        let v: Vec<_> = symbols
405            .iter()
406            .map(|s| ctx.get_symbol_name(*s).unwrap())
407            .collect();
408        v.join(", ")
409    }
410
411    #[test]
412    fn test_cone_of_influence() {
413        let (ctx, sys) = btor2::parse_file("../inputs/unittest/delay.btor").unwrap();
414        let reg0 = sys.get_state_by_name(&ctx, "reg0").unwrap().symbol;
415        let reg1 = sys.get_state_by_name(&ctx, "reg1").unwrap().symbol;
416        let cone0 = cone_of_influence(&ctx, &sys, reg0);
417        let cone1 = cone_of_influence(&ctx, &sys, reg1);
418        insta::assert_snapshot!(format_symbol_list(&ctx, &cone0));
419        insta::assert_snapshot!(format_symbol_list(&ctx, &cone1));
420        let cone2 = cone_of_influence_init(&ctx, &sys, reg0);
421        assert_eq!(cone2, [reg0], "reg0 is initialized to zero. {:?}", cone2);
422        let cone3 = cone_of_influence_init(&ctx, &sys, reg1);
423        assert_eq!(cone3, [reg1], "reg1 is initialized to zero. {:?}", cone3);
424    }
425}