/ compiler / passes / src / code_generation / statement.rs
statement.rs
  1  // Copyright (C) 2019-2025 ADnet Contributors
  2  // This file is part of the ADL library.
  3  
  4  // The ADL library is free software: you can redistribute it and/or modify
  5  // it under the terms of the GNU General Public License as published by
  6  // the Free Software Foundation, either version 3 of the License, or
  7  // (at your option) any later version.
  8  
  9  // The ADL library is distributed in the hope that it will be useful,
 10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
 11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 12  // GNU General Public License for more details.
 13  
 14  // You should have received a copy of the GNU General Public License
 15  // along with the ADL library. If not, see <https://www.gnu.org/licenses/>.
 16  
 17  use super::*;
 18  
 19  use adl_ast::{
 20      AssertStatement,
 21      AssertVariant,
 22      AssignStatement,
 23      Block,
 24      ConditionalStatement,
 25      DefinitionPlace,
 26      DefinitionStatement,
 27      Expression,
 28      ExpressionStatement,
 29      IterationStatement,
 30      Mode,
 31      Output,
 32      ReturnStatement,
 33      Statement,
 34      Type,
 35  };
 36  
 37  use indexmap::IndexMap;
 38  
 39  impl CodeGeneratingVisitor<'_> {
 40      fn visit_statement(&mut self, input: &Statement) -> Vec<AleoStmt> {
 41          match input {
 42              Statement::Assert(stmt) => self.visit_assert(stmt),
 43              Statement::Assign(stmt) => vec![self.visit_assign(stmt)],
 44              Statement::Block(stmt) => self.visit_block(stmt),
 45              Statement::Conditional(stmt) => self.visit_conditional(stmt),
 46              Statement::Const(_) => {
 47                  panic!("`ConstStatement`s should not be in the AST at this phase of compilation.")
 48              }
 49              Statement::Definition(stmt) => self.visit_definition(stmt),
 50              Statement::Expression(stmt) => self.visit_expression_statement(stmt),
 51              Statement::Iteration(stmt) => vec![self.visit_iteration(stmt)],
 52              Statement::Return(stmt) => self.visit_return(stmt),
 53          }
 54      }
 55  
 56      fn visit_assert(&mut self, input: &AssertStatement) -> Vec<AleoStmt> {
 57          match &input.variant {
 58              AssertVariant::Assert(expr) => {
 59                  let (operand, mut instructions) = self.visit_expression(expr);
 60                  let operand = operand.expect("Trying to assert an empty expression.");
 61                  instructions.push(AleoStmt::AssertEq(operand, AleoExpr::Bool(true)));
 62                  instructions
 63              }
 64              AssertVariant::AssertEq(left, right) => {
 65                  let (left, left_stmts) = self.visit_expression(left);
 66                  let (right, right_stmts) = self.visit_expression(right);
 67                  let left = left.expect("Trying to assert an empty expression.");
 68                  let right = right.expect("Trying to assert an empty expression.");
 69                  let assert_instruction = AleoStmt::AssertEq(left, right);
 70  
 71                  // Concatenate the instructions.
 72                  let mut instructions = left_stmts;
 73                  instructions.extend(right_stmts);
 74                  instructions.push(assert_instruction);
 75                  instructions
 76              }
 77              AssertVariant::AssertNeq(left, right) => {
 78                  let (left, left_stmts) = self.visit_expression(left);
 79                  let (right, right_stmts) = self.visit_expression(right);
 80                  let left = left.expect("Trying to assert an empty expression.");
 81                  let right = right.expect("Trying to assert an empty expression.");
 82                  let assert_instruction = AleoStmt::AssertNeq(left, right);
 83  
 84                  // Concatenate the instructions.
 85                  let mut instructions = left_stmts;
 86                  instructions.extend(right_stmts);
 87                  instructions.push(assert_instruction);
 88                  instructions
 89              }
 90          }
 91      }
 92  
 93      fn visit_return(&mut self, input: &ReturnStatement) -> Vec<AleoStmt> {
 94          let mut instructions = vec![];
 95          let mut operands: IndexMap<AleoExpr, &Output> =
 96              IndexMap::with_capacity(self.current_function.unwrap().output.len());
 97  
 98          if let Expression::Tuple(tuple) = &input.expression {
 99              // Now tuples only appear in return position, so let's handle this
100              // ourselves.
101              let outputs = &self.current_function.unwrap().output;
102              assert_eq!(tuple.elements.len(), outputs.len());
103  
104              for (expr, output) in tuple.elements.iter().zip_eq(outputs) {
105                  let (operand, op_instructions) = self.visit_expression(expr);
106                  instructions.extend(op_instructions);
107                  if let Some(operand) = operand {
108                      if self.internal_record_inputs.contains(&operand) || operands.contains_key(&operand) {
109                          // We can't output an internal record we received as input.
110                          // We also can't output the same value twice.
111                          // Either way, clone it.
112                          let (new_operand, new_instr) = self.clone_register(&operand, &output.type_);
113                          instructions.extend(new_instr);
114                          operands.insert(new_operand, output);
115                      } else {
116                          operands.insert(operand, output);
117                      }
118                  }
119              }
120          } else {
121              // Not a tuple - only one output.
122              let (operand, op_instructions) = self.visit_expression(&input.expression);
123              if let Some(operand) = operand {
124                  let output = &self.current_function.unwrap().output[0];
125  
126                  if self.internal_record_inputs.contains(&operand) {
127                      // We can't output an internal record we received as input.
128                      let (new_operand, new_instr) =
129                          self.clone_register(&operand, &self.current_function.unwrap().output_type);
130                      instructions.extend(new_instr);
131                      operands.insert(new_operand, output);
132                  } else {
133                      instructions = op_instructions;
134                      operands.insert(operand, output);
135                  }
136              }
137          }
138  
139          for (operand, output) in operands.iter() {
140              // Transitions outputs with no mode are private.
141              let visibility = match (self.variant.unwrap().is_transition(), output.mode) {
142                  (true, Mode::None) => Some(AleoVisibility::Private),
143                  (_, mode) => AleoVisibility::maybe_from(mode),
144              };
145              if let Type::Future(_) = output.type_ {
146                  instructions.push(AleoStmt::Output(
147                      operand.clone(),
148                      AleoType::Future {
149                          name: self.current_function.unwrap().identifier.to_string(),
150                          program: self.program_id.unwrap().name.to_string(),
151                          suffix: self.state.target.suffix().to_string(),
152                      },
153                      None,
154                  ));
155              } else if output.type_.is_empty() {
156                  // do nothing
157              } else {
158                  let (output_type, output_viz) = self.visit_type_with_visibility(&output.type_, visibility);
159                  instructions.push(AleoStmt::Output(operand.clone(), output_type, output_viz));
160              }
161          }
162  
163          instructions
164      }
165  
166      fn visit_definition(&mut self, input: &DefinitionStatement) -> Vec<AleoStmt> {
167          match (&input.place, &input.value) {
168              (DefinitionPlace::Single(identifier), _) => {
169                  let (operand, expression_instructions) = self.visit_expression(&input.value);
170                  if let Some(operand) = operand {
171                      self.variable_mapping.insert(identifier.name, operand);
172                  }
173                  expression_instructions
174              }
175              (DefinitionPlace::Multiple(identifiers), Expression::Call(_)) => {
176                  let (operand, expression_instructions) = self.visit_expression(&input.value);
177                  let Some(AleoExpr::Tuple(elems)) = operand else {
178                      panic!("Definition with multiple identifiers should yield a tuple")
179                  };
180                  // Add the destinations to the variable mapping.
181                  for (identifier, operand) in identifiers.iter().zip_eq(elems.iter()) {
182                      self.variable_mapping.insert(identifier.name, operand.clone());
183                  }
184                  expression_instructions
185              }
186              _ => panic!("Previous passes should have ensured that a definition with multiple identifiers is a `Call`."),
187          }
188      }
189  
190      fn visit_expression_statement(&mut self, input: &ExpressionStatement) -> Vec<AleoStmt> {
191          self.visit_expression(&input.expression).1
192      }
193  
194      fn visit_assign(&mut self, _input: &AssignStatement) -> AleoStmt {
195          panic!("AssignStatement's should not exist in SSA form.")
196      }
197  
198      fn visit_conditional(&mut self, _input: &ConditionalStatement) -> Vec<AleoStmt> {
199          // Note that this unwrap is safe because we set the variant before traversing the function.
200          if !self.variant.unwrap().is_async_function() {
201              panic!("`ConditionalStatement`s should not be in the AST at this phase of compilation.")
202          } else {
203              // Construct a label for the end of the `then` block.
204              let end_then_label = format!("end_then_{}_{}", self.conditional_depth, self.next_label);
205              self.next_label += 1;
206              // Construct a label for the end of the `otherwise` block if it exists.
207              let (has_otherwise, end_otherwise_label) = {
208                  match _input.otherwise.is_some() {
209                      true => {
210                          // Construct a label for the end of the `otherwise` block.
211                          let end_otherwise_label =
212                              { format!("end_otherwise_{}_{}", self.conditional_depth, self.next_label) };
213                          self.next_label += 1;
214                          (true, end_otherwise_label)
215                      }
216                      false => (false, String::new()),
217                  }
218              };
219  
220              // Increment the conditional depth.
221              self.conditional_depth += 1;
222  
223              // Create a `branch` instruction.
224              let (condition, mut instructions) = self.visit_expression(&_input.condition);
225              let condition = condition.expect("Trying to branch on an empty expression");
226              instructions.push(AleoStmt::BranchEq(condition, AleoExpr::Bool(false), end_then_label.clone()));
227  
228              // Visit the `then` block.
229              instructions.extend(self.visit_block(&_input.then));
230              // If the `otherwise` block is present, add a branch instruction to jump to the end of the `otherwise` block.
231              if has_otherwise {
232                  instructions.push(AleoStmt::BranchEq(
233                      AleoExpr::Bool(true),
234                      AleoExpr::Bool(true),
235                      end_otherwise_label.clone(),
236                  ));
237              }
238  
239              // Add a label for the end of the `then` block.
240              instructions.push(AleoStmt::Position(end_then_label));
241  
242              // Visit the `otherwise` block.
243              if let Some(else_block) = &_input.otherwise {
244                  // Visit the `otherwise` block.
245                  instructions.extend(self.visit_statement(else_block));
246                  // Add a label for the end of the `otherwise` block.
247                  instructions.push(AleoStmt::Position(end_otherwise_label));
248              }
249  
250              // Decrement the conditional depth.
251              self.conditional_depth -= 1;
252  
253              instructions
254          }
255      }
256  
257      fn visit_iteration(&mut self, _input: &IterationStatement) -> AleoStmt {
258          panic!("`IterationStatement`s should not be in the AST at this phase of compilation.");
259      }
260  
261      pub(crate) fn visit_block(&mut self, input: &Block) -> Vec<AleoStmt> {
262          // For each statement in the block, visit it and add its instructions to the list.
263          input.statements.iter().flat_map(|stmt| self.visit_statement(stmt)).collect()
264      }
265  }