From 698f8c2f01ea549d77d7dc3338a12e04c11057b9 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 17 Apr 2024 14:02:58 +0200 Subject: Adding upstream version 1.64.0+dfsg1. Signed-off-by: Daniel Baumann --- compiler/rustc_session/src/cgu_reuse_tracker.rs | 118 + compiler/rustc_session/src/code_stats.rs | 182 ++ compiler/rustc_session/src/config.rs | 2970 +++++++++++++++++++++++ compiler/rustc_session/src/cstore.rs | 218 ++ compiler/rustc_session/src/filesearch.rs | 125 + compiler/rustc_session/src/lib.rs | 40 + compiler/rustc_session/src/options.rs | 1673 +++++++++++++ compiler/rustc_session/src/output.rs | 202 ++ compiler/rustc_session/src/parse.rs | 326 +++ compiler/rustc_session/src/search_paths.rs | 93 + compiler/rustc_session/src/session.rs | 1599 ++++++++++++ compiler/rustc_session/src/utils.rs | 93 + 12 files changed, 7639 insertions(+) create mode 100644 compiler/rustc_session/src/cgu_reuse_tracker.rs create mode 100644 compiler/rustc_session/src/code_stats.rs create mode 100644 compiler/rustc_session/src/config.rs create mode 100644 compiler/rustc_session/src/cstore.rs create mode 100644 compiler/rustc_session/src/filesearch.rs create mode 100644 compiler/rustc_session/src/lib.rs create mode 100644 compiler/rustc_session/src/options.rs create mode 100644 compiler/rustc_session/src/output.rs create mode 100644 compiler/rustc_session/src/parse.rs create mode 100644 compiler/rustc_session/src/search_paths.rs create mode 100644 compiler/rustc_session/src/session.rs create mode 100644 compiler/rustc_session/src/utils.rs (limited to 'compiler/rustc_session/src') diff --git a/compiler/rustc_session/src/cgu_reuse_tracker.rs b/compiler/rustc_session/src/cgu_reuse_tracker.rs new file mode 100644 index 000000000..dd64e8ab7 --- /dev/null +++ b/compiler/rustc_session/src/cgu_reuse_tracker.rs @@ -0,0 +1,118 @@ +//! Some facilities for tracking how codegen-units are reused during incremental +//! compilation. This is used for incremental compilation tests and debug +//! output. + +use rustc_data_structures::fx::FxHashMap; +use rustc_span::{Span, Symbol}; +use std::sync::{Arc, Mutex}; +use tracing::debug; + +#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)] +pub enum CguReuse { + No, + PreLto, + PostLto, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum ComparisonKind { + Exact, + AtLeast, +} + +struct TrackerData { + actual_reuse: FxHashMap, + expected_reuse: FxHashMap, +} + +// Span does not implement `Send`, so we can't just store it in the shared +// `TrackerData` object. Instead of splitting up `TrackerData` into shared and +// non-shared parts (which would be complicated), we just mark the `Span` here +// explicitly as `Send`. That's safe because the span data here is only ever +// accessed from the main thread. +struct SendSpan(Span); +unsafe impl Send for SendSpan {} + +#[derive(Clone)] +pub struct CguReuseTracker { + data: Option>>, +} + +impl CguReuseTracker { + pub fn new() -> CguReuseTracker { + let data = + TrackerData { actual_reuse: Default::default(), expected_reuse: Default::default() }; + + CguReuseTracker { data: Some(Arc::new(Mutex::new(data))) } + } + + pub fn new_disabled() -> CguReuseTracker { + CguReuseTracker { data: None } + } + + pub fn set_actual_reuse(&self, cgu_name: &str, kind: CguReuse) { + if let Some(ref data) = self.data { + debug!("set_actual_reuse({cgu_name:?}, {kind:?})"); + + let prev_reuse = data.lock().unwrap().actual_reuse.insert(cgu_name.to_string(), kind); + + if let Some(prev_reuse) = prev_reuse { + // The only time it is legal to overwrite reuse state is when + // we discover during ThinLTO that we can actually reuse the + // post-LTO version of a CGU. + assert_eq!(prev_reuse, CguReuse::PreLto); + } + } + } + + pub fn set_expectation( + &self, + cgu_name: Symbol, + cgu_user_name: &str, + error_span: Span, + expected_reuse: CguReuse, + comparison_kind: ComparisonKind, + ) { + if let Some(ref data) = self.data { + debug!("set_expectation({cgu_name:?}, {expected_reuse:?}, {comparison_kind:?})"); + let mut data = data.lock().unwrap(); + + data.expected_reuse.insert( + cgu_name.to_string(), + (cgu_user_name.to_string(), SendSpan(error_span), expected_reuse, comparison_kind), + ); + } + } + + pub fn check_expected_reuse(&self, diag: &rustc_errors::Handler) { + if let Some(ref data) = self.data { + let data = data.lock().unwrap(); + + for (cgu_name, &(ref cgu_user_name, ref error_span, expected_reuse, comparison_kind)) in + &data.expected_reuse + { + if let Some(&actual_reuse) = data.actual_reuse.get(cgu_name) { + let (error, at_least) = match comparison_kind { + ComparisonKind::Exact => (expected_reuse != actual_reuse, false), + ComparisonKind::AtLeast => (actual_reuse < expected_reuse, true), + }; + + if error { + let at_least = if at_least { "at least " } else { "" }; + let msg = format!( + "CGU-reuse for `{cgu_user_name}` is `{actual_reuse:?}` but \ + should be {at_least}`{expected_reuse:?}`" + ); + diag.span_err(error_span.0, &msg); + } + } else { + let msg = format!( + "CGU-reuse for `{cgu_user_name}` (mangled: `{cgu_name}`) was \ + not recorded" + ); + diag.span_fatal(error_span.0, &msg) + } + } + } + } +} diff --git a/compiler/rustc_session/src/code_stats.rs b/compiler/rustc_session/src/code_stats.rs new file mode 100644 index 000000000..eede4d16e --- /dev/null +++ b/compiler/rustc_session/src/code_stats.rs @@ -0,0 +1,182 @@ +use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::sync::Lock; +use rustc_span::Symbol; +use rustc_target::abi::{Align, Size}; +use std::cmp::{self, Ordering}; + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub struct VariantInfo { + pub name: Option, + pub kind: SizeKind, + pub size: u64, + pub align: u64, + pub fields: Vec, +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum SizeKind { + Exact, + Min, +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug)] +pub struct FieldInfo { + pub name: Symbol, + pub offset: u64, + pub size: u64, + pub align: u64, +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub enum DataTypeKind { + Struct, + Union, + Enum, + Closure, +} + +#[derive(PartialEq, Eq, Hash, Debug)] +pub struct TypeSizeInfo { + pub kind: DataTypeKind, + pub type_description: String, + pub align: u64, + pub overall_size: u64, + pub packed: bool, + pub opt_discr_size: Option, + pub variants: Vec, +} + +#[derive(Default)] +pub struct CodeStats { + type_sizes: Lock>, +} + +impl CodeStats { + pub fn record_type_size( + &self, + kind: DataTypeKind, + type_desc: S, + align: Align, + overall_size: Size, + packed: bool, + opt_discr_size: Option, + mut variants: Vec, + ) { + // Sort variants so the largest ones are shown first. A stable sort is + // used here so that source code order is preserved for all variants + // that have the same size. + variants.sort_by(|info1, info2| info2.size.cmp(&info1.size)); + let info = TypeSizeInfo { + kind, + type_description: type_desc.to_string(), + align: align.bytes(), + overall_size: overall_size.bytes(), + packed, + opt_discr_size: opt_discr_size.map(|s| s.bytes()), + variants, + }; + self.type_sizes.borrow_mut().insert(info); + } + + pub fn print_type_sizes(&self) { + let type_sizes = self.type_sizes.borrow(); + let mut sorted: Vec<_> = type_sizes.iter().collect(); + + // Primary sort: large-to-small. + // Secondary sort: description (dictionary order) + sorted.sort_by(|info1, info2| { + // (reversing cmp order to get large-to-small ordering) + match info2.overall_size.cmp(&info1.overall_size) { + Ordering::Equal => info1.type_description.cmp(&info2.type_description), + other => other, + } + }); + + for info in sorted { + let TypeSizeInfo { type_description, overall_size, align, kind, variants, .. } = info; + println!( + "print-type-size type: `{type_description}`: {overall_size} bytes, alignment: {align} bytes" + ); + let indent = " "; + + let discr_size = if let Some(discr_size) = info.opt_discr_size { + println!("print-type-size {indent}discriminant: {discr_size} bytes"); + discr_size + } else { + 0 + }; + + // We start this at discr_size (rather than 0) because + // things like C-enums do not have variants but we still + // want the max_variant_size at the end of the loop below + // to reflect the presence of the discriminant. + let mut max_variant_size = discr_size; + + let struct_like = match kind { + DataTypeKind::Struct | DataTypeKind::Closure => true, + DataTypeKind::Enum | DataTypeKind::Union => false, + }; + for (i, variant_info) in variants.into_iter().enumerate() { + let VariantInfo { ref name, kind: _, align: _, size, ref fields } = *variant_info; + let indent = if !struct_like { + let name = match name.as_ref() { + Some(name) => name.to_string(), + None => i.to_string(), + }; + println!( + "print-type-size {indent}variant `{name}`: {diff} bytes", + diff = size - discr_size + ); + " " + } else { + assert!(i < 1); + " " + }; + max_variant_size = cmp::max(max_variant_size, size); + + let mut min_offset = discr_size; + + // We want to print fields by increasing offset. We also want + // zero-sized fields before non-zero-sized fields, otherwise + // the loop below goes wrong; hence the `f.size` in the sort + // key. + let mut fields = fields.clone(); + fields.sort_by_key(|f| (f.offset, f.size)); + + for field in fields { + let FieldInfo { ref name, offset, size, align } = field; + + if offset > min_offset { + let pad = offset - min_offset; + println!("print-type-size {indent}padding: {pad} bytes"); + } + + if offset < min_offset { + // If this happens it's probably a union. + println!( + "print-type-size {indent}field `.{name}`: {size} bytes, \ + offset: {offset} bytes, \ + alignment: {align} bytes" + ); + } else if info.packed || offset == min_offset { + println!("print-type-size {indent}field `.{name}`: {size} bytes"); + } else { + // Include field alignment in output only if it caused padding injection + println!( + "print-type-size {indent}field `.{name}`: {size} bytes, \ + alignment: {align} bytes" + ); + } + + min_offset = offset + size; + } + } + + match overall_size.checked_sub(max_variant_size) { + None => panic!("max_variant_size {max_variant_size} > {overall_size} overall_size"), + Some(diff @ 1..) => println!("print-type-size {indent}end padding: {diff} bytes"), + Some(0) => {} + } + } + } +} diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs new file mode 100644 index 000000000..6a8298605 --- /dev/null +++ b/compiler/rustc_session/src/config.rs @@ -0,0 +1,2970 @@ +//! Contains infrastructure for configuring the compiler, including parsing +//! command-line options. + +pub use crate::options::*; + +use crate::search_paths::SearchPath; +use crate::utils::{CanonicalizedPath, NativeLib, NativeLibKind}; +use crate::{early_error, early_warn, Session}; +use crate::{lint, HashStableContext}; + +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; + +use rustc_data_structures::stable_hasher::ToStableHashKey; +use rustc_target::abi::{Align, TargetDataLayout}; +use rustc_target::spec::{LinkerFlavor, SplitDebuginfo, Target, TargetTriple, TargetWarnings}; +use rustc_target::spec::{PanicStrategy, SanitizerSet, TARGETS}; + +use crate::parse::{CrateCheckConfig, CrateConfig}; +use rustc_feature::UnstableFeatures; +use rustc_span::edition::{Edition, DEFAULT_EDITION, EDITION_NAME_LIST, LATEST_STABLE_EDITION}; +use rustc_span::source_map::{FileName, FilePathMapping}; +use rustc_span::symbol::{sym, Symbol}; +use rustc_span::RealFileName; +use rustc_span::SourceFileHashAlgorithm; + +use rustc_errors::emitter::HumanReadableErrorType; +use rustc_errors::{ColorConfig, HandlerFlags}; + +use std::collections::btree_map::{ + Iter as BTreeMapIter, Keys as BTreeMapKeysIter, Values as BTreeMapValuesIter, +}; +use std::collections::{BTreeMap, BTreeSet}; +use std::fmt; +use std::hash::Hash; +use std::iter::{self, FromIterator}; +use std::path::{Path, PathBuf}; +use std::str::{self, FromStr}; + +/// The different settings that the `-C strip` flag can have. +#[derive(Clone, Copy, PartialEq, Hash, Debug)] +pub enum Strip { + /// Do not strip at all. + None, + + /// Strip debuginfo. + Debuginfo, + + /// Strip all symbols. + Symbols, +} + +/// The different settings that the `-C control-flow-guard` flag can have. +#[derive(Clone, Copy, PartialEq, Hash, Debug)] +pub enum CFGuard { + /// Do not emit Control Flow Guard metadata or checks. + Disabled, + + /// Emit Control Flow Guard metadata but no checks. + NoChecks, + + /// Emit Control Flow Guard metadata and checks. + Checks, +} + +/// The different settings that the `-Z cf-protection` flag can have. +#[derive(Clone, Copy, PartialEq, Hash, Debug)] +pub enum CFProtection { + /// Do not enable control-flow protection + None, + + /// Emit control-flow protection for branches (enables indirect branch tracking). + Branch, + + /// Emit control-flow protection for returns. + Return, + + /// Emit control-flow protection for both branches and returns. + Full, +} + +#[derive(Clone, Copy, Debug, PartialEq, Hash, HashStable_Generic)] +pub enum OptLevel { + No, // -O0 + Less, // -O1 + Default, // -O2 + Aggressive, // -O3 + Size, // -Os + SizeMin, // -Oz +} + +/// This is what the `LtoCli` values get mapped to after resolving defaults and +/// and taking other command line options into account. +/// +/// Note that linker plugin-based LTO is a different mechanism entirely. +#[derive(Clone, PartialEq)] +pub enum Lto { + /// Don't do any LTO whatsoever. + No, + + /// Do a full-crate-graph (inter-crate) LTO with ThinLTO. + Thin, + + /// Do a local ThinLTO (intra-crate, over the CodeGen Units of the local crate only). This is + /// only relevant if multiple CGUs are used. + ThinLocal, + + /// Do a full-crate-graph (inter-crate) LTO with "fat" LTO. + Fat, +} + +/// The different settings that the `-C lto` flag can have. +#[derive(Clone, Copy, PartialEq, Hash, Debug)] +pub enum LtoCli { + /// `-C lto=no` + No, + /// `-C lto=yes` + Yes, + /// `-C lto` + NoParam, + /// `-C lto=thin` + Thin, + /// `-C lto=fat` + Fat, + /// No `-C lto` flag passed + Unspecified, +} + +/// The different settings that the `-Z dump_mir_spanview` flag can have. `Statement` generates a +/// document highlighting each span of every statement (including terminators). `Terminator` and +/// `Block` highlight a single span per `BasicBlock`: the span of the block's `Terminator`, or a +/// computed span for the block, representing the entire range, covering the block's terminator and +/// all of its statements. +#[derive(Clone, Copy, PartialEq, Hash, Debug)] +pub enum MirSpanview { + /// Default `-Z dump_mir_spanview` or `-Z dump_mir_spanview=statement` + Statement, + /// `-Z dump_mir_spanview=terminator` + Terminator, + /// `-Z dump_mir_spanview=block` + Block, +} + +/// The different settings that the `-C instrument-coverage` flag can have. +/// +/// Coverage instrumentation now supports combining `-C instrument-coverage` +/// with compiler and linker optimization (enabled with `-O` or `-C opt-level=1` +/// and higher). Nevertheless, there are many variables, depending on options +/// selected, code structure, and enabled attributes. If errors are encountered, +/// either while compiling or when generating `llvm-cov show` reports, consider +/// lowering the optimization level, including or excluding `-C link-dead-code`, +/// or using `-Zunstable-options -C instrument-coverage=except-unused-functions` +/// or `-Zunstable-options -C instrument-coverage=except-unused-generics`. +/// +/// Note that `ExceptUnusedFunctions` means: When `mapgen.rs` generates the +/// coverage map, it will not attempt to generate synthetic functions for unused +/// (and not code-generated) functions (whether they are generic or not). As a +/// result, non-codegenned functions will not be included in the coverage map, +/// and will not appear, as covered or uncovered, in coverage reports. +/// +/// `ExceptUnusedGenerics` will add synthetic functions to the coverage map, +/// unless the function has type parameters. +#[derive(Clone, Copy, PartialEq, Hash, Debug)] +pub enum InstrumentCoverage { + /// Default `-C instrument-coverage` or `-C instrument-coverage=statement` + All, + /// `-Zunstable-options -C instrument-coverage=except-unused-generics` + ExceptUnusedGenerics, + /// `-Zunstable-options -C instrument-coverage=except-unused-functions` + ExceptUnusedFunctions, + /// `-C instrument-coverage=off` (or `no`, etc.) + Off, +} + +#[derive(Clone, PartialEq, Hash, Debug)] +pub enum LinkerPluginLto { + LinkerPlugin(PathBuf), + LinkerPluginAuto, + Disabled, +} + +/// Used with `-Z assert-incr-state`. +#[derive(Clone, Copy, PartialEq, Hash, Debug)] +pub enum IncrementalStateAssertion { + /// Found and loaded an existing session directory. + /// + /// Note that this says nothing about whether any particular query + /// will be found to be red or green. + Loaded, + /// Did not load an existing session directory. + NotLoaded, +} + +impl LinkerPluginLto { + pub fn enabled(&self) -> bool { + match *self { + LinkerPluginLto::LinkerPlugin(_) | LinkerPluginLto::LinkerPluginAuto => true, + LinkerPluginLto::Disabled => false, + } + } +} + +/// The different settings that can be enabled via the `-Z location-detail` flag. +#[derive(Clone, PartialEq, Hash, Debug)] +pub struct LocationDetail { + pub file: bool, + pub line: bool, + pub column: bool, +} + +impl LocationDetail { + pub fn all() -> Self { + Self { file: true, line: true, column: true } + } +} + +#[derive(Clone, PartialEq, Hash, Debug)] +pub enum SwitchWithOptPath { + Enabled(Option), + Disabled, +} + +impl SwitchWithOptPath { + pub fn enabled(&self) -> bool { + match *self { + SwitchWithOptPath::Enabled(_) => true, + SwitchWithOptPath::Disabled => false, + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, HashStable_Generic)] +#[derive(Encodable, Decodable)] +pub enum SymbolManglingVersion { + Legacy, + V0, +} + +#[derive(Clone, Copy, Debug, PartialEq, Hash)] +pub enum DebugInfo { + None, + Limited, + Full, +} + +/// Split debug-information is enabled by `-C split-debuginfo`, this enum is only used if split +/// debug-information is enabled (in either `Packed` or `Unpacked` modes), and the platform +/// uses DWARF for debug-information. +/// +/// Some debug-information requires link-time relocation and some does not. LLVM can partition +/// the debuginfo into sections depending on whether or not it requires link-time relocation. Split +/// DWARF provides a mechanism which allows the linker to skip the sections which don't require +/// link-time relocation - either by putting those sections in DWARF object files, or by keeping +/// them in the object file in such a way that the linker will skip them. +#[derive(Clone, Copy, Debug, PartialEq, Hash)] +pub enum SplitDwarfKind { + /// Sections which do not require relocation are written into object file but ignored by the + /// linker. + Single, + /// Sections which do not require relocation are written into a DWARF object (`.dwo`) file + /// which is ignored by the linker. + Split, +} + +impl FromStr for SplitDwarfKind { + type Err = (); + + fn from_str(s: &str) -> Result { + Ok(match s { + "single" => SplitDwarfKind::Single, + "split" => SplitDwarfKind::Split, + _ => return Err(()), + }) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, PartialOrd, Ord, HashStable_Generic)] +#[derive(Encodable, Decodable)] +pub enum OutputType { + Bitcode, + Assembly, + LlvmAssembly, + Mir, + Metadata, + Object, + Exe, + DepInfo, +} + +impl ToStableHashKey for OutputType { + type KeyType = Self; + + fn to_stable_hash_key(&self, _: &HCX) -> Self::KeyType { + *self + } +} + +impl OutputType { + fn is_compatible_with_codegen_units_and_single_output_file(&self) -> bool { + match *self { + OutputType::Exe | OutputType::DepInfo | OutputType::Metadata => true, + OutputType::Bitcode + | OutputType::Assembly + | OutputType::LlvmAssembly + | OutputType::Mir + | OutputType::Object => false, + } + } + + fn shorthand(&self) -> &'static str { + match *self { + OutputType::Bitcode => "llvm-bc", + OutputType::Assembly => "asm", + OutputType::LlvmAssembly => "llvm-ir", + OutputType::Mir => "mir", + OutputType::Object => "obj", + OutputType::Metadata => "metadata", + OutputType::Exe => "link", + OutputType::DepInfo => "dep-info", + } + } + + fn from_shorthand(shorthand: &str) -> Option { + Some(match shorthand { + "asm" => OutputType::Assembly, + "llvm-ir" => OutputType::LlvmAssembly, + "mir" => OutputType::Mir, + "llvm-bc" => OutputType::Bitcode, + "obj" => OutputType::Object, + "metadata" => OutputType::Metadata, + "link" => OutputType::Exe, + "dep-info" => OutputType::DepInfo, + _ => return None, + }) + } + + fn shorthands_display() -> String { + format!( + "`{}`, `{}`, `{}`, `{}`, `{}`, `{}`, `{}`, `{}`", + OutputType::Bitcode.shorthand(), + OutputType::Assembly.shorthand(), + OutputType::LlvmAssembly.shorthand(), + OutputType::Mir.shorthand(), + OutputType::Object.shorthand(), + OutputType::Metadata.shorthand(), + OutputType::Exe.shorthand(), + OutputType::DepInfo.shorthand(), + ) + } + + pub fn extension(&self) -> &'static str { + match *self { + OutputType::Bitcode => "bc", + OutputType::Assembly => "s", + OutputType::LlvmAssembly => "ll", + OutputType::Mir => "mir", + OutputType::Object => "o", + OutputType::Metadata => "rmeta", + OutputType::DepInfo => "d", + OutputType::Exe => "", + } + } +} + +/// The type of diagnostics output to generate. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ErrorOutputType { + /// Output meant for the consumption of humans. + HumanReadable(HumanReadableErrorType), + /// Output that's consumed by other tools such as `rustfix` or the `RLS`. + Json { + /// Render the JSON in a human readable way (with indents and newlines). + pretty: bool, + /// The JSON output includes a `rendered` field that includes the rendered + /// human output. + json_rendered: HumanReadableErrorType, + }, +} + +impl Default for ErrorOutputType { + fn default() -> Self { + Self::HumanReadable(HumanReadableErrorType::Default(ColorConfig::Auto)) + } +} + +/// Parameter to control path trimming. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +pub enum TrimmedDefPaths { + /// `try_print_trimmed_def_path` never prints a trimmed path and never calls the expensive query + #[default] + Never, + /// `try_print_trimmed_def_path` calls the expensive query, the query doesn't call `delay_good_path_bug` + Always, + /// `try_print_trimmed_def_path` calls the expensive query, the query calls `delay_good_path_bug` + GoodPath, +} + +/// Use tree-based collections to cheaply get a deterministic `Hash` implementation. +/// *Do not* switch `BTreeMap` out for an unsorted container type! That would break +/// dependency tracking for command-line arguments. Also only hash keys, since tracking +/// should only depend on the output types, not the paths they're written to. +#[derive(Clone, Debug, Hash, HashStable_Generic)] +pub struct OutputTypes(BTreeMap>); + +impl OutputTypes { + pub fn new(entries: &[(OutputType, Option)]) -> OutputTypes { + OutputTypes(BTreeMap::from_iter(entries.iter().map(|&(k, ref v)| (k, v.clone())))) + } + + pub fn get(&self, key: &OutputType) -> Option<&Option> { + self.0.get(key) + } + + pub fn contains_key(&self, key: &OutputType) -> bool { + self.0.contains_key(key) + } + + pub fn keys(&self) -> BTreeMapKeysIter<'_, OutputType, Option> { + self.0.keys() + } + + pub fn values(&self) -> BTreeMapValuesIter<'_, OutputType, Option> { + self.0.values() + } + + pub fn len(&self) -> usize { + self.0.len() + } + + /// Returns `true` if any of the output types require codegen or linking. + pub fn should_codegen(&self) -> bool { + self.0.keys().any(|k| match *k { + OutputType::Bitcode + | OutputType::Assembly + | OutputType::LlvmAssembly + | OutputType::Mir + | OutputType::Object + | OutputType::Exe => true, + OutputType::Metadata | OutputType::DepInfo => false, + }) + } + + /// Returns `true` if any of the output types require linking. + pub fn should_link(&self) -> bool { + self.0.keys().any(|k| match *k { + OutputType::Bitcode + | OutputType::Assembly + | OutputType::LlvmAssembly + | OutputType::Mir + | OutputType::Metadata + | OutputType::Object + | OutputType::DepInfo => false, + OutputType::Exe => true, + }) + } +} + +/// Use tree-based collections to cheaply get a deterministic `Hash` implementation. +/// *Do not* switch `BTreeMap` or `BTreeSet` out for an unsorted container type! That +/// would break dependency tracking for command-line arguments. +#[derive(Clone)] +pub struct Externs(BTreeMap); + +#[derive(Clone, Debug)] +pub struct ExternEntry { + pub location: ExternLocation, + /// Indicates this is a "private" dependency for the + /// `exported_private_dependencies` lint. + /// + /// This can be set with the `priv` option like + /// `--extern priv:name=foo.rlib`. + pub is_private_dep: bool, + /// Add the extern entry to the extern prelude. + /// + /// This can be disabled with the `noprelude` option like + /// `--extern noprelude:name`. + pub add_prelude: bool, + /// The extern entry shouldn't be considered for unused dependency warnings. + /// + /// `--extern nounused:std=/path/to/lib/libstd.rlib`. This is used to + /// suppress `unused-crate-dependencies` warnings. + pub nounused_dep: bool, +} + +#[derive(Clone, Debug)] +pub enum ExternLocation { + /// Indicates to look for the library in the search paths. + /// + /// Added via `--extern name`. + FoundInLibrarySearchDirectories, + /// The locations where this extern entry must be found. + /// + /// The `CrateLoader` is responsible for loading these and figuring out + /// which one to use. + /// + /// Added via `--extern prelude_name=some_file.rlib` + ExactPaths(BTreeSet), +} + +impl Externs { + /// Used for testing. + pub fn new(data: BTreeMap) -> Externs { + Externs(data) + } + + pub fn get(&self, key: &str) -> Option<&ExternEntry> { + self.0.get(key) + } + + pub fn iter(&self) -> BTreeMapIter<'_, String, ExternEntry> { + self.0.iter() + } + + pub fn len(&self) -> usize { + self.0.len() + } +} + +impl ExternEntry { + fn new(location: ExternLocation) -> ExternEntry { + ExternEntry { location, is_private_dep: false, add_prelude: false, nounused_dep: false } + } + + pub fn files(&self) -> Option> { + match &self.location { + ExternLocation::ExactPaths(set) => Some(set.iter()), + _ => None, + } + } +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum PrintRequest { + FileNames, + Sysroot, + TargetLibdir, + CrateName, + Cfg, + TargetList, + TargetCPUs, + TargetFeatures, + RelocationModels, + CodeModels, + TlsModels, + TargetSpec, + NativeStaticLibs, + StackProtectorStrategies, + LinkArgs, +} + +pub enum Input { + /// Load source code from a file. + File(PathBuf), + /// Load source code from a string. + Str { + /// A string that is shown in place of a filename. + name: FileName, + /// An anonymous string containing the source code. + input: String, + }, +} + +impl Input { + pub fn filestem(&self) -> &str { + match *self { + Input::File(ref ifile) => ifile.file_stem().unwrap().to_str().unwrap(), + Input::Str { .. } => "rust_out", + } + } + + pub fn source_name(&self) -> FileName { + match *self { + Input::File(ref ifile) => ifile.clone().into(), + Input::Str { ref name, .. } => name.clone(), + } + } +} + +#[derive(Clone, Hash, Debug, HashStable_Generic)] +pub struct OutputFilenames { + pub out_directory: PathBuf, + filestem: String, + pub single_output_file: Option, + pub temps_directory: Option, + pub outputs: OutputTypes, +} + +pub const RLINK_EXT: &str = "rlink"; +pub const RUST_CGU_EXT: &str = "rcgu"; +pub const DWARF_OBJECT_EXT: &str = "dwo"; + +impl OutputFilenames { + pub fn new( + out_directory: PathBuf, + out_filestem: String, + single_output_file: Option, + temps_directory: Option, + extra: String, + outputs: OutputTypes, + ) -> Self { + OutputFilenames { + out_directory, + single_output_file, + temps_directory, + outputs, + filestem: format!("{out_filestem}{extra}"), + } + } + + pub fn path(&self, flavor: OutputType) -> PathBuf { + self.outputs + .get(&flavor) + .and_then(|p| p.to_owned()) + .or_else(|| self.single_output_file.clone()) + .unwrap_or_else(|| self.output_path(flavor)) + } + + /// Gets the output path where a compilation artifact of the given type + /// should be placed on disk. + pub fn output_path(&self, flavor: OutputType) -> PathBuf { + let extension = flavor.extension(); + self.with_directory_and_extension(&self.out_directory, &extension) + } + + /// Gets the path where a compilation artifact of the given type for the + /// given codegen unit should be placed on disk. If codegen_unit_name is + /// None, a path distinct from those of any codegen unit will be generated. + pub fn temp_path(&self, flavor: OutputType, codegen_unit_name: Option<&str>) -> PathBuf { + let extension = flavor.extension(); + self.temp_path_ext(extension, codegen_unit_name) + } + + /// Like `temp_path`, but specifically for dwarf objects. + pub fn temp_path_dwo(&self, codegen_unit_name: Option<&str>) -> PathBuf { + self.temp_path_ext(DWARF_OBJECT_EXT, codegen_unit_name) + } + + /// Like `temp_path`, but also supports things where there is no corresponding + /// OutputType, like noopt-bitcode or lto-bitcode. + pub fn temp_path_ext(&self, ext: &str, codegen_unit_name: Option<&str>) -> PathBuf { + let mut extension = String::new(); + + if let Some(codegen_unit_name) = codegen_unit_name { + extension.push_str(codegen_unit_name); + } + + if !ext.is_empty() { + if !extension.is_empty() { + extension.push('.'); + extension.push_str(RUST_CGU_EXT); + extension.push('.'); + } + + extension.push_str(ext); + } + + let temps_directory = self.temps_directory.as_ref().unwrap_or(&self.out_directory); + + self.with_directory_and_extension(&temps_directory, &extension) + } + + pub fn with_extension(&self, extension: &str) -> PathBuf { + self.with_directory_and_extension(&self.out_directory, extension) + } + + fn with_directory_and_extension(&self, directory: &PathBuf, extension: &str) -> PathBuf { + let mut path = directory.join(&self.filestem); + path.set_extension(extension); + path + } + + /// Returns the path for the Split DWARF file - this can differ depending on which Split DWARF + /// mode is being used, which is the logic that this function is intended to encapsulate. + pub fn split_dwarf_path( + &self, + split_debuginfo_kind: SplitDebuginfo, + split_dwarf_kind: SplitDwarfKind, + cgu_name: Option<&str>, + ) -> Option { + let obj_out = self.temp_path(OutputType::Object, cgu_name); + let dwo_out = self.temp_path_dwo(cgu_name); + match (split_debuginfo_kind, split_dwarf_kind) { + (SplitDebuginfo::Off, SplitDwarfKind::Single | SplitDwarfKind::Split) => None, + // Single mode doesn't change how DWARF is emitted, but does add Split DWARF attributes + // (pointing at the path which is being determined here). Use the path to the current + // object file. + (SplitDebuginfo::Packed | SplitDebuginfo::Unpacked, SplitDwarfKind::Single) => { + Some(obj_out) + } + // Split mode emits the DWARF into a different file, use that path. + (SplitDebuginfo::Packed | SplitDebuginfo::Unpacked, SplitDwarfKind::Split) => { + Some(dwo_out) + } + } + } +} + +pub fn host_triple() -> &'static str { + // Get the host triple out of the build environment. This ensures that our + // idea of the host triple is the same as for the set of libraries we've + // actually built. We can't just take LLVM's host triple because they + // normalize all ix86 architectures to i386. + // + // Instead of grabbing the host triple (for the current host), we grab (at + // compile time) the target triple that this rustc is built with and + // calling that (at runtime) the host triple. + (option_env!("CFG_COMPILER_HOST_TRIPLE")).expect("CFG_COMPILER_HOST_TRIPLE") +} + +impl Default for Options { + fn default() -> Options { + Options { + assert_incr_state: None, + crate_types: Vec::new(), + optimize: OptLevel::No, + debuginfo: DebugInfo::None, + lint_opts: Vec::new(), + lint_cap: None, + describe_lints: false, + output_types: OutputTypes(BTreeMap::new()), + search_paths: vec![], + maybe_sysroot: None, + target_triple: TargetTriple::from_triple(host_triple()), + test: false, + incremental: None, + unstable_opts: Default::default(), + prints: Vec::new(), + cg: Default::default(), + error_format: ErrorOutputType::default(), + diagnostic_width: None, + externs: Externs(BTreeMap::new()), + crate_name: None, + libs: Vec::new(), + unstable_features: UnstableFeatures::Disallow, + debug_assertions: true, + actually_rustdoc: false, + trimmed_def_paths: TrimmedDefPaths::default(), + cli_forced_codegen_units: None, + cli_forced_thinlto_off: false, + remap_path_prefix: Vec::new(), + real_rust_source_base_dir: None, + edition: DEFAULT_EDITION, + json_artifact_notifications: false, + json_unused_externs: JsonUnusedExterns::No, + json_future_incompat: false, + pretty: None, + working_dir: RealFileName::LocalPath(std::env::current_dir().unwrap()), + } + } +} + +impl Options { + /// Returns `true` if there is a reason to build the dep graph. + pub fn build_dep_graph(&self) -> bool { + self.incremental.is_some() + || self.unstable_opts.dump_dep_graph + || self.unstable_opts.query_dep_graph + } + + pub fn file_path_mapping(&self) -> FilePathMapping { + FilePathMapping::new(self.remap_path_prefix.clone()) + } + + /// Returns `true` if there will be an output file generated. + pub fn will_create_output_file(&self) -> bool { + !self.unstable_opts.parse_only && // The file is just being parsed + !self.unstable_opts.ls // The file is just being queried + } + + #[inline] + pub fn share_generics(&self) -> bool { + match self.unstable_opts.share_generics { + Some(setting) => setting, + None => match self.optimize { + OptLevel::No | OptLevel::Less | OptLevel::Size | OptLevel::SizeMin => true, + OptLevel::Default | OptLevel::Aggressive => false, + }, + } + } + + pub fn get_symbol_mangling_version(&self) -> SymbolManglingVersion { + self.cg.symbol_mangling_version.unwrap_or(SymbolManglingVersion::Legacy) + } +} + +impl UnstableOptions { + pub fn diagnostic_handler_flags(&self, can_emit_warnings: bool) -> HandlerFlags { + HandlerFlags { + can_emit_warnings, + treat_err_as_bug: self.treat_err_as_bug, + dont_buffer_diagnostics: self.dont_buffer_diagnostics, + report_delayed_bugs: self.report_delayed_bugs, + macro_backtrace: self.macro_backtrace, + deduplicate_diagnostics: self.deduplicate_diagnostics, + } + } +} + +// The type of entry function, so users can have their own entry functions +#[derive(Copy, Clone, PartialEq, Hash, Debug, HashStable_Generic)] +pub enum EntryFnType { + Main, + Start, +} + +#[derive(Copy, PartialEq, PartialOrd, Clone, Ord, Eq, Hash, Debug, Encodable, Decodable)] +#[derive(HashStable_Generic)] +pub enum CrateType { + Executable, + Dylib, + Rlib, + Staticlib, + Cdylib, + ProcMacro, +} + +impl CrateType { + /// When generated, is this crate type an archive? + pub fn is_archive(&self) -> bool { + match *self { + CrateType::Rlib | CrateType::Staticlib => true, + CrateType::Executable | CrateType::Dylib | CrateType::Cdylib | CrateType::ProcMacro => { + false + } + } + } +} + +#[derive(Clone, Hash, Debug, PartialEq, Eq)] +pub enum Passes { + Some(Vec), + All, +} + +impl Passes { + pub fn is_empty(&self) -> bool { + match *self { + Passes::Some(ref v) => v.is_empty(), + Passes::All => false, + } + } + + pub fn extend(&mut self, passes: impl IntoIterator) { + match *self { + Passes::Some(ref mut v) => v.extend(passes), + Passes::All => {} + } + } +} + +#[derive(Clone, Copy, Hash, Debug, PartialEq)] +pub enum PAuthKey { + A, + B, +} + +#[derive(Clone, Copy, Hash, Debug, PartialEq)] +pub struct PacRet { + pub leaf: bool, + pub key: PAuthKey, +} + +#[derive(Clone, Copy, Hash, Debug, PartialEq)] +pub struct BranchProtection { + pub bti: bool, + pub pac_ret: Option, +} + +impl Default for BranchProtection { + fn default() -> Self { + BranchProtection { bti: false, pac_ret: None } + } +} + +pub const fn default_lib_output() -> CrateType { + CrateType::Rlib +} + +fn default_configuration(sess: &Session) -> CrateConfig { + // NOTE: This should be kept in sync with `CrateCheckConfig::fill_well_known` below. + let end = &sess.target.endian; + let arch = &sess.target.arch; + let wordsz = sess.target.pointer_width.to_string(); + let os = &sess.target.os; + let env = &sess.target.env; + let abi = &sess.target.abi; + let vendor = &sess.target.vendor; + let min_atomic_width = sess.target.min_atomic_width(); + let max_atomic_width = sess.target.max_atomic_width(); + let atomic_cas = sess.target.atomic_cas; + let layout = TargetDataLayout::parse(&sess.target).unwrap_or_else(|err| { + sess.fatal(&err); + }); + + let mut ret = FxHashSet::default(); + ret.reserve(7); // the minimum number of insertions + // Target bindings. + ret.insert((sym::target_os, Some(Symbol::intern(os)))); + for fam in sess.target.families.as_ref() { + ret.insert((sym::target_family, Some(Symbol::intern(fam)))); + if fam == "windows" { + ret.insert((sym::windows, None)); + } else if fam == "unix" { + ret.insert((sym::unix, None)); + } + } + ret.insert((sym::target_arch, Some(Symbol::intern(arch)))); + ret.insert((sym::target_endian, Some(Symbol::intern(end.as_str())))); + ret.insert((sym::target_pointer_width, Some(Symbol::intern(&wordsz)))); + ret.insert((sym::target_env, Some(Symbol::intern(env)))); + ret.insert((sym::target_abi, Some(Symbol::intern(abi)))); + ret.insert((sym::target_vendor, Some(Symbol::intern(vendor)))); + if sess.target.has_thread_local { + ret.insert((sym::target_thread_local, None)); + } + for (i, align) in [ + (8, layout.i8_align.abi), + (16, layout.i16_align.abi), + (32, layout.i32_align.abi), + (64, layout.i64_align.abi), + (128, layout.i128_align.abi), + ] { + if i >= min_atomic_width && i <= max_atomic_width { + let mut insert_atomic = |s, align: Align| { + ret.insert((sym::target_has_atomic_load_store, Some(Symbol::intern(s)))); + if atomic_cas { + ret.insert((sym::target_has_atomic, Some(Symbol::intern(s)))); + } + if align.bits() == i { + ret.insert((sym::target_has_atomic_equal_alignment, Some(Symbol::intern(s)))); + } + }; + let s = i.to_string(); + insert_atomic(&s, align); + if s == wordsz { + insert_atomic("ptr", layout.pointer_align.abi); + } + } + } + + let panic_strategy = sess.panic_strategy(); + ret.insert((sym::panic, Some(panic_strategy.desc_symbol()))); + + for s in sess.opts.unstable_opts.sanitizer { + let symbol = Symbol::intern(&s.to_string()); + ret.insert((sym::sanitize, Some(symbol))); + } + + if sess.opts.debug_assertions { + ret.insert((sym::debug_assertions, None)); + } + // JUSTIFICATION: before wrapper fn is available + #[cfg_attr(not(bootstrap), allow(rustc::bad_opt_access))] + if sess.opts.crate_types.contains(&CrateType::ProcMacro) { + ret.insert((sym::proc_macro, None)); + } + ret +} + +/// Converts the crate `cfg!` configuration from `String` to `Symbol`. +/// `rustc_interface::interface::Config` accepts this in the compiler configuration, +/// but the symbol interner is not yet set up then, so we must convert it later. +pub fn to_crate_config(cfg: FxHashSet<(String, Option)>) -> CrateConfig { + cfg.into_iter().map(|(a, b)| (Symbol::intern(&a), b.map(|b| Symbol::intern(&b)))).collect() +} + +/// The parsed `--check-cfg` options +pub struct CheckCfg { + /// The set of all `names()`, if None no name checking is performed + pub names_valid: Option>, + /// Is well known values activated + pub well_known_values: bool, + /// The set of all `values()` + pub values_valid: FxHashMap>, +} + +impl Default for CheckCfg { + fn default() -> Self { + CheckCfg { + names_valid: Default::default(), + values_valid: Default::default(), + well_known_values: false, + } + } +} + +impl CheckCfg { + fn map_data(&self, f: impl Fn(&T) -> O) -> CheckCfg { + CheckCfg { + names_valid: self + .names_valid + .as_ref() + .map(|names_valid| names_valid.iter().map(|a| f(a)).collect()), + values_valid: self + .values_valid + .iter() + .map(|(a, b)| (f(a), b.iter().map(|b| f(b)).collect())) + .collect(), + well_known_values: self.well_known_values, + } + } +} + +/// Converts the crate `--check-cfg` options from `String` to `Symbol`. +/// `rustc_interface::interface::Config` accepts this in the compiler configuration, +/// but the symbol interner is not yet set up then, so we must convert it later. +pub fn to_crate_check_config(cfg: CheckCfg) -> CrateCheckConfig { + cfg.map_data(|s| Symbol::intern(s)) +} + +impl CrateCheckConfig { + /// Fills a `CrateCheckConfig` with well-known configuration names. + fn fill_well_known_names(&mut self) { + // NOTE: This should be kept in sync with `default_configuration` and + // `fill_well_known_values` + const WELL_KNOWN_NAMES: &[Symbol] = &[ + // rustc + sym::unix, + sym::windows, + sym::target_os, + sym::target_family, + sym::target_arch, + sym::target_endian, + sym::target_pointer_width, + sym::target_env, + sym::target_abi, + sym::target_vendor, + sym::target_thread_local, + sym::target_has_atomic_load_store, + sym::target_has_atomic, + sym::target_has_atomic_equal_alignment, + sym::target_feature, + sym::panic, + sym::sanitize, + sym::debug_assertions, + sym::proc_macro, + sym::test, + sym::feature, + // rustdoc + sym::doc, + sym::doctest, + // miri + sym::miri, + ]; + + // We only insert well-known names if `names()` was activated + if let Some(names_valid) = &mut self.names_valid { + names_valid.extend(WELL_KNOWN_NAMES); + } + } + + /// Fills a `CrateCheckConfig` with well-known configuration values. + fn fill_well_known_values(&mut self) { + if !self.well_known_values { + return; + } + + // NOTE: This should be kept in sync with `default_configuration` and + // `fill_well_known_names` + + let panic_values = &PanicStrategy::all(); + + let atomic_values = &[ + sym::ptr, + sym::integer(8usize), + sym::integer(16usize), + sym::integer(32usize), + sym::integer(64usize), + sym::integer(128usize), + ]; + + let sanitize_values = SanitizerSet::all() + .into_iter() + .map(|sanitizer| Symbol::intern(sanitizer.as_str().unwrap())); + + // Unknown possible values: + // - `feature` + // - `target_feature` + + // No-values + for name in [ + sym::doc, + sym::miri, + sym::unix, + sym::test, + sym::doctest, + sym::windows, + sym::proc_macro, + sym::debug_assertions, + sym::target_thread_local, + ] { + self.values_valid.entry(name).or_default(); + } + + // Pre-defined values + self.values_valid.entry(sym::panic).or_default().extend(panic_values); + self.values_valid.entry(sym::sanitize).or_default().extend(sanitize_values); + self.values_valid.entry(sym::target_has_atomic).or_default().extend(atomic_values); + self.values_valid + .entry(sym::target_has_atomic_load_store) + .or_default() + .extend(atomic_values); + self.values_valid + .entry(sym::target_has_atomic_equal_alignment) + .or_default() + .extend(atomic_values); + + // Target specific values + { + const VALUES: [&Symbol; 8] = [ + &sym::target_os, + &sym::target_family, + &sym::target_arch, + &sym::target_endian, + &sym::target_env, + &sym::target_abi, + &sym::target_vendor, + &sym::target_pointer_width, + ]; + + // Initialize (if not already initialized) + for &e in VALUES { + self.values_valid.entry(e).or_default(); + } + + // Get all values map at once otherwise it would be costly. + // (8 values * 220 targets ~= 1760 times, at the time of writing this comment). + let [ + values_target_os, + values_target_family, + values_target_arch, + values_target_endian, + values_target_env, + values_target_abi, + values_target_vendor, + values_target_pointer_width, + ] = self + .values_valid + .get_many_mut(VALUES) + .expect("unable to get all the check-cfg values buckets"); + + for target in TARGETS + .iter() + .map(|target| Target::expect_builtin(&TargetTriple::from_triple(target))) + { + values_target_os.insert(Symbol::intern(&target.options.os)); + values_target_family + .extend(target.options.families.iter().map(|family| Symbol::intern(family))); + values_target_arch.insert(Symbol::intern(&target.arch)); + values_target_endian.insert(Symbol::intern(&target.options.endian.as_str())); + values_target_env.insert(Symbol::intern(&target.options.env)); + values_target_abi.insert(Symbol::intern(&target.options.abi)); + values_target_vendor.insert(Symbol::intern(&target.options.vendor)); + values_target_pointer_width.insert(sym::integer(target.pointer_width)); + } + } + } + + pub fn fill_well_known(&mut self) { + self.fill_well_known_names(); + self.fill_well_known_values(); + } +} + +pub fn build_configuration(sess: &Session, mut user_cfg: CrateConfig) -> CrateConfig { + // Combine the configuration requested by the session (command line) with + // some default and generated configuration items. + let default_cfg = default_configuration(sess); + // If the user wants a test runner, then add the test cfg. + if sess.opts.test { + user_cfg.insert((sym::test, None)); + } + user_cfg.extend(default_cfg.iter().cloned()); + user_cfg +} + +pub(super) fn build_target_config( + opts: &Options, + target_override: Option, + sysroot: &Path, +) -> Target { + let target_result = target_override.map_or_else( + || Target::search(&opts.target_triple, sysroot), + |t| Ok((t, TargetWarnings::empty())), + ); + let (target, target_warnings) = target_result.unwrap_or_else(|e| { + early_error( + opts.error_format, + &format!( + "Error loading target specification: {}. \ + Run `rustc --print target-list` for a list of built-in targets", + e + ), + ) + }); + for warning in target_warnings.warning_messages() { + early_warn(opts.error_format, &warning) + } + + if !matches!(target.pointer_width, 16 | 32 | 64) { + early_error( + opts.error_format, + &format!( + "target specification was invalid: \ + unrecognized target-pointer-width {}", + target.pointer_width + ), + ) + } + + target +} + +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +pub enum OptionStability { + Stable, + Unstable, +} + +pub struct RustcOptGroup { + pub apply: Box &mut getopts::Options>, + pub name: &'static str, + pub stability: OptionStability, +} + +impl RustcOptGroup { + pub fn is_stable(&self) -> bool { + self.stability == OptionStability::Stable + } + + pub fn stable(name: &'static str, f: F) -> RustcOptGroup + where + F: Fn(&mut getopts::Options) -> &mut getopts::Options + 'static, + { + RustcOptGroup { name, apply: Box::new(f), stability: OptionStability::Stable } + } + + pub fn unstable(name: &'static str, f: F) -> RustcOptGroup + where + F: Fn(&mut getopts::Options) -> &mut getopts::Options + 'static, + { + RustcOptGroup { name, apply: Box::new(f), stability: OptionStability::Unstable } + } +} + +// The `opt` local module holds wrappers around the `getopts` API that +// adds extra rustc-specific metadata to each option; such metadata +// is exposed by . The public +// functions below ending with `_u` are the functions that return +// *unstable* options, i.e., options that are only enabled when the +// user also passes the `-Z unstable-options` debugging flag. +mod opt { + // The `fn flag*` etc below are written so that we can use them + // in the future; do not warn about them not being used right now. + #![allow(dead_code)] + + use super::RustcOptGroup; + + pub type R = RustcOptGroup; + pub type S = &'static str; + + fn stable(name: S, f: F) -> R + where + F: Fn(&mut getopts::Options) -> &mut getopts::Options + 'static, + { + RustcOptGroup::stable(name, f) + } + + fn unstable(name: S, f: F) -> R + where + F: Fn(&mut getopts::Options) -> &mut getopts::Options + 'static, + { + RustcOptGroup::unstable(name, f) + } + + fn longer(a: S, b: S) -> S { + if a.len() > b.len() { a } else { b } + } + + pub fn opt_s(a: S, b: S, c: S, d: S) -> R { + stable(longer(a, b), move |opts| opts.optopt(a, b, c, d)) + } + pub fn multi_s(a: S, b: S, c: S, d: S) -> R { + stable(longer(a, b), move |opts| opts.optmulti(a, b, c, d)) + } + pub fn flag_s(a: S, b: S, c: S) -> R { + stable(longer(a, b), move |opts| opts.optflag(a, b, c)) + } + pub fn flagmulti_s(a: S, b: S, c: S) -> R { + stable(longer(a, b), move |opts| opts.optflagmulti(a, b, c)) + } + + pub fn opt(a: S, b: S, c: S, d: S) -> R { + unstable(longer(a, b), move |opts| opts.optopt(a, b, c, d)) + } + pub fn multi(a: S, b: S, c: S, d: S) -> R { + unstable(longer(a, b), move |opts| opts.optmulti(a, b, c, d)) + } +} + +/// Returns the "short" subset of the rustc command line options, +/// including metadata for each option, such as whether the option is +/// part of the stable long-term interface for rustc. +pub fn rustc_short_optgroups() -> Vec { + vec![ + opt::flag_s("h", "help", "Display this message"), + opt::multi_s("", "cfg", "Configure the compilation environment", "SPEC"), + opt::multi("", "check-cfg", "Provide list of valid cfg options for checking", "SPEC"), + opt::multi_s( + "L", + "", + "Add a directory to the library search path. The + optional KIND can be one of dependency, crate, native, + framework, or all (the default).", + "[KIND=]PATH", + ), + opt::multi_s( + "l", + "", + "Link the generated crate(s) to the specified native + library NAME. The optional KIND can be one of + static, framework, or dylib (the default). + Optional comma separated MODIFIERS (bundle|verbatim|whole-archive|as-needed) + may be specified each with a prefix of either '+' to + enable or '-' to disable.", + "[KIND[:MODIFIERS]=]NAME[:RENAME]", + ), + make_crate_type_option(), + opt::opt_s("", "crate-name", "Specify the name of the crate being built", "NAME"), + opt::opt_s( + "", + "edition", + "Specify which edition of the compiler to use when compiling code.", + EDITION_NAME_LIST, + ), + opt::multi_s( + "", + "emit", + "Comma separated list of types of output for \ + the compiler to emit", + "[asm|llvm-bc|llvm-ir|obj|metadata|link|dep-info|mir]", + ), + opt::multi_s( + "", + "print", + "Compiler information to print on stdout", + "[crate-name|file-names|sysroot|target-libdir|cfg|target-list|\ + target-cpus|target-features|relocation-models|code-models|\ + tls-models|target-spec-json|native-static-libs|stack-protector-strategies|\ + link-args]", + ), + opt::flagmulti_s("g", "", "Equivalent to -C debuginfo=2"), + opt::flagmulti_s("O", "", "Equivalent to -C opt-level=2"), + opt::opt_s("o", "", "Write output to ", "FILENAME"), + opt::opt_s( + "", + "out-dir", + "Write output to compiler-chosen filename \ + in ", + "DIR", + ), + opt::opt_s( + "", + "explain", + "Provide a detailed explanation of an error \ + message", + "OPT", + ), + opt::flag_s("", "test", "Build a test harness"), + opt::opt_s("", "target", "Target triple for which the code is compiled", "TARGET"), + opt::multi_s("A", "allow", "Set lint allowed", "LINT"), + opt::multi_s("W", "warn", "Set lint warnings", "LINT"), + opt::multi_s("", "force-warn", "Set lint force-warn", "LINT"), + opt::multi_s("D", "deny", "Set lint denied", "LINT"), + opt::multi_s("F", "forbid", "Set lint forbidden", "LINT"), + opt::multi_s( + "", + "cap-lints", + "Set the most restrictive lint level. \ + More restrictive lints are capped at this \ + level", + "LEVEL", + ), + opt::multi_s("C", "codegen", "Set a codegen option", "OPT[=VALUE]"), + opt::flag_s("V", "version", "Print version info and exit"), + opt::flag_s("v", "verbose", "Use verbose output"), + ] +} + +/// Returns all rustc command line options, including metadata for +/// each option, such as whether the option is part of the stable +/// long-term interface for rustc. +pub fn rustc_optgroups() -> Vec { + let mut opts = rustc_short_optgroups(); + // FIXME: none of these descriptions are actually used + opts.extend(vec![ + opt::multi_s( + "", + "extern", + "Specify where an external rust library is located", + "NAME[=PATH]", + ), + opt::opt_s("", "sysroot", "Override the system root", "PATH"), + opt::multi("Z", "", "Set unstable / perma-unstable options", "FLAG"), + opt::opt_s( + "", + "error-format", + "How errors and other messages are produced", + "human|json|short", + ), + opt::multi_s("", "json", "Configure the JSON output of the compiler", "CONFIG"), + opt::opt_s( + "", + "color", + "Configure coloring of output: + auto = colorize, if output goes to a tty (default); + always = always colorize output; + never = never colorize output", + "auto|always|never", + ), + opt::opt_s( + "", + "diagnostic-width", + "Inform rustc of the width of the output so that diagnostics can be truncated to fit", + "WIDTH", + ), + opt::multi_s( + "", + "remap-path-prefix", + "Remap source names in all output (compiler messages and output files)", + "FROM=TO", + ), + ]); + opts +} + +pub fn get_cmd_lint_options( + matches: &getopts::Matches, + error_format: ErrorOutputType, +) -> (Vec<(String, lint::Level)>, bool, Option) { + let mut lint_opts_with_position = vec![]; + let mut describe_lints = false; + + for level in [lint::Allow, lint::Warn, lint::ForceWarn(None), lint::Deny, lint::Forbid] { + for (arg_pos, lint_name) in matches.opt_strs_pos(level.as_str()) { + if lint_name == "help" { + describe_lints = true; + } else { + lint_opts_with_position.push((arg_pos, lint_name.replace('-', "_"), level)); + } + } + } + + lint_opts_with_position.sort_by_key(|x| x.0); + let lint_opts = lint_opts_with_position + .iter() + .cloned() + .map(|(_, lint_name, level)| (lint_name, level)) + .collect(); + + let lint_cap = matches.opt_str("cap-lints").map(|cap| { + lint::Level::from_str(&cap) + .unwrap_or_else(|| early_error(error_format, &format!("unknown lint level: `{cap}`"))) + }); + + (lint_opts, describe_lints, lint_cap) +} + +/// Parses the `--color` flag. +pub fn parse_color(matches: &getopts::Matches) -> ColorConfig { + match matches.opt_str("color").as_ref().map(|s| &s[..]) { + Some("auto") => ColorConfig::Auto, + Some("always") => ColorConfig::Always, + Some("never") => ColorConfig::Never, + + None => ColorConfig::Auto, + + Some(arg) => early_error( + ErrorOutputType::default(), + &format!( + "argument for `--color` must be auto, \ + always or never (instead was `{arg}`)" + ), + ), + } +} + +/// Possible json config files +pub struct JsonConfig { + pub json_rendered: HumanReadableErrorType, + pub json_artifact_notifications: bool, + pub json_unused_externs: JsonUnusedExterns, + pub json_future_incompat: bool, +} + +/// Report unused externs in event stream +#[derive(Copy, Clone)] +pub enum JsonUnusedExterns { + /// Do not + No, + /// Report, but do not exit with failure status for deny/forbid + Silent, + /// Report, and also exit with failure status for deny/forbid + Loud, +} + +impl JsonUnusedExterns { + pub fn is_enabled(&self) -> bool { + match self { + JsonUnusedExterns::No => false, + JsonUnusedExterns::Loud | JsonUnusedExterns::Silent => true, + } + } + + pub fn is_loud(&self) -> bool { + match self { + JsonUnusedExterns::No | JsonUnusedExterns::Silent => false, + JsonUnusedExterns::Loud => true, + } + } +} + +/// Parse the `--json` flag. +/// +/// The first value returned is how to render JSON diagnostics, and the second +/// is whether or not artifact notifications are enabled. +pub fn parse_json(matches: &getopts::Matches) -> JsonConfig { + let mut json_rendered: fn(ColorConfig) -> HumanReadableErrorType = + HumanReadableErrorType::Default; + let mut json_color = ColorConfig::Never; + let mut json_artifact_notifications = false; + let mut json_unused_externs = JsonUnusedExterns::No; + let mut json_future_incompat = false; + for option in matches.opt_strs("json") { + // For now conservatively forbid `--color` with `--json` since `--json` + // won't actually be emitting any colors and anything colorized is + // embedded in a diagnostic message anyway. + if matches.opt_str("color").is_some() { + early_error( + ErrorOutputType::default(), + "cannot specify the `--color` option with `--json`", + ); + } + + for sub_option in option.split(',') { + match sub_option { + "diagnostic-short" => json_rendered = HumanReadableErrorType::Short, + "diagnostic-rendered-ansi" => json_color = ColorConfig::Always, + "artifacts" => json_artifact_notifications = true, + "unused-externs" => json_unused_externs = JsonUnusedExterns::Loud, + "unused-externs-silent" => json_unused_externs = JsonUnusedExterns::Silent, + "future-incompat" => json_future_incompat = true, + s => early_error( + ErrorOutputType::default(), + &format!("unknown `--json` option `{s}`"), + ), + } + } + } + + JsonConfig { + json_rendered: json_rendered(json_color), + json_artifact_notifications, + json_unused_externs, + json_future_incompat, + } +} + +/// Parses the `--error-format` flag. +pub fn parse_error_format( + matches: &getopts::Matches, + color: ColorConfig, + json_rendered: HumanReadableErrorType, +) -> ErrorOutputType { + // We need the `opts_present` check because the driver will send us Matches + // with only stable options if no unstable options are used. Since error-format + // is unstable, it will not be present. We have to use `opts_present` not + // `opt_present` because the latter will panic. + let error_format = if matches.opts_present(&["error-format".to_owned()]) { + match matches.opt_str("error-format").as_ref().map(|s| &s[..]) { + None | Some("human") => { + ErrorOutputType::HumanReadable(HumanReadableErrorType::Default(color)) + } + Some("human-annotate-rs") => { + ErrorOutputType::HumanReadable(HumanReadableErrorType::AnnotateSnippet(color)) + } + Some("json") => ErrorOutputType::Json { pretty: false, json_rendered }, + Some("pretty-json") => ErrorOutputType::Json { pretty: true, json_rendered }, + Some("short") => ErrorOutputType::HumanReadable(HumanReadableErrorType::Short(color)), + + Some(arg) => early_error( + ErrorOutputType::HumanReadable(HumanReadableErrorType::Default(color)), + &format!( + "argument for `--error-format` must be `human`, `json` or \ + `short` (instead was `{arg}`)" + ), + ), + } + } else { + ErrorOutputType::HumanReadable(HumanReadableErrorType::Default(color)) + }; + + match error_format { + ErrorOutputType::Json { .. } => {} + + // Conservatively require that the `--json` argument is coupled with + // `--error-format=json`. This means that `--json` is specified we + // should actually be emitting JSON blobs. + _ if !matches.opt_strs("json").is_empty() => { + early_error( + ErrorOutputType::default(), + "using `--json` requires also using `--error-format=json`", + ); + } + + _ => {} + } + + error_format +} + +pub fn parse_crate_edition(matches: &getopts::Matches) -> Edition { + let edition = match matches.opt_str("edition") { + Some(arg) => Edition::from_str(&arg).unwrap_or_else(|_| { + early_error( + ErrorOutputType::default(), + &format!( + "argument for `--edition` must be one of: \ + {EDITION_NAME_LIST}. (instead was `{arg}`)" + ), + ) + }), + None => DEFAULT_EDITION, + }; + + if !edition.is_stable() && !nightly_options::is_unstable_enabled(matches) { + let is_nightly = nightly_options::match_is_nightly_build(matches); + let msg = if !is_nightly { + format!( + "the crate requires edition {}, but the latest edition supported by this Rust version is {}", + edition, LATEST_STABLE_EDITION + ) + } else { + format!("edition {edition} is unstable and only available with -Z unstable-options") + }; + early_error(ErrorOutputType::default(), &msg) + } + + edition +} + +fn check_error_format_stability( + unstable_opts: &UnstableOptions, + error_format: ErrorOutputType, + json_rendered: HumanReadableErrorType, +) { + if !unstable_opts.unstable_options { + if let ErrorOutputType::Json { pretty: true, json_rendered } = error_format { + early_error( + ErrorOutputType::Json { pretty: false, json_rendered }, + "`--error-format=pretty-json` is unstable", + ); + } + if let ErrorOutputType::HumanReadable(HumanReadableErrorType::AnnotateSnippet(_)) = + error_format + { + early_error( + ErrorOutputType::Json { pretty: false, json_rendered }, + "`--error-format=human-annotate-rs` is unstable", + ); + } + } +} + +fn parse_output_types( + unstable_opts: &UnstableOptions, + matches: &getopts::Matches, + error_format: ErrorOutputType, +) -> OutputTypes { + let mut output_types = BTreeMap::new(); + if !unstable_opts.parse_only { + for list in matches.opt_strs("emit") { + for output_type in list.split(',') { + let (shorthand, path) = match output_type.split_once('=') { + None => (output_type, None), + Some((shorthand, path)) => (shorthand, Some(PathBuf::from(path))), + }; + let output_type = OutputType::from_shorthand(shorthand).unwrap_or_else(|| { + early_error( + error_format, + &format!( + "unknown emission type: `{shorthand}` - expected one of: {display}", + display = OutputType::shorthands_display(), + ), + ) + }); + output_types.insert(output_type, path); + } + } + }; + if output_types.is_empty() { + output_types.insert(OutputType::Exe, None); + } + OutputTypes(output_types) +} + +fn should_override_cgus_and_disable_thinlto( + output_types: &OutputTypes, + matches: &getopts::Matches, + error_format: ErrorOutputType, + mut codegen_units: Option, +) -> (bool, Option) { + let mut disable_thinlto = false; + // Issue #30063: if user requests LLVM-related output to one + // particular path, disable codegen-units. + let incompatible: Vec<_> = output_types + .0 + .iter() + .map(|ot_path| ot_path.0) + .filter(|ot| !ot.is_compatible_with_codegen_units_and_single_output_file()) + .map(|ot| ot.shorthand()) + .collect(); + if !incompatible.is_empty() { + match codegen_units { + Some(n) if n > 1 => { + if matches.opt_present("o") { + for ot in &incompatible { + early_warn( + error_format, + &format!( + "`--emit={ot}` with `-o` incompatible with \ + `-C codegen-units=N` for N > 1", + ), + ); + } + early_warn(error_format, "resetting to default -C codegen-units=1"); + codegen_units = Some(1); + disable_thinlto = true; + } + } + _ => { + codegen_units = Some(1); + disable_thinlto = true; + } + } + } + + if codegen_units == Some(0) { + early_error(error_format, "value for codegen units must be a positive non-zero integer"); + } + + (disable_thinlto, codegen_units) +} + +fn check_thread_count(unstable_opts: &UnstableOptions, error_format: ErrorOutputType) { + if unstable_opts.threads == 0 { + early_error(error_format, "value for threads must be a positive non-zero integer"); + } + + if unstable_opts.threads > 1 && unstable_opts.fuel.is_some() { + early_error(error_format, "optimization fuel is incompatible with multiple threads"); + } +} + +fn collect_print_requests( + cg: &mut CodegenOptions, + unstable_opts: &mut UnstableOptions, + matches: &getopts::Matches, + error_format: ErrorOutputType, +) -> Vec { + let mut prints = Vec::::new(); + if cg.target_cpu.as_ref().map_or(false, |s| s == "help") { + prints.push(PrintRequest::TargetCPUs); + cg.target_cpu = None; + }; + if cg.target_feature == "help" { + prints.push(PrintRequest::TargetFeatures); + cg.target_feature = String::new(); + } + + prints.extend(matches.opt_strs("print").into_iter().map(|s| match &*s { + "crate-name" => PrintRequest::CrateName, + "file-names" => PrintRequest::FileNames, + "sysroot" => PrintRequest::Sysroot, + "target-libdir" => PrintRequest::TargetLibdir, + "cfg" => PrintRequest::Cfg, + "target-list" => PrintRequest::TargetList, + "target-cpus" => PrintRequest::TargetCPUs, + "target-features" => PrintRequest::TargetFeatures, + "relocation-models" => PrintRequest::RelocationModels, + "code-models" => PrintRequest::CodeModels, + "tls-models" => PrintRequest::TlsModels, + "native-static-libs" => PrintRequest::NativeStaticLibs, + "stack-protector-strategies" => PrintRequest::StackProtectorStrategies, + "target-spec-json" => { + if unstable_opts.unstable_options { + PrintRequest::TargetSpec + } else { + early_error( + error_format, + "the `-Z unstable-options` flag must also be passed to \ + enable the target-spec-json print option", + ); + } + } + "link-args" => PrintRequest::LinkArgs, + req => early_error(error_format, &format!("unknown print request `{req}`")), + })); + + prints +} + +pub fn parse_target_triple( + matches: &getopts::Matches, + error_format: ErrorOutputType, +) -> TargetTriple { + match matches.opt_str("target") { + Some(target) if target.ends_with(".json") => { + let path = Path::new(&target); + TargetTriple::from_path(&path).unwrap_or_else(|_| { + early_error(error_format, &format!("target file {path:?} does not exist")) + }) + } + Some(target) => TargetTriple::TargetTriple(target), + _ => TargetTriple::from_triple(host_triple()), + } +} + +fn parse_opt_level( + matches: &getopts::Matches, + cg: &CodegenOptions, + error_format: ErrorOutputType, +) -> OptLevel { + // The `-O` and `-C opt-level` flags specify the same setting, so we want to be able + // to use them interchangeably. However, because they're technically different flags, + // we need to work out manually which should take precedence if both are supplied (i.e. + // the rightmost flag). We do this by finding the (rightmost) position of both flags and + // comparing them. Note that if a flag is not found, its position will be `None`, which + // always compared less than `Some(_)`. + let max_o = matches.opt_positions("O").into_iter().max(); + let max_c = matches + .opt_strs_pos("C") + .into_iter() + .flat_map(|(i, s)| { + // NB: This can match a string without `=`. + if let Some("opt-level") = s.splitn(2, '=').next() { Some(i) } else { None } + }) + .max(); + if max_o > max_c { + OptLevel::Default + } else { + match cg.opt_level.as_ref() { + "0" => OptLevel::No, + "1" => OptLevel::Less, + "2" => OptLevel::Default, + "3" => OptLevel::Aggressive, + "s" => OptLevel::Size, + "z" => OptLevel::SizeMin, + arg => { + early_error( + error_format, + &format!( + "optimization level needs to be \ + between 0-3, s or z (instead was `{arg}`)" + ), + ); + } + } + } +} + +fn select_debuginfo( + matches: &getopts::Matches, + cg: &CodegenOptions, + error_format: ErrorOutputType, +) -> DebugInfo { + let max_g = matches.opt_positions("g").into_iter().max(); + let max_c = matches + .opt_strs_pos("C") + .into_iter() + .flat_map(|(i, s)| { + // NB: This can match a string without `=`. + if let Some("debuginfo") = s.splitn(2, '=').next() { Some(i) } else { None } + }) + .max(); + if max_g > max_c { + DebugInfo::Full + } else { + match cg.debuginfo { + 0 => DebugInfo::None, + 1 => DebugInfo::Limited, + 2 => DebugInfo::Full, + arg => { + early_error( + error_format, + &format!( + "debug info level needs to be between \ + 0-2 (instead was `{arg}`)" + ), + ); + } + } + } +} + +pub(crate) fn parse_assert_incr_state( + opt_assertion: &Option, + error_format: ErrorOutputType, +) -> Option { + match opt_assertion { + Some(s) if s.as_str() == "loaded" => Some(IncrementalStateAssertion::Loaded), + Some(s) if s.as_str() == "not-loaded" => Some(IncrementalStateAssertion::NotLoaded), + Some(s) => { + early_error(error_format, &format!("unexpected incremental state assertion value: {s}")) + } + None => None, + } +} + +fn parse_native_lib_kind( + matches: &getopts::Matches, + kind: &str, + error_format: ErrorOutputType, +) -> (NativeLibKind, Option) { + let (kind, modifiers) = match kind.split_once(':') { + None => (kind, None), + Some((kind, modifiers)) => (kind, Some(modifiers)), + }; + + let kind = match kind { + "static" => NativeLibKind::Static { bundle: None, whole_archive: None }, + "dylib" => NativeLibKind::Dylib { as_needed: None }, + "framework" => NativeLibKind::Framework { as_needed: None }, + "link-arg" => { + if !nightly_options::is_unstable_enabled(matches) { + let why = if nightly_options::match_is_nightly_build(matches) { + " and only accepted on the nightly compiler" + } else { + ", the `-Z unstable-options` flag must also be passed to use it" + }; + early_error(error_format, &format!("library kind `link-arg` is unstable{why}")) + } + NativeLibKind::LinkArg + } + _ => early_error( + error_format, + &format!( + "unknown library kind `{kind}`, expected one of: static, dylib, framework, link-arg" + ), + ), + }; + match modifiers { + None => (kind, None), + Some(modifiers) => parse_native_lib_modifiers(kind, modifiers, error_format, matches), + } +} + +fn parse_native_lib_modifiers( + mut kind: NativeLibKind, + modifiers: &str, + error_format: ErrorOutputType, + matches: &getopts::Matches, +) -> (NativeLibKind, Option) { + let mut verbatim = None; + for modifier in modifiers.split(',') { + let (modifier, value) = match modifier.strip_prefix(&['+', '-']) { + Some(m) => (m, modifier.starts_with('+')), + None => early_error( + error_format, + "invalid linking modifier syntax, expected '+' or '-' prefix \ + before one of: bundle, verbatim, whole-archive, as-needed", + ), + }; + + let report_unstable_modifier = || { + if !nightly_options::is_unstable_enabled(matches) { + let why = if nightly_options::match_is_nightly_build(matches) { + " and only accepted on the nightly compiler" + } else { + ", the `-Z unstable-options` flag must also be passed to use it" + }; + early_error( + error_format, + &format!("linking modifier `{modifier}` is unstable{why}"), + ) + } + }; + let assign_modifier = |dst: &mut Option| { + if dst.is_some() { + let msg = format!("multiple `{modifier}` modifiers in a single `-l` option"); + early_error(error_format, &msg) + } else { + *dst = Some(value); + } + }; + match (modifier, &mut kind) { + ("bundle", NativeLibKind::Static { bundle, .. }) => assign_modifier(bundle), + ("bundle", _) => early_error( + error_format, + "linking modifier `bundle` is only compatible with `static` linking kind", + ), + + ("verbatim", _) => { + report_unstable_modifier(); + assign_modifier(&mut verbatim) + } + + ("whole-archive", NativeLibKind::Static { whole_archive, .. }) => { + assign_modifier(whole_archive) + } + ("whole-archive", _) => early_error( + error_format, + "linking modifier `whole-archive` is only compatible with `static` linking kind", + ), + + ("as-needed", NativeLibKind::Dylib { as_needed }) + | ("as-needed", NativeLibKind::Framework { as_needed }) => { + report_unstable_modifier(); + assign_modifier(as_needed) + } + ("as-needed", _) => early_error( + error_format, + "linking modifier `as-needed` is only compatible with \ + `dylib` and `framework` linking kinds", + ), + + // Note: this error also excludes the case with empty modifier + // string, like `modifiers = ""`. + _ => early_error( + error_format, + &format!( + "unknown linking modifier `{modifier}`, expected one \ + of: bundle, verbatim, whole-archive, as-needed" + ), + ), + } + } + + (kind, verbatim) +} + +fn parse_libs(matches: &getopts::Matches, error_format: ErrorOutputType) -> Vec { + matches + .opt_strs("l") + .into_iter() + .map(|s| { + // Parse string of the form "[KIND[:MODIFIERS]=]lib[:new_name]", + // where KIND is one of "dylib", "framework", "static", "link-arg" and + // where MODIFIERS are a comma separated list of supported modifiers + // (bundle, verbatim, whole-archive, as-needed). Each modifier is prefixed + // with either + or - to indicate whether it is enabled or disabled. + // The last value specified for a given modifier wins. + let (name, kind, verbatim) = match s.split_once('=') { + None => (s, NativeLibKind::Unspecified, None), + Some((kind, name)) => { + let (kind, verbatim) = parse_native_lib_kind(matches, kind, error_format); + (name.to_string(), kind, verbatim) + } + }; + + let (name, new_name) = match name.split_once(':') { + None => (name, None), + Some((name, new_name)) => (name.to_string(), Some(new_name.to_owned())), + }; + if name.is_empty() { + early_error(error_format, "library name must not be empty"); + } + NativeLib { name, new_name, kind, verbatim } + }) + .collect() +} + +pub fn parse_externs( + matches: &getopts::Matches, + unstable_opts: &UnstableOptions, + error_format: ErrorOutputType, +) -> Externs { + let is_unstable_enabled = unstable_opts.unstable_options; + let mut externs: BTreeMap = BTreeMap::new(); + for arg in matches.opt_strs("extern") { + let (name, path) = match arg.split_once('=') { + None => (arg, None), + Some((name, path)) => (name.to_string(), Some(Path::new(path))), + }; + let (options, name) = match name.split_once(':') { + None => (None, name), + Some((opts, name)) => (Some(opts), name.to_string()), + }; + + let path = path.map(|p| CanonicalizedPath::new(p)); + + let entry = externs.entry(name.to_owned()); + + use std::collections::btree_map::Entry; + + let entry = if let Some(path) = path { + // --extern prelude_name=some_file.rlib + match entry { + Entry::Vacant(vacant) => { + let files = BTreeSet::from_iter(iter::once(path)); + vacant.insert(ExternEntry::new(ExternLocation::ExactPaths(files))) + } + Entry::Occupied(occupied) => { + let ext_ent = occupied.into_mut(); + match ext_ent { + ExternEntry { location: ExternLocation::ExactPaths(files), .. } => { + files.insert(path); + } + ExternEntry { + location: location @ ExternLocation::FoundInLibrarySearchDirectories, + .. + } => { + // Exact paths take precedence over search directories. + let files = BTreeSet::from_iter(iter::once(path)); + *location = ExternLocation::ExactPaths(files); + } + } + ext_ent + } + } + } else { + // --extern prelude_name + match entry { + Entry::Vacant(vacant) => { + vacant.insert(ExternEntry::new(ExternLocation::FoundInLibrarySearchDirectories)) + } + Entry::Occupied(occupied) => { + // Ignore if already specified. + occupied.into_mut() + } + } + }; + + let mut is_private_dep = false; + let mut add_prelude = true; + let mut nounused_dep = false; + if let Some(opts) = options { + if !is_unstable_enabled { + early_error( + error_format, + "the `-Z unstable-options` flag must also be passed to \ + enable `--extern options", + ); + } + for opt in opts.split(',') { + match opt { + "priv" => is_private_dep = true, + "noprelude" => { + if let ExternLocation::ExactPaths(_) = &entry.location { + add_prelude = false; + } else { + early_error( + error_format, + "the `noprelude` --extern option requires a file path", + ); + } + } + "nounused" => nounused_dep = true, + _ => early_error(error_format, &format!("unknown --extern option `{opt}`")), + } + } + } + + // Crates start out being not private, and go to being private `priv` + // is specified. + entry.is_private_dep |= is_private_dep; + // likewise `nounused` + entry.nounused_dep |= nounused_dep; + // If any flag is missing `noprelude`, then add to the prelude. + entry.add_prelude |= add_prelude; + } + Externs(externs) +} + +fn parse_remap_path_prefix( + matches: &getopts::Matches, + unstable_opts: &UnstableOptions, + error_format: ErrorOutputType, +) -> Vec<(PathBuf, PathBuf)> { + let mut mapping: Vec<(PathBuf, PathBuf)> = matches + .opt_strs("remap-path-prefix") + .into_iter() + .map(|remap| match remap.rsplit_once('=') { + None => early_error( + error_format, + "--remap-path-prefix must contain '=' between FROM and TO", + ), + Some((from, to)) => (PathBuf::from(from), PathBuf::from(to)), + }) + .collect(); + match &unstable_opts.remap_cwd_prefix { + Some(to) => match std::env::current_dir() { + Ok(cwd) => mapping.push((cwd, to.clone())), + Err(_) => (), + }, + None => (), + }; + mapping +} + +// JUSTIFICATION: before wrapper fn is available +#[cfg_attr(not(bootstrap), allow(rustc::bad_opt_access))] +pub fn build_session_options(matches: &getopts::Matches) -> Options { + let color = parse_color(matches); + + let edition = parse_crate_edition(matches); + + let JsonConfig { + json_rendered, + json_artifact_notifications, + json_unused_externs, + json_future_incompat, + } = parse_json(matches); + + let error_format = parse_error_format(matches, color, json_rendered); + + let diagnostic_width = matches.opt_get("diagnostic-width").unwrap_or_else(|_| { + early_error(error_format, "`--diagnostic-width` must be an positive integer"); + }); + + let unparsed_crate_types = matches.opt_strs("crate-type"); + let crate_types = parse_crate_types_from_list(unparsed_crate_types) + .unwrap_or_else(|e| early_error(error_format, &e)); + + let mut unstable_opts = UnstableOptions::build(matches, error_format); + let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(matches, error_format); + + check_error_format_stability(&unstable_opts, error_format, json_rendered); + + if !unstable_opts.unstable_options && json_unused_externs.is_enabled() { + early_error( + error_format, + "the `-Z unstable-options` flag must also be passed to enable \ + the flag `--json=unused-externs`", + ); + } + + let output_types = parse_output_types(&unstable_opts, matches, error_format); + + let mut cg = CodegenOptions::build(matches, error_format); + let (disable_thinlto, mut codegen_units) = should_override_cgus_and_disable_thinlto( + &output_types, + matches, + error_format, + cg.codegen_units, + ); + + check_thread_count(&unstable_opts, error_format); + + let incremental = cg.incremental.as_ref().map(PathBuf::from); + + let assert_incr_state = parse_assert_incr_state(&unstable_opts.assert_incr_state, error_format); + + if unstable_opts.profile && incremental.is_some() { + early_error( + error_format, + "can't instrument with gcov profiling when compiling incrementally", + ); + } + if unstable_opts.profile { + match codegen_units { + Some(1) => {} + None => codegen_units = Some(1), + Some(_) => early_error( + error_format, + "can't instrument with gcov profiling with multiple codegen units", + ), + } + } + + if cg.profile_generate.enabled() && cg.profile_use.is_some() { + early_error( + error_format, + "options `-C profile-generate` and `-C profile-use` are exclusive", + ); + } + + if unstable_opts.profile_sample_use.is_some() + && (cg.profile_generate.enabled() || cg.profile_use.is_some()) + { + early_error( + error_format, + "option `-Z profile-sample-use` cannot be used with `-C profile-generate` or `-C profile-use`", + ); + } + + // Handle both `-Z symbol-mangling-version` and `-C symbol-mangling-version`; the latter takes + // precedence. + match (cg.symbol_mangling_version, unstable_opts.symbol_mangling_version) { + (Some(smv_c), Some(smv_z)) if smv_c != smv_z => { + early_error( + error_format, + "incompatible values passed for `-C symbol-mangling-version` \ + and `-Z symbol-mangling-version`", + ); + } + (Some(SymbolManglingVersion::V0), _) => {} + (Some(_), _) if !unstable_opts.unstable_options => { + early_error( + error_format, + "`-C symbol-mangling-version=legacy` requires `-Z unstable-options`", + ); + } + (None, None) => {} + (None, smv) => { + early_warn( + error_format, + "`-Z symbol-mangling-version` is deprecated; use `-C symbol-mangling-version`", + ); + cg.symbol_mangling_version = smv; + } + _ => {} + } + + // Handle both `-Z instrument-coverage` and `-C instrument-coverage`; the latter takes + // precedence. + match (cg.instrument_coverage, unstable_opts.instrument_coverage) { + (Some(ic_c), Some(ic_z)) if ic_c != ic_z => { + early_error( + error_format, + "incompatible values passed for `-C instrument-coverage` \ + and `-Z instrument-coverage`", + ); + } + (Some(InstrumentCoverage::Off | InstrumentCoverage::All), _) => {} + (Some(_), _) if !unstable_opts.unstable_options => { + early_error( + error_format, + "`-C instrument-coverage=except-*` requires `-Z unstable-options`", + ); + } + (None, None) => {} + (None, ic) => { + early_warn( + error_format, + "`-Z instrument-coverage` is deprecated; use `-C instrument-coverage`", + ); + cg.instrument_coverage = ic; + } + _ => {} + } + + if cg.instrument_coverage.is_some() && cg.instrument_coverage != Some(InstrumentCoverage::Off) { + if cg.profile_generate.enabled() || cg.profile_use.is_some() { + early_error( + error_format, + "option `-C instrument-coverage` is not compatible with either `-C profile-use` \ + or `-C profile-generate`", + ); + } + + // `-C instrument-coverage` implies `-C symbol-mangling-version=v0` - to ensure consistent + // and reversible name mangling. Note, LLVM coverage tools can analyze coverage over + // multiple runs, including some changes to source code; so mangled names must be consistent + // across compilations. + match cg.symbol_mangling_version { + None => cg.symbol_mangling_version = Some(SymbolManglingVersion::V0), + Some(SymbolManglingVersion::Legacy) => { + early_warn( + error_format, + "-C instrument-coverage requires symbol mangling version `v0`, \ + but `-C symbol-mangling-version=legacy` was specified", + ); + } + Some(SymbolManglingVersion::V0) => {} + } + } + + if let Ok(graphviz_font) = std::env::var("RUSTC_GRAPHVIZ_FONT") { + unstable_opts.graphviz_font = graphviz_font; + } + + if !cg.embed_bitcode { + match cg.lto { + LtoCli::No | LtoCli::Unspecified => {} + LtoCli::Yes | LtoCli::NoParam | LtoCli::Thin | LtoCli::Fat => early_error( + error_format, + "options `-C embed-bitcode=no` and `-C lto` are incompatible", + ), + } + } + + if cg.linker_flavor == Some(LinkerFlavor::L4Bender) + && !nightly_options::is_unstable_enabled(matches) + { + early_error( + error_format, + "`l4-bender` linker flavor is unstable, `-Z unstable-options` \ + flag must also be passed to explicitly use it", + ); + } + + let prints = collect_print_requests(&mut cg, &mut unstable_opts, matches, error_format); + + let cg = cg; + + let sysroot_opt = matches.opt_str("sysroot").map(|m| PathBuf::from(&m)); + let target_triple = parse_target_triple(matches, error_format); + let opt_level = parse_opt_level(matches, &cg, error_format); + // The `-g` and `-C debuginfo` flags specify the same setting, so we want to be able + // to use them interchangeably. See the note above (regarding `-O` and `-C opt-level`) + // for more details. + let debug_assertions = cg.debug_assertions.unwrap_or(opt_level == OptLevel::No); + let debuginfo = select_debuginfo(matches, &cg, error_format); + + let mut search_paths = vec![]; + for s in &matches.opt_strs("L") { + search_paths.push(SearchPath::from_cli_opt(&s, error_format)); + } + + let libs = parse_libs(matches, error_format); + + let test = matches.opt_present("test"); + + if !cg.remark.is_empty() && debuginfo == DebugInfo::None { + early_warn(error_format, "-C remark requires \"-C debuginfo=n\" to show source locations"); + } + + let externs = parse_externs(matches, &unstable_opts, error_format); + + let crate_name = matches.opt_str("crate-name"); + + let remap_path_prefix = parse_remap_path_prefix(matches, &unstable_opts, error_format); + + let pretty = parse_pretty(&unstable_opts, error_format); + + if !unstable_opts.unstable_options + && !target_triple.triple().contains("apple") + && cg.split_debuginfo.is_some() + { + early_error(error_format, "`-Csplit-debuginfo` is unstable on this platform"); + } + + // Try to find a directory containing the Rust `src`, for more details see + // the doc comment on the `real_rust_source_base_dir` field. + let tmp_buf; + let sysroot = match &sysroot_opt { + Some(s) => s, + None => { + tmp_buf = crate::filesearch::get_or_default_sysroot(); + &tmp_buf + } + }; + let real_rust_source_base_dir = { + // This is the location used by the `rust-src` `rustup` component. + let mut candidate = sysroot.join("lib/rustlib/src/rust"); + if let Ok(metadata) = candidate.symlink_metadata() { + // Replace the symlink rustbuild creates, with its destination. + // We could try to use `fs::canonicalize` instead, but that might + // produce unnecessarily verbose path. + if metadata.file_type().is_symlink() { + if let Ok(symlink_dest) = std::fs::read_link(&candidate) { + candidate = symlink_dest; + } + } + } + + // Only use this directory if it has a file we can expect to always find. + if candidate.join("library/std/src/lib.rs").is_file() { Some(candidate) } else { None } + }; + + let working_dir = std::env::current_dir().unwrap_or_else(|e| { + early_error(error_format, &format!("Current directory is invalid: {e}")); + }); + + let (path, remapped) = + FilePathMapping::new(remap_path_prefix.clone()).map_prefix(working_dir.clone()); + let working_dir = if remapped { + RealFileName::Remapped { local_path: Some(working_dir), virtual_name: path } + } else { + RealFileName::LocalPath(path) + }; + + Options { + assert_incr_state, + crate_types, + optimize: opt_level, + debuginfo, + lint_opts, + lint_cap, + describe_lints, + output_types, + search_paths, + maybe_sysroot: sysroot_opt, + target_triple, + test, + incremental, + unstable_opts, + prints, + cg, + error_format, + diagnostic_width, + externs, + unstable_features: UnstableFeatures::from_environment(crate_name.as_deref()), + crate_name, + libs, + debug_assertions, + actually_rustdoc: false, + trimmed_def_paths: TrimmedDefPaths::default(), + cli_forced_codegen_units: codegen_units, + cli_forced_thinlto_off: disable_thinlto, + remap_path_prefix, + real_rust_source_base_dir, + edition, + json_artifact_notifications, + json_unused_externs, + json_future_incompat, + pretty, + working_dir, + } +} + +fn parse_pretty(unstable_opts: &UnstableOptions, efmt: ErrorOutputType) -> Option { + use PpMode::*; + + let first = match unstable_opts.unpretty.as_deref()? { + "normal" => Source(PpSourceMode::Normal), + "identified" => Source(PpSourceMode::Identified), + "expanded" => Source(PpSourceMode::Expanded), + "expanded,identified" => Source(PpSourceMode::ExpandedIdentified), + "expanded,hygiene" => Source(PpSourceMode::ExpandedHygiene), + "ast-tree" => AstTree(PpAstTreeMode::Normal), + "ast-tree,expanded" => AstTree(PpAstTreeMode::Expanded), + "hir" => Hir(PpHirMode::Normal), + "hir,identified" => Hir(PpHirMode::Identified), + "hir,typed" => Hir(PpHirMode::Typed), + "hir-tree" => HirTree, + "thir-tree" => ThirTree, + "mir" => Mir, + "mir-cfg" => MirCFG, + name => early_error( + efmt, + &format!( + "argument to `unpretty` must be one of `normal`, `identified`, \ + `expanded`, `expanded,identified`, `expanded,hygiene`, \ + `ast-tree`, `ast-tree,expanded`, `hir`, `hir,identified`, \ + `hir,typed`, `hir-tree`, `thir-tree`, `mir` or `mir-cfg`; got {name}" + ), + ), + }; + tracing::debug!("got unpretty option: {first:?}"); + Some(first) +} + +pub fn make_crate_type_option() -> RustcOptGroup { + opt::multi_s( + "", + "crate-type", + "Comma separated list of types of crates + for the compiler to emit", + "[bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]", + ) +} + +pub fn parse_crate_types_from_list(list_list: Vec) -> Result, String> { + let mut crate_types: Vec = Vec::new(); + for unparsed_crate_type in &list_list { + for part in unparsed_crate_type.split(',') { + let new_part = match part { + "lib" => default_lib_output(), + "rlib" => CrateType::Rlib, + "staticlib" => CrateType::Staticlib, + "dylib" => CrateType::Dylib, + "cdylib" => CrateType::Cdylib, + "bin" => CrateType::Executable, + "proc-macro" => CrateType::ProcMacro, + _ => return Err(format!("unknown crate type: `{part}`")), + }; + if !crate_types.contains(&new_part) { + crate_types.push(new_part) + } + } + } + + Ok(crate_types) +} + +pub mod nightly_options { + use super::{ErrorOutputType, OptionStability, RustcOptGroup}; + use crate::early_error; + use rustc_feature::UnstableFeatures; + + pub fn is_unstable_enabled(matches: &getopts::Matches) -> bool { + match_is_nightly_build(matches) + && matches.opt_strs("Z").iter().any(|x| *x == "unstable-options") + } + + pub fn match_is_nightly_build(matches: &getopts::Matches) -> bool { + is_nightly_build(matches.opt_str("crate-name").as_deref()) + } + + pub fn is_nightly_build(krate: Option<&str>) -> bool { + UnstableFeatures::from_environment(krate).is_nightly_build() + } + + pub fn check_nightly_options(matches: &getopts::Matches, flags: &[RustcOptGroup]) { + let has_z_unstable_option = matches.opt_strs("Z").iter().any(|x| *x == "unstable-options"); + let really_allows_unstable_options = match_is_nightly_build(matches); + + for opt in flags.iter() { + if opt.stability == OptionStability::Stable { + continue; + } + if !matches.opt_present(opt.name) { + continue; + } + if opt.name != "Z" && !has_z_unstable_option { + early_error( + ErrorOutputType::default(), + &format!( + "the `-Z unstable-options` flag must also be passed to enable \ + the flag `{}`", + opt.name + ), + ); + } + if really_allows_unstable_options { + continue; + } + match opt.stability { + OptionStability::Unstable => { + let msg = format!( + "the option `{}` is only accepted on the \ + nightly compiler", + opt.name + ); + early_error(ErrorOutputType::default(), &msg); + } + OptionStability::Stable => {} + } + } + } +} + +impl fmt::Display for CrateType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + CrateType::Executable => "bin".fmt(f), + CrateType::Dylib => "dylib".fmt(f), + CrateType::Rlib => "rlib".fmt(f), + CrateType::Staticlib => "staticlib".fmt(f), + CrateType::Cdylib => "cdylib".fmt(f), + CrateType::ProcMacro => "proc-macro".fmt(f), + } + } +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum PpSourceMode { + /// `-Zunpretty=normal` + Normal, + /// `-Zunpretty=expanded` + Expanded, + /// `-Zunpretty=identified` + Identified, + /// `-Zunpretty=expanded,identified` + ExpandedIdentified, + /// `-Zunpretty=expanded,hygiene` + ExpandedHygiene, +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum PpAstTreeMode { + /// `-Zunpretty=ast` + Normal, + /// `-Zunpretty=ast,expanded` + Expanded, +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum PpHirMode { + /// `-Zunpretty=hir` + Normal, + /// `-Zunpretty=hir,identified` + Identified, + /// `-Zunpretty=hir,typed` + Typed, +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum PpMode { + /// Options that print the source code, i.e. + /// `-Zunpretty=normal` and `-Zunpretty=expanded` + Source(PpSourceMode), + AstTree(PpAstTreeMode), + /// Options that print the HIR, i.e. `-Zunpretty=hir` + Hir(PpHirMode), + /// `-Zunpretty=hir-tree` + HirTree, + /// `-Zunpretty=thir-tree` + ThirTree, + /// `-Zunpretty=mir` + Mir, + /// `-Zunpretty=mir-cfg` + MirCFG, +} + +impl PpMode { + pub fn needs_ast_map(&self) -> bool { + use PpMode::*; + use PpSourceMode::*; + match *self { + Source(Normal | Identified) | AstTree(PpAstTreeMode::Normal) => false, + + Source(Expanded | ExpandedIdentified | ExpandedHygiene) + | AstTree(PpAstTreeMode::Expanded) + | Hir(_) + | HirTree + | ThirTree + | Mir + | MirCFG => true, + } + } + pub fn needs_hir(&self) -> bool { + use PpMode::*; + match *self { + Source(_) | AstTree(_) => false, + + Hir(_) | HirTree | ThirTree | Mir | MirCFG => true, + } + } + + pub fn needs_analysis(&self) -> bool { + use PpMode::*; + matches!(*self, Mir | MirCFG | ThirTree) + } +} + +/// Command-line arguments passed to the compiler have to be incorporated with +/// the dependency tracking system for incremental compilation. This module +/// provides some utilities to make this more convenient. +/// +/// The values of all command-line arguments that are relevant for dependency +/// tracking are hashed into a single value that determines whether the +/// incremental compilation cache can be re-used or not. This hashing is done +/// via the `DepTrackingHash` trait defined below, since the standard `Hash` +/// implementation might not be suitable (e.g., arguments are stored in a `Vec`, +/// the hash of which is order dependent, but we might not want the order of +/// arguments to make a difference for the hash). +/// +/// However, since the value provided by `Hash::hash` often *is* suitable, +/// especially for primitive types, there is the +/// `impl_dep_tracking_hash_via_hash!()` macro that allows to simply reuse the +/// `Hash` implementation for `DepTrackingHash`. It's important though that +/// we have an opt-in scheme here, so one is hopefully forced to think about +/// how the hash should be calculated when adding a new command-line argument. +pub(crate) mod dep_tracking { + use super::{ + BranchProtection, CFGuard, CFProtection, CrateType, DebugInfo, ErrorOutputType, + InstrumentCoverage, LdImpl, LinkerPluginLto, LocationDetail, LtoCli, OomStrategy, OptLevel, + OutputType, OutputTypes, Passes, SourceFileHashAlgorithm, SplitDwarfKind, + SwitchWithOptPath, SymbolManglingVersion, TrimmedDefPaths, + }; + use crate::lint; + use crate::options::WasiExecModel; + use crate::utils::{NativeLib, NativeLibKind}; + use rustc_errors::LanguageIdentifier; + use rustc_feature::UnstableFeatures; + use rustc_span::edition::Edition; + use rustc_span::RealFileName; + use rustc_target::spec::{CodeModel, MergeFunctions, PanicStrategy, RelocModel}; + use rustc_target::spec::{ + RelroLevel, SanitizerSet, SplitDebuginfo, StackProtector, TargetTriple, TlsModel, + }; + use std::collections::hash_map::DefaultHasher; + use std::collections::BTreeMap; + use std::hash::Hash; + use std::num::NonZeroUsize; + use std::path::PathBuf; + + pub trait DepTrackingHash { + fn hash( + &self, + hasher: &mut DefaultHasher, + error_format: ErrorOutputType, + for_crate_hash: bool, + ); + } + + macro_rules! impl_dep_tracking_hash_via_hash { + ($($t:ty),+ $(,)?) => {$( + impl DepTrackingHash for $t { + fn hash(&self, hasher: &mut DefaultHasher, _: ErrorOutputType, _for_crate_hash: bool) { + Hash::hash(self, hasher); + } + } + )+}; + } + + impl DepTrackingHash for Option { + fn hash( + &self, + hasher: &mut DefaultHasher, + error_format: ErrorOutputType, + for_crate_hash: bool, + ) { + match self { + Some(x) => { + Hash::hash(&1, hasher); + DepTrackingHash::hash(x, hasher, error_format, for_crate_hash); + } + None => Hash::hash(&0, hasher), + } + } + } + + impl_dep_tracking_hash_via_hash!( + bool, + usize, + NonZeroUsize, + u64, + String, + PathBuf, + lint::Level, + WasiExecModel, + u32, + RelocModel, + CodeModel, + TlsModel, + InstrumentCoverage, + CrateType, + MergeFunctions, + PanicStrategy, + RelroLevel, + Passes, + OptLevel, + LtoCli, + DebugInfo, + UnstableFeatures, + NativeLib, + NativeLibKind, + SanitizerSet, + CFGuard, + CFProtection, + TargetTriple, + Edition, + LinkerPluginLto, + SplitDebuginfo, + SplitDwarfKind, + StackProtector, + SwitchWithOptPath, + SymbolManglingVersion, + SourceFileHashAlgorithm, + TrimmedDefPaths, + Option, + OutputType, + RealFileName, + LocationDetail, + BranchProtection, + OomStrategy, + LanguageIdentifier, + ); + + impl DepTrackingHash for (T1, T2) + where + T1: DepTrackingHash, + T2: DepTrackingHash, + { + fn hash( + &self, + hasher: &mut DefaultHasher, + error_format: ErrorOutputType, + for_crate_hash: bool, + ) { + Hash::hash(&0, hasher); + DepTrackingHash::hash(&self.0, hasher, error_format, for_crate_hash); + Hash::hash(&1, hasher); + DepTrackingHash::hash(&self.1, hasher, error_format, for_crate_hash); + } + } + + impl DepTrackingHash for (T1, T2, T3) + where + T1: DepTrackingHash, + T2: DepTrackingHash, + T3: DepTrackingHash, + { + fn hash( + &self, + hasher: &mut DefaultHasher, + error_format: ErrorOutputType, + for_crate_hash: bool, + ) { + Hash::hash(&0, hasher); + DepTrackingHash::hash(&self.0, hasher, error_format, for_crate_hash); + Hash::hash(&1, hasher); + DepTrackingHash::hash(&self.1, hasher, error_format, for_crate_hash); + Hash::hash(&2, hasher); + DepTrackingHash::hash(&self.2, hasher, error_format, for_crate_hash); + } + } + + impl DepTrackingHash for Vec { + fn hash( + &self, + hasher: &mut DefaultHasher, + error_format: ErrorOutputType, + for_crate_hash: bool, + ) { + Hash::hash(&self.len(), hasher); + for (index, elem) in self.iter().enumerate() { + Hash::hash(&index, hasher); + DepTrackingHash::hash(elem, hasher, error_format, for_crate_hash); + } + } + } + + impl DepTrackingHash for OutputTypes { + fn hash( + &self, + hasher: &mut DefaultHasher, + error_format: ErrorOutputType, + for_crate_hash: bool, + ) { + Hash::hash(&self.0.len(), hasher); + for (key, val) in &self.0 { + DepTrackingHash::hash(key, hasher, error_format, for_crate_hash); + if !for_crate_hash { + DepTrackingHash::hash(val, hasher, error_format, for_crate_hash); + } + } + } + } + + // This is a stable hash because BTreeMap is a sorted container + pub(crate) fn stable_hash( + sub_hashes: BTreeMap<&'static str, &dyn DepTrackingHash>, + hasher: &mut DefaultHasher, + error_format: ErrorOutputType, + for_crate_hash: bool, + ) { + for (key, sub_hash) in sub_hashes { + // Using Hash::hash() instead of DepTrackingHash::hash() is fine for + // the keys, as they are just plain strings + Hash::hash(&key.len(), hasher); + Hash::hash(key, hasher); + sub_hash.hash(hasher, error_format, for_crate_hash); + } + } +} + +/// Default behavior to use in out-of-memory situations. +#[derive(Clone, Copy, PartialEq, Hash, Debug, Encodable, Decodable, HashStable_Generic)] +pub enum OomStrategy { + /// Generate a panic that can be caught by `catch_unwind`. + Panic, + + /// Abort the process immediately. + Abort, +} + +impl OomStrategy { + pub const SYMBOL: &'static str = "__rust_alloc_error_handler_should_panic"; + + pub fn should_panic(self) -> u8 { + match self { + OomStrategy::Panic => 1, + OomStrategy::Abort => 0, + } + } +} + +/// How to run proc-macro code when building this crate +#[derive(Clone, Copy, PartialEq, Hash, Debug)] +pub enum ProcMacroExecutionStrategy { + /// Run the proc-macro code on the same thread as the server. + SameThread, + + /// Run the proc-macro code on a different thread. + CrossThread, +} diff --git a/compiler/rustc_session/src/cstore.rs b/compiler/rustc_session/src/cstore.rs new file mode 100644 index 000000000..c1fd3c7c6 --- /dev/null +++ b/compiler/rustc_session/src/cstore.rs @@ -0,0 +1,218 @@ +//! the rustc crate store interface. This also includes types that +//! are *mostly* used as a part of that interface, but these should +//! probably get a better home if someone can find one. + +use crate::search_paths::PathKind; +use crate::utils::NativeLibKind; +use crate::Session; +use rustc_ast as ast; +use rustc_data_structures::sync::{self, MetadataRef}; +use rustc_hir::def_id::{CrateNum, DefId, StableCrateId, LOCAL_CRATE}; +use rustc_hir::definitions::{DefKey, DefPath, DefPathHash}; +use rustc_span::hygiene::{ExpnHash, ExpnId}; +use rustc_span::symbol::Symbol; +use rustc_span::Span; +use rustc_target::spec::Target; + +use std::any::Any; +use std::path::{Path, PathBuf}; + +// lonely orphan structs and enums looking for a better home + +/// Where a crate came from on the local filesystem. One of these three options +/// must be non-None. +#[derive(PartialEq, Clone, Debug, HashStable_Generic, Encodable, Decodable)] +pub struct CrateSource { + pub dylib: Option<(PathBuf, PathKind)>, + pub rlib: Option<(PathBuf, PathKind)>, + pub rmeta: Option<(PathBuf, PathKind)>, +} + +impl CrateSource { + #[inline] + pub fn paths(&self) -> impl Iterator { + self.dylib.iter().chain(self.rlib.iter()).chain(self.rmeta.iter()).map(|p| &p.0) + } +} + +#[derive(Encodable, Decodable, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Debug)] +#[derive(HashStable_Generic)] +pub enum CrateDepKind { + /// A dependency that is only used for its macros. + MacrosOnly, + /// A dependency that is always injected into the dependency list and so + /// doesn't need to be linked to an rlib, e.g., the injected allocator. + Implicit, + /// A dependency that is required by an rlib version of this crate. + /// Ordinary `extern crate`s result in `Explicit` dependencies. + Explicit, +} + +impl CrateDepKind { + #[inline] + pub fn macros_only(self) -> bool { + match self { + CrateDepKind::MacrosOnly => true, + CrateDepKind::Implicit | CrateDepKind::Explicit => false, + } + } +} + +#[derive(Copy, Debug, PartialEq, Clone, Encodable, Decodable, HashStable_Generic)] +pub enum LinkagePreference { + RequireDynamic, + RequireStatic, +} + +#[derive(Debug, Encodable, Decodable, HashStable_Generic)] +pub struct NativeLib { + pub kind: NativeLibKind, + pub name: Option, + pub cfg: Option, + pub foreign_module: Option, + pub wasm_import_module: Option, + pub verbatim: Option, + pub dll_imports: Vec, +} + +impl NativeLib { + pub fn has_modifiers(&self) -> bool { + self.verbatim.is_some() || self.kind.has_modifiers() + } +} + +#[derive(Clone, Debug, Encodable, Decodable, HashStable_Generic)] +pub struct DllImport { + pub name: Symbol, + pub ordinal: Option, + /// Calling convention for the function. + /// + /// On x86_64, this is always `DllCallingConvention::C`; on i686, it can be any + /// of the values, and we use `DllCallingConvention::C` to represent `"cdecl"`. + pub calling_convention: DllCallingConvention, + /// Span of import's "extern" declaration; used for diagnostics. + pub span: Span, +} + +/// Calling convention for a function defined in an external library. +/// +/// The usize value, where present, indicates the size of the function's argument list +/// in bytes. +#[derive(Clone, PartialEq, Debug, Encodable, Decodable, HashStable_Generic)] +pub enum DllCallingConvention { + C, + Stdcall(usize), + Fastcall(usize), + Vectorcall(usize), +} + +#[derive(Clone, Encodable, Decodable, HashStable_Generic, Debug)] +pub struct ForeignModule { + pub foreign_items: Vec, + pub def_id: DefId, +} + +#[derive(Copy, Clone, Debug, HashStable_Generic)] +pub struct ExternCrate { + pub src: ExternCrateSource, + + /// span of the extern crate that caused this to be loaded + pub span: Span, + + /// Number of links to reach the extern; + /// used to select the extern with the shortest path + pub path_len: usize, + + /// Crate that depends on this crate + pub dependency_of: CrateNum, +} + +impl ExternCrate { + /// If true, then this crate is the crate named by the extern + /// crate referenced above. If false, then this crate is a dep + /// of the crate. + #[inline] + pub fn is_direct(&self) -> bool { + self.dependency_of == LOCAL_CRATE + } + + #[inline] + pub fn rank(&self) -> impl PartialOrd { + // Prefer: + // - direct extern crate to indirect + // - shorter paths to longer + (self.is_direct(), !self.path_len) + } +} + +#[derive(Copy, Clone, Debug, HashStable_Generic)] +pub enum ExternCrateSource { + /// Crate is loaded by `extern crate`. + Extern( + /// def_id of the item in the current crate that caused + /// this crate to be loaded; note that there could be multiple + /// such ids + DefId, + ), + /// Crate is implicitly loaded by a path resolving through extern prelude. + Path, +} + +/// The backend's way to give the crate store access to the metadata in a library. +/// Note that it returns the raw metadata bytes stored in the library file, whether +/// it is compressed, uncompressed, some weird mix, etc. +/// rmeta files are backend independent and not handled here. +/// +/// At the time of this writing, there is only one backend and one way to store +/// metadata in library -- this trait just serves to decouple rustc_metadata from +/// the archive reader, which depends on LLVM. +pub trait MetadataLoader { + fn get_rlib_metadata(&self, target: &Target, filename: &Path) -> Result; + fn get_dylib_metadata(&self, target: &Target, filename: &Path) -> Result; +} + +pub type MetadataLoaderDyn = dyn MetadataLoader + Sync; + +/// A store of Rust crates, through which their metadata can be accessed. +/// +/// Note that this trait should probably not be expanding today. All new +/// functionality should be driven through queries instead! +/// +/// If you find a method on this trait named `{name}_untracked` it signifies +/// that it's *not* tracked for dependency information throughout compilation +/// (it'd break incremental compilation) and should only be called pre-HIR (e.g. +/// during resolve) +pub trait CrateStore: std::fmt::Debug { + fn as_any(&self) -> &dyn Any; + + // Foreign definitions. + // This information is safe to access, since it's hashed as part of the DefPathHash, which incr. + // comp. uses to identify a DefId. + fn def_key(&self, def: DefId) -> DefKey; + fn def_path(&self, def: DefId) -> DefPath; + fn def_path_hash(&self, def: DefId) -> DefPathHash; + + // This information is safe to access, since it's hashed as part of the StableCrateId, which + // incr. comp. uses to identify a CrateNum. + fn crate_name(&self, cnum: CrateNum) -> Symbol; + fn stable_crate_id(&self, cnum: CrateNum) -> StableCrateId; + fn stable_crate_id_to_crate_num(&self, stable_crate_id: StableCrateId) -> CrateNum; + + /// Fetch a DefId from a DefPathHash for a foreign crate. + fn def_path_hash_to_def_id(&self, cnum: CrateNum, hash: DefPathHash) -> DefId; + fn expn_hash_to_expn_id( + &self, + sess: &Session, + cnum: CrateNum, + index_guess: u32, + hash: ExpnHash, + ) -> ExpnId; + + /// Imports all `SourceFile`s from the given crate into the current session. + /// This normally happens automatically when we decode a `Span` from + /// that crate's metadata - however, the incr comp cache needs + /// to trigger this manually when decoding a foreign `Span` + fn import_source_files(&self, sess: &Session, cnum: CrateNum); +} + +pub type CrateStoreDyn = dyn CrateStore + sync::Sync; diff --git a/compiler/rustc_session/src/filesearch.rs b/compiler/rustc_session/src/filesearch.rs new file mode 100644 index 000000000..c973e3140 --- /dev/null +++ b/compiler/rustc_session/src/filesearch.rs @@ -0,0 +1,125 @@ +//! A module for searching for libraries + +use std::env; +use std::fs; +use std::iter::FromIterator; +use std::path::{Path, PathBuf}; + +use crate::search_paths::{PathKind, SearchPath}; +use rustc_fs_util::fix_windows_verbatim_for_gcc; +use tracing::debug; + +#[derive(Copy, Clone)] +pub enum FileMatch { + FileMatches, + FileDoesntMatch, +} + +#[derive(Clone)] +pub struct FileSearch<'a> { + sysroot: &'a Path, + triple: &'a str, + search_paths: &'a [SearchPath], + tlib_path: &'a SearchPath, + kind: PathKind, +} + +impl<'a> FileSearch<'a> { + pub fn search_paths(&self) -> impl Iterator { + let kind = self.kind; + self.search_paths + .iter() + .filter(move |sp| sp.kind.matches(kind)) + .chain(std::iter::once(self.tlib_path)) + } + + pub fn get_lib_path(&self) -> PathBuf { + make_target_lib_path(self.sysroot, self.triple) + } + + pub fn get_self_contained_lib_path(&self) -> PathBuf { + self.get_lib_path().join("self-contained") + } + + pub fn new( + sysroot: &'a Path, + triple: &'a str, + search_paths: &'a [SearchPath], + tlib_path: &'a SearchPath, + kind: PathKind, + ) -> FileSearch<'a> { + debug!("using sysroot = {}, triple = {}", sysroot.display(), triple); + FileSearch { sysroot, triple, search_paths, tlib_path, kind } + } + + /// Returns just the directories within the search paths. + pub fn search_path_dirs(&self) -> Vec { + self.search_paths().map(|sp| sp.dir.to_path_buf()).collect() + } +} + +pub fn make_target_lib_path(sysroot: &Path, target_triple: &str) -> PathBuf { + let rustlib_path = rustc_target::target_rustlib_path(sysroot, target_triple); + PathBuf::from_iter([sysroot, Path::new(&rustlib_path), Path::new("lib")]) +} + +/// This function checks if sysroot is found using env::args().next(), and if it +/// is not found, uses env::current_exe() to imply sysroot. +pub fn get_or_default_sysroot() -> PathBuf { + // Follow symlinks. If the resolved path is relative, make it absolute. + fn canonicalize(path: PathBuf) -> PathBuf { + let path = fs::canonicalize(&path).unwrap_or(path); + // See comments on this target function, but the gist is that + // gcc chokes on verbatim paths which fs::canonicalize generates + // so we try to avoid those kinds of paths. + fix_windows_verbatim_for_gcc(&path) + } + + // Use env::current_exe() to get the path of the executable following + // symlinks/canonicalizing components. + fn from_current_exe() -> PathBuf { + match env::current_exe() { + Ok(exe) => { + let mut p = canonicalize(exe); + p.pop(); + p.pop(); + p + } + Err(e) => panic!("failed to get current_exe: {e}"), + } + } + + // Use env::args().next() to get the path of the executable without + // following symlinks/canonicalizing any component. This makes the rustc + // binary able to locate Rust libraries in systems using content-addressable + // storage (CAS). + fn from_env_args_next() -> Option { + match env::args_os().next() { + Some(first_arg) => { + let mut p = PathBuf::from(first_arg); + + // Check if sysroot is found using env::args().next() only if the rustc in argv[0] + // is a symlink (see #79253). We might want to change/remove it to conform with + // https://www.gnu.org/prep/standards/standards.html#Finding-Program-Files in the + // future. + if fs::read_link(&p).is_err() { + // Path is not a symbolic link or does not exist. + return None; + } + + // Pop off `bin/rustc`, obtaining the suspected sysroot. + p.pop(); + p.pop(); + // Look for the target rustlib directory in the suspected sysroot. + let mut rustlib_path = rustc_target::target_rustlib_path(&p, "dummy"); + rustlib_path.pop(); // pop off the dummy target. + if rustlib_path.exists() { Some(p) } else { None } + } + None => None, + } + } + + // Check if sysroot is found using env::args().next(), and if is not found, + // use env::current_exe() to imply sysroot. + from_env_args_next().unwrap_or_else(from_current_exe) +} diff --git a/compiler/rustc_session/src/lib.rs b/compiler/rustc_session/src/lib.rs new file mode 100644 index 000000000..7353c1ca0 --- /dev/null +++ b/compiler/rustc_session/src/lib.rs @@ -0,0 +1,40 @@ +#![feature(if_let_guard)] +#![feature(let_chains)] +#![feature(let_else)] +#![feature(min_specialization)] +#![feature(never_type)] +#![feature(once_cell)] +#![feature(option_get_or_insert_default)] +#![feature(rustc_attrs)] +#![feature(map_many_mut)] +#![recursion_limit = "256"] +#![allow(rustc::potential_query_instability)] + +#[macro_use] +extern crate rustc_macros; + +pub mod cgu_reuse_tracker; +pub mod utils; +pub use lint::{declare_lint, declare_lint_pass, declare_tool_lint, impl_lint_pass}; +pub use rustc_lint_defs as lint; +pub mod parse; + +mod code_stats; +#[macro_use] +pub mod config; +pub mod cstore; +pub mod filesearch; +mod options; +pub mod search_paths; + +mod session; +pub use session::*; + +pub mod output; + +pub use getopts; + +/// Requirements for a `StableHashingContext` to be used in this crate. +/// This is a hack to allow using the `HashStable_Generic` derive macro +/// instead of implementing everything in `rustc_middle`. +pub trait HashStableContext: rustc_ast::HashStableContext + rustc_hir::HashStableContext {} diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs new file mode 100644 index 000000000..1827f1c20 --- /dev/null +++ b/compiler/rustc_session/src/options.rs @@ -0,0 +1,1673 @@ +use crate::config::*; + +use crate::early_error; +use crate::lint; +use crate::search_paths::SearchPath; +use crate::utils::NativeLib; +use rustc_errors::LanguageIdentifier; +use rustc_target::spec::{CodeModel, LinkerFlavor, MergeFunctions, PanicStrategy, SanitizerSet}; +use rustc_target::spec::{ + RelocModel, RelroLevel, SplitDebuginfo, StackProtector, TargetTriple, TlsModel, +}; + +use rustc_feature::UnstableFeatures; +use rustc_span::edition::Edition; +use rustc_span::RealFileName; +use rustc_span::SourceFileHashAlgorithm; + +use std::collections::BTreeMap; + +use std::collections::hash_map::DefaultHasher; +use std::hash::Hasher; +use std::num::NonZeroUsize; +use std::path::PathBuf; +use std::str; + +macro_rules! insert { + ($opt_name:ident, $opt_expr:expr, $sub_hashes:expr) => { + if $sub_hashes + .insert(stringify!($opt_name), $opt_expr as &dyn dep_tracking::DepTrackingHash) + .is_some() + { + panic!("duplicate key in CLI DepTrackingHash: {}", stringify!($opt_name)) + } + }; +} + +macro_rules! hash_opt { + ($opt_name:ident, $opt_expr:expr, $sub_hashes:expr, $_for_crate_hash: ident, [UNTRACKED]) => {{}}; + ($opt_name:ident, $opt_expr:expr, $sub_hashes:expr, $_for_crate_hash: ident, [TRACKED]) => {{ insert!($opt_name, $opt_expr, $sub_hashes) }}; + ($opt_name:ident, $opt_expr:expr, $sub_hashes:expr, $for_crate_hash: ident, [TRACKED_NO_CRATE_HASH]) => {{ + if !$for_crate_hash { + insert!($opt_name, $opt_expr, $sub_hashes) + } + }}; + ($opt_name:ident, $opt_expr:expr, $sub_hashes:expr, $_for_crate_hash: ident, [SUBSTRUCT]) => {{}}; +} + +macro_rules! hash_substruct { + ($opt_name:ident, $opt_expr:expr, $error_format:expr, $for_crate_hash:expr, $hasher:expr, [UNTRACKED]) => {{}}; + ($opt_name:ident, $opt_expr:expr, $error_format:expr, $for_crate_hash:expr, $hasher:expr, [TRACKED]) => {{}}; + ($opt_name:ident, $opt_expr:expr, $error_format:expr, $for_crate_hash:expr, $hasher:expr, [TRACKED_NO_CRATE_HASH]) => {{}}; + ($opt_name:ident, $opt_expr:expr, $error_format:expr, $for_crate_hash:expr, $hasher:expr, [SUBSTRUCT]) => { + use crate::config::dep_tracking::DepTrackingHash; + $opt_expr.dep_tracking_hash($for_crate_hash, $error_format).hash( + $hasher, + $error_format, + $for_crate_hash, + ); + }; +} + +macro_rules! top_level_options { + ( $( #[$top_level_attr:meta] )* pub struct Options { $( + $( #[$attr:meta] )* + $opt:ident : $t:ty [$dep_tracking_marker:ident], + )* } ) => ( + #[derive(Clone)] + $( #[$top_level_attr] )* + pub struct Options { + $( + $( #[$attr] )* + pub $opt: $t + ),* + } + + impl Options { + pub fn dep_tracking_hash(&self, for_crate_hash: bool) -> u64 { + let mut sub_hashes = BTreeMap::new(); + $({ + hash_opt!($opt, + &self.$opt, + &mut sub_hashes, + for_crate_hash, + [$dep_tracking_marker]); + })* + let mut hasher = DefaultHasher::new(); + dep_tracking::stable_hash(sub_hashes, + &mut hasher, + self.error_format, + for_crate_hash); + $({ + hash_substruct!($opt, + &self.$opt, + self.error_format, + for_crate_hash, + &mut hasher, + [$dep_tracking_marker]); + })* + hasher.finish() + } + } + ); +} + +top_level_options!( + /// The top-level command-line options struct. + /// + /// For each option, one has to specify how it behaves with regard to the + /// dependency tracking system of incremental compilation. This is done via the + /// square-bracketed directive after the field type. The options are: + /// + /// - `[TRACKED]` + /// A change in the given field will cause the compiler to completely clear the + /// incremental compilation cache before proceeding. + /// + /// - `[TRACKED_NO_CRATE_HASH]` + /// Same as `[TRACKED]`, but will not affect the crate hash. This is useful for options that only + /// affect the incremental cache. + /// + /// - `[UNTRACKED]` + /// Incremental compilation is not influenced by this option. + /// + /// - `[SUBSTRUCT]` + /// Second-level sub-structs containing more options. + /// + /// If you add a new option to this struct or one of the sub-structs like + /// `CodegenOptions`, think about how it influences incremental compilation. If in + /// doubt, specify `[TRACKED]`, which is always "correct" but might lead to + /// unnecessary re-compilation. + #[cfg_attr(not(bootstrap), rustc_lint_opt_ty)] + pub struct Options { + /// The crate config requested for the session, which may be combined + /// with additional crate configurations during the compile process. + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::crate_types` instead of this field"))] + crate_types: Vec [TRACKED], + optimize: OptLevel [TRACKED], + /// Include the `debug_assertions` flag in dependency tracking, since it + /// can influence whether overflow checks are done or not. + debug_assertions: bool [TRACKED], + debuginfo: DebugInfo [TRACKED], + lint_opts: Vec<(String, lint::Level)> [TRACKED_NO_CRATE_HASH], + lint_cap: Option [TRACKED_NO_CRATE_HASH], + describe_lints: bool [UNTRACKED], + output_types: OutputTypes [TRACKED], + search_paths: Vec [UNTRACKED], + libs: Vec [TRACKED], + maybe_sysroot: Option [UNTRACKED], + + target_triple: TargetTriple [TRACKED], + + test: bool [TRACKED], + error_format: ErrorOutputType [UNTRACKED], + diagnostic_width: Option [UNTRACKED], + + /// If `Some`, enable incremental compilation, using the given + /// directory to store intermediate results. + incremental: Option [UNTRACKED], + assert_incr_state: Option [UNTRACKED], + + unstable_opts: UnstableOptions [SUBSTRUCT], + prints: Vec [UNTRACKED], + cg: CodegenOptions [SUBSTRUCT], + externs: Externs [UNTRACKED], + crate_name: Option [TRACKED], + /// Indicates how the compiler should treat unstable features. + unstable_features: UnstableFeatures [TRACKED], + + /// Indicates whether this run of the compiler is actually rustdoc. This + /// is currently just a hack and will be removed eventually, so please + /// try to not rely on this too much. + actually_rustdoc: bool [TRACKED], + + /// Control path trimming. + trimmed_def_paths: TrimmedDefPaths [TRACKED], + + /// Specifications of codegen units / ThinLTO which are forced as a + /// result of parsing command line options. These are not necessarily + /// what rustc was invoked with, but massaged a bit to agree with + /// commands like `--emit llvm-ir` which they're often incompatible with + /// if we otherwise use the defaults of rustc. + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::codegen_units` instead of this field"))] + cli_forced_codegen_units: Option [UNTRACKED], + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::lto` instead of this field"))] + cli_forced_thinlto_off: bool [UNTRACKED], + + /// Remap source path prefixes in all output (messages, object files, debug, etc.). + remap_path_prefix: Vec<(PathBuf, PathBuf)> [TRACKED_NO_CRATE_HASH], + /// Base directory containing the `src/` for the Rust standard library, and + /// potentially `rustc` as well, if we can can find it. Right now it's always + /// `$sysroot/lib/rustlib/src/rust` (i.e. the `rustup` `rust-src` component). + /// + /// This directory is what the virtual `/rustc/$hash` is translated back to, + /// if Rust was built with path remapping to `/rustc/$hash` enabled + /// (the `rust.remap-debuginfo` option in `config.toml`). + real_rust_source_base_dir: Option [TRACKED_NO_CRATE_HASH], + + edition: Edition [TRACKED], + + /// `true` if we're emitting JSON blobs about each artifact produced + /// by the compiler. + json_artifact_notifications: bool [TRACKED], + + /// `true` if we're emitting a JSON blob containing the unused externs + json_unused_externs: JsonUnusedExterns [UNTRACKED], + + /// `true` if we're emitting a JSON job containing a future-incompat report for lints + json_future_incompat: bool [TRACKED], + + pretty: Option [UNTRACKED], + + /// The (potentially remapped) working directory + working_dir: RealFileName [TRACKED], + } +); + +/// Defines all `CodegenOptions`/`DebuggingOptions` fields and parsers all at once. The goal of this +/// macro is to define an interface that can be programmatically used by the option parser +/// to initialize the struct without hardcoding field names all over the place. +/// +/// The goal is to invoke this macro once with the correct fields, and then this macro generates all +/// necessary code. The main gotcha of this macro is the `cgsetters` module which is a bunch of +/// generated code to parse an option into its respective field in the struct. There are a few +/// hand-written parsers for parsing specific types of values in this module. +macro_rules! options { + ($struct_name:ident, $stat:ident, $optmod:ident, $prefix:expr, $outputname:expr, + $($( #[$attr:meta] )* $opt:ident : $t:ty = ( + $init:expr, + $parse:ident, + [$dep_tracking_marker:ident], + $desc:expr) + ),* ,) => +( + #[derive(Clone)] + #[cfg_attr(not(bootstrap), rustc_lint_opt_ty)] + pub struct $struct_name { $( $( #[$attr] )* pub $opt: $t),* } + + impl Default for $struct_name { + fn default() -> $struct_name { + $struct_name { $($opt: $init),* } + } + } + + impl $struct_name { + pub fn build( + matches: &getopts::Matches, + error_format: ErrorOutputType, + ) -> $struct_name { + build_options(matches, $stat, $prefix, $outputname, error_format) + } + + fn dep_tracking_hash(&self, for_crate_hash: bool, error_format: ErrorOutputType) -> u64 { + let mut sub_hashes = BTreeMap::new(); + $({ + hash_opt!($opt, + &self.$opt, + &mut sub_hashes, + for_crate_hash, + [$dep_tracking_marker]); + })* + let mut hasher = DefaultHasher::new(); + dep_tracking::stable_hash(sub_hashes, + &mut hasher, + error_format, + for_crate_hash + ); + hasher.finish() + } + } + + pub const $stat: OptionDescrs<$struct_name> = + &[ $( (stringify!($opt), $optmod::$opt, desc::$parse, $desc) ),* ]; + + mod $optmod { + $( + pub(super) fn $opt(cg: &mut super::$struct_name, v: Option<&str>) -> bool { + super::parse::$parse(&mut redirect_field!(cg.$opt), v) + } + )* + } + +) } + +impl Options { + // JUSTIFICATION: defn of the suggested wrapper fn + #[cfg_attr(not(bootstrap), allow(rustc::bad_opt_access))] + pub fn time_passes(&self) -> bool { + self.unstable_opts.time_passes || self.unstable_opts.time + } +} + +impl CodegenOptions { + // JUSTIFICATION: defn of the suggested wrapper fn + #[cfg_attr(not(bootstrap), allow(rustc::bad_opt_access))] + pub fn instrument_coverage(&self) -> InstrumentCoverage { + self.instrument_coverage.unwrap_or(InstrumentCoverage::Off) + } +} + +// Sometimes different options need to build a common structure. +// That structure can be kept in one of the options' fields, the others become dummy. +macro_rules! redirect_field { + ($cg:ident.link_arg) => { + $cg.link_args + }; + ($cg:ident.pre_link_arg) => { + $cg.pre_link_args + }; + ($cg:ident.$field:ident) => { + $cg.$field + }; +} + +type OptionSetter = fn(&mut O, v: Option<&str>) -> bool; +type OptionDescrs = &'static [(&'static str, OptionSetter, &'static str, &'static str)]; + +fn build_options( + matches: &getopts::Matches, + descrs: OptionDescrs, + prefix: &str, + outputname: &str, + error_format: ErrorOutputType, +) -> O { + let mut op = O::default(); + for option in matches.opt_strs(prefix) { + let (key, value) = match option.split_once('=') { + None => (option, None), + Some((k, v)) => (k.to_string(), Some(v)), + }; + + let option_to_lookup = key.replace('-', "_"); + match descrs.iter().find(|(name, ..)| *name == option_to_lookup) { + Some((_, setter, type_desc, _)) => { + if !setter(&mut op, value) { + match value { + None => early_error( + error_format, + &format!( + "{0} option `{1}` requires {2} ({3} {1}=)", + outputname, key, type_desc, prefix + ), + ), + Some(value) => early_error( + error_format, + &format!( + "incorrect value `{value}` for {outputname} option `{key}` - {type_desc} was expected" + ), + ), + } + } + } + None => early_error(error_format, &format!("unknown {outputname} option: `{key}`")), + } + } + return op; +} + +#[allow(non_upper_case_globals)] +mod desc { + pub const parse_no_flag: &str = "no value"; + pub const parse_bool: &str = "one of: `y`, `yes`, `on`, `n`, `no`, or `off`"; + pub const parse_opt_bool: &str = parse_bool; + pub const parse_string: &str = "a string"; + pub const parse_opt_string: &str = parse_string; + pub const parse_string_push: &str = parse_string; + pub const parse_opt_langid: &str = "a language identifier"; + pub const parse_opt_pathbuf: &str = "a path"; + pub const parse_list: &str = "a space-separated list of strings"; + pub const parse_list_with_polarity: &str = + "a comma-separated list of strings, with elements beginning with + or -"; + pub const parse_opt_comma_list: &str = "a comma-separated list of strings"; + pub const parse_number: &str = "a number"; + pub const parse_opt_number: &str = parse_number; + pub const parse_threads: &str = parse_number; + pub const parse_passes: &str = "a space-separated list of passes, or `all`"; + pub const parse_panic_strategy: &str = "either `unwind` or `abort`"; + pub const parse_opt_panic_strategy: &str = parse_panic_strategy; + pub const parse_oom_strategy: &str = "either `panic` or `abort`"; + pub const parse_relro_level: &str = "one of: `full`, `partial`, or `off`"; + pub const parse_sanitizers: &str = "comma separated list of sanitizers: `address`, `cfi`, `hwaddress`, `leak`, `memory`, `memtag`, `shadow-call-stack`, or `thread`"; + pub const parse_sanitizer_memory_track_origins: &str = "0, 1, or 2"; + pub const parse_cfguard: &str = + "either a boolean (`yes`, `no`, `on`, `off`, etc), `checks`, or `nochecks`"; + pub const parse_cfprotection: &str = "`none`|`no`|`n` (default), `branch`, `return`, or `full`|`yes`|`y` (equivalent to `branch` and `return`)"; + pub const parse_strip: &str = "either `none`, `debuginfo`, or `symbols`"; + pub const parse_linker_flavor: &str = ::rustc_target::spec::LinkerFlavor::one_of(); + pub const parse_optimization_fuel: &str = "crate=integer"; + pub const parse_mir_spanview: &str = "`statement` (default), `terminator`, or `block`"; + pub const parse_instrument_coverage: &str = + "`all` (default), `except-unused-generics`, `except-unused-functions`, or `off`"; + pub const parse_unpretty: &str = "`string` or `string=string`"; + pub const parse_treat_err_as_bug: &str = "either no value or a number bigger than 0"; + pub const parse_lto: &str = + "either a boolean (`yes`, `no`, `on`, `off`, etc), `thin`, `fat`, or omitted"; + pub const parse_linker_plugin_lto: &str = + "either a boolean (`yes`, `no`, `on`, `off`, etc), or the path to the linker plugin"; + pub const parse_location_detail: &str = "either `none`, or a comma separated list of location details to track: `file`, `line`, or `column`"; + pub const parse_switch_with_opt_path: &str = + "an optional path to the profiling data output directory"; + pub const parse_merge_functions: &str = "one of: `disabled`, `trampolines`, or `aliases`"; + pub const parse_symbol_mangling_version: &str = "either `legacy` or `v0` (RFC 2603)"; + pub const parse_src_file_hash: &str = "either `md5` or `sha1`"; + pub const parse_relocation_model: &str = + "one of supported relocation models (`rustc --print relocation-models`)"; + pub const parse_code_model: &str = "one of supported code models (`rustc --print code-models`)"; + pub const parse_tls_model: &str = "one of supported TLS models (`rustc --print tls-models`)"; + pub const parse_target_feature: &str = parse_string; + pub const parse_wasi_exec_model: &str = "either `command` or `reactor`"; + pub const parse_split_debuginfo: &str = + "one of supported split-debuginfo modes (`off`, `packed`, or `unpacked`)"; + pub const parse_split_dwarf_kind: &str = + "one of supported split dwarf modes (`split` or `single`)"; + pub const parse_gcc_ld: &str = "one of: no value, `lld`"; + pub const parse_stack_protector: &str = + "one of (`none` (default), `basic`, `strong`, or `all`)"; + pub const parse_branch_protection: &str = + "a `,` separated combination of `bti`, `b-key`, `pac-ret`, or `leaf`"; + pub const parse_proc_macro_execution_strategy: &str = + "one of supported execution strategies (`same-thread`, or `cross-thread`)"; +} + +mod parse { + pub(crate) use super::*; + use std::str::FromStr; + + /// This is for boolean options that don't take a value and start with + /// `no-`. This style of option is deprecated. + pub(crate) fn parse_no_flag(slot: &mut bool, v: Option<&str>) -> bool { + match v { + None => { + *slot = true; + true + } + Some(_) => false, + } + } + + /// Use this for any boolean option that has a static default. + pub(crate) fn parse_bool(slot: &mut bool, v: Option<&str>) -> bool { + match v { + Some("y") | Some("yes") | Some("on") | None => { + *slot = true; + true + } + Some("n") | Some("no") | Some("off") => { + *slot = false; + true + } + _ => false, + } + } + + /// Use this for any boolean option that lacks a static default. (The + /// actions taken when such an option is not specified will depend on + /// other factors, such as other options, or target options.) + pub(crate) fn parse_opt_bool(slot: &mut Option, v: Option<&str>) -> bool { + match v { + Some("y") | Some("yes") | Some("on") | None => { + *slot = Some(true); + true + } + Some("n") | Some("no") | Some("off") => { + *slot = Some(false); + true + } + _ => false, + } + } + + /// Use this for any string option that has a static default. + pub(crate) fn parse_string(slot: &mut String, v: Option<&str>) -> bool { + match v { + Some(s) => { + *slot = s.to_string(); + true + } + None => false, + } + } + + /// Use this for any string option that lacks a static default. + pub(crate) fn parse_opt_string(slot: &mut Option, v: Option<&str>) -> bool { + match v { + Some(s) => { + *slot = Some(s.to_string()); + true + } + None => false, + } + } + + /// Parse an optional language identifier, e.g. `en-US` or `zh-CN`. + pub(crate) fn parse_opt_langid(slot: &mut Option, v: Option<&str>) -> bool { + match v { + Some(s) => { + *slot = rustc_errors::LanguageIdentifier::from_str(s).ok(); + true + } + None => false, + } + } + + pub(crate) fn parse_opt_pathbuf(slot: &mut Option, v: Option<&str>) -> bool { + match v { + Some(s) => { + *slot = Some(PathBuf::from(s)); + true + } + None => false, + } + } + + pub(crate) fn parse_string_push(slot: &mut Vec, v: Option<&str>) -> bool { + match v { + Some(s) => { + slot.push(s.to_string()); + true + } + None => false, + } + } + + pub(crate) fn parse_list(slot: &mut Vec, v: Option<&str>) -> bool { + match v { + Some(s) => { + slot.extend(s.split_whitespace().map(|s| s.to_string())); + true + } + None => false, + } + } + + pub(crate) fn parse_list_with_polarity( + slot: &mut Vec<(String, bool)>, + v: Option<&str>, + ) -> bool { + match v { + Some(s) => { + for s in s.split(',') { + let Some(pass_name) = s.strip_prefix(&['+', '-'][..]) else { return false }; + slot.push((pass_name.to_string(), &s[..1] == "+")); + } + true + } + None => false, + } + } + + pub(crate) fn parse_location_detail(ld: &mut LocationDetail, v: Option<&str>) -> bool { + if let Some(v) = v { + ld.line = false; + ld.file = false; + ld.column = false; + if v == "none" { + return true; + } + for s in v.split(',') { + match s { + "file" => ld.file = true, + "line" => ld.line = true, + "column" => ld.column = true, + _ => return false, + } + } + true + } else { + false + } + } + + pub(crate) fn parse_opt_comma_list(slot: &mut Option>, v: Option<&str>) -> bool { + match v { + Some(s) => { + let mut v: Vec<_> = s.split(',').map(|s| s.to_string()).collect(); + v.sort_unstable(); + *slot = Some(v); + true + } + None => false, + } + } + + pub(crate) fn parse_threads(slot: &mut usize, v: Option<&str>) -> bool { + match v.and_then(|s| s.parse().ok()) { + Some(0) => { + *slot = ::num_cpus::get(); + true + } + Some(i) => { + *slot = i; + true + } + None => false, + } + } + + /// Use this for any numeric option that has a static default. + pub(crate) fn parse_number(slot: &mut T, v: Option<&str>) -> bool { + match v.and_then(|s| s.parse().ok()) { + Some(i) => { + *slot = i; + true + } + None => false, + } + } + + /// Use this for any numeric option that lacks a static default. + pub(crate) fn parse_opt_number( + slot: &mut Option, + v: Option<&str>, + ) -> bool { + match v { + Some(s) => { + *slot = s.parse().ok(); + slot.is_some() + } + None => false, + } + } + + pub(crate) fn parse_passes(slot: &mut Passes, v: Option<&str>) -> bool { + match v { + Some("all") => { + *slot = Passes::All; + true + } + v => { + let mut passes = vec![]; + if parse_list(&mut passes, v) { + slot.extend(passes); + true + } else { + false + } + } + } + } + + pub(crate) fn parse_opt_panic_strategy( + slot: &mut Option, + v: Option<&str>, + ) -> bool { + match v { + Some("unwind") => *slot = Some(PanicStrategy::Unwind), + Some("abort") => *slot = Some(PanicStrategy::Abort), + _ => return false, + } + true + } + + pub(crate) fn parse_panic_strategy(slot: &mut PanicStrategy, v: Option<&str>) -> bool { + match v { + Some("unwind") => *slot = PanicStrategy::Unwind, + Some("abort") => *slot = PanicStrategy::Abort, + _ => return false, + } + true + } + + pub(crate) fn parse_oom_strategy(slot: &mut OomStrategy, v: Option<&str>) -> bool { + match v { + Some("panic") => *slot = OomStrategy::Panic, + Some("abort") => *slot = OomStrategy::Abort, + _ => return false, + } + true + } + + pub(crate) fn parse_relro_level(slot: &mut Option, v: Option<&str>) -> bool { + match v { + Some(s) => match s.parse::() { + Ok(level) => *slot = Some(level), + _ => return false, + }, + _ => return false, + } + true + } + + pub(crate) fn parse_sanitizers(slot: &mut SanitizerSet, v: Option<&str>) -> bool { + if let Some(v) = v { + for s in v.split(',') { + *slot |= match s { + "address" => SanitizerSet::ADDRESS, + "cfi" => SanitizerSet::CFI, + "leak" => SanitizerSet::LEAK, + "memory" => SanitizerSet::MEMORY, + "memtag" => SanitizerSet::MEMTAG, + "shadow-call-stack" => SanitizerSet::SHADOWCALLSTACK, + "thread" => SanitizerSet::THREAD, + "hwaddress" => SanitizerSet::HWADDRESS, + _ => return false, + } + } + true + } else { + false + } + } + + pub(crate) fn parse_sanitizer_memory_track_origins(slot: &mut usize, v: Option<&str>) -> bool { + match v { + Some("2") | None => { + *slot = 2; + true + } + Some("1") => { + *slot = 1; + true + } + Some("0") => { + *slot = 0; + true + } + Some(_) => false, + } + } + + pub(crate) fn parse_strip(slot: &mut Strip, v: Option<&str>) -> bool { + match v { + Some("none") => *slot = Strip::None, + Some("debuginfo") => *slot = Strip::Debuginfo, + Some("symbols") => *slot = Strip::Symbols, + _ => return false, + } + true + } + + pub(crate) fn parse_cfguard(slot: &mut CFGuard, v: Option<&str>) -> bool { + if v.is_some() { + let mut bool_arg = None; + if parse_opt_bool(&mut bool_arg, v) { + *slot = if bool_arg.unwrap() { CFGuard::Checks } else { CFGuard::Disabled }; + return true; + } + } + + *slot = match v { + None => CFGuard::Checks, + Some("checks") => CFGuard::Checks, + Some("nochecks") => CFGuard::NoChecks, + Some(_) => return false, + }; + true + } + + pub(crate) fn parse_cfprotection(slot: &mut CFProtection, v: Option<&str>) -> bool { + if v.is_some() { + let mut bool_arg = None; + if parse_opt_bool(&mut bool_arg, v) { + *slot = if bool_arg.unwrap() { CFProtection::Full } else { CFProtection::None }; + return true; + } + } + + *slot = match v { + None | Some("none") => CFProtection::None, + Some("branch") => CFProtection::Branch, + Some("return") => CFProtection::Return, + Some("full") => CFProtection::Full, + Some(_) => return false, + }; + true + } + + pub(crate) fn parse_linker_flavor(slot: &mut Option, v: Option<&str>) -> bool { + match v.and_then(LinkerFlavor::from_str) { + Some(lf) => *slot = Some(lf), + _ => return false, + } + true + } + + pub(crate) fn parse_optimization_fuel( + slot: &mut Option<(String, u64)>, + v: Option<&str>, + ) -> bool { + match v { + None => false, + Some(s) => { + let parts = s.split('=').collect::>(); + if parts.len() != 2 { + return false; + } + let crate_name = parts[0].to_string(); + let fuel = parts[1].parse::(); + if fuel.is_err() { + return false; + } + *slot = Some((crate_name, fuel.unwrap())); + true + } + } + } + + pub(crate) fn parse_unpretty(slot: &mut Option, v: Option<&str>) -> bool { + match v { + None => false, + Some(s) if s.split('=').count() <= 2 => { + *slot = Some(s.to_string()); + true + } + _ => false, + } + } + + pub(crate) fn parse_mir_spanview(slot: &mut Option, v: Option<&str>) -> bool { + if v.is_some() { + let mut bool_arg = None; + if parse_opt_bool(&mut bool_arg, v) { + *slot = if bool_arg.unwrap() { Some(MirSpanview::Statement) } else { None }; + return true; + } + } + + let Some(v) = v else { + *slot = Some(MirSpanview::Statement); + return true; + }; + + *slot = Some(match v.trim_end_matches('s') { + "statement" | "stmt" => MirSpanview::Statement, + "terminator" | "term" => MirSpanview::Terminator, + "block" | "basicblock" => MirSpanview::Block, + _ => return false, + }); + true + } + + pub(crate) fn parse_instrument_coverage( + slot: &mut Option, + v: Option<&str>, + ) -> bool { + if v.is_some() { + let mut bool_arg = None; + if parse_opt_bool(&mut bool_arg, v) { + *slot = if bool_arg.unwrap() { Some(InstrumentCoverage::All) } else { None }; + return true; + } + } + + let Some(v) = v else { + *slot = Some(InstrumentCoverage::All); + return true; + }; + + *slot = Some(match v { + "all" => InstrumentCoverage::All, + "except-unused-generics" | "except_unused_generics" => { + InstrumentCoverage::ExceptUnusedGenerics + } + "except-unused-functions" | "except_unused_functions" => { + InstrumentCoverage::ExceptUnusedFunctions + } + "off" | "no" | "n" | "false" | "0" => InstrumentCoverage::Off, + _ => return false, + }); + true + } + + pub(crate) fn parse_treat_err_as_bug(slot: &mut Option, v: Option<&str>) -> bool { + match v { + Some(s) => { + *slot = s.parse().ok(); + slot.is_some() + } + None => { + *slot = NonZeroUsize::new(1); + true + } + } + } + + pub(crate) fn parse_lto(slot: &mut LtoCli, v: Option<&str>) -> bool { + if v.is_some() { + let mut bool_arg = None; + if parse_opt_bool(&mut bool_arg, v) { + *slot = if bool_arg.unwrap() { LtoCli::Yes } else { LtoCli::No }; + return true; + } + } + + *slot = match v { + None => LtoCli::NoParam, + Some("thin") => LtoCli::Thin, + Some("fat") => LtoCli::Fat, + Some(_) => return false, + }; + true + } + + pub(crate) fn parse_linker_plugin_lto(slot: &mut LinkerPluginLto, v: Option<&str>) -> bool { + if v.is_some() { + let mut bool_arg = None; + if parse_opt_bool(&mut bool_arg, v) { + *slot = if bool_arg.unwrap() { + LinkerPluginLto::LinkerPluginAuto + } else { + LinkerPluginLto::Disabled + }; + return true; + } + } + + *slot = match v { + None => LinkerPluginLto::LinkerPluginAuto, + Some(path) => LinkerPluginLto::LinkerPlugin(PathBuf::from(path)), + }; + true + } + + pub(crate) fn parse_switch_with_opt_path( + slot: &mut SwitchWithOptPath, + v: Option<&str>, + ) -> bool { + *slot = match v { + None => SwitchWithOptPath::Enabled(None), + Some(path) => SwitchWithOptPath::Enabled(Some(PathBuf::from(path))), + }; + true + } + + pub(crate) fn parse_merge_functions( + slot: &mut Option, + v: Option<&str>, + ) -> bool { + match v.and_then(|s| MergeFunctions::from_str(s).ok()) { + Some(mergefunc) => *slot = Some(mergefunc), + _ => return false, + } + true + } + + pub(crate) fn parse_relocation_model(slot: &mut Option, v: Option<&str>) -> bool { + match v.and_then(|s| RelocModel::from_str(s).ok()) { + Some(relocation_model) => *slot = Some(relocation_model), + None if v == Some("default") => *slot = None, + _ => return false, + } + true + } + + pub(crate) fn parse_code_model(slot: &mut Option, v: Option<&str>) -> bool { + match v.and_then(|s| CodeModel::from_str(s).ok()) { + Some(code_model) => *slot = Some(code_model), + _ => return false, + } + true + } + + pub(crate) fn parse_tls_model(slot: &mut Option, v: Option<&str>) -> bool { + match v.and_then(|s| TlsModel::from_str(s).ok()) { + Some(tls_model) => *slot = Some(tls_model), + _ => return false, + } + true + } + + pub(crate) fn parse_symbol_mangling_version( + slot: &mut Option, + v: Option<&str>, + ) -> bool { + *slot = match v { + Some("legacy") => Some(SymbolManglingVersion::Legacy), + Some("v0") => Some(SymbolManglingVersion::V0), + _ => return false, + }; + true + } + + pub(crate) fn parse_src_file_hash( + slot: &mut Option, + v: Option<&str>, + ) -> bool { + match v.and_then(|s| SourceFileHashAlgorithm::from_str(s).ok()) { + Some(hash_kind) => *slot = Some(hash_kind), + _ => return false, + } + true + } + + pub(crate) fn parse_target_feature(slot: &mut String, v: Option<&str>) -> bool { + match v { + Some(s) => { + if !slot.is_empty() { + slot.push(','); + } + slot.push_str(s); + true + } + None => false, + } + } + + pub(crate) fn parse_wasi_exec_model(slot: &mut Option, v: Option<&str>) -> bool { + match v { + Some("command") => *slot = Some(WasiExecModel::Command), + Some("reactor") => *slot = Some(WasiExecModel::Reactor), + _ => return false, + } + true + } + + pub(crate) fn parse_split_debuginfo( + slot: &mut Option, + v: Option<&str>, + ) -> bool { + match v.and_then(|s| SplitDebuginfo::from_str(s).ok()) { + Some(e) => *slot = Some(e), + _ => return false, + } + true + } + + pub(crate) fn parse_split_dwarf_kind(slot: &mut SplitDwarfKind, v: Option<&str>) -> bool { + match v.and_then(|s| SplitDwarfKind::from_str(s).ok()) { + Some(e) => *slot = e, + _ => return false, + } + true + } + + pub(crate) fn parse_gcc_ld(slot: &mut Option, v: Option<&str>) -> bool { + match v { + None => *slot = None, + Some("lld") => *slot = Some(LdImpl::Lld), + _ => return false, + } + true + } + + pub(crate) fn parse_stack_protector(slot: &mut StackProtector, v: Option<&str>) -> bool { + match v.and_then(|s| StackProtector::from_str(s).ok()) { + Some(ssp) => *slot = ssp, + _ => return false, + } + true + } + + pub(crate) fn parse_branch_protection( + slot: &mut Option, + v: Option<&str>, + ) -> bool { + match v { + Some(s) => { + let slot = slot.get_or_insert_default(); + for opt in s.split(',') { + match opt { + "bti" => slot.bti = true, + "pac-ret" if slot.pac_ret.is_none() => { + slot.pac_ret = Some(PacRet { leaf: false, key: PAuthKey::A }) + } + "leaf" => match slot.pac_ret.as_mut() { + Some(pac) => pac.leaf = true, + _ => return false, + }, + "b-key" => match slot.pac_ret.as_mut() { + Some(pac) => pac.key = PAuthKey::B, + _ => return false, + }, + _ => return false, + }; + } + } + _ => return false, + } + true + } + + pub(crate) fn parse_proc_macro_execution_strategy( + slot: &mut ProcMacroExecutionStrategy, + v: Option<&str>, + ) -> bool { + *slot = match v { + Some("same-thread") => ProcMacroExecutionStrategy::SameThread, + Some("cross-thread") => ProcMacroExecutionStrategy::CrossThread, + _ => return false, + }; + true + } +} + +options! { + CodegenOptions, CG_OPTIONS, cgopts, "C", "codegen", + + // This list is in alphabetical order. + // + // If you add a new option, please update: + // - compiler/rustc_interface/src/tests.rs + // - src/doc/rustc/src/codegen-options/index.md + + ar: String = (String::new(), parse_string, [UNTRACKED], + "this option is deprecated and does nothing"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::code_model` instead of this field"))] + code_model: Option = (None, parse_code_model, [TRACKED], + "choose the code model to use (`rustc --print code-models` for details)"), + codegen_units: Option = (None, parse_opt_number, [UNTRACKED], + "divide crate into N units to optimize in parallel"), + control_flow_guard: CFGuard = (CFGuard::Disabled, parse_cfguard, [TRACKED], + "use Windows Control Flow Guard (default: no)"), + debug_assertions: Option = (None, parse_opt_bool, [TRACKED], + "explicitly enable the `cfg(debug_assertions)` directive"), + debuginfo: usize = (0, parse_number, [TRACKED], + "debug info emission level (0 = no debug info, 1 = line tables only, \ + 2 = full debug info with variable and type information; default: 0)"), + default_linker_libraries: bool = (false, parse_bool, [UNTRACKED], + "allow the linker to link its default libraries (default: no)"), + embed_bitcode: bool = (true, parse_bool, [TRACKED], + "emit bitcode in rlibs (default: yes)"), + extra_filename: String = (String::new(), parse_string, [UNTRACKED], + "extra data to put in each output filename"), + force_frame_pointers: Option = (None, parse_opt_bool, [TRACKED], + "force use of the frame pointers"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::must_emit_unwind_tables` instead of this field"))] + force_unwind_tables: Option = (None, parse_opt_bool, [TRACKED], + "force use of unwind tables"), + incremental: Option = (None, parse_opt_string, [UNTRACKED], + "enable incremental compilation"), + inline_threshold: Option = (None, parse_opt_number, [TRACKED], + "set the threshold for inlining a function"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::instrument_coverage` instead of this field"))] + instrument_coverage: Option = (None, parse_instrument_coverage, [TRACKED], + "instrument the generated code to support LLVM source-based code coverage \ + reports (note, the compiler build config must include `profiler = true`); \ + implies `-C symbol-mangling-version=v0`. Optional values are: + `=all` (implicit value) + `=except-unused-generics` + `=except-unused-functions` + `=off` (default)"), + link_arg: (/* redirected to link_args */) = ((), parse_string_push, [UNTRACKED], + "a single extra argument to append to the linker invocation (can be used several times)"), + link_args: Vec = (Vec::new(), parse_list, [UNTRACKED], + "extra arguments to append to the linker invocation (space separated)"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::link_dead_code` instead of this field"))] + link_dead_code: Option = (None, parse_opt_bool, [TRACKED], + "keep dead code at link time (useful for code coverage) (default: no)"), + link_self_contained: Option = (None, parse_opt_bool, [UNTRACKED], + "control whether to link Rust provided C objects/libraries or rely + on C toolchain installed in the system"), + linker: Option = (None, parse_opt_pathbuf, [UNTRACKED], + "system linker to link outputs with"), + linker_flavor: Option = (None, parse_linker_flavor, [UNTRACKED], + "linker flavor"), + linker_plugin_lto: LinkerPluginLto = (LinkerPluginLto::Disabled, + parse_linker_plugin_lto, [TRACKED], + "generate build artifacts that are compatible with linker-based LTO"), + llvm_args: Vec = (Vec::new(), parse_list, [TRACKED], + "a list of arguments to pass to LLVM (space separated)"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::lto` instead of this field"))] + lto: LtoCli = (LtoCli::Unspecified, parse_lto, [TRACKED], + "perform LLVM link-time optimizations"), + metadata: Vec = (Vec::new(), parse_list, [TRACKED], + "metadata to mangle symbol names with"), + no_prepopulate_passes: bool = (false, parse_no_flag, [TRACKED], + "give an empty list of passes to the pass manager"), + no_redzone: Option = (None, parse_opt_bool, [TRACKED], + "disable the use of the redzone"), + no_stack_check: bool = (false, parse_no_flag, [UNTRACKED], + "this option is deprecated and does nothing"), + no_vectorize_loops: bool = (false, parse_no_flag, [TRACKED], + "disable loop vectorization optimization passes"), + no_vectorize_slp: bool = (false, parse_no_flag, [TRACKED], + "disable LLVM's SLP vectorization pass"), + opt_level: String = ("0".to_string(), parse_string, [TRACKED], + "optimization level (0-3, s, or z; default: 0)"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::overflow_checks` instead of this field"))] + overflow_checks: Option = (None, parse_opt_bool, [TRACKED], + "use overflow checks for integer arithmetic"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::panic_strategy` instead of this field"))] + panic: Option = (None, parse_opt_panic_strategy, [TRACKED], + "panic strategy to compile crate with"), + passes: Vec = (Vec::new(), parse_list, [TRACKED], + "a list of extra LLVM passes to run (space separated)"), + prefer_dynamic: bool = (false, parse_bool, [TRACKED], + "prefer dynamic linking to static linking (default: no)"), + profile_generate: SwitchWithOptPath = (SwitchWithOptPath::Disabled, + parse_switch_with_opt_path, [TRACKED], + "compile the program with profiling instrumentation"), + profile_use: Option = (None, parse_opt_pathbuf, [TRACKED], + "use the given `.profdata` file for profile-guided optimization"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::relocation_model` instead of this field"))] + relocation_model: Option = (None, parse_relocation_model, [TRACKED], + "control generation of position-independent code (PIC) \ + (`rustc --print relocation-models` for details)"), + remark: Passes = (Passes::Some(Vec::new()), parse_passes, [UNTRACKED], + "print remarks for these optimization passes (space separated, or \"all\")"), + rpath: bool = (false, parse_bool, [UNTRACKED], + "set rpath values in libs/exes (default: no)"), + save_temps: bool = (false, parse_bool, [UNTRACKED], + "save all temporary output files during compilation (default: no)"), + soft_float: bool = (false, parse_bool, [TRACKED], + "use soft float ABI (*eabihf targets only) (default: no)"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::split_debuginfo` instead of this field"))] + split_debuginfo: Option = (None, parse_split_debuginfo, [TRACKED], + "how to handle split-debuginfo, a platform-specific option"), + strip: Strip = (Strip::None, parse_strip, [UNTRACKED], + "tell the linker which information to strip (`none` (default), `debuginfo` or `symbols`)"), + symbol_mangling_version: Option = (None, + parse_symbol_mangling_version, [TRACKED], + "which mangling version to use for symbol names ('legacy' (default) or 'v0')"), + target_cpu: Option = (None, parse_opt_string, [TRACKED], + "select target processor (`rustc --print target-cpus` for details)"), + target_feature: String = (String::new(), parse_target_feature, [TRACKED], + "target specific attributes. (`rustc --print target-features` for details). \ + This feature is unsafe."), + + // This list is in alphabetical order. + // + // If you add a new option, please update: + // - compiler/rustc_interface/src/tests.rs + // - src/doc/rustc/src/codegen-options/index.md +} + +options! { + UnstableOptions, Z_OPTIONS, dbopts, "Z", "unstable", + + // This list is in alphabetical order. + // + // If you add a new option, please update: + // - compiler/rustc_interface/src/tests.rs + // - src/doc/unstable-book/src/compiler-flags + + allow_features: Option> = (None, parse_opt_comma_list, [TRACKED], + "only allow the listed language features to be enabled in code (space separated)"), + always_encode_mir: bool = (false, parse_bool, [TRACKED], + "encode MIR of all functions into the crate metadata (default: no)"), + assume_incomplete_release: bool = (false, parse_bool, [TRACKED], + "make cfg(version) treat the current version as incomplete (default: no)"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::asm_comments` instead of this field"))] + asm_comments: bool = (false, parse_bool, [TRACKED], + "generate comments into the assembly (may change behavior) (default: no)"), + assert_incr_state: Option = (None, parse_opt_string, [UNTRACKED], + "assert that the incremental cache is in given state: \ + either `loaded` or `not-loaded`."), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::binary_dep_depinfo` instead of this field"))] + binary_dep_depinfo: bool = (false, parse_bool, [TRACKED], + "include artifacts (sysroot, crate dependencies) used during compilation in dep-info \ + (default: no)"), + box_noalias: Option = (None, parse_opt_bool, [TRACKED], + "emit noalias metadata for box (default: yes)"), + branch_protection: Option = (None, parse_branch_protection, [TRACKED], + "set options for branch target identification and pointer authentication on AArch64"), + cf_protection: CFProtection = (CFProtection::None, parse_cfprotection, [TRACKED], + "instrument control-flow architecture protection"), + cgu_partitioning_strategy: Option = (None, parse_opt_string, [TRACKED], + "the codegen unit partitioning strategy to use"), + chalk: bool = (false, parse_bool, [TRACKED], + "enable the experimental Chalk-based trait solving engine"), + codegen_backend: Option = (None, parse_opt_string, [TRACKED], + "the backend to use"), + combine_cgu: bool = (false, parse_bool, [TRACKED], + "combine CGUs into a single one"), + crate_attr: Vec = (Vec::new(), parse_string_push, [TRACKED], + "inject the given attribute in the crate"), + debug_info_for_profiling: bool = (false, parse_bool, [TRACKED], + "emit discriminators and other data necessary for AutoFDO"), + debug_macros: bool = (false, parse_bool, [TRACKED], + "emit line numbers debug info inside macros (default: no)"), + deduplicate_diagnostics: bool = (true, parse_bool, [UNTRACKED], + "deduplicate identical diagnostics (default: yes)"), + dep_info_omit_d_target: bool = (false, parse_bool, [TRACKED], + "in dep-info output, omit targets for tracking dependencies of the dep-info files \ + themselves (default: no)"), + dep_tasks: bool = (false, parse_bool, [UNTRACKED], + "print tasks that execute and the color their dep node gets (requires debug build) \ + (default: no)"), + dlltool: Option = (None, parse_opt_pathbuf, [UNTRACKED], + "import library generation tool (windows-gnu only)"), + dont_buffer_diagnostics: bool = (false, parse_bool, [UNTRACKED], + "emit diagnostics rather than buffering (breaks NLL error downgrading, sorting) \ + (default: no)"), + drop_tracking: bool = (false, parse_bool, [TRACKED], + "enables drop tracking in generators (default: no)"), + dual_proc_macros: bool = (false, parse_bool, [TRACKED], + "load proc macros for both target and host, but only link to the target (default: no)"), + dump_dep_graph: bool = (false, parse_bool, [UNTRACKED], + "dump the dependency graph to $RUST_DEP_GRAPH (default: /tmp/dep_graph.gv) \ + (default: no)"), + dump_drop_tracking_cfg: Option = (None, parse_opt_string, [UNTRACKED], + "dump drop-tracking control-flow graph as a `.dot` file (default: no)"), + dump_mir: Option = (None, parse_opt_string, [UNTRACKED], + "dump MIR state to file. + `val` is used to select which passes and functions to dump. For example: + `all` matches all passes and functions, + `foo` matches all passes for functions whose name contains 'foo', + `foo & ConstProp` only the 'ConstProp' pass for function names containing 'foo', + `foo | bar` all passes for function names containing 'foo' or 'bar'."), + dump_mir_dataflow: bool = (false, parse_bool, [UNTRACKED], + "in addition to `.mir` files, create graphviz `.dot` files with dataflow results \ + (default: no)"), + dump_mir_dir: String = ("mir_dump".to_string(), parse_string, [UNTRACKED], + "the directory the MIR is dumped into (default: `mir_dump`)"), + dump_mir_exclude_pass_number: bool = (false, parse_bool, [UNTRACKED], + "exclude the pass number when dumping MIR (used in tests) (default: no)"), + dump_mir_graphviz: bool = (false, parse_bool, [UNTRACKED], + "in addition to `.mir` files, create graphviz `.dot` files (and with \ + `-Z instrument-coverage`, also create a `.dot` file for the MIR-derived \ + coverage graph) (default: no)"), + dump_mir_spanview: Option = (None, parse_mir_spanview, [UNTRACKED], + "in addition to `.mir` files, create `.html` files to view spans for \ + all `statement`s (including terminators), only `terminator` spans, or \ + computed `block` spans (one span encompassing a block's terminator and \ + all statements). If `-Z instrument-coverage` is also enabled, create \ + an additional `.html` file showing the computed coverage spans."), + dwarf_version: Option = (None, parse_opt_number, [TRACKED], + "version of DWARF debug information to emit (default: 2 or 4, depending on platform)"), + emit_stack_sizes: bool = (false, parse_bool, [UNTRACKED], + "emit a section containing stack size metadata (default: no)"), + emit_thin_lto: bool = (true, parse_bool, [TRACKED], + "emit the bc module with thin LTO info (default: yes)"), + export_executable_symbols: bool = (false, parse_bool, [TRACKED], + "export symbols from executables, as if they were dynamic libraries"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::fewer_names` instead of this field"))] + fewer_names: Option = (None, parse_opt_bool, [TRACKED], + "reduce memory use by retaining fewer names within compilation artifacts (LLVM-IR) \ + (default: no)"), + force_unstable_if_unmarked: bool = (false, parse_bool, [TRACKED], + "force all crates to be `rustc_private` unstable (default: no)"), + fuel: Option<(String, u64)> = (None, parse_optimization_fuel, [TRACKED], + "set the optimization fuel quota for a crate"), + function_sections: Option = (None, parse_opt_bool, [TRACKED], + "whether each function should go in its own section"), + future_incompat_test: bool = (false, parse_bool, [UNTRACKED], + "forces all lints to be future incompatible, used for internal testing (default: no)"), + gcc_ld: Option = (None, parse_gcc_ld, [TRACKED], "implementation of ld used by cc"), + graphviz_dark_mode: bool = (false, parse_bool, [UNTRACKED], + "use dark-themed colors in graphviz output (default: no)"), + graphviz_font: String = ("Courier, monospace".to_string(), parse_string, [UNTRACKED], + "use the given `fontname` in graphviz output; can be overridden by setting \ + environment variable `RUSTC_GRAPHVIZ_FONT` (default: `Courier, monospace`)"), + hir_stats: bool = (false, parse_bool, [UNTRACKED], + "print some statistics about AST and HIR (default: no)"), + human_readable_cgu_names: bool = (false, parse_bool, [TRACKED], + "generate human-readable, predictable names for codegen units (default: no)"), + identify_regions: bool = (false, parse_bool, [UNTRACKED], + "display unnamed regions as `'`, using a non-ident unique id (default: no)"), + incremental_ignore_spans: bool = (false, parse_bool, [UNTRACKED], + "ignore spans during ICH computation -- used for testing (default: no)"), + incremental_info: bool = (false, parse_bool, [UNTRACKED], + "print high-level information about incremental reuse (or the lack thereof) \ + (default: no)"), + incremental_relative_spans: bool = (false, parse_bool, [TRACKED], + "hash spans relative to their parent item for incr. comp. (default: no)"), + incremental_verify_ich: bool = (false, parse_bool, [UNTRACKED], + "verify incr. comp. hashes of green query instances (default: no)"), + inline_mir: Option = (None, parse_opt_bool, [TRACKED], + "enable MIR inlining (default: no)"), + inline_mir_threshold: Option = (None, parse_opt_number, [TRACKED], + "a default MIR inlining threshold (default: 50)"), + inline_mir_hint_threshold: Option = (None, parse_opt_number, [TRACKED], + "inlining threshold for functions with inline hint (default: 100)"), + inline_in_all_cgus: Option = (None, parse_opt_bool, [TRACKED], + "control whether `#[inline]` functions are in all CGUs"), + input_stats: bool = (false, parse_bool, [UNTRACKED], + "gather statistics about the input (default: no)"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::instrument_coverage` instead of this field"))] + instrument_coverage: Option = (None, parse_instrument_coverage, [TRACKED], + "instrument the generated code to support LLVM source-based code coverage \ + reports (note, the compiler build config must include `profiler = true`); \ + implies `-C symbol-mangling-version=v0`. Optional values are: + `=all` (implicit value) + `=except-unused-generics` + `=except-unused-functions` + `=off` (default)"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::instrument_mcount` instead of this field"))] + instrument_mcount: bool = (false, parse_bool, [TRACKED], + "insert function instrument code for mcount-based tracing (default: no)"), + keep_hygiene_data: bool = (false, parse_bool, [UNTRACKED], + "keep hygiene data after analysis (default: no)"), + link_native_libraries: bool = (true, parse_bool, [UNTRACKED], + "link native libraries in the linker invocation (default: yes)"), + link_only: bool = (false, parse_bool, [TRACKED], + "link the `.rlink` file generated by `-Z no-link` (default: no)"), + llvm_plugins: Vec = (Vec::new(), parse_list, [TRACKED], + "a list LLVM plugins to enable (space separated)"), + llvm_time_trace: bool = (false, parse_bool, [UNTRACKED], + "generate JSON tracing data file from LLVM data (default: no)"), + location_detail: LocationDetail = (LocationDetail::all(), parse_location_detail, [TRACKED], + "what location details should be tracked when using caller_location, either \ + `none`, or a comma separated list of location details, for which \ + valid options are `file`, `line`, and `column` (default: `file,line,column`)"), + ls: bool = (false, parse_bool, [UNTRACKED], + "list the symbols defined by a library crate (default: no)"), + macro_backtrace: bool = (false, parse_bool, [UNTRACKED], + "show macro backtraces (default: no)"), + merge_functions: Option = (None, parse_merge_functions, [TRACKED], + "control the operation of the MergeFunctions LLVM pass, taking \ + the same values as the target option of the same name"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::meta_stats` instead of this field"))] + meta_stats: bool = (false, parse_bool, [UNTRACKED], + "gather metadata statistics (default: no)"), + mir_emit_retag: bool = (false, parse_bool, [TRACKED], + "emit Retagging MIR statements, interpreted e.g., by miri; implies -Zmir-opt-level=0 \ + (default: no)"), + mir_enable_passes: Vec<(String, bool)> = (Vec::new(), parse_list_with_polarity, [TRACKED], + "use like `-Zmir-enable-passes=+DestProp,-InstCombine`. Forces the specified passes to be \ + enabled, overriding all other checks. Passes that are not specified are enabled or \ + disabled by other flags as usual."), + mir_pretty_relative_line_numbers: bool = (false, parse_bool, [UNTRACKED], + "use line numbers relative to the function in mir pretty printing"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::mir_opt_level` instead of this field"))] + mir_opt_level: Option = (None, parse_opt_number, [TRACKED], + "MIR optimization level (0-4; default: 1 in non optimized builds and 2 in optimized builds)"), + move_size_limit: Option = (None, parse_opt_number, [TRACKED], + "the size at which the `large_assignments` lint starts to be emitted"), + mutable_noalias: Option = (None, parse_opt_bool, [TRACKED], + "emit noalias metadata for mutable references (default: yes)"), + new_llvm_pass_manager: Option = (None, parse_opt_bool, [TRACKED], + "use new LLVM pass manager (default: no)"), + nll_facts: bool = (false, parse_bool, [UNTRACKED], + "dump facts from NLL analysis into side files (default: no)"), + nll_facts_dir: String = ("nll-facts".to_string(), parse_string, [UNTRACKED], + "the directory the NLL facts are dumped into (default: `nll-facts`)"), + no_analysis: bool = (false, parse_no_flag, [UNTRACKED], + "parse and expand the source, but run no analysis"), + no_codegen: bool = (false, parse_no_flag, [TRACKED_NO_CRATE_HASH], + "run all passes except codegen; no output"), + no_generate_arange_section: bool = (false, parse_no_flag, [TRACKED], + "omit DWARF address ranges that give faster lookups"), + no_interleave_lints: bool = (false, parse_no_flag, [UNTRACKED], + "execute lints separately; allows benchmarking individual lints"), + no_leak_check: bool = (false, parse_no_flag, [UNTRACKED], + "disable the 'leak check' for subtyping; unsound, but useful for tests"), + no_link: bool = (false, parse_no_flag, [TRACKED], + "compile without linking"), + no_parallel_llvm: bool = (false, parse_no_flag, [UNTRACKED], + "run LLVM in non-parallel mode (while keeping codegen-units and ThinLTO)"), + no_unique_section_names: bool = (false, parse_bool, [TRACKED], + "do not use unique names for text and data sections when -Z function-sections is used"), + no_profiler_runtime: bool = (false, parse_no_flag, [TRACKED], + "prevent automatic injection of the profiler_builtins crate"), + normalize_docs: bool = (false, parse_bool, [TRACKED], + "normalize associated items in rustdoc when generating documentation"), + oom: OomStrategy = (OomStrategy::Abort, parse_oom_strategy, [TRACKED], + "panic strategy for out-of-memory handling"), + osx_rpath_install_name: bool = (false, parse_bool, [TRACKED], + "pass `-install_name @rpath/...` to the macOS linker (default: no)"), + diagnostic_width: Option = (None, parse_opt_number, [UNTRACKED], + "set the current output width for diagnostic truncation"), + panic_abort_tests: bool = (false, parse_bool, [TRACKED], + "support compiling tests with panic=abort (default: no)"), + panic_in_drop: PanicStrategy = (PanicStrategy::Unwind, parse_panic_strategy, [TRACKED], + "panic strategy for panics in drops"), + parse_only: bool = (false, parse_bool, [UNTRACKED], + "parse only; do not compile, assemble, or link (default: no)"), + perf_stats: bool = (false, parse_bool, [UNTRACKED], + "print some performance-related statistics (default: no)"), + pick_stable_methods_before_any_unstable: bool = (true, parse_bool, [TRACKED], + "try to pick stable methods first before picking any unstable methods (default: yes)"), + plt: Option = (None, parse_opt_bool, [TRACKED], + "whether to use the PLT when calling into shared libraries; + only has effect for PIC code on systems with ELF binaries + (default: PLT is disabled if full relro is enabled)"), + polonius: bool = (false, parse_bool, [TRACKED], + "enable polonius-based borrow-checker (default: no)"), + polymorphize: bool = (false, parse_bool, [TRACKED], + "perform polymorphization analysis"), + pre_link_arg: (/* redirected to pre_link_args */) = ((), parse_string_push, [UNTRACKED], + "a single extra argument to prepend the linker invocation (can be used several times)"), + pre_link_args: Vec = (Vec::new(), parse_list, [UNTRACKED], + "extra arguments to prepend to the linker invocation (space separated)"), + precise_enum_drop_elaboration: bool = (true, parse_bool, [TRACKED], + "use a more precise version of drop elaboration for matches on enums (default: yes). \ + This results in better codegen, but has caused miscompilations on some tier 2 platforms. \ + See #77382 and #74551."), + print_fuel: Option = (None, parse_opt_string, [TRACKED], + "make rustc print the total optimization fuel used by a crate"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::print_llvm_passes` instead of this field"))] + print_llvm_passes: bool = (false, parse_bool, [UNTRACKED], + "print the LLVM optimization passes being run (default: no)"), + print_mono_items: Option = (None, parse_opt_string, [UNTRACKED], + "print the result of the monomorphization collection pass"), + print_type_sizes: bool = (false, parse_bool, [UNTRACKED], + "print layout information for each type encountered (default: no)"), + proc_macro_backtrace: bool = (false, parse_bool, [UNTRACKED], + "show backtraces for panics during proc-macro execution (default: no)"), + proc_macro_execution_strategy: ProcMacroExecutionStrategy = (ProcMacroExecutionStrategy::SameThread, + parse_proc_macro_execution_strategy, [UNTRACKED], + "how to run proc-macro code (default: same-thread)"), + profile: bool = (false, parse_bool, [TRACKED], + "insert profiling code (default: no)"), + profile_closures: bool = (false, parse_no_flag, [UNTRACKED], + "profile size of closures"), + profile_emit: Option = (None, parse_opt_pathbuf, [TRACKED], + "file path to emit profiling data at runtime when using 'profile' \ + (default based on relative source path)"), + profiler_runtime: String = (String::from("profiler_builtins"), parse_string, [TRACKED], + "name of the profiler runtime crate to automatically inject (default: `profiler_builtins`)"), + profile_sample_use: Option = (None, parse_opt_pathbuf, [TRACKED], + "use the given `.prof` file for sampled profile-guided optimization (also known as AutoFDO)"), + query_dep_graph: bool = (false, parse_bool, [UNTRACKED], + "enable queries of the dependency graph for regression testing (default: no)"), + randomize_layout: bool = (false, parse_bool, [TRACKED], + "randomize the layout of types (default: no)"), + layout_seed: Option = (None, parse_opt_number, [TRACKED], + "seed layout randomization"), + relax_elf_relocations: Option = (None, parse_opt_bool, [TRACKED], + "whether ELF relocations can be relaxed"), + relro_level: Option = (None, parse_relro_level, [TRACKED], + "choose which RELRO level to use"), + remap_cwd_prefix: Option = (None, parse_opt_pathbuf, [TRACKED], + "remap paths under the current working directory to this path prefix"), + simulate_remapped_rust_src_base: Option = (None, parse_opt_pathbuf, [TRACKED], + "simulate the effect of remap-debuginfo = true at bootstrapping by remapping path \ + to rust's source base directory. only meant for testing purposes"), + report_delayed_bugs: bool = (false, parse_bool, [TRACKED], + "immediately print bugs registered with `delay_span_bug` (default: no)"), + sanitizer: SanitizerSet = (SanitizerSet::empty(), parse_sanitizers, [TRACKED], + "use a sanitizer"), + sanitizer_memory_track_origins: usize = (0, parse_sanitizer_memory_track_origins, [TRACKED], + "enable origins tracking in MemorySanitizer"), + sanitizer_recover: SanitizerSet = (SanitizerSet::empty(), parse_sanitizers, [TRACKED], + "enable recovery for selected sanitizers"), + saturating_float_casts: Option = (None, parse_opt_bool, [TRACKED], + "make float->int casts UB-free: numbers outside the integer type's range are clipped to \ + the max/min integer respectively, and NaN is mapped to 0 (default: yes)"), + save_analysis: bool = (false, parse_bool, [UNTRACKED], + "write syntax and type analysis (in JSON format) information, in \ + addition to normal output (default: no)"), + self_profile: SwitchWithOptPath = (SwitchWithOptPath::Disabled, + parse_switch_with_opt_path, [UNTRACKED], + "run the self profiler and output the raw event data"), + /// keep this in sync with the event filter names in librustc_data_structures/profiling.rs + self_profile_events: Option> = (None, parse_opt_comma_list, [UNTRACKED], + "specify the events recorded by the self profiler; + for example: `-Z self-profile-events=default,query-keys` + all options: none, all, default, generic-activity, query-provider, query-cache-hit + query-blocked, incr-cache-load, incr-result-hashing, query-keys, function-args, args, llvm, artifact-sizes"), + self_profile_counter: String = ("wall-time".to_string(), parse_string, [UNTRACKED], + "counter used by the self profiler (default: `wall-time`), one of: + `wall-time` (monotonic clock, i.e. `std::time::Instant`) + `instructions:u` (retired instructions, userspace-only) + `instructions-minus-irqs:u` (subtracting hardware interrupt counts for extra accuracy)" + ), + share_generics: Option = (None, parse_opt_bool, [TRACKED], + "make the current crate share its generic instantiations"), + show_span: Option = (None, parse_opt_string, [TRACKED], + "show spans for compiler debugging (expr|pat|ty)"), + span_debug: bool = (false, parse_bool, [UNTRACKED], + "forward proc_macro::Span's `Debug` impl to `Span`"), + /// o/w tests have closure@path + span_free_formats: bool = (false, parse_bool, [UNTRACKED], + "exclude spans when debug-printing compiler state (default: no)"), + src_hash_algorithm: Option = (None, parse_src_file_hash, [TRACKED], + "hash algorithm of source files in debug info (`md5`, `sha1`, or `sha256`)"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::stack_protector` instead of this field"))] + stack_protector: StackProtector = (StackProtector::None, parse_stack_protector, [TRACKED], + "control stack smash protection strategy (`rustc --print stack-protector-strategies` for details)"), + strict_init_checks: bool = (false, parse_bool, [TRACKED], + "control if mem::uninitialized and mem::zeroed panic on more UB"), + strip: Strip = (Strip::None, parse_strip, [UNTRACKED], + "tell the linker which information to strip (`none` (default), `debuginfo` or `symbols`)"), + split_dwarf_kind: SplitDwarfKind = (SplitDwarfKind::Split, parse_split_dwarf_kind, [TRACKED], + "split dwarf variant (only if -Csplit-debuginfo is enabled and on relevant platform) + (default: `split`) + + `split`: sections which do not require relocation are written into a DWARF object (`.dwo`) + file which is ignored by the linker + `single`: sections which do not require relocation are written into object file but ignored + by the linker"), + split_dwarf_inlining: bool = (true, parse_bool, [TRACKED], + "provide minimal debug info in the object/executable to facilitate online \ + symbolication/stack traces in the absence of .dwo/.dwp files when using Split DWARF"), + symbol_mangling_version: Option = (None, + parse_symbol_mangling_version, [TRACKED], + "which mangling version to use for symbol names ('legacy' (default) or 'v0')"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::teach` instead of this field"))] + teach: bool = (false, parse_bool, [TRACKED], + "show extended diagnostic help (default: no)"), + temps_dir: Option = (None, parse_opt_string, [UNTRACKED], + "the directory the intermediate files are written to"), + // Diagnostics are considered side-effects of a query (see `QuerySideEffects`) and are saved + // alongside query results and changes to translation options can affect diagnostics - so + // translation options should be tracked. + translate_lang: Option = (None, parse_opt_langid, [TRACKED], + "language identifier for diagnostic output"), + translate_additional_ftl: Option = (None, parse_opt_pathbuf, [TRACKED], + "additional fluent translation to preferentially use (for testing translation)"), + translate_directionality_markers: bool = (false, parse_bool, [TRACKED], + "emit directionality isolation markers in translated diagnostics"), + tune_cpu: Option = (None, parse_opt_string, [TRACKED], + "select processor to schedule for (`rustc --print target-cpus` for details)"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::lto` instead of this field"))] + thinlto: Option = (None, parse_opt_bool, [TRACKED], + "enable ThinLTO when possible"), + thir_unsafeck: bool = (false, parse_bool, [TRACKED], + "use the THIR unsafety checker (default: no)"), + /// We default to 1 here since we want to behave like + /// a sequential compiler for now. This'll likely be adjusted + /// in the future. Note that -Zthreads=0 is the way to get + /// the num_cpus behavior. + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::threads` instead of this field"))] + threads: usize = (1, parse_threads, [UNTRACKED], + "use a thread pool with N threads"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::time_passes` instead of this field"))] + time: bool = (false, parse_bool, [UNTRACKED], + "measure time of rustc processes (default: no)"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::time_llvm_passes` instead of this field"))] + time_llvm_passes: bool = (false, parse_bool, [UNTRACKED], + "measure time of each LLVM pass (default: no)"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::time_passes` instead of this field"))] + time_passes: bool = (false, parse_bool, [UNTRACKED], + "measure time of each rustc pass (default: no)"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::tls_model` instead of this field"))] + tls_model: Option = (None, parse_tls_model, [TRACKED], + "choose the TLS model to use (`rustc --print tls-models` for details)"), + trace_macros: bool = (false, parse_bool, [UNTRACKED], + "for every macro invocation, print its name and arguments (default: no)"), + translate_remapped_path_to_local_path: bool = (true, parse_bool, [TRACKED], + "translate remapped paths into local paths when possible (default: yes)"), + trap_unreachable: Option = (None, parse_opt_bool, [TRACKED], + "generate trap instructions for unreachable intrinsics (default: use target setting, usually yes)"), + treat_err_as_bug: Option = (None, parse_treat_err_as_bug, [TRACKED], + "treat error number `val` that occurs as bug"), + trim_diagnostic_paths: bool = (true, parse_bool, [UNTRACKED], + "in diagnostics, use heuristics to shorten paths referring to items"), + ui_testing: bool = (false, parse_bool, [UNTRACKED], + "emit compiler diagnostics in a form suitable for UI testing (default: no)"), + uninit_const_chunk_threshold: usize = (16, parse_number, [TRACKED], + "allow generating const initializers with mixed init/uninit chunks, \ + and set the maximum number of chunks for which this is allowed (default: 16)"), + unleash_the_miri_inside_of_you: bool = (false, parse_bool, [TRACKED], + "take the brakes off const evaluation. NOTE: this is unsound (default: no)"), + unpretty: Option = (None, parse_unpretty, [UNTRACKED], + "present the input source, unstable (and less-pretty) variants; + `normal`, `identified`, + `expanded`, `expanded,identified`, + `expanded,hygiene` (with internal representations), + `ast-tree` (raw AST before expansion), + `ast-tree,expanded` (raw AST after expansion), + `hir` (the HIR), `hir,identified`, + `hir,typed` (HIR with types for each node), + `hir-tree` (dump the raw HIR), + `mir` (the MIR), or `mir-cfg` (graphviz formatted MIR)"), + unsound_mir_opts: bool = (false, parse_bool, [TRACKED], + "enable unsound and buggy MIR optimizations (default: no)"), + /// This name is kind of confusing: Most unstable options enable something themselves, while + /// this just allows "normal" options to be feature-gated. + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::unstable_options` instead of this field"))] + unstable_options: bool = (false, parse_bool, [UNTRACKED], + "adds unstable command line options to rustc interface (default: no)"), + use_ctors_section: Option = (None, parse_opt_bool, [TRACKED], + "use legacy .ctors section for initializers rather than .init_array"), + validate_mir: bool = (false, parse_bool, [UNTRACKED], + "validate MIR after each transformation"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::verbose` instead of this field"))] + verbose: bool = (false, parse_bool, [UNTRACKED], + "in general, enable more debug printouts (default: no)"), + #[cfg_attr(not(bootstrap), rustc_lint_opt_deny_field_access("use `Session::verify_llvm_ir` instead of this field"))] + verify_llvm_ir: bool = (false, parse_bool, [TRACKED], + "verify LLVM IR (default: no)"), + virtual_function_elimination: bool = (false, parse_bool, [TRACKED], + "enables dead virtual function elimination optimization. \ + Requires `-Clto[=[fat,yes]]`"), + wasi_exec_model: Option = (None, parse_wasi_exec_model, [TRACKED], + "whether to build a wasi command or reactor"), + + // This list is in alphabetical order. + // + // If you add a new option, please update: + // - compiler/rustc_interface/src/tests.rs +} + +#[derive(Clone, Hash, PartialEq, Eq, Debug)] +pub enum WasiExecModel { + Command, + Reactor, +} + +#[derive(Clone, Copy, Hash)] +pub enum LdImpl { + Lld, +} diff --git a/compiler/rustc_session/src/output.rs b/compiler/rustc_session/src/output.rs new file mode 100644 index 000000000..e5e6579d7 --- /dev/null +++ b/compiler/rustc_session/src/output.rs @@ -0,0 +1,202 @@ +//! Related to out filenames of compilation (e.g. save analysis, binaries). +use crate::config::{CrateType, Input, OutputFilenames, OutputType}; +use crate::Session; +use rustc_ast as ast; +use rustc_span::symbol::sym; +use rustc_span::Span; +use std::path::{Path, PathBuf}; + +pub fn out_filename( + sess: &Session, + crate_type: CrateType, + outputs: &OutputFilenames, + crate_name: &str, +) -> PathBuf { + let default_filename = filename_for_input(sess, crate_type, crate_name, outputs); + let out_filename = outputs + .outputs + .get(&OutputType::Exe) + .and_then(|s| s.to_owned()) + .or_else(|| outputs.single_output_file.clone()) + .unwrap_or(default_filename); + + check_file_is_writeable(&out_filename, sess); + + out_filename +} + +/// Make sure files are writeable. Mac, FreeBSD, and Windows system linkers +/// check this already -- however, the Linux linker will happily overwrite a +/// read-only file. We should be consistent. +pub fn check_file_is_writeable(file: &Path, sess: &Session) { + if !is_writeable(file) { + sess.fatal(&format!( + "output file {} is not writeable -- check its \ + permissions", + file.display() + )); + } +} + +fn is_writeable(p: &Path) -> bool { + match p.metadata() { + Err(..) => true, + Ok(m) => !m.permissions().readonly(), + } +} + +pub fn find_crate_name(sess: &Session, attrs: &[ast::Attribute], input: &Input) -> String { + let validate = |s: String, span: Option| { + validate_crate_name(sess, &s, span); + s + }; + + // Look in attributes 100% of the time to make sure the attribute is marked + // as used. After doing this, however, we still prioritize a crate name from + // the command line over one found in the #[crate_name] attribute. If we + // find both we ensure that they're the same later on as well. + let attr_crate_name = + sess.find_by_name(attrs, sym::crate_name).and_then(|at| at.value_str().map(|s| (at, s))); + + if let Some(ref s) = sess.opts.crate_name { + if let Some((attr, name)) = attr_crate_name { + if name.as_str() != s { + let msg = format!( + "`--crate-name` and `#[crate_name]` are \ + required to match, but `{s}` != `{name}`" + ); + sess.span_err(attr.span, &msg); + } + } + return validate(s.clone(), None); + } + + if let Some((attr, s)) = attr_crate_name { + return validate(s.to_string(), Some(attr.span)); + } + if let Input::File(ref path) = *input { + if let Some(s) = path.file_stem().and_then(|s| s.to_str()) { + if s.starts_with('-') { + let msg = format!( + "crate names cannot start with a `-`, but \ + `{s}` has a leading hyphen" + ); + sess.err(&msg); + } else { + return validate(s.replace('-', "_"), None); + } + } + } + + "rust_out".to_string() +} + +pub fn validate_crate_name(sess: &Session, s: &str, sp: Option) { + let mut err_count = 0; + { + let mut say = |s: &str| { + match sp { + Some(sp) => sess.span_err(sp, s), + None => sess.err(s), + }; + err_count += 1; + }; + if s.is_empty() { + say("crate name must not be empty"); + } + for c in s.chars() { + if c.is_alphanumeric() { + continue; + } + if c == '_' { + continue; + } + say(&format!("invalid character `{c}` in crate name: `{s}`")); + } + } + + if err_count > 0 { + sess.abort_if_errors(); + } +} + +pub fn filename_for_metadata( + sess: &Session, + crate_name: &str, + outputs: &OutputFilenames, +) -> PathBuf { + // If the command-line specified the path, use that directly. + if let Some(Some(out_filename)) = sess.opts.output_types.get(&OutputType::Metadata) { + return out_filename.clone(); + } + + let libname = format!("{}{}", crate_name, sess.opts.cg.extra_filename); + + let out_filename = outputs + .single_output_file + .clone() + .unwrap_or_else(|| outputs.out_directory.join(&format!("lib{libname}.rmeta"))); + + check_file_is_writeable(&out_filename, sess); + + out_filename +} + +pub fn filename_for_input( + sess: &Session, + crate_type: CrateType, + crate_name: &str, + outputs: &OutputFilenames, +) -> PathBuf { + let libname = format!("{}{}", crate_name, sess.opts.cg.extra_filename); + + match crate_type { + CrateType::Rlib => outputs.out_directory.join(&format!("lib{libname}.rlib")), + CrateType::Cdylib | CrateType::ProcMacro | CrateType::Dylib => { + let (prefix, suffix) = (&sess.target.dll_prefix, &sess.target.dll_suffix); + outputs.out_directory.join(&format!("{prefix}{libname}{suffix}")) + } + CrateType::Staticlib => { + let (prefix, suffix) = (&sess.target.staticlib_prefix, &sess.target.staticlib_suffix); + outputs.out_directory.join(&format!("{prefix}{libname}{suffix}")) + } + CrateType::Executable => { + let suffix = &sess.target.exe_suffix; + let out_filename = outputs.path(OutputType::Exe); + if suffix.is_empty() { out_filename } else { out_filename.with_extension(&suffix[1..]) } + } + } +} + +/// Returns default crate type for target +/// +/// Default crate type is used when crate type isn't provided neither +/// through cmd line arguments nor through crate attributes +/// +/// It is CrateType::Executable for all platforms but iOS as there is no +/// way to run iOS binaries anyway without jailbreaking and +/// interaction with Rust code through static library is the only +/// option for now +pub fn default_output_for_target(sess: &Session) -> CrateType { + if !sess.target.executables { CrateType::Staticlib } else { CrateType::Executable } +} + +/// Checks if target supports crate_type as output +pub fn invalid_output_for_target(sess: &Session, crate_type: CrateType) -> bool { + if let CrateType::Cdylib | CrateType::Dylib | CrateType::ProcMacro = crate_type { + if !sess.target.dynamic_linking { + return true; + } + if sess.crt_static(Some(crate_type)) && !sess.target.crt_static_allows_dylibs { + return true; + } + } + if let CrateType::ProcMacro | CrateType::Dylib = crate_type && sess.target.only_cdylib { + return true; + } + if let CrateType::Executable = crate_type && !sess.target.executables { + return true; + } + + false +} diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs new file mode 100644 index 000000000..f31d52147 --- /dev/null +++ b/compiler/rustc_session/src/parse.rs @@ -0,0 +1,326 @@ +//! Contains `ParseSess` which holds state living beyond what one `Parser` might. +//! It also serves as an input to the parser itself. + +use crate::config::CheckCfg; +use crate::lint::{BufferedEarlyLint, BuiltinLintDiagnostics, Lint, LintId}; +use crate::SessionDiagnostic; +use rustc_ast::node_id::NodeId; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::sync::{Lock, Lrc}; +use rustc_errors::{emitter::SilentEmitter, ColorConfig, Handler}; +use rustc_errors::{ + error_code, fallback_fluent_bundle, Applicability, Diagnostic, DiagnosticBuilder, + DiagnosticMessage, ErrorGuaranteed, MultiSpan, +}; +use rustc_feature::{find_feature_issue, GateIssue, UnstableFeatures}; +use rustc_span::edition::Edition; +use rustc_span::hygiene::ExpnId; +use rustc_span::source_map::{FilePathMapping, SourceMap}; +use rustc_span::{Span, Symbol}; + +use std::str; + +/// The set of keys (and, optionally, values) that define the compilation +/// environment of the crate, used to drive conditional compilation. +pub type CrateConfig = FxHashSet<(Symbol, Option)>; +pub type CrateCheckConfig = CheckCfg; + +/// Collected spans during parsing for places where a certain feature was +/// used and should be feature gated accordingly in `check_crate`. +#[derive(Default)] +pub struct GatedSpans { + pub spans: Lock>>, +} + +impl GatedSpans { + /// Feature gate the given `span` under the given `feature` + /// which is same `Symbol` used in `active.rs`. + pub fn gate(&self, feature: Symbol, span: Span) { + self.spans.borrow_mut().entry(feature).or_default().push(span); + } + + /// Ungate the last span under the given `feature`. + /// Panics if the given `span` wasn't the last one. + /// + /// Using this is discouraged unless you have a really good reason to. + pub fn ungate_last(&self, feature: Symbol, span: Span) { + let removed_span = self.spans.borrow_mut().entry(feature).or_default().pop().unwrap(); + debug_assert_eq!(span, removed_span); + } + + /// Is the provided `feature` gate ungated currently? + /// + /// Using this is discouraged unless you have a really good reason to. + pub fn is_ungated(&self, feature: Symbol) -> bool { + self.spans.borrow().get(&feature).map_or(true, |spans| spans.is_empty()) + } + + /// Prepend the given set of `spans` onto the set in `self`. + pub fn merge(&self, mut spans: FxHashMap>) { + let mut inner = self.spans.borrow_mut(); + for (gate, mut gate_spans) in inner.drain() { + spans.entry(gate).or_default().append(&mut gate_spans); + } + *inner = spans; + } +} + +#[derive(Default)] +pub struct SymbolGallery { + /// All symbols occurred and their first occurrence span. + pub symbols: Lock>, +} + +impl SymbolGallery { + /// Insert a symbol and its span into symbol gallery. + /// If the symbol has occurred before, ignore the new occurrence. + pub fn insert(&self, symbol: Symbol, span: Span) { + self.symbols.lock().entry(symbol).or_insert(span); + } +} + +/// Construct a diagnostic for a language feature error due to the given `span`. +/// The `feature`'s `Symbol` is the one you used in `active.rs` and `rustc_span::symbols`. +pub fn feature_err<'a>( + sess: &'a ParseSess, + feature: Symbol, + span: impl Into, + explain: &str, +) -> DiagnosticBuilder<'a, ErrorGuaranteed> { + feature_err_issue(sess, feature, span, GateIssue::Language, explain) +} + +/// Construct a diagnostic for a feature gate error. +/// +/// This variant allows you to control whether it is a library or language feature. +/// Almost always, you want to use this for a language feature. If so, prefer `feature_err`. +pub fn feature_err_issue<'a>( + sess: &'a ParseSess, + feature: Symbol, + span: impl Into, + issue: GateIssue, + explain: &str, +) -> DiagnosticBuilder<'a, ErrorGuaranteed> { + let mut err = sess.span_diagnostic.struct_span_err_with_code(span, explain, error_code!(E0658)); + add_feature_diagnostics_for_issue(&mut err, sess, feature, issue); + err +} + +/// Adds the diagnostics for a feature to an existing error. +pub fn add_feature_diagnostics<'a>(err: &mut Diagnostic, sess: &'a ParseSess, feature: Symbol) { + add_feature_diagnostics_for_issue(err, sess, feature, GateIssue::Language); +} + +/// Adds the diagnostics for a feature to an existing error. +/// +/// This variant allows you to control whether it is a library or language feature. +/// Almost always, you want to use this for a language feature. If so, prefer +/// `add_feature_diagnostics`. +pub fn add_feature_diagnostics_for_issue<'a>( + err: &mut Diagnostic, + sess: &'a ParseSess, + feature: Symbol, + issue: GateIssue, +) { + if let Some(n) = find_feature_issue(feature, issue) { + err.note(&format!( + "see issue #{n} for more information" + )); + } + + // #23973: do not suggest `#![feature(...)]` if we are in beta/stable + if sess.unstable_features.is_nightly_build() { + err.help(&format!("add `#![feature({feature})]` to the crate attributes to enable")); + } +} + +/// Info about a parsing session. +pub struct ParseSess { + pub span_diagnostic: Handler, + pub unstable_features: UnstableFeatures, + pub config: CrateConfig, + pub check_config: CrateCheckConfig, + pub edition: Edition, + /// Places where raw identifiers were used. This is used to avoid complaining about idents + /// clashing with keywords in new editions. + pub raw_identifier_spans: Lock>, + /// Places where identifiers that contain invalid Unicode codepoints but that look like they + /// should be. Useful to avoid bad tokenization when encountering emoji. We group them to + /// provide a single error per unique incorrect identifier. + pub bad_unicode_identifiers: Lock>>, + source_map: Lrc, + pub buffered_lints: Lock>, + /// Contains the spans of block expressions that could have been incomplete based on the + /// operation token that followed it, but that the parser cannot identify without further + /// analysis. + pub ambiguous_block_expr_parse: Lock>, + pub gated_spans: GatedSpans, + pub symbol_gallery: SymbolGallery, + /// The parser has reached `Eof` due to an unclosed brace. Used to silence unnecessary errors. + pub reached_eof: Lock, + /// Environment variables accessed during the build and their values when they exist. + pub env_depinfo: Lock)>>, + /// File paths accessed during the build. + pub file_depinfo: Lock>, + /// All the type ascriptions expressions that have had a suggestion for likely path typo. + pub type_ascription_path_suggestions: Lock>, + /// Whether cfg(version) should treat the current release as incomplete + pub assume_incomplete_release: bool, + /// Spans passed to `proc_macro::quote_span`. Each span has a numerical + /// identifier represented by its position in the vector. + pub proc_macro_quoted_spans: Lock>, +} + +impl ParseSess { + /// Used for testing. + pub fn new(file_path_mapping: FilePathMapping) -> Self { + let fallback_bundle = fallback_fluent_bundle(rustc_errors::DEFAULT_LOCALE_RESOURCES, false); + let sm = Lrc::new(SourceMap::new(file_path_mapping)); + let handler = Handler::with_tty_emitter( + ColorConfig::Auto, + true, + None, + Some(sm.clone()), + None, + fallback_bundle, + ); + ParseSess::with_span_handler(handler, sm) + } + + pub fn with_span_handler(handler: Handler, source_map: Lrc) -> Self { + Self { + span_diagnostic: handler, + unstable_features: UnstableFeatures::from_environment(None), + config: FxHashSet::default(), + check_config: CrateCheckConfig::default(), + edition: ExpnId::root().expn_data().edition, + raw_identifier_spans: Lock::new(Vec::new()), + bad_unicode_identifiers: Lock::new(Default::default()), + source_map, + buffered_lints: Lock::new(vec![]), + ambiguous_block_expr_parse: Lock::new(FxHashMap::default()), + gated_spans: GatedSpans::default(), + symbol_gallery: SymbolGallery::default(), + reached_eof: Lock::new(false), + env_depinfo: Default::default(), + file_depinfo: Default::default(), + type_ascription_path_suggestions: Default::default(), + assume_incomplete_release: false, + proc_macro_quoted_spans: Default::default(), + } + } + + pub fn with_silent_emitter(fatal_note: Option) -> Self { + let fallback_bundle = fallback_fluent_bundle(rustc_errors::DEFAULT_LOCALE_RESOURCES, false); + let sm = Lrc::new(SourceMap::new(FilePathMapping::empty())); + let fatal_handler = + Handler::with_tty_emitter(ColorConfig::Auto, false, None, None, None, fallback_bundle); + let handler = Handler::with_emitter( + false, + None, + Box::new(SilentEmitter { fatal_handler, fatal_note }), + ); + ParseSess::with_span_handler(handler, sm) + } + + #[inline] + pub fn source_map(&self) -> &SourceMap { + &self.source_map + } + + pub fn clone_source_map(&self) -> Lrc { + self.source_map.clone() + } + + pub fn buffer_lint( + &self, + lint: &'static Lint, + span: impl Into, + node_id: NodeId, + msg: &str, + ) { + self.buffered_lints.with_lock(|buffered_lints| { + buffered_lints.push(BufferedEarlyLint { + span: span.into(), + node_id, + msg: msg.into(), + lint_id: LintId::of(lint), + diagnostic: BuiltinLintDiagnostics::Normal, + }); + }); + } + + pub fn buffer_lint_with_diagnostic( + &self, + lint: &'static Lint, + span: impl Into, + node_id: NodeId, + msg: &str, + diagnostic: BuiltinLintDiagnostics, + ) { + self.buffered_lints.with_lock(|buffered_lints| { + buffered_lints.push(BufferedEarlyLint { + span: span.into(), + node_id, + msg: msg.into(), + lint_id: LintId::of(lint), + diagnostic, + }); + }); + } + + /// Extend an error with a suggestion to wrap an expression with parentheses to allow the + /// parser to continue parsing the following operation as part of the same expression. + pub fn expr_parentheses_needed(&self, err: &mut Diagnostic, span: Span) { + err.multipart_suggestion( + "parentheses are required to parse this as an expression", + vec![(span.shrink_to_lo(), "(".to_string()), (span.shrink_to_hi(), ")".to_string())], + Applicability::MachineApplicable, + ); + } + + pub fn save_proc_macro_span(&self, span: Span) -> usize { + let mut spans = self.proc_macro_quoted_spans.lock(); + spans.push(span); + return spans.len() - 1; + } + + pub fn proc_macro_quoted_spans(&self) -> Vec { + self.proc_macro_quoted_spans.lock().clone() + } + + pub fn create_err<'a>( + &'a self, + err: impl SessionDiagnostic<'a>, + ) -> DiagnosticBuilder<'a, ErrorGuaranteed> { + err.into_diagnostic(self) + } + + pub fn emit_err<'a>(&'a self, err: impl SessionDiagnostic<'a>) -> ErrorGuaranteed { + self.create_err(err).emit() + } + + pub fn create_warning<'a>( + &'a self, + warning: impl SessionDiagnostic<'a, ()>, + ) -> DiagnosticBuilder<'a, ()> { + warning.into_diagnostic(self) + } + + pub fn emit_warning<'a>(&'a self, warning: impl SessionDiagnostic<'a, ()>) { + self.create_warning(warning).emit() + } + + #[rustc_lint_diagnostics] + pub fn struct_err( + &self, + msg: impl Into, + ) -> DiagnosticBuilder<'_, ErrorGuaranteed> { + self.span_diagnostic.struct_err(msg) + } + + #[rustc_lint_diagnostics] + pub fn struct_warn(&self, msg: impl Into) -> DiagnosticBuilder<'_, ()> { + self.span_diagnostic.struct_warn(msg) + } +} diff --git a/compiler/rustc_session/src/search_paths.rs b/compiler/rustc_session/src/search_paths.rs new file mode 100644 index 000000000..56a6b6f3b --- /dev/null +++ b/compiler/rustc_session/src/search_paths.rs @@ -0,0 +1,93 @@ +use crate::filesearch::make_target_lib_path; +use crate::{config, early_error}; +use std::path::{Path, PathBuf}; + +#[derive(Clone, Debug)] +pub struct SearchPath { + pub kind: PathKind, + pub dir: PathBuf, + pub files: Vec, +} + +/// The obvious implementation of `SearchPath::files` is a `Vec`. But +/// it is searched repeatedly by `find_library_crate`, and the searches involve +/// checking the prefix and suffix of the filename of each `PathBuf`. This is +/// doable, but very slow, because it involves calls to `file_name` and +/// `extension` that are themselves slow. +/// +/// This type augments the `PathBuf` with an `String` containing the +/// `PathBuf`'s filename. The prefix and suffix checking is much faster on the +/// `String` than the `PathBuf`. (The filename must be valid UTF-8. If it's +/// not, the entry should be skipped, because all Rust output files are valid +/// UTF-8, and so a non-UTF-8 filename couldn't be one we're looking for.) +#[derive(Clone, Debug)] +pub struct SearchPathFile { + pub path: PathBuf, + pub file_name_str: String, +} + +#[derive(PartialEq, Clone, Copy, Debug, Hash, Eq, Encodable, Decodable, HashStable_Generic)] +pub enum PathKind { + Native, + Crate, + Dependency, + Framework, + ExternFlag, + All, +} + +impl PathKind { + pub fn matches(&self, kind: PathKind) -> bool { + match (self, kind) { + (PathKind::All, _) | (_, PathKind::All) => true, + _ => *self == kind, + } + } +} + +impl SearchPath { + pub fn from_cli_opt(path: &str, output: config::ErrorOutputType) -> Self { + let (kind, path) = if let Some(stripped) = path.strip_prefix("native=") { + (PathKind::Native, stripped) + } else if let Some(stripped) = path.strip_prefix("crate=") { + (PathKind::Crate, stripped) + } else if let Some(stripped) = path.strip_prefix("dependency=") { + (PathKind::Dependency, stripped) + } else if let Some(stripped) = path.strip_prefix("framework=") { + (PathKind::Framework, stripped) + } else if let Some(stripped) = path.strip_prefix("all=") { + (PathKind::All, stripped) + } else { + (PathKind::All, path) + }; + if path.is_empty() { + early_error(output, "empty search path given via `-L`"); + } + + let dir = PathBuf::from(path); + Self::new(kind, dir) + } + + pub fn from_sysroot_and_triple(sysroot: &Path, triple: &str) -> Self { + Self::new(PathKind::All, make_target_lib_path(sysroot, triple)) + } + + fn new(kind: PathKind, dir: PathBuf) -> Self { + // Get the files within the directory. + let files = match std::fs::read_dir(&dir) { + Ok(files) => files + .filter_map(|e| { + e.ok().and_then(|e| { + e.file_name().to_str().map(|s| SearchPathFile { + path: e.path(), + file_name_str: s.to_string(), + }) + }) + }) + .collect::>(), + Err(..) => vec![], + }; + + SearchPath { kind, dir, files } + } +} diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs new file mode 100644 index 000000000..9669287b3 --- /dev/null +++ b/compiler/rustc_session/src/session.rs @@ -0,0 +1,1599 @@ +use crate::cgu_reuse_tracker::CguReuseTracker; +use crate::code_stats::CodeStats; +pub use crate::code_stats::{DataTypeKind, FieldInfo, SizeKind, VariantInfo}; +use crate::config::{self, CrateType, InstrumentCoverage, OptLevel, OutputType, SwitchWithOptPath}; +use crate::parse::{add_feature_diagnostics, ParseSess}; +use crate::search_paths::{PathKind, SearchPath}; +use crate::{filesearch, lint}; + +pub use rustc_ast::attr::MarkedAttrs; +pub use rustc_ast::Attribute; +use rustc_data_structures::flock; +use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::jobserver::{self, Client}; +use rustc_data_structures::profiling::{duration_to_secs_str, SelfProfiler, SelfProfilerRef}; +use rustc_data_structures::sync::{ + self, AtomicU64, AtomicUsize, Lock, Lrc, OnceCell, OneThread, Ordering, Ordering::SeqCst, +}; +use rustc_errors::annotate_snippet_emitter_writer::AnnotateSnippetEmitterWriter; +use rustc_errors::emitter::{Emitter, EmitterWriter, HumanReadableErrorType}; +use rustc_errors::json::JsonEmitter; +use rustc_errors::registry::Registry; +use rustc_errors::{ + fallback_fluent_bundle, DiagnosticBuilder, DiagnosticId, DiagnosticMessage, EmissionGuarantee, + ErrorGuaranteed, FluentBundle, LazyFallbackBundle, MultiSpan, +}; +use rustc_macros::HashStable_Generic; +pub use rustc_span::def_id::StableCrateId; +use rustc_span::edition::Edition; +use rustc_span::source_map::{FileLoader, RealFileLoader, SourceMap, Span}; +use rustc_span::{sym, SourceFileHashAlgorithm, Symbol}; +use rustc_target::asm::InlineAsmArch; +use rustc_target::spec::{CodeModel, PanicStrategy, RelocModel, RelroLevel}; +use rustc_target::spec::{ + SanitizerSet, SplitDebuginfo, StackProtector, Target, TargetTriple, TlsModel, +}; + +use std::cell::{self, RefCell}; +use std::env; +use std::fmt; +use std::io::Write; +use std::ops::{Div, Mul}; +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use std::sync::Arc; +use std::time::Duration; + +pub struct OptimizationFuel { + /// If `-zfuel=crate=n` is specified, initially set to `n`, otherwise `0`. + remaining: u64, + /// We're rejecting all further optimizations. + out_of_fuel: bool, +} + +/// The behavior of the CTFE engine when an error occurs with regards to backtraces. +#[derive(Clone, Copy)] +pub enum CtfeBacktrace { + /// Do nothing special, return the error as usual without a backtrace. + Disabled, + /// Capture a backtrace at the point the error is created and return it in the error + /// (to be printed later if/when the error ever actually gets shown to the user). + Capture, + /// Capture a backtrace at the point the error is created and immediately print it out. + Immediate, +} + +/// New-type wrapper around `usize` for representing limits. Ensures that comparisons against +/// limits are consistent throughout the compiler. +#[derive(Clone, Copy, Debug, HashStable_Generic)] +pub struct Limit(pub usize); + +impl Limit { + /// Create a new limit from a `usize`. + pub fn new(value: usize) -> Self { + Limit(value) + } + + /// Check that `value` is within the limit. Ensures that the same comparisons are used + /// throughout the compiler, as mismatches can cause ICEs, see #72540. + #[inline] + pub fn value_within_limit(&self, value: usize) -> bool { + value <= self.0 + } +} + +impl From for Limit { + fn from(value: usize) -> Self { + Self::new(value) + } +} + +impl fmt::Display for Limit { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl Div for Limit { + type Output = Limit; + + fn div(self, rhs: usize) -> Self::Output { + Limit::new(self.0 / rhs) + } +} + +impl Mul for Limit { + type Output = Limit; + + fn mul(self, rhs: usize) -> Self::Output { + Limit::new(self.0 * rhs) + } +} + +#[derive(Clone, Copy, Debug, HashStable_Generic)] +pub struct Limits { + /// The maximum recursion limit for potentially infinitely recursive + /// operations such as auto-dereference and monomorphization. + pub recursion_limit: Limit, + /// The size at which the `large_assignments` lint starts + /// being emitted. + pub move_size_limit: Limit, + /// The maximum length of types during monomorphization. + pub type_length_limit: Limit, + /// The maximum blocks a const expression can evaluate. + pub const_eval_limit: Limit, +} + +/// Represents the data associated with a compilation +/// session for a single crate. +pub struct Session { + pub target: Target, + pub host: Target, + pub opts: config::Options, + pub host_tlib_path: Lrc, + pub target_tlib_path: Lrc, + pub parse_sess: ParseSess, + pub sysroot: PathBuf, + /// The name of the root source file of the crate, in the local file system. + /// `None` means that there is no source file. + pub local_crate_source_file: Option, + + crate_types: OnceCell>, + /// The `stable_crate_id` is constructed out of the crate name and all the + /// `-C metadata` arguments passed to the compiler. Its value forms a unique + /// global identifier for the crate. It is used to allow multiple crates + /// with the same name to coexist. See the + /// `rustc_codegen_llvm::back::symbol_names` module for more information. + pub stable_crate_id: OnceCell, + + features: OnceCell, + + incr_comp_session: OneThread>, + /// Used for incremental compilation tests. Will only be populated if + /// `-Zquery-dep-graph` is specified. + pub cgu_reuse_tracker: CguReuseTracker, + + /// Used by `-Z self-profile`. + pub prof: SelfProfilerRef, + + /// Some measurements that are being gathered during compilation. + pub perf_stats: PerfStats, + + /// Data about code being compiled, gathered during compilation. + pub code_stats: CodeStats, + + /// Tracks fuel info if `-zfuel=crate=n` is specified. + optimization_fuel: Lock, + + /// Always set to zero and incremented so that we can print fuel expended by a crate. + pub print_fuel: AtomicU64, + + /// Loaded up early on in the initialization of this `Session` to avoid + /// false positives about a job server in our environment. + pub jobserver: Client, + + /// Cap lint level specified by a driver specifically. + pub driver_lint_caps: FxHashMap, + + /// Tracks the current behavior of the CTFE engine when an error occurs. + /// Options range from returning the error without a backtrace to returning an error + /// and immediately printing the backtrace to stderr. + /// The `Lock` is only used by miri to allow setting `ctfe_backtrace` after analysis when + /// `MIRI_BACKTRACE` is set. This makes it only apply to miri's errors and not to all CTFE + /// errors. + pub ctfe_backtrace: Lock, + + /// This tracks where `-Zunleash-the-miri-inside-of-you` was used to get around a + /// const check, optionally with the relevant feature gate. We use this to + /// warn about unleashing, but with a single diagnostic instead of dozens that + /// drown everything else in noise. + miri_unleashed_features: Lock)>>, + + /// Architecture to use for interpreting asm!. + pub asm_arch: Option, + + /// Set of enabled features for the current target. + pub target_features: FxHashSet, + + /// Set of enabled features for the current target, including unstable ones. + pub unstable_target_features: FxHashSet, +} + +pub struct PerfStats { + /// The accumulated time spent on computing symbol hashes. + pub symbol_hash_time: Lock, + /// Total number of values canonicalized queries constructed. + pub queries_canonicalized: AtomicUsize, + /// Number of times this query is invoked. + pub normalize_generic_arg_after_erasing_regions: AtomicUsize, + /// Number of times this query is invoked. + pub normalize_projection_ty: AtomicUsize, +} + +/// Trait implemented by error types. This should not be implemented manually. Instead, use +/// `#[derive(SessionDiagnostic)]` -- see [rustc_macros::SessionDiagnostic]. +#[rustc_diagnostic_item = "SessionDiagnostic"] +pub trait SessionDiagnostic<'a, T: EmissionGuarantee = ErrorGuaranteed> { + /// Write out as a diagnostic out of `sess`. + #[must_use] + fn into_diagnostic(self, sess: &'a ParseSess) -> DiagnosticBuilder<'a, T>; +} + +impl Session { + pub fn miri_unleashed_feature(&self, span: Span, feature_gate: Option) { + self.miri_unleashed_features.lock().push((span, feature_gate)); + } + + fn check_miri_unleashed_features(&self) { + let unleashed_features = self.miri_unleashed_features.lock(); + if !unleashed_features.is_empty() { + let mut must_err = false; + // Create a diagnostic pointing at where things got unleashed. + let mut diag = self.struct_warn("skipping const checks"); + for &(span, feature_gate) in unleashed_features.iter() { + // FIXME: `span_label` doesn't do anything, so we use "help" as a hack. + if let Some(gate) = feature_gate { + diag.span_help(span, &format!("skipping check for `{gate}` feature")); + // The unleash flag must *not* be used to just "hack around" feature gates. + must_err = true; + } else { + diag.span_help(span, "skipping check that does not even have a feature gate"); + } + } + diag.emit(); + // If we should err, make sure we did. + if must_err && self.has_errors().is_none() { + // We have skipped a feature gate, and not run into other errors... reject. + self.err( + "`-Zunleash-the-miri-inside-of-you` may not be used to circumvent feature \ + gates, except when testing error paths in the CTFE engine", + ); + } + } + } + + /// Invoked all the way at the end to finish off diagnostics printing. + pub fn finish_diagnostics(&self, registry: &Registry) { + self.check_miri_unleashed_features(); + self.diagnostic().print_error_count(registry); + self.emit_future_breakage(); + } + + fn emit_future_breakage(&self) { + if !self.opts.json_future_incompat { + return; + } + + let diags = self.diagnostic().take_future_breakage_diagnostics(); + if diags.is_empty() { + return; + } + self.parse_sess.span_diagnostic.emit_future_breakage_report(diags); + } + + pub fn local_stable_crate_id(&self) -> StableCrateId { + self.stable_crate_id.get().copied().unwrap() + } + + pub fn crate_types(&self) -> &[CrateType] { + self.crate_types.get().unwrap().as_slice() + } + + pub fn init_crate_types(&self, crate_types: Vec) { + self.crate_types.set(crate_types).expect("`crate_types` was initialized twice") + } + + #[rustc_lint_diagnostics] + pub fn struct_span_warn>( + &self, + sp: S, + msg: impl Into, + ) -> DiagnosticBuilder<'_, ()> { + self.diagnostic().struct_span_warn(sp, msg) + } + #[rustc_lint_diagnostics] + pub fn struct_span_warn_with_expectation>( + &self, + sp: S, + msg: impl Into, + id: lint::LintExpectationId, + ) -> DiagnosticBuilder<'_, ()> { + self.diagnostic().struct_span_warn_with_expectation(sp, msg, id) + } + #[rustc_lint_diagnostics] + pub fn struct_span_warn_with_code>( + &self, + sp: S, + msg: impl Into, + code: DiagnosticId, + ) -> DiagnosticBuilder<'_, ()> { + self.diagnostic().struct_span_warn_with_code(sp, msg, code) + } + #[rustc_lint_diagnostics] + pub fn struct_warn(&self, msg: impl Into) -> DiagnosticBuilder<'_, ()> { + self.diagnostic().struct_warn(msg) + } + #[rustc_lint_diagnostics] + pub fn struct_warn_with_expectation( + &self, + msg: impl Into, + id: lint::LintExpectationId, + ) -> DiagnosticBuilder<'_, ()> { + self.diagnostic().struct_warn_with_expectation(msg, id) + } + #[rustc_lint_diagnostics] + pub fn struct_span_allow>( + &self, + sp: S, + msg: impl Into, + ) -> DiagnosticBuilder<'_, ()> { + self.diagnostic().struct_span_allow(sp, msg) + } + #[rustc_lint_diagnostics] + pub fn struct_allow(&self, msg: impl Into) -> DiagnosticBuilder<'_, ()> { + self.diagnostic().struct_allow(msg) + } + #[rustc_lint_diagnostics] + pub fn struct_expect( + &self, + msg: impl Into, + id: lint::LintExpectationId, + ) -> DiagnosticBuilder<'_, ()> { + self.diagnostic().struct_expect(msg, id) + } + #[rustc_lint_diagnostics] + pub fn struct_span_err>( + &self, + sp: S, + msg: impl Into, + ) -> DiagnosticBuilder<'_, ErrorGuaranteed> { + self.diagnostic().struct_span_err(sp, msg) + } + #[rustc_lint_diagnostics] + pub fn struct_span_err_with_code>( + &self, + sp: S, + msg: impl Into, + code: DiagnosticId, + ) -> DiagnosticBuilder<'_, ErrorGuaranteed> { + self.diagnostic().struct_span_err_with_code(sp, msg, code) + } + // FIXME: This method should be removed (every error should have an associated error code). + #[rustc_lint_diagnostics] + pub fn struct_err( + &self, + msg: impl Into, + ) -> DiagnosticBuilder<'_, ErrorGuaranteed> { + self.parse_sess.struct_err(msg) + } + #[rustc_lint_diagnostics] + pub fn struct_err_with_code( + &self, + msg: impl Into, + code: DiagnosticId, + ) -> DiagnosticBuilder<'_, ErrorGuaranteed> { + self.diagnostic().struct_err_with_code(msg, code) + } + #[rustc_lint_diagnostics] + pub fn struct_warn_with_code( + &self, + msg: impl Into, + code: DiagnosticId, + ) -> DiagnosticBuilder<'_, ()> { + self.diagnostic().struct_warn_with_code(msg, code) + } + #[rustc_lint_diagnostics] + pub fn struct_span_fatal>( + &self, + sp: S, + msg: impl Into, + ) -> DiagnosticBuilder<'_, !> { + self.diagnostic().struct_span_fatal(sp, msg) + } + #[rustc_lint_diagnostics] + pub fn struct_span_fatal_with_code>( + &self, + sp: S, + msg: impl Into, + code: DiagnosticId, + ) -> DiagnosticBuilder<'_, !> { + self.diagnostic().struct_span_fatal_with_code(sp, msg, code) + } + #[rustc_lint_diagnostics] + pub fn struct_fatal(&self, msg: impl Into) -> DiagnosticBuilder<'_, !> { + self.diagnostic().struct_fatal(msg) + } + + #[rustc_lint_diagnostics] + pub fn span_fatal>(&self, sp: S, msg: impl Into) -> ! { + self.diagnostic().span_fatal(sp, msg) + } + #[rustc_lint_diagnostics] + pub fn span_fatal_with_code>( + &self, + sp: S, + msg: impl Into, + code: DiagnosticId, + ) -> ! { + self.diagnostic().span_fatal_with_code(sp, msg, code) + } + #[rustc_lint_diagnostics] + pub fn fatal(&self, msg: impl Into) -> ! { + self.diagnostic().fatal(msg).raise() + } + #[rustc_lint_diagnostics] + pub fn span_err_or_warn>( + &self, + is_warning: bool, + sp: S, + msg: impl Into, + ) { + if is_warning { + self.span_warn(sp, msg); + } else { + self.span_err(sp, msg); + } + } + #[rustc_lint_diagnostics] + pub fn span_err>( + &self, + sp: S, + msg: impl Into, + ) -> ErrorGuaranteed { + self.diagnostic().span_err(sp, msg) + } + #[rustc_lint_diagnostics] + pub fn span_err_with_code>( + &self, + sp: S, + msg: impl Into, + code: DiagnosticId, + ) { + self.diagnostic().span_err_with_code(sp, msg, code) + } + #[rustc_lint_diagnostics] + pub fn err(&self, msg: impl Into) -> ErrorGuaranteed { + self.diagnostic().err(msg) + } + pub fn create_err<'a>( + &'a self, + err: impl SessionDiagnostic<'a>, + ) -> DiagnosticBuilder<'a, ErrorGuaranteed> { + self.parse_sess.create_err(err) + } + pub fn create_feature_err<'a>( + &'a self, + err: impl SessionDiagnostic<'a>, + feature: Symbol, + ) -> DiagnosticBuilder<'a, ErrorGuaranteed> { + let mut err = self.parse_sess.create_err(err); + add_feature_diagnostics(&mut err, &self.parse_sess, feature); + err + } + pub fn emit_err<'a>(&'a self, err: impl SessionDiagnostic<'a>) -> ErrorGuaranteed { + self.parse_sess.emit_err(err) + } + pub fn create_warning<'a>( + &'a self, + err: impl SessionDiagnostic<'a, ()>, + ) -> DiagnosticBuilder<'a, ()> { + self.parse_sess.create_warning(err) + } + pub fn emit_warning<'a>(&'a self, warning: impl SessionDiagnostic<'a, ()>) { + self.parse_sess.emit_warning(warning) + } + #[inline] + pub fn err_count(&self) -> usize { + self.diagnostic().err_count() + } + pub fn has_errors(&self) -> Option { + self.diagnostic().has_errors() + } + pub fn has_errors_or_delayed_span_bugs(&self) -> bool { + self.diagnostic().has_errors_or_delayed_span_bugs() + } + pub fn abort_if_errors(&self) { + self.diagnostic().abort_if_errors(); + } + pub fn compile_status(&self) -> Result<(), ErrorGuaranteed> { + if let Some(reported) = self.diagnostic().has_errors_or_lint_errors() { + let _ = self.diagnostic().emit_stashed_diagnostics(); + Err(reported) + } else { + Ok(()) + } + } + // FIXME(matthewjasper) Remove this method, it should never be needed. + pub fn track_errors(&self, f: F) -> Result + where + F: FnOnce() -> T, + { + let old_count = self.err_count(); + let result = f(); + if self.err_count() == old_count { + Ok(result) + } else { + Err(ErrorGuaranteed::unchecked_claim_error_was_emitted()) + } + } + pub fn span_warn>(&self, sp: S, msg: impl Into) { + self.diagnostic().span_warn(sp, msg) + } + pub fn span_warn_with_code>( + &self, + sp: S, + msg: impl Into, + code: DiagnosticId, + ) { + self.diagnostic().span_warn_with_code(sp, msg, code) + } + pub fn warn(&self, msg: impl Into) { + self.diagnostic().warn(msg) + } + /// Delay a span_bug() call until abort_if_errors() + #[track_caller] + pub fn delay_span_bug>( + &self, + sp: S, + msg: impl Into, + ) -> ErrorGuaranteed { + self.diagnostic().delay_span_bug(sp, msg) + } + + /// Used for code paths of expensive computations that should only take place when + /// warnings or errors are emitted. If no messages are emitted ("good path"), then + /// it's likely a bug. + pub fn delay_good_path_bug(&self, msg: impl Into) { + if self.opts.unstable_opts.print_type_sizes + || self.opts.unstable_opts.query_dep_graph + || self.opts.unstable_opts.dump_mir.is_some() + || self.opts.unstable_opts.unpretty.is_some() + || self.opts.output_types.contains_key(&OutputType::Mir) + || std::env::var_os("RUSTC_LOG").is_some() + { + return; + } + + self.diagnostic().delay_good_path_bug(msg) + } + + pub fn note_without_error(&self, msg: impl Into) { + self.diagnostic().note_without_error(msg) + } + pub fn span_note_without_error>( + &self, + sp: S, + msg: impl Into, + ) { + self.diagnostic().span_note_without_error(sp, msg) + } + pub fn struct_note_without_error( + &self, + msg: impl Into, + ) -> DiagnosticBuilder<'_, ()> { + self.diagnostic().struct_note_without_error(msg) + } + + #[inline] + pub fn diagnostic(&self) -> &rustc_errors::Handler { + &self.parse_sess.span_diagnostic + } + + #[inline] + pub fn source_map(&self) -> &SourceMap { + self.parse_sess.source_map() + } + + pub fn time_passes(&self) -> bool { + self.opts.time_passes() + } + + /// Returns `true` if internal lints should be added to the lint store - i.e. if + /// `-Zunstable-options` is provided and this isn't rustdoc (internal lints can trigger errors + /// to be emitted under rustdoc). + pub fn enable_internal_lints(&self) -> bool { + self.unstable_options() && !self.opts.actually_rustdoc + } + + pub fn instrument_coverage(&self) -> bool { + self.opts.cg.instrument_coverage() != InstrumentCoverage::Off + } + + pub fn instrument_coverage_except_unused_generics(&self) -> bool { + self.opts.cg.instrument_coverage() == InstrumentCoverage::ExceptUnusedGenerics + } + + pub fn instrument_coverage_except_unused_functions(&self) -> bool { + self.opts.cg.instrument_coverage() == InstrumentCoverage::ExceptUnusedFunctions + } + + /// Gets the features enabled for the current compilation session. + /// DO NOT USE THIS METHOD if there is a TyCtxt available, as it circumvents + /// dependency tracking. Use tcx.features() instead. + #[inline] + pub fn features_untracked(&self) -> &rustc_feature::Features { + self.features.get().unwrap() + } + + pub fn init_features(&self, features: rustc_feature::Features) { + match self.features.set(features) { + Ok(()) => {} + Err(_) => panic!("`features` was initialized twice"), + } + } + + pub fn is_sanitizer_cfi_enabled(&self) -> bool { + self.opts.unstable_opts.sanitizer.contains(SanitizerSet::CFI) + } + + /// Check whether this compile session and crate type use static crt. + pub fn crt_static(&self, crate_type: Option) -> bool { + if !self.target.crt_static_respected { + // If the target does not opt in to crt-static support, use its default. + return self.target.crt_static_default; + } + + let requested_features = self.opts.cg.target_feature.split(','); + let found_negative = requested_features.clone().any(|r| r == "-crt-static"); + let found_positive = requested_features.clone().any(|r| r == "+crt-static"); + + // JUSTIFICATION: necessary use of crate_types directly (see FIXME below) + #[cfg_attr(not(bootstrap), allow(rustc::bad_opt_access))] + if found_positive || found_negative { + found_positive + } else if crate_type == Some(CrateType::ProcMacro) + || crate_type == None && self.opts.crate_types.contains(&CrateType::ProcMacro) + { + // FIXME: When crate_type is not available, + // we use compiler options to determine the crate_type. + // We can't check `#![crate_type = "proc-macro"]` here. + false + } else { + self.target.crt_static_default + } + } + + pub fn is_wasi_reactor(&self) -> bool { + self.target.options.os == "wasi" + && matches!( + self.opts.unstable_opts.wasi_exec_model, + Some(config::WasiExecModel::Reactor) + ) + } + + pub fn target_can_use_split_dwarf(&self) -> bool { + !self.target.is_like_windows && !self.target.is_like_osx + } + + pub fn generate_proc_macro_decls_symbol(&self, stable_crate_id: StableCrateId) -> String { + format!("__rustc_proc_macro_decls_{:08x}__", stable_crate_id.to_u64()) + } + + pub fn target_filesearch(&self, kind: PathKind) -> filesearch::FileSearch<'_> { + filesearch::FileSearch::new( + &self.sysroot, + self.opts.target_triple.triple(), + &self.opts.search_paths, + &self.target_tlib_path, + kind, + ) + } + pub fn host_filesearch(&self, kind: PathKind) -> filesearch::FileSearch<'_> { + filesearch::FileSearch::new( + &self.sysroot, + config::host_triple(), + &self.opts.search_paths, + &self.host_tlib_path, + kind, + ) + } + + /// Returns a list of directories where target-specific tool binaries are located. + pub fn get_tools_search_paths(&self, self_contained: bool) -> Vec { + let rustlib_path = rustc_target::target_rustlib_path(&self.sysroot, &config::host_triple()); + let p = PathBuf::from_iter([ + Path::new(&self.sysroot), + Path::new(&rustlib_path), + Path::new("bin"), + ]); + if self_contained { vec![p.clone(), p.join("self-contained")] } else { vec![p] } + } + + pub fn init_incr_comp_session( + &self, + session_dir: PathBuf, + lock_file: flock::Lock, + load_dep_graph: bool, + ) { + let mut incr_comp_session = self.incr_comp_session.borrow_mut(); + + if let IncrCompSession::NotInitialized = *incr_comp_session { + } else { + panic!("Trying to initialize IncrCompSession `{:?}`", *incr_comp_session) + } + + *incr_comp_session = + IncrCompSession::Active { session_directory: session_dir, lock_file, load_dep_graph }; + } + + pub fn finalize_incr_comp_session(&self, new_directory_path: PathBuf) { + let mut incr_comp_session = self.incr_comp_session.borrow_mut(); + + if let IncrCompSession::Active { .. } = *incr_comp_session { + } else { + panic!("trying to finalize `IncrCompSession` `{:?}`", *incr_comp_session); + } + + // Note: this will also drop the lock file, thus unlocking the directory. + *incr_comp_session = IncrCompSession::Finalized { session_directory: new_directory_path }; + } + + pub fn mark_incr_comp_session_as_invalid(&self) { + let mut incr_comp_session = self.incr_comp_session.borrow_mut(); + + let session_directory = match *incr_comp_session { + IncrCompSession::Active { ref session_directory, .. } => session_directory.clone(), + IncrCompSession::InvalidBecauseOfErrors { .. } => return, + _ => panic!("trying to invalidate `IncrCompSession` `{:?}`", *incr_comp_session), + }; + + // Note: this will also drop the lock file, thus unlocking the directory. + *incr_comp_session = IncrCompSession::InvalidBecauseOfErrors { session_directory }; + } + + pub fn incr_comp_session_dir(&self) -> cell::Ref<'_, PathBuf> { + let incr_comp_session = self.incr_comp_session.borrow(); + cell::Ref::map(incr_comp_session, |incr_comp_session| match *incr_comp_session { + IncrCompSession::NotInitialized => panic!( + "trying to get session directory from `IncrCompSession`: {:?}", + *incr_comp_session, + ), + IncrCompSession::Active { ref session_directory, .. } + | IncrCompSession::Finalized { ref session_directory } + | IncrCompSession::InvalidBecauseOfErrors { ref session_directory } => { + session_directory + } + }) + } + + pub fn incr_comp_session_dir_opt(&self) -> Option> { + self.opts.incremental.as_ref().map(|_| self.incr_comp_session_dir()) + } + + pub fn print_perf_stats(&self) { + eprintln!( + "Total time spent computing symbol hashes: {}", + duration_to_secs_str(*self.perf_stats.symbol_hash_time.lock()) + ); + eprintln!( + "Total queries canonicalized: {}", + self.perf_stats.queries_canonicalized.load(Ordering::Relaxed) + ); + eprintln!( + "normalize_generic_arg_after_erasing_regions: {}", + self.perf_stats.normalize_generic_arg_after_erasing_regions.load(Ordering::Relaxed) + ); + eprintln!( + "normalize_projection_ty: {}", + self.perf_stats.normalize_projection_ty.load(Ordering::Relaxed) + ); + } + + /// We want to know if we're allowed to do an optimization for crate foo from -z fuel=foo=n. + /// This expends fuel if applicable, and records fuel if applicable. + pub fn consider_optimizing String>(&self, crate_name: &str, msg: T) -> bool { + let mut ret = true; + if let Some((ref c, _)) = self.opts.unstable_opts.fuel { + if c == crate_name { + assert_eq!(self.threads(), 1); + let mut fuel = self.optimization_fuel.lock(); + ret = fuel.remaining != 0; + if fuel.remaining == 0 && !fuel.out_of_fuel { + if self.diagnostic().can_emit_warnings() { + // We only call `msg` in case we can actually emit warnings. + // Otherwise, this could cause a `delay_good_path_bug` to + // trigger (issue #79546). + self.warn(&format!("optimization-fuel-exhausted: {}", msg())); + } + fuel.out_of_fuel = true; + } else if fuel.remaining > 0 { + fuel.remaining -= 1; + } + } + } + if let Some(ref c) = self.opts.unstable_opts.print_fuel { + if c == crate_name { + assert_eq!(self.threads(), 1); + self.print_fuel.fetch_add(1, SeqCst); + } + } + ret + } + + pub fn rust_2015(&self) -> bool { + self.edition() == Edition::Edition2015 + } + + /// Are we allowed to use features from the Rust 2018 edition? + pub fn rust_2018(&self) -> bool { + self.edition() >= Edition::Edition2018 + } + + /// Are we allowed to use features from the Rust 2021 edition? + pub fn rust_2021(&self) -> bool { + self.edition() >= Edition::Edition2021 + } + + /// Are we allowed to use features from the Rust 2024 edition? + pub fn rust_2024(&self) -> bool { + self.edition() >= Edition::Edition2024 + } + + /// Returns `true` if we cannot skip the PLT for shared library calls. + pub fn needs_plt(&self) -> bool { + // Check if the current target usually needs PLT to be enabled. + // The user can use the command line flag to override it. + let needs_plt = self.target.needs_plt; + + let dbg_opts = &self.opts.unstable_opts; + + let relro_level = dbg_opts.relro_level.unwrap_or(self.target.relro_level); + + // Only enable this optimization by default if full relro is also enabled. + // In this case, lazy binding was already unavailable, so nothing is lost. + // This also ensures `-Wl,-z,now` is supported by the linker. + let full_relro = RelroLevel::Full == relro_level; + + // If user didn't explicitly forced us to use / skip the PLT, + // then try to skip it where possible. + dbg_opts.plt.unwrap_or(needs_plt || !full_relro) + } + + /// Checks if LLVM lifetime markers should be emitted. + pub fn emit_lifetime_markers(&self) -> bool { + self.opts.optimize != config::OptLevel::No + // AddressSanitizer uses lifetimes to detect use after scope bugs. + // MemorySanitizer uses lifetimes to detect use of uninitialized stack variables. + // HWAddressSanitizer will use lifetimes to detect use after scope bugs in the future. + || self.opts.unstable_opts.sanitizer.intersects(SanitizerSet::ADDRESS | SanitizerSet::MEMORY | SanitizerSet::HWADDRESS) + } + + pub fn is_proc_macro_attr(&self, attr: &Attribute) -> bool { + [sym::proc_macro, sym::proc_macro_attribute, sym::proc_macro_derive] + .iter() + .any(|kind| attr.has_name(*kind)) + } + + pub fn contains_name(&self, attrs: &[Attribute], name: Symbol) -> bool { + attrs.iter().any(|item| item.has_name(name)) + } + + pub fn find_by_name<'a>( + &'a self, + attrs: &'a [Attribute], + name: Symbol, + ) -> Option<&'a Attribute> { + attrs.iter().find(|attr| attr.has_name(name)) + } + + pub fn filter_by_name<'a>( + &'a self, + attrs: &'a [Attribute], + name: Symbol, + ) -> impl Iterator { + attrs.iter().filter(move |attr| attr.has_name(name)) + } + + pub fn first_attr_value_str_by_name( + &self, + attrs: &[Attribute], + name: Symbol, + ) -> Option { + attrs.iter().find(|at| at.has_name(name)).and_then(|at| at.value_str()) + } +} + +// JUSTIFICATION: defn of the suggested wrapper fns +#[cfg_attr(not(bootstrap), allow(rustc::bad_opt_access))] +impl Session { + pub fn verbose(&self) -> bool { + self.opts.unstable_opts.verbose + } + + pub fn instrument_mcount(&self) -> bool { + self.opts.unstable_opts.instrument_mcount + } + + pub fn time_llvm_passes(&self) -> bool { + self.opts.unstable_opts.time_llvm_passes + } + + pub fn meta_stats(&self) -> bool { + self.opts.unstable_opts.meta_stats + } + + pub fn asm_comments(&self) -> bool { + self.opts.unstable_opts.asm_comments + } + + pub fn verify_llvm_ir(&self) -> bool { + self.opts.unstable_opts.verify_llvm_ir || option_env!("RUSTC_VERIFY_LLVM_IR").is_some() + } + + pub fn print_llvm_passes(&self) -> bool { + self.opts.unstable_opts.print_llvm_passes + } + + pub fn binary_dep_depinfo(&self) -> bool { + self.opts.unstable_opts.binary_dep_depinfo + } + + pub fn mir_opt_level(&self) -> usize { + self.opts + .unstable_opts + .mir_opt_level + .unwrap_or_else(|| if self.opts.optimize != OptLevel::No { 2 } else { 1 }) + } + + /// Calculates the flavor of LTO to use for this compilation. + pub fn lto(&self) -> config::Lto { + // If our target has codegen requirements ignore the command line + if self.target.requires_lto { + return config::Lto::Fat; + } + + // If the user specified something, return that. If they only said `-C + // lto` and we've for whatever reason forced off ThinLTO via the CLI, + // then ensure we can't use a ThinLTO. + match self.opts.cg.lto { + config::LtoCli::Unspecified => { + // The compiler was invoked without the `-Clto` flag. Fall + // through to the default handling + } + config::LtoCli::No => { + // The user explicitly opted out of any kind of LTO + return config::Lto::No; + } + config::LtoCli::Yes | config::LtoCli::Fat | config::LtoCli::NoParam => { + // All of these mean fat LTO + return config::Lto::Fat; + } + config::LtoCli::Thin => { + return if self.opts.cli_forced_thinlto_off { + config::Lto::Fat + } else { + config::Lto::Thin + }; + } + } + + // Ok at this point the target doesn't require anything and the user + // hasn't asked for anything. Our next decision is whether or not + // we enable "auto" ThinLTO where we use multiple codegen units and + // then do ThinLTO over those codegen units. The logic below will + // either return `No` or `ThinLocal`. + + // If processing command line options determined that we're incompatible + // with ThinLTO (e.g., `-C lto --emit llvm-ir`) then return that option. + if self.opts.cli_forced_thinlto_off { + return config::Lto::No; + } + + // If `-Z thinlto` specified process that, but note that this is mostly + // a deprecated option now that `-C lto=thin` exists. + if let Some(enabled) = self.opts.unstable_opts.thinlto { + if enabled { + return config::Lto::ThinLocal; + } else { + return config::Lto::No; + } + } + + // If there's only one codegen unit and LTO isn't enabled then there's + // no need for ThinLTO so just return false. + if self.codegen_units() == 1 { + return config::Lto::No; + } + + // Now we're in "defaults" territory. By default we enable ThinLTO for + // optimized compiles (anything greater than O0). + match self.opts.optimize { + config::OptLevel::No => config::Lto::No, + _ => config::Lto::ThinLocal, + } + } + + /// Returns the panic strategy for this compile session. If the user explicitly selected one + /// using '-C panic', use that, otherwise use the panic strategy defined by the target. + pub fn panic_strategy(&self) -> PanicStrategy { + self.opts.cg.panic.unwrap_or(self.target.panic_strategy) + } + + pub fn fewer_names(&self) -> bool { + if let Some(fewer_names) = self.opts.unstable_opts.fewer_names { + fewer_names + } else { + let more_names = self.opts.output_types.contains_key(&OutputType::LlvmAssembly) + || self.opts.output_types.contains_key(&OutputType::Bitcode) + // AddressSanitizer and MemorySanitizer use alloca name when reporting an issue. + || self.opts.unstable_opts.sanitizer.intersects(SanitizerSet::ADDRESS | SanitizerSet::MEMORY); + !more_names + } + } + + pub fn unstable_options(&self) -> bool { + self.opts.unstable_opts.unstable_options + } + + pub fn is_nightly_build(&self) -> bool { + self.opts.unstable_features.is_nightly_build() + } + + pub fn overflow_checks(&self) -> bool { + self.opts.cg.overflow_checks.unwrap_or(self.opts.debug_assertions) + } + + pub fn relocation_model(&self) -> RelocModel { + self.opts.cg.relocation_model.unwrap_or(self.target.relocation_model) + } + + pub fn code_model(&self) -> Option { + self.opts.cg.code_model.or(self.target.code_model) + } + + pub fn tls_model(&self) -> TlsModel { + self.opts.unstable_opts.tls_model.unwrap_or(self.target.tls_model) + } + + pub fn split_debuginfo(&self) -> SplitDebuginfo { + self.opts.cg.split_debuginfo.unwrap_or(self.target.split_debuginfo) + } + + pub fn stack_protector(&self) -> StackProtector { + if self.target.options.supports_stack_protector { + self.opts.unstable_opts.stack_protector + } else { + StackProtector::None + } + } + + pub fn must_emit_unwind_tables(&self) -> bool { + // This is used to control the emission of the `uwtable` attribute on + // LLVM functions. + // + // Unwind tables are needed when compiling with `-C panic=unwind`, but + // LLVM won't omit unwind tables unless the function is also marked as + // `nounwind`, so users are allowed to disable `uwtable` emission. + // Historically rustc always emits `uwtable` attributes by default, so + // even they can be disabled, they're still emitted by default. + // + // On some targets (including windows), however, exceptions include + // other events such as illegal instructions, segfaults, etc. This means + // that on Windows we end up still needing unwind tables even if the `-C + // panic=abort` flag is passed. + // + // You can also find more info on why Windows needs unwind tables in: + // https://bugzilla.mozilla.org/show_bug.cgi?id=1302078 + // + // If a target requires unwind tables, then they must be emitted. + // Otherwise, we can defer to the `-C force-unwind-tables=` + // value, if it is provided, or disable them, if not. + self.target.requires_uwtable + || self.opts.cg.force_unwind_tables.unwrap_or( + self.panic_strategy() == PanicStrategy::Unwind || self.target.default_uwtable, + ) + } + + /// Returns the number of query threads that should be used for this + /// compilation + pub fn threads(&self) -> usize { + self.opts.unstable_opts.threads + } + + /// Returns the number of codegen units that should be used for this + /// compilation + pub fn codegen_units(&self) -> usize { + if let Some(n) = self.opts.cli_forced_codegen_units { + return n; + } + if let Some(n) = self.target.default_codegen_units { + return n as usize; + } + + // If incremental compilation is turned on, we default to a high number + // codegen units in order to reduce the "collateral damage" small + // changes cause. + if self.opts.incremental.is_some() { + return 256; + } + + // Why is 16 codegen units the default all the time? + // + // The main reason for enabling multiple codegen units by default is to + // leverage the ability for the codegen backend to do codegen and + // optimization in parallel. This allows us, especially for large crates, to + // make good use of all available resources on the machine once we've + // hit that stage of compilation. Large crates especially then often + // take a long time in codegen/optimization and this helps us amortize that + // cost. + // + // Note that a high number here doesn't mean that we'll be spawning a + // large number of threads in parallel. The backend of rustc contains + // global rate limiting through the `jobserver` crate so we'll never + // overload the system with too much work, but rather we'll only be + // optimizing when we're otherwise cooperating with other instances of + // rustc. + // + // Rather a high number here means that we should be able to keep a lot + // of idle cpus busy. By ensuring that no codegen unit takes *too* long + // to build we'll be guaranteed that all cpus will finish pretty closely + // to one another and we should make relatively optimal use of system + // resources + // + // Note that the main cost of codegen units is that it prevents LLVM + // from inlining across codegen units. Users in general don't have a lot + // of control over how codegen units are split up so it's our job in the + // compiler to ensure that undue performance isn't lost when using + // codegen units (aka we can't require everyone to slap `#[inline]` on + // everything). + // + // If we're compiling at `-O0` then the number doesn't really matter too + // much because performance doesn't matter and inlining is ok to lose. + // In debug mode we just want to try to guarantee that no cpu is stuck + // doing work that could otherwise be farmed to others. + // + // In release mode, however (O1 and above) performance does indeed + // matter! To recover the loss in performance due to inlining we'll be + // enabling ThinLTO by default (the function for which is just below). + // This will ensure that we recover any inlining wins we otherwise lost + // through codegen unit partitioning. + // + // --- + // + // Ok that's a lot of words but the basic tl;dr; is that we want a high + // number here -- but not too high. Additionally we're "safe" to have it + // always at the same number at all optimization levels. + // + // As a result 16 was chosen here! Mostly because it was a power of 2 + // and most benchmarks agreed it was roughly a local optimum. Not very + // scientific. + 16 + } + + pub fn teach(&self, code: &DiagnosticId) -> bool { + self.opts.unstable_opts.teach && self.diagnostic().must_teach(code) + } + + pub fn edition(&self) -> Edition { + self.opts.edition + } + + pub fn link_dead_code(&self) -> bool { + self.opts.cg.link_dead_code.unwrap_or(false) + } +} + +// JUSTIFICATION: part of session construction +#[cfg_attr(not(bootstrap), allow(rustc::bad_opt_access))] +fn default_emitter( + sopts: &config::Options, + registry: rustc_errors::registry::Registry, + source_map: Lrc, + bundle: Option>, + fallback_bundle: LazyFallbackBundle, + emitter_dest: Option>, +) -> Box { + let macro_backtrace = sopts.unstable_opts.macro_backtrace; + match (sopts.error_format, emitter_dest) { + (config::ErrorOutputType::HumanReadable(kind), dst) => { + let (short, color_config) = kind.unzip(); + + if let HumanReadableErrorType::AnnotateSnippet(_) = kind { + let emitter = AnnotateSnippetEmitterWriter::new( + Some(source_map), + bundle, + fallback_bundle, + short, + macro_backtrace, + ); + Box::new(emitter.ui_testing(sopts.unstable_opts.ui_testing)) + } else { + let emitter = match dst { + None => EmitterWriter::stderr( + color_config, + Some(source_map), + bundle, + fallback_bundle, + short, + sopts.unstable_opts.teach, + sopts.diagnostic_width, + macro_backtrace, + ), + Some(dst) => EmitterWriter::new( + dst, + Some(source_map), + bundle, + fallback_bundle, + short, + false, // no teach messages when writing to a buffer + false, // no colors when writing to a buffer + None, // no diagnostic width + macro_backtrace, + ), + }; + Box::new(emitter.ui_testing(sopts.unstable_opts.ui_testing)) + } + } + (config::ErrorOutputType::Json { pretty, json_rendered }, None) => Box::new( + JsonEmitter::stderr( + Some(registry), + source_map, + bundle, + fallback_bundle, + pretty, + json_rendered, + sopts.diagnostic_width, + macro_backtrace, + ) + .ui_testing(sopts.unstable_opts.ui_testing), + ), + (config::ErrorOutputType::Json { pretty, json_rendered }, Some(dst)) => Box::new( + JsonEmitter::new( + dst, + Some(registry), + source_map, + bundle, + fallback_bundle, + pretty, + json_rendered, + sopts.diagnostic_width, + macro_backtrace, + ) + .ui_testing(sopts.unstable_opts.ui_testing), + ), + } +} + +pub enum DiagnosticOutput { + Default, + Raw(Box), +} + +// JUSTIFICATION: literally session construction +#[cfg_attr(not(bootstrap), allow(rustc::bad_opt_access))] +pub fn build_session( + sopts: config::Options, + local_crate_source_file: Option, + bundle: Option>, + registry: rustc_errors::registry::Registry, + diagnostics_output: DiagnosticOutput, + driver_lint_caps: FxHashMap, + file_loader: Option>, + target_override: Option, +) -> Session { + // FIXME: This is not general enough to make the warning lint completely override + // normal diagnostic warnings, since the warning lint can also be denied and changed + // later via the source code. + let warnings_allow = sopts + .lint_opts + .iter() + .filter(|&&(ref key, _)| *key == "warnings") + .map(|&(_, ref level)| *level == lint::Allow) + .last() + .unwrap_or(false); + let cap_lints_allow = sopts.lint_cap.map_or(false, |cap| cap == lint::Allow); + let can_emit_warnings = !(warnings_allow || cap_lints_allow); + + let write_dest = match diagnostics_output { + DiagnosticOutput::Default => None, + DiagnosticOutput::Raw(write) => Some(write), + }; + + let sysroot = match &sopts.maybe_sysroot { + Some(sysroot) => sysroot.clone(), + None => filesearch::get_or_default_sysroot(), + }; + + let target_cfg = config::build_target_config(&sopts, target_override, &sysroot); + let host_triple = TargetTriple::from_triple(config::host_triple()); + let (host, target_warnings) = Target::search(&host_triple, &sysroot).unwrap_or_else(|e| { + early_error(sopts.error_format, &format!("Error loading host specification: {e}")) + }); + for warning in target_warnings.warning_messages() { + early_warn(sopts.error_format, &warning) + } + + let loader = file_loader.unwrap_or_else(|| Box::new(RealFileLoader)); + let hash_kind = sopts.unstable_opts.src_hash_algorithm.unwrap_or_else(|| { + if target_cfg.is_like_msvc { + SourceFileHashAlgorithm::Sha1 + } else { + SourceFileHashAlgorithm::Md5 + } + }); + let source_map = Lrc::new(SourceMap::with_file_loader_and_hash_kind( + loader, + sopts.file_path_mapping(), + hash_kind, + )); + + let fallback_bundle = fallback_fluent_bundle( + rustc_errors::DEFAULT_LOCALE_RESOURCES, + sopts.unstable_opts.translate_directionality_markers, + ); + let emitter = + default_emitter(&sopts, registry, source_map.clone(), bundle, fallback_bundle, write_dest); + + let span_diagnostic = rustc_errors::Handler::with_emitter_and_flags( + emitter, + sopts.unstable_opts.diagnostic_handler_flags(can_emit_warnings), + ); + + let self_profiler = if let SwitchWithOptPath::Enabled(ref d) = sopts.unstable_opts.self_profile + { + let directory = + if let Some(ref directory) = d { directory } else { std::path::Path::new(".") }; + + let profiler = SelfProfiler::new( + directory, + sopts.crate_name.as_deref(), + sopts.unstable_opts.self_profile_events.as_ref().map(|xs| &xs[..]), + &sopts.unstable_opts.self_profile_counter, + ); + match profiler { + Ok(profiler) => Some(Arc::new(profiler)), + Err(e) => { + early_warn(sopts.error_format, &format!("failed to create profiler: {e}")); + None + } + } + } else { + None + }; + + let mut parse_sess = ParseSess::with_span_handler(span_diagnostic, source_map); + parse_sess.assume_incomplete_release = sopts.unstable_opts.assume_incomplete_release; + + let host_triple = config::host_triple(); + let target_triple = sopts.target_triple.triple(); + let host_tlib_path = Lrc::new(SearchPath::from_sysroot_and_triple(&sysroot, host_triple)); + let target_tlib_path = if host_triple == target_triple { + // Use the same `SearchPath` if host and target triple are identical to avoid unnecessary + // rescanning of the target lib path and an unnecessary allocation. + host_tlib_path.clone() + } else { + Lrc::new(SearchPath::from_sysroot_and_triple(&sysroot, target_triple)) + }; + + let file_path_mapping = sopts.file_path_mapping(); + + let local_crate_source_file = + local_crate_source_file.map(|path| file_path_mapping.map_prefix(path).0); + + let optimization_fuel = Lock::new(OptimizationFuel { + remaining: sopts.unstable_opts.fuel.as_ref().map_or(0, |i| i.1), + out_of_fuel: false, + }); + let print_fuel = AtomicU64::new(0); + + let cgu_reuse_tracker = if sopts.unstable_opts.query_dep_graph { + CguReuseTracker::new() + } else { + CguReuseTracker::new_disabled() + }; + + let prof = + SelfProfilerRef::new(self_profiler, sopts.time_passes(), sopts.unstable_opts.time_passes); + + let ctfe_backtrace = Lock::new(match env::var("RUSTC_CTFE_BACKTRACE") { + Ok(ref val) if val == "immediate" => CtfeBacktrace::Immediate, + Ok(ref val) if val != "0" => CtfeBacktrace::Capture, + _ => CtfeBacktrace::Disabled, + }); + + let asm_arch = + if target_cfg.allow_asm { InlineAsmArch::from_str(&target_cfg.arch).ok() } else { None }; + + let sess = Session { + target: target_cfg, + host, + opts: sopts, + host_tlib_path, + target_tlib_path, + parse_sess, + sysroot, + local_crate_source_file, + crate_types: OnceCell::new(), + stable_crate_id: OnceCell::new(), + features: OnceCell::new(), + incr_comp_session: OneThread::new(RefCell::new(IncrCompSession::NotInitialized)), + cgu_reuse_tracker, + prof, + perf_stats: PerfStats { + symbol_hash_time: Lock::new(Duration::from_secs(0)), + queries_canonicalized: AtomicUsize::new(0), + normalize_generic_arg_after_erasing_regions: AtomicUsize::new(0), + normalize_projection_ty: AtomicUsize::new(0), + }, + code_stats: Default::default(), + optimization_fuel, + print_fuel, + jobserver: jobserver::client(), + driver_lint_caps, + ctfe_backtrace, + miri_unleashed_features: Lock::new(Default::default()), + asm_arch, + target_features: FxHashSet::default(), + unstable_target_features: FxHashSet::default(), + }; + + validate_commandline_args_with_session_available(&sess); + + sess +} + +/// Validate command line arguments with a `Session`. +/// +/// If it is useful to have a Session available already for validating a commandline argument, you +/// can do so here. +// JUSTIFICATION: needs to access args to validate them +#[cfg_attr(not(bootstrap), allow(rustc::bad_opt_access))] +fn validate_commandline_args_with_session_available(sess: &Session) { + // Since we don't know if code in an rlib will be linked to statically or + // dynamically downstream, rustc generates `__imp_` symbols that help linkers + // on Windows deal with this lack of knowledge (#27438). Unfortunately, + // these manually generated symbols confuse LLD when it tries to merge + // bitcode during ThinLTO. Therefore we disallow dynamic linking on Windows + // when compiling for LLD ThinLTO. This way we can validly just not generate + // the `dllimport` attributes and `__imp_` symbols in that case. + if sess.opts.cg.linker_plugin_lto.enabled() + && sess.opts.cg.prefer_dynamic + && sess.target.is_like_windows + { + sess.err( + "Linker plugin based LTO is not supported together with \ + `-C prefer-dynamic` when targeting Windows-like targets", + ); + } + + // Make sure that any given profiling data actually exists so LLVM can't + // decide to silently skip PGO. + if let Some(ref path) = sess.opts.cg.profile_use { + if !path.exists() { + sess.err(&format!( + "File `{}` passed to `-C profile-use` does not exist.", + path.display() + )); + } + } + + // Do the same for sample profile data. + if let Some(ref path) = sess.opts.unstable_opts.profile_sample_use { + if !path.exists() { + sess.err(&format!( + "File `{}` passed to `-C profile-sample-use` does not exist.", + path.display() + )); + } + } + + // Unwind tables cannot be disabled if the target requires them. + if let Some(include_uwtables) = sess.opts.cg.force_unwind_tables { + if sess.target.requires_uwtable && !include_uwtables { + sess.err( + "target requires unwind tables, they cannot be disabled with \ + `-C force-unwind-tables=no`.", + ); + } + } + + // Sanitizers can only be used on platforms that we know have working sanitizer codegen. + let supported_sanitizers = sess.target.options.supported_sanitizers; + let unsupported_sanitizers = sess.opts.unstable_opts.sanitizer - supported_sanitizers; + match unsupported_sanitizers.into_iter().count() { + 0 => {} + 1 => { + sess.err(&format!( + "{} sanitizer is not supported for this target", + unsupported_sanitizers + )); + } + _ => { + sess.err(&format!( + "{} sanitizers are not supported for this target", + unsupported_sanitizers + )); + } + } + // Cannot mix and match sanitizers. + let mut sanitizer_iter = sess.opts.unstable_opts.sanitizer.into_iter(); + if let (Some(first), Some(second)) = (sanitizer_iter.next(), sanitizer_iter.next()) { + sess.err(&format!("`-Zsanitizer={first}` is incompatible with `-Zsanitizer={second}`")); + } + + // Cannot enable crt-static with sanitizers on Linux + if sess.crt_static(None) && !sess.opts.unstable_opts.sanitizer.is_empty() { + sess.err( + "sanitizer is incompatible with statically linked libc, \ + disable it using `-C target-feature=-crt-static`", + ); + } + + // LLVM CFI and VFE both require LTO. + if sess.lto() != config::Lto::Fat { + if sess.is_sanitizer_cfi_enabled() { + sess.err("`-Zsanitizer=cfi` requires `-Clto`"); + } + if sess.opts.unstable_opts.virtual_function_elimination { + sess.err("`-Zvirtual-function-elimination` requires `-Clto`"); + } + } + + if sess.opts.unstable_opts.stack_protector != StackProtector::None { + if !sess.target.options.supports_stack_protector { + sess.warn(&format!( + "`-Z stack-protector={}` is not supported for target {} and will be ignored", + sess.opts.unstable_opts.stack_protector, sess.opts.target_triple + )) + } + } + + if let Some(dwarf_version) = sess.opts.unstable_opts.dwarf_version { + if dwarf_version > 5 { + sess.err(&format!("requested DWARF version {} is greater than 5", dwarf_version)); + } + } +} + +/// Holds data on the current incremental compilation session, if there is one. +#[derive(Debug)] +pub enum IncrCompSession { + /// This is the state the session will be in until the incr. comp. dir is + /// needed. + NotInitialized, + /// This is the state during which the session directory is private and can + /// be modified. + Active { session_directory: PathBuf, lock_file: flock::Lock, load_dep_graph: bool }, + /// This is the state after the session directory has been finalized. In this + /// state, the contents of the directory must not be modified any more. + Finalized { session_directory: PathBuf }, + /// This is an error state that is reached when some compilation error has + /// occurred. It indicates that the contents of the session directory must + /// not be used, since they might be invalid. + InvalidBecauseOfErrors { session_directory: PathBuf }, +} + +fn early_error_handler(output: config::ErrorOutputType) -> rustc_errors::Handler { + let fallback_bundle = fallback_fluent_bundle(rustc_errors::DEFAULT_LOCALE_RESOURCES, false); + let emitter: Box = match output { + config::ErrorOutputType::HumanReadable(kind) => { + let (short, color_config) = kind.unzip(); + Box::new(EmitterWriter::stderr( + color_config, + None, + None, + fallback_bundle, + short, + false, + None, + false, + )) + } + config::ErrorOutputType::Json { pretty, json_rendered } => { + Box::new(JsonEmitter::basic(pretty, json_rendered, None, fallback_bundle, None, false)) + } + }; + rustc_errors::Handler::with_emitter(true, None, emitter) +} + +pub fn early_error_no_abort(output: config::ErrorOutputType, msg: &str) -> ErrorGuaranteed { + early_error_handler(output).struct_err(msg).emit() +} + +pub fn early_error(output: config::ErrorOutputType, msg: &str) -> ! { + early_error_handler(output).struct_fatal(msg).emit() +} + +pub fn early_warn(output: config::ErrorOutputType, msg: &str) { + early_error_handler(output).struct_warn(msg).emit() +} diff --git a/compiler/rustc_session/src/utils.rs b/compiler/rustc_session/src/utils.rs new file mode 100644 index 000000000..9a4f6f9f9 --- /dev/null +++ b/compiler/rustc_session/src/utils.rs @@ -0,0 +1,93 @@ +use crate::session::Session; +use rustc_data_structures::profiling::VerboseTimingGuard; +use std::path::{Path, PathBuf}; + +impl Session { + pub fn timer<'a>(&'a self, what: &'static str) -> VerboseTimingGuard<'a> { + self.prof.verbose_generic_activity(what) + } + pub fn time(&self, what: &'static str, f: impl FnOnce() -> R) -> R { + self.prof.verbose_generic_activity(what).run(f) + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Encodable, Decodable)] +#[derive(HashStable_Generic)] +pub enum NativeLibKind { + /// Static library (e.g. `libfoo.a` on Linux or `foo.lib` on Windows/MSVC) + Static { + /// Whether to bundle objects from static library into produced rlib + bundle: Option, + /// Whether to link static library without throwing any object files away + whole_archive: Option, + }, + /// Dynamic library (e.g. `libfoo.so` on Linux) + /// or an import library corresponding to a dynamic library (e.g. `foo.lib` on Windows/MSVC). + Dylib { + /// Whether the dynamic library will be linked only if it satisfies some undefined symbols + as_needed: Option, + }, + /// Dynamic library (e.g. `foo.dll` on Windows) without a corresponding import library. + RawDylib, + /// A macOS-specific kind of dynamic libraries. + Framework { + /// Whether the framework will be linked only if it satisfies some undefined symbols + as_needed: Option, + }, + /// Argument which is passed to linker, relative order with libraries and other arguments + /// is preserved + LinkArg, + /// The library kind wasn't specified, `Dylib` is currently used as a default. + Unspecified, +} + +impl NativeLibKind { + pub fn has_modifiers(&self) -> bool { + match self { + NativeLibKind::Static { bundle, whole_archive } => { + bundle.is_some() || whole_archive.is_some() + } + NativeLibKind::Dylib { as_needed } | NativeLibKind::Framework { as_needed } => { + as_needed.is_some() + } + NativeLibKind::RawDylib | NativeLibKind::Unspecified | NativeLibKind::LinkArg => false, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Encodable, Decodable)] +#[derive(HashStable_Generic)] +pub struct NativeLib { + pub name: String, + pub new_name: Option, + pub kind: NativeLibKind, + pub verbatim: Option, +} + +impl NativeLib { + pub fn has_modifiers(&self) -> bool { + self.verbatim.is_some() || self.kind.has_modifiers() + } +} + +/// A path that has been canonicalized along with its original, non-canonicalized form +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] +pub struct CanonicalizedPath { + // Optional since canonicalization can sometimes fail + canonicalized: Option, + original: PathBuf, +} + +impl CanonicalizedPath { + pub fn new(path: &Path) -> Self { + Self { original: path.to_owned(), canonicalized: std::fs::canonicalize(path).ok() } + } + + pub fn canonicalized(&self) -> &PathBuf { + self.canonicalized.as_ref().unwrap_or(self.original()) + } + + pub fn original(&self) -> &PathBuf { + &self.original + } +} -- cgit v1.2.3