leo_passes/
const_prop_unroll_and_morphing.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 crate::{
18    CompilerState,
19    ConstPropagation,
20    Monomorphization,
21    Pass,
22    RemoveUnreachable,
23    SymbolTableCreation,
24    TypeChecking,
25    TypeCheckingInput,
26    Unrolling,
27};
28
29use leo_ast::Node;
30use leo_errors::{CompilerError, Result};
31
32/// Pass that runs const propagation, loop unrolling, and monomorphization until a fixed point.
33pub struct ConstPropUnrollAndMorphing;
34
35impl Pass for ConstPropUnrollAndMorphing {
36    type Input = TypeCheckingInput;
37    type Output = ();
38
39    const NAME: &str = "ConstantPropogation+LoopUnrolling+Monomorphization";
40
41    fn do_pass(input: Self::Input, state: &mut CompilerState) -> Result<Self::Output> {
42        const LARGE_LOOP_BOUND: usize = 1024usize;
43
44        for _ in 0..LARGE_LOOP_BOUND {
45            let loop_unroll_output = Unrolling::do_pass((), state)?;
46
47            let const_prop_output = ConstPropagation::do_pass((), state)?;
48
49            let remove_unreachable_output = RemoveUnreachable::do_pass((), state)?;
50
51            let monomorphization_output = Monomorphization::do_pass((), state)?;
52
53            // Clear the symbol table and create it again. This is important because after all the passes above run, the
54            // program may have changed significantly (new functions may have been added, some functions may have been
55            // deleted, etc.) We do want to retain evaluated consts, so that const propagation can tell when it has evaluated a new one.
56            state.symbol_table.reset_but_consts();
57            SymbolTableCreation::do_pass((), state)?;
58
59            // Now run the type checker again to validate and infer types. Again, this is important because the program
60            // may have changed significantly after the passes above.
61            TypeChecking::do_pass(input.clone(), state)?;
62
63            if !const_prop_output.changed
64                && !loop_unroll_output.loop_unrolled
65                && !monomorphization_output.changed
66                && !remove_unreachable_output.changed
67            {
68                // We've got a fixed point, so see if we have any errors.
69                if let Some(not_evaluated_span) = const_prop_output.const_not_evaluated {
70                    return Err(CompilerError::const_not_evaluated(not_evaluated_span).into());
71                }
72
73                if let Some(not_evaluated_span) = const_prop_output.array_index_not_evaluated {
74                    return Err(CompilerError::array_index_not_evaluated(not_evaluated_span).into());
75                }
76
77                if let Some(not_evaluated_span) = const_prop_output.repeat_count_not_evaluated {
78                    return Err(CompilerError::repeat_count_not_evaluated(not_evaluated_span).into());
79                }
80
81                if let Some(not_evaluated_span) = const_prop_output.array_length_not_evaluated {
82                    return Err(CompilerError::array_length_not_evaluated(not_evaluated_span).into());
83                }
84
85                // Emit errors for all problematic calls
86                for call in &monomorphization_output.unresolved_calls {
87                    if let Some(arg) =
88                        call.const_arguments.iter().find(|arg| !matches!(arg, leo_ast::Expression::Literal(_)))
89                    {
90                        state.handler.emit_err(CompilerError::const_generic_not_resolved(
91                            "call to generic function",
92                            call.function.clone(),
93                            arg.span(),
94                        ));
95                    }
96                }
97
98                // Emit errors for all problematic struct expressions
99                for expr in &monomorphization_output.unresolved_struct_exprs {
100                    if let Some(arg) =
101                        expr.const_arguments.iter().find(|arg| !matches!(arg, leo_ast::Expression::Literal(_)))
102                    {
103                        state.handler.emit_err(CompilerError::const_generic_not_resolved(
104                            "struct expression",
105                            expr.path.clone(),
106                            arg.span(),
107                        ));
108                    }
109                }
110
111                // Emit errors for all problematic struct type instantiations
112                for ty in &monomorphization_output.unresolved_struct_types {
113                    if let Some(arg) =
114                        ty.const_arguments.iter().find(|arg| !matches!(arg, leo_ast::Expression::Literal(_)))
115                    {
116                        state.handler.emit_err(CompilerError::const_generic_not_resolved(
117                            "struct type",
118                            ty.path.clone(),
119                            arg.span(),
120                        ));
121                    }
122                }
123
124                // Exit with the handler's last error.
125                state.handler.last_err()?;
126
127                if let Some(not_unrolled_span) = loop_unroll_output.loop_not_unrolled {
128                    return Err(CompilerError::loop_bounds_not_evaluated(not_unrolled_span).into());
129                }
130
131                return Ok(());
132            }
133        }
134
135        // Note that it's challenging to write code in practice that demonstrates this error, because Leo code
136        // with many nested loops or operations will blow the stack in the compiler before this bound is hit.
137        Err(CompilerError::const_prop_unroll_many_loops(LARGE_LOOP_BOUND, Default::default()).into())
138    }
139}