cairo_lang_compiler/
lib.rs

1//! Cairo compiler.
2//!
3//! This crate is responsible for compiling a Cairo project into a Sierra program.
4//! It is the main entry point for the compiler.
5use std::path::Path;
6use std::sync::{Arc, Mutex};
7
8use ::cairo_lang_diagnostics::ToOption;
9use anyhow::{Context, Result};
10use cairo_lang_filesystem::ids::CrateId;
11use cairo_lang_lowering::ids::ConcreteFunctionWithBodyId;
12use cairo_lang_lowering::utils::InliningStrategy;
13use cairo_lang_sierra::debug_info::{Annotations, DebugInfo};
14use cairo_lang_sierra::program::{Program, ProgramArtifact};
15use cairo_lang_sierra_generator::db::SierraGenGroup;
16use cairo_lang_sierra_generator::executables::{collect_executables, find_executable_function_ids};
17use cairo_lang_sierra_generator::program_generator::{
18    SierraProgramWithDebug, try_get_function_with_body_id,
19};
20use cairo_lang_sierra_generator::replace_ids::replace_sierra_ids_in_program;
21use cairo_lang_utils::unordered_hash_set::UnorderedHashSet;
22
23use crate::db::RootDatabase;
24use crate::diagnostics::DiagnosticsReporter;
25use crate::project::{ProjectConfig, get_main_crate_ids_from_project, setup_project};
26
27pub mod db;
28pub mod diagnostics;
29pub mod project;
30
31#[cfg(test)]
32mod test;
33
34/// Configuration for the compiler.
35#[derive(Default)]
36pub struct CompilerConfig<'c> {
37    pub diagnostics_reporter: DiagnosticsReporter<'c>,
38
39    /// Replaces sierra ids with human-readable ones.
40    pub replace_ids: bool,
41
42    /// Disables inlining functions.
43    pub inlining_strategy: InliningStrategy,
44
45    /// The name of the allowed libfuncs list to use in compilation.
46    /// If None the default list of audited libfuncs will be used.
47    pub allowed_libfuncs_list_name: Option<String>,
48
49    /// Adds mapping used by [cairo-profiler](https://github.com/software-mansion/cairo-profiler) to
50    /// [cairo_lang_sierra::debug_info::Annotations] in [cairo_lang_sierra::debug_info::DebugInfo].
51    pub add_statements_functions: bool,
52
53    /// Adds mapping used by [cairo-coverage](https://github.com/software-mansion/cairo-coverage) to
54    /// [cairo_lang_sierra::debug_info::Annotations] in [cairo_lang_sierra::debug_info::DebugInfo].
55    pub add_statements_code_locations: bool,
56}
57
58/// Compiles a Cairo project at the given path.
59/// The project must be a valid Cairo project:
60/// Either a standalone `.cairo` file (a single crate), or a directory with a `cairo_project.toml`
61/// file.
62/// # Arguments
63/// * `path` - The path to the project.
64/// * `compiler_config` - The compiler configuration.
65/// # Returns
66/// * `Ok(Program)` - The compiled program.
67/// * `Err(anyhow::Error)` - Compilation failed.
68pub fn compile_cairo_project_at_path(
69    path: &Path,
70    compiler_config: CompilerConfig<'_>,
71) -> Result<Program> {
72    let mut db = RootDatabase::builder()
73        .with_inlining_strategy(compiler_config.inlining_strategy)
74        .detect_corelib()
75        .build()?;
76    let main_crate_ids = setup_project(&mut db, path)?;
77    compile_prepared_db_program(&mut db, main_crate_ids, compiler_config)
78}
79
80/// Compiles a Cairo project.
81/// The project must be a valid Cairo project.
82/// This function is a wrapper over [`RootDatabase::builder()`] and [`compile_prepared_db_program`].
83/// # Arguments
84/// * `project_config` - The project configuration.
85/// * `compiler_config` - The compiler configuration.
86/// # Returns
87/// * `Ok(Program)` - The compiled program.
88/// * `Err(anyhow::Error)` - Compilation failed.
89pub fn compile(
90    project_config: ProjectConfig,
91    compiler_config: CompilerConfig<'_>,
92) -> Result<Program> {
93    let mut db = RootDatabase::builder().with_project_config(project_config.clone()).build()?;
94    let main_crate_ids = get_main_crate_ids_from_project(&mut db, &project_config);
95
96    compile_prepared_db_program(&mut db, main_crate_ids, compiler_config)
97}
98
99/// Runs Cairo compiler.
100///
101/// # Arguments
102/// * `db` - Preloaded compilation database.
103/// * `main_crate_ids` - [`CrateId`]s to compile. Do not include dependencies here, only pass
104///   top-level crates in order to eliminate unused code. Use `CrateLongId::Real(name).intern(db)`
105///   in order to obtain [`CrateId`] from its name.
106/// * `compiler_config` - The compiler configuration.
107/// # Returns
108/// * `Ok(Program)` - The compiled program.
109/// * `Err(anyhow::Error)` - Compilation failed.
110pub fn compile_prepared_db_program(
111    db: &mut RootDatabase,
112    main_crate_ids: Vec<CrateId>,
113    compiler_config: CompilerConfig<'_>,
114) -> Result<Program> {
115    Ok(compile_prepared_db(db, main_crate_ids, compiler_config)?.program)
116}
117
118/// Runs Cairo compiler.
119///
120/// Similar to `compile_prepared_db_program`, but this function returns all the raw debug
121/// information.
122///
123/// # Arguments
124/// * `db` - Preloaded compilation database.
125/// * `main_crate_ids` - [`CrateId`]s to compile. Do not include dependencies here, only pass
126///   top-level crates in order to eliminate unused code. Use `CrateLongId::Real(name).intern(db)`
127///   in order to obtain [`CrateId`] from its name.
128/// * `compiler_config` - The compiler configuration.
129/// # Returns
130/// * `Ok(SierraProgramWithDebug)` - The compiled program with debug info.
131/// * `Err(anyhow::Error)` - Compilation failed.
132pub fn compile_prepared_db(
133    db: &RootDatabase,
134    main_crate_ids: Vec<CrateId>,
135    mut compiler_config: CompilerConfig<'_>,
136) -> Result<SierraProgramWithDebug> {
137    compiler_config.diagnostics_reporter.ensure(db)?;
138
139    let mut sierra_program_with_debug = Arc::unwrap_or_clone(
140        db.get_sierra_program(main_crate_ids)
141            .to_option()
142            .context("Compilation failed without any diagnostics")?,
143    );
144
145    if compiler_config.replace_ids {
146        sierra_program_with_debug.program =
147            replace_sierra_ids_in_program(db, &sierra_program_with_debug.program);
148    }
149
150    Ok(sierra_program_with_debug)
151}
152
153/// Spawns threads to compute the `function_with_body_sierra` query and all dependent queries for
154/// the requested functions and their dependencies.
155///
156/// Note that typically spawn_warmup_db should be used as this function is blocking.
157fn warmup_db_blocking(
158    snapshot: salsa::Snapshot<RootDatabase>,
159    requested_function_ids: Vec<ConcreteFunctionWithBodyId>,
160) {
161    let processed_function_ids =
162        &Mutex::new(UnorderedHashSet::<ConcreteFunctionWithBodyId>::default());
163    rayon::scope(move |s| {
164        for func_id in requested_function_ids {
165            let snapshot = salsa::ParallelDatabase::snapshot(&*snapshot);
166
167            s.spawn(move |_| {
168                fn handle_func_inner(
169                    processed_function_ids: &Mutex<UnorderedHashSet<ConcreteFunctionWithBodyId>>,
170                    snapshot: salsa::Snapshot<RootDatabase>,
171                    func_id: ConcreteFunctionWithBodyId,
172                ) {
173                    if processed_function_ids.lock().unwrap().insert(func_id) {
174                        rayon::scope(move |s| {
175                            let db = &*snapshot;
176                            let Ok(function) = db.function_with_body_sierra(func_id) else {
177                                return;
178                            };
179                            for statement in &function.body {
180                                let Some(related_function_id) =
181                                    try_get_function_with_body_id(db, statement)
182                                else {
183                                    continue;
184                                };
185
186                                let snapshot = salsa::ParallelDatabase::snapshot(&*snapshot);
187                                s.spawn(move |_| {
188                                    handle_func_inner(
189                                        processed_function_ids,
190                                        snapshot,
191                                        related_function_id,
192                                    )
193                                })
194                            }
195                        });
196                    }
197                }
198
199                handle_func_inner(processed_function_ids, snapshot, func_id)
200            });
201        }
202    });
203}
204
205/// Spawns a task to warm up the db.
206fn spawn_warmup_db(db: &RootDatabase, requested_function_ids: Vec<ConcreteFunctionWithBodyId>) {
207    let snapshot = salsa::ParallelDatabase::snapshot(db);
208    rayon::spawn(move || warmup_db_blocking(snapshot, requested_function_ids));
209}
210
211///  Checks if there are diagnostics in the database and if there are None, returns
212///  the [SierraProgramWithDebug] object of the requested functions
213pub fn get_sierra_program_for_functions(
214    db: &RootDatabase,
215    requested_function_ids: Vec<ConcreteFunctionWithBodyId>,
216    mut diagnostic_reporter: DiagnosticsReporter<'_>,
217) -> Result<Arc<SierraProgramWithDebug>> {
218    if rayon::current_num_threads() > 1 {
219        // If we have more than one thread, we can use the other threads to warm up the db.
220        diagnostic_reporter.warm_up_diagnostics(db);
221        spawn_warmup_db(db, requested_function_ids.clone());
222    }
223
224    diagnostic_reporter.ensure(db)?;
225    db.get_sierra_program_for_functions(requested_function_ids)
226        .to_option()
227        .with_context(|| "Compilation failed without any diagnostics.")
228}
229
230/// Runs Cairo compiler.
231///
232/// Wrapper over [`compile_prepared_db`], but this function returns [`ProgramArtifact`]
233/// with requested debug info.
234///
235/// # Arguments
236/// * `db` - Preloaded compilation database.
237/// * `main_crate_ids` - [`CrateId`]s to compile. Do not include dependencies here, only pass
238///   top-level crates in order to eliminate unused code. Use `CrateLongId::Real(name).intern(db)`
239///   in order to obtain [`CrateId`] from its name.
240/// * `compiler_config` - The compiler configuration.
241/// # Returns
242/// * `Ok(ProgramArtifact)` - The compiled program artifact with requested debug info.
243/// * `Err(anyhow::Error)` - Compilation failed.
244pub fn compile_prepared_db_program_artifact(
245    db: &mut RootDatabase,
246    main_crate_ids: Vec<CrateId>,
247    mut compiler_config: CompilerConfig<'_>,
248) -> Result<ProgramArtifact> {
249    let add_statements_functions = compiler_config.add_statements_functions;
250    let add_statements_code_locations = compiler_config.add_statements_code_locations;
251
252    compiler_config.diagnostics_reporter.ensure(db)?;
253
254    let executable_functions = find_executable_function_ids(db, main_crate_ids.clone());
255
256    let mut sierra_program_with_debug = if executable_functions.is_empty() {
257        // No executables found - compile for all main crates.
258        // TODO(maciektr): Deprecate in future. This compilation is useless, without `replace_ids`.
259        Arc::unwrap_or_clone(
260            db.get_sierra_program(main_crate_ids)
261                .to_option()
262                .context("Compilation failed without any diagnostics")?,
263        )
264    } else {
265        // Compile for executable functions only.
266        Arc::unwrap_or_clone(
267            db.get_sierra_program_for_functions(executable_functions.clone().into_keys().collect())
268                .to_option()
269                .context("Compilation failed without any diagnostics")?,
270        )
271    };
272
273    if compiler_config.replace_ids {
274        sierra_program_with_debug.program =
275            replace_sierra_ids_in_program(db, &sierra_program_with_debug.program);
276    }
277
278    let mut annotations = Annotations::default();
279
280    if add_statements_functions {
281        annotations.extend(Annotations::from(
282            sierra_program_with_debug
283                .debug_info
284                .statements_locations
285                .extract_statements_functions(db),
286        ))
287    };
288
289    if add_statements_code_locations {
290        annotations.extend(Annotations::from(
291            sierra_program_with_debug
292                .debug_info
293                .statements_locations
294                .extract_statements_source_code_locations(db),
295        ))
296    };
297
298    let debug_info = DebugInfo {
299        type_names: Default::default(),
300        libfunc_names: Default::default(),
301        user_func_names: Default::default(),
302        annotations,
303        executables: Default::default(),
304    };
305
306    // Calculate executable function Sierra ids.
307    let executables =
308        collect_executables(db, executable_functions, &sierra_program_with_debug.program);
309
310    Ok(ProgramArtifact::stripped(sierra_program_with_debug.program)
311        .with_debug_info(DebugInfo { executables, ..debug_info }))
312}