cairo_lang_compiler/
lib.rs1use 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;
22use rayon::{ThreadPool, ThreadPoolBuilder};
23
24use crate::db::RootDatabase;
25use crate::diagnostics::{DiagnosticsError, DiagnosticsReporter};
26use crate::project::{ProjectConfig, get_main_crate_ids_from_project, setup_project};
27
28pub mod db;
29pub mod diagnostics;
30pub mod project;
31
32#[cfg(test)]
33mod test;
34
35#[derive(Default)]
37pub struct CompilerConfig<'c> {
38 pub diagnostics_reporter: DiagnosticsReporter<'c>,
39
40 pub replace_ids: bool,
42
43 pub inlining_strategy: InliningStrategy,
45
46 pub allowed_libfuncs_list_name: Option<String>,
49
50 pub add_statements_functions: bool,
53
54 pub add_statements_code_locations: bool,
57}
58
59pub fn compile_cairo_project_at_path(
70 path: &Path,
71 compiler_config: CompilerConfig<'_>,
72) -> Result<Program> {
73 let mut db = RootDatabase::builder()
74 .with_inlining_strategy(compiler_config.inlining_strategy)
75 .detect_corelib()
76 .build()?;
77 let main_crate_ids = setup_project(&mut db, path)?;
78 compile_prepared_db_program(&mut db, main_crate_ids, compiler_config)
79}
80
81pub fn compile(
91 project_config: ProjectConfig,
92 compiler_config: CompilerConfig<'_>,
93) -> Result<Program> {
94 let mut db = RootDatabase::builder()
95 .with_inlining_strategy(compiler_config.inlining_strategy)
96 .with_project_config(project_config.clone())
97 .build()?;
98 let main_crate_ids = get_main_crate_ids_from_project(&mut db, &project_config);
99
100 compile_prepared_db_program(&mut db, main_crate_ids, compiler_config)
101}
102
103pub fn compile_prepared_db_program(
115 db: &mut RootDatabase,
116 main_crate_ids: Vec<CrateId>,
117 compiler_config: CompilerConfig<'_>,
118) -> Result<Program> {
119 Ok(compile_prepared_db(db, main_crate_ids, compiler_config)?.program)
120}
121
122pub fn compile_prepared_db(
137 db: &RootDatabase,
138 main_crate_ids: Vec<CrateId>,
139 mut compiler_config: CompilerConfig<'_>,
140) -> Result<SierraProgramWithDebug> {
141 compiler_config.diagnostics_reporter.ensure(db)?;
142
143 let mut sierra_program_with_debug = Arc::unwrap_or_clone(
144 db.get_sierra_program(main_crate_ids)
145 .to_option()
146 .context("Compilation failed without any diagnostics")?,
147 );
148
149 if compiler_config.replace_ids {
150 sierra_program_with_debug.program =
151 replace_sierra_ids_in_program(db, &sierra_program_with_debug.program);
152 }
153
154 Ok(sierra_program_with_debug)
155}
156
157pub enum DbWarmupContext {
168 Warmup { pool: ThreadPool },
169 NoWarmup,
170}
171
172impl DbWarmupContext {
173 pub fn new() -> Self {
175 if !Self::should_warmup() {
176 return Self::NoWarmup;
177 }
178 const MAX_WARMUP_PARALLELISM: usize = 4;
179 let pool = ThreadPoolBuilder::new()
180 .num_threads(rayon::current_num_threads().min(MAX_WARMUP_PARALLELISM))
181 .build()
182 .expect("failed to build rayon thread pool");
183 Self::Warmup { pool }
184 }
185
186 fn should_warmup() -> bool {
188 rayon::current_num_threads() > 1
189 }
190
191 fn warmup_diagnostics(
193 &self,
194 db: &RootDatabase,
195 diagnostic_reporter: &mut DiagnosticsReporter<'_>,
196 ) {
197 match self {
198 Self::Warmup { pool } => diagnostic_reporter.warm_up_diagnostics(db, pool),
199 Self::NoWarmup => {}
200 }
201 }
202
203 pub fn ensure_diagnostics(
208 &self,
209 db: &RootDatabase,
210 diagnostic_reporter: &mut DiagnosticsReporter<'_>,
211 ) -> std::result::Result<(), DiagnosticsError> {
212 self.warmup_diagnostics(db, diagnostic_reporter);
213 diagnostic_reporter.ensure(db)?;
214 Ok(())
215 }
216
217 fn warmup_db(
219 &self,
220 db: &RootDatabase,
221 requested_function_ids: Vec<ConcreteFunctionWithBodyId>,
222 ) {
223 match self {
224 Self::Warmup { pool } => {
225 let snapshot = salsa::ParallelDatabase::snapshot(db);
226 pool.spawn(move || warmup_db_blocking(snapshot, requested_function_ids));
227 }
228 Self::NoWarmup => {}
229 }
230 }
231}
232
233impl Default for DbWarmupContext {
234 fn default() -> Self {
235 Self::new()
236 }
237}
238
239fn warmup_db_blocking(
244 snapshot: salsa::Snapshot<RootDatabase>,
245 requested_function_ids: Vec<ConcreteFunctionWithBodyId>,
246) {
247 let processed_function_ids =
248 &Mutex::new(UnorderedHashSet::<ConcreteFunctionWithBodyId>::default());
249 rayon::scope(move |s| {
250 for func_id in requested_function_ids {
251 let snapshot = salsa::ParallelDatabase::snapshot(&*snapshot);
252
253 s.spawn(move |_| {
254 fn handle_func_inner(
255 processed_function_ids: &Mutex<UnorderedHashSet<ConcreteFunctionWithBodyId>>,
256 snapshot: salsa::Snapshot<RootDatabase>,
257 func_id: ConcreteFunctionWithBodyId,
258 ) {
259 if processed_function_ids.lock().unwrap().insert(func_id) {
260 rayon::scope(move |s| {
261 let db = &*snapshot;
262 let Ok(function) = db.function_with_body_sierra(func_id) else {
263 return;
264 };
265 for statement in &function.body {
266 let Some(related_function_id) =
267 try_get_function_with_body_id(db, statement)
268 else {
269 continue;
270 };
271
272 let snapshot = salsa::ParallelDatabase::snapshot(&*snapshot);
273 s.spawn(move |_| {
274 handle_func_inner(
275 processed_function_ids,
276 snapshot,
277 related_function_id,
278 )
279 })
280 }
281 });
282 }
283 }
284
285 handle_func_inner(processed_function_ids, snapshot, func_id)
286 });
287 }
288 });
289}
290
291pub fn get_sierra_program_for_functions(
294 db: &RootDatabase,
295 requested_function_ids: Vec<ConcreteFunctionWithBodyId>,
296 context: DbWarmupContext,
297) -> Result<Arc<SierraProgramWithDebug>> {
298 context.warmup_db(db, requested_function_ids.clone());
299 db.get_sierra_program_for_functions(requested_function_ids)
300 .to_option()
301 .with_context(|| "Compilation failed without any diagnostics.")
302}
303
304pub fn compile_prepared_db_program_artifact(
319 db: &mut RootDatabase,
320 main_crate_ids: Vec<CrateId>,
321 mut compiler_config: CompilerConfig<'_>,
322) -> Result<ProgramArtifact> {
323 let add_statements_functions = compiler_config.add_statements_functions;
324 let add_statements_code_locations = compiler_config.add_statements_code_locations;
325
326 compiler_config.diagnostics_reporter.ensure(db)?;
327
328 let executable_functions = find_executable_function_ids(db, main_crate_ids.clone());
329
330 let mut sierra_program_with_debug = if executable_functions.is_empty() {
331 Arc::unwrap_or_clone(
334 db.get_sierra_program(main_crate_ids)
335 .to_option()
336 .context("Compilation failed without any diagnostics")?,
337 )
338 } else {
339 Arc::unwrap_or_clone(
341 db.get_sierra_program_for_functions(executable_functions.clone().into_keys().collect())
342 .to_option()
343 .context("Compilation failed without any diagnostics")?,
344 )
345 };
346
347 if compiler_config.replace_ids {
348 sierra_program_with_debug.program =
349 replace_sierra_ids_in_program(db, &sierra_program_with_debug.program);
350 }
351
352 let mut annotations = Annotations::default();
353
354 if add_statements_functions {
355 annotations.extend(Annotations::from(
356 sierra_program_with_debug
357 .debug_info
358 .statements_locations
359 .extract_statements_functions(db),
360 ))
361 };
362
363 if add_statements_code_locations {
364 annotations.extend(Annotations::from(
365 sierra_program_with_debug
366 .debug_info
367 .statements_locations
368 .extract_statements_source_code_locations(db),
369 ))
370 };
371
372 let debug_info = DebugInfo {
373 type_names: Default::default(),
374 libfunc_names: Default::default(),
375 user_func_names: Default::default(),
376 annotations,
377 executables: Default::default(),
378 };
379
380 let executables =
382 collect_executables(db, executable_functions, &sierra_program_with_debug.program);
383
384 Ok(ProgramArtifact::stripped(sierra_program_with_debug.program)
385 .with_debug_info(DebugInfo { executables, ..debug_info }))
386}