1use std::path::Path;
6use std::sync::Mutex;
7
8use ::cairo_lang_diagnostics::ToOption;
9use anyhow::{Context, Result};
10use cairo_lang_defs::db::DefsGroup;
11use cairo_lang_filesystem::ids::{CrateId, CrateInput};
12use cairo_lang_lowering::db::LoweringGroup;
13use cairo_lang_lowering::ids::ConcreteFunctionWithBodyId;
14use cairo_lang_lowering::optimizations::config::Optimizations;
15use cairo_lang_lowering::utils::InliningStrategy;
16use cairo_lang_parser::db::ParserGroup;
17use cairo_lang_semantic::db::SemanticGroup;
18use cairo_lang_sierra::debug_info::{Annotations, DebugInfo};
19use cairo_lang_sierra::program::{Program, ProgramArtifact};
20use cairo_lang_sierra_generator::db::SierraGenGroup;
21use cairo_lang_sierra_generator::debug_info::SerializableTypeNamesDebugInfo;
22use cairo_lang_sierra_generator::executables::{collect_executables, find_executable_function_ids};
23use cairo_lang_sierra_generator::program_generator::{
24 SierraProgramWithDebug, find_all_free_function_ids, try_get_function_with_body_id,
25};
26use cairo_lang_sierra_generator::replace_ids::replace_sierra_ids_in_program;
27use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
28use cairo_lang_utils::{CloneableDatabase, Intern};
29use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator};
30use salsa::Database;
31
32use crate::db::RootDatabase;
33use crate::diagnostics::{DiagnosticsError, DiagnosticsReporter};
34use crate::project::{ProjectConfig, get_main_crate_ids_from_project, setup_project};
35
36pub mod db;
37pub mod diagnostics;
38pub mod project;
39
40#[cfg(test)]
41mod test;
42
43#[derive(Default)]
45pub struct CompilerConfig<'a> {
46 pub diagnostics_reporter: DiagnosticsReporter<'a>,
47
48 pub replace_ids: bool,
50
51 pub add_statements_functions: bool,
54
55 pub add_statements_code_locations: bool,
58
59 pub add_functions_debug_info: bool,
62
63 pub add_type_names: bool,
65}
66
67pub fn compile_cairo_project_at_path(
78 path: &Path,
79 compiler_config: CompilerConfig<'_>,
80 inlining_strategy: InliningStrategy,
81) -> Result<Program> {
82 let mut db = RootDatabase::builder()
83 .with_optimizations(Optimizations::enabled_with_default_movable_functions(
84 inlining_strategy,
85 ))
86 .detect_corelib()
87 .build()?;
88 let main_crate_ids = setup_project(&mut db, path)?;
89 compile_prepared_db_program(
90 &db,
91 CrateInput::into_crate_ids(&db, main_crate_ids),
92 compiler_config,
93 )
94}
95
96pub fn compile(
106 project_config: ProjectConfig,
107 compiler_config: CompilerConfig<'_>,
108) -> Result<Program> {
109 let db = RootDatabase::builder()
110 .with_optimizations(Optimizations::enabled_with_default_movable_functions(
111 InliningStrategy::Default,
112 ))
113 .with_project_config(project_config.clone())
114 .build()?;
115 let main_crate_ids = get_main_crate_ids_from_project(&db, &project_config);
116
117 compile_prepared_db_program(&db, main_crate_ids, compiler_config)
118}
119
120pub fn compile_prepared_db_program<'db>(
132 db: &'db dyn Database,
133 main_crate_ids: Vec<CrateId<'db>>,
134 compiler_config: CompilerConfig<'_>,
135) -> Result<Program> {
136 Ok(compile_prepared_db(db, main_crate_ids, compiler_config)?.program)
137}
138
139pub fn compile_prepared_db<'db>(
154 db: &'db dyn Database,
155 main_crate_ids: Vec<CrateId<'db>>,
156 mut compiler_config: CompilerConfig<'_>,
157) -> Result<SierraProgramWithDebug<'db>> {
158 compiler_config.diagnostics_reporter.ensure(db)?;
159
160 let mut sierra_program_with_debug = db
161 .get_sierra_program(main_crate_ids)
162 .to_option()
163 .context("Compilation failed without any diagnostics")?
164 .clone();
165
166 if compiler_config.replace_ids {
167 sierra_program_with_debug.program =
168 replace_sierra_ids_in_program(db, &sierra_program_with_debug.program);
169 }
170
171 Ok(sierra_program_with_debug)
172}
173
174fn should_warmup() -> bool {
176 rayon::current_num_threads() > 1
177}
178
179pub fn ensure_diagnostics(
190 db: &dyn CloneableDatabase,
191 diagnostic_reporter: &mut DiagnosticsReporter<'_>,
192) -> std::result::Result<(), DiagnosticsError> {
193 if should_warmup() {
194 let crates = diagnostic_reporter.crates_of_interest(db);
195 let warmup_db = db.dyn_clone();
196 let ensure_db = db.dyn_clone();
197 rayon::join(
198 move || warmup_diagnostics_blocking(warmup_db.as_ref(), crates),
199 move || diagnostic_reporter.ensure(ensure_db.as_ref()),
200 )
201 .1
202 } else {
203 diagnostic_reporter.ensure(db)
204 }
205}
206
207fn warmup_diagnostics_blocking(db: &dyn CloneableDatabase, crates: Vec<CrateInput>) {
210 crates.into_par_iter().for_each_with(db.dyn_clone(), |db, crate_input| {
211 let db = db.as_ref();
212 let crate_id = crate_input.into_crate_long_id(db).intern(db);
213 db.crate_modules(crate_id).into_par_iter().for_each_with(
214 db.dyn_clone(),
215 |db, module_id| {
216 for file_id in db.module_files(*module_id).unwrap_or_default().iter().copied() {
217 db.file_syntax_diagnostics(file_id);
218 }
219 let _ = db.module_semantic_diagnostics(*module_id);
220 let _ = db.module_lowering_diagnostics(*module_id);
221 },
222 );
223 });
224}
225
226fn warmup_functions_blocking<'db>(
231 db: &dyn CloneableDatabase,
232 requested_function_ids: Vec<ConcreteFunctionWithBodyId<'db>>,
233) {
234 let processed_function_ids = &Mutex::new(UnorderedHashSet::<salsa::Id>::default());
235 requested_function_ids.into_par_iter().for_each_with(db.dyn_clone(), move |db, func_id| {
236 fn handle_func_inner<'db>(
237 processed_function_ids: &Mutex<UnorderedHashSet<salsa::Id>>,
238 db: &dyn CloneableDatabase,
239 func_id: ConcreteFunctionWithBodyId<'db>,
240 ) {
241 if processed_function_ids.lock().unwrap().insert(func_id.as_intern_id()) {
242 let Ok(function) = db.function_with_body_sierra(func_id) else {
243 return;
244 };
245 function.body.par_iter().for_each_with(db.dyn_clone(), move |db, statement| {
246 let related_function_id: ConcreteFunctionWithBodyId<'_> =
247 if let Some(r_id) = try_get_function_with_body_id(db.as_ref(), statement) {
248 r_id
249 } else {
250 return;
251 };
252
253 handle_func_inner(processed_function_ids, db.as_ref(), related_function_id);
254 });
255 }
256 }
257 handle_func_inner(processed_function_ids, db.as_ref(), func_id)
258 });
259}
260
261pub fn get_sierra_program_for_functions<'db>(
264 db: &'db dyn CloneableDatabase,
265 requested_function_ids: Vec<ConcreteFunctionWithBodyId<'db>>,
266) -> Result<&'db SierraProgramWithDebug<'db>> {
267 if should_warmup() {
268 let requested_function_ids = requested_function_ids.clone();
269 warmup_functions_blocking(db, requested_function_ids);
270 }
271 db.get_sierra_program_for_functions(requested_function_ids)
272 .to_option()
273 .context("Compilation failed without any diagnostics.")
274}
275
276pub fn compile_prepared_db_program_artifact<'db>(
291 db: &'db dyn CloneableDatabase,
292 main_crate_ids: Vec<CrateId<'db>>,
293 mut compiler_config: CompilerConfig<'_>,
294) -> Result<ProgramArtifact> {
295 ensure_diagnostics(db, &mut compiler_config.diagnostics_reporter)?;
296
297 let executable_functions = find_executable_function_ids(db, main_crate_ids.clone());
298
299 let function_ids = if executable_functions.is_empty() {
300 find_all_free_function_ids(db, main_crate_ids)
303 .to_option()
304 .context("Compilation failed without any diagnostics.")?
305 } else {
306 executable_functions.keys().cloned().collect()
308 };
309
310 let mut program_artifact =
311 compile_prepared_db_program_artifact_for_functions(db, function_ids, compiler_config)?;
312
313 let executables = collect_executables(db, executable_functions, &program_artifact.program);
315
316 let debug_info = program_artifact.debug_info.take().unwrap_or_default();
317
318 Ok(program_artifact.with_debug_info(DebugInfo { executables, ..debug_info }))
319}
320
321pub fn compile_prepared_db_program_artifact_for_functions<'db>(
334 db: &'db dyn CloneableDatabase,
335 requested_function_ids: Vec<ConcreteFunctionWithBodyId<'db>>,
336 compiler_config: CompilerConfig<'_>,
337) -> Result<ProgramArtifact> {
338 let mut sierra_program_with_debug =
339 get_sierra_program_for_functions(db, requested_function_ids)?.clone();
340
341 if compiler_config.replace_ids {
342 sierra_program_with_debug.program =
343 replace_sierra_ids_in_program(db, &sierra_program_with_debug.program);
344 }
345
346 let mut annotations = Annotations::default();
347
348 if compiler_config.add_statements_functions {
349 annotations.extend(Annotations::from(
350 sierra_program_with_debug
351 .debug_info
352 .statements_locations
353 .extract_statements_functions(db),
354 ))
355 };
356
357 if compiler_config.add_statements_code_locations {
358 annotations.extend(Annotations::from(
359 sierra_program_with_debug
360 .debug_info
361 .statements_locations
362 .extract_statements_source_code_locations(db),
363 ))
364 };
365
366 if compiler_config.add_functions_debug_info {
367 annotations.extend(Annotations::from(
368 sierra_program_with_debug.debug_info.functions_info.extract_serializable_debug_info(db),
369 ))
370 }
371
372 if compiler_config.add_type_names {
373 annotations.extend(Annotations::from(SerializableTypeNamesDebugInfo::extract_type_names(
374 db,
375 &sierra_program_with_debug.program,
376 )))
377 }
378
379 let debug_info = DebugInfo {
380 type_names: Default::default(),
381 libfunc_names: Default::default(),
382 user_func_names: Default::default(),
383 annotations,
384 executables: Default::default(),
385 };
386
387 Ok(ProgramArtifact::stripped(sierra_program_with_debug.program).with_debug_info(debug_info))
388}