midenc_session/
lib.rs

1#![feature(debug_closure_helpers)]
2#![no_std]
3
4extern crate alloc;
5#[cfg(feature = "std")]
6extern crate std;
7use alloc::{
8    borrow::ToOwned,
9    string::{String, ToString},
10    vec::Vec,
11};
12
13mod color;
14pub mod diagnostics;
15#[cfg(feature = "std")]
16mod duration;
17mod emit;
18mod emitter;
19pub mod flags;
20mod inputs;
21mod libs;
22mod options;
23mod outputs;
24#[cfg(feature = "std")]
25mod statistics;
26
27use alloc::{fmt, sync::Arc};
28use std::path::{Path, PathBuf};
29
30/// The version associated with the current compiler toolchain
31pub const MIDENC_BUILD_VERSION: &str = env!("MIDENC_BUILD_VERSION");
32
33/// The git revision associated with the current compiler toolchain
34pub const MIDENC_BUILD_REV: &str = env!("MIDENC_BUILD_REV");
35
36use clap::ValueEnum;
37use midenc_hir_symbol::Symbol;
38
39pub use self::{
40    color::ColorChoice,
41    diagnostics::{DiagnosticsHandler, Emitter, SourceManager},
42    duration::HumanDuration,
43    emit::Emit,
44    flags::{CompileFlag, CompileFlags, FlagAction},
45    inputs::{FileType, InputFile, InputType, InvalidInputError},
46    libs::{LibraryKind, LinkLibrary},
47    options::*,
48    outputs::{OutputFile, OutputFiles, OutputMode, OutputType, OutputTypeSpec, OutputTypes},
49    statistics::Statistics,
50};
51
52/// The type of project being compiled
53#[derive(Debug, Copy, Clone, Default)]
54pub enum ProjectType {
55    /// Compile a Miden program that can be run on the Miden VM
56    #[default]
57    Program,
58    /// Compile a Miden library which can be linked into a program
59    Library,
60}
61impl ProjectType {
62    pub fn default_for_target(target: TargetEnv) -> Self {
63        match target {
64            // We default to compiling a program unless we find later
65            // that we do not have an entrypoint.
66            TargetEnv::Base | TargetEnv::Rollup => Self::Program,
67            // The emulator can run either programs or individual library functions,
68            // so we compile as a library and delegate the choice of how to run it
69            // to the emulator
70            TargetEnv::Emu => Self::Library,
71        }
72    }
73}
74
75/// This struct provides access to all of the metadata and configuration
76/// needed during a single compilation session.
77pub struct Session {
78    /// The name of this session
79    pub name: String,
80    /// Configuration for the current compiler session
81    pub options: Options,
82    /// The current source manager
83    pub source_manager: Arc<dyn SourceManager>,
84    /// The current diagnostics handler
85    pub diagnostics: Arc<DiagnosticsHandler>,
86    /// The inputs being compiled
87    pub inputs: Vec<InputFile>,
88    /// The outputs to be produced by the compiler during compilation
89    pub output_files: OutputFiles,
90    /// Statistics gathered from the current compiler session
91    #[cfg(feature = "std")]
92    pub statistics: Statistics,
93}
94
95impl fmt::Debug for Session {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        let inputs = self.inputs.iter().map(|input| input.file_name()).collect::<Vec<_>>();
98        f.debug_struct("Session")
99            .field("name", &self.name)
100            .field("options", &self.options)
101            .field("inputs", &inputs)
102            .field("output_files", &self.output_files)
103            .finish_non_exhaustive()
104    }
105}
106
107impl Session {
108    pub fn new<I>(
109        inputs: I,
110        output_dir: Option<PathBuf>,
111        output_file: Option<OutputFile>,
112        target_dir: PathBuf,
113        options: Options,
114        emitter: Option<Arc<dyn Emitter>>,
115        source_manager: Arc<dyn SourceManager>,
116    ) -> Self
117    where
118        I: IntoIterator<Item = InputFile>,
119    {
120        let inputs = inputs.into_iter().collect::<Vec<_>>();
121
122        Self::make(inputs, output_dir, output_file, target_dir, options, emitter, source_manager)
123    }
124
125    fn make(
126        inputs: Vec<InputFile>,
127        output_dir: Option<PathBuf>,
128        output_file: Option<OutputFile>,
129        target_dir: PathBuf,
130        options: Options,
131        emitter: Option<Arc<dyn Emitter>>,
132        source_manager: Arc<dyn SourceManager>,
133    ) -> Self {
134        log::debug!("creating session for {} inputs:", inputs.len());
135        if log::log_enabled!(log::Level::Debug) {
136            for input in inputs.iter() {
137                log::debug!(" - {} ({})", input.file_name(), input.file_type());
138            }
139            log::debug!(
140                " | outputs_dir = {}",
141                output_dir
142                    .as_ref()
143                    .map(|p| p.display().to_string())
144                    .unwrap_or("<unset>".to_string())
145            );
146            log::debug!(
147                " | output_file = {}",
148                output_file.as_ref().map(|of| of.to_string()).unwrap_or("<unset>".to_string())
149            );
150            log::debug!(" | target_dir = {}", target_dir.display());
151        }
152        let diagnostics = Arc::new(DiagnosticsHandler::new(
153            options.diagnostics,
154            source_manager.clone(),
155            emitter.unwrap_or_else(|| options.default_emitter()),
156        ));
157
158        let output_dir = output_dir
159            .as_deref()
160            .or_else(|| output_file.as_ref().and_then(|of| of.parent()))
161            .map(|path| path.to_path_buf());
162
163        let name = options
164            .name
165            .clone()
166            .or_else(|| {
167                output_file
168                    .as_ref()
169                    .and_then(|of| of.filestem().map(|stem| stem.to_string_lossy().into_owned()))
170            })
171            .unwrap_or_else(|| match inputs.first() {
172                Some(InputFile {
173                    file: InputType::Real(ref path),
174                    ..
175                }) => path
176                    .file_stem()
177                    .and_then(|stem| stem.to_str())
178                    .or_else(|| path.extension().and_then(|stem| stem.to_str()))
179                    .unwrap_or_else(|| {
180                        panic!(
181                            "invalid input path: '{}' has no file stem or extension",
182                            path.display()
183                        )
184                    })
185                    .to_string(),
186                Some(
187                    input @ InputFile {
188                        file: InputType::Stdin { ref name, .. },
189                        ..
190                    },
191                ) => {
192                    let name = name.as_str();
193                    if matches!(name, "empty" | "stdin") {
194                        options
195                            .current_dir
196                            .file_stem()
197                            .and_then(|stem| stem.to_str())
198                            .unwrap_or(name)
199                            .to_string()
200                    } else {
201                        input.filestem().to_owned()
202                    }
203                }
204                None => "out".to_owned(),
205            });
206
207        let output_files = OutputFiles::new(
208            name.clone(),
209            options.current_dir.clone(),
210            output_dir.unwrap_or_else(|| options.current_dir.clone()),
211            output_file,
212            target_dir,
213            options.output_types.clone(),
214        );
215
216        Self {
217            name,
218            options,
219            source_manager,
220            diagnostics,
221            inputs,
222            output_files,
223            statistics: Default::default(),
224        }
225    }
226
227    pub fn with_project_type(mut self, ty: ProjectType) -> Self {
228        self.options.project_type = ty;
229        self
230    }
231
232    #[doc(hidden)]
233    pub fn with_output_type(mut self, ty: OutputType, path: Option<OutputFile>) -> Self {
234        self.output_files.outputs.insert(ty, path.clone());
235        self.options.output_types.insert(ty, path.clone());
236        self
237    }
238
239    #[doc(hidden)]
240    pub fn with_extra_flags(mut self, flags: CompileFlags) -> Self {
241        self.options.set_extra_flags(flags);
242        self
243    }
244
245    /// Get the value of a custom flag with action `FlagAction::SetTrue` or `FlagAction::SetFalse`
246    #[inline]
247    pub fn get_flag(&self, name: &str) -> bool {
248        self.options.flags.get_flag(name)
249    }
250
251    /// Get the count of a specific custom flag with action `FlagAction::Count`
252    #[inline]
253    pub fn get_flag_count(&self, name: &str) -> usize {
254        self.options.flags.get_flag_count(name)
255    }
256
257    /// Get the value of a specific custom flag
258    #[inline]
259    pub fn get_flag_value<T>(&self, name: &str) -> Option<&T>
260    where
261        T: core::any::Any + Clone + Send + Sync + 'static,
262    {
263        self.options.flags.get_flag_value(name)
264    }
265
266    /// Iterate over values of a specific custom flag
267    #[inline]
268    #[cfg(feature = "std")]
269    pub fn get_flag_values<T>(&self, name: &str) -> Option<clap::parser::ValuesRef<'_, T>>
270    where
271        T: core::any::Any + Clone + Send + Sync + 'static,
272    {
273        self.options.flags.get_flag_values(name)
274    }
275
276    /// Get the remaining [clap::ArgMatches] left after parsing the base session configuration
277    #[inline]
278    #[cfg(feature = "std")]
279    pub fn matches(&self) -> &clap::ArgMatches {
280        self.options.flags.matches()
281    }
282
283    /// The name of this session (used as the name of the project, output file, etc.)
284    pub fn name(&self) -> &str {
285        &self.name
286    }
287
288    /// Get the [OutputFile] to write the assembled MAST output to
289    pub fn out_file(&self) -> OutputFile {
290        let out_file = self.output_files.output_file(OutputType::Masl, None);
291
292        if let OutputFile::Real(ref path) = out_file {
293            self.check_file_is_writeable(path);
294        }
295
296        out_file
297    }
298
299    fn check_file_is_writeable(&self, file: &Path) {
300        if let Ok(m) = file.metadata() {
301            if m.permissions().readonly() {
302                panic!(
303                    "Compiler exited with a fatal error: file is not writeable: {}",
304                    file.display()
305                );
306            }
307        }
308    }
309
310    /// Returns true if the compiler should exit after parsing the input
311    pub fn parse_only(&self) -> bool {
312        self.options.parse_only
313    }
314
315    /// Returns true if the compiler should exit after performing semantic analysis
316    pub fn analyze_only(&self) -> bool {
317        self.options.analyze_only
318    }
319
320    /// Returns true if the compiler should exit after applying rewrites to the IR
321    pub fn rewrite_only(&self) -> bool {
322        let link_or_masm_requested = self.should_link() || self.should_codegen();
323        !self.options.parse_only && !self.options.analyze_only && !link_or_masm_requested
324    }
325
326    /// Returns true if an [OutputType] that requires linking + assembly was requested
327    pub fn should_link(&self) -> bool {
328        self.options.output_types.should_link() && !self.options.no_link
329    }
330
331    /// Returns true if an [OutputType] that requires generating Miden Assembly was requested
332    pub fn should_codegen(&self) -> bool {
333        self.options.output_types.should_codegen() && !self.options.link_only
334    }
335
336    /// Returns true if an [OutputType] that requires assembling MAST was requested
337    pub fn should_assemble(&self) -> bool {
338        self.options.output_types.should_assemble() && !self.options.link_only
339    }
340
341    /// Returns true if the given [OutputType] should be emitted as an output
342    pub fn should_emit(&self, ty: OutputType) -> bool {
343        self.options.output_types.contains_key(&ty)
344    }
345
346    /// Returns true if IR should be printed to stdout, after executing a pass named `pass`
347    pub fn should_print_ir(&self, pass: &str) -> bool {
348        self.options.print_ir_after_all
349            || self.options.print_ir_after_pass.iter().any(|p| p == pass)
350    }
351
352    /// Returns true if CFG should be printed to stdout, after executing a pass named `pass`
353    pub fn should_print_cfg(&self, pass: &str) -> bool {
354        self.options.print_cfg_after_all
355            || self.options.print_cfg_after_pass.iter().any(|p| p == pass)
356    }
357
358    /// Print the given emittable IR to stdout, as produced by a pass with name `pass`
359    pub fn print(&self, ir: impl Emit, pass: &str) -> std::io::Result<()> {
360        if self.should_print_ir(pass) {
361            ir.write_to_stdout(self)?;
362        }
363        Ok(())
364    }
365
366    /// Get the path to emit the given [OutputType] to
367    pub fn emit_to(&self, ty: OutputType, name: Option<Symbol>) -> Option<PathBuf> {
368        if self.should_emit(ty) {
369            match self.output_files.output_file(ty, name.map(|n| n.as_str())) {
370                OutputFile::Real(path) => Some(path),
371                OutputFile::Stdout => None,
372            }
373        } else {
374            None
375        }
376    }
377
378    /// Emit an item to stdout/file system depending on the current configuration
379    pub fn emit<E: Emit>(&self, mode: OutputMode, item: &E) -> std::io::Result<()> {
380        let output_type = item.output_type(mode);
381        if self.should_emit(output_type) {
382            let name = item.name().map(|n| n.as_str());
383            match self.output_files.output_file(output_type, name) {
384                OutputFile::Real(path) => {
385                    item.write_to_file(&path, mode, self)?;
386                }
387                OutputFile::Stdout => {
388                    let stdout = std::io::stdout().lock();
389                    item.write_to(stdout, mode, self)?;
390                }
391            }
392        }
393
394        Ok(())
395    }
396}
397
398/// This enum describes the different target environments targetable by the compiler
399#[derive(Debug, Copy, Clone, Default)]
400#[cfg_attr(feature = "std", derive(ValueEnum))]
401pub enum TargetEnv {
402    /// The emulator environment, which has a more restrictive instruction set
403    Emu,
404    /// The default Miden VM environment
405    #[default]
406    Base,
407    /// The Miden Rollup environment, using the Rollup kernel
408    Rollup,
409}
410impl fmt::Display for TargetEnv {
411    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
412        match self {
413            Self::Emu => f.write_str("emu"),
414            Self::Base => f.write_str("base"),
415            Self::Rollup => f.write_str("rollup"),
416        }
417    }
418}