leo_compiler/
compiler.rs

1// Copyright (C) 2019-2024 Aleo Systems 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
17//! The compiler for Leo programs.
18//!
19//! The [`Compiler`] type compiles Leo programs into R1CS circuits.
20
21use crate::CompilerOptions;
22
23pub use leo_ast::Ast;
24use leo_ast::{NodeBuilder, Program, Stub};
25use leo_errors::{CompilerError, Result, emitter::Handler};
26pub use leo_passes::SymbolTable;
27use leo_passes::*;
28use leo_span::{Symbol, source_map::FileName, symbol::with_session_globals};
29
30use snarkvm::prelude::Network;
31
32use indexmap::{IndexMap, IndexSet};
33use sha2::{Digest, Sha256};
34use std::{fs, path::PathBuf};
35
36/// The primary entry point of the Leo compiler.
37#[derive(Clone)]
38pub struct Compiler<'a, N: Network> {
39    /// The handler is used for error and warning emissions.
40    handler: &'a Handler,
41    /// The path to the main leo file.
42    main_file_path: PathBuf,
43    /// The path to where the compiler outputs all generated files.
44    output_directory: PathBuf,
45    /// The program name,
46    pub program_name: String,
47    /// The network name,
48    pub network: String,
49    /// The AST for the program.
50    pub ast: Ast,
51    /// Options configuring compilation.
52    compiler_options: CompilerOptions,
53    /// The `NodeCounter` used to generate sequentially increasing `NodeID`s.
54    node_builder: NodeBuilder,
55    /// The `Assigner` is used to construct (unique) assignment statements.
56    assigner: Assigner,
57    /// The type table.
58    type_table: TypeTable,
59    /// The stubs for imported programs. Produced by `Retriever` module.
60    import_stubs: IndexMap<Symbol, Stub>,
61    // Allows the compiler to be generic over the network.
62    phantom: std::marker::PhantomData<N>,
63}
64
65impl<'a, N: Network> Compiler<'a, N> {
66    /// Returns a new Leo compiler.
67    pub fn new(
68        program_name: String,
69        network: String,
70        handler: &'a Handler,
71        main_file_path: PathBuf,
72        output_directory: PathBuf,
73        compiler_options: Option<CompilerOptions>,
74        import_stubs: IndexMap<Symbol, Stub>,
75    ) -> Self {
76        let node_builder = NodeBuilder::default();
77        let assigner = Assigner::default();
78        let type_table = TypeTable::default();
79        Self {
80            handler,
81            main_file_path,
82            output_directory,
83            program_name,
84            network,
85            ast: Ast::new(Program::default()),
86            compiler_options: compiler_options.unwrap_or_default(),
87            node_builder,
88            assigner,
89            import_stubs,
90            type_table,
91            phantom: Default::default(),
92        }
93    }
94
95    /// Returns a SHA256 checksum of the program file.
96    pub fn checksum(&self) -> Result<String> {
97        // Read in the main file as string
98        let unparsed_file = fs::read_to_string(&self.main_file_path)
99            .map_err(|e| CompilerError::file_read_error(self.main_file_path.clone(), e))?;
100
101        // Hash the file contents
102        let mut hasher = Sha256::new();
103        hasher.update(unparsed_file.as_bytes());
104        let hash = hasher.finalize();
105
106        Ok(format!("{hash:x}"))
107    }
108
109    /// Parses and stores a program file content from a string, constructs a syntax tree, and generates a program.
110    pub fn parse_program_from_string(&mut self, program_string: &str, name: FileName) -> Result<()> {
111        // Register the source (`program_string`) in the source map.
112        let prg_sf = with_session_globals(|s| s.source_map.new_source(program_string, name));
113
114        // Use the parser to construct the abstract syntax tree (ast).
115        self.ast = leo_parser::parse_ast::<N>(self.handler, &self.node_builder, &prg_sf.src, prg_sf.start_pos)?;
116
117        // If the program is imported, then check that the name of its program scope matches the file name.
118        // Note that parsing enforces that there is exactly one program scope in a file.
119        // TODO: Clean up check.
120        let program_scope = self.ast.ast.program_scopes.values().next().unwrap();
121        let program_scope_name = format!("{}", program_scope.program_id.name);
122        if program_scope_name != self.program_name {
123            return Err(CompilerError::program_scope_name_does_not_match(
124                program_scope_name,
125                self.program_name.clone(),
126                program_scope.program_id.name.span,
127            )
128            .into());
129        }
130
131        if self.compiler_options.output.initial_ast {
132            self.write_ast_to_json("initial_ast.json")?;
133        }
134
135        Ok(())
136    }
137
138    /// Parses and stores the main program file, constructs a syntax tree, and generates a program.
139    pub fn parse_program(&mut self) -> Result<()> {
140        // Load the program file.
141        let program_string = fs::read_to_string(&self.main_file_path)
142            .map_err(|e| CompilerError::file_read_error(&self.main_file_path, e))?;
143
144        self.parse_program_from_string(&program_string, FileName::Real(self.main_file_path.clone()))
145    }
146
147    /// Runs the symbol table pass.
148    pub fn symbol_table_pass(&self) -> Result<SymbolTable> {
149        let symbol_table = SymbolTableCreator::do_pass((&self.ast, self.handler))?;
150        if self.compiler_options.output.initial_symbol_table {
151            self.write_symbol_table_to_json("initial_symbol_table.json", &symbol_table)?;
152        }
153        Ok(symbol_table)
154    }
155
156    /// Runs the type checker pass.
157    pub fn type_checker_pass(&'a self, symbol_table: SymbolTable) -> Result<(SymbolTable, StructGraph, CallGraph)> {
158        let (symbol_table, struct_graph, call_graph) =
159            TypeChecker::<N>::do_pass((&self.ast, self.handler, symbol_table, &self.type_table))?;
160        if self.compiler_options.output.type_checked_symbol_table {
161            self.write_symbol_table_to_json("type_checked_symbol_table.json", &symbol_table)?;
162        }
163        Ok((symbol_table, struct_graph, call_graph))
164    }
165
166    /// Runs the static analysis pass.
167    pub fn static_analysis_pass(&mut self, symbol_table: &SymbolTable) -> Result<()> {
168        StaticAnalyzer::<N>::do_pass((
169            &self.ast,
170            self.handler,
171            symbol_table,
172            &self.type_table,
173            self.compiler_options.build.conditional_block_max_depth,
174            self.compiler_options.build.disable_conditional_branch_type_checking,
175        ))
176    }
177
178    /// Runs the loop unrolling pass.
179    pub fn loop_unrolling_pass(&mut self, symbol_table: SymbolTable) -> Result<SymbolTable> {
180        let (ast, symbol_table) = Unroller::do_pass((
181            std::mem::take(&mut self.ast),
182            self.handler,
183            &self.node_builder,
184            symbol_table,
185            &self.type_table,
186        ))?;
187        self.ast = ast;
188
189        if self.compiler_options.output.unrolled_ast {
190            self.write_ast_to_json("unrolled_ast.json")?;
191        }
192
193        if self.compiler_options.output.unrolled_symbol_table {
194            self.write_symbol_table_to_json("unrolled_symbol_table.json", &symbol_table)?;
195        }
196
197        Ok(symbol_table)
198    }
199
200    /// Runs the static single assignment pass.
201    pub fn static_single_assignment_pass(&mut self, symbol_table: &SymbolTable) -> Result<()> {
202        self.ast = StaticSingleAssigner::do_pass((
203            std::mem::take(&mut self.ast),
204            &self.node_builder,
205            &self.assigner,
206            symbol_table,
207            &self.type_table,
208        ))?;
209
210        if self.compiler_options.output.ssa_ast {
211            self.write_ast_to_json("ssa_ast.json")?;
212        }
213
214        Ok(())
215    }
216
217    /// Runs the flattening pass.
218    pub fn flattening_pass(&mut self, symbol_table: &SymbolTable) -> Result<()> {
219        self.ast = Flattener::do_pass((
220            std::mem::take(&mut self.ast),
221            symbol_table,
222            &self.type_table,
223            &self.node_builder,
224            &self.assigner,
225        ))?;
226
227        if self.compiler_options.output.flattened_ast {
228            self.write_ast_to_json("flattened_ast.json")?;
229        }
230
231        Ok(())
232    }
233
234    /// Runs the destructuring pass.
235    pub fn destructuring_pass(&mut self) -> Result<()> {
236        self.ast = Destructurer::do_pass((
237            std::mem::take(&mut self.ast),
238            &self.type_table,
239            &self.node_builder,
240            &self.assigner,
241        ))?;
242
243        if self.compiler_options.output.destructured_ast {
244            self.write_ast_to_json("destructured_ast.json")?;
245        }
246
247        Ok(())
248    }
249
250    /// Runs the function inlining pass.
251    pub fn function_inlining_pass(&mut self, call_graph: &CallGraph) -> Result<()> {
252        let ast = FunctionInliner::do_pass((
253            std::mem::take(&mut self.ast),
254            &self.node_builder,
255            call_graph,
256            &self.assigner,
257            &self.type_table,
258        ))?;
259        self.ast = ast;
260
261        if self.compiler_options.output.inlined_ast {
262            self.write_ast_to_json("inlined_ast.json")?;
263        }
264
265        Ok(())
266    }
267
268    /// Runs the dead code elimination pass.
269    pub fn dead_code_elimination_pass(&mut self) -> Result<()> {
270        if self.compiler_options.build.dce_enabled {
271            self.ast = DeadCodeEliminator::do_pass((std::mem::take(&mut self.ast), &self.node_builder))?;
272        }
273
274        if self.compiler_options.output.dce_ast {
275            self.write_ast_to_json("dce_ast.json")?;
276        }
277
278        Ok(())
279    }
280
281    /// Runs the code generation pass.
282    pub fn code_generation_pass(
283        &mut self,
284        symbol_table: &SymbolTable,
285        struct_graph: &StructGraph,
286        call_graph: &CallGraph,
287    ) -> Result<String> {
288        CodeGenerator::do_pass((&self.ast, symbol_table, &self.type_table, struct_graph, call_graph, &self.ast.ast))
289    }
290
291    /// Runs the compiler stages.
292    pub fn compiler_stages(&mut self) -> Result<(SymbolTable, StructGraph, CallGraph)> {
293        let st = self.symbol_table_pass()?;
294
295        let (st, struct_graph, call_graph) = self.type_checker_pass(st)?;
296
297        self.static_analysis_pass(&st)?;
298
299        // TODO: Make this pass optional.
300        let st = self.loop_unrolling_pass(st)?;
301
302        self.static_single_assignment_pass(&st)?;
303
304        self.flattening_pass(&st)?;
305
306        self.destructuring_pass()?;
307
308        self.function_inlining_pass(&call_graph)?;
309
310        self.dead_code_elimination_pass()?;
311
312        Ok((st, struct_graph, call_graph))
313    }
314
315    /// Returns a compiled Leo program.
316    pub fn compile(&mut self) -> Result<String> {
317        // Parse the program.
318        self.parse_program()?;
319        // Copy the dependencies specified in `program.json` into the AST.
320        self.add_import_stubs()?;
321        // Run the intermediate compiler stages.
322        let (symbol_table, struct_graph, call_graph) = self.compiler_stages()?;
323        // Run code generation.
324        let bytecode = self.code_generation_pass(&symbol_table, &struct_graph, &call_graph)?;
325        Ok(bytecode)
326    }
327
328    /// Writes the AST to a JSON file.
329    fn write_ast_to_json(&self, file_suffix: &str) -> Result<()> {
330        // Remove `Span`s if they are not enabled.
331        if self.compiler_options.output.ast_spans_enabled {
332            self.ast.to_json_file(self.output_directory.clone(), &format!("{}.{file_suffix}", self.program_name))?;
333        } else {
334            self.ast.to_json_file_without_keys(
335                self.output_directory.clone(),
336                &format!("{}.{file_suffix}", self.program_name),
337                &["_span", "span"],
338            )?;
339        }
340        Ok(())
341    }
342
343    /// Writes the Symbol Table to a JSON file.
344    fn write_symbol_table_to_json(&self, file_suffix: &str, symbol_table: &SymbolTable) -> Result<()> {
345        // Remove `Span`s if they are not enabled.
346        if self.compiler_options.output.symbol_table_spans_enabled {
347            symbol_table
348                .to_json_file(self.output_directory.clone(), &format!("{}.{file_suffix}", self.program_name))?;
349        } else {
350            symbol_table.to_json_file_without_keys(
351                self.output_directory.clone(),
352                &format!("{}.{file_suffix}", self.program_name),
353                &["_span", "span"],
354            )?;
355        }
356        Ok(())
357    }
358
359    /// Merges the dependencies defined in `program.json` with the dependencies imported in `.leo` file
360    pub fn add_import_stubs(&mut self) -> Result<()> {
361        // Create a list of both the explicit dependencies specified in the `.leo` file, as well as the implicit ones derived from those dependencies.
362        let (mut unexplored, mut explored): (IndexSet<Symbol>, IndexSet<Symbol>) =
363            (self.ast.ast.imports.keys().cloned().collect(), IndexSet::new());
364        while !unexplored.is_empty() {
365            let mut current_dependencies: IndexSet<Symbol> = IndexSet::new();
366            for program_name in unexplored.iter() {
367                if let Some(stub) = self.import_stubs.get(program_name) {
368                    // Add the program to the explored set
369                    explored.insert(*program_name);
370                    for dependency in stub.imports.iter() {
371                        // If dependency is already explored then don't need to re-explore it
372                        if explored.insert(dependency.name.name) {
373                            current_dependencies.insert(dependency.name.name);
374                        }
375                    }
376                } else {
377                    return Err(CompilerError::imported_program_not_found(
378                        self.program_name.clone(),
379                        *program_name,
380                        self.ast.ast.imports[program_name].1,
381                    )
382                    .into());
383                }
384            }
385
386            // Create next batch to explore
387            unexplored = current_dependencies;
388        }
389
390        // Combine the dependencies from `program.json` and `.leo` file while preserving the post-order
391        self.ast.ast.stubs =
392            self.import_stubs.clone().into_iter().filter(|(program_name, _)| explored.contains(program_name)).collect();
393        Ok(())
394    }
395}