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