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::{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_plugin_suite(executable_plugin_suite())
74        .build()?;
75
76    let main_crate_ids = setup_project(&mut db, Path::new(&path))?;
77
78    compile_executable_in_prepared_db(
79        &db,
80        executable_path,
81        main_crate_ids,
82        diagnostics_reporter,
83        config,
84    )
85}
86
87/// Runs compiler on the specified executable function.
88/// If no executable was specified, verify that there is only one.
89/// Otherwise, return an error.
90pub fn compile_executable_in_prepared_db(
91    db: &RootDatabase,
92    executable_path: Option<&str>,
93    main_crate_ids: Vec<CrateId>,
94    mut diagnostics_reporter: DiagnosticsReporter<'_>,
95    config: ExecutableConfig,
96) -> Result<CompiledFunction> {
97    let mut executables: Vec<_> = find_executable_function_ids(db, main_crate_ids)
98        .into_iter()
99        .filter_map(|(id, labels)| {
100            labels.into_iter().any(|l| l == EXECUTABLE_RAW_ATTR).then_some(id)
101        })
102        .collect();
103
104    if let Some(executable_path) = executable_path {
105        executables
106            .retain(|executable| originating_function_path(db, *executable) == executable_path);
107    };
108    let executable = match executables.len() {
109        0 => {
110            // Report diagnostics as they might reveal the reason why no executable was found.
111            diagnostics_reporter.ensure(db)?;
112            anyhow::bail!("Requested `#[executable]` not found.");
113        }
114        1 => executables[0],
115        _ => {
116            let executable_names = executables
117                .iter()
118                .map(|executable| originating_function_path(db, *executable))
119                .join("\n  ");
120            anyhow::bail!(
121                "More than one executable found in the main crate: \n  {}\nUse --executable to \
122                 specify which to compile.",
123                executable_names
124            );
125        }
126    };
127
128    compile_executable_function_in_prepared_db(db, executable, diagnostics_reporter, config)
129}
130
131/// Returns the path to the function that the executable is wrapping.
132///
133/// If the executable is not wrapping a function, returns the full path of the executable.
134fn originating_function_path(db: &RootDatabase, wrapper: ConcreteFunctionWithBodyId) -> String {
135    let wrapper_name = wrapper.name(db);
136    let wrapper_full_path = wrapper.base_semantic_function(db).full_path(db.upcast());
137    let Some(wrapped_name) = wrapper_name.strip_prefix(EXECUTABLE_PREFIX) else {
138        return wrapper_full_path;
139    };
140    let Some(wrapper_path_to_module) = wrapper_full_path.strip_suffix(wrapper_name.as_str()) else {
141        return wrapper_full_path;
142    };
143    format!("{}{}", wrapper_path_to_module, wrapped_name)
144}
145
146/// Runs compiler for an executable function.
147///
148/// # Arguments
149/// * `db` - Preloaded compilation database.
150/// * `executable` - [`ConcreteFunctionWithBodyId`]s to compile.
151/// * `diagnostics_reporter` - The diagnostics reporter.
152/// * `config` - If true, the compilation will not fail if the program is not sound.
153/// # Returns
154/// * `Ok(Vec<String>)` - The result artifact of the compilation.
155/// * `Err(anyhow::Error)` - Compilation failed.
156pub fn compile_executable_function_in_prepared_db(
157    db: &RootDatabase,
158    executable: ConcreteFunctionWithBodyId,
159    mut diagnostics_reporter: DiagnosticsReporter<'_>,
160    config: ExecutableConfig,
161) -> Result<CompiledFunction> {
162    diagnostics_reporter.ensure(db)?;
163    let SierraProgramWithDebug { program: sierra_program, debug_info } = Arc::unwrap_or_clone(
164        db.get_sierra_program_for_functions(vec![executable])
165            .ok()
166            .with_context(|| "Compilation failed without any diagnostics.")?,
167    );
168    if !config.allow_syscalls {
169        for libfunc in &sierra_program.libfunc_declarations {
170            if libfunc.long_id.generic_id.0.ends_with("_syscall") {
171                anyhow::bail!(
172                    "The function is using libfunc `{}`. Syscalls are not supported in \
173                     `#[executable]`.",
174                    libfunc.long_id.generic_id
175                );
176            }
177        }
178    }
179
180    let executable_func = sierra_program.funcs[0].clone();
181    let builder = RunnableBuilder::new(sierra_program, None).map_err(|err| {
182        let mut locs = vec![];
183        for stmt_idx in err.stmt_indices() {
184            // Note that the `last` is used here as the call site is the most relevant location.
185            if let Some(loc) = debug_info
186                .statements_locations
187                .locations
188                .get(&stmt_idx)
189                .and_then(|stmt_locs| stmt_locs.last())
190            {
191                locs.push(format!("#{stmt_idx} {:?}", loc.diagnostic_location(db).debug(db)))
192            }
193        }
194
195        anyhow::anyhow!("Failed to create runnable builder: {}\n{}", err, locs.join("\n"))
196    })?;
197
198    let wrapper = builder.create_wrapper_info(&executable_func, EntryCodeConfig::executable())?;
199    Ok(CompiledFunction { program: builder.casm_program().clone(), wrapper })
200}