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_filesystem::ids::CrateId;
9use cairo_lang_lowering::ids::ConcreteFunctionWithBodyId;
10use cairo_lang_runnable_utils::builder::{
11    CasmProgramWrapperInfo, EntryCodeConfig, RunnableBuilder,
12};
13use cairo_lang_sierra_generator::db::SierraGenGroup;
14use cairo_lang_sierra_generator::executables::find_executable_function_ids;
15use cairo_lang_sierra_generator::program_generator::SierraProgramWithDebug;
16use cairo_lang_sierra_to_casm::compiler::CairoProgram;
17use cairo_lang_utils::{Upcast, write_comma_separated};
18use itertools::Itertools;
19
20use crate::plugin::{EXECUTABLE_PREFIX, EXECUTABLE_RAW_ATTR, executable_plugin_suite};
21
22/// The CASM compilation result.
23pub struct CompiledFunction {
24    /// The compiled CASM program.
25    pub program: CairoProgram,
26    /// The wrapper information for the program.
27    pub wrapper: CasmProgramWrapperInfo,
28}
29impl std::fmt::Display for CompiledFunction {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        write!(f, "# builtins:")?;
32        if !self.wrapper.builtins.is_empty() {
33            write!(f, " ")?;
34            write_comma_separated(f, self.wrapper.builtins.iter().map(|b| b.to_str()))?;
35        }
36        writeln!(f)?;
37        writeln!(f, "# header #")?;
38        for instruction in &self.wrapper.header {
39            writeln!(f, "{};", instruction)?;
40        }
41        writeln!(f, "# sierra based code #")?;
42        write!(f, "{}", self.program)?;
43        writeln!(f, "# footer #")?;
44        for instruction in &self.wrapper.footer {
45            writeln!(f, "{};", instruction)?;
46        }
47        Ok(())
48    }
49}
50
51/// Compile the function given by path.
52/// Errors if there is ambiguity.
53pub fn compile_executable(
54    path: &Path,
55    executable_path: Option<&str>,
56    diagnostics_reporter: DiagnosticsReporter<'_>,
57) -> Result<CompiledFunction> {
58    let mut db = RootDatabase::builder()
59        .skip_auto_withdraw_gas()
60        .detect_corelib()
61        .with_plugin_suite(executable_plugin_suite())
62        .build()?;
63
64    let main_crate_ids = setup_project(&mut db, Path::new(&path))?;
65
66    compile_executable_in_prepared_db(&db, executable_path, main_crate_ids, diagnostics_reporter)
67}
68
69/// Runs compiler on the specified executable function.
70/// If no executable was specified, verify that there is only one.
71/// Otherwise, return an error.
72pub fn compile_executable_in_prepared_db(
73    db: &RootDatabase,
74    executable_path: Option<&str>,
75    main_crate_ids: Vec<CrateId>,
76    mut diagnostics_reporter: DiagnosticsReporter<'_>,
77) -> Result<CompiledFunction> {
78    let mut executables: Vec<_> = find_executable_function_ids(db, main_crate_ids)
79        .into_iter()
80        .filter_map(|(id, labels)| {
81            labels.into_iter().any(|l| l == EXECUTABLE_RAW_ATTR).then_some(id)
82        })
83        .collect();
84
85    if let Some(executable_path) = executable_path {
86        executables
87            .retain(|executable| originating_function_path(db, *executable) == executable_path);
88    };
89    let executable = match executables.len() {
90        0 => {
91            // Report diagnostics as they might reveal the reason why no executable was found.
92            diagnostics_reporter.ensure(db)?;
93            anyhow::bail!("Requested `#[executable]` not found.");
94        }
95        1 => executables[0],
96        _ => {
97            let executable_names = executables
98                .iter()
99                .map(|executable| originating_function_path(db, *executable))
100                .join("\n  ");
101            anyhow::bail!(
102                "More than one executable found in the main crate: \n  {}\nUse --executable to \
103                 specify which to compile.",
104                executable_names
105            );
106        }
107    };
108
109    compile_executable_function_in_prepared_db(db, executable, diagnostics_reporter)
110}
111
112/// Returns the path to the function that the executable is wrapping.
113///
114/// If the executable is not wrapping a function, returns the full path of the executable.
115fn originating_function_path(db: &RootDatabase, wrapper: ConcreteFunctionWithBodyId) -> String {
116    let wrapper_name = wrapper.name(db);
117    let wrapper_full_path = wrapper.base_semantic_function(db).full_path(db.upcast());
118    let Some(wrapped_name) = wrapper_name.strip_prefix(EXECUTABLE_PREFIX) else {
119        return wrapper_full_path;
120    };
121    let Some(wrapper_path_to_module) = wrapper_full_path.strip_suffix(wrapper_name.as_str()) else {
122        return wrapper_full_path;
123    };
124    format!("{}{}", wrapper_path_to_module, wrapped_name)
125}
126
127/// Runs compiler for an executable function.
128///
129/// # Arguments
130/// * `db` - Preloaded compilation database.
131/// * `executable` - [`ConcreteFunctionWithBodyId`]s to compile.
132/// * `compiler_config` - The compiler configuration.
133/// # Returns
134/// * `Ok(Vec<String>)` - The result artifact of the compilation.
135/// * `Err(anyhow::Error)` - Compilation failed.
136pub fn compile_executable_function_in_prepared_db(
137    db: &RootDatabase,
138    executable: ConcreteFunctionWithBodyId,
139    mut diagnostics_reporter: DiagnosticsReporter<'_>,
140) -> Result<CompiledFunction> {
141    diagnostics_reporter.ensure(db)?;
142    let SierraProgramWithDebug { program: sierra_program, debug_info: _ } = Arc::unwrap_or_clone(
143        db.get_sierra_program_for_functions(vec![executable])
144            .ok()
145            .with_context(|| "Compilation failed without any diagnostics.")?,
146    );
147    let executable_func = sierra_program.funcs[0].clone();
148    let builder = RunnableBuilder::new(sierra_program, None)?;
149    let wrapper = builder.create_wrapper_info(&executable_func, EntryCodeConfig::executable())?;
150    Ok(CompiledFunction { program: builder.casm_program().clone(), wrapper })
151}