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 string::{String, ToString},
16 vec::Vec,
17};
18
19mod color;
20pub mod diagnostics;
21#[cfg(feature = "std")]
22mod duration;
23mod emit;
24mod emitter;
25pub mod flags;
26mod inputs;
27mod libs;
28mod options;
29mod outputs;
30mod path;
31#[cfg(feature = "std")]
32mod statistics;
33
34use alloc::{fmt, sync::Arc};
35
36pub const MIDENC_BUILD_VERSION: &str = env!("MIDENC_BUILD_VERSION");
38
39pub const MIDENC_BUILD_REV: &str = env!("MIDENC_BUILD_REV");
41
42#[cfg(feature = "std")]
43use clap::ValueEnum;
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, BASE, STDLIB,
55 },
56 options::*,
57 outputs::{OutputFile, OutputFiles, OutputMode, OutputType, OutputTypeSpec, OutputTypes},
58 path::{Path, PathBuf},
59};
60#[cfg(feature = "std")]
61pub use self::{duration::HumanDuration, emit::EmitExt, statistics::Statistics};
62
63#[derive(Debug, Copy, Clone, Default)]
65pub enum ProjectType {
66 #[default]
68 Program,
69 Library,
71}
72impl ProjectType {
73 pub fn default_for_target(target: TargetEnv) -> Self {
74 match target {
75 TargetEnv::Base | TargetEnv::Rollup => Self::Program,
78 TargetEnv::Emu => Self::Library,
82 }
83 }
84}
85
86pub struct Session {
89 pub name: String,
91 pub options: Options,
93 pub source_manager: Arc<dyn SourceManager>,
95 pub diagnostics: Arc<DiagnosticsHandler>,
97 pub inputs: Vec<InputFile>,
99 pub output_files: OutputFiles,
101 #[cfg(feature = "std")]
103 pub statistics: Statistics,
104}
105
106impl fmt::Debug for Session {
107 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
108 let inputs = self.inputs.iter().map(|input| input.file_name()).collect::<Vec<_>>();
109 f.debug_struct("Session")
110 .field("name", &self.name)
111 .field("options", &self.options)
112 .field("inputs", &inputs)
113 .field("output_files", &self.output_files)
114 .finish_non_exhaustive()
115 }
116}
117
118impl Session {
119 pub fn new<I>(
120 inputs: I,
121 output_dir: Option<PathBuf>,
122 output_file: Option<OutputFile>,
123 target_dir: PathBuf,
124 options: Options,
125 emitter: Option<Arc<dyn Emitter>>,
126 source_manager: Arc<dyn SourceManager>,
127 ) -> Self
128 where
129 I: IntoIterator<Item = InputFile>,
130 {
131 let inputs = inputs.into_iter().collect::<Vec<_>>();
132
133 Self::make(inputs, output_dir, output_file, target_dir, options, emitter, source_manager)
134 }
135
136 fn make(
137 inputs: Vec<InputFile>,
138 output_dir: Option<PathBuf>,
139 output_file: Option<OutputFile>,
140 target_dir: PathBuf,
141 options: Options,
142 emitter: Option<Arc<dyn Emitter>>,
143 source_manager: Arc<dyn SourceManager>,
144 ) -> Self {
145 log::debug!(target: "driver", "creating session for {} inputs:", inputs.len());
146 if log::log_enabled!(target: "driver", log::Level::Debug) {
147 for input in inputs.iter() {
148 log::debug!(target: "driver", " - {} ({})", input.file_name(), input.file_type());
149 }
150 log::debug!(
151 target: "driver",
152 " | outputs_dir = {}",
153 output_dir
154 .as_ref()
155 .map(|p| p.display().to_string())
156 .unwrap_or("<unset>".to_string())
157 );
158 log::debug!(
159 target: "driver",
160 " | output_file = {}",
161 output_file.as_ref().map(|of| of.to_string()).unwrap_or("<unset>".to_string())
162 );
163 log::debug!(target: "driver", " | target_dir = {}", target_dir.display());
164 }
165 let diagnostics = Arc::new(DiagnosticsHandler::new(
166 options.diagnostics,
167 source_manager.clone(),
168 emitter.unwrap_or_else(|| options.default_emitter()),
169 ));
170
171 let output_dir = output_dir
172 .as_deref()
173 .or_else(|| output_file.as_ref().and_then(|of| of.parent()))
174 .map(|path| path.to_path_buf());
175
176 if let Some(output_dir) = output_dir.as_deref() {
177 log::debug!(target: "driver", " | output dir = {}", output_dir.display());
178 } else {
179 log::debug!(target: "driver", " | output dir = <unset>");
180 }
181
182 log::debug!(target: "driver", " | target = {}", &options.target);
183 log::debug!(target: "driver", " | type = {:?}", &options.project_type);
184 if log::log_enabled!(target: "driver", log::Level::Debug) {
185 for lib in options.link_libraries.iter() {
186 if let Some(path) = lib.path.as_deref() {
187 log::debug!(target: "driver", " | linking {} library '{}' from {}", &lib.kind, &lib.name, path.display());
188 } else {
189 log::debug!(target: "driver", " | linking {} library '{}'", &lib.kind, &lib.name);
190 }
191 }
192 }
193
194 let name = options
195 .name
196 .clone()
197 .or_else(|| {
198 log::debug!(target: "driver", "no name specified, attempting to derive from output file");
199 output_file.as_ref().and_then(|of| of.filestem().map(|stem| stem.to_string()))
200 })
201 .unwrap_or_else(|| {
202 log::debug!(target: "driver", "unable to derive name from output file, deriving from input");
203 match inputs.first() {
204 Some(InputFile {
205 file: InputType::Real(ref path),
206 ..
207 }) => path
208 .file_stem()
209 .and_then(|stem| stem.to_str())
210 .or_else(|| path.extension().and_then(|stem| stem.to_str()))
211 .unwrap_or_else(|| {
212 panic!(
213 "invalid input path: '{}' has no file stem or extension",
214 path.display()
215 )
216 })
217 .to_string(),
218 Some(
219 input @ InputFile {
220 file: InputType::Stdin { ref name, .. },
221 ..
222 },
223 ) => {
224 let name = name.as_str();
225 if matches!(name, "empty" | "stdin") {
226 log::debug!(target: "driver", "no good input file name to use, using current directory base name");
227 options
228 .current_dir
229 .file_stem()
230 .and_then(|stem| stem.to_str())
231 .unwrap_or(name)
232 .to_string()
233 } else {
234 input.filestem().to_owned()
235 }
236 }
237 None => "out".to_owned(),
238 }
239 });
240 log::debug!(target: "driver", "artifact name set to '{name}'");
241
242 let output_files = OutputFiles::new(
243 name.clone(),
244 options.current_dir.clone(),
245 output_dir.unwrap_or_else(|| options.current_dir.clone()),
246 output_file,
247 target_dir,
248 options.output_types.clone(),
249 );
250
251 Self {
252 name,
253 options,
254 source_manager,
255 diagnostics,
256 inputs,
257 output_files,
258 #[cfg(feature = "std")]
259 statistics: Default::default(),
260 }
261 }
262
263 pub fn with_project_type(mut self, ty: ProjectType) -> Self {
264 self.options.project_type = ty;
265 self
266 }
267
268 #[doc(hidden)]
269 pub fn with_output_type(mut self, ty: OutputType, path: Option<OutputFile>) -> Self {
270 self.output_files.outputs.insert(ty, path.clone());
271 self.options.output_types.insert(ty, path.clone());
272 self
273 }
274
275 #[doc(hidden)]
276 pub fn with_extra_flags(mut self, flags: CompileFlags) -> Self {
277 self.options.set_extra_flags(flags);
278 self
279 }
280
281 #[inline]
283 pub fn get_flag(&self, name: &str) -> bool {
284 self.options.flags.get_flag(name)
285 }
286
287 #[inline]
289 pub fn get_flag_count(&self, name: &str) -> usize {
290 self.options.flags.get_flag_count(name)
291 }
292
293 #[inline]
295 pub fn matches(&self) -> &ArgMatches {
296 self.options.flags.matches()
297 }
298
299 pub fn name(&self) -> &str {
301 &self.name
302 }
303
304 pub fn out_file(&self) -> OutputFile {
306 let out_file = self.output_files.output_file(OutputType::Masl, None);
307
308 if let OutputFile::Real(ref path) = out_file {
309 self.check_file_is_writeable(path);
310 }
311
312 out_file
313 }
314
315 #[cfg(not(feature = "std"))]
316 fn check_file_is_writeable(&self, file: &Path) {
317 panic!(
318 "Compiler exited with a fatal error: cannot write '{}' - compiler was built without \
319 standard library",
320 file.display()
321 );
322 }
323
324 #[cfg(feature = "std")]
325 fn check_file_is_writeable(&self, file: &Path) {
326 if let Ok(m) = file.metadata() {
327 if m.permissions().readonly() {
328 panic!(
329 "Compiler exited with a fatal error: file is not writeable: {}",
330 file.display()
331 );
332 }
333 }
334 }
335
336 pub fn parse_only(&self) -> bool {
338 self.options.parse_only
339 }
340
341 pub fn analyze_only(&self) -> bool {
343 self.options.analyze_only
344 }
345
346 pub fn rewrite_only(&self) -> bool {
348 let link_or_masm_requested = self.should_link() || self.should_codegen();
349 !self.options.parse_only && !self.options.analyze_only && !link_or_masm_requested
350 }
351
352 pub fn should_link(&self) -> bool {
354 self.options.output_types.should_link() && !self.options.no_link
355 }
356
357 pub fn should_codegen(&self) -> bool {
359 self.options.output_types.should_codegen() && !self.options.link_only
360 }
361
362 pub fn should_assemble(&self) -> bool {
364 self.options.output_types.should_assemble() && !self.options.link_only
365 }
366
367 pub fn should_emit(&self, ty: OutputType) -> bool {
369 self.options.output_types.contains_key(&ty)
370 }
371
372 pub fn should_print_ir(&self, pass: &str) -> bool {
374 self.options.print_ir_after_all
375 || self.options.print_ir_after_pass.iter().any(|p| p == pass)
376 }
377
378 pub fn should_print_cfg(&self, pass: &str) -> bool {
380 self.options.print_cfg_after_all
381 || self.options.print_cfg_after_pass.iter().any(|p| p == pass)
382 }
383
384 #[cfg(feature = "std")]
386 pub fn print(&self, ir: impl Emit, pass: &str) -> anyhow::Result<()> {
387 if self.should_print_ir(pass) {
388 ir.write_to_stdout(self)?;
389 }
390 Ok(())
391 }
392
393 pub fn emit_to(&self, ty: OutputType, name: Option<Symbol>) -> Option<PathBuf> {
395 if self.should_emit(ty) {
396 match self.output_files.output_file(ty, name.map(|n| n.as_str())) {
397 OutputFile::Real(path) => Some(path),
398 OutputFile::Stdout => None,
399 }
400 } else {
401 None
402 }
403 }
404
405 #[cfg(feature = "std")]
407 pub fn emit<E: Emit>(&self, mode: OutputMode, item: &E) -> anyhow::Result<()> {
408 let output_type = item.output_type(mode);
409 if self.should_emit(output_type) {
410 let name = item.name().map(|n| n.as_str());
411 match self.output_files.output_file(output_type, name) {
412 OutputFile::Real(path) => {
413 item.write_to_file(&path, mode, self)?;
414 }
415 OutputFile::Stdout => {
416 let stdout = std::io::stdout().lock();
417 item.write_to(stdout, mode, self)?;
418 }
419 }
420 }
421
422 Ok(())
423 }
424
425 #[cfg(not(feature = "std"))]
426 pub fn emit<E: Emit>(&self, _mode: OutputMode, _item: &E) -> anyhow::Result<()> {
427 Ok(())
428 }
429}
430
431#[derive(Debug, Copy, Clone, Default)]
433#[cfg_attr(feature = "std", derive(ValueEnum))]
434pub enum TargetEnv {
435 Emu,
437 #[default]
439 Base,
440 Rollup,
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 => f.write_str("rollup"),
449 }
450 }
451}