cairo_lang_executable/
compile.rs1use std::path::Path;
2
3use anyhow::Result;
4use cairo_lang_compiler::db::RootDatabase;
5use cairo_lang_compiler::diagnostics::DiagnosticsReporter;
6use cairo_lang_compiler::project::setup_project;
7use cairo_lang_compiler::{ensure_diagnostics, get_sierra_program_for_functions};
8use cairo_lang_debug::debug::DebugWithDb;
9use cairo_lang_executable_plugin::{
10 EXECUTABLE_PREFIX, EXECUTABLE_RAW_ATTR, executable_plugin_suite,
11};
12use cairo_lang_filesystem::cfg::{Cfg, CfgSet};
13use cairo_lang_filesystem::ids::{CrateId, CrateInput};
14use cairo_lang_lowering::ids::ConcreteFunctionWithBodyId;
15use cairo_lang_runnable_utils::builder::{
16 CasmProgramWrapperInfo, EntryCodeConfig, RunnableBuilder,
17};
18use cairo_lang_sierra_generator::db::SierraGenGroup;
19use cairo_lang_sierra_generator::debug_info::SierraProgramDebugInfo;
20use cairo_lang_sierra_generator::executables::find_executable_function_ids;
21use cairo_lang_sierra_generator::program_generator::SierraProgramWithDebug;
22use cairo_lang_sierra_to_casm::compiler::CairoProgram;
23use cairo_lang_utils::{CloneableDatabase, write_comma_separated};
24use cairo_vm::types::builtin_name::BuiltinName;
25use itertools::Itertools;
26use salsa::Database;
27
28pub struct CompiledFunction {
30 pub program: CairoProgram,
32 pub wrapper: CasmProgramWrapperInfo,
34}
35impl std::fmt::Display for CompiledFunction {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 write!(f, "// builtins:")?;
38 if !self.wrapper.builtins.is_empty() {
39 write!(f, " ")?;
40 write_comma_separated(f, self.wrapper.builtins.iter().map(|b| b.to_str()))?;
41 }
42 writeln!(f)?;
43 writeln!(f, "// header")?;
44 for instruction in &self.wrapper.header {
45 writeln!(f, "{instruction};")?;
46 }
47 writeln!(f, "// sierra based code")?;
48 write!(f, "{}", self.program)?;
49 writeln!(f, "// footer")?;
50 for instruction in &self.wrapper.footer {
51 writeln!(f, "{instruction};")?;
52 }
53 Ok(())
54 }
55}
56
57#[derive(Debug, Clone, Default)]
58pub struct ExecutableConfig {
59 pub allow_syscalls: bool,
63
64 pub unsafe_panic: bool,
67
68 pub builtin_list: Option<Vec<BuiltinName>>,
71}
72
73pub struct CompileExecutableResult<'db> {
77 pub compiled_function: CompiledFunction,
79 pub builder: RunnableBuilder,
81 pub debug_info: SierraProgramDebugInfo<'db>,
83}
84
85pub fn prepare_db(config: &ExecutableConfig) -> Result<RootDatabase> {
90 let mut builder = RootDatabase::builder();
91 builder
92 .skip_auto_withdraw_gas()
93 .with_cfg(CfgSet::from_iter([Cfg::kv("gas", "disabled")]))
94 .detect_corelib()
95 .with_default_plugin_suite(executable_plugin_suite());
96 if config.unsafe_panic {
97 builder.with_unsafe_panic();
98 }
99
100 builder.build()
101}
102
103pub fn compile_executable<'db>(
106 db: &'db mut dyn CloneableDatabase,
107 path: &Path,
108 executable_path: Option<&str>,
109 diagnostics_reporter: DiagnosticsReporter<'_>,
110 config: ExecutableConfig,
111) -> Result<CompileExecutableResult<'db>> {
112 let main_crate_inputs = setup_project(db, path)?;
113 let diagnostics_reporter = diagnostics_reporter.with_crates(&main_crate_inputs);
114 let main_crate_ids = CrateInput::into_crate_ids(db, main_crate_inputs);
115
116 compile_executable_in_prepared_db(
117 db,
118 executable_path,
119 main_crate_ids,
120 diagnostics_reporter,
121 config,
122 )
123}
124
125pub fn compile_executable_in_prepared_db<'db>(
129 db: &'db dyn CloneableDatabase,
130 executable_path: Option<&str>,
131 main_crate_ids: Vec<CrateId<'db>>,
132 mut diagnostics_reporter: DiagnosticsReporter<'_>,
133 config: ExecutableConfig,
134) -> Result<CompileExecutableResult<'db>> {
135 ensure_diagnostics(db, &mut diagnostics_reporter)?;
136
137 let executables = find_executable_functions(db, main_crate_ids, executable_path);
138
139 match executables.iter().exactly_one() {
140 Ok(executable) => compile_executable_function_in_prepared_db(db, *executable, config),
141 Err(_) if executables.is_empty() => {
142 anyhow::bail!("Requested `#[executable]` not found.");
143 }
144 Err(_) => {
145 let executable_names = executables
146 .into_iter()
147 .map(|executable| originating_function_path(db, executable))
148 .join("\n ");
149 anyhow::bail!(
150 "More than one executable found in the main crate: \n {}\nUse --executable to \
151 specify which to compile.",
152 executable_names
153 );
154 }
155 }
156}
157
158pub fn find_executable_functions<'db>(
161 db: &'db dyn Database,
162 main_crate_ids: Vec<CrateId<'db>>,
163 executable_path: Option<&str>,
164) -> Vec<ConcreteFunctionWithBodyId<'db>> {
165 let mut executables: Vec<_> = find_executable_function_ids(db, main_crate_ids)
166 .into_iter()
167 .filter_map(|(id, labels)| {
168 labels.into_iter().any(|ssid| ssid.long(db) == EXECUTABLE_RAW_ATTR).then_some(id)
169 })
170 .collect();
171
172 if let Some(executable_path) = executable_path {
173 executables
174 .retain(|executable| originating_function_path(db, *executable) == executable_path);
175 };
176 executables
177}
178
179pub fn originating_function_path<'db>(
183 db: &'db dyn Database,
184 wrapper: ConcreteFunctionWithBodyId<'db>,
185) -> String {
186 let semantic = wrapper.base_semantic_function(db);
187 let wrapper_name = semantic.name(db).long(db).as_str();
188 let wrapper_full_path = semantic.full_path(db);
189 let Some(wrapped_name) = wrapper_name.strip_prefix(EXECUTABLE_PREFIX) else {
190 return wrapper_full_path;
191 };
192 let Some(wrapper_path_to_module) = wrapper_full_path.strip_suffix(wrapper_name) else {
193 return wrapper_full_path;
194 };
195 format!("{wrapper_path_to_module}{wrapped_name}")
196}
197
198pub fn compile_executable_function_in_prepared_db<'db>(
211 db: &'db dyn CloneableDatabase,
212 executable: ConcreteFunctionWithBodyId<'db>,
213 config: ExecutableConfig,
214) -> Result<CompileExecutableResult<'db>> {
215 let SierraProgramWithDebug { program: sierra_program, debug_info } =
216 get_sierra_program_for_functions(db, vec![executable])?;
217 if !config.allow_syscalls {
218 for libfunc in &sierra_program.libfunc_declarations {
221 if libfunc.long_id.generic_id.0.ends_with("_syscall") {
222 anyhow::bail!(
223 "The function is using libfunc `{}`. Syscalls are not supported in \
224 `#[executable]`.",
225 libfunc.long_id.generic_id
226 );
227 }
228 }
229 }
230
231 let executable_func = sierra_program.funcs[0].clone();
234 assert_eq!(executable_func.id, db.intern_sierra_function(executable.function_id(db).unwrap()));
235 let builder = RunnableBuilder::new(sierra_program.clone(), None).map_err(|err| {
236 let mut locs = vec![];
237 for stmt_idx in err.stmt_indices() {
238 if let Some(loc) =
239 debug_info.statements_locations.statement_diagnostic_location(db, stmt_idx)
240 {
241 locs.push(format!("#{stmt_idx} {:?}", loc.debug(db)))
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.create_wrapper_info(
250 &executable_func,
251 EntryCodeConfig::executable(allow_unsound, config.builtin_list),
252 )?;
253 let compiled_function = CompiledFunction { program: builder.casm_program().clone(), wrapper };
254 Ok(CompileExecutableResult { compiled_function, builder, debug_info: debug_info.clone() })
255}