circomspect_program_structure/control_flow_graph/
cfg.rs

1use log::debug;
2use std::collections::HashSet;
3use std::fmt;
4use std::time::{Instant, Duration};
5
6use crate::constants::UsefulConstants;
7use crate::file_definition::FileID;
8use crate::ir::declarations::{Declaration, Declarations};
9use crate::ir::degree_meta::{DegreeEnvironment, Degree, DegreeRange};
10use crate::ir::value_meta::ValueEnvironment;
11use crate::ir::variable_meta::VariableMeta;
12use crate::ir::{VariableName, VariableType, SignalType};
13use crate::ssa::dominator_tree::DominatorTree;
14use crate::ssa::errors::SSAResult;
15use crate::ssa::{insert_phi_statements, insert_ssa_variables};
16
17use super::basic_block::BasicBlock;
18use super::parameters::Parameters;
19use super::ssa_impl;
20use super::ssa_impl::{Config, Environment};
21
22/// Basic block index type.
23pub type Index = usize;
24
25const MAX_ANALYSIS_DURATION: Duration = Duration::from_secs(10);
26
27#[derive(Clone)]
28pub enum DefinitionType {
29    Function,
30    Template,
31    CustomTemplate,
32}
33
34impl fmt::Display for DefinitionType {
35    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
36        match self {
37            DefinitionType::Function => write!(f, "function"),
38            DefinitionType::Template => write!(f, "template"),
39            DefinitionType::CustomTemplate => write!(f, "custom template"),
40        }
41    }
42}
43
44pub struct Cfg {
45    name: String,
46    constants: UsefulConstants,
47    parameters: Parameters,
48    declarations: Declarations,
49    basic_blocks: Vec<BasicBlock>,
50    definition_type: DefinitionType,
51    dominator_tree: DominatorTree<BasicBlock>,
52}
53
54impl Cfg {
55    pub(crate) fn new(
56        name: String,
57        constants: UsefulConstants,
58        definition_type: DefinitionType,
59        parameters: Parameters,
60        declarations: Declarations,
61        basic_blocks: Vec<BasicBlock>,
62        dominator_tree: DominatorTree<BasicBlock>,
63    ) -> Cfg {
64        Cfg {
65            name,
66            constants,
67            parameters,
68            declarations,
69            basic_blocks,
70            definition_type,
71            dominator_tree,
72        }
73    }
74    /// Returns the entry (first) block of the CFG.
75    #[must_use]
76    pub fn entry_block(&self) -> &BasicBlock {
77        &self.basic_blocks[Index::default()]
78    }
79
80    #[must_use]
81    pub fn get_basic_block(&self, index: Index) -> Option<&BasicBlock> {
82        self.basic_blocks.get(index)
83    }
84
85    /// Returns the number of basic blocks in the CFG.
86    #[must_use]
87    pub fn len(&self) -> usize {
88        self.basic_blocks.len()
89    }
90
91    /// Returns true if the CFG is empty.
92    #[must_use]
93    pub fn is_empty(&self) -> bool {
94        self.basic_blocks.is_empty()
95    }
96
97    /// Convert the CFG into SSA form.
98    pub fn into_ssa(mut self) -> SSAResult<Cfg> {
99        debug!("converting `{}` CFG to SSA", self.name());
100
101        // 1. Insert phi statements and convert variables to SSA.
102        let mut env = Environment::new(self.parameters(), self.declarations());
103        insert_phi_statements::<Config>(&mut self.basic_blocks, &self.dominator_tree, &mut env);
104        insert_ssa_variables::<Config>(&mut self.basic_blocks, &self.dominator_tree, &mut env)?;
105
106        // 2. Update parameters to SSA form.
107        for name in self.parameters.iter_mut() {
108            *name = name.with_version(0);
109        }
110
111        // 3. Update declarations to track SSA variables.
112        self.declarations =
113            ssa_impl::update_declarations(&mut self.basic_blocks, &self.parameters, &env);
114
115        // 4. Propagate metadata to all child nodes. Since determining variable
116        // use requires that variable types are available, type propagation must
117        // run before caching variable use.
118        self.propagate_types();
119        self.propagate_values();
120        self.propagate_degrees();
121        self.cache_variable_use();
122
123        // 5. Print trace output of CFG.
124        for basic_block in self.basic_blocks.iter() {
125            debug!(
126                "basic block {}: (predecessors: {:?}, successors: {:?})",
127                basic_block.index(),
128                basic_block.predecessors(),
129                basic_block.successors(),
130            );
131            for stmt in basic_block.iter() {
132                debug!("    {stmt:?}")
133            }
134        }
135        Ok(self)
136    }
137
138    /// Get the name of the corresponding function or template.
139    #[must_use]
140    pub fn name(&self) -> &str {
141        &self.name
142    }
143
144    /// Get the file ID for the corresponding function or template.
145    #[must_use]
146    pub fn file_id(&self) -> &Option<FileID> {
147        self.parameters.file_id()
148    }
149
150    #[must_use]
151    pub fn definition_type(&self) -> &DefinitionType {
152        &self.definition_type
153    }
154
155    #[must_use]
156    pub fn constants(&self) -> &UsefulConstants {
157        &self.constants
158    }
159
160    /// Returns the parameter data for the corresponding function or template.
161    #[must_use]
162    pub fn parameters(&self) -> &Parameters {
163        &self.parameters
164    }
165
166    /// Returns the variable declaration for the CFG.
167    #[must_use]
168    pub fn declarations(&self) -> &Declarations {
169        &self.declarations
170    }
171
172    /// Returns an iterator over the set of variables defined by the CFG.
173    pub fn variables(&self) -> impl Iterator<Item = &VariableName> {
174        self.declarations.iter().map(|(name, _)| name)
175    }
176
177    /// Returns an iterator over the input signals of the CFG.
178    pub fn input_signals(&self) -> impl Iterator<Item = &VariableName> {
179        use SignalType::*;
180        use VariableType::*;
181        self.declarations.iter().filter_map(|(name, declaration)| {
182            match declaration.variable_type() {
183                Signal(Input, _) => Some(name),
184                _ => None,
185            }
186        })
187    }
188
189    /// Returns an iterator over the output signals of the CFG.
190    pub fn output_signals(&self) -> impl Iterator<Item = &VariableName> {
191        use SignalType::*;
192        use VariableType::*;
193        self.declarations.iter().filter_map(|(name, declaration)| {
194            match declaration.variable_type() {
195                Signal(Output, _) => Some(name),
196                _ => None,
197            }
198        })
199    }
200
201    /// Returns the declaration of the given variable.
202    #[must_use]
203    pub fn get_declaration(&self, name: &VariableName) -> Option<&Declaration> {
204        self.declarations.get_declaration(name)
205    }
206
207    /// Returns the type of the given variable.
208    #[must_use]
209    pub fn get_type(&self, name: &VariableName) -> Option<&VariableType> {
210        self.declarations.get_type(name)
211    }
212
213    /// Returns an iterator over the basic blocks in the CFG. This iterator
214    /// guarantees that if `i` dominates `j`, then `i` comes before `j`.
215    pub fn iter(&self) -> impl Iterator<Item = &BasicBlock> {
216        self.basic_blocks.iter()
217    }
218
219    /// Returns a mutable iterator over the basic blocks in the CFG.
220    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut BasicBlock> {
221        self.basic_blocks.iter_mut()
222    }
223
224    /// Returns the dominators of the given basic block. The basic block `i`
225    /// dominates `j` if any path from the entry point to `j` must contain `i`.
226    /// (Note that this relation is reflexive, so `i` always dominates itself.)
227    #[must_use]
228    pub fn get_dominators(&self, basic_block: &BasicBlock) -> Vec<&BasicBlock> {
229        self.dominator_tree
230            .get_dominators(basic_block.index())
231            .iter()
232            .map(|&i| &self.basic_blocks[i])
233            .collect()
234    }
235
236    /// Returns the immediate dominator of the basic block (that is, the
237    /// predecessor of the node in the CFG dominator tree), if it exists.
238    #[must_use]
239    pub fn get_immediate_dominator(&self, basic_block: &BasicBlock) -> Option<&BasicBlock> {
240        self.dominator_tree
241            .get_immediate_dominator(basic_block.index())
242            .map(|i| &self.basic_blocks[i])
243    }
244
245    /// Get immediate successors of the basic block in the CFG dominator tree.
246    /// (For a definition of the dominator relation, see `CFG::get_dominators`.)
247    #[must_use]
248    pub fn get_dominator_successors(&self, basic_block: &BasicBlock) -> Vec<&BasicBlock> {
249        self.dominator_tree
250            .get_dominator_successors(basic_block.index())
251            .iter()
252            .map(|&i| &self.basic_blocks[i])
253            .collect()
254    }
255
256    /// Returns the dominance frontier of the basic block. The _dominance
257    /// frontier_ of `i` is defined as all basic blocks `j` such that `i`
258    /// dominates an immediate predecessor of `j`, but i does not strictly
259    /// dominate `j`. (`j` is where `i`s dominance ends.)
260    #[must_use]
261    pub fn get_dominance_frontier(&self, basic_block: &BasicBlock) -> Vec<&BasicBlock> {
262        self.dominator_tree
263            .get_dominance_frontier(basic_block.index())
264            .iter()
265            .map(|&i| &self.basic_blocks[i])
266            .collect()
267    }
268
269    /// Returns the predecessors of the given basic block.
270    pub fn get_predecessors(&self, basic_block: &BasicBlock) -> Vec<&BasicBlock> {
271        let mut predecessors = HashSet::new();
272        let mut update = HashSet::from([basic_block.index()]);
273        while !update.is_subset(&predecessors) {
274            predecessors.extend(update.iter().cloned());
275            update = update
276                .iter()
277                .flat_map(|index| {
278                    self.get_basic_block(*index)
279                        .expect("in control-flow graph")
280                        .predecessors()
281                        .iter()
282                        .cloned()
283                })
284                .collect();
285        }
286        // Remove the initial block.
287        predecessors.remove(&basic_block.index());
288        predecessors
289            .iter()
290            .map(|index| self.get_basic_block(*index).expect("in control-flow graph"))
291            .collect::<Vec<_>>()
292    }
293
294    /// Returns the successors of the given basic block.
295    pub fn get_successors(&self, basic_block: &BasicBlock) -> Vec<&BasicBlock> {
296        let mut successors = HashSet::new();
297        let mut update = HashSet::from([basic_block.index()]);
298        while !update.is_subset(&successors) {
299            successors.extend(update.iter().cloned());
300            update = update
301                .iter()
302                .flat_map(|index| {
303                    self.get_basic_block(*index)
304                        .expect("in control-flow graph")
305                        .successors()
306                        .iter()
307                        .cloned()
308                })
309                .collect();
310        }
311        // Remove the initial block.
312        successors.remove(&basic_block.index());
313        successors
314            .iter()
315            .map(|index| self.get_basic_block(*index).expect("in control-flow graph"))
316            .collect::<Vec<_>>()
317    }
318
319    /// Returns all block in the interval [start_block, end_block). That is, all
320    /// successors of the starting block (including the starting block) which
321    /// are also predecessors of the end block.
322    pub fn get_interval(
323        &self,
324        start_block: &BasicBlock,
325        end_block: &BasicBlock,
326    ) -> Vec<&BasicBlock> {
327        // Compute the successors of the start block (including the start block).
328        let mut successors = HashSet::new();
329        let mut update = HashSet::from([start_block.index()]);
330        while !update.is_subset(&successors) {
331            successors.extend(update.iter().cloned());
332            update = update
333                .iter()
334                .flat_map(|index| {
335                    self.get_basic_block(*index)
336                        .expect("in control-flow graph")
337                        .successors()
338                        .iter()
339                        .cloned()
340                })
341                .collect();
342        }
343
344        // Compute the strict predecessors of the end block.
345        let mut predecessors = HashSet::new();
346        let mut update = HashSet::from([end_block.index()]);
347        while !update.is_subset(&predecessors) {
348            predecessors.extend(update.iter().cloned());
349            update = update
350                .iter()
351                .flat_map(|index| {
352                    self.get_basic_block(*index)
353                        .expect("in control-flow graph")
354                        .predecessors()
355                        .iter()
356                        .cloned()
357                })
358                .collect();
359        }
360        predecessors.remove(&end_block.index());
361
362        // Return the basic blocks corresponding to the intersection of the two
363        // sets.
364        successors
365            .intersection(&predecessors)
366            .map(|index| self.get_basic_block(*index).expect("in control-flow graph"))
367            .collect::<Vec<_>>()
368    }
369
370    /// Returns the basic blocks corresponding to the true branch of the
371    /// if-statement at the end of the given header block.
372    ///
373    /// # Panics
374    ///
375    /// This method panics if the given block does not end with an if-statement node.
376    pub fn get_true_branch(&self, header_block: &BasicBlock) -> Vec<&BasicBlock> {
377        use crate::ir::Statement::*;
378        if let Some(IfThenElse { true_index, .. }) = header_block.statements().last() {
379            let start_block = self.get_basic_block(*true_index).expect("in control-flow graph");
380            let end_blocks = self.get_dominance_frontier(start_block);
381
382            if end_blocks.is_empty() {
383                // True and false branches do not join up.
384                let mut result = self.get_successors(start_block);
385                result.push(start_block);
386                result
387            } else {
388                // True and false branches join up at the dominance frontier.
389                let mut result = Vec::new();
390                for end_block in end_blocks {
391                    result.extend(self.get_interval(start_block, end_block))
392                }
393                result
394            }
395        } else {
396            panic!("the given header block does not end with an if-statement");
397        }
398    }
399
400    /// Returns the basic blocks corresponding to the false branch of the
401    /// if-statement at the end of the given header block.
402    ///
403    /// # Panics
404    ///
405    /// This method panics if the given block does not end with an if-statement node.
406    pub fn get_false_branch(&self, header_block: &BasicBlock) -> Vec<&BasicBlock> {
407        use crate::ir::Statement::*;
408        if let Some(IfThenElse { true_index, false_index, .. }) = header_block.statements().last() {
409            if let Some(false_index) = false_index {
410                if self.dominator_tree.get_dominance_frontier(*true_index).contains(false_index) {
411                    // The false branch is empty.
412                    return Vec::new();
413                }
414                let start_block =
415                    self.get_basic_block(*false_index).expect("in control-flow graph");
416                let end_blocks = self.get_dominance_frontier(start_block);
417
418                if end_blocks.is_empty() {
419                    // True and false branches do not join up.
420                    let mut result = self.get_successors(start_block);
421                    result.push(start_block);
422                    result
423                } else {
424                    // True and false branches join up at the dominance frontier.
425                    let mut result = Vec::new();
426                    for end_block in end_blocks {
427                        result.extend(self.get_interval(start_block, end_block))
428                    }
429                    result
430                }
431            } else {
432                Vec::new()
433            }
434        } else {
435            panic!("the given header block does not end with an if-statement");
436        }
437    }
438
439    /// Cache variable use for each node in the CFG.
440    pub(crate) fn cache_variable_use(&mut self) {
441        debug!("computing variable use for `{}`", self.name());
442        for basic_block in self.iter_mut() {
443            basic_block.cache_variable_use();
444        }
445    }
446
447    /// Propagate expression degrees along the CFG.
448    pub(crate) fn propagate_degrees(&mut self) {
449        use Degree::*;
450        debug!("propagating expression degrees for `{}`", self.name());
451        let mut env = DegreeEnvironment::new();
452        for param in self.parameters().iter() {
453            env.set_type(param, &VariableType::Local);
454            if matches!(self.definition_type(), DefinitionType::Function) {
455                // For functions, the parameters may be constants or signals.
456                let range = DegreeRange::new(Constant, Linear);
457                env.set_degree(param, &range);
458            } else {
459                // For templates, the parameters are constants.
460                env.set_degree(param, &Constant.into());
461            }
462        }
463        let mut rerun = true;
464        let start = Instant::now();
465        while rerun {
466            // Rerun degree propagation if a single child node was updated.
467            rerun = false;
468            for basic_block in self.iter_mut() {
469                rerun = rerun || basic_block.propagate_degrees(&mut env);
470            }
471            // Bail out if analysis takes more than 10 seconds.
472            if start.elapsed() > MAX_ANALYSIS_DURATION {
473                debug!("failed to propagate degrees within allotted time");
474                rerun = false;
475            }
476        }
477    }
478
479    /// Propagate constant values along the CFG.
480    pub(crate) fn propagate_values(&mut self) {
481        debug!("propagating constant values for `{}`", self.name());
482        let mut env = ValueEnvironment::new(&self.constants);
483        let mut rerun = true;
484        let start = Instant::now();
485        while rerun {
486            // Rerun value propagation if a single child node was updated.
487            rerun = false;
488            for basic_block in self.iter_mut() {
489                rerun = rerun || basic_block.propagate_values(&mut env);
490            }
491            // Bail out if analysis takes more than 10 seconds.
492            if start.elapsed() > MAX_ANALYSIS_DURATION {
493                debug!("failed to propagate values within allotted time");
494                rerun = false;
495            }
496        }
497    }
498
499    /// Propagate variable types along the CFG.
500    pub(crate) fn propagate_types(&mut self) {
501        debug!("propagating variable types for `{}`", self.name());
502        // Need to clone declarations here since we cannot borrow self both
503        // mutably and immutably.
504        let declarations = self.declarations.clone();
505        for basic_block in self.iter_mut() {
506            basic_block.propagate_types(&declarations);
507        }
508    }
509}
510
511impl fmt::Debug for Cfg {
512    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
513        for basic_block in self.iter() {
514            writeln!(
515                f,
516                "basic block {}, predecessors: {:?}, successors: {:?}",
517                basic_block.index(),
518                basic_block.predecessors(),
519                basic_block.successors(),
520            )?;
521            write!(f, "{basic_block:?}")?;
522        }
523        Ok(())
524    }
525}