cairo_lang_executable/
compile.rs

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