cairo_lang_compiler/
db.rs

1use std::sync::Arc;
2
3use anyhow::{Result, anyhow, bail};
4use cairo_lang_defs::db::{init_defs_group, init_external_files};
5use cairo_lang_diagnostics::Maybe;
6use cairo_lang_filesystem::cfg::CfgSet;
7use cairo_lang_filesystem::db::{CORELIB_VERSION, FilesGroup, init_dev_corelib, init_files_group};
8use cairo_lang_filesystem::detect::detect_corelib;
9use cairo_lang_filesystem::flag::Flag;
10use cairo_lang_filesystem::ids::{CrateId, FlagLongId};
11use cairo_lang_lowering::db::init_lowering_group;
12use cairo_lang_lowering::ids::ConcreteFunctionWithBodyId;
13use cairo_lang_lowering::optimizations::config::Optimizations;
14use cairo_lang_project::ProjectConfig;
15use cairo_lang_runnable_utils::builder::RunnableBuilder;
16use cairo_lang_semantic::db::{PluginSuiteInput, init_semantic_group};
17use cairo_lang_semantic::inline_macros::get_default_plugin_suite;
18use cairo_lang_semantic::plugin::PluginSuite;
19use cairo_lang_sierra_generator::db::init_sierra_gen_group;
20use cairo_lang_sierra_generator::program_generator::get_dummy_program_for_size_estimation;
21use salsa::Database;
22
23use crate::InliningStrategy;
24use crate::project::update_crate_roots_from_project_config;
25
26/// Estimates the size of a function by compiling it to CASM.
27/// Note that the size is not accurate since we don't use the real costs for the dummy functions.
28fn estimate_code_size(
29    db: &dyn Database,
30    function_id: ConcreteFunctionWithBodyId<'_>,
31) -> Maybe<isize> {
32    let program = get_dummy_program_for_size_estimation(db, function_id)?;
33
34    // All the functions except the first one are dummy functions.
35    let n_dummy_functions = program.funcs.len() - 1;
36
37    // TODO(ilya): Consider adding set costs to dummy functions.
38    let builder = match RunnableBuilder::new(program, Default::default()) {
39        Ok(builder) => builder,
40        Err(err) => {
41            if err.is_ap_overflow_error() {
42                // If the compilation failed due to an AP overflow, we don't want to panic as it can
43                // happen for valid code. In this case, the function is probably too large for
44                // inline so we can just return the max size.
45                return Ok(isize::MAX);
46            }
47            if std::env::var("CAIRO_DEBUG_SIERRA_GEN").is_ok() {
48                // If we are debugging sierra generation, we want to finish the compilation rather
49                // than panic.
50                return Ok(isize::MAX);
51            }
52
53            panic!(
54                "Internal compiler error when compiling function `{}` to casm: `{err}`. You can \
55                 set the CAIRO_DEBUG_SIERRA_GEN environment if you want to finish the compilation \
56                 and debug the sierra program.",
57                function_id.full_path(db)
58            );
59        }
60    };
61    let casm = builder.casm_program();
62    let total_size = casm.instructions.iter().map(|inst| inst.body.op_size()).sum::<usize>();
63
64    // The size of a dummy function is currently 3 felts. call (2) + ret (1).
65    const DUMMY_FUNCTION_SIZE: usize = 3;
66    Ok((total_size - (n_dummy_functions * DUMMY_FUNCTION_SIZE)).try_into().unwrap_or(0))
67}
68
69#[salsa::db]
70#[derive(Clone)]
71pub struct RootDatabase {
72    storage: salsa::Storage<RootDatabase>,
73}
74#[salsa::db]
75impl salsa::Database for RootDatabase {}
76
77impl RootDatabase {
78    fn new(default_plugin_suite: PluginSuite, optimizations: Optimizations) -> Self {
79        let mut res = Self { storage: Default::default() };
80        init_external_files(&mut res);
81        init_files_group(&mut res);
82        init_lowering_group(&mut res, optimizations, Some(estimate_code_size));
83        init_defs_group(&mut res);
84        init_semantic_group(&mut res);
85        init_sierra_gen_group(&mut res);
86
87        res.set_default_plugins_from_suite(default_plugin_suite);
88
89        res
90    }
91
92    pub fn empty() -> Self {
93        Self::builder().clear_plugins().build().unwrap()
94    }
95
96    pub fn builder() -> RootDatabaseBuilder {
97        RootDatabaseBuilder::new()
98    }
99
100    /// Snapshots the db for read only.
101    pub fn snapshot(&self) -> RootDatabase {
102        RootDatabase { storage: self.storage.clone() }
103    }
104}
105
106impl Default for RootDatabase {
107    fn default() -> Self {
108        Self::builder().build().unwrap()
109    }
110}
111
112#[derive(Clone, Debug)]
113pub struct RootDatabaseBuilder {
114    default_plugin_suite: PluginSuite,
115    detect_corelib: bool,
116    auto_withdraw_gas: bool,
117    panic_backtrace: bool,
118    unsafe_panic: bool,
119    project_config: Option<Box<ProjectConfig>>,
120    cfg_set: Option<CfgSet>,
121    optimizations: Optimizations,
122}
123
124impl RootDatabaseBuilder {
125    fn new() -> Self {
126        Self {
127            default_plugin_suite: get_default_plugin_suite(),
128            detect_corelib: false,
129            auto_withdraw_gas: true,
130            panic_backtrace: false,
131            unsafe_panic: false,
132            project_config: None,
133            cfg_set: None,
134            optimizations: Optimizations::enabled_with_default_movable_functions(
135                InliningStrategy::Default,
136            ),
137        }
138    }
139
140    pub fn with_default_plugin_suite(&mut self, suite: PluginSuite) -> &mut Self {
141        self.default_plugin_suite.add(suite);
142        self
143    }
144
145    pub fn clear_plugins(&mut self) -> &mut Self {
146        self.default_plugin_suite = get_default_plugin_suite();
147        self
148    }
149
150    pub fn with_optimizations(&mut self, optimizations: Optimizations) -> &mut Self {
151        self.optimizations = optimizations;
152        self
153    }
154
155    pub fn detect_corelib(&mut self) -> &mut Self {
156        self.detect_corelib = true;
157        self
158    }
159
160    pub fn with_project_config(&mut self, config: ProjectConfig) -> &mut Self {
161        self.project_config = Some(Box::new(config));
162        self
163    }
164
165    pub fn with_cfg(&mut self, cfg_set: impl Into<CfgSet>) -> &mut Self {
166        self.cfg_set = Some(cfg_set.into());
167        self
168    }
169
170    pub fn skip_auto_withdraw_gas(&mut self) -> &mut Self {
171        self.auto_withdraw_gas = false;
172        self
173    }
174
175    pub fn with_panic_backtrace(&mut self) -> &mut Self {
176        self.panic_backtrace = true;
177        self
178    }
179
180    pub fn with_unsafe_panic(&mut self) -> &mut Self {
181        self.unsafe_panic = true;
182        self
183    }
184
185    pub fn build(&mut self) -> Result<RootDatabase> {
186        // NOTE: Order of operations matters here!
187        //   Errors if something is not OK are very subtle, mostly this results in missing
188        //   identifier diagnostics, or panics regarding lack of corelib items.
189
190        let mut db =
191            RootDatabase::new(self.default_plugin_suite.clone(), self.optimizations.clone());
192
193        if let Some(cfg_set) = &self.cfg_set {
194            db.use_cfg(cfg_set);
195        }
196
197        if self.detect_corelib {
198            let path =
199                detect_corelib().ok_or_else(|| anyhow!("Failed to find development corelib."))?;
200            init_dev_corelib(&mut db, path)
201        }
202
203        let add_withdraw_gas_flag_id = FlagLongId("add_withdraw_gas".into());
204        db.set_flag(
205            add_withdraw_gas_flag_id,
206            Some(Arc::new(Flag::AddWithdrawGas(self.auto_withdraw_gas))),
207        );
208        let panic_backtrace_flag_id = FlagLongId("panic_backtrace".into());
209        db.set_flag(
210            panic_backtrace_flag_id,
211            Some(Arc::new(Flag::PanicBacktrace(self.panic_backtrace))),
212        );
213
214        let unsafe_panic_flag_id = FlagLongId("unsafe_panic".into());
215        db.set_flag(unsafe_panic_flag_id, Some(Arc::new(Flag::UnsafePanic(self.unsafe_panic))));
216
217        if let Some(config) = &self.project_config {
218            update_crate_roots_from_project_config(&mut db, config.as_ref());
219        }
220        validate_corelib(&db)?;
221
222        Ok(db)
223    }
224}
225
226/// Validates that the corelib version matches the expected one.
227pub fn validate_corelib(db: &(dyn salsa::Database + 'static)) -> Result<()> {
228    let Some(config) = db.crate_config(CrateId::core(db)) else {
229        return Ok(());
230    };
231    let Some(found) = &config.settings.version else {
232        return Ok(());
233    };
234    let Ok(expected) = semver::Version::parse(CORELIB_VERSION) else {
235        return Ok(());
236    };
237    if found == &expected {
238        return Ok(());
239    }
240    let path_part = match &config.root {
241        cairo_lang_filesystem::ids::Directory::Real(path) => {
242            format!(" for `{}`", path.to_string_lossy())
243        }
244        cairo_lang_filesystem::ids::Directory::Virtual { .. } => "".to_string(),
245    };
246    bail!("Corelib version mismatch: expected `{expected}`, found `{found}`{path_part}.");
247}