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, 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 pub unsafe_panic: bool,
63}
64
65pub fn compile_executable(
68 path: &Path,
69 executable_path: Option<&str>,
70 diagnostics_reporter: DiagnosticsReporter<'_>,
71 config: ExecutableConfig,
72) -> Result<CompiledFunction> {
73 let mut builder = RootDatabase::builder();
74 builder
75 .skip_auto_withdraw_gas()
76 .with_cfg(CfgSet::from_iter([Cfg::kv("gas", "disabled")]))
77 .detect_corelib()
78 .with_default_plugin_suite(executable_plugin_suite());
79 if config.unsafe_panic {
80 builder.with_unsafe_panic();
81 }
82
83 let mut db = builder.build()?;
84
85 let main_crate_ids = setup_project(&mut db, Path::new(&path))?;
86 let diagnostics_reporter = diagnostics_reporter.with_crates(&main_crate_ids);
87
88 compile_executable_in_prepared_db(
89 &db,
90 executable_path,
91 main_crate_ids,
92 diagnostics_reporter,
93 config,
94 )
95}
96
97pub fn compile_executable_in_prepared_db(
101 db: &RootDatabase,
102 executable_path: Option<&str>,
103 main_crate_ids: Vec<CrateId>,
104 mut diagnostics_reporter: DiagnosticsReporter<'_>,
105 config: ExecutableConfig,
106) -> Result<CompiledFunction> {
107 let executables = find_executable_functions(db, main_crate_ids, executable_path);
108
109 let executable = match executables.len() {
110 0 => {
111 diagnostics_reporter.ensure(db)?;
113 anyhow::bail!("Requested `#[executable]` not found.");
114 }
115 1 => executables[0],
116 _ => {
117 let executable_names = executables
118 .iter()
119 .map(|executable| originating_function_path(db, *executable))
120 .join("\n ");
121 anyhow::bail!(
122 "More than one executable found in the main crate: \n {}\nUse --executable to \
123 specify which to compile.",
124 executable_names
125 );
126 }
127 };
128
129 compile_executable_function_in_prepared_db(db, executable, diagnostics_reporter, config)
130}
131
132pub fn find_executable_functions(
135 db: &RootDatabase,
136 main_crate_ids: Vec<CrateId>,
137 executable_path: Option<&str>,
138) -> Vec<ConcreteFunctionWithBodyId> {
139 let mut executables: Vec<_> = find_executable_function_ids(db, main_crate_ids)
140 .into_iter()
141 .filter_map(|(id, labels)| {
142 labels.into_iter().any(|l| l == EXECUTABLE_RAW_ATTR).then_some(id)
143 })
144 .collect();
145
146 if let Some(executable_path) = executable_path {
147 executables
148 .retain(|executable| originating_function_path(db, *executable) == executable_path);
149 };
150 executables
151}
152
153pub fn originating_function_path(db: &RootDatabase, wrapper: ConcreteFunctionWithBodyId) -> String {
157 let semantic = wrapper.base_semantic_function(db);
158 let wrapper_name = semantic.name(db);
159 let wrapper_full_path = semantic.full_path(db);
160 let Some(wrapped_name) = wrapper_name.strip_prefix(EXECUTABLE_PREFIX) else {
161 return wrapper_full_path;
162 };
163 let Some(wrapper_path_to_module) = wrapper_full_path.strip_suffix(wrapper_name.as_str()) else {
164 return wrapper_full_path;
165 };
166 format!("{wrapper_path_to_module}{wrapped_name}")
167}
168
169pub fn compile_executable_function_in_prepared_db(
180 db: &RootDatabase,
181 executable: ConcreteFunctionWithBodyId,
182 mut diagnostics_reporter: DiagnosticsReporter<'_>,
183 config: ExecutableConfig,
184) -> Result<CompiledFunction> {
185 diagnostics_reporter.ensure(db)?;
186 let SierraProgramWithDebug { program: sierra_program, debug_info } = Arc::unwrap_or_clone(
187 db.get_sierra_program_for_functions(vec![executable])
188 .ok()
189 .with_context(|| "Compilation failed without any diagnostics.")?,
190 );
191 if !config.allow_syscalls {
192 for libfunc in &sierra_program.libfunc_declarations {
195 if libfunc.long_id.generic_id.0.ends_with("_syscall") {
196 anyhow::bail!(
197 "The function is using libfunc `{}`. Syscalls are not supported in \
198 `#[executable]`.",
199 libfunc.long_id.generic_id
200 );
201 }
202 }
203 }
204
205 let executable_func = sierra_program.funcs[0].clone();
208 assert_eq!(executable_func.id, executable.function_id(db).unwrap().intern(db));
209 let builder = RunnableBuilder::new(sierra_program, None).map_err(|err| {
210 let mut locs = vec![];
211 for stmt_idx in err.stmt_indices() {
212 if let Some(loc) = debug_info
214 .statements_locations
215 .locations
216 .get(&stmt_idx)
217 .and_then(|stmt_locs| stmt_locs.last())
218 {
219 locs.push(format!("#{stmt_idx} {:?}", loc.diagnostic_location(db).debug(db)))
220 }
221 }
222
223 anyhow::anyhow!("Failed to create runnable builder: {}\n{}", err, locs.join("\n"))
224 })?;
225
226 let allow_unsound = config.allow_syscalls;
228 let wrapper = builder
229 .create_wrapper_info(&executable_func, EntryCodeConfig::executable(allow_unsound))?;
230 Ok(CompiledFunction { program: builder.casm_program().clone(), wrapper })
231}