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