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
31pub 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 ensure_unique_variables(&mut body, ¶meters, reports)?;
156
157 let mut env = LiftingEnvironment::from(¶meters);
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 cfg.propagate_types();
179 cfg.cache_variable_use();
180
181 Ok(cfg)
182}
183
184pub(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
200fn 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 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 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 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 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, });
269 let header_index = current_index + 1;
270
271 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 if pred_set.is_empty() {
283 pred_set.insert(basic_blocks.last().index());
284 }
285 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 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, });
304
305 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 if if_pred_set.is_empty() {
316 if_pred_set.insert(basic_blocks.last().index());
317 }
318
319 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 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 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
364fn 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 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}