cairo_lang_compiler/
db.rs

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