use std::path::Path;
use std::sync::{Arc, Mutex};
use ::cairo_lang_diagnostics::ToOption;
use anyhow::{Context, Result};
use cairo_lang_filesystem::ids::CrateId;
use cairo_lang_lowering::ids::ConcreteFunctionWithBodyId;
use cairo_lang_lowering::utils::InliningStrategy;
use cairo_lang_sierra::debug_info::{Annotations, DebugInfo};
use cairo_lang_sierra::program::{Program, ProgramArtifact};
use cairo_lang_sierra_generator::db::SierraGenGroup;
use cairo_lang_sierra_generator::executables::{collect_executables, find_executable_function_ids};
use cairo_lang_sierra_generator::program_generator::{
SierraProgramWithDebug, try_get_function_with_body_id,
};
use cairo_lang_sierra_generator::replace_ids::replace_sierra_ids_in_program;
use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
use crate::db::RootDatabase;
use crate::diagnostics::DiagnosticsReporter;
use crate::project::{ProjectConfig, get_main_crate_ids_from_project, setup_project};
pub mod db;
pub mod diagnostics;
pub mod project;
#[cfg(test)]
mod test;
#[derive(Default)]
pub struct CompilerConfig<'c> {
pub diagnostics_reporter: DiagnosticsReporter<'c>,
pub replace_ids: bool,
pub inlining_strategy: InliningStrategy,
pub allowed_libfuncs_list_name: Option<String>,
pub add_statements_functions: bool,
pub add_statements_code_locations: bool,
}
pub fn compile_cairo_project_at_path(
path: &Path,
compiler_config: CompilerConfig<'_>,
) -> Result<Program> {
let mut db = RootDatabase::builder()
.with_inlining_strategy(compiler_config.inlining_strategy)
.detect_corelib()
.build()?;
let main_crate_ids = setup_project(&mut db, path)?;
compile_prepared_db_program(&mut db, main_crate_ids, compiler_config)
}
pub fn compile(
project_config: ProjectConfig,
compiler_config: CompilerConfig<'_>,
) -> Result<Program> {
let mut db = RootDatabase::builder().with_project_config(project_config.clone()).build()?;
let main_crate_ids = get_main_crate_ids_from_project(&mut db, &project_config);
compile_prepared_db_program(&mut db, main_crate_ids, compiler_config)
}
pub fn compile_prepared_db_program(
db: &mut RootDatabase,
main_crate_ids: Vec<CrateId>,
compiler_config: CompilerConfig<'_>,
) -> Result<Program> {
Ok(compile_prepared_db(db, main_crate_ids, compiler_config)?.program)
}
pub fn compile_prepared_db(
db: &RootDatabase,
main_crate_ids: Vec<CrateId>,
mut compiler_config: CompilerConfig<'_>,
) -> Result<SierraProgramWithDebug> {
compiler_config.diagnostics_reporter.ensure(db)?;
let mut sierra_program_with_debug = Arc::unwrap_or_clone(
db.get_sierra_program(main_crate_ids)
.to_option()
.context("Compilation failed without any diagnostics")?,
);
if compiler_config.replace_ids {
sierra_program_with_debug.program =
replace_sierra_ids_in_program(db, &sierra_program_with_debug.program);
}
Ok(sierra_program_with_debug)
}
fn warmup_db_blocking(
snapshot: salsa::Snapshot<RootDatabase>,
requested_function_ids: Vec<ConcreteFunctionWithBodyId>,
) {
let processed_function_ids =
&Mutex::new(UnorderedHashSet::<ConcreteFunctionWithBodyId>::default());
rayon::scope(move |s| {
for func_id in requested_function_ids {
let snapshot = salsa::ParallelDatabase::snapshot(&*snapshot);
s.spawn(move |_| {
fn handle_func_inner(
processed_function_ids: &Mutex<UnorderedHashSet<ConcreteFunctionWithBodyId>>,
snapshot: salsa::Snapshot<RootDatabase>,
func_id: ConcreteFunctionWithBodyId,
) {
if processed_function_ids.lock().unwrap().insert(func_id) {
rayon::scope(move |s| {
let db = &*snapshot;
let Ok(function) = db.function_with_body_sierra(func_id) else {
return;
};
for statement in &function.body {
let Some(related_function_id) =
try_get_function_with_body_id(db, statement)
else {
continue;
};
let snapshot = salsa::ParallelDatabase::snapshot(&*snapshot);
s.spawn(move |_| {
handle_func_inner(
processed_function_ids,
snapshot,
related_function_id,
)
})
}
});
}
}
handle_func_inner(processed_function_ids, snapshot, func_id)
});
}
});
}
fn spawn_warmup_db(db: &RootDatabase, requested_function_ids: Vec<ConcreteFunctionWithBodyId>) {
let snapshot = salsa::ParallelDatabase::snapshot(db);
rayon::spawn(move || warmup_db_blocking(snapshot, requested_function_ids));
}
pub fn get_sierra_program_for_functions(
db: &RootDatabase,
requested_function_ids: Vec<ConcreteFunctionWithBodyId>,
mut diagnostic_reporter: DiagnosticsReporter<'_>,
) -> Result<Arc<SierraProgramWithDebug>> {
if rayon::current_num_threads() > 1 {
diagnostic_reporter.warm_up_diagnostics(db);
spawn_warmup_db(db, requested_function_ids.clone());
}
diagnostic_reporter.ensure(db)?;
db.get_sierra_program_for_functions(requested_function_ids)
.to_option()
.with_context(|| "Compilation failed without any diagnostics.")
}
pub fn compile_prepared_db_program_artifact(
db: &mut RootDatabase,
main_crate_ids: Vec<CrateId>,
mut compiler_config: CompilerConfig<'_>,
) -> Result<ProgramArtifact> {
let add_statements_functions = compiler_config.add_statements_functions;
let add_statements_code_locations = compiler_config.add_statements_code_locations;
compiler_config.diagnostics_reporter.ensure(db)?;
let executable_functions = find_executable_function_ids(db, main_crate_ids.clone());
let mut sierra_program_with_debug = if executable_functions.is_empty() {
Arc::unwrap_or_clone(
db.get_sierra_program(main_crate_ids)
.to_option()
.context("Compilation failed without any diagnostics")?,
)
} else {
Arc::unwrap_or_clone(
db.get_sierra_program_for_functions(executable_functions.clone().into_keys().collect())
.to_option()
.context("Compilation failed without any diagnostics")?,
)
};
if compiler_config.replace_ids {
sierra_program_with_debug.program =
replace_sierra_ids_in_program(db, &sierra_program_with_debug.program);
}
let mut annotations = Annotations::default();
if add_statements_functions {
annotations.extend(Annotations::from(
sierra_program_with_debug
.debug_info
.statements_locations
.extract_statements_functions(db),
))
};
if add_statements_code_locations {
annotations.extend(Annotations::from(
sierra_program_with_debug
.debug_info
.statements_locations
.extract_statements_source_code_locations(db),
))
};
let debug_info = DebugInfo {
type_names: Default::default(),
libfunc_names: Default::default(),
user_func_names: Default::default(),
annotations,
executables: Default::default(),
};
let executables =
collect_executables(db, executable_functions, &sierra_program_with_debug.program);
Ok(ProgramArtifact::stripped(sierra_program_with_debug.program)
.with_debug_info(DebugInfo { executables, ..debug_info }))
}