use crate::CompilerOptions;
pub use leo_ast::Ast;
use leo_ast::{NodeBuilder, Program, Stub};
use leo_errors::{CompilerError, Result, emitter::Handler};
use leo_passes::*;
use leo_span::{Symbol, source_map::FileName, symbol::with_session_globals};
use snarkvm::prelude::Network;
use indexmap::{IndexMap, IndexSet};
use sha2::{Digest, Sha256};
use std::{fs, path::PathBuf};
#[derive(Clone)]
pub struct Compiler<'a, N: Network> {
handler: &'a Handler,
main_file_path: PathBuf,
output_directory: PathBuf,
pub program_name: String,
pub network: String,
pub ast: Ast,
compiler_options: CompilerOptions,
node_builder: NodeBuilder,
assigner: Assigner,
type_table: TypeTable,
import_stubs: IndexMap<Symbol, Stub>,
phantom: std::marker::PhantomData<N>,
}
impl<'a, N: Network> Compiler<'a, N> {
pub fn new(
program_name: String,
network: String,
handler: &'a Handler,
main_file_path: PathBuf,
output_directory: PathBuf,
compiler_options: Option<CompilerOptions>,
import_stubs: IndexMap<Symbol, Stub>,
) -> Self {
let node_builder = NodeBuilder::default();
let assigner = Assigner::default();
let type_table = TypeTable::default();
Self {
handler,
main_file_path,
output_directory,
program_name,
network,
ast: Ast::new(Program::default()),
compiler_options: compiler_options.unwrap_or_default(),
node_builder,
assigner,
import_stubs,
type_table,
phantom: Default::default(),
}
}
pub fn checksum(&self) -> Result<String> {
let unparsed_file = fs::read_to_string(&self.main_file_path)
.map_err(|e| CompilerError::file_read_error(self.main_file_path.clone(), e))?;
let mut hasher = Sha256::new();
hasher.update(unparsed_file.as_bytes());
let hash = hasher.finalize();
Ok(format!("{hash:x}"))
}
pub fn parse_program_from_string(&mut self, program_string: &str, name: FileName) -> Result<()> {
let prg_sf = with_session_globals(|s| s.source_map.new_source(program_string, name));
self.ast = leo_parser::parse_ast::<N>(self.handler, &self.node_builder, &prg_sf.src, prg_sf.start_pos)?;
let program_scope = self.ast.ast.program_scopes.values().next().unwrap();
let program_scope_name = format!("{}", program_scope.program_id.name);
if program_scope_name != self.program_name {
return Err(CompilerError::program_scope_name_does_not_match(
program_scope_name,
self.program_name.clone(),
program_scope.program_id.name.span,
)
.into());
}
if self.compiler_options.output.initial_ast {
self.write_ast_to_json("initial_ast.json")?;
}
Ok(())
}
pub fn parse_program(&mut self) -> Result<()> {
let program_string = fs::read_to_string(&self.main_file_path)
.map_err(|e| CompilerError::file_read_error(&self.main_file_path, e))?;
self.parse_program_from_string(&program_string, FileName::Real(self.main_file_path.clone()))
}
pub fn symbol_table_pass(&self) -> Result<SymbolTable> {
let symbol_table = SymbolTableCreator::do_pass((&self.ast, self.handler))?;
Ok(symbol_table)
}
pub fn type_checker_pass(&'a self, symbol_table: &mut SymbolTable) -> Result<(StructGraph, CallGraph)> {
let (struct_graph, call_graph) =
TypeChecker::do_pass((&self.ast, self.handler, symbol_table, &self.type_table, NetworkLimits {
max_array_elements: N::MAX_ARRAY_ELEMENTS,
max_mappings: N::MAX_MAPPINGS,
max_functions: N::MAX_FUNCTIONS,
}))?;
Ok((struct_graph, call_graph))
}
pub fn static_analysis_pass(&mut self, symbol_table: &SymbolTable) -> Result<()> {
StaticAnalyzer::<N>::do_pass((
&self.ast,
self.handler,
symbol_table,
&self.type_table,
self.compiler_options.build.conditional_block_max_depth,
self.compiler_options.build.disable_conditional_branch_type_checking,
))
}
pub fn const_propagation_and_unroll_loop(&mut self, symbol_table: &mut SymbolTable) -> Result<()> {
const LARGE_LOOP_BOUND: usize = 1024usize;
for _ in 0..LARGE_LOOP_BOUND {
let loop_unroll_output = self.loop_unrolling_pass(symbol_table)?;
let const_prop_output = self.const_propagation_pass(symbol_table)?;
if !const_prop_output.changed && !loop_unroll_output.loop_unrolled {
if let Some(not_evaluated_span) = const_prop_output.const_not_evaluated {
return Err(CompilerError::const_not_evaluated(not_evaluated_span).into());
}
if let Some(not_evaluated_span) = const_prop_output.array_index_not_evaluated {
return Err(CompilerError::array_index_not_evaluated(not_evaluated_span).into());
}
if let Some(not_unrolled_span) = loop_unroll_output.loop_not_unrolled {
return Err(CompilerError::loop_bounds_not_evaluated(not_unrolled_span).into());
}
if self.compiler_options.output.unrolled_ast {
self.write_ast_to_json("unrolled_ast.json")?;
}
return Ok(());
}
}
Err(CompilerError::const_prop_unroll_many_loops(LARGE_LOOP_BOUND, Default::default()).into())
}
pub fn const_propagation_pass(&mut self, symbol_table: &mut SymbolTable) -> Result<ConstPropagatorOutput> {
let (ast, output) = ConstPropagator::do_pass((
std::mem::take(&mut self.ast),
self.handler,
symbol_table,
&self.type_table,
&self.node_builder,
))?;
self.ast = ast;
Ok(output)
}
pub fn loop_unrolling_pass(&mut self, symbol_table: &mut SymbolTable) -> Result<UnrollerOutput> {
let (ast, output) = Unroller::do_pass((
std::mem::take(&mut self.ast),
self.handler,
&self.node_builder,
symbol_table,
&self.type_table,
))?;
self.ast = ast;
Ok(output)
}
pub fn static_single_assignment_pass(&mut self, symbol_table: &SymbolTable) -> Result<()> {
self.ast = StaticSingleAssigner::do_pass((
std::mem::take(&mut self.ast),
&self.node_builder,
&self.assigner,
symbol_table,
&self.type_table,
))?;
if self.compiler_options.output.ssa_ast {
self.write_ast_to_json("ssa_ast.json")?;
}
Ok(())
}
pub fn flattening_pass(&mut self, symbol_table: &SymbolTable) -> Result<()> {
self.ast = Flattener::do_pass((
std::mem::take(&mut self.ast),
symbol_table,
&self.type_table,
&self.node_builder,
&self.assigner,
))?;
if self.compiler_options.output.flattened_ast {
self.write_ast_to_json("flattened_ast.json")?;
}
Ok(())
}
pub fn destructuring_pass(&mut self) -> Result<()> {
self.ast = Destructurer::do_pass((
std::mem::take(&mut self.ast),
&self.type_table,
&self.node_builder,
&self.assigner,
))?;
if self.compiler_options.output.destructured_ast {
self.write_ast_to_json("destructured_ast.json")?;
}
Ok(())
}
pub fn function_inlining_pass(&mut self, call_graph: &CallGraph) -> Result<()> {
let ast = FunctionInliner::do_pass((
std::mem::take(&mut self.ast),
&self.node_builder,
call_graph,
&self.assigner,
&self.type_table,
))?;
self.ast = ast;
if self.compiler_options.output.inlined_ast {
self.write_ast_to_json("inlined_ast.json")?;
}
Ok(())
}
pub fn dead_code_elimination_pass(&mut self) -> Result<()> {
if self.compiler_options.build.dce_enabled {
self.ast = DeadCodeEliminator::do_pass((std::mem::take(&mut self.ast), &self.node_builder))?;
}
if self.compiler_options.output.dce_ast {
self.write_ast_to_json("dce_ast.json")?;
}
Ok(())
}
pub fn code_generation_pass(
&mut self,
symbol_table: &SymbolTable,
struct_graph: &StructGraph,
call_graph: &CallGraph,
) -> Result<String> {
CodeGenerator::do_pass((&self.ast, symbol_table, &self.type_table, struct_graph, call_graph, &self.ast.ast))
}
pub fn compiler_stages(&mut self) -> Result<(SymbolTable, StructGraph, CallGraph)> {
let mut st = self.symbol_table_pass()?;
let (struct_graph, call_graph) = self.type_checker_pass(&mut st)?;
self.static_analysis_pass(&st)?;
self.const_propagation_and_unroll_loop(&mut st)?;
self.static_single_assignment_pass(&st)?;
self.flattening_pass(&st)?;
self.destructuring_pass()?;
self.function_inlining_pass(&call_graph)?;
self.dead_code_elimination_pass()?;
Ok((st, struct_graph, call_graph))
}
pub fn compile(&mut self) -> Result<String> {
self.parse_program()?;
self.add_import_stubs()?;
let (symbol_table, struct_graph, call_graph) = self.compiler_stages()?;
let bytecode = self.code_generation_pass(&symbol_table, &struct_graph, &call_graph)?;
Ok(bytecode)
}
fn write_ast_to_json(&self, file_suffix: &str) -> Result<()> {
if self.compiler_options.output.ast_spans_enabled {
self.ast.to_json_file(self.output_directory.clone(), &format!("{}.{file_suffix}", self.program_name))?;
} else {
self.ast.to_json_file_without_keys(
self.output_directory.clone(),
&format!("{}.{file_suffix}", self.program_name),
&["_span", "span"],
)?;
}
Ok(())
}
pub fn add_import_stubs(&mut self) -> Result<()> {
let (mut unexplored, mut explored): (IndexSet<Symbol>, IndexSet<Symbol>) =
(self.ast.ast.imports.keys().cloned().collect(), IndexSet::new());
while !unexplored.is_empty() {
let mut current_dependencies: IndexSet<Symbol> = IndexSet::new();
for program_name in unexplored.iter() {
if let Some(stub) = self.import_stubs.get(program_name) {
explored.insert(*program_name);
for dependency in stub.imports.iter() {
if explored.insert(dependency.name.name) {
current_dependencies.insert(dependency.name.name);
}
}
} else {
return Err(CompilerError::imported_program_not_found(
self.program_name.clone(),
*program_name,
self.ast.ast.imports[program_name].1,
)
.into());
}
}
unexplored = current_dependencies;
}
self.ast.ast.stubs =
self.import_stubs.clone().into_iter().filter(|(program_name, _)| explored.contains(program_name)).collect();
Ok(())
}
}