1#![no_std]
2#![feature(debug_closure_helpers)]
3#![feature(specialization)]
4#![feature(slice_split_once)]
5#![allow(incomplete_features)]
7#![deny(warnings)]
8
9extern crate alloc;
10#[cfg(feature = "std")]
11extern crate std;
12
13use alloc::{
14 borrow::ToOwned,
15 format,
16 string::{String, ToString},
17 vec::Vec,
18};
19use core::str::FromStr;
20
21mod color;
22pub mod diagnostics;
23#[cfg(feature = "std")]
24mod duration;
25mod emit;
26mod emitter;
27pub mod flags;
28mod inputs;
29mod libs;
30mod options;
31mod outputs;
32mod path;
33#[cfg(feature = "std")]
34mod statistics;
35
36use alloc::{fmt, sync::Arc};
37
38pub const MIDENC_BUILD_VERSION: &str = env!("MIDENC_BUILD_VERSION");
40
41pub const MIDENC_BUILD_REV: &str = env!("MIDENC_BUILD_REV");
43
44pub use miden_assembly;
45use midenc_hir_symbol::Symbol;
46
47pub use self::{
48 color::ColorChoice,
49 diagnostics::{DiagnosticsHandler, Emitter, SourceManager},
50 emit::{Emit, Writer},
51 flags::{ArgMatches, CompileFlag, CompileFlags, FlagAction},
52 inputs::{FileName, FileType, InputFile, InputType, InvalidInputError},
53 libs::{
54 LibraryKind, LibraryNamespace, LibraryPath, LibraryPathComponent, LinkLibrary, STDLIB,
55 add_target_link_libraries,
56 },
57 options::*,
58 outputs::{OutputFile, OutputFiles, OutputMode, OutputType, OutputTypeSpec, OutputTypes},
59 path::{Path, PathBuf},
60};
61#[cfg(feature = "std")]
62pub use self::{duration::HumanDuration, emit::EmitExt, statistics::Statistics};
63
64#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
66pub enum ProjectType {
67 #[default]
69 Program,
70 Library,
72}
73impl ProjectType {
74 pub fn default_for_target(target: TargetEnv) -> Self {
75 match target {
76 TargetEnv::Base | TargetEnv::Rollup { .. } => Self::Program,
79 TargetEnv::Emu => Self::Library,
83 }
84 }
85}
86
87pub struct Session {
90 pub name: String,
92 pub options: Options,
94 pub source_manager: Arc<dyn SourceManager + Send + Sync>,
96 pub diagnostics: Arc<DiagnosticsHandler>,
98 pub inputs: Vec<InputFile>,
100 pub output_files: OutputFiles,
102 #[cfg(feature = "std")]
104 pub statistics: Statistics,
105}
106
107impl fmt::Debug for Session {
108 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109 let inputs = self.inputs.iter().map(|input| input.file_name()).collect::<Vec<_>>();
110 f.debug_struct("Session")
111 .field("name", &self.name)
112 .field("options", &self.options)
113 .field("inputs", &inputs)
114 .field("output_files", &self.output_files)
115 .finish_non_exhaustive()
116 }
117}
118
119impl Session {
120 pub fn new<I>(
121 inputs: I,
122 output_dir: Option<PathBuf>,
123 output_file: Option<OutputFile>,
124 target_dir: PathBuf,
125 options: Options,
126 emitter: Option<Arc<dyn Emitter>>,
127 source_manager: Arc<dyn SourceManager + Send + Sync>,
128 ) -> Self
129 where
130 I: IntoIterator<Item = InputFile>,
131 {
132 let inputs = inputs.into_iter().collect::<Vec<_>>();
133
134 Self::make(inputs, output_dir, output_file, target_dir, options, emitter, source_manager)
135 }
136
137 fn make(
138 inputs: Vec<InputFile>,
139 output_dir: Option<PathBuf>,
140 output_file: Option<OutputFile>,
141 target_dir: PathBuf,
142 options: Options,
143 emitter: Option<Arc<dyn Emitter>>,
144 source_manager: Arc<dyn SourceManager + Send + Sync>,
145 ) -> Self {
146 log::debug!(target: "driver", "creating session for {} inputs:", inputs.len());
147 if log::log_enabled!(target: "driver", log::Level::Debug) {
148 for input in inputs.iter() {
149 log::debug!(target: "driver", " - {} ({})", input.file_name(), input.file_type());
150 }
151 log::debug!(
152 target: "driver",
153 " | outputs_dir = {}",
154 output_dir
155 .as_ref()
156 .map(|p| p.display().to_string())
157 .unwrap_or("<unset>".to_string())
158 );
159 log::debug!(
160 target: "driver",
161 " | output_file = {}",
162 output_file.as_ref().map(|of| of.to_string()).unwrap_or("<unset>".to_string())
163 );
164 log::debug!(target: "driver", " | target_dir = {}", target_dir.display());
165 }
166 let diagnostics = Arc::new(DiagnosticsHandler::new(
167 options.diagnostics,
168 source_manager.clone(),
169 emitter.unwrap_or_else(|| options.default_emitter()),
170 ));
171
172 let output_dir = output_dir
173 .as_deref()
174 .or_else(|| output_file.as_ref().and_then(|of| of.parent()))
175 .map(|path| path.to_path_buf());
176
177 if let Some(output_dir) = output_dir.as_deref() {
178 log::debug!(target: "driver", " | output dir = {}", output_dir.display());
179 } else {
180 log::debug!(target: "driver", " | output dir = <unset>");
181 }
182
183 log::debug!(target: "driver", " | target = {}", &options.target);
184 log::debug!(target: "driver", " | type = {:?}", &options.project_type);
185 if log::log_enabled!(target: "driver", log::Level::Debug) {
186 for lib in options.link_libraries.iter() {
187 if let Some(path) = lib.path.as_deref() {
188 log::debug!(target: "driver", " | linking {} library '{}' from {}", &lib.kind, &lib.name, path.display());
189 } else {
190 log::debug!(target: "driver", " | linking {} library '{}'", &lib.kind, &lib.name);
191 }
192 }
193 }
194
195 let name = options
196 .name
197 .clone()
198 .or_else(|| {
199 log::debug!(target: "driver", "no name specified, attempting to derive from output file");
200 output_file.as_ref().and_then(|of| of.filestem().map(|stem| stem.to_string()))
201 })
202 .unwrap_or_else(|| {
203 log::debug!(target: "driver", "unable to derive name from output file, deriving from input");
204 match inputs.first() {
205 Some(InputFile {
206 file: InputType::Real(path),
207 ..
208 }) => path
209 .file_stem()
210 .and_then(|stem| stem.to_str())
211 .or_else(|| path.extension().and_then(|stem| stem.to_str()))
212 .unwrap_or_else(|| {
213 panic!(
214 "invalid input path: '{}' has no file stem or extension",
215 path.display()
216 )
217 })
218 .to_string(),
219 Some(
220 input @ InputFile {
221 file: InputType::Stdin { name, .. },
222 ..
223 },
224 ) => {
225 let name = name.as_str();
226 if matches!(name, "empty" | "stdin") {
227 log::debug!(target: "driver", "no good input file name to use, using current directory base name");
228 options
229 .current_dir
230 .file_stem()
231 .and_then(|stem| stem.to_str())
232 .unwrap_or(name)
233 .to_string()
234 } else {
235 input.filestem().to_owned()
236 }
237 }
238 None => "out".to_owned(),
239 }
240 });
241 log::debug!(target: "driver", "artifact name set to '{name}'");
242
243 let output_files = OutputFiles::new(
244 name.clone(),
245 options.current_dir.clone(),
246 output_dir.unwrap_or_else(|| options.current_dir.clone()),
247 output_file,
248 target_dir,
249 options.output_types.clone(),
250 );
251
252 Self {
253 name,
254 options,
255 source_manager,
256 diagnostics,
257 inputs,
258 output_files,
259 #[cfg(feature = "std")]
260 statistics: Default::default(),
261 }
262 }
263
264 pub fn with_project_type(mut self, ty: ProjectType) -> Self {
265 self.options.project_type = ty;
266 self
267 }
268
269 #[doc(hidden)]
270 pub fn with_output_type(mut self, ty: OutputType, path: Option<OutputFile>) -> Self {
271 self.output_files.outputs.insert(ty, path.clone());
272 self.options.output_types.insert(ty, path.clone());
273 self
274 }
275
276 #[doc(hidden)]
277 pub fn with_extra_flags(mut self, flags: CompileFlags) -> Self {
278 self.options.set_extra_flags(flags);
279 self
280 }
281
282 #[inline]
284 pub fn get_flag(&self, name: &str) -> bool {
285 self.options.flags.get_flag(name)
286 }
287
288 #[inline]
290 pub fn get_flag_count(&self, name: &str) -> usize {
291 self.options.flags.get_flag_count(name)
292 }
293
294 #[inline]
296 pub fn matches(&self) -> &ArgMatches {
297 self.options.flags.matches()
298 }
299
300 pub fn name(&self) -> &str {
302 &self.name
303 }
304
305 pub fn out_file(&self) -> OutputFile {
307 let out_file = self.output_files.output_file(OutputType::Masl, None);
308
309 if let OutputFile::Real(ref path) = out_file {
310 self.check_file_is_writeable(path);
311 }
312
313 out_file
314 }
315
316 #[cfg(not(feature = "std"))]
317 fn check_file_is_writeable(&self, file: &Path) {
318 panic!(
319 "Compiler exited with a fatal error: cannot write '{}' - compiler was built without \
320 standard library",
321 file.display()
322 );
323 }
324
325 #[cfg(feature = "std")]
326 fn check_file_is_writeable(&self, file: &Path) {
327 if let Ok(m) = file.metadata()
328 && m.permissions().readonly()
329 {
330 panic!("Compiler exited with a fatal error: file is not writeable: {}", file.display());
331 }
332 }
333
334 pub fn parse_only(&self) -> bool {
336 self.options.parse_only
337 }
338
339 pub fn analyze_only(&self) -> bool {
341 self.options.analyze_only
342 }
343
344 pub fn rewrite_only(&self) -> bool {
346 let link_or_masm_requested = self.should_link() || self.should_codegen();
347 !self.options.parse_only && !self.options.analyze_only && !link_or_masm_requested
348 }
349
350 pub fn should_link(&self) -> bool {
352 self.options.output_types.should_link() && !self.options.no_link
353 }
354
355 pub fn should_codegen(&self) -> bool {
357 self.options.output_types.should_codegen() && !self.options.link_only
358 }
359
360 pub fn should_assemble(&self) -> bool {
362 self.options.output_types.should_assemble() && !self.options.link_only
363 }
364
365 pub fn should_emit(&self, ty: OutputType) -> bool {
367 self.options.output_types.contains_key(&ty)
368 }
369
370 pub fn should_print_ir(&self, pass: &str) -> bool {
372 self.options.print_ir_after_all
373 || self.options.print_ir_after_pass.iter().any(|p| p == pass)
374 }
375
376 pub fn should_print_cfg(&self, pass: &str) -> bool {
378 self.options.print_cfg_after_all
379 || self.options.print_cfg_after_pass.iter().any(|p| p == pass)
380 }
381
382 #[cfg(feature = "std")]
384 pub fn print(&self, ir: impl Emit, pass: &str) -> anyhow::Result<()> {
385 if self.should_print_ir(pass) {
386 ir.write_to_stdout(self)?;
387 }
388 Ok(())
389 }
390
391 pub fn emit_to(&self, ty: OutputType, name: Option<Symbol>) -> Option<PathBuf> {
393 if self.should_emit(ty) {
394 match self.output_files.output_file(ty, name.map(|n| n.as_str())) {
395 OutputFile::Real(path) => Some(path),
396 OutputFile::Stdout => None,
397 }
398 } else {
399 None
400 }
401 }
402
403 #[cfg(feature = "std")]
405 pub fn emit<E: Emit>(&self, mode: OutputMode, item: &E) -> anyhow::Result<()> {
406 let output_type = item.output_type(mode);
407 if self.should_emit(output_type) {
408 let name = item.name().map(|n| n.as_str());
409 match self.output_files.output_file(output_type, name) {
410 OutputFile::Real(path) => {
411 item.write_to_file(&path, mode, self)?;
412 }
413 OutputFile::Stdout => {
414 let stdout = std::io::stdout().lock();
415 item.write_to(stdout, mode, self)?;
416 }
417 }
418 }
419
420 Ok(())
421 }
422
423 #[cfg(not(feature = "std"))]
424 pub fn emit<E: Emit>(&self, _mode: OutputMode, _item: &E) -> anyhow::Result<()> {
425 Ok(())
426 }
427}
428
429#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
431pub enum TargetEnv {
432 Emu,
434 #[default]
436 Base,
437 Rollup { target: RollupTarget },
439}
440impl fmt::Display for TargetEnv {
441 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
442 match self {
443 Self::Emu => f.write_str("emu"),
444 Self::Base => f.write_str("base"),
445 Self::Rollup { target } => f.write_str(&format!("rollup:{target}")),
446 }
447 }
448}
449
450impl FromStr for TargetEnv {
451 type Err = anyhow::Error;
452
453 fn from_str(s: &str) -> Result<Self, Self::Err> {
454 match s {
455 "emu" => Ok(Self::Emu),
456 "base" => Ok(Self::Base),
457 "rollup" => Ok(Self::Rollup {
458 target: RollupTarget::default(),
459 }),
460 "rollup:account" => Ok(Self::Rollup {
461 target: RollupTarget::Account,
462 }),
463 "rollup:note-script" => Ok(Self::Rollup {
464 target: RollupTarget::NoteScript,
465 }),
466 "rollup:transaction-script" => Ok(Self::Rollup {
467 target: RollupTarget::TransactionScript,
468 }),
469 "rollup:authentication-component" => Ok(Self::Rollup {
470 target: RollupTarget::AuthComponent,
471 }),
472 _ => Err(anyhow::anyhow!("invalid target environment: {s}")),
473 }
474 }
475}
476
477#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
479pub enum RollupTarget {
480 #[default]
481 Account,
482 NoteScript,
483 TransactionScript,
484 AuthComponent,
488}
489
490impl fmt::Display for RollupTarget {
491 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
492 match self {
493 Self::Account => f.write_str("account"),
494 Self::NoteScript => f.write_str("note-script"),
495 Self::TransactionScript => f.write_str("transaction-script"),
496 Self::AuthComponent => f.write_str("authentication-component"),
497 }
498 }
499}