Skip to main content

cairo_lang_compiler/
db.rs

1use anyhow::{Result, anyhow, bail};
2use cairo_lang_defs::db::{init_defs_group, init_external_files};
3use cairo_lang_diagnostics::Maybe;
4use cairo_lang_filesystem::cfg::CfgSet;
5use cairo_lang_filesystem::db::{CORELIB_VERSION, FilesGroup, init_dev_corelib, init_files_group};
6use cairo_lang_filesystem::detect::detect_corelib;
7use cairo_lang_filesystem::flag::{Flag, FlagsGroup};
8use cairo_lang_filesystem::ids::{CrateId, FlagLongId};
9use cairo_lang_lowering::db::init_lowering_group;
10use cairo_lang_lowering::ids::ConcreteFunctionWithBodyId;
11use cairo_lang_lowering::optimizations::config::Optimizations;
12use cairo_lang_lowering::utils::InliningStrategy;
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 cairo_lang_utils::CloneableDatabase;
21use salsa::Database;
22
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 to
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 {}
75impl CloneableDatabase for RootDatabase {
76    fn dyn_clone(&self) -> Box<dyn CloneableDatabase> {
77        Box::new(self.clone())
78    }
79}
80
81impl RootDatabase {
82    fn new(default_plugin_suite: PluginSuite, optimizations: Optimizations) -> Self {
83        let mut res = Self { storage: Default::default() };
84        init_external_files(&mut res);
85        init_files_group(&mut res);
86        init_lowering_group(&mut res, optimizations, Some(estimate_code_size));
87        init_defs_group(&mut res);
88        init_semantic_group(&mut res);
89        init_sierra_gen_group(&mut res);
90
91        res.set_default_plugins_from_suite(default_plugin_suite);
92
93        res
94    }
95
96    pub fn empty() -> Self {
97        Self::builder().clear_plugins().build().unwrap()
98    }
99
100    pub fn builder() -> RootDatabaseBuilder {
101        RootDatabaseBuilder::new()
102    }
103
104    /// Snapshots the db for read only.
105    pub fn snapshot(&self) -> RootDatabase {
106        RootDatabase { storage: self.storage.clone() }
107    }
108}
109
110impl Default for RootDatabase {
111    fn default() -> Self {
112        Self::builder().build().unwrap()
113    }
114}
115
116#[derive(Clone, Debug)]
117pub struct RootDatabaseBuilder {
118    default_plugin_suite: PluginSuite,
119    detect_corelib: bool,
120    auto_withdraw_gas: bool,
121    panic_backtrace: bool,
122    unsafe_panic: bool,
123    project_config: Option<Box<ProjectConfig>>,
124    cfg_set: Option<CfgSet>,
125    optimizations: Optimizations,
126}
127
128impl RootDatabaseBuilder {
129    fn new() -> Self {
130        Self {
131            default_plugin_suite: get_default_plugin_suite(),
132            detect_corelib: false,
133            auto_withdraw_gas: true,
134            panic_backtrace: false,
135            unsafe_panic: false,
136            project_config: None,
137            cfg_set: None,
138            optimizations: Optimizations::enabled_with_default_movable_functions(
139                InliningStrategy::Default,
140            ),
141        }
142    }
143
144    pub fn with_default_plugin_suite(&mut self, suite: PluginSuite) -> &mut Self {
145        self.default_plugin_suite.add(suite);
146        self
147    }
148
149    pub fn clear_plugins(&mut self) -> &mut Self {
150        self.default_plugin_suite = get_default_plugin_suite();
151        self
152    }
153
154    pub fn with_optimizations(&mut self, optimizations: Optimizations) -> &mut Self {
155        self.optimizations = optimizations;
156        self
157    }
158
159    pub fn detect_corelib(&mut self) -> &mut Self {
160        self.detect_corelib = true;
161        self
162    }
163
164    pub fn with_project_config(&mut self, config: ProjectConfig) -> &mut Self {
165        self.project_config = Some(Box::new(config));
166        self
167    }
168
169    pub fn with_cfg(&mut self, cfg_set: impl Into<CfgSet>) -> &mut Self {
170        self.cfg_set = Some(cfg_set.into());
171        self
172    }
173
174    pub fn skip_auto_withdraw_gas(&mut self) -> &mut Self {
175        self.auto_withdraw_gas = false;
176        self
177    }
178
179    pub fn with_panic_backtrace(&mut self) -> &mut Self {
180        self.panic_backtrace = true;
181        self
182    }
183
184    pub fn with_unsafe_panic(&mut self) -> &mut Self {
185        self.unsafe_panic = true;
186        self
187    }
188
189    pub fn build(&mut self) -> Result<RootDatabase> {
190        // NOTE: Order of operations matters here!
191        //   Errors from incorrect ordering are very subtle, mostly resulting in missing
192        //   identifier diagnostics, or panics regarding lack of corelib items.
193
194        let mut db =
195            RootDatabase::new(self.default_plugin_suite.clone(), self.optimizations.clone());
196
197        if let Some(cfg_set) = &self.cfg_set {
198            db.use_cfg(cfg_set);
199        }
200
201        if self.detect_corelib {
202            let path =
203                detect_corelib().ok_or_else(|| anyhow!("Failed to find development corelib."))?;
204            init_dev_corelib(&mut db, path)
205        }
206
207        let add_withdraw_gas_flag_id = FlagLongId(Flag::ADD_WITHDRAW_GAS.into());
208        db.set_flag(add_withdraw_gas_flag_id, Some(Flag::AddWithdrawGas(self.auto_withdraw_gas)));
209        let panic_backtrace_flag_id = FlagLongId(Flag::PANIC_BACKTRACE.into());
210        db.set_flag(panic_backtrace_flag_id, Some(Flag::PanicBacktrace(self.panic_backtrace)));
211        let unsafe_panic_flag_id = FlagLongId(Flag::UNSAFE_PANIC.into());
212        db.set_flag(unsafe_panic_flag_id, Some(Flag::UnsafePanic(self.unsafe_panic)));
213
214        if let Some(config) = &self.project_config {
215            update_crate_roots_from_project_config(&mut db, config.as_ref());
216        }
217        validate_corelib(&db)?;
218
219        Ok(db)
220    }
221}
222
223/// Validates that the corelib version matches the expected one.
224pub fn validate_corelib(db: &(dyn salsa::Database + 'static)) -> Result<()> {
225    let Some(config) = db.crate_config(CrateId::core(db)) else {
226        return Ok(());
227    };
228    let Some(found) = &config.settings.version else {
229        return Ok(());
230    };
231    let Ok(expected) = semver::Version::parse(CORELIB_VERSION) else {
232        return Ok(());
233    };
234    if found == &expected {
235        return Ok(());
236    }
237    let path_part = match &config.root {
238        cairo_lang_filesystem::ids::Directory::Real(path) => {
239            format!(" for `{}`", path.to_string_lossy())
240        }
241        cairo_lang_filesystem::ids::Directory::Virtual { .. } => "".to_string(),
242    };
243    bail!("Corelib version mismatch: expected `{expected}`, found `{found}`{path_part}.");
244}