1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
use log::{debug, trace};
use std::collections::HashSet;

use crate::ast;
use crate::ast::Definition;

use crate::constants::{UsefulConstants, Curve};
use crate::function_data::FunctionData;
use crate::ir;
use crate::ir::declarations::{Declaration, Declarations};
use crate::ir::errors::IRResult;
use crate::ir::lifting::{LiftingEnvironment, TryLift};
use crate::ir::VariableType;

use crate::report::ReportCollection;
use crate::nonempty_vec::NonEmptyVec;
use crate::ssa::dominator_tree::DominatorTree;
use crate::template_data::TemplateData;

use super::basic_block::BasicBlock;
use super::cfg::DefinitionType;
use super::errors::{CFGError, CFGResult};
use super::parameters::Parameters;
use super::unique_vars::ensure_unique_variables;
use super::Cfg;

type Index = usize;
type IndexSet = HashSet<Index>;
type BasicBlockVec = NonEmptyVec<BasicBlock>;

/// This is a high level trait which simply wraps the implementation provided by
/// `TryLift`. We need to pass the prime to the CFG here, to be able to do value
/// propagation when converting to SSA.
pub trait IntoCfg {
    fn into_cfg(self, curve: &Curve, reports: &mut ReportCollection) -> CFGResult<Cfg>;
}

impl<T> IntoCfg for T
where
    T: TryLift<UsefulConstants, IR = Cfg, Error = CFGError>,
{
    fn into_cfg(self, curve: &Curve, reports: &mut ReportCollection) -> CFGResult<Cfg> {
        let constants = UsefulConstants::new(curve);
        self.try_lift(constants, reports)
    }
}

impl From<&Parameters> for LiftingEnvironment {
    fn from(params: &Parameters) -> LiftingEnvironment {
        let mut env = LiftingEnvironment::new();
        for name in params.iter() {
            let declaration = Declaration::new(
                name,
                &VariableType::Local,
                &Vec::new(),
                params.file_id(),
                params.file_location(),
            );
            env.add_declaration(&declaration);
        }
        env
    }
}

impl TryLift<UsefulConstants> for &TemplateData {
    type IR = Cfg;
    type Error = CFGError;

    fn try_lift(
        &self,
        constants: UsefulConstants,
        reports: &mut ReportCollection,
    ) -> CFGResult<Cfg> {
        let name = self.get_name().to_string();
        let parameters = Parameters::from(*self);
        let body = self.get_body().clone();
        let definition_type = if self.is_custom_gate() {
            DefinitionType::CustomTemplate
        } else {
            DefinitionType::Template
        };
        debug!("building CFG for template `{name}`");
        try_lift_impl(name, definition_type, constants, parameters, body, reports)
    }
}

impl TryLift<UsefulConstants> for &FunctionData {
    type IR = Cfg;
    type Error = CFGError;

    fn try_lift(
        &self,
        constants: UsefulConstants,
        reports: &mut ReportCollection,
    ) -> CFGResult<Cfg> {
        let name = self.get_name().to_string();
        let parameters = Parameters::from(*self);
        let body = self.get_body().clone();

        debug!("building CFG for function `{name}`");
        try_lift_impl(name, DefinitionType::Function, constants, parameters, body, reports)
    }
}

impl TryLift<UsefulConstants> for Definition {
    type IR = Cfg;
    type Error = CFGError;

    fn try_lift(
        &self,
        constants: UsefulConstants,
        reports: &mut ReportCollection,
    ) -> CFGResult<Cfg> {
        match self {
            Definition::Template { name, body, is_custom_gate, .. } => {
                debug!("building CFG for template `{name}`");
                let definition_type = if *is_custom_gate {
                    DefinitionType::CustomTemplate
                } else {
                    DefinitionType::Template
                };
                try_lift_impl(
                    name.clone(),
                    definition_type,
                    constants,
                    self.into(),
                    body.clone(),
                    reports,
                )
            }
            Definition::Function { name, body, .. } => {
                debug!("building CFG for function `{name}`");
                try_lift_impl(
                    name.clone(),
                    DefinitionType::Function,
                    constants,
                    self.into(),
                    body.clone(),
                    reports,
                )
            }
        }
    }
}

