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_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
26pub struct CompiledFunction {
28 pub program: CairoProgram,
30 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 pub allow_syscalls: bool,
61
62 pub unsafe_panic: bool,
65}
66
67pub struct CompileExecutableResult {
71 pub compiled_function: CompiledFunction,
73 pub builder: RunnableBuilder,
75 pub debug_info: SierraProgramDebugInfo,
77}
78
79pub 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
95pub 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
117pub 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 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
154pub 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
175pub 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
191pub 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 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 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 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 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}