circomspect_program_structure/static_single_assignment/
traits.rs

1use log::trace;
2use std::collections::HashSet;
3use std::hash::Hash;
4
5use super::errors::SSAResult;
6
7pub trait SSAConfig: Sized {
8    /// The type used to track variable versions.
9    type Version;
10
11    /// The type of a variable.
12    type Variable: PartialEq + Eq + Hash + Clone;
13
14    /// An environment type used to track version across the CFG.
15    type Environment: SSAEnvironment;
16
17    /// The type of a statement.
18    type Statement: SSAStatement<Self>;
19
20    /// The type of a basic block.
21    type BasicBlock: SSABasicBlock<Self> + DirectedGraphNode;
22}
23
24/// An environment used to track variable versions across a CFG.
25pub trait SSAEnvironment {
26    /// Enter variable scope.
27    fn add_variable_scope(&mut self);
28
29    /// Leave variable scope.
30    fn remove_variable_scope(&mut self);
31}
32
33/// A basic block containing a (possibly empty) list of statements.
34pub trait SSABasicBlock<Cfg: SSAConfig>: DirectedGraphNode {
35    /// Add the given statement to the front of the basic block.
36    fn prepend_statement(&mut self, stmt: Cfg::Statement);
37
38    /// Returns an iterator over the statements of the basic block.
39    ///
40    /// Note: We have to use dynamic dispatch here because returning `impl
41    /// Trait` from trait methods is not a thing yet. For details, see
42    /// rust-lang.github.io/impl-trait-initiative/RFCs/rpit-in-traits.html)
43    fn statements<'a>(&'a self) -> Box<dyn Iterator<Item = &'a Cfg::Statement> + 'a>;
44
45    /// Returns an iterator over mutable references to the statements of the
46    /// basic block.
47    ///
48    /// Note: We have to use dynamic dispatch here because returning `impl
49    /// Trait` from trait methods is not a thing yet. For details, see
50    /// rust-lang.github.io/impl-trait-initiative/RFCs/rpit-in-traits.html)
51    fn statements_mut<'a>(&'a mut self) -> Box<dyn Iterator<Item = &'a mut Cfg::Statement> + 'a>;
52
53    /// Returns the set of variables written by the basic block.
54    fn variables_written(&self) -> HashSet<Cfg::Variable> {
55        self.statements().fold(HashSet::new(), |mut vars, stmt| {
56            vars.extend(stmt.variables_written());
57            vars
58        })
59    }
60
61    /// Returns true if the basic block has a phi statement for the given
62    /// variable.
63    fn has_phi_statement(&self, var: &Cfg::Variable) -> bool {
64        self.statements().any(|stmt| stmt.is_phi_statement_for(var))
65    }
66
67    /// Inserts a new phi statement for the given variable at the top of the basic
68    /// block.
69    fn insert_phi_statement(&mut self, var: &Cfg::Variable, env: &Cfg::Environment) {
70        self.prepend_statement(SSAStatement::new_phi_statement(var, env));
71    }
72
73    /// Updates the RHS of each phi statement in the basic block with the SSA
74    /// variable versions from the given environment.
75    fn update_phi_statements(&mut self, env: &Cfg::Environment) {
76        trace!("updating phi expression arguments in block {}", self.index());
77        for stmt in self.statements_mut() {
78            if stmt.is_phi_statement() {
79                stmt.ensure_phi_argument(env);
80            } else {
81                // Since phi statements proceed all other statements we are done
82                // here.
83                break;
84            }
85        }
86    }
87
88    /// Updates each variable to the corresponding SSA variable, in each
89    /// statement in the basic block.
90    fn insert_ssa_variables(&mut self, env: &mut Cfg::Environment) -> SSAResult<()> {
91        trace!("inserting SSA variables in block {}", self.index());
92        for stmt in self.statements_mut() {
93            stmt.insert_ssa_variables(env)?;
94        }
95        Ok(())
96    }
97}
98
99/// A statement in the language.
100pub trait SSAStatement<Cfg: SSAConfig>: Clone {
101    /// Returns the set of variables written by statement.
102    fn variables_written(&self) -> HashSet<Cfg::Variable>;
103
104    /// Returns a new phi statement (with empty RHS) for the given variable.
105    fn new_phi_statement(name: &Cfg::Variable, env: &Cfg::Environment) -> Self;
106
107    /// Returns true iff the statement is a phi statement.
108    fn is_phi_statement(&self) -> bool;
109
110    /// Returns true iff the statement is a phi statement for the given variable.
111    fn is_phi_statement_for(&self, var: &Cfg::Variable) -> bool;
112
113    /// Ensure that the phi expression argument list of a phi statement contains the
114    /// current version of the variable, according to the given environment.
115    ///
116    /// Panics if the statement is not a phi statement.
117    fn ensure_phi_argument(&mut self, env: &Cfg::Environment);
118
119    /// Replace each variable occurring in the statement by the corresponding
120    /// versioned SSA variable.
121    fn insert_ssa_variables(&mut self, env: &mut Cfg::Environment) -> SSAResult<()>;
122}
123
124pub type Index = usize;
125pub type IndexSet = HashSet<Index>;
126
127/// This trait is used to make graph algorithms (like dominator tree and dominator
128/// frontier generation) generic over the graph node type for unit testing purposes.
129pub trait DirectedGraphNode {
130    fn index(&self) -> Index;
131
132    fn predecessors(&self) -> &IndexSet;
133
134    fn successors(&self) -> &IndexSet;
135}