fn try_lift_impl(
    name: String,
    definition_type: DefinitionType,
    constants: UsefulConstants,
    parameters: Parameters,
    mut body: ast::Statement,
    reports: &mut ReportCollection,
) -> CFGResult<Cfg> {
    // 1. Ensure that variable names are globally unique before converting to basic blocks.
    ensure_unique_variables(&mut body, &parameters, reports)?;

    // 2. Convert template AST to CFG and compute dominator tree.
    let mut env = LiftingEnvironment::from(&parameters);
    let basic_blocks = build_basic_blocks(&body, &mut env, reports)?;
    let dominator_tree = DominatorTree::new(&basic_blocks);
    let declarations = Declarations::from(env);
    let mut cfg = Cfg::new(
        name,
        constants,
        definition_type,
        parameters,
        declarations,
        basic_blocks,
        dominator_tree,
    );

    // 3. Propagate metadata to all child nodes. Since determining variable use
    // requires that variable types are available, type propagation must run
    // before caching variable use.
    //
    // Note that the current implementations of value and degree propagation
    // only make sense in SSA form.
    cfg.propagate_types();
    cfg.cache_variable_use();

    Ok(cfg)
}

/// This function generates a vector of basic blocks containing `ir::Statement`s
/// from a function or template body. The vector is guaranteed to be non-empty,
/// and the first block (with index 0) will always be the entry block.
pub(crate) fn build_basic_blocks(
    body: &ast::Statement,
    env: &mut LiftingEnvironment,
    reports: &mut ReportCollection,
) -> IRResult<Vec<BasicBlock>> {
    assert!(matches!(body, ast::Statement::Block { .. }));

    let meta = body.get_meta().try_lift((), reports)?;
    let mut basic_blocks = BasicBlockVec::new(BasicBlock::new(Index::default(), meta));
    visit_statement(body, env, reports, &mut basic_blocks)?;
    Ok(basic_blocks.into())
}

