cairo_lang_executable/
compile.rs1use 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
24pub struct CompiledFunction {
26 pub program: CairoProgram,
28 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 pub allow_syscalls: bool,
59}
60
61pub 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
88pub 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 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
123pub 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
144pub 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
160pub 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 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 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 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 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}