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