/// Update the CFG with the current statement. This implementation assumes that
/// all control-flow statement bodies are wrapped by a `Block` statement. Blocks
/// are finalized and the current block (i.e. last block) is updated when:
///
///   2. The current statement is a `While` statement. An `IfThenElse` statement
///      is added to the current block. The successors of the if-statement will be
///      the while-statement body and the while-statement successor (if any).
///   3. The current statement is an `IfThenElse` statement. The current statement
///      is added to the current block. The successors of the if-statement will
///      be the if-case body and else-case body (if any).
///
/// The function returns the predecessors of the next block in the CFG.
fn visit_statement(
    stmt: &ast::Statement,
    env: &mut LiftingEnvironment,
    reports: &mut ReportCollection,
    basic_blocks: &mut BasicBlockVec,
) -> IRResult<IndexSet> {
    let current_index = basic_blocks.last().index();

    match stmt {
        ast::Statement::InitializationBlock { initializations: stmts, .. } => {
            // Add each statement in the initialization block to the current
            // block. Since initialization blocks only contain declarations and
            // substitutions, we do not need to track predecessors here.
            trace!("entering initialization block statement");
            for stmt in stmts {
                assert!(visit_statement(stmt, env, reports, basic_blocks)?.is_empty());
            }
            trace!("leaving initialization block statement");
            Ok(HashSet::new())
        }
        ast::Statement::Block { stmts, .. } => {
            // Add each statement in the basic block to the current block. If a
            // call to `visit_statement` completes a basic block and returns a set
            // of predecessors for the next block, we create a new block before
            // continuing.
            trace!("entering block statement");

            let mut pred_set = IndexSet::new();
            for stmt in stmts {
                if !pred_set.is_empty() {
                    let meta = stmt.get_meta().try_lift((), reports)?;
                    complete_basic_block(basic_blocks, &pred_set, meta);
                }
                pred_set = visit_statement(stmt, env, reports, basic_blocks)?;
            }
            trace!("leaving block statement (predecessors: {:?})", pred_set);

            // If the last statement of the block is a control-flow statement,
            // `pred_set` will be non-empty. Otherwise it will be empty.
            Ok(pred_set)
        }
        ast::Statement::While { meta, cond, stmt: while_body, .. } => {
            let pred_set = HashSet::from([current_index]);
            complete_basic_block(basic_blocks, &pred_set, meta.try_lift((), reports)?);

            // While statements are translated into a loop head with a single
            // if-statement, and a loop body containing the while-statement
            // body. The index of the loop header will be `current_index + 1`,
            // and the index of the loop body will be `current_index + 2`.
            trace!("appending statement `if {cond}` to basic block {current_index}");
            basic_blocks.last_mut().append_statement(ir::Statement::IfThenElse {
                meta: meta.try_lift((), reports)?,
                cond: cond.try_lift((), reports)?,
                true_index: current_index + 2,
                false_index: None, // May be updated later.
            });
            let header_index = current_index + 1;

            // Visit the while-statement body.
            let meta = while_body.get_meta().try_lift((), reports)?;
            let pred_set = HashSet::from([header_index]);
            complete_basic_block(basic_blocks, &pred_set, meta);

            trace!("visiting while body");
            let mut pred_set = visit_statement(while_body, env, reports, basic_blocks)?;
            // The returned predecessor set will be empty if the last statement
            // of the body is not a conditional. In this case we need to add the
            // last block of the body to complete the corresponding block.
            if pred_set.is_empty() {
                pred_set.insert(basic_blocks.last().index());
            }
            // The loop header is the successor of all blocks in `pred_set`.
            trace!("adding predecessors {:?} to loop header {header_index}", pred_set);
            for i in pred_set {
                basic_blocks[i].add_successor(header_index);
                basic_blocks[header_index].add_predecessor(i);
            }

            // The next block (if any) will be the false branch and a successor
            // of the loop header.
            Ok(HashSet::from([header_index]))
        }
        ast::Statement::IfThenElse { meta, cond, if_case, else_case, .. } => {
            trace!("appending statement `if {cond}` to basic block {current_index}");
            basic_blocks.last_mut().append_statement(ir::Statement::IfThenElse {
                meta: meta.try_lift((), reports)?,
                cond: cond.try_lift((), reports)?,
                true_index: current_index + 1,
                false_index: None, // May be updated later.
            });

            // Visit the if-case body.
            let meta = if_case.get_meta().try_lift((), reports)?;
            let pred_set = HashSet::from([current_index]);
            complete_basic_block(basic_blocks, &pred_set, meta);

            trace!("visiting true if-statement branch");
            let mut if_pred_set = visit_statement(if_case, env, reports, basic_blocks)?;
            // The returned predecessor set will be empty if the last statement
            // of the body is not a conditional. In this case we need to add the
            // last block of the body to complete the corresponding block.
            if if_pred_set.is_empty() {
                if_pred_set.insert(basic_blocks.last().index());
            }

            // Visit the else-case body.
            if let Some(else_case) = else_case {
                trace!("visiting false if-statement branch");
                let meta = else_case.get_meta().try_lift((), reports)?;
                let pred_set = HashSet::from([current_index]);
                complete_basic_block(basic_blocks, &pred_set, meta);

                let mut else_pred_set = visit_statement(else_case, env, reports, basic_blocks)?;
                // The returned predecessor set will be empty if the last statement
                // of the body is not a conditional. In this case we need to add the
                // last block of the body to complete the corresponding block.
                if else_pred_set.is_empty() {
                    else_pred_set.insert(basic_blocks.last().index());
                }
                Ok(if_pred_set.union(&else_pred_set).cloned().collect())
            } else {
                if_pred_set.insert(current_index);
                Ok(if_pred_set)
            }
        }
        ast::Statement::Declaration { meta, name, xtype, dimensions, .. } => {
            // Declarations are also tracked by the CFG header.
            trace!("appending `{stmt}` to basic block {current_index}");
            env.add_declaration(&Declaration::new(
                &name.try_lift(meta, reports)?,
                &xtype.try_lift((), reports)?,
                &dimensions
                    .iter()
                    .map(|size| size.try_lift((), reports))
                    .collect::<IRResult<Vec<_>>>()?,
                &meta.file_id,
                &meta.location,
            ));
            basic_blocks.last_mut().append_statement(stmt.try_lift((), reports)?);
            Ok(HashSet::new())
        }
        _ => {
            trace!("appending `{stmt}` to basic block {current_index}");
            basic_blocks.last_mut().append_statement(stmt.try_lift((), reports)?);
            Ok(HashSet::new())
        }
    }
}

/// Complete the current (i.e. last) basic block and add a new basic block to
/// the vector with the given `meta`, and `pred_set` as predecessors. Update all
/// predecessors adding the new block as a successor.
///
/// If the final statement of the predecessor block is a control-flow statement,
/// and the new block is not the true branch target of the statement, the new
/// block is added as the false branch target.
fn complete_basic_block(basic_blocks: &mut BasicBlockVec, pred_set: &IndexSet, meta: ir::Meta) {
    use ir::Statement::*;
    trace!("finalizing basic block {}", basic_blocks.last().index());
    let j = basic_blocks.len();
    basic_blocks.push(BasicBlock::new(j, meta));
    for i in pred_set {
        basic_blocks[i].add_successor(j);
        basic_blocks[j].add_predecessor(*i);

        // If the final statement `S` of block `i` is a control flow statement,
        // and `j` is not the true branch of `S`, update the false branch of `S`
        // to `j`.
        if let Some(IfThenElse { cond, true_index, false_index, .. }) =
            basic_blocks[i].statements_mut().last_mut()
        {
            if j != *true_index && false_index.is_none() {
                trace!("updating false branch target of `if {cond}`");
                *false_index = Some(j)
            }
        }
    }
}