leo_passes/code_generation/
program.rs

1// Copyright (C) 2019-2025 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo 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 Leo 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 Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17use super::*;
18
19use leo_ast::{
20    Composite,
21    Constructor,
22    Function,
23    Location,
24    Mapping,
25    Member,
26    Mode,
27    NetworkName,
28    Program,
29    ProgramScope,
30    Type,
31    UpgradeVariant,
32    Variant,
33};
34use leo_span::{Symbol, sym};
35
36use indexmap::IndexMap;
37use itertools::Itertools;
38use snarkvm::prelude::{CanaryV0, MainnetV0, TestnetV0};
39
40impl<'a> CodeGeneratingVisitor<'a> {
41    pub fn visit_program(&mut self, input: &'a Program) -> AleoProgram {
42        // Dependencies of the program. Already arranged in post order by Retriever module.
43
44        let imports = input.stubs.iter().map(|(program_name, _)| program_name.to_string()).collect();
45
46        // Retrieve the program scope.
47        // Note that type checking guarantees that there is exactly one program scope.
48        let program_scope: &ProgramScope = input.program_scopes.values().next().unwrap();
49
50        let program_id = program_scope.program_id;
51        self.program_id = Some(program_id);
52
53        // Get the post-order ordering of the composite data types.
54        // Note that the unwrap is safe since type checking guarantees that the struct dependency graph is acyclic.
55        let order = self.state.struct_graph.post_order().unwrap();
56
57        let this_program = self.program_id.unwrap().name.name;
58
59        let lookup = |name: &[Symbol]| {
60            self.state
61                .symbol_table
62                .lookup_struct(name)
63                .or_else(|| self.state.symbol_table.lookup_record(&Location::new(this_program, name.to_vec())))
64        };
65
66        // Add each `Struct` or `Record` in the post-ordering and produce an Aleo struct or record.
67        let data_types = order
68            .into_iter()
69            .filter_map(|name| lookup(&name).map(|struct_| self.visit_struct_or_record(struct_, &name)))
70            .collect();
71
72        // Visit each mapping in the Leo AST and produce an Aleo mapping declaration.
73        let mappings = program_scope.mappings.iter().map(|(_symbol, mapping)| self.visit_mapping(mapping)).collect();
74
75        // Visit each function in the program scope and produce an Aleo function.
76        // Note that in the function inlining pass, we reorder the functions such that they are in post-order.
77        // In other words, a callee function precedes its caller function in the program scope.
78        let functions = program_scope
79            .functions
80            .iter()
81            .filter_map(|(_symbol, function)| {
82                if function.variant != Variant::AsyncFunction {
83                    let mut aleo_function = self.visit_function(function);
84
85                    // Attach the associated finalize to async transitions.
86                    if function.variant == Variant::AsyncTransition {
87                        // Set state variables.
88                        self.finalize_caller = Some(function.identifier.name);
89                        // Generate code for the associated finalize function.
90                        let finalize = &self
91                            .state
92                            .symbol_table
93                            .lookup_function(&Location::new(
94                                self.program_id.unwrap().name.name,
95                                vec![function.identifier.name], // Guaranteed to live in program scope, not in any submodule
96                            ))
97                            .unwrap()
98                            .clone()
99                            .finalizer
100                            .unwrap();
101                        // Write the finalize string.
102                        if let Some(caller) = &mut aleo_function {
103                            caller.as_function_ref_mut().finalize = Some(
104                                self.visit_function_with(
105                                    &program_scope
106                                        .functions
107                                        .iter()
108                                        .find(|(name, _f)| vec![*name] == finalize.location.path)
109                                        .unwrap()
110                                        .1,
111                                    &finalize.future_inputs,
112                                )
113                                .unwrap()
114                                .as_finalize(),
115                            );
116                        }
117                    }
118                    aleo_function
119                } else {
120                    None
121                }
122            })
123            .collect();
124
125        // If the constructor exists, visit it and produce an Aleo constructor.
126        let constructor = program_scope.constructor.as_ref().map(|c| self.visit_constructor(c));
127
128        AleoProgram { imports, program_id, data_types, mappings, functions, constructor }
129    }
130
131    fn visit_struct_or_record(&mut self, struct_: &'a Composite, absolute_path: &[Symbol]) -> AleoDatatype {
132        if struct_.is_record {
133            AleoDatatype::Record(self.visit_record(struct_, absolute_path))
134        } else {
135            AleoDatatype::Struct(self.visit_struct(struct_, absolute_path))
136        }
137    }
138
139    fn visit_struct(&mut self, struct_: &'a Composite, absolute_path: &[Symbol]) -> AleoStruct {
140        // Add private symbol to composite types.
141        self.composite_mapping.insert(absolute_path.to_vec(), false); // todo: private by default here.
142
143        // todo: check if this is safe from name conflicts.
144        let name = Self::legalize_path(absolute_path).unwrap_or_else(|| {
145            panic!("path format cannot be legalized at this point: {}", absolute_path.iter().join("::"))
146        });
147
148        // Construct and append the record variables.
149        let fields = struct_
150            .members
151            .iter()
152            .filter_map(|var| {
153                if var.type_.is_empty() {
154                    None
155                } else {
156                    Some((var.identifier.to_string(), Self::visit_type(&var.type_)))
157                }
158            })
159            .collect();
160
161        AleoStruct { name, fields }
162    }
163
164    fn visit_record(&mut self, record: &'a Composite, absolute_path: &[Symbol]) -> AleoRecord {
165        // Add record symbol to composite types.
166        self.composite_mapping.insert(absolute_path.to_vec(), true);
167
168        let name = record.identifier.to_string(); // todo: check if this is safe from name conflicts.
169
170        let mut members = Vec::with_capacity(record.members.len());
171        let mut member_map: IndexMap<Symbol, Member> =
172            record.members.clone().into_iter().map(|member| (member.identifier.name, member)).collect();
173
174        // Add the owner field to the beginning of the members list.
175        // Note that type checking ensures that the owner field exists.
176        members.push(member_map.shift_remove(&sym::owner).unwrap());
177
178        // Add the remaining fields to the members list.
179        members.extend(member_map.into_iter().map(|(_, member)| member));
180
181        // Construct and append the record variables.
182        let fields = members
183            .iter()
184            .filter_map(|var| {
185                if var.type_.is_empty() {
186                    None
187                } else {
188                    Some((var.identifier.to_string(), Self::visit_type(&var.type_), match var.mode {
189                        Mode::Constant => AleoVisibility::Constant,
190                        Mode::Public => AleoVisibility::Public,
191                        Mode::None | Mode::Private => AleoVisibility::Private,
192                    }))
193                }
194            })
195            .collect();
196
197        AleoRecord { name, fields }
198    }
199
200    fn visit_function_with(&mut self, function: &'a Function, futures: &[Location]) -> Option<AleoFunctional> {
201        // Initialize the state of `self` with the appropriate values before visiting `function`.
202        self.next_register = 0;
203        self.variable_mapping = IndexMap::new();
204        self.variant = Some(function.variant);
205        // TODO: Figure out a better way to initialize.
206        self.variable_mapping.insert(sym::SelfLower, AleoExpr::Reg(AleoReg::Self_));
207        self.variable_mapping.insert(sym::block, AleoExpr::Reg(AleoReg::Block));
208        self.variable_mapping.insert(sym::network, AleoExpr::Reg(AleoReg::Network));
209        self.current_function = Some(function);
210
211        // Construct the header of the function.
212        // If a function is a program function, generate an Aleo `function`,
213        // if it is a standard function generate an Aleo `closure`,
214        // otherwise, it is an inline function, in which case a function should not be generated.
215        let function_name = match function.variant {
216            Variant::Inline => return None,
217            Variant::Script => panic!("script should not appear in native code"),
218            Variant::Transition | Variant::AsyncTransition => function.identifier.to_string(),
219            Variant::Function => function.identifier.to_string(),
220            Variant::AsyncFunction => self.finalize_caller.unwrap().to_string(),
221        };
222
223        let mut futures = futures.iter();
224
225        self.internal_record_inputs.clear();
226
227        // Construct and append the input declarations of the function.
228        let inputs: Vec<AleoInput> = function
229            .input
230            .iter()
231            .filter_map(|input| {
232                if input.type_.is_empty() {
233                    return None;
234                }
235                let register_num = self.next_register();
236
237                // Track all internal record inputs.
238                if let Type::Composite(comp) = &input.type_ {
239                    let program = comp.program.unwrap_or(self.program_id.unwrap().name.name);
240                    if let Some(record) = self
241                        .state
242                        .symbol_table
243                        .lookup_record(&Location::new(program, comp.path.absolute_path().to_vec()))
244                        && (record.external.is_none() || record.external == self.program_id.map(|id| id.name.name))
245                    {
246                        self.internal_record_inputs.insert(AleoExpr::Reg(register_num.clone()));
247                    }
248                }
249
250                let (input_type, input_visibility) = {
251                    self.variable_mapping.insert(input.identifier.name, AleoExpr::Reg(register_num.clone()));
252                    // Note that this unwrap is safe because we set the variant at the beginning of the function.
253                    let visibility = match (self.variant.unwrap(), input.mode) {
254                        (Variant::AsyncTransition, Mode::None) | (Variant::Transition, Mode::None) => {
255                            Some(AleoVisibility::Private)
256                        }
257                        (Variant::AsyncFunction, Mode::None) => Some(AleoVisibility::Public),
258                        (_, mode) => AleoVisibility::maybe_from(mode),
259                    };
260                    // Futures are displayed differently in the input section. `input r0 as foo.aleo/bar.future;`
261                    if matches!(input.type_, Type::Future(_)) {
262                        let location = futures
263                            .next()
264                            .expect("Type checking guarantees we have future locations for each future input");
265                        let [future_name] = location.path.as_slice() else {
266                            panic!(
267                                "All futures must have a single segment paths since they don't belong to submodules."
268                            )
269                        };
270                        (
271                            AleoType::Future { name: future_name.to_string(), program: location.program.to_string() },
272                            None,
273                        )
274                    } else {
275                        self.visit_type_with_visibility(&input.type_, visibility)
276                    }
277                };
278
279                Some(AleoInput { register: register_num, type_: input_type, visibility: input_visibility })
280            })
281            .collect();
282
283        //  Construct and append the function body.
284        let mut statements = self.visit_block(&function.block);
285        if matches!(self.variant.unwrap(), Variant::Function | Variant::AsyncFunction)
286            && statements.iter().all(|stm| matches!(stm, AleoStmt::Output(..)))
287        {
288            // There are no real instructions, which is invalid in Aleo, so
289            // add a dummy instruction.
290            statements.insert(0, AleoStmt::AssertEq(AleoExpr::Bool(true), AleoExpr::Bool(true)));
291        }
292
293        match function.variant {
294            Variant::Inline | Variant::Script => None,
295            Variant::Transition | Variant::AsyncTransition => {
296                Some(AleoFunctional::Function(AleoFunction { name: function_name, inputs, statements, finalize: None })) // finalize added by caller
297            }
298            Variant::Function => Some(AleoFunctional::Closure(AleoClosure { name: function_name, inputs, statements })),
299            Variant::AsyncFunction => {
300                Some(AleoFunctional::Finalize(AleoFinalize { caller_name: function_name, inputs, statements }))
301            }
302        }
303    }
304
305    fn visit_function(&mut self, function: &'a Function) -> Option<AleoFunctional> {
306        self.visit_function_with(function, &[])
307    }
308
309    fn visit_constructor(&mut self, constructor: &'a Constructor) -> AleoConstructor {
310        // Initialize the state of `self` with the appropriate values before visiting `constructor`.
311        self.next_register = 0;
312        self.variable_mapping = IndexMap::new();
313        self.variant = Some(Variant::AsyncFunction);
314        // TODO: Figure out a better way to initialize.
315        self.variable_mapping.insert(sym::SelfLower, AleoExpr::Reg(AleoReg::Self_));
316        self.variable_mapping.insert(sym::block, AleoExpr::Reg(AleoReg::Block));
317        self.variable_mapping.insert(sym::network, AleoExpr::Reg(AleoReg::Network));
318
319        // Get the upgrade variant.
320        let upgrade_variant = constructor
321            .get_upgrade_variant_with_network(self.state.network)
322            .expect("Type checking should have validated the upgrade variant");
323
324        // Construct the constructor.
325        // If the constructor is one of the standard constructors, use the hardcoded defaults.
326        let constructor = match &upgrade_variant {
327            // This is the expected snarkVM constructor bytecode for a program that is only upgradable by a fixed admin.
328            UpgradeVariant::Admin { address } => AleoConstructor {
329                is_custom: false,
330                statements: vec![AleoStmt::AssertEq(
331                    AleoExpr::RawName("program_owner".to_string()),
332                    AleoExpr::RawName(address.to_string()),
333                )],
334            },
335
336            UpgradeVariant::Checksum { mapping, key, .. } => {
337                let map_name = if mapping.program
338                    == self.program_id.expect("Program ID should be set before traversing the program").name.name
339                {
340                    let [mapping_name] = &mapping.path[..] else {
341                        panic!("Mappings are only allowed in the top level program at this stage");
342                    };
343                    mapping_name.to_string()
344                } else {
345                    mapping.to_string()
346                };
347                // This is the required snarkVM constructor bytecode for a program that is only upgradable
348                // if the new program's checksum matches the one declared in a pre-determined mapping.
349                AleoConstructor {
350                    is_custom: false,
351                    statements: vec![
352                        AleoStmt::BranchEq(
353                            AleoExpr::RawName("edition".to_string()),
354                            AleoExpr::U16(0),
355                            "end".to_string(),
356                        ),
357                        AleoStmt::Get(AleoExpr::RawName(map_name), AleoExpr::RawName(key.to_string()), AleoReg::R(0)),
358                        AleoStmt::AssertEq(AleoExpr::RawName("checksum".to_string()), AleoExpr::Reg(AleoReg::R(0))),
359                        AleoStmt::Position("end".to_string()),
360                    ],
361                }
362            }
363            UpgradeVariant::Custom => {
364                AleoConstructor { statements: self.visit_block(&constructor.block), is_custom: true }
365            }
366            UpgradeVariant::NoUpgrade => {
367                // This is the expected snarkVM constructor bytecode for a program that is not upgradable.
368                AleoConstructor {
369                    is_custom: false,
370                    statements: vec![AleoStmt::AssertEq(AleoExpr::RawName("edition".to_string()), AleoExpr::U16(0))],
371                }
372            }
373        };
374
375        // Check that the constructor is well-formed.
376        if let Err(e) = match self.state.network {
377            NetworkName::MainnetV0 => check_snarkvm_constructor::<MainnetV0>(&constructor),
378            NetworkName::TestnetV0 => check_snarkvm_constructor::<TestnetV0>(&constructor),
379            NetworkName::CanaryV0 => check_snarkvm_constructor::<CanaryV0>(&constructor),
380        } {
381            panic!("Compilation produced an invalid constructor: {e}");
382        };
383
384        // Return the constructor string.
385        constructor
386    }
387
388    fn visit_mapping(&mut self, mapping: &'a Mapping) -> AleoMapping {
389        let legalized_mapping_name = Self::legalize_path(&[mapping.identifier.name]);
390        // Create the prefix of the mapping string, e.g. `mapping foo:`.
391        let name = legalized_mapping_name
392            .clone()
393            .unwrap_or_else(|| panic!("path format cannot be legalized at this point: {}", mapping.identifier));
394
395        // Helper to construct the string associated with the type.
396        let create_type = |type_: &Type| {
397            match type_ {
398                Type::Mapping(_) | Type::Tuple(_) => panic!("Mappings cannot contain mappings or tuples."),
399                Type::Identifier(identifier) => {
400                    // Lookup the type in the composite mapping.
401                    // Note that this unwrap is safe since all struct and records have been added to the composite mapping.
402                    let is_record = self.composite_mapping.get(&vec![identifier.name]).unwrap();
403                    assert!(!is_record, "Type checking guarantees that mappings cannot contain records.");
404                    self.visit_type_with_visibility(type_, Some(AleoVisibility::Public))
405                }
406                type_ => self.visit_type_with_visibility(type_, Some(AleoVisibility::Public)),
407            }
408        };
409
410        // Create the key string, e.g. `    key as address.public`.
411        let (key_type, key_visibility) = create_type(&mapping.key_type);
412
413        // Create the value string, e.g. `    value as address.public`.
414        let (value_type, value_visibility) = create_type(&mapping.value_type);
415
416        // Add the mapping to the variable mapping.
417        self.global_mapping.insert(
418            mapping.identifier.name,
419            AleoExpr::RawName(
420                legalized_mapping_name
421                    .unwrap_or_else(|| panic!("path format cannot be legalized at this point: {}", mapping.identifier)),
422            ),
423        );
424
425        AleoMapping { name, key_type, value_type, key_visibility, value_visibility }
426    }
427}