1use crate::{AstSnapshots, CompilerOptions};
22
23pub use leo_ast::{Ast, DiGraph, Program};
24use leo_ast::{Library, NetworkName, NodeBuilder, Stub};
25use leo_errors::{CompilerError, Handler, Result};
26use leo_passes::*;
27use leo_span::{
28 Span,
29 Symbol,
30 file_source::{DiskFileSource, FileSource},
31 source_map::FileName,
32 with_session_globals,
33};
34
35use std::{
36 fs,
37 path::{Path, PathBuf},
38 rc::Rc,
39};
40
41use indexmap::IndexMap;
42
43pub struct CompiledProgram {
45 pub name: String,
47 pub bytecode: String,
49 pub abi: leo_abi::Program,
51}
52
53pub struct Compiled {
55 pub primary: CompiledProgram,
57 pub imports: Vec<CompiledProgram>,
59}
60
61pub struct Compiler {
63 output_directory: PathBuf,
65 pub program_name: Option<String>,
67 compiler_options: CompilerOptions,
69 state: CompilerState,
71 import_stubs: IndexMap<Symbol, Stub>,
73 pub statements_before_dce: u32,
75 pub statements_after_dce: u32,
77}
78
79impl Compiler {
80 pub fn network(&self) -> NetworkName {
81 self.state.network
82 }
83
84 pub fn parse_program(&mut self, source: &str, filename: FileName, modules: &[(&str, FileName)]) -> Result<()> {
94 let source_file = with_session_globals(|s| s.source_map.new_source(source, filename.clone()));
96
97 let modules = modules
99 .iter()
100 .map(|(source, filename)| with_session_globals(|s| s.source_map.new_source(source, filename.clone())))
101 .collect::<Vec<_>>();
102
103 let program = leo_parser::parse_program(
105 self.state.handler.clone(),
106 &self.state.node_builder,
107 &source_file,
108 &modules,
109 self.state.network,
110 )?;
111
112 let program_scope = program.program_scopes.values().next().unwrap();
115 if let Some(program_name) = &self.program_name {
116 if program_name != &program_scope.program_id.as_symbol().to_string() {
117 return Err(CompilerError::program_name_should_match_file_name(
118 program_scope.program_id.as_symbol(),
119 if self.state.is_test {
121 format!(
122 "`{}` (the test file name)",
123 filename.to_string().split("/").last().expect("Could not get file name")
124 )
125 } else {
126 format!("`{program_name}` (specified in `program.json`)")
127 },
128 program_scope.program_id.span(),
129 )
130 .into());
131 }
132 } else {
133 self.program_name = Some(program_scope.program_id.as_symbol().to_string());
134 }
135
136 self.state.ast = Ast::Program(program);
137
138 if self.compiler_options.initial_ast {
139 self.write_ast_to_json("initial.json")?;
140 self.write_ast("initial.ast")?;
141 }
142
143 Ok(())
144 }
145
146 pub fn parse_and_return_program(
148 &mut self,
149 source: &str,
150 filename: FileName,
151 modules: &[(&str, FileName)],
152 ) -> Result<Program> {
153 self.parse_program(source, filename, modules)?;
155
156 match &self.state.ast {
157 Ast::Program(program) => Ok(program.clone()),
158 Ast::Library(_) => unreachable!("expected Program AST"),
159 }
160 }
161
162 pub fn parse_and_return_library(
164 &mut self,
165 library_name: &str,
166 source: &str,
167 filename: FileName,
168 modules: &[(&str, FileName)],
169 ) -> Result<Library> {
170 self.parse_library(Symbol::intern(library_name), source, filename, modules)?;
171
172 match &self.state.ast {
173 Ast::Program(_) => unreachable!("expected Library AST"),
174 Ast::Library(library) => Ok(library.clone()),
175 }
176 }
177
178 pub fn parse_library(
183 &mut self,
184 library_name: Symbol,
185 source: &str,
186 filename: FileName,
187 modules: &[(&str, FileName)],
188 ) -> Result<()> {
189 let source_file = with_session_globals(|s| s.source_map.new_source(source, filename.clone()));
190
191 let module_files = modules
193 .iter()
194 .map(|(src, name)| with_session_globals(|s| s.source_map.new_source(src, name.clone())))
195 .collect::<Vec<_>>();
196
197 self.state.ast = Ast::Library(leo_parser::parse_library(
198 self.state.handler.clone(),
199 &self.state.node_builder,
200 library_name,
201 &source_file,
202 &module_files,
203 self.state.network,
204 )?);
205
206 Ok(())
207 }
208
209 #[allow(clippy::too_many_arguments)]
211 pub fn new(
212 expected_program_name: Option<String>,
213 is_test: bool,
214 handler: Handler,
215 node_builder: Rc<NodeBuilder>,
216 output_directory: PathBuf,
217 compiler_options: Option<CompilerOptions>,
218 import_stubs: IndexMap<Symbol, Stub>,
219 network: NetworkName,
220 ) -> Self {
221 Self {
222 state: CompilerState {
223 handler,
224 node_builder: Rc::clone(&node_builder),
225 is_test,
226 network,
227 ..Default::default()
228 },
229 output_directory,
230 program_name: expected_program_name,
231 compiler_options: compiler_options.unwrap_or_default(),
232 import_stubs,
233 statements_before_dce: 0,
234 statements_after_dce: 0,
235 }
236 }
237
238 pub fn do_pass<P: Pass>(&mut self, input: P::Input) -> Result<P::Output> {
239 let output = P::do_pass(input, &mut self.state)?;
240
241 let write = match &self.compiler_options.ast_snapshots {
242 AstSnapshots::All => true,
243 AstSnapshots::Some(passes) => passes.contains(P::NAME),
244 };
245
246 if write {
247 self.write_ast_to_json(&format!("{}.json", P::NAME))?;
248 self.write_ast(&format!("{}.ast", P::NAME))?;
249 }
250
251 Ok(output)
252 }
253
254 pub fn frontend_passes(&mut self) -> Result<()> {
256 self.do_pass::<NameValidation>(())?;
257 self.do_pass::<GlobalVarsCollection>(())?;
258 self.do_pass::<PathResolution>(())?;
259 self.do_pass::<GlobalItemsCollection>(())?;
260 self.do_pass::<CheckInterfaces>(())?;
261 self.do_pass::<TypeChecking>(TypeCheckingInput::new(self.state.network))?;
262 self.do_pass::<Disambiguate>(())?;
263 self.do_pass::<ProcessingAsync>(TypeCheckingInput::new(self.state.network))?;
264 self.do_pass::<StaticAnalyzing>(())
265 }
266
267 pub fn intermediate_passes(&mut self) -> Result<(leo_abi::Program, IndexMap<String, leo_abi::Program>)> {
273 let type_checking_config = TypeCheckingInput::new(self.state.network);
274
275 self.frontend_passes()?;
276
277 self.do_pass::<ConstPropUnrollAndMorphing>(type_checking_config.clone())?;
278
279 let abis = self.generate_abi();
282
283 self.do_pass::<StorageLowering>(type_checking_config.clone())?;
284
285 self.do_pass::<OptionLowering>(type_checking_config)?;
286
287 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: true })?;
288
289 self.do_pass::<Destructuring>(())?;
290
291 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
292
293 self.do_pass::<WriteTransforming>(())?;
294
295 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
296
297 self.do_pass::<Flattening>(())?;
298
299 self.do_pass::<FunctionInlining>(())?;
300
301 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
303
304 self.do_pass::<SsaConstPropagation>(())?;
305
306 self.do_pass::<SsaForming>(SsaFormingInput { rename_defs: false })?;
307
308 self.do_pass::<CommonSubexpressionEliminating>(())?;
309
310 let output = self.do_pass::<DeadCodeEliminating>(())?;
311 self.statements_before_dce = output.statements_before;
312 self.statements_after_dce = output.statements_after;
313
314 Ok(abis)
315 }
316
317 fn generate_abi(&self) -> (leo_abi::Program, IndexMap<String, leo_abi::Program>) {
324 let program = match &self.state.ast {
325 Ast::Program(program) => program,
326 Ast::Library(_) => panic!("expected Program AST"),
327 };
328
329 let primary_abi = leo_abi::generate(program);
331
332 let import_abis: IndexMap<String, leo_abi::Program> = program
334 .stubs
335 .iter()
336 .filter(|(_, stub)| !matches!(stub, Stub::FromLibrary { .. }))
337 .map(|(name, stub)| {
338 let abi = match stub {
339 Stub::FromLeo { program, .. } => leo_abi::generate(program),
340 Stub::FromAleo { program, .. } => leo_abi::aleo::generate(program),
341 Stub::FromLibrary { .. } => unreachable!("filtered out"),
342 };
343 (name.to_string(), abi)
344 })
345 .collect();
346
347 (primary_abi, import_abis)
348 }
349
350 pub fn compile(&mut self, source: &str, filename: FileName, modules: &Vec<(&str, FileName)>) -> Result<Compiled> {
365 self.parse_program(source, filename, modules)?;
367 self.add_import_stubs()?;
369 let (primary_abi, import_abis) = self.intermediate_passes()?;
371 let bytecodes = CodeGenerating::do_pass((), &mut self.state)?;
373
374 let primary = CompiledProgram {
376 name: self.program_name.clone().unwrap(),
377 bytecode: bytecodes.primary_bytecode,
378 abi: primary_abi,
379 };
380
381 let imports: Vec<CompiledProgram> = bytecodes
383 .import_bytecodes
384 .into_iter()
385 .map(|bc| {
386 let abi = import_abis.get(&bc.program_name).expect("ABI should exist for all imports").clone();
387 CompiledProgram { name: bc.program_name, bytecode: bc.bytecode, abi }
388 })
389 .collect();
390
391 Ok(Compiled { primary, imports })
392 }
393
394 fn read_sources_and_modules(
410 file_source: &impl FileSource,
411 entry_file_path: impl AsRef<Path>,
412 source_directory: impl AsRef<Path>,
413 ) -> Result<(String, Vec<(String, FileName)>)> {
414 let entry_file_path = entry_file_path.as_ref();
415 let source_directory = source_directory.as_ref();
416
417 let source = file_source
419 .read_file(entry_file_path)
420 .map_err(|e| CompilerError::file_read_error(entry_file_path.display().to_string(), e))?;
421
422 let files = file_source
423 .list_leo_files(source_directory, entry_file_path)
424 .map_err(|e| CompilerError::file_read_error(source_directory.display().to_string(), e))?;
425
426 let mut modules = Vec::with_capacity(files.len());
427 for path in files {
428 let module_source = file_source
429 .read_file(&path)
430 .map_err(|e| CompilerError::file_read_error(path.display().to_string(), e))?;
431 modules.push((module_source, FileName::Real(path)));
432 }
433
434 Ok((source, modules))
435 }
436
437 pub fn compile_from_directory(
439 &mut self,
440 entry_file_path: impl AsRef<Path>,
441 source_directory: impl AsRef<Path>,
442 ) -> Result<Compiled> {
443 self.compile_from_directory_with_file_source(entry_file_path, source_directory, &DiskFileSource)
444 }
445
446 pub fn compile_from_directory_with_file_source(
448 &mut self,
449 entry_file_path: impl AsRef<Path>,
450 source_directory: impl AsRef<Path>,
451 file_source: &impl FileSource,
452 ) -> Result<Compiled> {
453 let (source, modules_owned) = Self::read_sources_and_modules(file_source, &entry_file_path, &source_directory)?;
454
455 let module_refs: Vec<(&str, FileName)> =
457 modules_owned.iter().map(|(src, fname)| (src.as_str(), fname.clone())).collect();
458
459 self.compile(&source, FileName::Real(entry_file_path.as_ref().into()), &module_refs)
461 }
462
463 pub fn parse_program_from_directory(
465 &mut self,
466 entry_file_path: impl AsRef<Path>,
467 source_directory: impl AsRef<Path>,
468 ) -> Result<Program> {
469 self.parse_program_from_directory_with_file_source(entry_file_path, source_directory, &DiskFileSource)
470 }
471
472 pub fn parse_program_from_directory_with_file_source(
474 &mut self,
475 entry_file_path: impl AsRef<Path>,
476 source_directory: impl AsRef<Path>,
477 file_source: &impl FileSource,
478 ) -> Result<Program> {
479 let (source, modules_owned) = Self::read_sources_and_modules(file_source, &entry_file_path, &source_directory)?;
480
481 let module_refs: Vec<(&str, FileName)> =
483 modules_owned.iter().map(|(src, fname)| (src.as_str(), fname.clone())).collect();
484
485 self.parse_program(&source, FileName::Real(entry_file_path.as_ref().into()), &module_refs)?;
487
488 match &self.state.ast {
489 Ast::Program(program) => Ok(program.clone()),
490 Ast::Library(_) => unreachable!("expected Program AST"),
491 }
492 }
493
494 pub fn parse_library_from_directory(
496 &mut self,
497 library_name: Symbol,
498 entry_file_path: impl AsRef<Path>,
499 source_directory: impl AsRef<Path>,
500 ) -> Result<Library> {
501 self.parse_library_from_directory_with_file_source(
502 library_name,
503 entry_file_path,
504 source_directory,
505 &DiskFileSource,
506 )
507 }
508
509 pub fn parse_library_from_directory_with_file_source(
511 &mut self,
512 library_name: Symbol,
513 entry_file_path: impl AsRef<Path>,
514 source_directory: impl AsRef<Path>,
515 file_source: &impl FileSource,
516 ) -> Result<Library> {
517 let (source, modules_owned) = Self::read_sources_and_modules(file_source, &entry_file_path, &source_directory)?;
518
519 let module_refs: Vec<(&str, FileName)> =
520 modules_owned.iter().map(|(src, fname)| (src.as_str(), fname.clone())).collect();
521
522 self.parse_library(library_name, &source, FileName::Real(entry_file_path.as_ref().into()), &module_refs)?;
523
524 match &self.state.ast {
525 Ast::Library(library) => Ok(library.clone()),
526 Ast::Program(_) => unreachable!("expected Library AST"),
527 }
528 }
529
530 fn write_ast_to_json(&self, file_suffix: &str) -> Result<()> {
532 match &self.state.ast {
533 Ast::Program(program) => {
534 if self.compiler_options.ast_spans_enabled {
536 program.to_json_file(
537 self.output_directory.clone(),
538 &format!("{}.{file_suffix}", self.program_name.as_ref().unwrap()),
539 )?;
540 } else {
541 program.to_json_file_without_keys(
542 self.output_directory.clone(),
543 &format!("{}.{file_suffix}", self.program_name.as_ref().unwrap()),
544 &["_span", "span"],
545 )?;
546 }
547 }
548 Ast::Library(_) => {
549 }
551 }
552 Ok(())
553 }
554
555 fn write_ast(&self, file_suffix: &str) -> Result<()> {
557 let filename = format!("{}.{file_suffix}", self.program_name.as_ref().unwrap());
558 let full_filename = self.output_directory.join(&filename);
559
560 let contents = match &self.state.ast {
561 Ast::Program(program) => program.to_string(),
562 Ast::Library(_) => String::new(), };
564
565 fs::write(&full_filename, contents).map_err(|e| CompilerError::failed_ast_file(full_filename.display(), e))?;
566
567 Ok(())
568 }
569
570 pub fn add_import_stubs(&mut self) -> Result<()> {
585 use indexmap::IndexSet;
586
587 let mut explored = IndexSet::<Symbol>::new();
589
590 let initial_imports: IndexMap<Symbol, Span> = match &self.state.ast {
592 Ast::Program(program) => {
593 let mut map: IndexMap<Symbol, Span> =
594 program.imports.iter().map(|(name, id)| (*name, id.span())).collect();
595 for (stub_name, stub) in &self.import_stubs {
597 if matches!(stub, Stub::FromLibrary { .. })
598 && stub.parents().contains(&Symbol::intern(self.program_name.as_ref().unwrap()))
599 {
600 map.insert(
601 *stub_name,
602 Span::default(), );
604 }
605 }
606 map
607 }
608 Ast::Library(_) => IndexMap::new(),
609 };
610
611 let mut to_explore: Vec<(Symbol, Span)> = initial_imports.iter().map(|(sym, span)| (*sym, *span)).collect();
613
614 if let Some(main_program_name) = self.program_name.clone() {
616 let main_symbol = Symbol::intern(&main_program_name);
617 for import in initial_imports.keys() {
618 if let Some(child_stub) = self.import_stubs.get_mut(import) {
619 child_stub.add_parent(main_symbol);
620 }
621 }
622 }
623
624 while let Some((import_symbol, span)) = to_explore.pop() {
626 explored.insert(import_symbol);
628
629 let Some(stub) = self.import_stubs.get(&import_symbol) else {
631 return Err(CompilerError::imported_program_not_found(
632 self.program_name.as_ref().unwrap(),
633 import_symbol,
634 span,
635 )
636 .into());
637 };
638
639 let mut combined_imports: IndexMap<Symbol, Span> = stub.explicit_imports().collect();
641 for (lib_name, lib_stub) in &self.import_stubs {
642 if matches!(lib_stub, Stub::FromLibrary { .. }) && lib_stub.parents().contains(&import_symbol) {
643 combined_imports.insert(
644 *lib_name,
645 Span::default(), );
647 }
648 }
649
650 for (child_symbol, child_span) in combined_imports {
651 if let Some(child_stub) = self.import_stubs.get_mut(&child_symbol) {
653 child_stub.add_parent(import_symbol);
654 }
655
656 if explored.insert(child_symbol) {
658 to_explore.push((child_symbol, child_span));
659 }
660 }
661 }
662
663 if let Ast::Program(program) = &mut self.state.ast {
665 program.stubs = self
666 .import_stubs
667 .iter()
668 .filter(|(symbol, _)| explored.contains(*symbol))
669 .map(|(symbol, stub)| (*symbol, stub.clone()))
670 .collect();
671 }
672
673 Ok(())
674 }
675}
676
677#[cfg(test)]
678mod tests {
679 use super::Compiler;
680
681 use leo_ast::{NetworkName, NodeBuilder};
682 use leo_errors::Handler;
683 use leo_span::{Symbol, create_session_if_not_set_then, file_source::InMemoryFileSource};
684
685 use std::{path::PathBuf, rc::Rc};
686
687 use indexmap::IndexMap;
688
689 #[test]
690 fn parse_library_from_directory_in_memory() {
691 create_session_if_not_set_then(|_| {
692 let mut source = InMemoryFileSource::new();
693 source.set(
694 PathBuf::from("/mylib/src/lib.leo"),
695 concat!("const SCALE: u32 = 10u32;\n", "const OFFSET: u32 = SCALE + 1u32;\n",).into(),
696 );
697
698 let handler = Handler::default();
699 let node_builder = Rc::new(NodeBuilder::default());
700 let mut compiler = Compiler::new(
701 None,
702 false,
703 handler,
704 node_builder,
705 PathBuf::from("/unused"),
706 None,
707 IndexMap::new(),
708 NetworkName::TestnetV0,
709 );
710
711 let library = compiler
712 .parse_library_from_directory_with_file_source(
713 Symbol::intern("mylib"),
714 "/mylib/src/lib.leo",
715 "/mylib/src",
716 &source,
717 )
718 .unwrap_or_else(|err| panic!("parsing library from in-memory file source failed: {err}"));
719
720 assert_eq!(library.name, Symbol::intern("mylib"));
721 assert_eq!(library.consts.len(), 2, "expected 2 consts, got {}", library.consts.len());
722 assert!(
723 library.consts.iter().any(|(name, _)| *name == Symbol::intern("SCALE")),
724 "expected const `SCALE` in library"
725 );
726 assert!(
727 library.consts.iter().any(|(name, _)| *name == Symbol::intern("OFFSET")),
728 "expected const `OFFSET` in library"
729 );
730 });
731 }
732
733 #[test]
734 fn parse_program_from_directory_in_memory_with_module() {
735 create_session_if_not_set_then(|_| {
736 let mut source = InMemoryFileSource::new();
737 source.set(
738 PathBuf::from("/project/src/main.leo"),
739 concat!(
740 "program test.aleo {\n",
741 " fn main() -> u32 {\n",
742 " return utils::helper();\n",
743 " }\n",
744 "}\n",
745 )
746 .into(),
747 );
748 source.set(PathBuf::from("/project/src/utils.leo"), "fn helper() -> u32 {\n return 42u32;\n}\n".into());
749
750 let handler = Handler::default();
751 let node_builder = Rc::new(NodeBuilder::default());
752 let mut compiler = Compiler::new(
753 Some("test.aleo".into()),
754 false,
755 handler,
756 node_builder,
757 PathBuf::from("/unused"),
758 None,
759 IndexMap::new(),
760 NetworkName::TestnetV0,
761 );
762
763 let ast = compiler
764 .parse_program_from_directory_with_file_source("/project/src/main.leo", "/project/src", &source)
765 .unwrap_or_else(|err| panic!("parsing from in-memory file source failed: {err}"));
766 let utils_key = vec![Symbol::intern("utils")];
767
768 assert!(
769 ast.modules.contains_key(&utils_key),
770 "module `utils` should be loaded from the in-memory file source; found keys: {:?}",
771 ast.modules.keys().collect::<Vec<_>>()
772 );
773 });
774 }
775}