circomspect_program_structure/control_flow_graph/
lifting.rs

1use log::{debug, trace};
2use std::collections::HashSet;
3
4use crate::ast;
5use crate::ast::Definition;
6
7use crate::constants::{UsefulConstants, Curve};
8use crate::function_data::FunctionData;
9use crate::ir;
10use crate::ir::declarations::{Declaration, Declarations};
11use crate::ir::errors::IRResult;
12use crate::ir::lifting::{LiftingEnvironment, TryLift};
13use crate::ir::VariableType;
14
15use crate::report::ReportCollection;
16use crate::nonempty_vec::NonEmptyVec;
17use crate::ssa::dominator_tree::DominatorTree;
18use crate::template_data::TemplateData;
19
20use super::basic_block::BasicBlock;
21use super::cfg::DefinitionType;
22use super::errors::{CFGError, CFGResult};
23use super::parameters::Parameters;
24use super::unique_vars::ensure_unique_variables;
25use super::Cfg;
26
27type Index = usize;
28type IndexSet = HashSet<Index>;
29type BasicBlockVec = NonEmptyVec<BasicBlock>;
30
31/// This is a high level trait which simply wraps the implementation provided by
32/// `TryLift`. We need to pass the prime to the CFG here, to be able to do value
33/// propagation when converting to SSA.
34pub trait IntoCfg {
35    fn into_cfg(self, curve: &Curve, reports: &mut ReportCollection) -> CFGResult<Cfg>;
36}
37
38impl<T> IntoCfg for T
39where
40    T: TryLift<UsefulConstants, IR = Cfg, Error = CFGError>,
41{
42    fn into_cfg(self, curve: &Curve, reports: &mut ReportCollection) -> CFGResult<Cfg> {
43        let constants = UsefulConstants::new(curve);
44        self.try_lift(constants, reports)
45    }
46}
47
48impl From<&Parameters> for LiftingEnvironment {
49    fn from(params: &Parameters) -> LiftingEnvironment {
50        let mut env = LiftingEnvironment::new();
51        for name in params.iter() {
52            let declaration = Declaration::new(
53                name,
54                &VariableType::Local,
55                &Vec::new(),
56                params.file_id(),
57                params.file_location(),
58            );
59            env.add_declaration(&declaration);
60        }
61        env
62    }
63}
64
65impl TryLift<UsefulConstants> for &TemplateData {
66    type IR = Cfg;
67    type Error = CFGError;
68
69    fn try_lift(
70        &self,
71        constants: UsefulConstants,
72        reports: &mut ReportCollection,
73    ) -> CFGResult<Cfg> {
74        let name = self.get_name().to_string();
75        let parameters = Parameters::from(*self);
76        let body = self.get_body().clone();
77        let definition_type = if self.is_custom_gate() {
78            DefinitionType::CustomTemplate
79        } else {
80            DefinitionType::Template
81        };
82        debug!("building CFG for template `{name}`");
83        try_lift_impl(name, definition_type, constants, parameters, body, reports)
84    }
85}
86
87impl TryLift<UsefulConstants> for &FunctionData {
88    type IR = Cfg;
89    type Error = CFGError;
90
91    fn try_lift(
92        &self,
93        constants: UsefulConstants,
94        reports: &mut ReportCollection,
95    ) -> CFGResult<Cfg> {
96        let name = self.get_name().to_string();
97        let parameters = Parameters::from(*self);
98        let body = self.get_body().clone();
99
100        debug!("building CFG for function `{name}`");
101        try_lift_impl(name, DefinitionType::Function, constants, parameters, body, reports)
102    }
103}
104
105impl TryLift<UsefulConstants> for Definition {
106    type IR = Cfg;
107    type Error = CFGError;
108
109    fn try_lift(
110        &self,
111        constants: UsefulConstants,
112        reports: &mut ReportCollection,
113    ) -> CFGResult<Cfg> {
114        match self {
115            Definition::Template { name, body, is_custom_gate, .. } => {
116                debug!("building CFG for template `{name}`");
117                let definition_type = if *is_custom_gate {
118                    DefinitionType::CustomTemplate
119                } else {
120                    DefinitionType::Template
121                };
122                try_lift_impl(
123                    name.clone(),
124                    definition_type,
125                    constants,
126                    self.into(),
127                    body.clone(),
128                    reports,
129                )
130            }
131            Definition::Function { name, body, .. } => {
132                debug!("building CFG for function `{name}`");
133                try_lift_impl(
134                    name.clone(),
135                    DefinitionType::Function,
136                    constants,
137                    self.into(),
138                    body.clone(),
139                    reports,
140                )
141            }
142        }
143    }
144}
145
146fn try_lift_impl(
147    name: String,
148    definition_type: DefinitionType,
149    constants: UsefulConstants,
150    parameters: Parameters,
151    mut body: ast::Statement,
152    reports: &mut ReportCollection,
153) -> CFGResult<Cfg> {
154    // 1. Ensure that variable names are globally unique before converting to basic blocks.
155    ensure_unique_variables(&mut body, &parameters, reports)?;
156
157    // 2. Convert template AST to CFG and compute dominator tree.
158    let mut env = LiftingEnvironment::from(&parameters);
159    let basic_blocks = build_basic_blocks(&body, &mut env, reports)?;
160    let dominator_tree = DominatorTree::new(&basic_blocks);
161    let declarations = Declarations::from(env);
162    let mut cfg = Cfg::new(
163        name,
164        constants,
165        definition_type,
166        parameters,
167        declarations,
168        basic_blocks,
169        dominator_tree,
170    );
171
172    // 3. Propagate metadata to all child nodes. Since determining variable use
173    // requires that variable types are available, type propagation must run
174    // before caching variable use.
175    //
176    // Note that the current implementations of value and degree propagation
177    // only make sense in SSA form.
178    cfg.propagate_types();
179    cfg.cache_variable_use();
180
181    Ok(cfg)
182}
183
184/// This function generates a vector of basic blocks containing `ir::Statement`s
185/// from a function or template body. The vector is guaranteed to be non-empty,
186/// and the first block (with index 0) will always be the entry block.
187pub(crate) fn build_basic_blocks(
188    body: &ast::Statement,
189    env: &mut LiftingEnvironment,
190    reports: &mut ReportCollection,
191) -> IRResult<Vec<BasicBlock>> {
192    assert!(matches!(body, ast::Statement::Block { .. }));
193
194    let meta = body.get_meta().try_lift((), reports)?;
195    let mut basic_blocks = BasicBlockVec::new(BasicBlock::new(meta, Index::default(), 0));
196    visit_statement(body, 0, env, reports, &mut basic_blocks)?;
197    Ok(basic_blocks.into())
198}
199
200/// Update the CFG with the current statement. This implementation assumes that
201/// all control-flow statement bodies are wrapped by a `Block` statement. Blocks
202/// are finalized and the current block (i.e. last block) is updated when:
203///
204///   1. The current statement is a `While` statement. An `IfThenElse` statement
205///      is added to the current block. The successors of the if-statement will be
206///      the while-statement body and the while-statement successor (if any).
207///   2. The current statement is an `IfThenElse` statement. The current statement
208///      is added to the current block. The successors of the if-statement will
209///      be the if-case body and else-case body (if any).
210///
211/// The function returns the predecessors of the next block in the CFG.
212fn visit_statement(
213    stmt: &ast::Statement,
214    loop_depth: usize,
215    env: &mut LiftingEnvironment,
216    reports: &mut ReportCollection,
217    basic_blocks: &mut BasicBlockVec,
218) -> IRResult<IndexSet> {
219    let current_index = basic_blocks.last().index();
220
221    match stmt {
222        ast::Statement::InitializationBlock { initializations: stmts, .. } => {
223            // Add each statement in the initialization block to the current
224            // block. Since initialization blocks only contain declarations and
225            // substitutions, we do not need to track predecessors here.
226            trace!("entering initialization block statement");
227            for stmt in stmts {
228                assert!(visit_statement(stmt, loop_depth, env, reports, basic_blocks)?.is_empty());
229            }
230            trace!("leaving initialization block statement");
231            Ok(HashSet::new())
232        }
233        ast::Statement::Block { stmts, .. } => {
234            // Add each statement in the basic block to the current block. If a
235            // call to `visit_statement` completes a basic block and returns a set
236            // of predecessors for the next block, we create a new block before
237            // continuing.
238            trace!("entering block statement");
239
240            let mut pred_set = IndexSet::new();
241            for stmt in stmts {
242                if !pred_set.is_empty() {
243                    let meta = stmt.get_meta().try_lift((), reports)?;
244                    complete_basic_block(basic_blocks, meta, &pred_set, loop_depth);
245                }
246                pred_set = visit_statement(stmt, loop_depth, env, reports, basic_blocks)?;
247            }
248            trace!("leaving block statement (predecessors: {:?})", pred_set);
249
250            // If the last statement of the block is a control-flow statement,
251            // `pred_set` will be non-empty. Otherwise it will be empty.
252            Ok(pred_set)
253        }
254        ast::Statement::While { meta, cond, stmt: while_body, .. } => {
255            let pred_set = HashSet::from([current_index]);
256            complete_basic_block(basic_blocks, meta.try_lift((), reports)?, &pred_set, loop_depth);
257
258            // While statements are translated into a loop head with a single
259            // if-statement, and a loop body containing the while-statement
260            // body. The index of the loop header will be `current_index + 1`,
261            // and the index of the loop body will be `current_index + 2`.
262            trace!("appending statement `if {cond}` to basic block {current_index}");
263            basic_blocks.last_mut().append_statement(ir::Statement::IfThenElse {
264                meta: meta.try_lift((), reports)?,
265                cond: cond.try_lift((), reports)?,
266                true_index: current_index + 2,
267                false_index: None, // May be updated later.
268            });
269            let header_index = current_index + 1;
270
271            // Visit the while-statement body.
272            let meta = while_body.get_meta().try_lift((), reports)?;
273            let pred_set = HashSet::from([header_index]);
274            complete_basic_block(basic_blocks, meta, &pred_set, loop_depth + 1);
275
276            trace!("visiting while body");
277            let mut pred_set =
278                visit_statement(while_body, loop_depth + 1, env, reports, basic_blocks)?;
279            // The returned predecessor set will be empty if the last statement
280            // of the body is not a conditional. In this case we need to add the
281            // last block of the body to complete the corresponding block.
282            if pred_set.is_empty() {
283                pred_set.insert(basic_blocks.last().index());
284            }
285            // The loop header is the successor of all blocks in `pred_set`.
286            trace!("adding predecessors {:?} to loop header {header_index}", pred_set);
287            for i in pred_set {
288                basic_blocks[i].add_successor(header_index);
289                basic_blocks[header_index].add_predecessor(i);
290            }
291
292            // The next block (if any) will be the false branch and a successor
293            // of the loop header.
294            Ok(HashSet::from([header_index]))
295        }
296        ast::Statement::IfThenElse { meta, cond, if_case, else_case, .. } => {
297            trace!("appending statement `if {cond}` to basic block {current_index}");
298            basic_blocks.last_mut().append_statement(ir::Statement::IfThenElse {
299                meta: meta.try_lift((), reports)?,
300                cond: cond.try_lift((), reports)?,
301                true_index: current_index + 1,
302                false_index: None, // May be updated later.
303            });
304
305            // Visit the if-case body.
306            let meta = if_case.get_meta().try_lift((), reports)?;
307            let pred_set = HashSet::from([current_index]);
308            complete_basic_block(basic_blocks, meta, &pred_set, loop_depth);
309
310            trace!("visiting true if-statement branch");
311            let mut if_pred_set = visit_statement(if_case, loop_depth, env, reports, basic_blocks)?;
312            // The returned predecessor set will be empty if the last statement
313            // of the body is not a conditional. In this case we need to add the
314            // last block of the body to complete the corresponding block.
315            if if_pred_set.is_empty() {
316                if_pred_set.insert(basic_blocks.last().index());
317            }
318
319            // Visit the else-case body.
320            if let Some(else_case) = else_case {
321                trace!("visiting false if-statement branch");
322                let meta = else_case.get_meta().try_lift((), reports)?;
323                let pred_set = HashSet::from([current_index]);
324                complete_basic_block(basic_blocks, meta, &pred_set, loop_depth);
325
326                let mut else_pred_set =
327                    visit_statement(else_case, loop_depth, env, reports, basic_blocks)?;
328                // The returned predecessor set will be empty if the last statement
329                // of the body is not a conditional. In this case we need to add the
330                // last block of the body to complete the corresponding block.
331                if else_pred_set.is_empty() {
332                    else_pred_set.insert(basic_blocks.last().index());
333                }
334                Ok(if_pred_set.union(&else_pred_set).cloned().collect())
335            } else {
336                if_pred_set.insert(current_index);
337                Ok(if_pred_set)
338            }
339        }
340        ast::Statement::Declaration { meta, name, xtype, dimensions, .. } => {
341            // Declarations are also tracked by the CFG header.
342            trace!("appending `{stmt}` to basic block {current_index}");
343            env.add_declaration(&Declaration::new(
344                &name.try_lift(meta, reports)?,
345                &xtype.try_lift((), reports)?,
346                &dimensions
347                    .iter()
348                    .map(|size| size.try_lift((), reports))
349                    .collect::<IRResult<Vec<_>>>()?,
350                &meta.file_id,
351                &meta.location,
352            ));
353            basic_blocks.last_mut().append_statement(stmt.try_lift((), reports)?);
354            Ok(HashSet::new())
355        }
356        _ => {
357            trace!("appending `{stmt}` to basic block {current_index}");
358            basic_blocks.last_mut().append_statement(stmt.try_lift((), reports)?);
359            Ok(HashSet::new())
360        }
361    }
362}
363
364/// Complete the current (i.e. last) basic block and add a new basic block to
365/// the vector with the given `meta`, and `pred_set` as predecessors. Update all
366/// predecessors adding the new block as a successor.
367///
368/// If the final statement of the predecessor block is a control-flow statement,
369/// and the new block is not the true branch target of the statement, the new
370/// block is added as the false branch target.
371fn complete_basic_block(
372    basic_blocks: &mut BasicBlockVec,
373    meta: ir::Meta,
374    pred_set: &IndexSet,
375    loop_depth: usize,
376) {
377    use ir::Statement::*;
378    trace!("finalizing basic block {}", basic_blocks.last().index());
379    let j = basic_blocks.len();
380    basic_blocks.push(BasicBlock::new(meta, j, loop_depth));
381    for i in pred_set {
382        basic_blocks[i].add_successor(j);
383        basic_blocks[j].add_predecessor(*i);
384
385        // If the final statement `S` of block `i` is a control flow statement,
386        // and `j` is not the true branch of `S`, update the false branch of `S`
387        // to `j`.
388        if let Some(IfThenElse { cond, true_index, false_index, .. }) =
389            basic_blocks[i].statements_mut().last_mut()
390        {
391            if j != *true_index && false_index.is_none() {
392                trace!("updating false branch target of `if {cond}`");
393                *false_index = Some(j)
394            }
395        }
396    }
397}