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 add_target_link_libraries, LibraryKind, LibraryNamespace, LibraryPath,
55 LibraryPathComponent, LinkLibrary, STDLIB,
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)]
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>,
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>,
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>,
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(ref 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 { ref 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 if m.permissions().readonly() {
329 panic!(
330 "Compiler exited with a fatal error: file is not writeable: {}",
331 file.display()
332 );
333 }
334 }
335 }
336
337 pub fn parse_only(&self) -> bool {
339 self.options.parse_only
340 }
341
342 pub fn analyze_only(&self) -> bool {
344 self.options.analyze_only
345 }
346
347 pub fn rewrite_only(&self) -> bool {
349 let link_or_masm_requested = self.should_link() || self.should_codegen();
350 !self.options.parse_only && !self.options.analyze_only && !link_or_masm_requested
351 }
352
353 pub fn should_link(&self) -> bool {
355 self.options.output_types.should_link() && !self.options.no_link
356 }
357
358 pub fn should_codegen(&self) -> bool {
360 self.options.output_types.should_codegen() && !self.options.link_only
361 }
362
363 pub fn should_assemble(&self) -> bool {
365 self.options.output_types.should_assemble() && !self.options.link_only
366 }
367
368 pub fn should_emit(&self, ty: OutputType) -> bool {
370 self.options.output_types.contains_key(&ty)
371 }
372
373 pub fn should_print_ir(&self, pass: &str) -> bool {
375 self.options.print_ir_after_all
376 || self.options.print_ir_after_pass.iter().any(|p| p == pass)
377 }
378
379 pub fn should_print_cfg(&self, pass: &str) -> bool {
381 self.options.print_cfg_after_all
382 || self.options.print_cfg_after_pass.iter().any(|p| p == pass)
383 }
384
385 #[cfg(feature = "std")]
387 pub fn print(&self, ir: impl Emit, pass: &str) -> anyhow::Result<()> {
388 if self.should_print_ir(pass) {
389 ir.write_to_stdout(self)?;
390 }
391 Ok(())
392 }
393
394 pub fn emit_to(&self, ty: OutputType, name: Option<Symbol>) -> Option<PathBuf> {
396 if self.should_emit(ty) {
397 match self.output_files.output_file(ty, name.map(|n| n.as_str())) {
398 OutputFile::Real(path) => Some(path),
399 OutputFile::Stdout => None,
400 }
401 } else {
402 None
403 }
404 }
405
406 #[cfg(feature = "std")]
408 pub fn emit<E: Emit>(&self, mode: OutputMode, item: &E) -> anyhow::Result<()> {
409 let output_type = item.output_type(mode);
410 if self.should_emit(output_type) {
411 let name = item.name().map(|n| n.as_str());
412 match self.output_files.output_file(output_type, name) {
413 OutputFile::Real(path) => {
414 item.write_to_file(&path, mode, self)?;
415 }
416 OutputFile::Stdout => {
417 let stdout = std::io::stdout().lock();
418 item.write_to(stdout, mode, self)?;
419 }
420 }
421 }
422
423 Ok(())
424 }
425
426 #[cfg(not(feature = "std"))]
427 pub fn emit<E: Emit>(&self, _mode: OutputMode, _item: &E) -> anyhow::Result<()> {
428 Ok(())
429 }
430}
431
432#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
434pub enum TargetEnv {
435 Emu,
437 #[default]
439 Base,
440 Rollup { target: RollupTarget },
442}
443impl fmt::Display for TargetEnv {
444 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
445 match self {
446 Self::Emu => f.write_str("emu"),
447 Self::Base => f.write_str("base"),
448 Self::Rollup { target } => f.write_str(&format!("rollup:{}", target)),
449 }
450 }
451}
452
453impl FromStr for TargetEnv {
454 type Err = anyhow::Error;
455
456 fn from_str(s: &str) -> Result<Self, Self::Err> {
457 match s {
458 "emu" => Ok(Self::Emu),
459 "base" => Ok(Self::Base),
460 "rollup" => Ok(Self::Rollup {
461 target: RollupTarget::default(),
462 }),
463 "rollup:account" => Ok(Self::Rollup {
464 target: RollupTarget::Account,
465 }),
466 "rollup:note_script" => Ok(Self::Rollup {
467 target: RollupTarget::NoteScript,
468 }),
469 _ => Err(anyhow::anyhow!("invalid target environment: {}", s)),
470 }
471 }
472}
473
474#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
476pub enum RollupTarget {
477 #[default]
478 Account,
479 NoteScript,
480}
481
482impl fmt::Display for RollupTarget {
483 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
484 match self {
485 Self::Account => f.write_str("account"),
486 Self::NoteScript => f.write_str("note_script"),
487 }
488 }
489}