Skip to main content

midenc_session/options/
mod.rs

1mod printing;
2
3use alloc::{
4    boxed::Box,
5    fmt,
6    str::FromStr,
7    string::{String, ToString},
8    sync::Arc,
9    vec,
10    vec::Vec,
11};
12
13use miden_debug_types::SourceManager;
14use miden_project::TargetType;
15
16pub use self::printing::IrFilter;
17use crate::{
18    ColorChoice, CompileFlags, InputFile, LinkLibrary, OutputFile, OutputTypes, PathBuf,
19    diagnostics::{DiagnosticsConfig, Emitter, Report},
20};
21
22/// This struct contains all of the configuration options for the compiler
23#[derive(Debug, Clone)]
24pub struct Options {
25    /// The path to the current project manifest, if present
26    pub manifest_path: Option<PathBuf>,
27    /// The name of the program being compiled
28    pub name: Option<String>,
29    /// The name of the function to call as the entrypoint
30    pub entrypoint: Option<String>,
31    /// The name of the build profile to use
32    pub profile: String,
33    /// Build all packages in the current workspace (used by `cargo miden`)
34    pub workspace: bool,
35    /// Build the specified packages in the current workspace (used by `cargo miden`)
36    pub packages: Vec<String>,
37    /// The name of the current project target being compiled
38    pub target: Option<String>,
39    /// The type of target that was requested
40    pub target_type: Option<TargetType>,
41    /// The optimization level for the current program
42    pub optimize: OptLevel,
43    /// The level of debugging info for the current program
44    pub debug: DebugInfo,
45    /// The type of outputs to emit
46    pub output_types: OutputTypes,
47    /// The paths in which to search for Miden Assembly libraries to link against
48    pub search_paths: Vec<PathBuf>,
49    /// The set of Miden libraries to link against
50    pub link_libraries: Vec<LinkLibrary>,
51    /// A set of Miden Assembly modules to link against
52    pub link_modules: Vec<(miden_assembly_syntax::PathBuf, String)>,
53    /// The path to the current toolchain directory, which contains libraries and other tools that
54    /// the compiler may use.
55    ///
56    /// This is expected to be set by `midenup` when the compiler is invoked via `miden` CLI
57    pub sysroot: Option<PathBuf>,
58    /// The path to `midenup`'s home directory
59    ///
60    /// This is expected to be set by `midenup` when the compiler is invoked via `miden` CLI
61    pub midenup_home: Option<PathBuf>,
62    /// The name of the current `midenup` toolchain
63    ///
64    /// This is expected to be set by `midenup` when the compiler is invoked via `miden` CLI
65    pub toolchain: Option<String>,
66    /// Whether, and how, to color terminal output
67    pub color: ColorChoice,
68    /// The current diagnostics configuration
69    pub diagnostics: DiagnosticsConfig,
70    /// The current working directory of the compiler
71    pub current_dir: PathBuf,
72    /// The target directory of the compiler
73    pub target_dir: PathBuf,
74    /// The artifact output directory of the compiler
75    pub output_dir: Option<PathBuf>,
76    /// The output file requested by the user, if requested
77    pub output_file: Option<OutputFile>,
78    /// Path prefixes to remap for any file paths encoded in debug info
79    pub remap_path_prefixes: Vec<RemapPathPrefix>,
80    /// Print source location information in HIR output
81    pub print_hir_source_locations: bool,
82    /// Only parse inputs
83    pub parse_only: bool,
84    /// Only perform semantic analysis on the input
85    pub analyze_only: bool,
86    /// Run the linker on the inputs, but do not generate Miden Assembly
87    pub link_only: bool,
88    /// Generate Miden Assembly from the inputs without the linker
89    pub no_link: bool,
90    /// Run the experimental Miden Assembly linter prior to codegen
91    ///
92    /// This linter uses the HIR dataflow analysis framework to check for issues such as
93    /// unconstrained advice usage.
94    pub lint: bool,
95    /// Print CFG to stdout after each pass
96    pub print_cfg_after_all: bool,
97    /// Print CFG to stdout each time the named passes are applied
98    pub print_cfg_after_pass: Vec<String>,
99    /// Print IR to stdout at the start of each stage
100    pub print_ir_before_stage: Vec<String>,
101    /// Print IR to stdout after each pass
102    pub print_ir_after_all: bool,
103    /// Print IR to stdout each time the named passes are applied
104    pub print_ir_after_pass: Vec<String>,
105    /// Only print the IR if the pass modified the IR structure.
106    pub print_ir_after_modified: bool,
107    /// Apply filters to what IR is printed, when printing is enabled
108    pub print_ir_filters: Vec<IrFilter>,
109    /// Save intermediate artifacts in memory during compilation
110    pub save_temps: bool,
111    /// Custom RUSTFLAGS to set when building Rust
112    pub rustflags: Option<String>,
113    /// Look for `cargo -Zscript`-style frontmatter when compiling standalone Rust sources
114    pub cargo_frontmatter: bool,
115    /// We store any leftover argument matches in the session options for use
116    /// by any downstream crates that register custom flags
117    pub flags: CompileFlags,
118}
119
120impl Default for Options {
121    fn default() -> Self {
122        let current_dir = current_dir();
123        let target_dir = current_dir.join("target");
124        Self::new(None, None, current_dir, target_dir, None, None)
125    }
126}
127
128impl Options {
129    pub fn new(
130        name: Option<String>,
131        target: Option<TargetType>,
132        current_dir: PathBuf,
133        target_dir: PathBuf,
134        output_dir: Option<PathBuf>,
135        sysroot: Option<PathBuf>,
136    ) -> Self {
137        let search_paths = if let Some(sysroot) = sysroot.as_deref() {
138            let lib_dir = sysroot.join("lib");
139            if lib_dir.try_exists().is_ok_and(|exists| exists) {
140                vec![lib_dir]
141            } else {
142                vec![]
143            }
144        } else {
145            vec![]
146        };
147
148        Self {
149            manifest_path: None,
150            name,
151            profile: "dev".to_string(),
152            workspace: false,
153            packages: vec![],
154            target: None,
155            target_type: target,
156            entrypoint: None,
157            optimize: OptLevel::None,
158            debug: DebugInfo::None,
159            output_types: Default::default(),
160            search_paths,
161            link_libraries: vec![],
162            link_modules: vec![],
163            sysroot,
164            midenup_home: None,
165            toolchain: None,
166            color: Default::default(),
167            diagnostics: Default::default(),
168            current_dir,
169            target_dir,
170            output_dir,
171            output_file: None,
172            print_hir_source_locations: false,
173            parse_only: false,
174            analyze_only: false,
175            link_only: false,
176            no_link: false,
177            save_temps: false,
178            lint: false,
179            cargo_frontmatter: false,
180            print_cfg_after_all: false,
181            print_cfg_after_pass: vec![],
182            print_ir_before_stage: vec![],
183            print_ir_after_all: false,
184            print_ir_after_pass: vec![],
185            print_ir_after_modified: false,
186            print_ir_filters: vec![],
187            rustflags: None,
188            remap_path_prefixes: vec![],
189            flags: CompileFlags::default(),
190        }
191    }
192
193    #[inline(always)]
194    pub fn with_color(mut self: Box<Self>, color: ColorChoice) -> Box<Self> {
195        self.color = color;
196        self
197    }
198
199    #[inline(always)]
200    pub fn with_verbosity(mut self: Box<Self>, verbosity: Verbosity) -> Box<Self> {
201        self.diagnostics.verbosity = verbosity;
202        self
203    }
204
205    #[inline(always)]
206    pub fn with_debug_info(mut self: Box<Self>, debug: DebugInfo) -> Box<Self> {
207        self.debug = debug;
208        self
209    }
210
211    #[inline(always)]
212    pub fn with_optimization(mut self: Box<Self>, level: OptLevel) -> Box<Self> {
213        self.optimize = level;
214        self
215    }
216
217    pub fn with_warnings(mut self: Box<Self>, warnings: Warnings) -> Box<Self> {
218        self.diagnostics.warnings = warnings;
219        self
220    }
221
222    pub fn with_output_types(
223        mut self: Box<Self>,
224        mut output_types: OutputTypes,
225        output_file: Option<OutputFile>,
226    ) -> Box<Self> {
227        use crate::OutputType;
228        let has_final_output = output_types.keys().any(|ty| matches!(ty, OutputType::Masp));
229        if !has_final_output {
230            // By default, we always produce a final artifact; `--emit` selects additional outputs.
231            output_types.insert(OutputType::Masp, output_file);
232        } else if output_file.is_some() && output_types.get(&OutputType::Masp).is_some() {
233            // The -o flag overrides --emit
234            output_types.insert(OutputType::Masp, output_file);
235        }
236        self.output_types = output_types;
237        self
238    }
239
240    #[doc(hidden)]
241    pub fn with_extra_flags(mut self: Box<Self>, flags: CompileFlags) -> Box<Self> {
242        self.flags = flags;
243        self
244    }
245
246    #[doc(hidden)]
247    pub fn set_extra_flags(&mut self, flags: CompileFlags) {
248        self.flags = flags;
249    }
250
251    /// Use this configuration to obtain a [Session] used for compilation
252    pub fn into_session(
253        self: Box<Self>,
254        input: InputFile,
255        emitter: Option<Arc<dyn Emitter>>,
256        source_manager: Option<Arc<dyn SourceManager + Send + Sync>>,
257    ) -> Result<crate::Session, Report> {
258        use crate::diagnostics::DefaultSourceManager;
259
260        let source_manager =
261            source_manager.unwrap_or_else(|| Arc::new(DefaultSourceManager::default()));
262        crate::Session::new(input, self, emitter, source_manager)
263    }
264
265    /// Get a new [Emitter] based on the current options.
266    pub fn default_emitter(&self) -> Arc<dyn Emitter> {
267        use crate::diagnostics::{DefaultEmitter, NullEmitter};
268
269        match self.diagnostics.verbosity {
270            Verbosity::Silent => Arc::new(NullEmitter::new(self.color)),
271            _ => Arc::new(DefaultEmitter::new(self.color)),
272        }
273    }
274
275    /// Returns true if source location information should be emitted by the compiler
276    #[inline(always)]
277    pub fn emit_source_locations(&self) -> bool {
278        matches!(self.debug, DebugInfo::Line | DebugInfo::Full)
279    }
280
281    /// Returns true if rich debugging information should be emitted by the compiler.
282    /// This enables AssemblyOp decorators which carry source location info for runtime errors.
283    #[inline(always)]
284    pub fn emit_debug_decorators(&self) -> bool {
285        matches!(self.debug, DebugInfo::Line | DebugInfo::Full)
286    }
287
288    /// Returns true if debug assertions are enabled
289    #[inline(always)]
290    pub fn emit_debug_assertions(&self) -> bool {
291        self.debug != DebugInfo::None && matches!(self.optimize, OptLevel::None | OptLevel::Basic)
292    }
293
294    /// Returns true if the requested target type is a protocol target
295    pub fn target_requires_protocol(&self) -> bool {
296        use miden_project::TargetType;
297        !matches!(
298            self.target_type,
299            Some(TargetType::Kernel | TargetType::Executable | TargetType::Library) | None
300        )
301    }
302}
303
304/// This enum describes the degree to which compiled programs will be optimized
305#[derive(Debug, Copy, Clone, Default)]
306#[cfg_attr(feature = "std", derive(clap::ValueEnum))]
307pub enum OptLevel {
308    /// No optimizations at all
309    None,
310    /// Only basic optimizations are applied, e.g. constant propagation
311    Basic,
312    /// Most optimizations are applied, except when the cost is particularly high.
313    #[default]
314    Balanced,
315    /// All optimizations are applied, with all tradeoffs in favor of runtime performance
316    Max,
317    /// Most optimizations are applied, but tuned to trade runtime performance for code size
318    Size,
319    /// Only optimizations which reduce code size are applied
320    SizeMin,
321}
322
323/// This enum describes what type of debugging information to emit in compiled programs
324#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
325#[cfg_attr(feature = "std", derive(clap::ValueEnum))]
326pub enum DebugInfo {
327    /// Do not emit debug info in the final output
328    None,
329    /// Emit source location information in the final output
330    #[default]
331    Line,
332    /// Emit all available debug information in the final output
333    Full,
334}
335
336/// This enum represents the behavior of the compiler with regard to warnings
337#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
338#[cfg_attr(feature = "std", derive(clap::ValueEnum))]
339pub enum Warnings {
340    /// Disable all warnings
341    None,
342    /// Enable all warnings
343    #[default]
344    All,
345    /// Promotes warnings to errors
346    Error,
347}
348impl Warnings {
349    #[inline]
350    pub fn should_be_pedantic(&self) -> bool {
351        matches!(self, Self::All)
352    }
353
354    #[inline]
355    pub fn warnings_as_errors(&self) -> bool {
356        matches!(self, Self::Error)
357    }
358}
359impl fmt::Display for Warnings {
360    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
361        match self {
362            Self::None => f.write_str("none"),
363            Self::All => f.write_str("auto"),
364            Self::Error => f.write_str("error"),
365        }
366    }
367}
368impl FromStr for Warnings {
369    type Err = ();
370
371    fn from_str(s: &str) -> Result<Self, Self::Err> {
372        match s {
373            "none" => Ok(Self::None),
374            "all" => Ok(Self::All),
375            "error" => Ok(Self::Error),
376            _ => Err(()),
377        }
378    }
379}
380
381/// This enum represents the type of messages produced by the compiler during execution
382#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord)]
383#[cfg_attr(feature = "std", derive(clap::ValueEnum))]
384pub enum Verbosity {
385    /// Emit additional debug/trace information during compilation
386    Debug,
387    /// Emit the standard informational, warning, and error messages
388    #[default]
389    Info,
390    /// Only emit warnings and errors
391    Warning,
392    /// Only emit errors
393    Error,
394    /// Do not emit anything to stdout/stderr
395    Silent,
396}
397
398/// Represents the `--remap-path-prefix` flag, which rewrites source paths encoded in debug info
399/// with a different path, typically to avoid encoding machine-specific details in artifacts.
400#[derive(Debug, Clone)]
401pub struct RemapPathPrefix {
402    /// The path prefix to remap
403    pub from: Box<crate::Path>,
404    /// The remapped path prefix
405    ///
406    /// If `None`, the value `.` is used, representing the current working directory
407    pub to: Option<Box<crate::Path>>,
408}
409
410impl RemapPathPrefix {
411    pub fn source_prefix(&self) -> &crate::Path {
412        &self.from
413    }
414
415    pub fn target_prefix(&self) -> &crate::Path {
416        self.to.as_deref().unwrap_or(crate::Path::new(""))
417    }
418}
419
420/// Parses `--remap-path-prefix=<from>`, `--remap-path-prefix=<from>=<to>`
421#[doc(hidden)]
422#[derive(Clone)]
423#[cfg(feature = "std")]
424pub struct RemapPathPrefixParser;
425
426#[cfg(feature = "std")]
427impl clap::builder::TypedValueParser for RemapPathPrefixParser {
428    type Value = RemapPathPrefix;
429
430    fn parse_ref(
431        &self,
432        _cmd: &clap::Command,
433        _arg: Option<&clap::Arg>,
434        value: &std::ffi::OsStr,
435    ) -> Result<Self::Value, clap::error::Error> {
436        use clap::error::{Error, ErrorKind};
437
438        let input = value.to_str().ok_or_else(|| Error::new(ErrorKind::InvalidUtf8))?;
439
440        Ok(match input.split_once('=') {
441            Some((from, to)) => RemapPathPrefix {
442                from: PathBuf::from(from.trim()).into_boxed_path(),
443                to: Some(PathBuf::from(to.trim()).into_boxed_path()),
444            },
445            None => RemapPathPrefix {
446                from: PathBuf::from(input.trim()).into_boxed_path(),
447                to: None,
448            },
449        })
450    }
451}
452
453#[cfg(feature = "std")]
454fn current_dir() -> PathBuf {
455    std::env::current_dir().expect("could not get working directory")
456}
457
458#[cfg(not(feature = "std"))]
459fn current_dir() -> PathBuf {
460    PathBuf::from(".")
461}