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::{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_plugin_suite(executable_plugin_suite())
74 .build()?;
75
76 let main_crate_ids = setup_project(&mut db, Path::new(&path))?;
77
78 compile_executable_in_prepared_db(
79 &db,
80 executable_path,
81 main_crate_ids,
82 diagnostics_reporter,
83 config,
84 )
85}
86
87pub fn compile_executable_in_prepared_db(
91 db: &RootDatabase,
92 executable_path: Option<&str>,
93 main_crate_ids: Vec<CrateId>,
94 mut diagnostics_reporter: DiagnosticsReporter<'_>,
95 config: ExecutableConfig,
96) -> Result<CompiledFunction> {
97 let mut executables: Vec<_> = find_executable_function_ids(db, main_crate_ids)
98 .into_iter()
99 .filter_map(|(id, labels)| {
100 labels.into_iter().any(|l| l == EXECUTABLE_RAW_ATTR).then_some(id)
101 })
102 .collect();
103
104 if let Some(executable_path) = executable_path {
105 executables
106 .retain(|executable| originating_function_path(db, *executable) == executable_path);
107 };
108 let executable = match executables.len() {
109 0 => {
110 diagnostics_reporter.ensure(db)?;
112 anyhow::bail!("Requested `#[executable]` not found.");
113 }
114 1 => executables[0],
115 _ => {
116 let executable_names = executables
117 .iter()
118 .map(|executable| originating_function_path(db, *executable))
119 .join("\n ");
120 anyhow::bail!(
121 "More than one executable found in the main crate: \n {}\nUse --executable to \
122 specify which to compile.",
123 executable_names
124 );
125 }
126 };
127
128 compile_executable_function_in_prepared_db(db, executable, diagnostics_reporter, config)
129}
130
131fn originating_function_path(db: &RootDatabase, wrapper: ConcreteFunctionWithBodyId) -> String {
135 let wrapper_name = wrapper.name(db);
136 let wrapper_full_path = wrapper.base_semantic_function(db).full_path(db.upcast());
137 let Some(wrapped_name) = wrapper_name.strip_prefix(EXECUTABLE_PREFIX) else {
138 return wrapper_full_path;
139 };
140 let Some(wrapper_path_to_module) = wrapper_full_path.strip_suffix(wrapper_name.as_str()) else {
141 return wrapper_full_path;
142 };
143 format!("{}{}", wrapper_path_to_module, wrapped_name)
144}
145
146pub fn compile_executable_function_in_prepared_db(
157 db: &RootDatabase,
158 executable: ConcreteFunctionWithBodyId,
159 mut diagnostics_reporter: DiagnosticsReporter<'_>,
160 config: ExecutableConfig,
161) -> Result<CompiledFunction> {
162 diagnostics_reporter.ensure(db)?;
163 let SierraProgramWithDebug { program: sierra_program, debug_info } = Arc::unwrap_or_clone(
164 db.get_sierra_program_for_functions(vec![executable])
165 .ok()
166 .with_context(|| "Compilation failed without any diagnostics.")?,
167 );
168 if !config.allow_syscalls {
169 for libfunc in &sierra_program.libfunc_declarations {
170 if libfunc.long_id.generic_id.0.ends_with("_syscall") {
171 anyhow::bail!(
172 "The function is using libfunc `{}`. Syscalls are not supported in \
173 `#[executable]`.",
174 libfunc.long_id.generic_id
175 );
176 }
177 }
178 }
179
180 let executable_func = sierra_program.funcs[0].clone();
181 let builder = RunnableBuilder::new(sierra_program, None).map_err(|err| {
182 let mut locs = vec![];
183 for stmt_idx in err.stmt_indices() {
184 if let Some(loc) = debug_info
186 .statements_locations
187 .locations
188 .get(&stmt_idx)
189 .and_then(|stmt_locs| stmt_locs.last())
190 {
191 locs.push(format!("#{stmt_idx} {:?}", loc.diagnostic_location(db).debug(db)))
192 }
193 }
194
195 anyhow::anyhow!("Failed to create runnable builder: {}\n{}", err, locs.join("\n"))
196 })?;
197
198 let wrapper = builder.create_wrapper_info(&executable_func, EntryCodeConfig::executable())?;
199 Ok(CompiledFunction { program: builder.casm_program().clone(), wrapper })
200}