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