cairo_lang_executable/
compile.rs

1use std::path::Path;
2
3use anyhow::{Context, Result};
4use cairo_lang_compiler::db::RootDatabase;
5use cairo_lang_compiler::diagnostics::DiagnosticsReporter;
6use cairo_lang_compiler::project::setup_project;
7use cairo_lang_compiler::{ensure_diagnostics, get_sierra_program_for_functions};
8use cairo_lang_debug::debug::DebugWithDb;
9use cairo_lang_executable_plugin::{
10    EXECUTABLE_PREFIX, EXECUTABLE_RAW_ATTR, executable_plugin_suite,
11};
12use cairo_lang_filesystem::cfg::{Cfg, CfgSet};
13use cairo_lang_filesystem::ids::{CrateId, CrateInput};
14use cairo_lang_lowering::ids::ConcreteFunctionWithBodyId;
15use cairo_lang_runnable_utils::builder::{
16    CasmProgramWrapperInfo, EntryCodeConfig, RunnableBuilder,
17};
18use cairo_lang_sierra_generator::db::SierraGenGroup;
19use cairo_lang_sierra_generator::executables::find_executable_function_ids;
20use cairo_lang_sierra_generator::program_generator::{
21    SierraProgramDebugInfo, SierraProgramWithDebug,
22};
23use cairo_lang_sierra_to_casm::compiler::CairoProgram;
24use cairo_lang_utils::write_comma_separated;
25use itertools::Itertools;
26use salsa::Database;
27
28/// The CASM compilation result.
29pub struct CompiledFunction {
30    /// The compiled CASM program.
31    pub program: CairoProgram,
32    /// The wrapper information for the program.
33    pub wrapper: CasmProgramWrapperInfo,
34}
35impl std::fmt::Display for CompiledFunction {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        write!(f, "// builtins:")?;
38        if !self.wrapper.builtins.is_empty() {
39            write!(f, " ")?;
40            write_comma_separated(f, self.wrapper.builtins.iter().map(|b| b.to_str()))?;
41        }
42        writeln!(f)?;
43        writeln!(f, "// header")?;
44        for instruction in &self.wrapper.header {
45            writeln!(f, "{instruction};")?;
46        }
47        writeln!(f, "// sierra based code")?;
48        write!(f, "{}", self.program)?;
49        writeln!(f, "// footer")?;
50        for instruction in &self.wrapper.footer {
51            writeln!(f, "{instruction};")?;
52        }
53        Ok(())
54    }
55}
56
57#[derive(Debug, Clone, Default)]
58pub struct ExecutableConfig {
59    /// If true, will allow syscalls in the program.
60    ///
61    /// In general, syscalls are not allowed in executables, as they are currently not verified.
62    pub allow_syscalls: bool,
63
64    /// Replace the panic flow with an unprovable opcode, this reduces code size but might make it
65    /// more difficult to debug.
66    pub unsafe_panic: bool,
67}
68
69/// Represents the output of compiling an executable.
70///
71/// Includes the `CompiledFunction` along with supplementary objects useful for profiling.
72pub struct CompileExecutableResult<'db> {
73    /// The compiled function.
74    pub compiled_function: CompiledFunction,
75    /// A runnable builder with the program corresponding to the compiled function.
76    pub builder: RunnableBuilder,
77    /// The debug info for the sierra program in the builder.
78    pub debug_info: SierraProgramDebugInfo<'db>,
79}
80
81/// Compile the function given by path.
82/// Errors if there is ambiguity.
83pub fn prepare_db(config: &ExecutableConfig) -> Result<RootDatabase> {
84    let mut builder = RootDatabase::builder();
85    builder
86        .skip_auto_withdraw_gas()
87        .with_cfg(CfgSet::from_iter([Cfg::kv("gas", "disabled")]))
88        .detect_corelib()
89        .with_default_plugin_suite(executable_plugin_suite());
90    if config.unsafe_panic {
91        builder.with_unsafe_panic();
92    }
93
94    builder.build()
95}
96
97/// Compile the function given by path.
98/// Errors if there is ambiguity.
99pub fn compile_executable<'db>(
100    db: &'db mut dyn Database,
101    path: &Path,
102    executable_path: Option<&str>,
103    diagnostics_reporter: DiagnosticsReporter<'_>,
104    config: ExecutableConfig,
105) -> Result<CompileExecutableResult<'db>> {
106    // let mut db = prepare_db(&config)?;
107
108    let main_crate_inputs = setup_project(db, Path::new(&path))?;
109    let diagnostics_reporter = diagnostics_reporter.with_crates(&main_crate_inputs);
110    let main_crate_ids = CrateInput::into_crate_ids(db, main_crate_inputs);
111
112    compile_executable_in_prepared_db(
113        db,
114        executable_path,
115        main_crate_ids,
116        diagnostics_reporter,
117        config,
118    )
119}
120
121/// Runs compiler on the specified executable function.
122/// If no executable was specified, verify that there is only one.
123/// Otherwise, return an error.
124pub fn compile_executable_in_prepared_db<'db>(
125    db: &'db dyn Database,
126    executable_path: Option<&str>,
127    main_crate_ids: Vec<CrateId<'db>>,
128    mut diagnostics_reporter: DiagnosticsReporter<'_>,
129    config: ExecutableConfig,
130) -> Result<CompileExecutableResult<'db>> {
131    ensure_diagnostics(db, &mut diagnostics_reporter)?;
132
133    let executables = find_executable_functions(db, main_crate_ids, executable_path);
134
135    let executable = match executables.len() {
136        0 => {
137            // Report diagnostics as they might reveal the reason why no executable was found.
138            anyhow::bail!("Requested `#[executable]` not found.");
139        }
140        1 => executables[0],
141        _ => {
142            let executable_names = executables
143                .iter()
144                .map(|executable| originating_function_path(db, *executable))
145                .join("\n  ");
146            anyhow::bail!(
147                "More than one executable found in the main crate: \n  {}\nUse --executable to \
148                 specify which to compile.",
149                executable_names
150            );
151        }
152    };
153
154    compile_executable_function_in_prepared_db(db, executable, config)
155}
156
157/// Search crates identified by `main_crate_ids` for executable functions.
158/// If `executable_path` is provided, only functions with exactly the same path will be returned.
159pub fn find_executable_functions<'db>(
160    db: &'db dyn Database,
161    main_crate_ids: Vec<CrateId<'db>>,
162    executable_path: Option<&str>,
163) -> Vec<ConcreteFunctionWithBodyId<'db>> {
164    let mut executables: Vec<_> = find_executable_function_ids(db, main_crate_ids)
165        .into_iter()
166        .filter_map(|(id, labels)| {
167            labels.into_iter().any(|ssid| ssid.long(db) == EXECUTABLE_RAW_ATTR).then_some(id)
168        })
169        .collect();
170
171    if let Some(executable_path) = executable_path {
172        executables
173            .retain(|executable| originating_function_path(db, *executable) == executable_path);
174    };
175    executables
176}
177
178/// Returns the path to the function that the executable is wrapping.
179///
180/// If the executable is not wrapping a function, returns the full path of the executable.
181pub fn originating_function_path<'db>(
182    db: &'db dyn Database,
183    wrapper: ConcreteFunctionWithBodyId<'db>,
184) -> String {
185    let semantic = wrapper.base_semantic_function(db);
186    let wrapper_name = semantic.name(db).long(db).as_str();
187    let wrapper_full_path = semantic.full_path(db);
188    let Some(wrapped_name) = wrapper_name.strip_prefix(EXECUTABLE_PREFIX) else {
189        return wrapper_full_path;
190    };
191    let Some(wrapper_path_to_module) = wrapper_full_path.strip_suffix(wrapper_name) else {
192        return wrapper_full_path;
193    };
194    format!("{wrapper_path_to_module}{wrapped_name}")
195}
196
197/// Runs compiler for an executable function.
198///
199/// # Arguments
200/// * `db` - Preloaded compilation database.
201/// * `executable` - [`ConcreteFunctionWithBodyId`]s to compile.
202/// * `diagnostics_reporter` - The diagnostics reporter.
203/// * `config` - If true, the compilation will not fail if the program is not sound.
204/// # Returns
205/// * `Ok(Vec<String>)` - The result artifact of the compilation.
206/// * `Err(anyhow::Error)` - Compilation failed.
207pub fn compile_executable_function_in_prepared_db<'db>(
208    db: &'db dyn Database,
209    executable: ConcreteFunctionWithBodyId<'db>,
210    config: ExecutableConfig,
211) -> Result<CompileExecutableResult<'db>> {
212    let SierraProgramWithDebug { program: sierra_program, debug_info } =
213        get_sierra_program_for_functions(db, vec![executable])
214            .with_context(|| "Compilation failed without any diagnostics.")?;
215    if !config.allow_syscalls {
216        // Finding if any syscall libfuncs are used in the program.
217        // If any are found, the compilation will fail, as syscalls are not proved in executables.
218        for libfunc in &sierra_program.libfunc_declarations {
219            if libfunc.long_id.generic_id.0.ends_with("_syscall") {
220                anyhow::bail!(
221                    "The function is using libfunc `{}`. Syscalls are not supported in \
222                     `#[executable]`.",
223                    libfunc.long_id.generic_id
224                );
225            }
226        }
227    }
228
229    // Since we build the entry point asking for a single function - we know it will be first, and
230    // that it will be available.
231    let executable_func = sierra_program.funcs[0].clone();
232    assert_eq!(executable_func.id, db.intern_sierra_function(executable.function_id(db).unwrap()));
233    let builder = RunnableBuilder::new(sierra_program.clone(), None).map_err(|err| {
234        let mut locs = vec![];
235        for stmt_idx in err.stmt_indices() {
236            if let Some(loc) =
237                debug_info.statements_locations.statement_diagnostic_location(db, stmt_idx)
238            {
239                locs.push(format!("#{stmt_idx} {:?}", loc.debug(db)))
240            }
241        }
242        anyhow::anyhow!("Failed to create runnable builder: {}\n{}", err, locs.join("\n"))
243    })?;
244
245    // If syscalls are allowed it means we allow for unsound programs.
246    let allow_unsound = config.allow_syscalls;
247    let wrapper = builder
248        .create_wrapper_info(&executable_func, EntryCodeConfig::executable(allow_unsound))?;
249    let compiled_function = CompiledFunction { program: builder.casm_program().clone(), wrapper };
250    Ok(CompileExecutableResult { compiled_function, builder, debug_info: debug_info.clone() })
251}