about summary refs log tree commit diff
path: root/compiler/rustc_session/src
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_session/src')
-rw-r--r--compiler/rustc_session/src/code_stats.rs270
-rw-r--r--compiler/rustc_session/src/config.rs3422
-rw-r--r--compiler/rustc_session/src/config/cfg.rs463
-rw-r--r--compiler/rustc_session/src/config/sigpipe.rs25
-rw-r--r--compiler/rustc_session/src/cstore.rs240
-rw-r--r--compiler/rustc_session/src/errors.rs513
-rw-r--r--compiler/rustc_session/src/filesearch.rs277
-rw-r--r--compiler/rustc_session/src/lib.rs40
-rw-r--r--compiler/rustc_session/src/options.rs2201
-rw-r--r--compiler/rustc_session/src/output.rs266
-rw-r--r--compiler/rustc_session/src/parse.rs348
-rw-r--r--compiler/rustc_session/src/search_paths.rs117
-rw-r--r--compiler/rustc_session/src/session.rs1543
-rw-r--r--compiler/rustc_session/src/utils.rs179
-rw-r--r--compiler/rustc_session/src/version.rs29
15 files changed, 9933 insertions, 0 deletions
diff --git a/compiler/rustc_session/src/code_stats.rs b/compiler/rustc_session/src/code_stats.rs
new file mode 100644
index 00000000000..f3c21992784
--- /dev/null
+++ b/compiler/rustc_session/src/code_stats.rs
@@ -0,0 +1,270 @@
+use std::cmp;
+
+use rustc_abi::{Align, Size};
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_data_structures::sync::Lock;
+use rustc_span::Symbol;
+use rustc_span::def_id::DefId;
+
+#[derive(Clone, PartialEq, Eq, Hash, Debug)]
+pub struct VariantInfo {
+    pub name: Option<Symbol>,
+    pub kind: SizeKind,
+    pub size: u64,
+    pub align: u64,
+    pub fields: Vec<FieldInfo>,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub enum SizeKind {
+    Exact,
+    Min,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub enum FieldKind {
+    AdtField,
+    Upvar,
+    CoroutineLocal,
+}
+
+impl std::fmt::Display for FieldKind {
+    fn fmt(&self, w: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            FieldKind::AdtField => write!(w, "field"),
+            FieldKind::Upvar => write!(w, "upvar"),
+            FieldKind::CoroutineLocal => write!(w, "local"),
+        }
+    }
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub struct FieldInfo {
+    pub kind: FieldKind,
+    pub name: Symbol,
+    pub offset: u64,
+    pub size: u64,
+    pub align: u64,
+    /// Name of the type of this field.
+    /// Present only if the creator thought that this would be important for identifying the field,
+    /// typically because the field name is uninformative.
+    pub type_name: Option<Symbol>,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub enum DataTypeKind {
+    Struct,
+    Union,
+    Enum,
+    Closure,
+    Coroutine,
+}
+
+#[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<u64>,
+    pub variants: Vec<VariantInfo>,
+}
+
+pub struct VTableSizeInfo {
+    pub trait_name: String,
+
+    /// Number of entries in a vtable with the current algorithm
+    /// (i.e. with upcasting).
+    pub entries: usize,
+
+    /// Number of entries in a vtable, as-if we did not have trait upcasting.
+    pub entries_ignoring_upcasting: usize,
+
+    /// Number of entries in a vtable needed solely for upcasting
+    /// (i.e. `entries - entries_ignoring_upcasting`).
+    pub entries_for_upcasting: usize,
+
+    /// Cost of having upcasting in % relative to the number of entries without
+    /// upcasting (i.e. `entries_for_upcasting / entries_ignoring_upcasting * 100%`).
+    pub upcasting_cost_percent: f64,
+}
+
+#[derive(Default)]
+pub struct CodeStats {
+    type_sizes: Lock<FxHashSet<TypeSizeInfo>>,
+    vtable_sizes: Lock<FxHashMap<DefId, VTableSizeInfo>>,
+}
+
+impl CodeStats {
+    pub fn record_type_size<S: ToString>(
+        &self,
+        kind: DataTypeKind,
+        type_desc: S,
+        align: Align,
+        overall_size: Size,
+        packed: bool,
+        opt_discr_size: Option<Size>,
+        mut variants: Vec<VariantInfo>,
+    ) {
+        // 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.
+        // Except for Coroutines, whose variants are already sorted according to
+        // their yield points in `variant_info_for_coroutine`.
+        if kind != DataTypeKind::Coroutine {
+            variants.sort_by_key(|info| cmp::Reverse(info.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 record_vtable_size(&self, trait_did: DefId, trait_name: &str, info: VTableSizeInfo) {
+        let prev = self.vtable_sizes.lock().insert(trait_did, info);
+        assert!(
+            prev.is_none(),
+            "size of vtable for `{trait_name}` ({trait_did:?}) is already recorded"
+        );
+    }
+
+    pub fn print_type_sizes(&self) {
+        let type_sizes = self.type_sizes.borrow();
+        // We will soon sort, so the initial order does not matter.
+        #[allow(rustc::potential_query_instability)]
+        let mut sorted: Vec<_> = type_sizes.iter().collect();
+
+        // Primary sort: large-to-small.
+        // Secondary sort: description (dictionary order)
+        sorted.sort_by_key(|info| (cmp::Reverse(info.overall_size), &info.type_description));
+
+        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 | DataTypeKind::Coroutine => 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 { kind, ref name, offset, size, align, type_name } = 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.
+                        print!(
+                            "print-type-size {indent}{kind} `.{name}`: {size} bytes, \
+                                  offset: {offset} bytes, \
+                                  alignment: {align} bytes"
+                        );
+                    } else if info.packed || offset == min_offset {
+                        print!("print-type-size {indent}{kind} `.{name}`: {size} bytes");
+                    } else {
+                        // Include field alignment in output only if it caused padding injection
+                        print!(
+                            "print-type-size {indent}{kind} `.{name}`: {size} bytes, \
+                                  alignment: {align} bytes"
+                        );
+                    }
+
+                    if let Some(type_name) = type_name {
+                        println!(", type: {type_name}");
+                    } else {
+                        println!();
+                    }
+
+                    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) => {}
+            }
+        }
+    }
+
+    pub fn print_vtable_sizes(&self, crate_name: Symbol) {
+        // We will soon sort, so the initial order does not matter.
+        #[allow(rustc::potential_query_instability)]
+        let mut infos =
+            std::mem::take(&mut *self.vtable_sizes.lock()).into_values().collect::<Vec<_>>();
+
+        // Primary sort: cost % in reverse order (from largest to smallest)
+        // Secondary sort: trait_name
+        infos.sort_by(|a, b| {
+            a.upcasting_cost_percent
+                .total_cmp(&b.upcasting_cost_percent)
+                .reverse()
+                .then_with(|| a.trait_name.cmp(&b.trait_name))
+        });
+
+        for VTableSizeInfo {
+            trait_name,
+            entries,
+            entries_ignoring_upcasting,
+            entries_for_upcasting,
+            upcasting_cost_percent,
+        } in infos
+        {
+            println!(
+                r#"print-vtable-sizes {{ "crate_name": "{crate_name}", "trait_name": "{trait_name}", "entries": "{entries}", "entries_ignoring_upcasting": "{entries_ignoring_upcasting}", "entries_for_upcasting": "{entries_for_upcasting}", "upcasting_cost_percent": "{upcasting_cost_percent}" }}"#
+            );
+        }
+    }
+}
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
new file mode 100644
index 00000000000..44721bd889a
--- /dev/null
+++ b/compiler/rustc_session/src/config.rs
@@ -0,0 +1,3422 @@
+//! Contains infrastructure for configuring the compiler, including parsing
+//! command-line options.
+
+#![allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
+
+use std::collections::btree_map::{
+    Iter as BTreeMapIter, Keys as BTreeMapKeysIter, Values as BTreeMapValuesIter,
+};
+use std::collections::{BTreeMap, BTreeSet};
+use std::ffi::OsStr;
+use std::hash::Hash;
+use std::path::{Path, PathBuf};
+use std::str::{self, FromStr};
+use std::sync::LazyLock;
+use std::{cmp, fmt, fs, iter};
+
+use rustc_data_structures::fx::{FxHashSet, FxIndexMap};
+use rustc_data_structures::stable_hasher::{StableOrd, ToStableHashKey};
+use rustc_errors::emitter::HumanReadableErrorType;
+use rustc_errors::{ColorConfig, DiagArgValue, DiagCtxtFlags, IntoDiagArg};
+use rustc_feature::UnstableFeatures;
+use rustc_macros::{Decodable, Encodable, HashStable_Generic};
+use rustc_span::edition::{DEFAULT_EDITION, EDITION_NAME_LIST, Edition, LATEST_STABLE_EDITION};
+use rustc_span::source_map::FilePathMapping;
+use rustc_span::{
+    FileName, FileNameDisplayPreference, RealFileName, SourceFileHashAlgorithm, Symbol, sym,
+};
+use rustc_target::spec::{
+    FramePointer, LinkSelfContainedComponents, LinkerFeatures, SplitDebuginfo, Target, TargetTuple,
+};
+use tracing::debug;
+
+use crate::errors::FileWriteFail;
+pub use crate::options::*;
+use crate::search_paths::SearchPath;
+use crate::utils::{CanonicalizedPath, NativeLib, NativeLibKind};
+use crate::{EarlyDiagCtxt, HashStableContext, Session, filesearch, lint};
+
+mod cfg;
+pub mod sigpipe;
+
+pub use cfg::{Cfg, CheckCfg, ExpectedValues};
+
+/// 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 `-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, or including/excluding `-C link-dead-code`.
+#[derive(Clone, Copy, PartialEq, Hash, Debug)]
+pub enum InstrumentCoverage {
+    /// `-C instrument-coverage=no` (or `off`, `false` etc.)
+    No,
+    /// `-C instrument-coverage` or `-C instrument-coverage=yes`
+    Yes,
+}
+
+/// Individual flag values controlled by `-Z coverage-options`.
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
+pub struct CoverageOptions {
+    pub level: CoverageLevel,
+
+    /// `-Z coverage-options=no-mir-spans`: Don't extract block coverage spans
+    /// from MIR statements/terminators, making it easier to inspect/debug
+    /// branch and MC/DC coverage mappings.
+    ///
+    /// For internal debugging only. If other code changes would make it hard
+    /// to keep supporting this flag, remove it.
+    pub no_mir_spans: bool,
+}
+
+/// Controls whether branch coverage or MC/DC coverage is enabled.
+#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
+pub enum CoverageLevel {
+    /// Instrument for coverage at the MIR block level.
+    Block,
+    /// Also instrument branch points (includes block coverage).
+    Branch,
+    /// Same as branch coverage, but also adds branch instrumentation for
+    /// certain boolean expressions that are not directly used for branching.
+    ///
+    /// For example, in the following code, `b` does not directly participate
+    /// in a branch, but condition coverage will instrument it as its own
+    /// artificial branch:
+    /// ```
+    /// # let (a, b) = (false, true);
+    /// let x = a && b;
+    /// //           ^ last operand
+    /// ```
+    ///
+    /// This level is mainly intended to be a stepping-stone towards full MC/DC
+    /// instrumentation, so it might be removed in the future when MC/DC is
+    /// sufficiently complete, or if it is making MC/DC changes difficult.
+    Condition,
+    /// Instrument for MC/DC. Mostly a superset of condition coverage, but might
+    /// differ in some corner cases.
+    Mcdc,
+}
+
+impl Default for CoverageLevel {
+    fn default() -> Self {
+        Self::Block
+    }
+}
+
+/// Settings for `-Z instrument-xray` flag.
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
+pub struct InstrumentXRay {
+    /// `-Z instrument-xray=always`, force instrumentation
+    pub always: bool,
+    /// `-Z instrument-xray=never`, disable instrumentation
+    pub never: bool,
+    /// `-Z instrument-xray=ignore-loops`, ignore presence of loops,
+    /// instrument functions based only on instruction count
+    pub ignore_loops: bool,
+    /// `-Z instrument-xray=instruction-threshold=N`, explicitly set instruction threshold
+    /// for instrumentation, or `None` to use compiler's default
+    pub instruction_threshold: Option<usize>,
+    /// `-Z instrument-xray=skip-entry`, do not instrument function entry
+    pub skip_entry: bool,
+    /// `-Z instrument-xray=skip-exit`, do not instrument function exit
+    pub skip_exit: bool,
+}
+
+#[derive(Clone, PartialEq, Hash, Debug)]
+pub enum LinkerPluginLto {
+    LinkerPlugin(PathBuf),
+    LinkerPluginAuto,
+    Disabled,
+}
+
+impl LinkerPluginLto {
+    pub fn enabled(&self) -> bool {
+        match *self {
+            LinkerPluginLto::LinkerPlugin(_) | LinkerPluginLto::LinkerPluginAuto => true,
+            LinkerPluginLto::Disabled => false,
+        }
+    }
+}
+
+/// The different values `-C link-self-contained` can take: a list of individually enabled or
+/// disabled components used during linking, coming from the rustc distribution, instead of being
+/// found somewhere on the host system.
+///
+/// They can be set in bulk via `-C link-self-contained=yes|y|on` or `-C
+/// link-self-contained=no|n|off`, and those boolean values are the historical defaults.
+///
+/// But each component is fine-grained, and can be unstably targeted, to use:
+/// - some CRT objects
+/// - the libc static library
+/// - libgcc/libunwind libraries
+/// - a linker we distribute
+/// - some sanitizer runtime libraries
+/// - all other MinGW libraries and Windows import libs
+///
+#[derive(Default, Clone, PartialEq, Debug)]
+pub struct LinkSelfContained {
+    /// Whether the user explicitly set `-C link-self-contained` on or off, the historical values.
+    /// Used for compatibility with the existing opt-in and target inference.
+    pub explicitly_set: Option<bool>,
+
+    /// The components that are enabled on the CLI, using the `+component` syntax or one of the
+    /// `true` shortcuts.
+    enabled_components: LinkSelfContainedComponents,
+
+    /// The components that are disabled on the CLI, using the `-component` syntax or one of the
+    /// `false` shortcuts.
+    disabled_components: LinkSelfContainedComponents,
+}
+
+impl LinkSelfContained {
+    /// Incorporates an enabled or disabled component as specified on the CLI, if possible.
+    /// For example: `+linker`, and `-crto`.
+    pub(crate) fn handle_cli_component(&mut self, component: &str) -> Option<()> {
+        // Note that for example `-Cself-contained=y -Cself-contained=-linker` is not an explicit
+        // set of all values like `y` or `n` used to be. Therefore, if this flag had previously been
+        // set in bulk with its historical values, then manually setting a component clears that
+        // `explicitly_set` state.
+        if let Some(component_to_enable) = component.strip_prefix('+') {
+            self.explicitly_set = None;
+            self.enabled_components
+                .insert(LinkSelfContainedComponents::from_str(component_to_enable)?);
+            Some(())
+        } else if let Some(component_to_disable) = component.strip_prefix('-') {
+            self.explicitly_set = None;
+            self.disabled_components
+                .insert(LinkSelfContainedComponents::from_str(component_to_disable)?);
+            Some(())
+        } else {
+            None
+        }
+    }
+
+    /// Turns all components on or off and records that this was done explicitly for compatibility
+    /// purposes.
+    pub(crate) fn set_all_explicitly(&mut self, enabled: bool) {
+        self.explicitly_set = Some(enabled);
+
+        if enabled {
+            self.enabled_components = LinkSelfContainedComponents::all();
+            self.disabled_components = LinkSelfContainedComponents::empty();
+        } else {
+            self.enabled_components = LinkSelfContainedComponents::empty();
+            self.disabled_components = LinkSelfContainedComponents::all();
+        }
+    }
+
+    /// Helper creating a fully enabled `LinkSelfContained` instance. Used in tests.
+    pub fn on() -> Self {
+        let mut on = LinkSelfContained::default();
+        on.set_all_explicitly(true);
+        on
+    }
+
+    /// To help checking CLI usage while some of the values are unstable: returns whether one of the
+    /// components was set individually. This would also require the `-Zunstable-options` flag, to
+    /// be allowed.
+    fn are_unstable_variants_set(&self) -> bool {
+        let any_component_set =
+            !self.enabled_components.is_empty() || !self.disabled_components.is_empty();
+        self.explicitly_set.is_none() && any_component_set
+    }
+
+    /// Returns whether the self-contained linker component was enabled on the CLI, using the
+    /// `-C link-self-contained=+linker` syntax, or one of the `true` shortcuts.
+    pub fn is_linker_enabled(&self) -> bool {
+        self.enabled_components.contains(LinkSelfContainedComponents::LINKER)
+    }
+
+    /// Returns whether the self-contained linker component was disabled on the CLI, using the
+    /// `-C link-self-contained=-linker` syntax, or one of the `false` shortcuts.
+    pub fn is_linker_disabled(&self) -> bool {
+        self.disabled_components.contains(LinkSelfContainedComponents::LINKER)
+    }
+
+    /// Returns CLI inconsistencies to emit errors: individual components were both enabled and
+    /// disabled.
+    fn check_consistency(&self) -> Option<LinkSelfContainedComponents> {
+        if self.explicitly_set.is_some() {
+            None
+        } else {
+            let common = self.enabled_components.intersection(self.disabled_components);
+            if common.is_empty() { None } else { Some(common) }
+        }
+    }
+}
+
+/// The different values that `-Z linker-features` can take on the CLI: a list of individually
+/// enabled or disabled features used during linking.
+///
+/// There is no need to enable or disable them in bulk. Each feature is fine-grained, and can be
+/// used to turn `LinkerFeatures` on or off, without needing to change the linker flavor:
+/// - using the system lld, or the self-contained `rust-lld` linker
+/// - using a C/C++ compiler to drive the linker (not yet exposed on the CLI)
+/// - etc.
+#[derive(Default, Copy, Clone, PartialEq, Debug)]
+pub struct LinkerFeaturesCli {
+    /// The linker features that are enabled on the CLI, using the `+feature` syntax.
+    pub enabled: LinkerFeatures,
+
+    /// The linker features that are disabled on the CLI, using the `-feature` syntax.
+    pub disabled: LinkerFeatures,
+}
+
+impl LinkerFeaturesCli {
+    /// Accumulates an enabled or disabled feature as specified on the CLI, if possible.
+    /// For example: `+lld`, and `-lld`.
+    pub(crate) fn handle_cli_feature(&mut self, feature: &str) -> Option<()> {
+        // Duplicate flags are reduced as we go, the last occurrence wins:
+        // `+feature,-feature,+feature` only enables the feature, and does not record it as both
+        // enabled and disabled on the CLI.
+        // We also only expose `+/-lld` at the moment, as it's currently the only implemented linker
+        // feature and toggling `LinkerFeatures::CC` would be a noop.
+        match feature {
+            "+lld" => {
+                self.enabled.insert(LinkerFeatures::LLD);
+                self.disabled.remove(LinkerFeatures::LLD);
+                Some(())
+            }
+            "-lld" => {
+                self.disabled.insert(LinkerFeatures::LLD);
+                self.enabled.remove(LinkerFeatures::LLD);
+                Some(())
+            }
+            _ => None,
+        }
+    }
+}
+
+/// 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,
+}
+
+/// The different settings that can be enabled via the `-Z location-detail` flag.
+#[derive(Copy, Clone, PartialEq, Hash, Debug)]
+pub struct LocationDetail {
+    pub file: bool,
+    pub line: bool,
+    pub column: bool,
+}
+
+impl LocationDetail {
+    pub(crate) fn all() -> Self {
+        Self { file: true, line: true, column: true }
+    }
+}
+
+/// Values for the `-Z fmt-debug` flag.
+#[derive(Copy, Clone, PartialEq, Hash, Debug)]
+pub enum FmtDebug {
+    /// Derive fully-featured implementation
+    Full,
+    /// Print only type name, without fields
+    Shallow,
+    /// `#[derive(Debug)]` and `{:?}` are no-ops
+    None,
+}
+
+impl FmtDebug {
+    pub(crate) fn all() -> [Symbol; 3] {
+        [sym::full, sym::none, sym::shallow]
+    }
+}
+
+#[derive(Clone, PartialEq, Hash, Debug)]
+pub enum SwitchWithOptPath {
+    Enabled(Option<PathBuf>),
+    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,
+    Hashed,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Hash)]
+pub enum DebugInfo {
+    None,
+    LineDirectivesOnly,
+    LineTablesOnly,
+    Limited,
+    Full,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Hash)]
+pub enum DebugInfoCompression {
+    None,
+    Zlib,
+    Zstd,
+}
+
+impl ToString for DebugInfoCompression {
+    fn to_string(&self) -> String {
+        match self {
+            DebugInfoCompression::None => "none",
+            DebugInfoCompression::Zlib => "zlib",
+            DebugInfoCompression::Zstd => "zstd",
+        }
+        .to_owned()
+    }
+}
+
+/// 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<Self, ()> {
+        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,
+    ThinLinkBitcode,
+    Assembly,
+    LlvmAssembly,
+    Mir,
+    Metadata,
+    Object,
+    Exe,
+    DepInfo,
+}
+
+impl StableOrd for OutputType {
+    const CAN_USE_UNSTABLE_SORT: bool = true;
+
+    // Trivial C-Style enums have a stable sort order across compilation sessions.
+    const THIS_IMPLEMENTATION_HAS_BEEN_TRIPLE_CHECKED: () = ();
+}
+
+impl<HCX: HashStableContext> ToStableHashKey<HCX> 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::ThinLinkBitcode
+            | OutputType::Assembly
+            | OutputType::LlvmAssembly
+            | OutputType::Mir
+            | OutputType::Object => false,
+        }
+    }
+
+    pub fn shorthand(&self) -> &'static str {
+        match *self {
+            OutputType::Bitcode => "llvm-bc",
+            OutputType::ThinLinkBitcode => "thin-link-bitcode",
+            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<Self> {
+        Some(match shorthand {
+            "asm" => OutputType::Assembly,
+            "llvm-ir" => OutputType::LlvmAssembly,
+            "mir" => OutputType::Mir,
+            "llvm-bc" => OutputType::Bitcode,
+            "thin-link-bitcode" => OutputType::ThinLinkBitcode,
+            "obj" => OutputType::Object,
+            "metadata" => OutputType::Metadata,
+            "link" => OutputType::Exe,
+            "dep-info" => OutputType::DepInfo,
+            _ => return None,
+        })
+    }
+
+    fn shorthands_display() -> String {
+        format!(
+            "`{}`, `{}`, `{}`, `{}`, `{}`, `{}`, `{}`, `{}`, `{}`",
+            OutputType::Bitcode.shorthand(),
+            OutputType::ThinLinkBitcode.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::ThinLinkBitcode => "indexing.o",
+            OutputType::Assembly => "s",
+            OutputType::LlvmAssembly => "ll",
+            OutputType::Mir => "mir",
+            OutputType::Object => "o",
+            OutputType::Metadata => "rmeta",
+            OutputType::DepInfo => "d",
+            OutputType::Exe => "",
+        }
+    }
+
+    pub fn is_text_output(&self) -> bool {
+        match *self {
+            OutputType::Assembly
+            | OutputType::LlvmAssembly
+            | OutputType::Mir
+            | OutputType::DepInfo => true,
+            OutputType::Bitcode
+            | OutputType::ThinLinkBitcode
+            | OutputType::Object
+            | OutputType::Metadata
+            | OutputType::Exe => false,
+        }
+    }
+}
+
+/// 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, ColorConfig),
+    /// 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,
+        color_config: ColorConfig,
+    },
+}
+
+impl Default for ErrorOutputType {
+    fn default() -> Self {
+        Self::HumanReadable(HumanReadableErrorType::Default, ColorConfig::Auto)
+    }
+}
+
+#[derive(Clone, Hash, Debug)]
+pub enum ResolveDocLinks {
+    /// Do not resolve doc links.
+    None,
+    /// Resolve doc links on exported items only for crate types that have metadata.
+    ExportedMetadata,
+    /// Resolve doc links on exported items.
+    Exported,
+    /// Resolve doc links on all items.
+    All,
+}
+
+/// 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, Encodable, Decodable)]
+pub struct OutputTypes(BTreeMap<OutputType, Option<OutFileName>>);
+
+impl OutputTypes {
+    pub fn new(entries: &[(OutputType, Option<OutFileName>)]) -> OutputTypes {
+        OutputTypes(BTreeMap::from_iter(entries.iter().map(|&(k, ref v)| (k, v.clone()))))
+    }
+
+    pub(crate) fn get(&self, key: &OutputType) -> Option<&Option<OutFileName>> {
+        self.0.get(key)
+    }
+
+    pub fn contains_key(&self, key: &OutputType) -> bool {
+        self.0.contains_key(key)
+    }
+
+    /// Returns `true` if user specified a name and not just produced type
+    pub fn contains_explicit_name(&self, key: &OutputType) -> bool {
+        self.0.get(key).map_or(false, |f| f.is_some())
+    }
+
+    pub fn iter(&self) -> BTreeMapIter<'_, OutputType, Option<OutFileName>> {
+        self.0.iter()
+    }
+
+    pub fn keys(&self) -> BTreeMapKeysIter<'_, OutputType, Option<OutFileName>> {
+        self.0.keys()
+    }
+
+    pub fn values(&self) -> BTreeMapValuesIter<'_, OutputType, Option<OutFileName>> {
+        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::ThinLinkBitcode
+            | 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::ThinLinkBitcode
+            | 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<String, ExternEntry>);
+
+#[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,
+    /// If the extern entry is not referenced in the crate, force it to be resolved anyway.
+    ///
+    /// Allows a dependency satisfying, for instance, a missing panic handler to be injected
+    /// without modifying source:
+    /// `--extern force:extras=/path/to/lib/libstd.rlib`
+    pub force: 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<CanonicalizedPath>),
+}
+
+impl Externs {
+    /// Used for testing.
+    pub fn new(data: BTreeMap<String, ExternEntry>) -> 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()
+    }
+}
+
+impl ExternEntry {
+    fn new(location: ExternLocation) -> ExternEntry {
+        ExternEntry {
+            location,
+            is_private_dep: false,
+            add_prelude: false,
+            nounused_dep: false,
+            force: false,
+        }
+    }
+
+    pub fn files(&self) -> Option<impl Iterator<Item = &CanonicalizedPath>> {
+        match &self.location {
+            ExternLocation::ExactPaths(set) => Some(set.iter()),
+            _ => None,
+        }
+    }
+}
+
+#[derive(Clone, PartialEq, Debug)]
+pub struct PrintRequest {
+    pub kind: PrintKind,
+    pub out: OutFileName,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum PrintKind {
+    FileNames,
+    HostTuple,
+    Sysroot,
+    TargetLibdir,
+    CrateName,
+    Cfg,
+    CheckCfg,
+    CallingConventions,
+    TargetList,
+    TargetCPUs,
+    TargetFeatures,
+    RelocationModels,
+    CodeModels,
+    TlsModels,
+    TargetSpec,
+    AllTargetSpecs,
+    NativeStaticLibs,
+    StackProtectorStrategies,
+    LinkArgs,
+    SplitDebuginfo,
+    DeploymentTarget,
+}
+
+#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
+pub struct NextSolverConfig {
+    /// Whether the new trait solver should be enabled in coherence.
+    pub coherence: bool,
+    /// Whether the new trait solver should be enabled everywhere.
+    /// This is only `true` if `coherence` is also enabled.
+    pub globally: bool,
+}
+impl Default for NextSolverConfig {
+    fn default() -> Self {
+        NextSolverConfig { coherence: true, globally: false }
+    }
+}
+
+#[derive(Clone)]
+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 {
+        if let Input::File(ifile) = self {
+            // If for some reason getting the file stem as a UTF-8 string fails,
+            // then fallback to a fixed name.
+            if let Some(name) = ifile.file_stem().and_then(OsStr::to_str) {
+                return name;
+            }
+        }
+        "rust_out"
+    }
+
+    pub fn source_name(&self) -> FileName {
+        match *self {
+            Input::File(ref ifile) => ifile.clone().into(),
+            Input::Str { ref name, .. } => name.clone(),
+        }
+    }
+
+    pub fn opt_path(&self) -> Option<&Path> {
+        match self {
+            Input::File(file) => Some(file),
+            Input::Str { name, .. } => match name {
+                FileName::Real(real) => real.local_path(),
+                FileName::QuoteExpansion(_) => None,
+                FileName::Anon(_) => None,
+                FileName::MacroExpansion(_) => None,
+                FileName::ProcMacroSourceCode(_) => None,
+                FileName::CliCrateAttr(_) => None,
+                FileName::Custom(_) => None,
+                FileName::DocTest(path, _) => Some(path),
+                FileName::InlineAsm(_) => None,
+            },
+        }
+    }
+}
+
+#[derive(Clone, Hash, Debug, HashStable_Generic, PartialEq, Encodable, Decodable)]
+pub enum OutFileName {
+    Real(PathBuf),
+    Stdout,
+}
+
+impl OutFileName {
+    pub fn parent(&self) -> Option<&Path> {
+        match *self {
+            OutFileName::Real(ref path) => path.parent(),
+            OutFileName::Stdout => None,
+        }
+    }
+
+    pub fn filestem(&self) -> Option<&OsStr> {
+        match *self {
+            OutFileName::Real(ref path) => path.file_stem(),
+            OutFileName::Stdout => Some(OsStr::new("stdout")),
+        }
+    }
+
+    pub fn is_stdout(&self) -> bool {
+        match *self {
+            OutFileName::Real(_) => false,
+            OutFileName::Stdout => true,
+        }
+    }
+
+    pub fn is_tty(&self) -> bool {
+        use std::io::IsTerminal;
+        match *self {
+            OutFileName::Real(_) => false,
+            OutFileName::Stdout => std::io::stdout().is_terminal(),
+        }
+    }
+
+    pub fn as_path(&self) -> &Path {
+        match *self {
+            OutFileName::Real(ref path) => path.as_ref(),
+            OutFileName::Stdout => Path::new("stdout"),
+        }
+    }
+
+    /// For a given output filename, return the actual name of the file that
+    /// can be used to write codegen data of type `flavor`. For real-path
+    /// output filenames, this would be trivial as we can just use the path.
+    /// Otherwise for stdout, return a temporary path so that the codegen data
+    /// may be later copied to stdout.
+    pub fn file_for_writing(
+        &self,
+        outputs: &OutputFilenames,
+        flavor: OutputType,
+        codegen_unit_name: Option<&str>,
+    ) -> PathBuf {
+        match *self {
+            OutFileName::Real(ref path) => path.clone(),
+            OutFileName::Stdout => outputs.temp_path(flavor, codegen_unit_name),
+        }
+    }
+
+    pub fn overwrite(&self, content: &str, sess: &Session) {
+        match self {
+            OutFileName::Stdout => print!("{content}"),
+            OutFileName::Real(path) => {
+                if let Err(e) = fs::write(path, content) {
+                    sess.dcx().emit_fatal(FileWriteFail { path, err: e.to_string() });
+                }
+            }
+        }
+    }
+}
+
+#[derive(Clone, Hash, Debug, HashStable_Generic, Encodable, Decodable)]
+pub struct OutputFilenames {
+    pub(crate) out_directory: PathBuf,
+    /// Crate name. Never contains '-'.
+    crate_stem: String,
+    /// Typically based on `.rs` input file name. Any '-' is preserved.
+    filestem: String,
+    pub single_output_file: Option<OutFileName>,
+    temps_directory: Option<PathBuf>,
+    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_crate_name: String,
+        out_filestem: String,
+        single_output_file: Option<OutFileName>,
+        temps_directory: Option<PathBuf>,
+        extra: String,
+        outputs: OutputTypes,
+    ) -> Self {
+        OutputFilenames {
+            out_directory,
+            single_output_file,
+            temps_directory,
+            outputs,
+            crate_stem: format!("{out_crate_name}{extra}"),
+            filestem: format!("{out_filestem}{extra}"),
+        }
+    }
+
+    pub fn path(&self, flavor: OutputType) -> OutFileName {
+        self.outputs
+            .get(&flavor)
+            .and_then(|p| p.to_owned())
+            .or_else(|| self.single_output_file.clone())
+            .unwrap_or_else(|| OutFileName::Real(self.output_path(flavor)))
+    }
+
+    /// Gets the output path where a compilation artifact of the given type
+    /// should be placed on disk.
+    fn output_path(&self, flavor: OutputType) -> PathBuf {
+        let extension = flavor.extension();
+        match flavor {
+            OutputType::Metadata => {
+                self.out_directory.join(format!("lib{}.{}", self.crate_stem, 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<PathBuf> {
+        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)
+            }
+        }
+    }
+}
+
+bitflags::bitflags! {
+    /// Scopes used to determined if it need to apply to --remap-path-prefix
+    #[derive(Clone, Copy, PartialEq, Eq, Hash)]
+    pub struct RemapPathScopeComponents: u8 {
+        /// Apply remappings to the expansion of std::file!() macro
+        const MACRO = 1 << 0;
+        /// Apply remappings to printed compiler diagnostics
+        const DIAGNOSTICS = 1 << 1;
+        /// Apply remappings to debug information
+        const DEBUGINFO = 1 << 3;
+
+        /// An alias for `macro` and `debuginfo`. This ensures all paths in compiled
+        /// executables or libraries are remapped but not elsewhere.
+        const OBJECT = Self::MACRO.bits() | Self::DEBUGINFO.bits();
+    }
+}
+
+pub fn host_tuple() -> &'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")
+}
+
+fn file_path_mapping(
+    remap_path_prefix: Vec<(PathBuf, PathBuf)>,
+    unstable_opts: &UnstableOptions,
+) -> FilePathMapping {
+    FilePathMapping::new(
+        remap_path_prefix.clone(),
+        if unstable_opts.remap_path_scope.contains(RemapPathScopeComponents::DIAGNOSTICS)
+            && !remap_path_prefix.is_empty()
+        {
+            FileNameDisplayPreference::Remapped
+        } else {
+            FileNameDisplayPreference::Local
+        },
+    )
+}
+
+impl Default for Options {
+    fn default() -> Options {
+        Options {
+            assert_incr_state: None,
+            crate_types: Vec::new(),
+            optimize: OptLevel::No,
+            debuginfo: DebugInfo::None,
+            debuginfo_compression: DebugInfoCompression::None,
+            lint_opts: Vec::new(),
+            lint_cap: None,
+            describe_lints: false,
+            output_types: OutputTypes(BTreeMap::new()),
+            search_paths: vec![],
+            maybe_sysroot: None,
+            target_triple: TargetTuple::from_tuple(host_tuple()),
+            test: false,
+            incremental: None,
+            untracked_state_hash: Default::default(),
+            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,
+            resolve_doc_links: ResolveDocLinks::None,
+            trimmed_def_paths: false,
+            cli_forced_codegen_units: None,
+            cli_forced_local_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()),
+            color: ColorConfig::Auto,
+            logical_env: FxIndexMap::default(),
+            verbose: false,
+        }
+    }
+}
+
+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 {
+        file_path_mapping(self.remap_path_prefix.clone(), &self.unstable_opts)
+    }
+
+    /// 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.is_empty() // 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 dcx_flags(&self, can_emit_warnings: bool) -> DiagCtxtFlags {
+        DiagCtxtFlags {
+            can_emit_warnings,
+            treat_err_as_bug: self.treat_err_as_bug,
+            eagerly_emit_delayed_bugs: self.eagerly_emit_delayed_bugs,
+            macro_backtrace: self.macro_backtrace,
+            deduplicate_diagnostics: self.deduplicate_diagnostics,
+            track_diagnostics: self.track_diagnostics,
+        }
+    }
+
+    pub fn src_hash_algorithm(&self, target: &Target) -> SourceFileHashAlgorithm {
+        self.src_hash_algorithm.unwrap_or_else(|| {
+            if target.is_like_msvc {
+                SourceFileHashAlgorithm::Sha256
+            } else {
+                SourceFileHashAlgorithm::Md5
+            }
+        })
+    }
+
+    pub fn checksum_hash_algorithm(&self) -> Option<SourceFileHashAlgorithm> {
+        self.checksum_hash_algorithm
+    }
+}
+
+// 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 {
+        /// Specifies what to do with `SIGPIPE` before calling `fn main()`.
+        ///
+        /// What values that are valid and what they mean must be in sync
+        /// across rustc and libstd, but we don't want it public in libstd,
+        /// so we take a bit of an unusual approach with simple constants
+        /// and an `include!()`.
+        sigpipe: u8,
+    },
+    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 {
+    pub fn has_metadata(self) -> bool {
+        match self {
+            CrateType::Rlib | CrateType::Dylib | CrateType::ProcMacro => true,
+            CrateType::Executable | CrateType::Cdylib | CrateType::Staticlib => false,
+        }
+    }
+}
+
+#[derive(Clone, Hash, Debug, PartialEq, Eq)]
+pub enum Passes {
+    Some(Vec<String>),
+    All,
+}
+
+impl Passes {
+    fn is_empty(&self) -> bool {
+        match *self {
+            Passes::Some(ref v) => v.is_empty(),
+            Passes::All => false,
+        }
+    }
+
+    pub(crate) fn extend(&mut self, passes: impl IntoIterator<Item = String>) {
+        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 pc: bool,
+    pub key: PAuthKey,
+}
+
+#[derive(Clone, Copy, Hash, Debug, PartialEq, Default)]
+pub struct BranchProtection {
+    pub bti: bool,
+    pub pac_ret: Option<PacRet>,
+}
+
+pub(crate) const fn default_lib_output() -> CrateType {
+    CrateType::Rlib
+}
+
+pub fn build_configuration(sess: &Session, mut user_cfg: Cfg) -> Cfg {
+    // First disallow some configuration given on the command line
+    cfg::disallow_cfgs(sess, &user_cfg);
+
+    // Then combine the configuration requested by the session (command line) with
+    // some default and generated configuration items.
+    user_cfg.extend(cfg::default_configuration(sess));
+    user_cfg
+}
+
+pub fn build_target_config(early_dcx: &EarlyDiagCtxt, opts: &Options, sysroot: &Path) -> Target {
+    match Target::search(&opts.target_triple, sysroot) {
+        Ok((target, warnings)) => {
+            for warning in warnings.warning_messages() {
+                early_dcx.early_warn(warning)
+            }
+
+            if !matches!(target.pointer_width, 16 | 32 | 64) {
+                early_dcx.early_fatal(format!(
+                    "target specification was invalid: unrecognized target-pointer-width {}",
+                    target.pointer_width
+                ))
+            }
+            target
+        }
+        Err(e) => early_dcx.early_fatal(format!(
+            "Error loading target specification: {e}. \
+                     Run `rustc --print target-list` for a list of built-in targets"
+        )),
+    }
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum OptionStability {
+    Stable,
+    Unstable,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum OptionKind {
+    /// An option that takes a value, and cannot appear more than once (e.g. `--out-dir`).
+    ///
+    /// Corresponds to [`getopts::Options::optopt`].
+    Opt,
+
+    /// An option that takes a value, and can appear multiple times (e.g. `--emit`).
+    ///
+    /// Corresponds to [`getopts::Options::optmulti`].
+    Multi,
+
+    /// An option that does not take a value, and cannot appear more than once (e.g. `--help`).
+    ///
+    /// Corresponds to [`getopts::Options::optflag`].
+    /// The `hint` string must be empty.
+    Flag,
+
+    /// An option that does not take a value, and can appear multiple times (e.g. `-O`).
+    ///
+    /// Corresponds to [`getopts::Options::optflagmulti`].
+    /// The `hint` string must be empty.
+    FlagMulti,
+}
+
+pub struct RustcOptGroup {
+    /// The "primary" name for this option. Normally equal to `long_name`,
+    /// except for options that don't have a long name, in which case
+    /// `short_name` is used.
+    ///
+    /// This is needed when interacting with `getopts` in some situations,
+    /// because if an option has both forms, that library treats the long name
+    /// as primary and the short name as an alias.
+    pub name: &'static str,
+    stability: OptionStability,
+    kind: OptionKind,
+
+    short_name: &'static str,
+    long_name: &'static str,
+    desc: &'static str,
+    value_hint: &'static str,
+
+    /// If true, this option should not be printed by `rustc --help`, but
+    /// should still be printed by `rustc --help -v`.
+    pub is_verbose_help_only: bool,
+}
+
+impl RustcOptGroup {
+    pub fn is_stable(&self) -> bool {
+        self.stability == OptionStability::Stable
+    }
+
+    pub fn apply(&self, options: &mut getopts::Options) {
+        let &Self { short_name, long_name, desc, value_hint, .. } = self;
+        match self.kind {
+            OptionKind::Opt => options.optopt(short_name, long_name, desc, value_hint),
+            OptionKind::Multi => options.optmulti(short_name, long_name, desc, value_hint),
+            OptionKind::Flag => options.optflag(short_name, long_name, desc),
+            OptionKind::FlagMulti => options.optflagmulti(short_name, long_name, desc),
+        };
+    }
+}
+
+pub fn make_opt(
+    stability: OptionStability,
+    kind: OptionKind,
+    short_name: &'static str,
+    long_name: &'static str,
+    desc: &'static str,
+    value_hint: &'static str,
+) -> RustcOptGroup {
+    // "Flag" options don't have a value, and therefore don't have a value hint.
+    match kind {
+        OptionKind::Opt | OptionKind::Multi => {}
+        OptionKind::Flag | OptionKind::FlagMulti => assert_eq!(value_hint, ""),
+    }
+    RustcOptGroup {
+        name: cmp::max_by_key(short_name, long_name, |s| s.len()),
+        stability,
+        kind,
+        short_name,
+        long_name,
+        desc,
+        value_hint,
+        is_verbose_help_only: false,
+    }
+}
+
+static EDITION_STRING: LazyLock<String> = LazyLock::new(|| {
+    format!(
+        "Specify which edition of the compiler to use when compiling code. \
+The default is {DEFAULT_EDITION} and the latest stable edition is {LATEST_STABLE_EDITION}."
+    )
+});
+
+/// Returns all rustc command line options, including metadata for
+/// each option, such as whether the option is stable.
+pub fn rustc_optgroups() -> Vec<RustcOptGroup> {
+    use OptionKind::{Flag, FlagMulti, Multi, Opt};
+    use OptionStability::{Stable, Unstable};
+
+    use self::make_opt as opt;
+
+    let mut options = vec![
+        opt(Stable, Flag, "h", "help", "Display this message", ""),
+        opt(
+            Stable,
+            Multi,
+            "",
+            "cfg",
+            "Configure the compilation environment.\n\
+                SPEC supports the syntax `NAME[=\"VALUE\"]`.",
+            "SPEC",
+        ),
+        opt(Stable, Multi, "", "check-cfg", "Provide list of expected cfgs for checking", "SPEC"),
+        opt(
+            Stable,
+            Multi,
+            "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(
+            Stable,
+            Multi,
+            "l",
+            "",
+            "Link the generated crate(s) to the specified native\n\
+                library NAME. The optional KIND can be one of\n\
+                static, framework, or dylib (the default).\n\
+                Optional comma separated MODIFIERS\n\
+                (bundle|verbatim|whole-archive|as-needed)\n\
+                may be specified each with a prefix of either '+' to\n\
+                enable or '-' to disable.",
+            "[KIND[:MODIFIERS]=]NAME[:RENAME]",
+        ),
+        make_crate_type_option(),
+        opt(Stable, Opt, "", "crate-name", "Specify the name of the crate being built", "NAME"),
+        opt(Stable, Opt, "", "edition", &EDITION_STRING, EDITION_NAME_LIST),
+        opt(
+            Stable,
+            Multi,
+            "",
+            "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(
+            Stable,
+            Multi,
+            "",
+            "print",
+            "Compiler information to print on stdout",
+            "[crate-name|file-names|sysroot|target-libdir|cfg|check-cfg|calling-conventions|\
+             target-list|target-cpus|target-features|relocation-models|code-models|\
+             tls-models|target-spec-json|all-target-specs-json|native-static-libs|\
+             stack-protector-strategies|link-args|deployment-target]",
+        ),
+        opt(Stable, FlagMulti, "g", "", "Equivalent to -C debuginfo=2", ""),
+        opt(Stable, FlagMulti, "O", "", "Equivalent to -C opt-level=2", ""),
+        opt(Stable, Opt, "o", "", "Write output to <filename>", "FILENAME"),
+        opt(Stable, Opt, "", "out-dir", "Write output to compiler-chosen filename in <dir>", "DIR"),
+        opt(
+            Stable,
+            Opt,
+            "",
+            "explain",
+            "Provide a detailed explanation of an error message",
+            "OPT",
+        ),
+        opt(Stable, Flag, "", "test", "Build a test harness", ""),
+        opt(Stable, Opt, "", "target", "Target triple for which the code is compiled", "TARGET"),
+        opt(Stable, Multi, "A", "allow", "Set lint allowed", "LINT"),
+        opt(Stable, Multi, "W", "warn", "Set lint warnings", "LINT"),
+        opt(Stable, Multi, "", "force-warn", "Set lint force-warn", "LINT"),
+        opt(Stable, Multi, "D", "deny", "Set lint denied", "LINT"),
+        opt(Stable, Multi, "F", "forbid", "Set lint forbidden", "LINT"),
+        opt(
+            Stable,
+            Multi,
+            "",
+            "cap-lints",
+            "Set the most restrictive lint level. More restrictive lints are capped at this level",
+            "LEVEL",
+        ),
+        opt(Stable, Multi, "C", "codegen", "Set a codegen option", "OPT[=VALUE]"),
+        opt(Stable, Flag, "V", "version", "Print version info and exit", ""),
+        opt(Stable, Flag, "v", "verbose", "Use verbose output", ""),
+    ];
+
+    // Options in this list are hidden from `rustc --help` by default, but are
+    // shown by `rustc --help -v`.
+    let verbose_only = [
+        opt(
+            Stable,
+            Multi,
+            "",
+            "extern",
+            "Specify where an external rust library is located",
+            "NAME[=PATH]",
+        ),
+        opt(Stable, Opt, "", "sysroot", "Override the system root", "PATH"),
+        opt(Unstable, Multi, "Z", "", "Set unstable / perma-unstable options", "FLAG"),
+        opt(
+            Stable,
+            Opt,
+            "",
+            "error-format",
+            "How errors and other messages are produced",
+            "human|json|short",
+        ),
+        opt(Stable, Multi, "", "json", "Configure the JSON output of the compiler", "CONFIG"),
+        opt(
+            Stable,
+            Opt,
+            "",
+            "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(
+            Stable,
+            Opt,
+            "",
+            "diagnostic-width",
+            "Inform rustc of the width of the output so that diagnostics can be truncated to fit",
+            "WIDTH",
+        ),
+        opt(
+            Stable,
+            Multi,
+            "",
+            "remap-path-prefix",
+            "Remap source names in all output (compiler messages and output files)",
+            "FROM=TO",
+        ),
+        opt(Unstable, Multi, "", "env-set", "Inject an environment variable", "VAR=VALUE"),
+    ];
+    options.extend(verbose_only.into_iter().map(|mut opt| {
+        opt.is_verbose_help_only = true;
+        opt
+    }));
+
+    options
+}
+
+pub fn get_cmd_lint_options(
+    early_dcx: &EarlyDiagCtxt,
+    matches: &getopts::Matches,
+) -> (Vec<(String, lint::Level)>, bool, Option<lint::Level>) {
+    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_dcx.early_fatal(format!("unknown lint level: `{cap}`")))
+    });
+
+    (lint_opts, describe_lints, lint_cap)
+}
+
+/// Parses the `--color` flag.
+pub fn parse_color(early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches) -> ColorConfig {
+    match matches.opt_str("color").as_deref() {
+        Some("auto") => ColorConfig::Auto,
+        Some("always") => ColorConfig::Always,
+        Some("never") => ColorConfig::Never,
+
+        None => ColorConfig::Auto,
+
+        Some(arg) => early_dcx.early_fatal(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_color: ColorConfig,
+    json_artifact_notifications: bool,
+    pub json_unused_externs: JsonUnusedExterns,
+    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(early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches) -> JsonConfig {
+    let mut json_rendered = 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_dcx.early_fatal("cannot specify the `--color` option with `--json`");
+        }
+
+        for sub_option in option.split(',') {
+            match sub_option {
+                "diagnostic-short" => json_rendered = HumanReadableErrorType::Short,
+                "diagnostic-unicode" => {
+                    json_rendered = HumanReadableErrorType::Unicode;
+                }
+                "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_dcx.early_fatal(format!("unknown `--json` option `{s}`")),
+            }
+        }
+    }
+
+    JsonConfig {
+        json_rendered,
+        json_color,
+        json_artifact_notifications,
+        json_unused_externs,
+        json_future_incompat,
+    }
+}
+
+/// Parses the `--error-format` flag.
+pub fn parse_error_format(
+    early_dcx: &mut EarlyDiagCtxt,
+    matches: &getopts::Matches,
+    color: ColorConfig,
+    json_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_deref() {
+            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, color_config: json_color }
+            }
+            Some("pretty-json") => {
+                ErrorOutputType::Json { pretty: true, json_rendered, color_config: json_color }
+            }
+            Some("short") => ErrorOutputType::HumanReadable(HumanReadableErrorType::Short, color),
+            Some("human-unicode") => {
+                ErrorOutputType::HumanReadable(HumanReadableErrorType::Unicode, color)
+            }
+            Some(arg) => {
+                early_dcx.abort_if_error_and_set_error_format(ErrorOutputType::HumanReadable(
+                    HumanReadableErrorType::Default,
+                    color,
+                ));
+                early_dcx.early_fatal(format!(
+                    "argument for `--error-format` must be `human`, `human-annotate-rs`, \
+                    `human-unicode`, `json`, `pretty-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_dcx.early_fatal("using `--json` requires also using `--error-format=json`");
+        }
+
+        _ => {}
+    }
+
+    error_format
+}
+
+pub fn parse_crate_edition(early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches) -> Edition {
+    let edition = match matches.opt_str("edition") {
+        Some(arg) => Edition::from_str(&arg).unwrap_or_else(|_| {
+            early_dcx.early_fatal(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 {edition}, but the latest edition supported by this Rust version is {LATEST_STABLE_EDITION}"
+            )
+        } else {
+            format!("edition {edition} is unstable and only available with -Z unstable-options")
+        };
+        early_dcx.early_fatal(msg)
+    }
+
+    edition
+}
+
+fn check_error_format_stability(
+    early_dcx: &EarlyDiagCtxt,
+    unstable_opts: &UnstableOptions,
+    format: ErrorOutputType,
+) {
+    if unstable_opts.unstable_options {
+        return;
+    }
+    let format = match format {
+        ErrorOutputType::Json { pretty: true, .. } => "pretty-json",
+        ErrorOutputType::HumanReadable(format, _) => match format {
+            HumanReadableErrorType::AnnotateSnippet => "human-annotate-rs",
+            HumanReadableErrorType::Unicode => "human-unicode",
+            _ => return,
+        },
+        _ => return,
+    };
+    early_dcx.early_fatal(format!("`--error-format={format}` is unstable"))
+}
+
+fn parse_output_types(
+    early_dcx: &EarlyDiagCtxt,
+    unstable_opts: &UnstableOptions,
+    matches: &getopts::Matches,
+) -> 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) = split_out_file_name(output_type);
+                let output_type = OutputType::from_shorthand(shorthand).unwrap_or_else(|| {
+                    early_dcx.early_fatal(format!(
+                        "unknown emission type: `{shorthand}` - expected one of: {display}",
+                        display = OutputType::shorthands_display(),
+                    ))
+                });
+                if output_type == OutputType::ThinLinkBitcode && !unstable_opts.unstable_options {
+                    early_dcx.early_fatal(format!(
+                        "{} requested but -Zunstable-options not specified",
+                        OutputType::ThinLinkBitcode.shorthand()
+                    ));
+                }
+                output_types.insert(output_type, path);
+            }
+        }
+    };
+    if output_types.is_empty() {
+        output_types.insert(OutputType::Exe, None);
+    }
+    OutputTypes(output_types)
+}
+
+fn split_out_file_name(arg: &str) -> (&str, Option<OutFileName>) {
+    match arg.split_once('=') {
+        None => (arg, None),
+        Some((kind, "-")) => (kind, Some(OutFileName::Stdout)),
+        Some((kind, path)) => (kind, Some(OutFileName::Real(PathBuf::from(path)))),
+    }
+}
+
+fn should_override_cgus_and_disable_thinlto(
+    early_dcx: &EarlyDiagCtxt,
+    output_types: &OutputTypes,
+    matches: &getopts::Matches,
+    mut codegen_units: Option<usize>,
+) -> (bool, Option<usize>) {
+    let mut disable_local_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_dcx.early_warn(format!(
+                            "`--emit={ot}` with `-o` incompatible with \
+                                 `-C codegen-units=N` for N > 1",
+                        ));
+                    }
+                    early_dcx.early_warn("resetting to default -C codegen-units=1");
+                    codegen_units = Some(1);
+                    disable_local_thinlto = true;
+                }
+            }
+            _ => {
+                codegen_units = Some(1);
+                disable_local_thinlto = true;
+            }
+        }
+    }
+
+    if codegen_units == Some(0) {
+        early_dcx.early_fatal("value for codegen units must be a positive non-zero integer");
+    }
+
+    (disable_local_thinlto, codegen_units)
+}
+
+fn collect_print_requests(
+    early_dcx: &EarlyDiagCtxt,
+    cg: &mut CodegenOptions,
+    unstable_opts: &UnstableOptions,
+    matches: &getopts::Matches,
+) -> Vec<PrintRequest> {
+    let mut prints = Vec::<PrintRequest>::new();
+    if cg.target_cpu.as_ref().is_some_and(|s| s == "help") {
+        prints.push(PrintRequest { kind: PrintKind::TargetCPUs, out: OutFileName::Stdout });
+        cg.target_cpu = None;
+    };
+    if cg.target_feature == "help" {
+        prints.push(PrintRequest { kind: PrintKind::TargetFeatures, out: OutFileName::Stdout });
+        cg.target_feature = String::new();
+    }
+
+    const PRINT_KINDS: &[(&str, PrintKind)] = &[
+        // tidy-alphabetical-start
+        ("all-target-specs-json", PrintKind::AllTargetSpecs),
+        ("calling-conventions", PrintKind::CallingConventions),
+        ("cfg", PrintKind::Cfg),
+        ("check-cfg", PrintKind::CheckCfg),
+        ("code-models", PrintKind::CodeModels),
+        ("crate-name", PrintKind::CrateName),
+        ("deployment-target", PrintKind::DeploymentTarget),
+        ("file-names", PrintKind::FileNames),
+        ("host-tuple", PrintKind::HostTuple),
+        ("link-args", PrintKind::LinkArgs),
+        ("native-static-libs", PrintKind::NativeStaticLibs),
+        ("relocation-models", PrintKind::RelocationModels),
+        ("split-debuginfo", PrintKind::SplitDebuginfo),
+        ("stack-protector-strategies", PrintKind::StackProtectorStrategies),
+        ("sysroot", PrintKind::Sysroot),
+        ("target-cpus", PrintKind::TargetCPUs),
+        ("target-features", PrintKind::TargetFeatures),
+        ("target-libdir", PrintKind::TargetLibdir),
+        ("target-list", PrintKind::TargetList),
+        ("target-spec-json", PrintKind::TargetSpec),
+        ("tls-models", PrintKind::TlsModels),
+        // tidy-alphabetical-end
+    ];
+
+    // We disallow reusing the same path in multiple prints, such as `--print
+    // cfg=output.txt --print link-args=output.txt`, because outputs are printed
+    // by disparate pieces of the compiler, and keeping track of which files
+    // need to be overwritten vs appended to is annoying.
+    let mut printed_paths = FxHashSet::default();
+
+    prints.extend(matches.opt_strs("print").into_iter().map(|req| {
+        let (req, out) = split_out_file_name(&req);
+
+        let kind = match PRINT_KINDS.iter().find(|&&(name, _)| name == req) {
+            Some((_, PrintKind::TargetSpec)) => {
+                if unstable_opts.unstable_options {
+                    PrintKind::TargetSpec
+                } else {
+                    early_dcx.early_fatal(
+                        "the `-Z unstable-options` flag must also be passed to \
+                         enable the target-spec-json print option",
+                    );
+                }
+            }
+            Some((_, PrintKind::AllTargetSpecs)) => {
+                if unstable_opts.unstable_options {
+                    PrintKind::AllTargetSpecs
+                } else {
+                    early_dcx.early_fatal(
+                        "the `-Z unstable-options` flag must also be passed to \
+                         enable the all-target-specs-json print option",
+                    );
+                }
+            }
+            Some((_, PrintKind::CheckCfg)) => {
+                if unstable_opts.unstable_options {
+                    PrintKind::CheckCfg
+                } else {
+                    early_dcx.early_fatal(
+                        "the `-Z unstable-options` flag must also be passed to \
+                         enable the check-cfg print option",
+                    );
+                }
+            }
+            Some(&(_, print_kind)) => print_kind,
+            None => {
+                let prints =
+                    PRINT_KINDS.iter().map(|(name, _)| format!("`{name}`")).collect::<Vec<_>>();
+                let prints = prints.join(", ");
+
+                let mut diag =
+                    early_dcx.early_struct_fatal(format!("unknown print request: `{req}`"));
+                #[allow(rustc::diagnostic_outside_of_impl)]
+                diag.help(format!("valid print requests are: {prints}"));
+                diag.emit()
+            }
+        };
+
+        let out = out.unwrap_or(OutFileName::Stdout);
+        if let OutFileName::Real(path) = &out {
+            if !printed_paths.insert(path.clone()) {
+                early_dcx.early_fatal(format!(
+                    "cannot print multiple outputs to the same path: {}",
+                    path.display(),
+                ));
+            }
+        }
+
+        PrintRequest { kind, out }
+    }));
+
+    prints
+}
+
+pub fn parse_target_triple(early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches) -> TargetTuple {
+    match matches.opt_str("target") {
+        Some(target) if target.ends_with(".json") => {
+            let path = Path::new(&target);
+            TargetTuple::from_path(path).unwrap_or_else(|_| {
+                early_dcx.early_fatal(format!("target file {path:?} does not exist"))
+            })
+        }
+        Some(target) => TargetTuple::TargetTuple(target),
+        _ => TargetTuple::from_tuple(host_tuple()),
+    }
+}
+
+fn parse_opt_level(
+    early_dcx: &EarlyDiagCtxt,
+    matches: &getopts::Matches,
+    cg: &CodegenOptions,
+) -> 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.split('=').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_dcx.early_fatal(format!(
+                    "optimization level needs to be \
+                            between 0-3, s or z (instead was `{arg}`)"
+                ));
+            }
+        }
+    }
+}
+
+fn select_debuginfo(matches: &getopts::Matches, cg: &CodegenOptions) -> 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.split('=').next() { Some(i) } else { None }
+        })
+        .max();
+    if max_g > max_c { DebugInfo::Full } else { cg.debuginfo }
+}
+
+fn parse_assert_incr_state(
+    early_dcx: &EarlyDiagCtxt,
+    opt_assertion: &Option<String>,
+) -> Option<IncrementalStateAssertion> {
+    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_dcx.early_fatal(format!("unexpected incremental state assertion value: {s}"))
+        }
+        None => None,
+    }
+}
+
+fn parse_native_lib_kind(
+    early_dcx: &EarlyDiagCtxt,
+    matches: &getopts::Matches,
+    kind: &str,
+) -> (NativeLibKind, Option<bool>) {
+    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_dcx.early_fatal(format!("library kind `link-arg` is unstable{why}"))
+            }
+            NativeLibKind::LinkArg
+        }
+        _ => early_dcx.early_fatal(format!(
+            "unknown library kind `{kind}`, expected one of: static, dylib, framework, link-arg"
+        )),
+    };
+    match modifiers {
+        None => (kind, None),
+        Some(modifiers) => parse_native_lib_modifiers(early_dcx, kind, modifiers, matches),
+    }
+}
+
+fn parse_native_lib_modifiers(
+    early_dcx: &EarlyDiagCtxt,
+    mut kind: NativeLibKind,
+    modifiers: &str,
+    matches: &getopts::Matches,
+) -> (NativeLibKind, Option<bool>) {
+    let mut verbatim = None;
+    for modifier in modifiers.split(',') {
+        let (modifier, value) = match modifier.strip_prefix(['+', '-']) {
+            Some(m) => (m, modifier.starts_with('+')),
+            None => early_dcx.early_fatal(
+                "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_dcx.early_fatal(format!("linking modifier `{modifier}` is unstable{why}"))
+            }
+        };
+        let assign_modifier = |dst: &mut Option<bool>| {
+            if dst.is_some() {
+                let msg = format!("multiple `{modifier}` modifiers in a single `-l` option");
+                early_dcx.early_fatal(msg)
+            } else {
+                *dst = Some(value);
+            }
+        };
+        match (modifier, &mut kind) {
+            ("bundle", NativeLibKind::Static { bundle, .. }) => assign_modifier(bundle),
+            ("bundle", _) => early_dcx.early_fatal(
+                "linking modifier `bundle` is only compatible with `static` linking kind",
+            ),
+
+            ("verbatim", _) => assign_modifier(&mut verbatim),
+
+            ("whole-archive", NativeLibKind::Static { whole_archive, .. }) => {
+                assign_modifier(whole_archive)
+            }
+            ("whole-archive", _) => early_dcx.early_fatal(
+                "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_dcx.early_fatal(
+                "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_dcx.early_fatal(format!(
+                "unknown linking modifier `{modifier}`, expected one \
+                     of: bundle, verbatim, whole-archive, as-needed"
+            )),
+        }
+    }
+
+    (kind, verbatim)
+}
+
+fn parse_libs(early_dcx: &EarlyDiagCtxt, matches: &getopts::Matches) -> Vec<NativeLib> {
+    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(early_dcx, matches, kind);
+                    (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_dcx.early_fatal("library name must not be empty");
+            }
+            NativeLib { name, new_name, kind, verbatim }
+        })
+        .collect()
+}
+
+pub fn parse_externs(
+    early_dcx: &EarlyDiagCtxt,
+    matches: &getopts::Matches,
+    unstable_opts: &UnstableOptions,
+) -> Externs {
+    fn is_ascii_ident(string: &str) -> bool {
+        let mut chars = string.chars();
+        if let Some(start) = chars.next()
+            && (start.is_ascii_alphabetic() || start == '_')
+        {
+            chars.all(|char| char.is_ascii_alphanumeric() || char == '_')
+        } else {
+            false
+        }
+    }
+
+    let is_unstable_enabled = unstable_opts.unstable_options;
+    let mut externs: BTreeMap<String, ExternEntry> = 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()),
+        };
+
+        if !is_ascii_ident(&name) {
+            let mut error = early_dcx.early_struct_fatal(format!(
+                "crate name `{name}` passed to `--extern` is not a valid ASCII identifier"
+            ));
+            let adjusted_name = name.replace('-', "_");
+            if is_ascii_ident(&adjusted_name) {
+                #[allow(rustc::diagnostic_outside_of_impl)] // FIXME
+                error.help(format!(
+                    "consider replacing the dashes with underscores: `{adjusted_name}`"
+                ));
+            }
+            error.emit();
+        }
+
+        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;
+        let mut force = false;
+        if let Some(opts) = options {
+            if !is_unstable_enabled {
+                early_dcx.early_fatal(
+                    "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_dcx.early_fatal(
+                                "the `noprelude` --extern option requires a file path",
+                            );
+                        }
+                    }
+                    "nounused" => nounused_dep = true,
+                    "force" => force = true,
+                    _ => early_dcx.early_fatal(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;
+        // and `force`
+        entry.force |= force;
+        // If any flag is missing `noprelude`, then add to the prelude.
+        entry.add_prelude |= add_prelude;
+    }
+    Externs(externs)
+}
+
+fn parse_remap_path_prefix(
+    early_dcx: &EarlyDiagCtxt,
+    matches: &getopts::Matches,
+    unstable_opts: &UnstableOptions,
+) -> 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_dcx.early_fatal("--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
+}
+
+fn parse_logical_env(
+    early_dcx: &EarlyDiagCtxt,
+    matches: &getopts::Matches,
+) -> FxIndexMap<String, String> {
+    let mut vars = FxIndexMap::default();
+
+    for arg in matches.opt_strs("env-set") {
+        if let Some((name, val)) = arg.split_once('=') {
+            vars.insert(name.to_string(), val.to_string());
+        } else {
+            early_dcx.early_fatal(format!("`--env-set`: specify value for variable `{arg}`"));
+        }
+    }
+
+    vars
+}
+
+// JUSTIFICATION: before wrapper fn is available
+#[allow(rustc::bad_opt_access)]
+pub fn build_session_options(early_dcx: &mut EarlyDiagCtxt, matches: &getopts::Matches) -> Options {
+    let color = parse_color(early_dcx, matches);
+
+    let edition = parse_crate_edition(early_dcx, matches);
+
+    let JsonConfig {
+        json_rendered,
+        json_color,
+        json_artifact_notifications,
+        json_unused_externs,
+        json_future_incompat,
+    } = parse_json(early_dcx, matches);
+
+    let error_format = parse_error_format(early_dcx, matches, color, json_color, json_rendered);
+
+    early_dcx.abort_if_error_and_set_error_format(error_format);
+
+    let diagnostic_width = matches.opt_get("diagnostic-width").unwrap_or_else(|_| {
+        early_dcx.early_fatal("`--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_dcx.early_fatal(e));
+
+    let mut unstable_opts = UnstableOptions::build(early_dcx, matches);
+    let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(early_dcx, matches);
+
+    check_error_format_stability(early_dcx, &unstable_opts, error_format);
+
+    let output_types = parse_output_types(early_dcx, &unstable_opts, matches);
+
+    let mut cg = CodegenOptions::build(early_dcx, matches);
+    let (disable_local_thinlto, codegen_units) = should_override_cgus_and_disable_thinlto(
+        early_dcx,
+        &output_types,
+        matches,
+        cg.codegen_units,
+    );
+
+    if unstable_opts.threads == 0 {
+        early_dcx.early_fatal("value for threads must be a positive non-zero integer");
+    }
+
+    if unstable_opts.threads == parse::MAX_THREADS_CAP {
+        early_dcx.early_warn(format!("number of threads was capped at {}", parse::MAX_THREADS_CAP));
+    }
+
+    let fuel = unstable_opts.fuel.is_some() || unstable_opts.print_fuel.is_some();
+    if fuel && unstable_opts.threads > 1 {
+        early_dcx.early_fatal("optimization fuel is incompatible with multiple threads");
+    }
+    if fuel && cg.incremental.is_some() {
+        early_dcx.early_fatal("optimization fuel is incompatible with incremental compilation");
+    }
+
+    let incremental = cg.incremental.as_ref().map(PathBuf::from);
+
+    let assert_incr_state = parse_assert_incr_state(early_dcx, &unstable_opts.assert_incr_state);
+
+    if cg.profile_generate.enabled() && cg.profile_use.is_some() {
+        early_dcx.early_fatal("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_dcx.early_fatal(
+            "option `-Z profile-sample-use` cannot be used with `-C profile-generate` or `-C profile-use`",
+        );
+    }
+
+    // Check for unstable values of `-C symbol-mangling-version`.
+    // This is what prevents them from being used on stable compilers.
+    match cg.symbol_mangling_version {
+        // Stable values:
+        None | Some(SymbolManglingVersion::V0) => {}
+
+        // Unstable values:
+        Some(SymbolManglingVersion::Legacy) => {
+            if !unstable_opts.unstable_options {
+                early_dcx.early_fatal(
+                    "`-C symbol-mangling-version=legacy` requires `-Z unstable-options`",
+                );
+            }
+        }
+        Some(SymbolManglingVersion::Hashed) => {
+            if !unstable_opts.unstable_options {
+                early_dcx.early_fatal(
+                    "`-C symbol-mangling-version=hashed` requires `-Z unstable-options`",
+                );
+            }
+        }
+    }
+
+    if cg.instrument_coverage != InstrumentCoverage::No {
+        if cg.profile_generate.enabled() || cg.profile_use.is_some() {
+            early_dcx.early_fatal(
+                "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_dcx.early_warn(
+                    "-C instrument-coverage requires symbol mangling version `v0`, \
+                    but `-C symbol-mangling-version=legacy` was specified",
+                );
+            }
+            Some(SymbolManglingVersion::V0) => {}
+            Some(SymbolManglingVersion::Hashed) => {
+                early_dcx.early_warn(
+                    "-C instrument-coverage requires symbol mangling version `v0`, \
+                    but `-C symbol-mangling-version=hashed` was specified",
+                );
+            }
+        }
+    }
+
+    if let Ok(graphviz_font) = std::env::var("RUSTC_GRAPHVIZ_FONT") {
+        // FIXME: this is only mutation of UnstableOptions here, move into
+        // UnstableOptions::build?
+        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_dcx.early_fatal("options `-C embed-bitcode=no` and `-C lto` are incompatible")
+            }
+        }
+    }
+
+    if !nightly_options::is_unstable_enabled(matches)
+        && cg.force_frame_pointers == FramePointer::NonLeaf
+    {
+        early_dcx.early_fatal(
+            "`-Cforce-frame-pointers=non-leaf` or `always` also requires `-Zunstable-options` \
+                and a nightly compiler",
+        )
+    }
+
+    // For testing purposes, until we have more feedback about these options: ensure `-Z
+    // unstable-options` is required when using the unstable `-C link-self-contained` and `-C
+    // linker-flavor` options.
+    if !nightly_options::is_unstable_enabled(matches) {
+        let uses_unstable_self_contained_option =
+            cg.link_self_contained.are_unstable_variants_set();
+        if uses_unstable_self_contained_option {
+            early_dcx.early_fatal(
+                "only `-C link-self-contained` values `y`/`yes`/`on`/`n`/`no`/`off` are stable, \
+                the `-Z unstable-options` flag must also be passed to use the unstable values",
+            );
+        }
+
+        if let Some(flavor) = cg.linker_flavor {
+            if flavor.is_unstable() {
+                early_dcx.early_fatal(format!(
+                    "the linker flavor `{}` is unstable, the `-Z unstable-options` \
+                        flag must also be passed to use the unstable values",
+                    flavor.desc()
+                ));
+            }
+        }
+    }
+
+    // Check `-C link-self-contained` for consistency: individual components cannot be both enabled
+    // and disabled at the same time.
+    if let Some(erroneous_components) = cg.link_self_contained.check_consistency() {
+        let names: String = erroneous_components
+            .into_iter()
+            .map(|c| c.as_str().unwrap())
+            .intersperse(", ")
+            .collect();
+        early_dcx.early_fatal(format!(
+            "some `-C link-self-contained` components were both enabled and disabled: {names}"
+        ));
+    }
+
+    let prints = collect_print_requests(early_dcx, &mut cg, &unstable_opts, matches);
+
+    let cg = cg;
+
+    let sysroot_opt = matches.opt_str("sysroot").map(|m| PathBuf::from(&m));
+    let target_triple = parse_target_triple(early_dcx, matches);
+    let opt_level = parse_opt_level(early_dcx, matches, &cg);
+    // 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);
+    let debuginfo_compression = unstable_opts.debuginfo_compression;
+
+    let libs = parse_libs(early_dcx, matches);
+
+    let test = matches.opt_present("test");
+
+    if !cg.remark.is_empty() && debuginfo == DebugInfo::None {
+        early_dcx.early_warn("-C remark requires \"-C debuginfo=n\" to show source locations");
+    }
+
+    if cg.remark.is_empty() && unstable_opts.remark_dir.is_some() {
+        early_dcx
+            .early_warn("using -Z remark-dir without enabling remarks using e.g. -C remark=all");
+    }
+
+    let externs = parse_externs(early_dcx, matches, &unstable_opts);
+
+    let crate_name = matches.opt_str("crate-name");
+
+    let remap_path_prefix = parse_remap_path_prefix(early_dcx, matches, &unstable_opts);
+
+    let pretty = parse_pretty(early_dcx, &unstable_opts);
+
+    // query-dep-graph is required if dump-dep-graph is given #106736
+    if unstable_opts.dump_dep_graph && !unstable_opts.query_dep_graph {
+        early_dcx.early_fatal("can't dump dependency graph without `-Z query-dep-graph`");
+    }
+
+    let logical_env = parse_logical_env(early_dcx, matches);
+
+    let sysroot = filesearch::materialize_sysroot(sysroot_opt);
+
+    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 bootstrap 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.
+        candidate.join("library/std/src/lib.rs").is_file().then_some(candidate)
+    };
+
+    let mut search_paths = vec![];
+    for s in &matches.opt_strs("L") {
+        search_paths.push(SearchPath::from_cli_opt(
+            &sysroot,
+            &target_triple,
+            early_dcx,
+            s,
+            unstable_opts.unstable_options,
+        ));
+    }
+
+    let working_dir = std::env::current_dir().unwrap_or_else(|e| {
+        early_dcx.early_fatal(format!("Current directory is invalid: {e}"));
+    });
+
+    let file_mapping = file_path_mapping(remap_path_prefix.clone(), &unstable_opts);
+    let working_dir = file_mapping.to_real_filename(&working_dir);
+
+    let verbose = matches.opt_present("verbose") || unstable_opts.verbose_internals;
+
+    Options {
+        assert_incr_state,
+        crate_types,
+        optimize: opt_level,
+        debuginfo,
+        debuginfo_compression,
+        lint_opts,
+        lint_cap,
+        describe_lints,
+        output_types,
+        search_paths,
+        maybe_sysroot: Some(sysroot),
+        target_triple,
+        test,
+        incremental,
+        untracked_state_hash: Default::default(),
+        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,
+        resolve_doc_links: ResolveDocLinks::ExportedMetadata,
+        trimmed_def_paths: false,
+        cli_forced_codegen_units: codegen_units,
+        cli_forced_local_thinlto_off: disable_local_thinlto,
+        remap_path_prefix,
+        real_rust_source_base_dir,
+        edition,
+        json_artifact_notifications,
+        json_unused_externs,
+        json_future_incompat,
+        pretty,
+        working_dir,
+        color,
+        logical_env,
+        verbose,
+    }
+}
+
+fn parse_pretty(early_dcx: &EarlyDiagCtxt, unstable_opts: &UnstableOptions) -> Option<PpMode> {
+    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,
+        "ast-tree,expanded" => AstTreeExpanded,
+        "hir" => Hir(PpHirMode::Normal),
+        "hir,identified" => Hir(PpHirMode::Identified),
+        "hir,typed" => Hir(PpHirMode::Typed),
+        "hir-tree" => HirTree,
+        "thir-tree" => ThirTree,
+        "thir-flat" => ThirFlat,
+        "mir" => Mir,
+        "stable-mir" => StableMir,
+        "mir-cfg" => MirCFG,
+        name => early_dcx.early_fatal(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`, `thir-flat`, `mir`, `stable-mir`, or \
+                            `mir-cfg`; got {name}"
+        )),
+    };
+    debug!("got unpretty option: {first:?}");
+    Some(first)
+}
+
+pub fn make_crate_type_option() -> RustcOptGroup {
+    make_opt(
+        OptionStability::Stable,
+        OptionKind::Multi,
+        "",
+        "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<String>) -> Result<Vec<CrateType>, String> {
+    let mut crate_types: Vec<CrateType> = 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 rustc_feature::UnstableFeatures;
+
+    use super::{OptionStability, RustcOptGroup};
+    use crate::EarlyDiagCtxt;
+
+    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())
+    }
+
+    fn is_nightly_build(krate: Option<&str>) -> bool {
+        UnstableFeatures::from_environment(krate).is_nightly_build()
+    }
+
+    pub fn check_nightly_options(
+        early_dcx: &EarlyDiagCtxt,
+        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);
+        let mut nightly_options_on_stable = 0;
+
+        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_dcx.early_fatal(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 => {
+                    nightly_options_on_stable += 1;
+                    let msg = format!(
+                        "the option `{}` is only accepted on the nightly compiler",
+                        opt.name
+                    );
+                    let _ = early_dcx.early_err(msg);
+                }
+                OptionStability::Stable => {}
+            }
+        }
+        if nightly_options_on_stable > 0 {
+            early_dcx
+                .early_help("consider switching to a nightly toolchain: `rustup default nightly`");
+            early_dcx.early_note("selecting a toolchain with `+toolchain` arguments require a rustup proxy; see <https://rust-lang.github.io/rustup/concepts/index.html>");
+            early_dcx.early_note("for more information about Rust's stability policy, see <https://doc.rust-lang.org/book/appendix-07-nightly-rust.html#unstable-features>");
+            early_dcx.early_fatal(format!(
+                "{} nightly option{} were parsed",
+                nightly_options_on_stable,
+                if nightly_options_on_stable > 1 { "s" } else { "" }
+            ));
+        }
+    }
+}
+
+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),
+        }
+    }
+}
+
+impl IntoDiagArg for CrateType {
+    fn into_diag_arg(self) -> DiagArgValue {
+        self.to_string().into_diag_arg()
+    }
+}
+
+#[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 PpHirMode {
+    /// `-Zunpretty=hir`
+    Normal,
+    /// `-Zunpretty=hir,identified`
+    Identified,
+    /// `-Zunpretty=hir,typed`
+    Typed,
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+/// Pretty print mode
+pub enum PpMode {
+    /// Options that print the source code, i.e.
+    /// `-Zunpretty=normal` and `-Zunpretty=expanded`
+    Source(PpSourceMode),
+    /// `-Zunpretty=ast-tree`
+    AstTree,
+    /// `-Zunpretty=ast-tree,expanded`
+    AstTreeExpanded,
+    /// Options that print the HIR, i.e. `-Zunpretty=hir`
+    Hir(PpHirMode),
+    /// `-Zunpretty=hir-tree`
+    HirTree,
+    /// `-Zunpretty=thir-tree`
+    ThirTree,
+    /// `-Zunpretty=thir-flat`
+    ThirFlat,
+    /// `-Zunpretty=mir`
+    Mir,
+    /// `-Zunpretty=mir-cfg`
+    MirCFG,
+    /// `-Zunpretty=stable-mir`
+    StableMir,
+}
+
+impl PpMode {
+    pub fn needs_ast_map(&self) -> bool {
+        use PpMode::*;
+        use PpSourceMode::*;
+        match *self {
+            Source(Normal | Identified) | AstTree => false,
+
+            Source(Expanded | ExpandedIdentified | ExpandedHygiene)
+            | AstTreeExpanded
+            | Hir(_)
+            | HirTree
+            | ThirTree
+            | ThirFlat
+            | Mir
+            | MirCFG
+            | StableMir => true,
+        }
+    }
+    pub fn needs_hir(&self) -> bool {
+        use PpMode::*;
+        match *self {
+            Source(_) | AstTree | AstTreeExpanded => false,
+
+            Hir(_) | HirTree | ThirTree | ThirFlat | Mir | MirCFG | StableMir => true,
+        }
+    }
+
+    pub fn needs_analysis(&self) -> bool {
+        use PpMode::*;
+        matches!(*self, Hir(PpHirMode::Typed) | Mir | StableMir | MirCFG | ThirTree | ThirFlat)
+    }
+}
+
+#[derive(Clone, Hash, PartialEq, Eq, Debug)]
+pub enum WasiExecModel {
+    Command,
+    Reactor,
+}
+
+/// 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 std::collections::BTreeMap;
+    use std::hash::{DefaultHasher, Hash};
+    use std::num::NonZero;
+    use std::path::PathBuf;
+
+    use rustc_data_structures::fx::FxIndexMap;
+    use rustc_data_structures::stable_hasher::Hash64;
+    use rustc_errors::LanguageIdentifier;
+    use rustc_feature::UnstableFeatures;
+    use rustc_span::RealFileName;
+    use rustc_span::edition::Edition;
+    use rustc_target::spec::{
+        CodeModel, FramePointer, MergeFunctions, OnBrokenPipe, PanicStrategy, RelocModel,
+        RelroLevel, SanitizerSet, SplitDebuginfo, StackProtector, SymbolVisibility, TargetTuple,
+        TlsModel, WasmCAbi,
+    };
+
+    use super::{
+        BranchProtection, CFGuard, CFProtection, CollapseMacroDebuginfo, CoverageOptions,
+        CrateType, DebugInfo, DebugInfoCompression, ErrorOutputType, FmtDebug, FunctionReturn,
+        InliningThreshold, InstrumentCoverage, InstrumentXRay, LinkerPluginLto, LocationDetail,
+        LtoCli, NextSolverConfig, OomStrategy, OptLevel, OutFileName, OutputType, OutputTypes,
+        PatchableFunctionEntry, Polonius, RemapPathScopeComponents, ResolveDocLinks,
+        SourceFileHashAlgorithm, SplitDwarfKind, SwitchWithOptPath, SymbolManglingVersion,
+        WasiExecModel,
+    };
+    use crate::lint;
+    use crate::utils::NativeLib;
+
+    pub(crate) 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<T: DepTrackingHash> DepTrackingHash for Option<T> {
+        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,
+        NonZero<usize>,
+        u64,
+        Hash64,
+        String,
+        PathBuf,
+        lint::Level,
+        WasiExecModel,
+        u32,
+        FramePointer,
+        RelocModel,
+        CodeModel,
+        TlsModel,
+        InstrumentCoverage,
+        CoverageOptions,
+        InstrumentXRay,
+        CrateType,
+        MergeFunctions,
+        OnBrokenPipe,
+        PanicStrategy,
+        RelroLevel,
+        OptLevel,
+        LtoCli,
+        DebugInfo,
+        DebugInfoCompression,
+        CollapseMacroDebuginfo,
+        UnstableFeatures,
+        NativeLib,
+        SanitizerSet,
+        CFGuard,
+        CFProtection,
+        TargetTuple,
+        Edition,
+        LinkerPluginLto,
+        ResolveDocLinks,
+        SplitDebuginfo,
+        SplitDwarfKind,
+        StackProtector,
+        SwitchWithOptPath,
+        SymbolManglingVersion,
+        SymbolVisibility,
+        RemapPathScopeComponents,
+        SourceFileHashAlgorithm,
+        OutFileName,
+        OutputType,
+        RealFileName,
+        LocationDetail,
+        FmtDebug,
+        BranchProtection,
+        OomStrategy,
+        LanguageIdentifier,
+        NextSolverConfig,
+        PatchableFunctionEntry,
+        Polonius,
+        InliningThreshold,
+        FunctionReturn,
+        WasmCAbi,
+    );
+
+    impl<T1, T2> 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<T1, T2, T3> 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<T: DepTrackingHash> DepTrackingHash for Vec<T> {
+        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<T: DepTrackingHash, V: DepTrackingHash> DepTrackingHash for FxIndexMap<T, V> {
+        fn hash(
+            &self,
+            hasher: &mut DefaultHasher,
+            error_format: ErrorOutputType,
+            for_crate_hash: bool,
+        ) {
+            Hash::hash(&self.len(), hasher);
+            for (key, value) in self.iter() {
+                DepTrackingHash::hash(key, hasher, error_format, for_crate_hash);
+                DepTrackingHash::hash(value, 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,
+}
+
+/// How to perform collapse macros debug info
+/// if-ext - if macro from different crate (related to callsite code)
+/// | cmd \ attr    | no  | (unspecified) | external | yes |
+/// | no            | no  | no            | no       | no  |
+/// | (unspecified) | no  | no            | if-ext   | yes |
+/// | external      | no  | if-ext        | if-ext   | yes |
+/// | yes           | yes | yes           | yes      | yes |
+#[derive(Clone, Copy, PartialEq, Hash, Debug)]
+pub enum CollapseMacroDebuginfo {
+    /// Don't collapse debuginfo for the macro
+    No = 0,
+    /// Unspecified value
+    Unspecified = 1,
+    /// Collapse debuginfo if the macro comes from a different crate
+    External = 2,
+    /// Collapse debuginfo for the macro
+    Yes = 3,
+}
+
+/// Which format to use for `-Z dump-mono-stats`
+#[derive(Clone, Copy, PartialEq, Hash, Debug)]
+pub enum DumpMonoStatsFormat {
+    /// Pretty-print a markdown table
+    Markdown,
+    /// Emit structured JSON
+    Json,
+}
+
+impl DumpMonoStatsFormat {
+    pub fn extension(self) -> &'static str {
+        match self {
+            Self::Markdown => "md",
+            Self::Json => "json",
+        }
+    }
+}
+
+/// `-Z patchable-function-entry` representation - how many nops to put before and after function
+/// entry.
+#[derive(Clone, Copy, PartialEq, Hash, Debug, Default)]
+pub struct PatchableFunctionEntry {
+    /// Nops before the entry
+    prefix: u8,
+    /// Nops after the entry
+    entry: u8,
+}
+
+impl PatchableFunctionEntry {
+    pub fn from_total_and_prefix_nops(
+        total_nops: u8,
+        prefix_nops: u8,
+    ) -> Option<PatchableFunctionEntry> {
+        if total_nops < prefix_nops {
+            None
+        } else {
+            Some(Self { prefix: prefix_nops, entry: total_nops - prefix_nops })
+        }
+    }
+    pub fn prefix(&self) -> u8 {
+        self.prefix
+    }
+    pub fn entry(&self) -> u8 {
+        self.entry
+    }
+}
+
+/// `-Zpolonius` values, enabling the borrow checker polonius analysis, and which version: legacy,
+/// or future prototype.
+#[derive(Clone, Copy, PartialEq, Hash, Debug, Default)]
+pub enum Polonius {
+    /// The default value: disabled.
+    #[default]
+    Off,
+
+    /// Legacy version, using datalog and the `polonius-engine` crate. Historical value for `-Zpolonius`.
+    Legacy,
+
+    /// In-tree prototype, extending the NLL infrastructure.
+    Next,
+}
+
+impl Polonius {
+    /// Returns whether the legacy version of polonius is enabled
+    pub fn is_legacy_enabled(&self) -> bool {
+        matches!(self, Polonius::Legacy)
+    }
+
+    /// Returns whether the "next" version of polonius is enabled
+    pub fn is_next_enabled(&self) -> bool {
+        matches!(self, Polonius::Next)
+    }
+}
+
+#[derive(Clone, Copy, PartialEq, Hash, Debug)]
+pub enum InliningThreshold {
+    Always,
+    Sometimes(usize),
+    Never,
+}
+
+impl Default for InliningThreshold {
+    fn default() -> Self {
+        Self::Sometimes(100)
+    }
+}
+
+/// The different settings that the `-Zfunction-return` flag can have.
+#[derive(Clone, Copy, PartialEq, Hash, Debug, Default)]
+pub enum FunctionReturn {
+    /// Keep the function return unmodified.
+    #[default]
+    Keep,
+
+    /// Replace returns with jumps to thunk, without emitting the thunk.
+    ThunkExtern,
+}
+
+/// Whether extra span comments are included when dumping MIR, via the `-Z mir-include-spans` flag.
+/// By default, only enabled in the NLL MIR dumps, and disabled in all other passes.
+#[derive(Clone, Copy, Default, PartialEq, Debug)]
+pub enum MirIncludeSpans {
+    Off,
+    On,
+    /// Default: include extra comments in NLL MIR dumps only. Can be ignored and considered as
+    /// `Off` in all other cases.
+    #[default]
+    Nll,
+}
+
+impl MirIncludeSpans {
+    /// Unless opting into extra comments for all passes, they can be considered disabled.
+    /// The cases where a distinction between on/off and a per-pass value can exist will be handled
+    /// in the passes themselves: i.e. the `Nll` value is considered off for all intents and
+    /// purposes, except for the NLL MIR dump pass.
+    pub fn is_enabled(self) -> bool {
+        self == MirIncludeSpans::On
+    }
+}
diff --git a/compiler/rustc_session/src/config/cfg.rs b/compiler/rustc_session/src/config/cfg.rs
new file mode 100644
index 00000000000..99d9d5b7665
--- /dev/null
+++ b/compiler/rustc_session/src/config/cfg.rs
@@ -0,0 +1,463 @@
+//! cfg and check-cfg configuration
+//!
+//! This module contains the definition of [`Cfg`] and [`CheckCfg`]
+//! as well as the logic for creating the default configuration for a
+//! given [`Session`].
+//!
+//! It also contains the filling of the well known configs, which should
+//! ALWAYS be in sync with the default_configuration.
+//!
+//! ## Adding a new cfg
+//!
+//! Adding a new feature requires two new symbols one for the cfg it-self
+//! and the second one for the unstable feature gate, those are defined in
+//! `rustc_span::symbol`.
+//!
+//! As well as the following points,
+//!  - Add the activation logic in [`default_configuration`]
+//!  - Add the cfg to [`CheckCfg::fill_well_known`] (and related files),
+//!    so that the compiler can know the cfg is expected
+//!  - Add the cfg in [`disallow_cfgs`] to disallow users from setting it via `--cfg`
+//!  - Add the feature gating in `compiler/rustc_feature/src/builtin_attrs.rs`
+
+use std::hash::Hash;
+use std::iter;
+
+use rustc_abi::Align;
+use rustc_ast::ast;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
+use rustc_lint_defs::BuiltinLintDiag;
+use rustc_lint_defs::builtin::EXPLICIT_BUILTIN_CFGS_IN_FLAGS;
+use rustc_span::symbol::{Symbol, sym};
+use rustc_target::spec::{PanicStrategy, RelocModel, SanitizerSet, TARGETS, Target, TargetTuple};
+
+use crate::Session;
+use crate::config::{CrateType, FmtDebug};
+
+/// The parsed `--cfg` options that define the compilation environment of the
+/// crate, used to drive conditional compilation.
+///
+/// An `FxIndexSet` is used to ensure deterministic ordering of error messages
+/// relating to `--cfg`.
+pub type Cfg = FxIndexSet<(Symbol, Option<Symbol>)>;
+
+/// The parsed `--check-cfg` options.
+#[derive(Default)]
+pub struct CheckCfg {
+    /// Is well known names activated
+    pub exhaustive_names: bool,
+    /// Is well known values activated
+    pub exhaustive_values: bool,
+    /// All the expected values for a config name
+    pub expecteds: FxHashMap<Symbol, ExpectedValues<Symbol>>,
+    /// Well known names (only used for diagnostics purposes)
+    pub well_known_names: FxHashSet<Symbol>,
+}
+
+pub enum ExpectedValues<T> {
+    Some(FxHashSet<Option<T>>),
+    Any,
+}
+
+impl<T: Eq + Hash> ExpectedValues<T> {
+    fn insert(&mut self, value: T) -> bool {
+        match self {
+            ExpectedValues::Some(expecteds) => expecteds.insert(Some(value)),
+            ExpectedValues::Any => false,
+        }
+    }
+}
+
+impl<T: Eq + Hash> Extend<T> for ExpectedValues<T> {
+    fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
+        match self {
+            ExpectedValues::Some(expecteds) => expecteds.extend(iter.into_iter().map(Some)),
+            ExpectedValues::Any => {}
+        }
+    }
+}
+
+impl<'a, T: Eq + Hash + Copy + 'a> Extend<&'a T> for ExpectedValues<T> {
+    fn extend<I: IntoIterator<Item = &'a T>>(&mut self, iter: I) {
+        match self {
+            ExpectedValues::Some(expecteds) => expecteds.extend(iter.into_iter().map(|a| Some(*a))),
+            ExpectedValues::Any => {}
+        }
+    }
+}
+
+/// Disallow builtin cfgs from the CLI.
+pub(crate) fn disallow_cfgs(sess: &Session, user_cfgs: &Cfg) {
+    let disallow = |cfg: &(Symbol, Option<Symbol>), controlled_by| {
+        let cfg_name = cfg.0;
+        let cfg = if let Some(value) = cfg.1 {
+            format!(r#"{}="{}""#, cfg_name, value)
+        } else {
+            format!("{}", cfg_name)
+        };
+        sess.psess.opt_span_buffer_lint(
+            EXPLICIT_BUILTIN_CFGS_IN_FLAGS,
+            None,
+            ast::CRATE_NODE_ID,
+            BuiltinLintDiag::UnexpectedBuiltinCfg { cfg, cfg_name, controlled_by },
+        )
+    };
+
+    // We want to restrict setting builtin cfgs that will produce incoherent behavior
+    // between the cfg and the rustc cli flag that sets it.
+    //
+    // The tests are in tests/ui/cfg/disallowed-cli-cfgs.rs.
+
+    // By-default all builtin cfgs are disallowed, only those are allowed:
+    //  - test: as it makes sense to the have the `test` cfg active without the builtin
+    //          test harness. See Cargo `harness = false` config.
+    //
+    // Cargo `--cfg test`: https://github.com/rust-lang/cargo/blob/bc89bffa5987d4af8f71011c7557119b39e44a65/src/cargo/core/compiler/mod.rs#L1124
+
+    for cfg in user_cfgs {
+        match cfg {
+            (sym::overflow_checks, None) => disallow(cfg, "-C overflow-checks"),
+            (sym::debug_assertions, None) => disallow(cfg, "-C debug-assertions"),
+            (sym::ub_checks, None) => disallow(cfg, "-Z ub-checks"),
+            (sym::sanitize, None | Some(_)) => disallow(cfg, "-Z sanitizer"),
+            (
+                sym::sanitizer_cfi_generalize_pointers | sym::sanitizer_cfi_normalize_integers,
+                None | Some(_),
+            ) => disallow(cfg, "-Z sanitizer=cfi"),
+            (sym::proc_macro, None) => disallow(cfg, "--crate-type proc-macro"),
+            (sym::panic, Some(sym::abort | sym::unwind)) => disallow(cfg, "-C panic"),
+            (sym::target_feature, Some(_)) => disallow(cfg, "-C target-feature"),
+            (sym::unix, None)
+            | (sym::windows, None)
+            | (sym::relocation_model, Some(_))
+            | (sym::target_abi, None | Some(_))
+            | (sym::target_arch, Some(_))
+            | (sym::target_endian, Some(_))
+            | (sym::target_env, None | Some(_))
+            | (sym::target_family, Some(_))
+            | (sym::target_os, Some(_))
+            | (sym::target_pointer_width, Some(_))
+            | (sym::target_vendor, None | Some(_))
+            | (sym::target_has_atomic, Some(_))
+            | (sym::target_has_atomic_equal_alignment, Some(_))
+            | (sym::target_has_atomic_load_store, Some(_))
+            | (sym::target_thread_local, None) => disallow(cfg, "--target"),
+            (sym::fmt_debug, None | Some(_)) => disallow(cfg, "-Z fmt-debug"),
+            _ => {}
+        }
+    }
+}
+
+/// Generate the default configs for a given session
+pub(crate) fn default_configuration(sess: &Session) -> Cfg {
+    let mut ret = Cfg::default();
+
+    macro_rules! ins_none {
+        ($key:expr) => {
+            ret.insert(($key, None));
+        };
+    }
+    macro_rules! ins_str {
+        ($key:expr, $val_str:expr) => {
+            ret.insert(($key, Some(Symbol::intern($val_str))));
+        };
+    }
+    macro_rules! ins_sym {
+        ($key:expr, $val_sym:expr) => {
+            ret.insert(($key, Some($val_sym)));
+        };
+    }
+
+    // Symbols are inserted in alphabetical order as much as possible.
+    // The exceptions are where control flow forces things out of order.
+    //
+    // Run `rustc --print cfg` to see the configuration in practice.
+    //
+    // NOTE: These insertions should be kept in sync with
+    // `CheckCfg::fill_well_known` below.
+
+    if sess.opts.debug_assertions {
+        ins_none!(sym::debug_assertions);
+    }
+
+    if sess.is_nightly_build() {
+        match sess.opts.unstable_opts.fmt_debug {
+            FmtDebug::Full => {
+                ins_sym!(sym::fmt_debug, sym::full);
+            }
+            FmtDebug::Shallow => {
+                ins_sym!(sym::fmt_debug, sym::shallow);
+            }
+            FmtDebug::None => {
+                ins_sym!(sym::fmt_debug, sym::none);
+            }
+        }
+    }
+
+    if sess.overflow_checks() {
+        ins_none!(sym::overflow_checks);
+    }
+
+    ins_sym!(sym::panic, sess.panic_strategy().desc_symbol());
+
+    // JUSTIFICATION: before wrapper fn is available
+    #[allow(rustc::bad_opt_access)]
+    if sess.opts.crate_types.contains(&CrateType::ProcMacro) {
+        ins_none!(sym::proc_macro);
+    }
+
+    if sess.is_nightly_build() {
+        ins_sym!(sym::relocation_model, sess.target.relocation_model.desc_symbol());
+    }
+
+    for mut s in sess.opts.unstable_opts.sanitizer {
+        // KASAN is still ASAN under the hood, so it uses the same attribute.
+        if s == SanitizerSet::KERNELADDRESS {
+            s = SanitizerSet::ADDRESS;
+        }
+        ins_str!(sym::sanitize, &s.to_string());
+    }
+
+    if sess.is_sanitizer_cfi_generalize_pointers_enabled() {
+        ins_none!(sym::sanitizer_cfi_generalize_pointers);
+    }
+    if sess.is_sanitizer_cfi_normalize_integers_enabled() {
+        ins_none!(sym::sanitizer_cfi_normalize_integers);
+    }
+
+    ins_str!(sym::target_abi, &sess.target.abi);
+    ins_str!(sym::target_arch, &sess.target.arch);
+    ins_str!(sym::target_endian, sess.target.endian.as_str());
+    ins_str!(sym::target_env, &sess.target.env);
+
+    for family in sess.target.families.as_ref() {
+        ins_str!(sym::target_family, family);
+        if family == "windows" {
+            ins_none!(sym::windows);
+        } else if family == "unix" {
+            ins_none!(sym::unix);
+        }
+    }
+
+    // `target_has_atomic*`
+    let layout = sess.target.parse_data_layout().unwrap_or_else(|err| {
+        sess.dcx().emit_fatal(err);
+    });
+    let mut has_atomic = false;
+    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 >= sess.target.min_atomic_width() && i <= sess.target.max_atomic_width() {
+            if !has_atomic {
+                has_atomic = true;
+                if sess.is_nightly_build() {
+                    if sess.target.atomic_cas {
+                        ins_none!(sym::target_has_atomic);
+                    }
+                    ins_none!(sym::target_has_atomic_load_store);
+                }
+            }
+            let mut insert_atomic = |sym, align: Align| {
+                if sess.target.atomic_cas {
+                    ins_sym!(sym::target_has_atomic, sym);
+                }
+                if align.bits() == i {
+                    ins_sym!(sym::target_has_atomic_equal_alignment, sym);
+                }
+                ins_sym!(sym::target_has_atomic_load_store, sym);
+            };
+            insert_atomic(sym::integer(i), align);
+            if sess.target.pointer_width as u64 == i {
+                insert_atomic(sym::ptr, layout.pointer_align.abi);
+            }
+        }
+    }
+
+    ins_str!(sym::target_os, &sess.target.os);
+    ins_sym!(sym::target_pointer_width, sym::integer(sess.target.pointer_width));
+
+    if sess.opts.unstable_opts.has_thread_local.unwrap_or(sess.target.has_thread_local) {
+        ins_none!(sym::target_thread_local);
+    }
+
+    ins_str!(sym::target_vendor, &sess.target.vendor);
+
+    // If the user wants a test runner, then add the test cfg.
+    if sess.is_test_crate() {
+        ins_none!(sym::test);
+    }
+
+    if sess.ub_checks() {
+        ins_none!(sym::ub_checks);
+    }
+
+    ret
+}
+
+impl CheckCfg {
+    /// Fill the current [`CheckCfg`] with all the well known cfgs
+    pub fn fill_well_known(&mut self, current_target: &Target) {
+        if !self.exhaustive_values && !self.exhaustive_names {
+            return;
+        }
+
+        // for `#[cfg(foo)]` (ie. cfg value is none)
+        let no_values = || {
+            let mut values = FxHashSet::default();
+            values.insert(None);
+            ExpectedValues::Some(values)
+        };
+
+        // preparation for inserting some values
+        let empty_values = || {
+            let values = FxHashSet::default();
+            ExpectedValues::Some(values)
+        };
+
+        macro_rules! ins {
+            ($name:expr, $values:expr) => {{
+                self.well_known_names.insert($name);
+                self.expecteds.entry($name).or_insert_with($values)
+            }};
+        }
+
+        // Symbols are inserted in alphabetical order as much as possible.
+        // The exceptions are where control flow forces things out of order.
+        //
+        // NOTE: This should be kept in sync with `default_configuration`.
+        // Note that symbols inserted conditionally in `default_configuration`
+        // are inserted unconditionally here.
+        //
+        // When adding a new config here you should also update
+        // `tests/ui/check-cfg/well-known-values.rs` (in order to test the
+        // expected values of the new config) and bless the all directory.
+        //
+        // Don't forget to update `src/doc/rustc/src/check-cfg.md`
+        // in the unstable book as well!
+
+        ins!(sym::debug_assertions, no_values);
+
+        ins!(sym::fmt_debug, empty_values).extend(FmtDebug::all());
+
+        // These four are never set by rustc, but we set them anyway; they
+        // should not trigger the lint because `cargo clippy`, `cargo doc`,
+        // `cargo test`, `cargo miri run` and `cargo fmt` (respectively)
+        // can set them.
+        ins!(sym::clippy, no_values);
+        ins!(sym::doc, no_values);
+        ins!(sym::doctest, no_values);
+        ins!(sym::miri, no_values);
+        ins!(sym::rustfmt, no_values);
+
+        ins!(sym::overflow_checks, no_values);
+
+        ins!(sym::panic, empty_values).extend(&PanicStrategy::all());
+
+        ins!(sym::proc_macro, no_values);
+
+        ins!(sym::relocation_model, empty_values).extend(RelocModel::all());
+
+        let sanitize_values = SanitizerSet::all()
+            .into_iter()
+            .map(|sanitizer| Symbol::intern(sanitizer.as_str().unwrap()));
+        ins!(sym::sanitize, empty_values).extend(sanitize_values);
+
+        ins!(sym::sanitizer_cfi_generalize_pointers, no_values);
+        ins!(sym::sanitizer_cfi_normalize_integers, no_values);
+
+        ins!(sym::target_feature, empty_values).extend(
+            rustc_target::target_features::all_rust_features()
+                .filter(|(_, s)| s.is_supported())
+                .map(|(f, _s)| f)
+                .chain(rustc_target::target_features::RUSTC_SPECIFIC_FEATURES.iter().cloned())
+                .map(Symbol::intern),
+        );
+
+        // sym::target_*
+        {
+            const VALUES: [&Symbol; 8] = [
+                &sym::target_abi,
+                &sym::target_arch,
+                &sym::target_endian,
+                &sym::target_env,
+                &sym::target_family,
+                &sym::target_os,
+                &sym::target_pointer_width,
+                &sym::target_vendor,
+            ];
+
+            // Initialize (if not already initialized)
+            for &e in VALUES {
+                if !self.exhaustive_values {
+                    ins!(e, || ExpectedValues::Any);
+                } else {
+                    ins!(e, empty_values);
+                }
+            }
+
+            if self.exhaustive_values {
+                // 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 [
+                    Some(values_target_abi),
+                    Some(values_target_arch),
+                    Some(values_target_endian),
+                    Some(values_target_env),
+                    Some(values_target_family),
+                    Some(values_target_os),
+                    Some(values_target_pointer_width),
+                    Some(values_target_vendor),
+                ] = self.expecteds.get_many_mut(VALUES)
+                else {
+                    panic!("unable to get all the check-cfg values buckets");
+                };
+
+                for target in TARGETS
+                    .iter()
+                    .map(|target| Target::expect_builtin(&TargetTuple::from_tuple(target)))
+                    .chain(iter::once(current_target.clone()))
+                {
+                    values_target_abi.insert(Symbol::intern(&target.options.abi));
+                    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_family.extend(
+                        target.options.families.iter().map(|family| Symbol::intern(family)),
+                    );
+                    values_target_os.insert(Symbol::intern(&target.options.os));
+                    values_target_pointer_width.insert(sym::integer(target.pointer_width));
+                    values_target_vendor.insert(Symbol::intern(&target.options.vendor));
+                }
+            }
+        }
+
+        let atomic_values = &[
+            sym::ptr,
+            sym::integer(8usize),
+            sym::integer(16usize),
+            sym::integer(32usize),
+            sym::integer(64usize),
+            sym::integer(128usize),
+        ];
+        for sym in [
+            sym::target_has_atomic,
+            sym::target_has_atomic_equal_alignment,
+            sym::target_has_atomic_load_store,
+        ] {
+            ins!(sym, no_values).extend(atomic_values);
+        }
+
+        ins!(sym::target_thread_local, no_values);
+
+        ins!(sym::test, no_values);
+
+        ins!(sym::ub_checks, no_values);
+
+        ins!(sym::unix, no_values);
+        ins!(sym::windows, no_values);
+    }
+}
diff --git a/compiler/rustc_session/src/config/sigpipe.rs b/compiler/rustc_session/src/config/sigpipe.rs
new file mode 100644
index 00000000000..1830ee03485
--- /dev/null
+++ b/compiler/rustc_session/src/config/sigpipe.rs
@@ -0,0 +1,25 @@
+//! NOTE: Keep these constants in sync with `library/std/src/sys/pal/unix/mod.rs`!
+
+/// The default value if `-Zon-broken-pipe=...` is not specified. This resolves
+/// to `SIG_IGN` in `library/std/src/sys/pal/unix/mod.rs`.
+///
+/// Note that `SIG_IGN` has been the Rust default since 2014. See
+/// <https://github.com/rust-lang/rust/issues/62569>.
+#[allow(dead_code)]
+pub const DEFAULT: u8 = 0;
+
+/// Do not touch `SIGPIPE`. Use whatever the parent process uses.
+#[allow(dead_code)]
+pub const INHERIT: u8 = 1;
+
+/// Change `SIGPIPE` to `SIG_IGN` so that failed writes results in `EPIPE`
+/// that are eventually converted to `ErrorKind::BrokenPipe`.
+#[allow(dead_code)]
+pub const SIG_IGN: u8 = 2;
+
+/// Change `SIGPIPE` to `SIG_DFL` so that the process is killed when trying
+/// to write to a closed pipe. This is usually the desired behavior for CLI
+/// apps that produce textual output that you want to pipe to other programs
+/// such as `head -n 1`.
+#[allow(dead_code)]
+pub const SIG_DFL: u8 = 3;
diff --git a/compiler/rustc_session/src/cstore.rs b/compiler/rustc_session/src/cstore.rs
new file mode 100644
index 00000000000..3e2cbc18933
--- /dev/null
+++ b/compiler/rustc_session/src/cstore.rs
@@ -0,0 +1,240 @@
+//! 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 std::any::Any;
+use std::path::PathBuf;
+
+use rustc_abi::ExternAbi;
+use rustc_ast as ast;
+use rustc_data_structures::sync::{self, AppendOnlyIndexVec, FreezeLock};
+use rustc_hir::def_id::{
+    CrateNum, DefId, LOCAL_CRATE, LocalDefId, StableCrateId, StableCrateIdMap,
+};
+use rustc_hir::definitions::{DefKey, DefPath, DefPathHash, Definitions};
+use rustc_macros::{Decodable, Encodable, HashStable_Generic};
+use rustc_span::Span;
+use rustc_span::symbol::Symbol;
+
+use crate::search_paths::PathKind;
+use crate::utils::NativeLibKind;
+
+// 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<Item = &PathBuf> {
+        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: Symbol,
+    /// If packed_bundled_libs enabled, actual filename of library is stored.
+    pub filename: Option<Symbol>,
+    pub cfg: Option<ast::MetaItemInner>,
+    pub foreign_module: Option<DefId>,
+    pub verbatim: Option<bool>,
+    pub dll_imports: Vec<DllImport>,
+}
+
+impl NativeLib {
+    pub fn has_modifiers(&self) -> bool {
+        self.verbatim.is_some() || self.kind.has_modifiers()
+    }
+
+    pub fn wasm_import_module(&self) -> Option<Symbol> {
+        if self.kind == NativeLibKind::WasmImportModule { Some(self.name) } else { None }
+    }
+}
+
+/// Different ways that the PE Format can decorate a symbol name.
+/// From <https://docs.microsoft.com/en-us/windows/win32/debug/pe-format#import-name-type>
+#[derive(Copy, Clone, Debug, Encodable, Decodable, HashStable_Generic, PartialEq, Eq)]
+pub enum PeImportNameType {
+    /// IMPORT_ORDINAL
+    /// Uses the ordinal (i.e., a number) rather than the name.
+    Ordinal(u16),
+    /// Same as IMPORT_NAME
+    /// Name is decorated with all prefixes and suffixes.
+    Decorated,
+    /// Same as IMPORT_NAME_NOPREFIX
+    /// Prefix (e.g., the leading `_` or `@`) is skipped, but suffix is kept.
+    NoPrefix,
+    /// Same as IMPORT_NAME_UNDECORATE
+    /// Prefix (e.g., the leading `_` or `@`) and suffix (the first `@` and all
+    /// trailing characters) are skipped.
+    Undecorated,
+}
+
+#[derive(Clone, Debug, Encodable, Decodable, HashStable_Generic)]
+pub struct DllImport {
+    pub name: Symbol,
+    pub import_name_type: Option<PeImportNameType>,
+    /// 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,
+    /// Is this for a function (rather than a static variable).
+    pub is_fn: bool,
+}
+
+impl DllImport {
+    pub fn ordinal(&self) -> Option<u16> {
+        if let Some(PeImportNameType::Ordinal(ordinal)) = self.import_name_type {
+            Some(ordinal)
+        } else {
+            None
+        }
+    }
+
+    pub fn is_missing_decorations(&self) -> bool {
+        self.import_name_type == Some(PeImportNameType::Undecorated)
+            || self.import_name_type == Some(PeImportNameType::NoPrefix)
+    }
+}
+
+/// 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<DefId>,
+    pub def_id: DefId,
+    pub abi: ExternAbi,
+}
+
+#[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,
+}
+
+/// 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;
+    fn untracked_as_any(&mut self) -> &mut 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;
+}
+
+pub type CrateStoreDyn = dyn CrateStore + sync::DynSync + sync::DynSend;
+
+pub struct Untracked {
+    pub cstore: FreezeLock<Box<CrateStoreDyn>>,
+    /// Reference span for definitions.
+    pub source_span: AppendOnlyIndexVec<LocalDefId, Span>,
+    pub definitions: FreezeLock<Definitions>,
+    /// The interned [StableCrateId]s.
+    pub stable_crate_ids: FreezeLock<StableCrateIdMap>,
+}
diff --git a/compiler/rustc_session/src/errors.rs b/compiler/rustc_session/src/errors.rs
new file mode 100644
index 00000000000..33f84f10447
--- /dev/null
+++ b/compiler/rustc_session/src/errors.rs
@@ -0,0 +1,513 @@
+use std::num::NonZero;
+
+use rustc_ast::token;
+use rustc_ast::util::literal::LitError;
+use rustc_errors::codes::*;
+use rustc_errors::{
+    Diag, DiagCtxtHandle, DiagMessage, Diagnostic, EmissionGuarantee, ErrorGuaranteed, Level,
+    MultiSpan,
+};
+use rustc_macros::{Diagnostic, Subdiagnostic};
+use rustc_span::{Span, Symbol};
+use rustc_target::spec::{SplitDebuginfo, StackProtector, TargetTuple};
+
+use crate::config::CrateType;
+use crate::parse::ParseSess;
+
+pub(crate) struct FeatureGateError {
+    pub(crate) span: MultiSpan,
+    pub(crate) explain: DiagMessage,
+}
+
+impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for FeatureGateError {
+    #[track_caller]
+    fn into_diag(self, dcx: DiagCtxtHandle<'a>, level: Level) -> Diag<'a, G> {
+        Diag::new(dcx, level, self.explain).with_span(self.span).with_code(E0658)
+    }
+}
+
+#[derive(Subdiagnostic)]
+#[note(session_feature_diagnostic_for_issue)]
+pub(crate) struct FeatureDiagnosticForIssue {
+    pub(crate) n: NonZero<u32>,
+}
+
+#[derive(Subdiagnostic)]
+#[note(session_feature_suggest_upgrade_compiler)]
+pub(crate) struct SuggestUpgradeCompiler {
+    date: &'static str,
+}
+
+impl SuggestUpgradeCompiler {
+    pub(crate) fn ui_testing() -> Self {
+        Self { date: "YYYY-MM-DD" }
+    }
+
+    pub(crate) fn new() -> Option<Self> {
+        let date = option_env!("CFG_VER_DATE")?;
+
+        Some(Self { date })
+    }
+}
+
+#[derive(Subdiagnostic)]
+#[help(session_feature_diagnostic_help)]
+pub(crate) struct FeatureDiagnosticHelp {
+    pub(crate) feature: Symbol,
+}
+
+#[derive(Subdiagnostic)]
+#[suggestion(
+    session_feature_diagnostic_suggestion,
+    applicability = "maybe-incorrect",
+    code = "#![feature({feature})]\n"
+)]
+pub struct FeatureDiagnosticSuggestion {
+    pub feature: Symbol,
+    #[primary_span]
+    pub span: Span,
+}
+
+#[derive(Subdiagnostic)]
+#[help(session_cli_feature_diagnostic_help)]
+pub(crate) struct CliFeatureDiagnosticHelp {
+    pub(crate) feature: Symbol,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_not_circumvent_feature)]
+pub(crate) struct NotCircumventFeature;
+
+#[derive(Diagnostic)]
+#[diag(session_linker_plugin_lto_windows_not_supported)]
+pub(crate) struct LinkerPluginToWindowsNotSupported;
+
+#[derive(Diagnostic)]
+#[diag(session_profile_use_file_does_not_exist)]
+pub(crate) struct ProfileUseFileDoesNotExist<'a> {
+    pub(crate) path: &'a std::path::Path,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_profile_sample_use_file_does_not_exist)]
+pub(crate) struct ProfileSampleUseFileDoesNotExist<'a> {
+    pub(crate) path: &'a std::path::Path,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_target_requires_unwind_tables)]
+pub(crate) struct TargetRequiresUnwindTables;
+
+#[derive(Diagnostic)]
+#[diag(session_instrumentation_not_supported)]
+pub(crate) struct InstrumentationNotSupported {
+    pub(crate) us: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_sanitizer_not_supported)]
+pub(crate) struct SanitizerNotSupported {
+    pub(crate) us: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_sanitizers_not_supported)]
+pub(crate) struct SanitizersNotSupported {
+    pub(crate) us: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_cannot_mix_and_match_sanitizers)]
+pub(crate) struct CannotMixAndMatchSanitizers {
+    pub(crate) first: String,
+    pub(crate) second: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_cannot_enable_crt_static_linux)]
+pub(crate) struct CannotEnableCrtStaticLinux;
+
+#[derive(Diagnostic)]
+#[diag(session_sanitizer_cfi_requires_lto)]
+pub(crate) struct SanitizerCfiRequiresLto;
+
+#[derive(Diagnostic)]
+#[diag(session_sanitizer_cfi_requires_single_codegen_unit)]
+pub(crate) struct SanitizerCfiRequiresSingleCodegenUnit;
+
+#[derive(Diagnostic)]
+#[diag(session_sanitizer_cfi_canonical_jump_tables_requires_cfi)]
+pub(crate) struct SanitizerCfiCanonicalJumpTablesRequiresCfi;
+
+#[derive(Diagnostic)]
+#[diag(session_sanitizer_cfi_generalize_pointers_requires_cfi)]
+pub(crate) struct SanitizerCfiGeneralizePointersRequiresCfi;
+
+#[derive(Diagnostic)]
+#[diag(session_sanitizer_cfi_normalize_integers_requires_cfi)]
+pub(crate) struct SanitizerCfiNormalizeIntegersRequiresCfi;
+
+#[derive(Diagnostic)]
+#[diag(session_sanitizer_kcfi_requires_panic_abort)]
+pub(crate) struct SanitizerKcfiRequiresPanicAbort;
+
+#[derive(Diagnostic)]
+#[diag(session_split_lto_unit_requires_lto)]
+pub(crate) struct SplitLtoUnitRequiresLto;
+
+#[derive(Diagnostic)]
+#[diag(session_unstable_virtual_function_elimination)]
+pub(crate) struct UnstableVirtualFunctionElimination;
+
+#[derive(Diagnostic)]
+#[diag(session_unsupported_dwarf_version)]
+pub(crate) struct UnsupportedDwarfVersion {
+    pub(crate) dwarf_version: u32,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_embed_source_insufficient_dwarf_version)]
+pub(crate) struct EmbedSourceInsufficientDwarfVersion {
+    pub(crate) dwarf_version: u32,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_embed_source_requires_debug_info)]
+pub(crate) struct EmbedSourceRequiresDebugInfo;
+
+#[derive(Diagnostic)]
+#[diag(session_target_stack_protector_not_supported)]
+pub(crate) struct StackProtectorNotSupportedForTarget<'a> {
+    pub(crate) stack_protector: StackProtector,
+    pub(crate) target_triple: &'a TargetTuple,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_target_small_data_threshold_not_supported)]
+pub(crate) struct SmallDataThresholdNotSupportedForTarget<'a> {
+    pub(crate) target_triple: &'a TargetTuple,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_branch_protection_requires_aarch64)]
+pub(crate) struct BranchProtectionRequiresAArch64;
+
+#[derive(Diagnostic)]
+#[diag(session_split_debuginfo_unstable_platform)]
+pub(crate) struct SplitDebugInfoUnstablePlatform {
+    pub(crate) debuginfo: SplitDebuginfo,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_file_is_not_writeable)]
+pub(crate) struct FileIsNotWriteable<'a> {
+    pub(crate) file: &'a std::path::Path,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_file_write_fail)]
+pub(crate) struct FileWriteFail<'a> {
+    pub(crate) path: &'a std::path::Path,
+    pub(crate) err: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_crate_name_does_not_match)]
+pub(crate) struct CrateNameDoesNotMatch {
+    #[primary_span]
+    pub(crate) span: Span,
+    pub(crate) s: Symbol,
+    pub(crate) name: Symbol,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_crate_name_invalid)]
+pub(crate) struct CrateNameInvalid<'a> {
+    pub(crate) s: &'a str,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_crate_name_empty)]
+pub(crate) struct CrateNameEmpty {
+    #[primary_span]
+    pub(crate) span: Option<Span>,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_invalid_character_in_create_name)]
+pub(crate) struct InvalidCharacterInCrateName {
+    #[primary_span]
+    pub(crate) span: Option<Span>,
+    pub(crate) character: char,
+    pub(crate) crate_name: Symbol,
+    #[subdiagnostic]
+    pub(crate) crate_name_help: Option<InvalidCrateNameHelp>,
+}
+
+#[derive(Subdiagnostic)]
+pub(crate) enum InvalidCrateNameHelp {
+    #[help(session_invalid_character_in_create_name_help)]
+    AddCrateName,
+}
+
+#[derive(Subdiagnostic)]
+#[multipart_suggestion(session_expr_parentheses_needed, applicability = "machine-applicable")]
+pub struct ExprParenthesesNeeded {
+    #[suggestion_part(code = "(")]
+    left: Span,
+    #[suggestion_part(code = ")")]
+    right: Span,
+}
+
+impl ExprParenthesesNeeded {
+    pub fn surrounding(s: Span) -> Self {
+        ExprParenthesesNeeded { left: s.shrink_to_lo(), right: s.shrink_to_hi() }
+    }
+}
+
+#[derive(Diagnostic)]
+#[diag(session_skipping_const_checks)]
+pub(crate) struct SkippingConstChecks {
+    #[subdiagnostic]
+    pub(crate) unleashed_features: Vec<UnleashedFeatureHelp>,
+}
+
+#[derive(Subdiagnostic)]
+pub(crate) enum UnleashedFeatureHelp {
+    #[help(session_unleashed_feature_help_named)]
+    Named {
+        #[primary_span]
+        span: Span,
+        gate: Symbol,
+    },
+    #[help(session_unleashed_feature_help_unnamed)]
+    Unnamed {
+        #[primary_span]
+        span: Span,
+    },
+}
+
+#[derive(Diagnostic)]
+#[diag(session_invalid_literal_suffix)]
+struct InvalidLiteralSuffix<'a> {
+    #[primary_span]
+    #[label]
+    span: Span,
+    // FIXME(#100717)
+    kind: &'a str,
+    suffix: Symbol,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_invalid_int_literal_width)]
+#[help]
+struct InvalidIntLiteralWidth {
+    #[primary_span]
+    span: Span,
+    width: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_invalid_num_literal_base_prefix)]
+#[note]
+struct InvalidNumLiteralBasePrefix {
+    #[primary_span]
+    #[suggestion(applicability = "maybe-incorrect", code = "{fixed}")]
+    span: Span,
+    fixed: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_invalid_num_literal_suffix)]
+#[help]
+struct InvalidNumLiteralSuffix {
+    #[primary_span]
+    #[label]
+    span: Span,
+    suffix: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_invalid_float_literal_width)]
+#[help]
+struct InvalidFloatLiteralWidth {
+    #[primary_span]
+    span: Span,
+    width: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_invalid_float_literal_suffix)]
+#[help]
+struct InvalidFloatLiteralSuffix {
+    #[primary_span]
+    #[label]
+    span: Span,
+    suffix: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_int_literal_too_large)]
+#[note]
+struct IntLiteralTooLarge {
+    #[primary_span]
+    span: Span,
+    limit: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_hexadecimal_float_literal_not_supported)]
+struct HexadecimalFloatLiteralNotSupported {
+    #[primary_span]
+    #[label(session_not_supported)]
+    span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_octal_float_literal_not_supported)]
+struct OctalFloatLiteralNotSupported {
+    #[primary_span]
+    #[label(session_not_supported)]
+    span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_binary_float_literal_not_supported)]
+struct BinaryFloatLiteralNotSupported {
+    #[primary_span]
+    #[label(session_not_supported)]
+    span: Span,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_unsupported_crate_type_for_target)]
+pub(crate) struct UnsupportedCrateTypeForTarget<'a> {
+    pub(crate) crate_type: CrateType,
+    pub(crate) target_triple: &'a TargetTuple,
+}
+
+pub fn report_lit_error(
+    psess: &ParseSess,
+    err: LitError,
+    lit: token::Lit,
+    span: Span,
+) -> ErrorGuaranteed {
+    // Checks if `s` looks like i32 or u1234 etc.
+    fn looks_like_width_suffix(first_chars: &[char], s: &str) -> bool {
+        s.len() > 1 && s.starts_with(first_chars) && s[1..].chars().all(|c| c.is_ascii_digit())
+    }
+
+    // Try to lowercase the prefix if the prefix and suffix are valid.
+    fn fix_base_capitalisation(prefix: &str, suffix: &str) -> Option<String> {
+        let mut chars = suffix.chars();
+
+        let base_char = chars.next().unwrap();
+        let base = match base_char {
+            'B' => 2,
+            'O' => 8,
+            'X' => 16,
+            _ => return None,
+        };
+
+        // check that the suffix contains only base-appropriate characters
+        let valid = prefix == "0"
+            && chars
+                .filter(|c| *c != '_')
+                .take_while(|c| *c != 'i' && *c != 'u')
+                .all(|c| c.to_digit(base).is_some());
+
+        valid.then(|| format!("0{}{}", base_char.to_ascii_lowercase(), &suffix[1..]))
+    }
+
+    let dcx = psess.dcx();
+    match err {
+        LitError::InvalidSuffix(suffix) => {
+            dcx.emit_err(InvalidLiteralSuffix { span, kind: lit.kind.descr(), suffix })
+        }
+        LitError::InvalidIntSuffix(suffix) => {
+            let suf = suffix.as_str();
+            if looks_like_width_suffix(&['i', 'u'], suf) {
+                // If it looks like a width, try to be helpful.
+                dcx.emit_err(InvalidIntLiteralWidth { span, width: suf[1..].into() })
+            } else if let Some(fixed) = fix_base_capitalisation(lit.symbol.as_str(), suf) {
+                dcx.emit_err(InvalidNumLiteralBasePrefix { span, fixed })
+            } else {
+                dcx.emit_err(InvalidNumLiteralSuffix { span, suffix: suf.to_string() })
+            }
+        }
+        LitError::InvalidFloatSuffix(suffix) => {
+            let suf = suffix.as_str();
+            if looks_like_width_suffix(&['f'], suf) {
+                // If it looks like a width, try to be helpful.
+                dcx.emit_err(InvalidFloatLiteralWidth { span, width: suf[1..].to_string() })
+            } else {
+                dcx.emit_err(InvalidFloatLiteralSuffix { span, suffix: suf.to_string() })
+            }
+        }
+        LitError::NonDecimalFloat(base) => match base {
+            16 => dcx.emit_err(HexadecimalFloatLiteralNotSupported { span }),
+            8 => dcx.emit_err(OctalFloatLiteralNotSupported { span }),
+            2 => dcx.emit_err(BinaryFloatLiteralNotSupported { span }),
+            _ => unreachable!(),
+        },
+        LitError::IntTooLarge(base) => {
+            let max = u128::MAX;
+            let limit = match base {
+                2 => format!("{max:#b}"),
+                8 => format!("{max:#o}"),
+                16 => format!("{max:#x}"),
+                _ => format!("{max}"),
+            };
+            dcx.emit_err(IntLiteralTooLarge { span, limit })
+        }
+    }
+}
+
+#[derive(Diagnostic)]
+#[diag(session_optimization_fuel_exhausted)]
+pub(crate) struct OptimisationFuelExhausted {
+    pub(crate) msg: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_incompatible_linker_flavor)]
+#[note]
+pub(crate) struct IncompatibleLinkerFlavor {
+    pub(crate) flavor: &'static str,
+    pub(crate) compatible_list: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_function_return_requires_x86_or_x86_64)]
+pub(crate) struct FunctionReturnRequiresX86OrX8664;
+
+#[derive(Diagnostic)]
+#[diag(session_function_return_thunk_extern_requires_non_large_code_model)]
+pub(crate) struct FunctionReturnThunkExternRequiresNonLargeCodeModel;
+
+#[derive(Diagnostic)]
+#[diag(session_unsupported_regparm)]
+pub(crate) struct UnsupportedRegparm {
+    pub(crate) regparm: u32,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_unsupported_regparm_arch)]
+pub(crate) struct UnsupportedRegparmArch;
+
+#[derive(Diagnostic)]
+#[diag(session_failed_to_create_profiler)]
+pub(crate) struct FailedToCreateProfiler {
+    pub(crate) err: String,
+}
+
+#[derive(Diagnostic)]
+#[diag(session_soft_float_ignored)]
+#[note]
+pub(crate) struct SoftFloatIgnored;
+
+#[derive(Diagnostic)]
+#[diag(session_soft_float_deprecated)]
+#[note]
+#[note(session_soft_float_deprecated_issue)]
+pub(crate) struct SoftFloatDeprecated;
diff --git a/compiler/rustc_session/src/filesearch.rs b/compiler/rustc_session/src/filesearch.rs
new file mode 100644
index 00000000000..213a94ab880
--- /dev/null
+++ b/compiler/rustc_session/src/filesearch.rs
@@ -0,0 +1,277 @@
+//! A module for searching for libraries
+
+use std::path::{Path, PathBuf};
+use std::{env, fs};
+
+use rustc_fs_util::{fix_windows_verbatim_for_gcc, try_canonicalize};
+use smallvec::{SmallVec, smallvec};
+
+use crate::search_paths::{PathKind, SearchPath};
+
+#[derive(Clone)]
+pub struct FileSearch<'a> {
+    cli_search_paths: &'a [SearchPath],
+    tlib_path: &'a SearchPath,
+    kind: PathKind,
+}
+
+impl<'a> FileSearch<'a> {
+    pub fn cli_search_paths(&self) -> impl Iterator<Item = &'a SearchPath> {
+        let kind = self.kind;
+        self.cli_search_paths.iter().filter(move |sp| sp.kind.matches(kind))
+    }
+
+    pub fn search_paths(&self) -> impl Iterator<Item = &'a SearchPath> {
+        let kind = self.kind;
+        self.cli_search_paths
+            .iter()
+            .filter(move |sp| sp.kind.matches(kind))
+            .chain(std::iter::once(self.tlib_path))
+    }
+
+    pub fn new(
+        cli_search_paths: &'a [SearchPath],
+        tlib_path: &'a SearchPath,
+        kind: PathKind,
+    ) -> FileSearch<'a> {
+        FileSearch { cli_search_paths, tlib_path, kind }
+    }
+}
+
+pub fn make_target_lib_path(sysroot: &Path, target_triple: &str) -> PathBuf {
+    let rustlib_path = rustc_target::relative_target_rustlib_path(sysroot, target_triple);
+    sysroot.join(rustlib_path).join("lib")
+}
+
+/// Returns a path to the target's `bin` folder within its `rustlib` path in the sysroot. This is
+/// where binaries are usually installed, e.g. the self-contained linkers, lld-wrappers, LLVM tools,
+/// etc.
+pub fn make_target_bin_path(sysroot: &Path, target_triple: &str) -> PathBuf {
+    let rustlib_path = rustc_target::relative_target_rustlib_path(sysroot, target_triple);
+    sysroot.join(rustlib_path).join("bin")
+}
+
+#[cfg(unix)]
+fn current_dll_path() -> Result<PathBuf, String> {
+    use std::ffi::{CStr, OsStr};
+    use std::os::unix::prelude::*;
+
+    #[cfg(not(target_os = "aix"))]
+    unsafe {
+        let addr = current_dll_path as usize as *mut _;
+        let mut info = std::mem::zeroed();
+        if libc::dladdr(addr, &mut info) == 0 {
+            return Err("dladdr failed".into());
+        }
+        if info.dli_fname.is_null() {
+            return Err("dladdr returned null pointer".into());
+        }
+        let bytes = CStr::from_ptr(info.dli_fname).to_bytes();
+        let os = OsStr::from_bytes(bytes);
+        Ok(PathBuf::from(os))
+    }
+
+    #[cfg(target_os = "aix")]
+    unsafe {
+        // On AIX, the symbol `current_dll_path` references a function descriptor.
+        // A function descriptor is consisted of (See https://reviews.llvm.org/D62532)
+        // * The address of the entry point of the function.
+        // * The TOC base address for the function.
+        // * The environment pointer.
+        // The function descriptor is in the data section.
+        let addr = current_dll_path as u64;
+        let mut buffer = vec![std::mem::zeroed::<libc::ld_info>(); 64];
+        loop {
+            if libc::loadquery(
+                libc::L_GETINFO,
+                buffer.as_mut_ptr() as *mut u8,
+                (std::mem::size_of::<libc::ld_info>() * buffer.len()) as u32,
+            ) >= 0
+            {
+                break;
+            } else {
+                if std::io::Error::last_os_error().raw_os_error().unwrap() != libc::ENOMEM {
+                    return Err("loadquery failed".into());
+                }
+                buffer.resize(buffer.len() * 2, std::mem::zeroed::<libc::ld_info>());
+            }
+        }
+        let mut current = buffer.as_mut_ptr() as *mut libc::ld_info;
+        loop {
+            let data_base = (*current).ldinfo_dataorg as u64;
+            let data_end = data_base + (*current).ldinfo_datasize;
+            if (data_base..data_end).contains(&addr) {
+                let bytes = CStr::from_ptr(&(*current).ldinfo_filename[0]).to_bytes();
+                let os = OsStr::from_bytes(bytes);
+                return Ok(PathBuf::from(os));
+            }
+            if (*current).ldinfo_next == 0 {
+                break;
+            }
+            current =
+                (current as *mut i8).offset((*current).ldinfo_next as isize) as *mut libc::ld_info;
+        }
+        return Err(format!("current dll's address {} is not in the load map", addr));
+    }
+}
+
+#[cfg(windows)]
+fn current_dll_path() -> Result<PathBuf, String> {
+    use std::ffi::OsString;
+    use std::io;
+    use std::os::windows::prelude::*;
+
+    use windows::Win32::Foundation::HMODULE;
+    use windows::Win32::System::LibraryLoader::{
+        GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, GetModuleFileNameW, GetModuleHandleExW,
+    };
+    use windows::core::PCWSTR;
+
+    let mut module = HMODULE::default();
+    unsafe {
+        GetModuleHandleExW(
+            GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
+            PCWSTR(current_dll_path as *mut u16),
+            &mut module,
+        )
+    }
+    .map_err(|e| e.to_string())?;
+
+    let mut filename = vec![0; 1024];
+    let n = unsafe { GetModuleFileNameW(module, &mut filename) } as usize;
+    if n == 0 {
+        return Err(format!("GetModuleFileNameW failed: {}", io::Error::last_os_error()));
+    }
+    if n >= filename.capacity() {
+        return Err(format!("our buffer was too small? {}", io::Error::last_os_error()));
+    }
+
+    filename.truncate(n);
+
+    Ok(OsString::from_wide(&filename).into())
+}
+
+pub fn sysroot_candidates() -> SmallVec<[PathBuf; 2]> {
+    let target = crate::config::host_tuple();
+    let mut sysroot_candidates: SmallVec<[PathBuf; 2]> =
+        smallvec![get_or_default_sysroot().expect("Failed finding sysroot")];
+    let path = current_dll_path().and_then(|s| try_canonicalize(s).map_err(|e| e.to_string()));
+    if let Ok(dll) = path {
+        // use `parent` twice to chop off the file name and then also the
+        // directory containing the dll which should be either `lib` or `bin`.
+        if let Some(path) = dll.parent().and_then(|p| p.parent()) {
+            // The original `path` pointed at the `rustc_driver` crate's dll.
+            // Now that dll should only be in one of two locations. The first is
+            // in the compiler's libdir, for example `$sysroot/lib/*.dll`. The
+            // other is the target's libdir, for example
+            // `$sysroot/lib/rustlib/$target/lib/*.dll`.
+            //
+            // We don't know which, so let's assume that if our `path` above
+            // ends in `$target` we *could* be in the target libdir, and always
+            // assume that we may be in the main libdir.
+            sysroot_candidates.push(path.to_owned());
+
+            if path.ends_with(target) {
+                sysroot_candidates.extend(
+                    path.parent() // chop off `$target`
+                        .and_then(|p| p.parent()) // chop off `rustlib`
+                        .and_then(|p| p.parent()) // chop off `lib`
+                        .map(|s| s.to_owned()),
+                );
+            }
+        }
+    }
+
+    sysroot_candidates
+}
+
+/// Returns the provided sysroot or calls [`get_or_default_sysroot`] if it's none.
+/// Panics if [`get_or_default_sysroot`]  returns an error.
+pub fn materialize_sysroot(maybe_sysroot: Option<PathBuf>) -> PathBuf {
+    maybe_sysroot.unwrap_or_else(|| get_or_default_sysroot().expect("Failed finding sysroot"))
+}
+
+/// This function checks if sysroot is found using env::args().next(), and if it
+/// is not found, finds sysroot from current rustc_driver dll.
+pub fn get_or_default_sysroot() -> Result<PathBuf, String> {
+    // Follow symlinks. If the resolved path is relative, make it absolute.
+    fn canonicalize(path: PathBuf) -> PathBuf {
+        let path = try_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)
+    }
+
+    fn default_from_rustc_driver_dll() -> Result<PathBuf, String> {
+        let dll = current_dll_path().map(|s| canonicalize(s))?;
+
+        // `dll` will be in one of the following two:
+        // - compiler's libdir: $sysroot/lib/*.dll
+        // - target's libdir: $sysroot/lib/rustlib/$target/lib/*.dll
+        //
+        // use `parent` twice to chop off the file name and then also the
+        // directory containing the dll
+        let dir = dll.parent().and_then(|p| p.parent()).ok_or(format!(
+            "Could not move 2 levels upper using `parent()` on {}",
+            dll.display()
+        ))?;
+
+        // if `dir` points target's dir, move up to the sysroot
+        let mut sysroot_dir = if dir.ends_with(crate::config::host_tuple()) {
+            dir.parent() // chop off `$target`
+                .and_then(|p| p.parent()) // chop off `rustlib`
+                .and_then(|p| p.parent()) // chop off `lib`
+                .map(|s| s.to_owned())
+                .ok_or_else(|| {
+                    format!("Could not move 3 levels upper using `parent()` on {}", dir.display())
+                })?
+        } else {
+            dir.to_owned()
+        };
+
+        // On multiarch linux systems, there will be multiarch directory named
+        // with the architecture(e.g `x86_64-linux-gnu`) under the `lib` directory.
+        // Which cause us to mistakenly end up in the lib directory instead of the sysroot directory.
+        if sysroot_dir.ends_with("lib") {
+            sysroot_dir =
+                sysroot_dir.parent().map(|real_sysroot| real_sysroot.to_owned()).ok_or_else(
+                    || format!("Could not move to parent path of {}", sysroot_dir.display()),
+                )?
+        }
+
+        Ok(sysroot_dir)
+    }
+
+    // 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<PathBuf> {
+        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::relative_target_rustlib_path(&p, "dummy");
+                rustlib_path.pop(); // pop off the dummy target.
+                rustlib_path.exists().then_some(p)
+            }
+            None => None,
+        }
+    }
+
+    Ok(from_env_args_next().unwrap_or(default_from_rustc_driver_dll()?))
+}
diff --git a/compiler/rustc_session/src/lib.rs b/compiler/rustc_session/src/lib.rs
new file mode 100644
index 00000000000..0b4470b2b0f
--- /dev/null
+++ b/compiler/rustc_session/src/lib.rs
@@ -0,0 +1,40 @@
+// tidy-alphabetical-start
+#![allow(internal_features)]
+#![feature(iter_intersperse)]
+#![feature(let_chains)]
+#![feature(map_many_mut)]
+#![feature(rustc_attrs)]
+#![warn(unreachable_pub)]
+// tidy-alphabetical-end
+
+pub mod errors;
+
+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;
+
+pub 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;
+
+mod version;
+pub use version::RustcVersion;
+
+rustc_fluent_macro::fluent_messages! { "../messages.ftl" }
+
+/// 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 00000000000..f485e8cace5
--- /dev/null
+++ b/compiler/rustc_session/src/options.rs
@@ -0,0 +1,2201 @@
+use std::collections::BTreeMap;
+use std::hash::{DefaultHasher, Hasher};
+use std::num::{IntErrorKind, NonZero};
+use std::path::PathBuf;
+use std::str;
+
+use rustc_data_structures::fx::FxIndexMap;
+use rustc_data_structures::profiling::TimePassesFormat;
+use rustc_data_structures::stable_hasher::Hash64;
+use rustc_errors::{ColorConfig, LanguageIdentifier, TerminalUrl};
+use rustc_feature::UnstableFeatures;
+use rustc_span::edition::Edition;
+use rustc_span::{RealFileName, SourceFileHashAlgorithm};
+use rustc_target::spec::{
+    CodeModel, FramePointer, LinkerFlavorCli, MergeFunctions, OnBrokenPipe, PanicStrategy,
+    RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo, StackProtector, SymbolVisibility,
+    TargetTuple, TlsModel, WasmCAbi,
+};
+
+use crate::config::*;
+use crate::search_paths::SearchPath;
+use crate::utils::NativeLib;
+use crate::{EarlyDiagCtxt, lint};
+
+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.
+    #[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.
+        #[rustc_lint_opt_deny_field_access("use `TyCtxt::crate_types` instead of this field")]
+        crate_types: Vec<CrateType> [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],
+        debuginfo_compression: DebugInfoCompression [TRACKED],
+        lint_opts: Vec<(String, lint::Level)> [TRACKED_NO_CRATE_HASH],
+        lint_cap: Option<lint::Level> [TRACKED_NO_CRATE_HASH],
+        describe_lints: bool [UNTRACKED],
+        output_types: OutputTypes [TRACKED],
+        search_paths: Vec<SearchPath> [UNTRACKED],
+        libs: Vec<NativeLib> [TRACKED],
+        maybe_sysroot: Option<PathBuf> [UNTRACKED],
+
+        target_triple: TargetTuple [TRACKED],
+
+        /// Effective logical environment used by `env!`/`option_env!` macros
+        logical_env: FxIndexMap<String, String> [TRACKED],
+
+        test: bool [TRACKED],
+        error_format: ErrorOutputType [UNTRACKED],
+        diagnostic_width: Option<usize> [UNTRACKED],
+
+        /// If `Some`, enable incremental compilation, using the given
+        /// directory to store intermediate results.
+        incremental: Option<PathBuf> [UNTRACKED],
+        assert_incr_state: Option<IncrementalStateAssertion> [UNTRACKED],
+        /// Set by the `Config::hash_untracked_state` callback for custom
+        /// drivers to invalidate the incremental cache
+        #[rustc_lint_opt_deny_field_access("should only be used via `Config::hash_untracked_state`")]
+        untracked_state_hash: Hash64 [TRACKED_NO_CRATE_HASH],
+
+        unstable_opts: UnstableOptions [SUBSTRUCT],
+        prints: Vec<PrintRequest> [UNTRACKED],
+        cg: CodegenOptions [SUBSTRUCT],
+        externs: Externs [UNTRACKED],
+        crate_name: Option<String> [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],
+        /// Whether name resolver should resolve documentation links.
+        resolve_doc_links: ResolveDocLinks [TRACKED],
+
+        /// Control path trimming.
+        trimmed_def_paths: bool [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.
+        #[rustc_lint_opt_deny_field_access("use `Session::codegen_units` instead of this field")]
+        cli_forced_codegen_units: Option<usize> [UNTRACKED],
+        #[rustc_lint_opt_deny_field_access("use `Session::lto` instead of this field")]
+        cli_forced_local_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 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<PathBuf> [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<PpMode> [UNTRACKED],
+
+        /// The (potentially remapped) working directory
+        working_dir: RealFileName [TRACKED],
+        color: ColorConfig [UNTRACKED],
+
+        verbose: bool [TRACKED_NO_CRATE_HASH],
+    }
+);
+
+/// 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)]
+    #[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(
+            early_dcx: &EarlyDiagCtxt,
+            matches: &getopts::Matches,
+        ) -> $struct_name {
+            build_options(early_dcx, matches, $stat, $prefix, $outputname)
+        }
+
+        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 CodegenOptions {
+    // JUSTIFICATION: defn of the suggested wrapper fn
+    #[allow(rustc::bad_opt_access)]
+    pub fn instrument_coverage(&self) -> InstrumentCoverage {
+        self.instrument_coverage
+    }
+}
+
+// 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<O> = fn(&mut O, v: Option<&str>) -> bool;
+type OptionDescrs<O> = &'static [(&'static str, OptionSetter<O>, &'static str, &'static str)];
+
+#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
+fn build_options<O: Default>(
+    early_dcx: &EarlyDiagCtxt,
+    matches: &getopts::Matches,
+    descrs: OptionDescrs<O>,
+    prefix: &str,
+    outputname: &str,
+) -> 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_dcx.early_fatal(
+                            format!(
+                                "{outputname} option `{key}` requires {type_desc} ({prefix} {key}=<value>)"
+                            ),
+                        ),
+                        Some(value) => early_dcx.early_fatal(
+                            format!(
+                                "incorrect value `{value}` for {outputname} option `{key}` - {type_desc} was expected"
+                            ),
+                        ),
+                    }
+                }
+            }
+            None => early_dcx.early_fatal(format!("unknown {outputname} option: `{key}`")),
+        }
+    }
+    op
+}
+
+#[allow(non_upper_case_globals)]
+mod desc {
+    pub(crate) const parse_no_flag: &str = "no value";
+    pub(crate) const parse_bool: &str =
+        "one of: `y`, `yes`, `on`, `true`, `n`, `no`, `off` or `false`";
+    pub(crate) const parse_opt_bool: &str = parse_bool;
+    pub(crate) const parse_string: &str = "a string";
+    pub(crate) const parse_opt_string: &str = parse_string;
+    pub(crate) const parse_string_push: &str = parse_string;
+    pub(crate) const parse_opt_langid: &str = "a language identifier";
+    pub(crate) const parse_opt_pathbuf: &str = "a path";
+    pub(crate) const parse_list: &str = "a space-separated list of strings";
+    pub(crate) const parse_list_with_polarity: &str =
+        "a comma-separated list of strings, with elements beginning with + or -";
+    pub(crate) const parse_comma_list: &str = "a comma-separated list of strings";
+    pub(crate) const parse_opt_comma_list: &str = parse_comma_list;
+    pub(crate) const parse_number: &str = "a number";
+    pub(crate) const parse_opt_number: &str = parse_number;
+    pub(crate) const parse_frame_pointer: &str = "one of `true`/`yes`/`on`, `false`/`no`/`off`, or (with -Zunstable-options) `non-leaf` or `always`";
+    pub(crate) const parse_threads: &str = parse_number;
+    pub(crate) const parse_time_passes_format: &str = "`text` (default) or `json`";
+    pub(crate) const parse_passes: &str = "a space-separated list of passes, or `all`";
+    pub(crate) const parse_panic_strategy: &str = "either `unwind` or `abort`";
+    pub(crate) const parse_on_broken_pipe: &str = "either `kill`, `error`, or `inherit`";
+    pub(crate) const parse_patchable_function_entry: &str = "either two comma separated integers (total_nops,prefix_nops), with prefix_nops <= total_nops, or one integer (total_nops)";
+    pub(crate) const parse_opt_panic_strategy: &str = parse_panic_strategy;
+    pub(crate) const parse_oom_strategy: &str = "either `panic` or `abort`";
+    pub(crate) const parse_relro_level: &str = "one of: `full`, `partial`, or `off`";
+    pub(crate) const parse_sanitizers: &str = "comma separated list of sanitizers: `address`, `cfi`, `dataflow`, `hwaddress`, `kcfi`, `kernel-address`, `leak`, `memory`, `memtag`, `safestack`, `shadow-call-stack`, or `thread`";
+    pub(crate) const parse_sanitizer_memory_track_origins: &str = "0, 1, or 2";
+    pub(crate) const parse_cfguard: &str =
+        "either a boolean (`yes`, `no`, `on`, `off`, etc), `checks`, or `nochecks`";
+    pub(crate) const parse_cfprotection: &str = "`none`|`no`|`n` (default), `branch`, `return`, or `full`|`yes`|`y` (equivalent to `branch` and `return`)";
+    pub(crate) const parse_debuginfo: &str = "either an integer (0, 1, 2), `none`, `line-directives-only`, `line-tables-only`, `limited`, or `full`";
+    pub(crate) const parse_debuginfo_compression: &str = "one of `none`, `zlib`, or `zstd`";
+    pub(crate) const parse_collapse_macro_debuginfo: &str = "one of `no`, `external`, or `yes`";
+    pub(crate) const parse_strip: &str = "either `none`, `debuginfo`, or `symbols`";
+    pub(crate) const parse_linker_flavor: &str = ::rustc_target::spec::LinkerFlavorCli::one_of();
+    pub(crate) const parse_optimization_fuel: &str = "crate=integer";
+    pub(crate) const parse_dump_mono_stats: &str = "`markdown` (default) or `json`";
+    pub(crate) const parse_instrument_coverage: &str = parse_bool;
+    pub(crate) const parse_coverage_options: &str =
+        "`block` | `branch` | `condition` | `mcdc` | `no-mir-spans`";
+    pub(crate) const parse_instrument_xray: &str = "either a boolean (`yes`, `no`, `on`, `off`, etc), or a comma separated list of settings: `always` or `never` (mutually exclusive), `ignore-loops`, `instruction-threshold=N`, `skip-entry`, `skip-exit`";
+    pub(crate) const parse_unpretty: &str = "`string` or `string=string`";
+    pub(crate) const parse_treat_err_as_bug: &str = "either no value or a non-negative number";
+    pub(crate) const parse_next_solver_config: &str =
+        "either `globally` (when used without an argument), `coherence` (default) or `no`";
+    pub(crate) const parse_lto: &str =
+        "either a boolean (`yes`, `no`, `on`, `off`, etc), `thin`, `fat`, or omitted";
+    pub(crate) const parse_linker_plugin_lto: &str =
+        "either a boolean (`yes`, `no`, `on`, `off`, etc), or the path to the linker plugin";
+    pub(crate) const parse_location_detail: &str = "either `none`, or a comma separated list of location details to track: `file`, `line`, or `column`";
+    pub(crate) const parse_fmt_debug: &str = "either `full`, `shallow`, or `none`";
+    pub(crate) const parse_switch_with_opt_path: &str =
+        "an optional path to the profiling data output directory";
+    pub(crate) const parse_merge_functions: &str =
+        "one of: `disabled`, `trampolines`, or `aliases`";
+    pub(crate) const parse_symbol_mangling_version: &str =
+        "one of: `legacy`, `v0` (RFC 2603), or `hashed`";
+    pub(crate) const parse_opt_symbol_visibility: &str =
+        "one of: `hidden`, `protected`, or `interposable`";
+    pub(crate) const parse_cargo_src_file_hash: &str =
+        "one of `blake3`, `md5`, `sha1`, or `sha256`";
+    pub(crate) const parse_src_file_hash: &str = "one of `md5`, `sha1`, or `sha256`";
+    pub(crate) const parse_relocation_model: &str =
+        "one of supported relocation models (`rustc --print relocation-models`)";
+    pub(crate) const parse_code_model: &str =
+        "one of supported code models (`rustc --print code-models`)";
+    pub(crate) const parse_tls_model: &str =
+        "one of supported TLS models (`rustc --print tls-models`)";
+    pub(crate) const parse_target_feature: &str = parse_string;
+    pub(crate) const parse_terminal_url: &str =
+        "either a boolean (`yes`, `no`, `on`, `off`, etc), or `auto`";
+    pub(crate) const parse_wasi_exec_model: &str = "either `command` or `reactor`";
+    pub(crate) const parse_split_debuginfo: &str =
+        "one of supported split-debuginfo modes (`off`, `packed`, or `unpacked`)";
+    pub(crate) const parse_split_dwarf_kind: &str =
+        "one of supported split dwarf modes (`split` or `single`)";
+    pub(crate) const parse_link_self_contained: &str = "one of: `y`, `yes`, `on`, `n`, `no`, `off`, or a list of enabled (`+` prefix) and disabled (`-` prefix) \
+        components: `crto`, `libc`, `unwind`, `linker`, `sanitizers`, `mingw`";
+    pub(crate) const parse_linker_features: &str =
+        "a list of enabled (`+` prefix) and disabled (`-` prefix) features: `lld`";
+    pub(crate) const parse_polonius: &str = "either no value or `legacy` (the default), or `next`";
+    pub(crate) const parse_stack_protector: &str =
+        "one of (`none` (default), `basic`, `strong`, or `all`)";
+    pub(crate) const parse_branch_protection: &str = "a `,` separated combination of `bti`, `pac-ret`, followed by a combination of `pc`, `b-key`, or `leaf`";
+    pub(crate) const parse_proc_macro_execution_strategy: &str =
+        "one of supported execution strategies (`same-thread`, or `cross-thread`)";
+    pub(crate) const parse_remap_path_scope: &str =
+        "comma separated list of scopes: `macro`, `diagnostics`, `debuginfo`, `object`, `all`";
+    pub(crate) const parse_inlining_threshold: &str =
+        "either a boolean (`yes`, `no`, `on`, `off`, etc), or a non-negative number";
+    pub(crate) const parse_llvm_module_flag: &str = "<key>:<type>:<value>:<behavior>. Type must currently be `u32`. Behavior should be one of (`error`, `warning`, `require`, `override`, `append`, `appendunique`, `max`, `min`)";
+    pub(crate) const parse_function_return: &str = "`keep` or `thunk-extern`";
+    pub(crate) const parse_wasm_c_abi: &str = "`legacy` or `spec`";
+    pub(crate) const parse_mir_include_spans: &str =
+        "either a boolean (`yes`, `no`, `on`, `off`, etc), or `nll` (default: `nll`)";
+}
+
+pub mod parse {
+    use std::str::FromStr;
+
+    pub(crate) use super::*;
+    pub(crate) const MAX_THREADS_CAP: usize = 256;
+
+    /// 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") | Some("true") | None => {
+                *slot = true;
+                true
+            }
+            Some("n") | Some("no") | Some("off") | Some("false") => {
+                *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<bool>, v: Option<&str>) -> bool {
+        match v {
+            Some("y") | Some("yes") | Some("on") | Some("true") | None => {
+                *slot = Some(true);
+                true
+            }
+            Some("n") | Some("no") | Some("off") | Some("false") => {
+                *slot = Some(false);
+                true
+            }
+            _ => false,
+        }
+    }
+
+    /// Parses whether polonius is enabled, and if so, which version.
+    pub(crate) fn parse_polonius(slot: &mut Polonius, v: Option<&str>) -> bool {
+        match v {
+            Some("legacy") | None => {
+                *slot = Polonius::Legacy;
+                true
+            }
+            Some("next") => {
+                *slot = Polonius::Next;
+                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<String>, 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<LanguageIdentifier>, 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<PathBuf>, 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<String>, v: Option<&str>) -> bool {
+        match v {
+            Some(s) => {
+                slot.push(s.to_string());
+                true
+            }
+            None => false,
+        }
+    }
+
+    pub(crate) fn parse_list(slot: &mut Vec<String>, 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_fmt_debug(opt: &mut FmtDebug, v: Option<&str>) -> bool {
+        *opt = match v {
+            Some("full") => FmtDebug::Full,
+            Some("shallow") => FmtDebug::Shallow,
+            Some("none") => FmtDebug::None,
+            _ => return false,
+        };
+        true
+    }
+
+    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_comma_list(slot: &mut Vec<String>, v: Option<&str>) -> bool {
+        match v {
+            Some(s) => {
+                let mut v: Vec<_> = s.split(',').map(|s| s.to_string()).collect();
+                v.sort_unstable();
+                *slot = v;
+                true
+            }
+            None => false,
+        }
+    }
+
+    pub(crate) fn parse_opt_comma_list(slot: &mut Option<Vec<String>>, 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 {
+        let ret = match v.and_then(|s| s.parse().ok()) {
+            Some(0) => {
+                *slot = std::thread::available_parallelism().map_or(1, NonZero::<usize>::get);
+                true
+            }
+            Some(i) => {
+                *slot = i;
+                true
+            }
+            None => false,
+        };
+        // We want to cap the number of threads here to avoid large numbers like 999999 and compiler panics.
+        // This solution was suggested here https://github.com/rust-lang/rust/issues/117638#issuecomment-1800925067
+        *slot = slot.clone().min(MAX_THREADS_CAP);
+        ret
+    }
+
+    /// Use this for any numeric option that has a static default.
+    pub(crate) fn parse_number<T: Copy + FromStr>(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<T: Copy + FromStr>(
+        slot: &mut Option<T>,
+        v: Option<&str>,
+    ) -> bool {
+        match v {
+            Some(s) => {
+                *slot = s.parse().ok();
+                slot.is_some()
+            }
+            None => false,
+        }
+    }
+
+    pub(crate) fn parse_frame_pointer(slot: &mut FramePointer, v: Option<&str>) -> bool {
+        let mut yes = false;
+        match v {
+            _ if parse_bool(&mut yes, v) && yes => slot.ratchet(FramePointer::Always),
+            _ if parse_bool(&mut yes, v) => slot.ratchet(FramePointer::MayOmit),
+            Some("always") => slot.ratchet(FramePointer::Always),
+            Some("non-leaf") => slot.ratchet(FramePointer::NonLeaf),
+            _ => return false,
+        };
+        true
+    }
+
+    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<PanicStrategy>,
+        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_on_broken_pipe(slot: &mut OnBrokenPipe, v: Option<&str>) -> bool {
+        match v {
+            // OnBrokenPipe::Default can't be explicitly specified
+            Some("kill") => *slot = OnBrokenPipe::Kill,
+            Some("error") => *slot = OnBrokenPipe::Error,
+            Some("inherit") => *slot = OnBrokenPipe::Inherit,
+            _ => return false,
+        }
+        true
+    }
+
+    pub(crate) fn parse_patchable_function_entry(
+        slot: &mut PatchableFunctionEntry,
+        v: Option<&str>,
+    ) -> bool {
+        let mut total_nops = 0;
+        let mut prefix_nops = 0;
+
+        if !parse_number(&mut total_nops, v) {
+            let parts = v.and_then(|v| v.split_once(',')).unzip();
+            if !parse_number(&mut total_nops, parts.0) {
+                return false;
+            }
+            if !parse_number(&mut prefix_nops, parts.1) {
+                return false;
+            }
+        }
+
+        if let Some(pfe) =
+            PatchableFunctionEntry::from_total_and_prefix_nops(total_nops, prefix_nops)
+        {
+            *slot = pfe;
+            return true;
+        }
+        false
+    }
+
+    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<RelroLevel>, v: Option<&str>) -> bool {
+        match v {
+            Some(s) => match s.parse::<RelroLevel>() {
+                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,
+                    "dataflow" => SanitizerSet::DATAFLOW,
+                    "kcfi" => SanitizerSet::KCFI,
+                    "kernel-address" => SanitizerSet::KERNELADDRESS,
+                    "leak" => SanitizerSet::LEAK,
+                    "memory" => SanitizerSet::MEMORY,
+                    "memtag" => SanitizerSet::MEMTAG,
+                    "shadow-call-stack" => SanitizerSet::SHADOWCALLSTACK,
+                    "thread" => SanitizerSet::THREAD,
+                    "hwaddress" => SanitizerSet::HWADDRESS,
+                    "safestack" => SanitizerSet::SAFESTACK,
+                    _ => 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_debuginfo(slot: &mut DebugInfo, v: Option<&str>) -> bool {
+        match v {
+            Some("0") | Some("none") => *slot = DebugInfo::None,
+            Some("line-directives-only") => *slot = DebugInfo::LineDirectivesOnly,
+            Some("line-tables-only") => *slot = DebugInfo::LineTablesOnly,
+            Some("1") | Some("limited") => *slot = DebugInfo::Limited,
+            Some("2") | Some("full") => *slot = DebugInfo::Full,
+            _ => return false,
+        }
+        true
+    }
+
+    pub(crate) fn parse_debuginfo_compression(
+        slot: &mut DebugInfoCompression,
+        v: Option<&str>,
+    ) -> bool {
+        match v {
+            Some("none") => *slot = DebugInfoCompression::None,
+            Some("zlib") => *slot = DebugInfoCompression::Zlib,
+            Some("zstd") => *slot = DebugInfoCompression::Zstd,
+            _ => return false,
+        };
+        true
+    }
+
+    pub(crate) fn parse_linker_flavor(slot: &mut Option<LinkerFlavorCli>, v: Option<&str>) -> bool {
+        match v.and_then(LinkerFlavorCli::from_str) {
+            Some(lf) => *slot = Some(lf),
+            _ => return false,
+        }
+        true
+    }
+
+    pub(crate) fn parse_opt_symbol_visibility(
+        slot: &mut Option<SymbolVisibility>,
+        v: Option<&str>,
+    ) -> bool {
+        if let Some(v) = v {
+            if let Ok(vis) = SymbolVisibility::from_str(v) {
+                *slot = Some(vis);
+            } else {
+                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 [crate_name, fuel] = *s.split('=').collect::<Vec<_>>() else { return false };
+                let Ok(fuel) = fuel.parse::<u64>() else { return false };
+                *slot = Some((crate_name.to_string(), fuel));
+                true
+            }
+        }
+    }
+
+    pub(crate) fn parse_unpretty(slot: &mut Option<String>, 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_time_passes_format(slot: &mut TimePassesFormat, v: Option<&str>) -> bool {
+        match v {
+            None => true,
+            Some("json") => {
+                *slot = TimePassesFormat::Json;
+                true
+            }
+            Some("text") => {
+                *slot = TimePassesFormat::Text;
+                true
+            }
+            Some(_) => false,
+        }
+    }
+
+    pub(crate) fn parse_dump_mono_stats(slot: &mut DumpMonoStatsFormat, v: Option<&str>) -> bool {
+        match v {
+            None => true,
+            Some("json") => {
+                *slot = DumpMonoStatsFormat::Json;
+                true
+            }
+            Some("markdown") => {
+                *slot = DumpMonoStatsFormat::Markdown;
+                true
+            }
+            Some(_) => false,
+        }
+    }
+
+    pub(crate) fn parse_instrument_coverage(
+        slot: &mut InstrumentCoverage,
+        v: Option<&str>,
+    ) -> bool {
+        if v.is_some() {
+            let mut bool_arg = false;
+            if parse_bool(&mut bool_arg, v) {
+                *slot = if bool_arg { InstrumentCoverage::Yes } else { InstrumentCoverage::No };
+                return true;
+            }
+        }
+
+        let Some(v) = v else {
+            *slot = InstrumentCoverage::Yes;
+            return true;
+        };
+
+        // Parse values that have historically been accepted by stable compilers,
+        // even though they're currently just aliases for boolean values.
+        *slot = match v {
+            "all" => InstrumentCoverage::Yes,
+            "0" => InstrumentCoverage::No,
+            _ => return false,
+        };
+        true
+    }
+
+    pub(crate) fn parse_coverage_options(slot: &mut CoverageOptions, v: Option<&str>) -> bool {
+        let Some(v) = v else { return true };
+
+        for option in v.split(',') {
+            match option {
+                "block" => slot.level = CoverageLevel::Block,
+                "branch" => slot.level = CoverageLevel::Branch,
+                "condition" => slot.level = CoverageLevel::Condition,
+                "mcdc" => slot.level = CoverageLevel::Mcdc,
+                "no-mir-spans" => slot.no_mir_spans = true,
+                _ => return false,
+            }
+        }
+        true
+    }
+
+    pub(crate) fn parse_instrument_xray(
+        slot: &mut Option<InstrumentXRay>,
+        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(InstrumentXRay::default()) } else { None };
+                return true;
+            }
+        }
+
+        let options = slot.get_or_insert_default();
+        let mut seen_always = false;
+        let mut seen_never = false;
+        let mut seen_ignore_loops = false;
+        let mut seen_instruction_threshold = false;
+        let mut seen_skip_entry = false;
+        let mut seen_skip_exit = false;
+        for option in v.into_iter().flat_map(|v| v.split(',')) {
+            match option {
+                "always" if !seen_always && !seen_never => {
+                    options.always = true;
+                    options.never = false;
+                    seen_always = true;
+                }
+                "never" if !seen_never && !seen_always => {
+                    options.never = true;
+                    options.always = false;
+                    seen_never = true;
+                }
+                "ignore-loops" if !seen_ignore_loops => {
+                    options.ignore_loops = true;
+                    seen_ignore_loops = true;
+                }
+                option
+                    if option.starts_with("instruction-threshold")
+                        && !seen_instruction_threshold =>
+                {
+                    let Some(("instruction-threshold", n)) = option.split_once('=') else {
+                        return false;
+                    };
+                    match n.parse() {
+                        Ok(n) => options.instruction_threshold = Some(n),
+                        Err(_) => return false,
+                    }
+                    seen_instruction_threshold = true;
+                }
+                "skip-entry" if !seen_skip_entry => {
+                    options.skip_entry = true;
+                    seen_skip_entry = true;
+                }
+                "skip-exit" if !seen_skip_exit => {
+                    options.skip_exit = true;
+                    seen_skip_exit = true;
+                }
+                _ => return false,
+            }
+        }
+        true
+    }
+
+    pub(crate) fn parse_treat_err_as_bug(
+        slot: &mut Option<NonZero<usize>>,
+        v: Option<&str>,
+    ) -> bool {
+        match v {
+            Some(s) => match s.parse() {
+                Ok(val) => {
+                    *slot = Some(val);
+                    true
+                }
+                Err(e) => {
+                    *slot = None;
+                    e.kind() == &IntErrorKind::Zero
+                }
+            },
+            None => {
+                *slot = NonZero::new(1);
+                true
+            }
+        }
+    }
+
+    pub(crate) fn parse_next_solver_config(slot: &mut NextSolverConfig, v: Option<&str>) -> bool {
+        if let Some(config) = v {
+            *slot = match config {
+                "no" => NextSolverConfig { coherence: false, globally: false },
+                "coherence" => NextSolverConfig { coherence: true, globally: false },
+                "globally" => NextSolverConfig { coherence: true, globally: true },
+                _ => return false,
+            };
+        } else {
+            *slot = NextSolverConfig { coherence: true, globally: true };
+        }
+
+        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<MergeFunctions>,
+        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_remap_path_scope(
+        slot: &mut RemapPathScopeComponents,
+        v: Option<&str>,
+    ) -> bool {
+        if let Some(v) = v {
+            *slot = RemapPathScopeComponents::empty();
+            for s in v.split(',') {
+                *slot |= match s {
+                    "macro" => RemapPathScopeComponents::MACRO,
+                    "diagnostics" => RemapPathScopeComponents::DIAGNOSTICS,
+                    "debuginfo" => RemapPathScopeComponents::DEBUGINFO,
+                    "object" => RemapPathScopeComponents::OBJECT,
+                    "all" => RemapPathScopeComponents::all(),
+                    _ => return false,
+                }
+            }
+            true
+        } else {
+            false
+        }
+    }
+
+    pub(crate) fn parse_relocation_model(slot: &mut Option<RelocModel>, 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<CodeModel>, 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<TlsModel>, 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_terminal_url(slot: &mut TerminalUrl, v: Option<&str>) -> bool {
+        *slot = match v {
+            Some("on" | "" | "yes" | "y") | None => TerminalUrl::Yes,
+            Some("off" | "no" | "n") => TerminalUrl::No,
+            Some("auto") => TerminalUrl::Auto,
+            _ => return false,
+        };
+        true
+    }
+
+    pub(crate) fn parse_symbol_mangling_version(
+        slot: &mut Option<SymbolManglingVersion>,
+        v: Option<&str>,
+    ) -> bool {
+        *slot = match v {
+            Some("legacy") => Some(SymbolManglingVersion::Legacy),
+            Some("v0") => Some(SymbolManglingVersion::V0),
+            Some("hashed") => Some(SymbolManglingVersion::Hashed),
+            _ => return false,
+        };
+        true
+    }
+
+    pub(crate) fn parse_src_file_hash(
+        slot: &mut Option<SourceFileHashAlgorithm>,
+        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_cargo_src_file_hash(
+        slot: &mut Option<SourceFileHashAlgorithm>,
+        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_link_self_contained(slot: &mut LinkSelfContained, v: Option<&str>) -> bool {
+        // Whenever `-C link-self-contained` is passed without a value, it's an opt-in
+        // just like `parse_opt_bool`, the historical value of this flag.
+        //
+        // 1. Parse historical single bool values
+        let s = v.unwrap_or("y");
+        match s {
+            "y" | "yes" | "on" => {
+                slot.set_all_explicitly(true);
+                return true;
+            }
+            "n" | "no" | "off" => {
+                slot.set_all_explicitly(false);
+                return true;
+            }
+            _ => {}
+        }
+
+        // 2. Parse a list of enabled and disabled components.
+        for comp in s.split(',') {
+            if slot.handle_cli_component(comp).is_none() {
+                return false;
+            }
+        }
+
+        true
+    }
+
+    /// Parse a comma-separated list of enabled and disabled linker features.
+    pub(crate) fn parse_linker_features(slot: &mut LinkerFeaturesCli, v: Option<&str>) -> bool {
+        match v {
+            Some(s) => {
+                for feature in s.split(',') {
+                    if slot.handle_cli_feature(feature).is_none() {
+                        return false;
+                    }
+                }
+
+                true
+            }
+            None => false,
+        }
+    }
+
+    pub(crate) fn parse_wasi_exec_model(slot: &mut Option<WasiExecModel>, 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<SplitDebuginfo>,
+        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_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<BranchProtection>,
+        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, pc: 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,
+                        },
+                        "pc" => match slot.pac_ret.as_mut() {
+                            Some(pac) => pac.pc = true,
+                            _ => return false,
+                        },
+                        _ => return false,
+                    };
+                }
+            }
+            _ => return false,
+        }
+        true
+    }
+
+    pub(crate) fn parse_collapse_macro_debuginfo(
+        slot: &mut CollapseMacroDebuginfo,
+        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() {
+                    CollapseMacroDebuginfo::Yes
+                } else {
+                    CollapseMacroDebuginfo::No
+                };
+                return true;
+            }
+        }
+
+        *slot = match v {
+            Some("external") => CollapseMacroDebuginfo::External,
+            _ => 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
+    }
+
+    pub(crate) fn parse_inlining_threshold(slot: &mut InliningThreshold, v: Option<&str>) -> bool {
+        match v {
+            Some("always" | "yes") => {
+                *slot = InliningThreshold::Always;
+            }
+            Some("never") => {
+                *slot = InliningThreshold::Never;
+            }
+            Some(v) => {
+                if let Ok(threshold) = v.parse() {
+                    *slot = InliningThreshold::Sometimes(threshold);
+                } else {
+                    return false;
+                }
+            }
+            None => return false,
+        }
+        true
+    }
+
+    pub(crate) fn parse_llvm_module_flag(
+        slot: &mut Vec<(String, u32, String)>,
+        v: Option<&str>,
+    ) -> bool {
+        let elements = v.unwrap_or_default().split(':').collect::<Vec<_>>();
+        let [key, md_type, value, behavior] = elements.as_slice() else {
+            return false;
+        };
+        if *md_type != "u32" {
+            // Currently we only support u32 metadata flags, but require the
+            // type for forward-compatibility.
+            return false;
+        }
+        let Ok(value) = value.parse::<u32>() else {
+            return false;
+        };
+        let behavior = behavior.to_lowercase();
+        let all_behaviors =
+            ["error", "warning", "require", "override", "append", "appendunique", "max", "min"];
+        if !all_behaviors.contains(&behavior.as_str()) {
+            return false;
+        }
+
+        slot.push((key.to_string(), value, behavior));
+        true
+    }
+
+    pub(crate) fn parse_function_return(slot: &mut FunctionReturn, v: Option<&str>) -> bool {
+        match v {
+            Some("keep") => *slot = FunctionReturn::Keep,
+            Some("thunk-extern") => *slot = FunctionReturn::ThunkExtern,
+            _ => return false,
+        }
+        true
+    }
+
+    pub(crate) fn parse_wasm_c_abi(slot: &mut WasmCAbi, v: Option<&str>) -> bool {
+        match v {
+            Some("spec") => *slot = WasmCAbi::Spec,
+            Some("legacy") => *slot = WasmCAbi::Legacy,
+            _ => return false,
+        }
+        true
+    }
+
+    pub(crate) fn parse_mir_include_spans(slot: &mut MirIncludeSpans, v: Option<&str>) -> bool {
+        *slot = match v {
+            Some("on" | "yes" | "y" | "true") | None => MirIncludeSpans::On,
+            Some("off" | "no" | "n" | "false") => MirIncludeSpans::Off,
+            Some("nll") => MirIncludeSpans::Nll,
+            _ => return false,
+        };
+
+        true
+    }
+}
+
+options! {
+    CodegenOptions, CG_OPTIONS, cgopts, "C", "codegen",
+
+    // If you add a new option, please update:
+    // - compiler/rustc_interface/src/tests.rs
+    // - src/doc/rustc/src/codegen-options/index.md
+
+    // tidy-alphabetical-start
+    #[rustc_lint_opt_deny_field_access("documented to do nothing")]
+    ar: String = (String::new(), parse_string, [UNTRACKED],
+        "this option is deprecated and does nothing"),
+    #[rustc_lint_opt_deny_field_access("use `Session::code_model` instead of this field")]
+    code_model: Option<CodeModel> = (None, parse_code_model, [TRACKED],
+        "choose the code model to use (`rustc --print code-models` for details)"),
+    codegen_units: Option<usize> = (None, parse_opt_number, [UNTRACKED],
+        "divide crate into N units to optimize in parallel"),
+    collapse_macro_debuginfo: CollapseMacroDebuginfo = (CollapseMacroDebuginfo::Unspecified,
+        parse_collapse_macro_debuginfo, [TRACKED],
+        "set option to collapse debuginfo for macros"),
+    control_flow_guard: CFGuard = (CFGuard::Disabled, parse_cfguard, [TRACKED],
+        "use Windows Control Flow Guard (default: no)"),
+    debug_assertions: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "explicitly enable the `cfg(debug_assertions)` directive"),
+    debuginfo: DebugInfo = (DebugInfo::None, parse_debuginfo, [TRACKED],
+        "debug info emission level (0-2, none, line-directives-only, \
+        line-tables-only, limited, or full; default: 0)"),
+    default_linker_libraries: bool = (false, parse_bool, [UNTRACKED],
+        "allow the linker to link its default libraries (default: no)"),
+    dlltool: Option<PathBuf> = (None, parse_opt_pathbuf, [UNTRACKED],
+        "import library generation tool (ignored except when targeting windows-gnu)"),
+    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: FramePointer = (FramePointer::MayOmit, parse_frame_pointer, [TRACKED],
+        "force use of the frame pointers"),
+    #[rustc_lint_opt_deny_field_access("use `Session::must_emit_unwind_tables` instead of this field")]
+    force_unwind_tables: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "force use of unwind tables"),
+    incremental: Option<String> = (None, parse_opt_string, [UNTRACKED],
+        "enable incremental compilation"),
+    #[rustc_lint_opt_deny_field_access("documented to do nothing")]
+    inline_threshold: Option<u32> = (None, parse_opt_number, [TRACKED],
+        "this option is deprecated and does nothing \
+        (consider using `-Cllvm-args=--inline-threshold=...`)"),
+    #[rustc_lint_opt_deny_field_access("use `Session::instrument_coverage` instead of this field")]
+    instrument_coverage: InstrumentCoverage = (InstrumentCoverage::No, 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`"),
+    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<String> = (Vec::new(), parse_list, [UNTRACKED],
+        "extra arguments to append to the linker invocation (space separated)"),
+    #[rustc_lint_opt_deny_field_access("use `Session::link_dead_code` instead of this field")]
+    link_dead_code: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "keep dead code at link time (useful for code coverage) (default: no)"),
+    link_self_contained: LinkSelfContained = (LinkSelfContained::default(), parse_link_self_contained, [UNTRACKED],
+        "control whether to link Rust provided C objects/libraries or rely \
+        on a C toolchain or linker installed in the system"),
+    linker: Option<PathBuf> = (None, parse_opt_pathbuf, [UNTRACKED],
+        "system linker to link outputs with"),
+    linker_flavor: Option<LinkerFlavorCli> = (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<String> = (Vec::new(), parse_list, [TRACKED],
+        "a list of arguments to pass to LLVM (space separated)"),
+    #[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<String> = (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<bool> = (None, parse_opt_bool, [TRACKED],
+        "disable the use of the redzone"),
+    #[rustc_lint_opt_deny_field_access("documented to do nothing")]
+    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)"),
+    #[rustc_lint_opt_deny_field_access("use `Session::overflow_checks` instead of this field")]
+    overflow_checks: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "use overflow checks for integer arithmetic"),
+    #[rustc_lint_opt_deny_field_access("use `Session::panic_strategy` instead of this field")]
+    panic: Option<PanicStrategy> = (None, parse_opt_panic_strategy, [TRACKED],
+        "panic strategy to compile crate with"),
+    passes: Vec<String> = (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<PathBuf> = (None, parse_opt_pathbuf, [TRACKED],
+        "use the given `.profdata` file for profile-guided optimization"),
+    #[rustc_lint_opt_deny_field_access("use `Session::relocation_model` instead of this field")]
+    relocation_model: Option<RelocModel> = (None, parse_relocation_model, [TRACKED],
+        "control generation of position-independent code (PIC) \
+        (`rustc --print relocation-models` for details)"),
+    relro_level: Option<RelroLevel> = (None, parse_relro_level, [TRACKED],
+        "choose which RELRO level to use"),
+    remark: Passes = (Passes::Some(Vec::new()), parse_passes, [UNTRACKED],
+        "output 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],
+        "deprecated option: use soft float ABI (*eabihf targets only) (default: no)"),
+    #[rustc_lint_opt_deny_field_access("use `Session::split_debuginfo` instead of this field")]
+    split_debuginfo: Option<SplitDebuginfo> = (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<SymbolManglingVersion> = (None,
+        parse_symbol_mangling_version, [TRACKED],
+        "which mangling version to use for symbol names ('legacy' (default), 'v0', or 'hashed')"),
+    target_cpu: Option<String> = (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."),
+    // tidy-alphabetical-end
+
+    // 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",
+
+    // If you add a new option, please update:
+    // - compiler/rustc_interface/src/tests.rs
+    // - src/doc/unstable-book/src/compiler-flags
+
+    // tidy-alphabetical-start
+    allow_features: Option<Vec<String>> = (None, parse_opt_comma_list, [TRACKED],
+        "only allow the listed language features to be enabled in code (comma separated)"),
+    always_encode_mir: bool = (false, parse_bool, [TRACKED],
+        "encode MIR of all functions into the crate metadata (default: no)"),
+    assert_incr_state: Option<String> = (None, parse_opt_string, [UNTRACKED],
+        "assert that the incremental cache is in given state: \
+         either `loaded` or `not-loaded`."),
+    assume_incomplete_release: bool = (false, parse_bool, [TRACKED],
+        "make cfg(version) treat the current version as incomplete (default: no)"),
+    #[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: bool = (true, parse_bool, [TRACKED],
+        "emit noalias metadata for box (default: yes)"),
+    branch_protection: Option<BranchProtection> = (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"),
+    check_cfg_all_expected: bool = (false, parse_bool, [UNTRACKED],
+        "show all expected values in check-cfg diagnostics (default: no)"),
+    checksum_hash_algorithm: Option<SourceFileHashAlgorithm> = (None, parse_cargo_src_file_hash, [TRACKED],
+        "hash algorithm of source files used to check freshness in cargo (`blake3` or `sha256`)"),
+    codegen_backend: Option<String> = (None, parse_opt_string, [TRACKED],
+        "the backend to use"),
+    combine_cgu: bool = (false, parse_bool, [TRACKED],
+        "combine CGUs into a single one"),
+    coverage_options: CoverageOptions = (CoverageOptions::default(), parse_coverage_options, [TRACKED],
+        "control details of coverage instrumentation"),
+    crate_attr: Vec<String> = (Vec::new(), parse_string_push, [TRACKED],
+        "inject the given attribute in the crate"),
+    cross_crate_inline_threshold: InliningThreshold = (InliningThreshold::Sometimes(100), parse_inlining_threshold, [TRACKED],
+        "threshold to allow cross crate inlining of functions"),
+    debug_info_for_profiling: bool = (false, parse_bool, [TRACKED],
+        "emit discriminators and other data necessary for AutoFDO"),
+    debuginfo_compression: DebugInfoCompression = (DebugInfoCompression::None, parse_debuginfo_compression, [TRACKED],
+        "compress debug info sections (none, zlib, zstd, default: none)"),
+    deduplicate_diagnostics: bool = (true, parse_bool, [UNTRACKED],
+        "deduplicate identical diagnostics (default: yes)"),
+    default_visibility: Option<SymbolVisibility> = (None, parse_opt_symbol_visibility, [TRACKED],
+        "overrides the `default_visibility` setting of the target"),
+    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)"),
+    direct_access_external_data: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "Direct or use GOT indirect to reference external data symbols"),
+    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_mir: Option<String> = (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_alloc_bytes: bool = (false, parse_bool, [UNTRACKED],
+        "exclude the raw bytes of allocations when dumping MIR (used in tests) (default: no)"),
+    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 (default: no)"),
+    dump_mono_stats: SwitchWithOptPath = (SwitchWithOptPath::Disabled,
+        parse_switch_with_opt_path, [UNTRACKED],
+        "output statistics about monomorphization collection"),
+    dump_mono_stats_format: DumpMonoStatsFormat = (DumpMonoStatsFormat::Markdown, parse_dump_mono_stats, [UNTRACKED],
+        "the format to use for -Z dump-mono-stats (`markdown` (default) or `json`)"),
+    dwarf_version: Option<u32> = (None, parse_opt_number, [TRACKED],
+        "version of DWARF debug information to emit (default: 2 or 4, depending on platform)"),
+    dylib_lto: bool = (false, parse_bool, [UNTRACKED],
+        "enables LTO for dylib crate type"),
+    eagerly_emit_delayed_bugs: bool = (false, parse_bool, [UNTRACKED],
+        "emit delayed bugs eagerly as errors instead of stashing them and emitting \
+        them only if an error has not been emitted"),
+    ehcont_guard: bool = (false, parse_bool, [TRACKED],
+        "generate Windows EHCont Guard tables"),
+    embed_source: bool = (false, parse_bool, [TRACKED],
+        "embed source text in DWARF debug sections (default: no)"),
+    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)"),
+    enforce_type_length_limit: bool = (false, parse_bool, [TRACKED],
+        "enforce the type length limit when monomorphizing instances in codegen"),
+    export_executable_symbols: bool = (false, parse_bool, [TRACKED],
+        "export symbols from executables, as if they were dynamic libraries"),
+    external_clangrt: bool = (false, parse_bool, [UNTRACKED],
+        "rely on user specified linker commands to find clangrt"),
+    extra_const_ub_checks: bool = (false, parse_bool, [TRACKED],
+        "turns on more checks to detect const UB, which can be slow (default: no)"),
+    #[rustc_lint_opt_deny_field_access("use `Session::fewer_names` instead of this field")]
+    fewer_names: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "reduce memory use by retaining fewer names within compilation artifacts (LLVM-IR) \
+        (default: no)"),
+    fixed_x18: bool = (false, parse_bool, [TRACKED],
+        "make the x18 register reserved on AArch64 (default: no)"),
+    flatten_format_args: bool = (true, parse_bool, [TRACKED],
+        "flatten nested format_args!() and literals into a simplified format_args!() call \
+        (default: yes)"),
+    fmt_debug: FmtDebug = (FmtDebug::Full, parse_fmt_debug, [TRACKED],
+        "how detailed `#[derive(Debug)]` should be. `full` prints types recursively, \
+        `shallow` prints only type names, `none` prints nothing and disables `{:?}`. (default: `full`)"),
+    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_return: FunctionReturn = (FunctionReturn::default(), parse_function_return, [TRACKED],
+        "replace returns with jumps to `__x86_return_thunk` (default: `keep`)"),
+    function_sections: Option<bool> = (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)"),
+    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`)"),
+    has_thread_local: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "explicitly enable the `cfg(target_thread_local)` directive"),
+    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 `'<id>`, using a non-ident unique id (default: no)"),
+    ignore_directory_in_diagnostics_source_blocks: Vec<String> = (Vec::new(), parse_string_push, [UNTRACKED],
+        "do not display the source code block in diagnostics for files in the directory"),
+    incremental_ignore_spans: bool = (false, parse_bool, [TRACKED],
+        "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_verify_ich: bool = (false, parse_bool, [UNTRACKED],
+        "verify extended properties for incr. comp. (default: no):
+        - hashes of green query instances
+        - hash collisions of query keys"),
+    inline_in_all_cgus: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "control whether `#[inline]` functions are in all CGUs"),
+    inline_llvm: bool = (true, parse_bool, [TRACKED],
+        "enable LLVM inlining (default: yes)"),
+    inline_mir: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "enable MIR inlining (default: no)"),
+    inline_mir_forwarder_threshold: Option<usize> = (None, parse_opt_number, [TRACKED],
+        "inlining threshold when the caller is a simple forwarding function (default: 30)"),
+    inline_mir_hint_threshold: Option<usize> = (None, parse_opt_number, [TRACKED],
+        "inlining threshold for functions with inline hint (default: 100)"),
+    inline_mir_preserve_debug: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "when MIR inlining, whether to preserve debug info for callee variables \
+        (default: preserve for debuginfo != None, otherwise remove)"),
+    inline_mir_threshold: Option<usize> = (None, parse_opt_number, [TRACKED],
+        "a default MIR inlining threshold (default: 50)"),
+    input_stats: bool = (false, parse_bool, [UNTRACKED],
+        "gather statistics about the input (default: no)"),
+    instrument_mcount: bool = (false, parse_bool, [TRACKED],
+        "insert function instrument code for mcount-based tracing (default: no)"),
+    instrument_xray: Option<InstrumentXRay> = (None, parse_instrument_xray, [TRACKED],
+        "insert function instrument code for XRay-based tracing (default: no)
+         Optional extra settings:
+         `=always`
+         `=never`
+         `=ignore-loops`
+         `=instruction-threshold=N`
+         `=skip-entry`
+         `=skip-exit`
+         Multiple options can be combined with commas."),
+    layout_seed: Option<u64> = (None, parse_opt_number, [TRACKED],
+        "seed layout randomization"),
+    link_directives: bool = (true, parse_bool, [TRACKED],
+        "honor #[link] directives in the compiled crate (default: yes)"),
+    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)"),
+    linker_features: LinkerFeaturesCli = (LinkerFeaturesCli::default(), parse_linker_features, [UNTRACKED],
+        "a comma-separated list of linker features to enable (+) or disable (-): `lld`"),
+    lint_llvm_ir: bool = (false, parse_bool, [TRACKED],
+        "lint LLVM IR (default: no)"),
+    lint_mir: bool = (false, parse_bool, [UNTRACKED],
+        "lint MIR before and after each transformation"),
+    llvm_module_flag: Vec<(String, u32, String)> = (Vec::new(), parse_llvm_module_flag, [TRACKED],
+        "a list of module flags to pass to LLVM (space separated)"),
+    llvm_plugins: Vec<String> = (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: Vec<String> = (Vec::new(), parse_list, [UNTRACKED],
+        "decode and print various parts of the crate metadata for a library crate \
+        (space separated)"),
+    macro_backtrace: bool = (false, parse_bool, [UNTRACKED],
+        "show macro backtraces (default: no)"),
+    maximal_hir_to_mir_coverage: bool = (false, parse_bool, [TRACKED],
+        "save as much information as possible about the correspondence between MIR and HIR \
+        as source scopes (default: no)"),
+    merge_functions: Option<MergeFunctions> = (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"),
+    meta_stats: bool = (false, parse_bool, [UNTRACKED],
+        "gather metadata statistics (default: no)"),
+    metrics_dir: Option<PathBuf> = (None, parse_opt_pathbuf, [UNTRACKED],
+        "stores metrics about the errors being emitted by rustc to disk"),
+    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=+DestinationPropagation,-InstSimplify`. Forces the \
+        specified passes to be enabled, overriding all other checks. In particular, this will \
+        enable unsound (known-buggy and hence usually disabled) passes without further warning! \
+        Passes that are not specified are enabled or disabled by other flags as usual."),
+    mir_include_spans: MirIncludeSpans = (MirIncludeSpans::default(), parse_mir_include_spans, [UNTRACKED],
+        "include extra comments in mir pretty printing, like line numbers and statement indices, \
+         details about types, etc. (boolean for all passes, 'nll' to enable in NLL MIR only, default: 'nll')"),
+    mir_keep_place_mention: bool = (false, parse_bool, [TRACKED],
+        "keep place mention MIR statements, interpreted e.g., by miri; implies -Zmir-opt-level=0 \
+        (default: no)"),
+    #[rustc_lint_opt_deny_field_access("use `Session::mir_opt_level` instead of this field")]
+    mir_opt_level: Option<usize> = (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<usize> = (None, parse_opt_number, [TRACKED],
+        "the size at which the `large_assignments` lint starts to be emitted"),
+    mutable_noalias: bool = (true, parse_bool, [TRACKED],
+        "emit noalias metadata for mutable references (default: yes)"),
+    next_solver: NextSolverConfig = (NextSolverConfig::default(), parse_next_solver_config, [TRACKED],
+        "enable and configure the next generation trait solver used by rustc"),
+    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_implied_bounds_compat: bool = (false, parse_bool, [TRACKED],
+        "disable the compatibility version of the `implied_bounds_ty` query"),
+    no_jump_tables: bool = (false, parse_no_flag, [TRACKED],
+        "disable the jump tables and lookup tables that can be generated from a switch case lowering"),
+    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_backend: bool = (false, parse_no_flag, [UNTRACKED],
+        "run LLVM in non-parallel mode (while keeping codegen-units and ThinLTO)"),
+    no_profiler_runtime: bool = (false, parse_no_flag, [TRACKED],
+        "prevent automatic injection of the profiler_builtins crate"),
+    no_trait_vptr: bool = (false, parse_no_flag, [TRACKED],
+        "disable generation of trait vptr in vtable for upcasting"),
+    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"),
+    normalize_docs: bool = (false, parse_bool, [TRACKED],
+        "normalize associated items in rustdoc when generating documentation"),
+    on_broken_pipe: OnBrokenPipe = (OnBrokenPipe::Default, parse_on_broken_pipe, [TRACKED],
+        "behavior of std::io::ErrorKind::BrokenPipe (SIGPIPE)"),
+    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)"),
+    packed_bundled_libs: bool = (false, parse_bool, [TRACKED],
+        "change rlib format to store native libraries as archives"),
+    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)"),
+    patchable_function_entry: PatchableFunctionEntry = (PatchableFunctionEntry::default(), parse_patchable_function_entry, [TRACKED],
+        "nop padding at function entry"),
+    plt: Option<bool> = (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 on x86_64)"),
+    polonius: Polonius = (Polonius::default(), parse_polonius, [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<String> = (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."),
+    #[rustc_lint_opt_deny_field_access("use `Session::print_codegen_stats` instead of this field")]
+    print_codegen_stats: bool = (false, parse_bool, [UNTRACKED],
+        "print codegen statistics (default: no)"),
+    print_fuel: Option<String> = (None, parse_opt_string, [TRACKED],
+        "make rustc print the total optimization fuel used by a crate"),
+    print_llvm_passes: bool = (false, parse_bool, [UNTRACKED],
+        "print the LLVM optimization passes being run (default: no)"),
+    print_mono_items: Option<String> = (None, parse_opt_string, [UNTRACKED],
+        "print the result of the monomorphization collection pass. \
+         Value `lazy` means to use normal collection; `eager` means to collect all items.
+         Note that this overwrites the effect `-Clink-dead-code` has on collection!"),
+    print_type_sizes: bool = (false, parse_bool, [UNTRACKED],
+        "print layout information for each type encountered (default: no)"),
+    print_vtable_sizes: bool = (false, parse_bool, [UNTRACKED],
+        "print size comparison between old and new vtable layouts (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_closures: bool = (false, parse_no_flag, [UNTRACKED],
+        "profile size of closures"),
+    profile_sample_use: Option<PathBuf> = (None, parse_opt_pathbuf, [TRACKED],
+        "use the given `.prof` file for sampled profile-guided optimization (also known as AutoFDO)"),
+    profiler_runtime: String = (String::from("profiler_builtins"), parse_string, [TRACKED],
+        "name of the profiler runtime crate to automatically inject (default: `profiler_builtins`)"),
+    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)"),
+    regparm: Option<u32> = (None, parse_opt_number, [TRACKED],
+        "On x86-32 targets, setting this to N causes the compiler to pass N arguments \
+        in registers EAX, EDX, and ECX instead of on the stack for\
+        \"C\", \"cdecl\", and \"stdcall\" fn.\
+        It is UNSOUND to link together crates that use different values for this flag!"),
+    relax_elf_relocations: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "whether ELF relocations can be relaxed"),
+    remap_cwd_prefix: Option<PathBuf> = (None, parse_opt_pathbuf, [TRACKED],
+        "remap paths under the current working directory to this path prefix"),
+    remap_path_scope: RemapPathScopeComponents = (RemapPathScopeComponents::all(), parse_remap_path_scope, [TRACKED],
+        "remap path scope (default: all)"),
+    remark_dir: Option<PathBuf> = (None, parse_opt_pathbuf, [UNTRACKED],
+        "directory into which to write optimization remarks (if not specified, they will be \
+written to standard error output)"),
+    sanitizer: SanitizerSet = (SanitizerSet::empty(), parse_sanitizers, [TRACKED],
+        "use a sanitizer"),
+    sanitizer_cfi_canonical_jump_tables: Option<bool> = (Some(true), parse_opt_bool, [TRACKED],
+        "enable canonical jump tables (default: yes)"),
+    sanitizer_cfi_generalize_pointers: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "enable generalizing pointer types (default: no)"),
+    sanitizer_cfi_normalize_integers: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "enable normalizing integer types (default: no)"),
+    sanitizer_dataflow_abilist: Vec<String> = (Vec::new(), parse_comma_list, [TRACKED],
+        "additional ABI list files that control how shadow parameters are passed (comma separated)"),
+    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<bool> = (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)"),
+    self_profile: SwitchWithOptPath = (SwitchWithOptPath::Disabled,
+        parse_switch_with_opt_path, [UNTRACKED],
+        "run the self profiler and output the raw event data"),
+    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)"
+    ),
+    /// keep this in sync with the event filter names in librustc_data_structures/profiling.rs
+    self_profile_events: Option<Vec<String>> = (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"),
+    share_generics: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "make the current crate share its generic instantiations"),
+    shell_argfiles: bool = (false, parse_bool, [UNTRACKED],
+        "allow argument files to be specified with POSIX \"shell-style\" argument quoting"),
+    show_span: Option<String> = (None, parse_opt_string, [TRACKED],
+        "show spans for compiler debugging (expr|pat|ty)"),
+    simulate_remapped_rust_src_base: Option<PathBuf> = (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"),
+    small_data_threshold: Option<usize> = (None, parse_opt_number, [TRACKED],
+        "Set the threshold for objects to be stored in a \"small data\" section"),
+    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)"),
+    split_dwarf_inlining: bool = (false, 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"),
+    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_lto_unit: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "enable LTO unit splitting (default: no)"),
+    src_hash_algorithm: Option<SourceFileHashAlgorithm> = (None, parse_src_file_hash, [TRACKED],
+        "hash algorithm of source files in debug info (`md5`, `sha1`, or `sha256`)"),
+    #[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)"),
+    staticlib_allow_rdylib_deps: bool = (false, parse_bool, [TRACKED],
+        "allow staticlibs to have rust dylib dependencies"),
+    staticlib_prefer_dynamic: bool = (false, parse_bool, [TRACKED],
+        "prefer dynamic linking to static linking for staticlibs (default: no)"),
+    strict_init_checks: bool = (false, parse_bool, [TRACKED],
+        "control if mem::uninitialized and mem::zeroed panic on more UB"),
+    #[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<String> = (None, parse_opt_string, [UNTRACKED],
+        "the directory the intermediate files are written to"),
+    terminal_urls: TerminalUrl = (TerminalUrl::No, parse_terminal_url, [UNTRACKED],
+        "use the OSC 8 hyperlink terminal specification to print hyperlinks in the compiler output"),
+    #[rustc_lint_opt_deny_field_access("use `Session::lto` instead of this field")]
+    thinlto: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "enable ThinLTO when possible"),
+    /// 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.
+    #[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"),
+    time_llvm_passes: bool = (false, parse_bool, [UNTRACKED],
+        "measure time of each LLVM pass (default: no)"),
+    time_passes: bool = (false, parse_bool, [UNTRACKED],
+        "measure time of each rustc pass (default: no)"),
+    time_passes_format: TimePassesFormat = (TimePassesFormat::Text, parse_time_passes_format, [UNTRACKED],
+        "the format to use for -Z time-passes (`text` (default) or `json`)"),
+    tiny_const_eval_limit: bool = (false, parse_bool, [TRACKED],
+        "sets a tiny, non-configurable limit for const eval; useful for compiler tests"),
+    #[rustc_lint_opt_deny_field_access("use `Session::tls_model` instead of this field")]
+    tls_model: Option<TlsModel> = (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)"),
+    track_diagnostics: bool = (false, parse_bool, [UNTRACKED],
+        "tracks where in rustc a diagnostic was emitted"),
+    // 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_additional_ftl: Option<PathBuf> = (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"),
+    translate_lang: Option<LanguageIdentifier> = (None, parse_opt_langid, [TRACKED],
+        "language identifier for diagnostic output"),
+    translate_remapped_path_to_local_path: bool = (true, parse_bool, [TRACKED],
+        "translate remapped paths into local paths when possible (default: yes)"),
+    trap_unreachable: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "generate trap instructions for unreachable intrinsics (default: use target setting, usually yes)"),
+    treat_err_as_bug: Option<NonZero<usize>> = (None, parse_treat_err_as_bug, [TRACKED],
+        "treat the `val`th error that occurs as bug (default if not specified: 0 - don't treat errors as bugs. \
+        default if specified without a value: 1 - treat the first error as bug)"),
+    trim_diagnostic_paths: bool = (true, parse_bool, [UNTRACKED],
+        "in diagnostics, use heuristics to shorten paths referring to items"),
+    tune_cpu: Option<String> = (None, parse_opt_string, [TRACKED],
+        "select processor to schedule for (`rustc --print target-cpus` for details)"),
+    #[rustc_lint_opt_deny_field_access("use `Session::ub_checks` instead of this field")]
+    ub_checks: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "emit runtime checks for Undefined Behavior (default: -Cdebug-assertions)"),
+    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<String> = (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),
+        `thir-tree`, `thir-flat`,
+        `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.
+    #[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<bool> = (None, parse_opt_bool, [TRACKED],
+        "use legacy .ctors section for initializers rather than .init_array"),
+    use_sync_unwind: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "Generate sync unwind tables instead of async unwind tables (default: no)"),
+    validate_mir: bool = (false, parse_bool, [UNTRACKED],
+        "validate MIR after each transformation"),
+    verbose_asm: bool = (false, parse_bool, [TRACKED],
+        "add descriptive comments from LLVM to the assembly (may change behavior) (default: no)"),
+    #[rustc_lint_opt_deny_field_access("use `Session::verbose_internals` instead of this field")]
+    verbose_internals: bool = (false, parse_bool, [TRACKED_NO_CRATE_HASH],
+        "in general, enable more debug printouts (default: no)"),
+    #[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<WasiExecModel> = (None, parse_wasi_exec_model, [TRACKED],
+        "whether to build a wasi command or reactor"),
+    wasm_c_abi: WasmCAbi = (WasmCAbi::Legacy, parse_wasm_c_abi, [TRACKED],
+        "use spec-compliant C ABI for `wasm32-unknown-unknown` (default: legacy)"),
+    write_long_types_to_disk: bool = (true, parse_bool, [UNTRACKED],
+        "whether long type names should be written to files instead of being printed in errors"),
+    // tidy-alphabetical-end
+
+    // If you add a new option, please update:
+    // - compiler/rustc_interface/src/tests.rs
+    // - src/doc/unstable-book/src/compiler-flags
+}
diff --git a/compiler/rustc_session/src/output.rs b/compiler/rustc_session/src/output.rs
new file mode 100644
index 00000000000..357d746c184
--- /dev/null
+++ b/compiler/rustc_session/src/output.rs
@@ -0,0 +1,266 @@
+//! Related to out filenames of compilation (e.g. binaries).
+
+use std::path::Path;
+
+use rustc_ast::{self as ast, attr};
+use rustc_errors::FatalError;
+use rustc_span::symbol::sym;
+use rustc_span::{Span, Symbol};
+
+use crate::Session;
+use crate::config::{self, CrateType, Input, OutFileName, OutputFilenames, OutputType};
+use crate::errors::{
+    self, CrateNameDoesNotMatch, CrateNameEmpty, CrateNameInvalid, FileIsNotWriteable,
+    InvalidCharacterInCrateName, InvalidCrateNameHelp,
+};
+
+pub fn out_filename(
+    sess: &Session,
+    crate_type: CrateType,
+    outputs: &OutputFilenames,
+    crate_name: Symbol,
+) -> OutFileName {
+    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);
+
+    if let OutFileName::Real(ref path) = out_filename {
+        check_file_is_writeable(path, 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.dcx().emit_fatal(FileIsNotWriteable { file });
+    }
+}
+
+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]) -> Symbol {
+    let validate = |s: Symbol, span: Option<Span>| {
+        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 =
+        attr::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 {
+        let s = Symbol::intern(s);
+        if let Some((attr, name)) = attr_crate_name {
+            if name != s {
+                sess.dcx().emit_err(CrateNameDoesNotMatch { span: attr.span, s, name });
+            }
+        }
+        return validate(s, None);
+    }
+
+    if let Some((attr, s)) = attr_crate_name {
+        return validate(s, Some(attr.span));
+    }
+    if let Input::File(ref path) = sess.io.input {
+        if let Some(s) = path.file_stem().and_then(|s| s.to_str()) {
+            if s.starts_with('-') {
+                sess.dcx().emit_err(CrateNameInvalid { s });
+            } else {
+                return validate(Symbol::intern(&s.replace('-', "_")), None);
+            }
+        }
+    }
+
+    Symbol::intern("rust_out")
+}
+
+pub fn validate_crate_name(sess: &Session, s: Symbol, sp: Option<Span>) {
+    let mut err_count = 0;
+    {
+        if s.is_empty() {
+            err_count += 1;
+            sess.dcx().emit_err(CrateNameEmpty { span: sp });
+        }
+        for c in s.as_str().chars() {
+            if c.is_alphanumeric() {
+                continue;
+            }
+            if c == '_' {
+                continue;
+            }
+            err_count += 1;
+            sess.dcx().emit_err(InvalidCharacterInCrateName {
+                span: sp,
+                character: c,
+                crate_name: s,
+                crate_name_help: if sp.is_none() {
+                    Some(InvalidCrateNameHelp::AddCrateName)
+                } else {
+                    None
+                },
+            });
+        }
+    }
+
+    if err_count > 0 {
+        FatalError.raise();
+    }
+}
+
+pub fn filename_for_metadata(sess: &Session, outputs: &OutputFilenames) -> OutFileName {
+    let out_filename = outputs.path(OutputType::Metadata);
+    if let OutFileName::Real(ref path) = out_filename {
+        check_file_is_writeable(path, sess);
+    }
+    out_filename
+}
+
+pub fn filename_for_input(
+    sess: &Session,
+    crate_type: CrateType,
+    crate_name: Symbol,
+    outputs: &OutputFilenames,
+) -> OutFileName {
+    let libname = format!("{}{}", crate_name, sess.opts.cg.extra_filename);
+
+    match crate_type {
+        CrateType::Rlib => {
+            OutFileName::Real(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);
+            OutFileName::Real(outputs.out_directory.join(&format!("{prefix}{libname}{suffix}")))
+        }
+        CrateType::Staticlib => {
+            let (prefix, suffix) = (&sess.target.staticlib_prefix, &sess.target.staticlib_suffix);
+            OutFileName::Real(outputs.out_directory.join(&format!("{prefix}{libname}{suffix}")))
+        }
+        CrateType::Executable => {
+            let suffix = &sess.target.exe_suffix;
+            let out_filename = outputs.path(OutputType::Exe);
+            if let OutFileName::Real(ref path) = out_filename {
+                if suffix.is_empty() {
+                    out_filename
+                } else {
+                    OutFileName::Real(path.with_extension(&suffix[1..]))
+                }
+            } else {
+                out_filename
+            }
+        }
+    }
+}
+
+/// 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
+}
+
+pub const CRATE_TYPES: &[(Symbol, CrateType)] = &[
+    (sym::rlib, CrateType::Rlib),
+    (sym::dylib, CrateType::Dylib),
+    (sym::cdylib, CrateType::Cdylib),
+    (sym::lib, config::default_lib_output()),
+    (sym::staticlib, CrateType::Staticlib),
+    (sym::proc_dash_macro, CrateType::ProcMacro),
+    (sym::bin, CrateType::Executable),
+];
+
+pub fn categorize_crate_type(s: Symbol) -> Option<CrateType> {
+    Some(CRATE_TYPES.iter().find(|(key, _)| *key == s)?.1)
+}
+
+pub fn collect_crate_types(session: &Session, attrs: &[ast::Attribute]) -> Vec<CrateType> {
+    // If we're generating a test executable, then ignore all other output
+    // styles at all other locations
+    if session.opts.test {
+        return vec![CrateType::Executable];
+    }
+
+    // Only check command line flags if present. If no types are specified by
+    // command line, then reuse the empty `base` Vec to hold the types that
+    // will be found in crate attributes.
+    // JUSTIFICATION: before wrapper fn is available
+    #[allow(rustc::bad_opt_access)]
+    let mut base = session.opts.crate_types.clone();
+    if base.is_empty() {
+        let attr_types = attrs.iter().filter_map(|a| {
+            if a.has_name(sym::crate_type)
+                && let Some(s) = a.value_str()
+            {
+                categorize_crate_type(s)
+            } else {
+                None
+            }
+        });
+        base.extend(attr_types);
+        if base.is_empty() {
+            base.push(default_output_for_target(session));
+        } else {
+            base.sort();
+            base.dedup();
+        }
+    }
+
+    base.retain(|crate_type| {
+        if invalid_output_for_target(session, *crate_type) {
+            session.dcx().emit_warn(errors::UnsupportedCrateTypeForTarget {
+                crate_type: *crate_type,
+                target_triple: &session.opts.target_triple,
+            });
+            false
+        } else {
+            true
+        }
+    });
+
+    base
+}
diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs
new file mode 100644
index 00000000000..21c11655110
--- /dev/null
+++ b/compiler/rustc_session/src/parse.rs
@@ -0,0 +1,348 @@
+//! Contains `ParseSess` which holds state living beyond what one `Parser` might.
+//! It also serves as an input to the parser itself.
+
+use std::str;
+
+use rustc_ast::attr::AttrIdGenerator;
+use rustc_ast::node_id::NodeId;
+use rustc_data_structures::fx::{FxHashMap, FxIndexMap, FxIndexSet};
+use rustc_data_structures::sync::{AppendOnlyVec, Lock, Lrc};
+use rustc_errors::emitter::{HumanEmitter, SilentEmitter, stderr_destination};
+use rustc_errors::{
+    ColorConfig, Diag, DiagCtxt, DiagCtxtHandle, DiagMessage, EmissionGuarantee, MultiSpan,
+    StashKey, fallback_fluent_bundle,
+};
+use rustc_feature::{GateIssue, UnstableFeatures, find_feature_issue};
+use rustc_span::edition::Edition;
+use rustc_span::hygiene::ExpnId;
+use rustc_span::source_map::{FilePathMapping, SourceMap};
+use rustc_span::{Span, Symbol};
+
+use crate::Session;
+use crate::config::{Cfg, CheckCfg};
+use crate::errors::{
+    CliFeatureDiagnosticHelp, FeatureDiagnosticForIssue, FeatureDiagnosticHelp,
+    FeatureDiagnosticSuggestion, FeatureGateError, SuggestUpgradeCompiler,
+};
+use crate::lint::builtin::UNSTABLE_SYNTAX_PRE_EXPANSION;
+use crate::lint::{BufferedEarlyLint, BuiltinLintDiag, Lint, LintId};
+
+/// 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<FxHashMap<Symbol, Vec<Span>>>,
+}
+
+impl GatedSpans {
+    /// Feature gate the given `span` under the given `feature`
+    /// which is same `Symbol` used in `unstable.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);
+    }
+
+    /// Prepend the given set of `spans` onto the set in `self`.
+    pub fn merge(&self, mut spans: FxHashMap<Symbol, Vec<Span>>) {
+        let mut inner = self.spans.borrow_mut();
+        // The entries will be moved to another map so the drain order does not
+        // matter.
+        #[allow(rustc::potential_query_instability)]
+        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<FxIndexMap<Symbol, Span>>,
+}
+
+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);
+    }
+}
+
+// todo: this function now accepts `Session` instead of `ParseSess` and should be relocated
+/// Construct a diagnostic for a language feature error due to the given `span`.
+/// The `feature`'s `Symbol` is the one you used in `unstable.rs` and `rustc_span::symbols`.
+#[track_caller]
+pub fn feature_err(
+    sess: &Session,
+    feature: Symbol,
+    span: impl Into<MultiSpan>,
+    explain: impl Into<DiagMessage>,
+) -> Diag<'_> {
+    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`.
+#[track_caller]
+pub fn feature_err_issue(
+    sess: &Session,
+    feature: Symbol,
+    span: impl Into<MultiSpan>,
+    issue: GateIssue,
+    explain: impl Into<DiagMessage>,
+) -> Diag<'_> {
+    let span = span.into();
+
+    // Cancel an earlier warning for this same error, if it exists.
+    if let Some(span) = span.primary_span() {
+        if let Some(err) = sess.dcx().steal_non_err(span, StashKey::EarlySyntaxWarning) {
+            err.cancel()
+        }
+    }
+
+    let mut err = sess.dcx().create_err(FeatureGateError { span, explain: explain.into() });
+    add_feature_diagnostics_for_issue(&mut err, sess, feature, issue, false, None);
+    err
+}
+
+/// Construct a future incompatibility diagnostic for a feature gate.
+///
+/// This diagnostic is only a warning and *does not cause compilation to fail*.
+#[track_caller]
+pub fn feature_warn(sess: &Session, feature: Symbol, span: Span, explain: &'static str) {
+    feature_warn_issue(sess, feature, span, GateIssue::Language, explain);
+}
+
+/// Construct a future incompatibility diagnostic for a feature gate.
+///
+/// This diagnostic is only a warning and *does not cause compilation to fail*.
+///
+/// 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_warn`.
+#[allow(rustc::diagnostic_outside_of_impl)]
+#[allow(rustc::untranslatable_diagnostic)]
+#[track_caller]
+pub fn feature_warn_issue(
+    sess: &Session,
+    feature: Symbol,
+    span: Span,
+    issue: GateIssue,
+    explain: &'static str,
+) {
+    let mut err = sess.dcx().struct_span_warn(span, explain);
+    add_feature_diagnostics_for_issue(&mut err, sess, feature, issue, false, None);
+
+    // Decorate this as a future-incompatibility lint as in rustc_middle::lint::lint_level
+    let lint = UNSTABLE_SYNTAX_PRE_EXPANSION;
+    let future_incompatible = lint.future_incompatible.as_ref().unwrap();
+    err.is_lint(lint.name_lower(), /* has_future_breakage */ false);
+    err.warn(lint.desc);
+    err.note(format!("for more information, see {}", future_incompatible.reference));
+
+    // A later feature_err call can steal and cancel this warning.
+    err.stash(span, StashKey::EarlySyntaxWarning);
+}
+
+/// Adds the diagnostics for a feature to an existing error.
+/// Must be a language feature!
+pub fn add_feature_diagnostics<G: EmissionGuarantee>(
+    err: &mut Diag<'_, G>,
+    sess: &Session,
+    feature: Symbol,
+) {
+    add_feature_diagnostics_for_issue(err, sess, feature, GateIssue::Language, false, None);
+}
+
+/// 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`.
+#[allow(rustc::diagnostic_outside_of_impl)] // FIXME
+pub fn add_feature_diagnostics_for_issue<G: EmissionGuarantee>(
+    err: &mut Diag<'_, G>,
+    sess: &Session,
+    feature: Symbol,
+    issue: GateIssue,
+    feature_from_cli: bool,
+    inject_span: Option<Span>,
+) {
+    if let Some(n) = find_feature_issue(feature, issue) {
+        err.subdiagnostic(FeatureDiagnosticForIssue { n });
+    }
+
+    // #23973: do not suggest `#![feature(...)]` if we are in beta/stable
+    if sess.psess.unstable_features.is_nightly_build() {
+        if feature_from_cli {
+            err.subdiagnostic(CliFeatureDiagnosticHelp { feature });
+        } else if let Some(span) = inject_span {
+            err.subdiagnostic(FeatureDiagnosticSuggestion { feature, span });
+        } else {
+            err.subdiagnostic(FeatureDiagnosticHelp { feature });
+        }
+
+        if sess.opts.unstable_opts.ui_testing {
+            err.subdiagnostic(SuggestUpgradeCompiler::ui_testing());
+        } else if let Some(suggestion) = SuggestUpgradeCompiler::new() {
+            err.subdiagnostic(suggestion);
+        }
+    }
+}
+
+/// Info about a parsing session.
+pub struct ParseSess {
+    dcx: DiagCtxt,
+    pub unstable_features: UnstableFeatures,
+    pub config: Cfg,
+    pub check_config: CheckCfg,
+    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: AppendOnlyVec<Span>,
+    /// 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<FxIndexMap<Symbol, Vec<Span>>>,
+    source_map: Lrc<SourceMap>,
+    pub buffered_lints: Lock<Vec<BufferedEarlyLint>>,
+    /// 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<FxIndexMap<Span, Span>>,
+    pub gated_spans: GatedSpans,
+    pub symbol_gallery: SymbolGallery,
+    /// Environment variables accessed during the build and their values when they exist.
+    pub env_depinfo: Lock<FxIndexSet<(Symbol, Option<Symbol>)>>,
+    /// File paths accessed during the build.
+    pub file_depinfo: Lock<FxIndexSet<Symbol>>,
+    /// 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.
+    proc_macro_quoted_spans: AppendOnlyVec<Span>,
+    /// Used to generate new `AttrId`s. Every `AttrId` is unique.
+    pub attr_id_generator: AttrIdGenerator,
+}
+
+impl ParseSess {
+    /// Used for testing.
+    pub fn new(locale_resources: Vec<&'static str>) -> Self {
+        let fallback_bundle = fallback_fluent_bundle(locale_resources, false);
+        let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
+        let emitter = Box::new(
+            HumanEmitter::new(stderr_destination(ColorConfig::Auto), fallback_bundle)
+                .sm(Some(Lrc::clone(&sm))),
+        );
+        let dcx = DiagCtxt::new(emitter);
+        ParseSess::with_dcx(dcx, sm)
+    }
+
+    pub fn with_dcx(dcx: DiagCtxt, source_map: Lrc<SourceMap>) -> Self {
+        Self {
+            dcx,
+            unstable_features: UnstableFeatures::from_environment(None),
+            config: Cfg::default(),
+            check_config: CheckCfg::default(),
+            edition: ExpnId::root().expn_data().edition,
+            raw_identifier_spans: Default::default(),
+            bad_unicode_identifiers: Lock::new(Default::default()),
+            source_map,
+            buffered_lints: Lock::new(vec![]),
+            ambiguous_block_expr_parse: Lock::new(Default::default()),
+            gated_spans: GatedSpans::default(),
+            symbol_gallery: SymbolGallery::default(),
+            env_depinfo: Default::default(),
+            file_depinfo: Default::default(),
+            assume_incomplete_release: false,
+            proc_macro_quoted_spans: Default::default(),
+            attr_id_generator: AttrIdGenerator::new(),
+        }
+    }
+
+    pub fn with_silent_emitter(
+        locale_resources: Vec<&'static str>,
+        fatal_note: String,
+        emit_fatal_diagnostic: bool,
+    ) -> Self {
+        let fallback_bundle = fallback_fluent_bundle(locale_resources, false);
+        let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
+        let emitter = Box::new(HumanEmitter::new(
+            stderr_destination(ColorConfig::Auto),
+            Lrc::clone(&fallback_bundle),
+        ));
+        let fatal_dcx = DiagCtxt::new(emitter);
+        let dcx = DiagCtxt::new(Box::new(SilentEmitter {
+            fallback_bundle,
+            fatal_dcx,
+            fatal_note: Some(fatal_note),
+            emit_fatal_diagnostic,
+        }))
+        .disable_warnings();
+        ParseSess::with_dcx(dcx, sm)
+    }
+
+    #[inline]
+    pub fn source_map(&self) -> &SourceMap {
+        &self.source_map
+    }
+
+    pub fn clone_source_map(&self) -> Lrc<SourceMap> {
+        Lrc::clone(&self.source_map)
+    }
+
+    pub fn buffer_lint(
+        &self,
+        lint: &'static Lint,
+        span: impl Into<MultiSpan>,
+        node_id: NodeId,
+        diagnostic: BuiltinLintDiag,
+    ) {
+        self.opt_span_buffer_lint(lint, Some(span.into()), node_id, diagnostic)
+    }
+
+    pub fn opt_span_buffer_lint(
+        &self,
+        lint: &'static Lint,
+        span: Option<MultiSpan>,
+        node_id: NodeId,
+        diagnostic: BuiltinLintDiag,
+    ) {
+        self.buffered_lints.with_lock(|buffered_lints| {
+            buffered_lints.push(BufferedEarlyLint {
+                span,
+                node_id,
+                lint_id: LintId::of(lint),
+                diagnostic,
+            });
+        });
+    }
+
+    pub fn save_proc_macro_span(&self, span: Span) -> usize {
+        self.proc_macro_quoted_spans.push(span)
+    }
+
+    pub fn proc_macro_quoted_spans(&self) -> impl Iterator<Item = (usize, Span)> + '_ {
+        // This is equivalent to `.iter().copied().enumerate()`, but that isn't possible for
+        // AppendOnlyVec, so we resort to this scheme.
+        self.proc_macro_quoted_spans.iter_enumerated()
+    }
+
+    pub fn dcx(&self) -> DiagCtxtHandle<'_> {
+        self.dcx.handle()
+    }
+
+    pub fn set_dcx(&mut self, dcx: DiagCtxt) {
+        self.dcx = dcx;
+    }
+}
diff --git a/compiler/rustc_session/src/search_paths.rs b/compiler/rustc_session/src/search_paths.rs
new file mode 100644
index 00000000000..c148b09c718
--- /dev/null
+++ b/compiler/rustc_session/src/search_paths.rs
@@ -0,0 +1,117 @@
+use std::path::{Path, PathBuf};
+
+use rustc_macros::{Decodable, Encodable, HashStable_Generic};
+use rustc_target::spec::TargetTuple;
+
+use crate::EarlyDiagCtxt;
+use crate::filesearch::make_target_lib_path;
+
+#[derive(Clone, Debug)]
+pub struct SearchPath {
+    pub kind: PathKind,
+    pub dir: PathBuf,
+    pub files: Vec<SearchPathFile>,
+}
+
+/// The obvious implementation of `SearchPath::files` is a `Vec<PathBuf>`. 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(
+        sysroot: &Path,
+        triple: &TargetTuple,
+        early_dcx: &EarlyDiagCtxt,
+        path: &str,
+        is_unstable_enabled: bool,
+    ) -> 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)
+        };
+        let dir = match path.strip_prefix("@RUSTC_BUILTIN") {
+            Some(stripped) => {
+                if !is_unstable_enabled {
+                    #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
+                    early_dcx.early_fatal(
+                        "the `-Z unstable-options` flag must also be passed to \
+                         enable the use of `@RUSTC_BUILTIN`",
+                    );
+                }
+
+                make_target_lib_path(sysroot, triple.tuple()).join("builtin").join(stripped)
+            }
+            None => PathBuf::from(path),
+        };
+        if dir.as_os_str().is_empty() {
+            #[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
+            early_dcx.early_fatal("empty search path given via `-L`");
+        }
+
+        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))
+    }
+
+    pub 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::<Vec<_>>(),
+            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 00000000000..d8d6b79974f
--- /dev/null
+++ b/compiler/rustc_session/src/session.rs
@@ -0,0 +1,1543 @@
+use std::any::Any;
+use std::ops::{Div, Mul};
+use std::path::{Path, PathBuf};
+use std::str::FromStr;
+use std::sync::Arc;
+use std::sync::atomic::AtomicBool;
+use std::sync::atomic::Ordering::SeqCst;
+use std::{env, fmt, io};
+
+use rustc_data_structures::flock;
+use rustc_data_structures::fx::{FxHashMap, FxIndexSet};
+use rustc_data_structures::jobserver::{self, Client};
+use rustc_data_structures::profiling::{SelfProfiler, SelfProfilerRef};
+use rustc_data_structures::sync::{
+    AtomicU64, DynSend, DynSync, Lock, Lrc, MappedReadGuard, ReadGuard, RwLock,
+};
+use rustc_errors::annotate_snippet_emitter_writer::AnnotateSnippetEmitter;
+use rustc_errors::codes::*;
+use rustc_errors::emitter::{
+    DynEmitter, HumanEmitter, HumanReadableErrorType, OutputTheme, stderr_destination,
+};
+use rustc_errors::json::JsonEmitter;
+use rustc_errors::registry::Registry;
+use rustc_errors::{
+    Diag, DiagCtxt, DiagCtxtHandle, DiagMessage, Diagnostic, ErrorGuaranteed, FatalAbort,
+    FluentBundle, LazyFallbackBundle, TerminalUrl, fallback_fluent_bundle,
+};
+use rustc_macros::HashStable_Generic;
+pub use rustc_span::def_id::StableCrateId;
+use rustc_span::edition::Edition;
+use rustc_span::source_map::{FilePathMapping, SourceMap};
+use rustc_span::{FileNameDisplayPreference, RealFileName, Span, Symbol};
+use rustc_target::asm::InlineAsmArch;
+use rustc_target::spec::{
+    CodeModel, DebuginfoKind, PanicStrategy, RelocModel, RelroLevel, SanitizerSet,
+    SmallDataThresholdSupport, SplitDebuginfo, StackProtector, SymbolVisibility, Target,
+    TargetTuple, TlsModel,
+};
+
+use crate::code_stats::CodeStats;
+pub use crate::code_stats::{DataTypeKind, FieldInfo, FieldKind, SizeKind, VariantInfo};
+use crate::config::{
+    self, CoverageLevel, CrateType, DebugInfo, ErrorOutputType, FunctionReturn, Input,
+    InstrumentCoverage, OptLevel, OutFileName, OutputType, RemapPathScopeComponents,
+    SwitchWithOptPath,
+};
+use crate::parse::{ParseSess, add_feature_diagnostics};
+use crate::search_paths::{PathKind, SearchPath};
+use crate::{errors, filesearch, lint};
+
+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<usize> 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<usize> for Limit {
+    type Output = Limit;
+
+    fn div(self, rhs: usize) -> Self::Output {
+        Limit::new(self.0 / rhs)
+    }
+}
+
+impl Mul<usize> for Limit {
+    type Output = Limit;
+
+    fn mul(self, rhs: usize) -> Self::Output {
+        Limit::new(self.0 * rhs)
+    }
+}
+
+impl rustc_errors::IntoDiagArg for Limit {
+    fn into_diag_arg(self) -> rustc_errors::DiagArgValue {
+        self.to_string().into_diag_arg()
+    }
+}
+
+#[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,
+}
+
+pub struct CompilerIO {
+    pub input: Input,
+    pub output_dir: Option<PathBuf>,
+    pub output_file: Option<OutFileName>,
+    pub temps_dir: Option<PathBuf>,
+}
+
+pub trait LintStoreMarker: Any + DynSync + DynSend {}
+
+/// 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<SearchPath>,
+    pub target_tlib_path: Lrc<SearchPath>,
+    pub psess: ParseSess,
+    pub sysroot: PathBuf,
+    /// Input, input file path and output file path to this compilation process.
+    pub io: CompilerIO,
+
+    incr_comp_session: RwLock<IncrCompSession>,
+
+    /// Used by `-Z self-profile`.
+    pub prof: SelfProfilerRef,
+
+    /// Data about code being compiled, gathered during compilation.
+    pub code_stats: CodeStats,
+
+    /// Tracks fuel info if `-zfuel=crate=n` is specified.
+    optimization_fuel: Lock<OptimizationFuel>,
+
+    /// 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,
+
+    /// This only ever stores a `LintStore` but we don't want a dependency on that type here.
+    pub lint_store: Option<Lrc<dyn LintStoreMarker>>,
+
+    /// Should be set if any lints are registered in `lint_store`.
+    pub registered_lints: bool,
+
+    /// Cap lint level specified by a driver specifically.
+    pub driver_lint_caps: FxHashMap<lint::LintId, lint::Level>,
+
+    /// 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<CtfeBacktrace>,
+
+    /// 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<Vec<(Span, Option<Symbol>)>>,
+
+    /// Architecture to use for interpreting asm!.
+    pub asm_arch: Option<InlineAsmArch>,
+
+    /// Set of enabled features for the current target.
+    pub target_features: FxIndexSet<Symbol>,
+
+    /// Set of enabled features for the current target, including unstable ones.
+    pub unstable_target_features: FxIndexSet<Symbol>,
+
+    /// The version of the rustc process, possibly including a commit hash and description.
+    pub cfg_version: &'static str,
+
+    /// The inner atomic value is set to true when a feature marked as `internal` is
+    /// enabled. Makes it so that "please report a bug" is hidden, as ICEs with
+    /// internal features are wontfix, and they are usually the cause of the ICEs.
+    /// None signifies that this is not tracked.
+    pub using_internal_features: Arc<AtomicBool>,
+
+    /// All commandline args used to invoke the compiler, with @file args fully expanded.
+    /// This will only be used within debug info, e.g. in the pdb file on windows
+    /// This is mainly useful for other tools that reads that debuginfo to figure out
+    /// how to call the compiler with the same arguments.
+    pub expanded_args: Vec<String>,
+}
+
+#[derive(PartialEq, Eq, PartialOrd, Ord)]
+pub enum MetadataKind {
+    None,
+    Uncompressed,
+    Compressed,
+}
+
+#[derive(Clone, Copy)]
+pub enum CodegenUnits {
+    /// Specified by the user. In this case we try fairly hard to produce the
+    /// number of CGUs requested.
+    User(usize),
+
+    /// A default value, i.e. not specified by the user. In this case we take
+    /// more liberties about CGU formation, e.g. avoid producing very small
+    /// CGUs.
+    Default(usize),
+}
+
+impl CodegenUnits {
+    pub fn as_usize(self) -> usize {
+        match self {
+            CodegenUnits::User(n) => n,
+            CodegenUnits::Default(n) => n,
+        }
+    }
+}
+
+impl Session {
+    pub fn miri_unleashed_feature(&self, span: Span, feature_gate: Option<Symbol>) {
+        self.miri_unleashed_features.lock().push((span, feature_gate));
+    }
+
+    pub fn local_crate_source_file(&self) -> Option<RealFileName> {
+        Some(self.source_map().path_mapping().to_real_filename(self.io.input.opt_path()?))
+    }
+
+    fn check_miri_unleashed_features(&self) -> Option<ErrorGuaranteed> {
+        let mut guar = None;
+        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.
+            self.dcx().emit_warn(errors::SkippingConstChecks {
+                unleashed_features: unleashed_features
+                    .iter()
+                    .map(|(span, gate)| {
+                        gate.map(|gate| {
+                            must_err = true;
+                            errors::UnleashedFeatureHelp::Named { span: *span, gate }
+                        })
+                        .unwrap_or(errors::UnleashedFeatureHelp::Unnamed { span: *span })
+                    })
+                    .collect(),
+            });
+
+            // If we should err, make sure we did.
+            if must_err && self.dcx().has_errors().is_none() {
+                // We have skipped a feature gate, and not run into other errors... reject.
+                guar = Some(self.dcx().emit_err(errors::NotCircumventFeature));
+            }
+        }
+        guar
+    }
+
+    /// Invoked all the way at the end to finish off diagnostics printing.
+    pub fn finish_diagnostics(&self, registry: &Registry) -> Option<ErrorGuaranteed> {
+        let mut guar = None;
+        guar = guar.or(self.check_miri_unleashed_features());
+        guar = guar.or(self.dcx().emit_stashed_diagnostics());
+        self.dcx().print_error_count(registry);
+        if self.opts.json_future_incompat {
+            self.dcx().emit_future_breakage_report();
+        }
+        guar
+    }
+
+    /// Returns true if the crate is a testing one.
+    pub fn is_test_crate(&self) -> bool {
+        self.opts.test
+    }
+
+    /// `feature` must be a language feature.
+    #[track_caller]
+    pub fn create_feature_err<'a>(&'a self, err: impl Diagnostic<'a>, feature: Symbol) -> Diag<'a> {
+        let mut err = self.dcx().create_err(err);
+        if err.code.is_none() {
+            #[allow(rustc::diagnostic_outside_of_impl)]
+            err.code(E0658);
+        }
+        add_feature_diagnostics(&mut err, self, feature);
+        err
+    }
+
+    /// Record the fact that we called `trimmed_def_paths`, and do some
+    /// checking about whether its cost was justified.
+    pub fn record_trimmed_def_paths(&self) {
+        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.dcx().set_must_produce_diag()
+    }
+
+    #[inline]
+    pub fn dcx(&self) -> DiagCtxtHandle<'_> {
+        self.psess.dcx()
+    }
+
+    #[inline]
+    pub fn source_map(&self) -> &SourceMap {
+        self.psess.source_map()
+    }
+
+    /// 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::No
+    }
+
+    pub fn instrument_coverage_branch(&self) -> bool {
+        self.instrument_coverage()
+            && self.opts.unstable_opts.coverage_options.level >= CoverageLevel::Branch
+    }
+
+    pub fn instrument_coverage_condition(&self) -> bool {
+        self.instrument_coverage()
+            && self.opts.unstable_opts.coverage_options.level >= CoverageLevel::Condition
+    }
+
+    pub fn instrument_coverage_mcdc(&self) -> bool {
+        self.instrument_coverage()
+            && self.opts.unstable_opts.coverage_options.level >= CoverageLevel::Mcdc
+    }
+
+    /// True if `-Zcoverage-options=no-mir-spans` was passed.
+    pub fn coverage_no_mir_spans(&self) -> bool {
+        self.opts.unstable_opts.coverage_options.no_mir_spans
+    }
+
+    pub fn is_sanitizer_cfi_enabled(&self) -> bool {
+        self.opts.unstable_opts.sanitizer.contains(SanitizerSet::CFI)
+    }
+
+    pub fn is_sanitizer_cfi_canonical_jump_tables_disabled(&self) -> bool {
+        self.opts.unstable_opts.sanitizer_cfi_canonical_jump_tables == Some(false)
+    }
+
+    pub fn is_sanitizer_cfi_canonical_jump_tables_enabled(&self) -> bool {
+        self.opts.unstable_opts.sanitizer_cfi_canonical_jump_tables == Some(true)
+    }
+
+    pub fn is_sanitizer_cfi_generalize_pointers_enabled(&self) -> bool {
+        self.opts.unstable_opts.sanitizer_cfi_generalize_pointers == Some(true)
+    }
+
+    pub fn is_sanitizer_cfi_normalize_integers_enabled(&self) -> bool {
+        self.opts.unstable_opts.sanitizer_cfi_normalize_integers == Some(true)
+    }
+
+    pub fn is_sanitizer_kcfi_enabled(&self) -> bool {
+        self.opts.unstable_opts.sanitizer.contains(SanitizerSet::KCFI)
+    }
+
+    pub fn is_split_lto_unit_enabled(&self) -> bool {
+        self.opts.unstable_opts.split_lto_unit == Some(true)
+    }
+
+    /// Check whether this compile session and crate type use static crt.
+    pub fn crt_static(&self, crate_type: Option<CrateType>) -> 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)
+        #[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)
+            )
+    }
+
+    /// Returns `true` if the target can use the current split debuginfo configuration.
+    pub fn target_can_use_split_dwarf(&self) -> bool {
+        self.target.debuginfo_kind == DebuginfoKind::Dwarf
+    }
+
+    pub fn generate_proc_macro_decls_symbol(&self, stable_crate_id: StableCrateId) -> String {
+        format!("__rustc_proc_macro_decls_{:08x}__", stable_crate_id.as_u64())
+    }
+
+    pub fn target_filesearch(&self, kind: PathKind) -> filesearch::FileSearch<'_> {
+        filesearch::FileSearch::new(&self.opts.search_paths, &self.target_tlib_path, kind)
+    }
+    pub fn host_filesearch(&self, kind: PathKind) -> filesearch::FileSearch<'_> {
+        filesearch::FileSearch::new(&self.opts.search_paths, &self.host_tlib_path, kind)
+    }
+
+    /// Returns a list of directories where target-specific tool binaries are located. Some fallback
+    /// directories are also returned, for example if `--sysroot` is used but tools are missing
+    /// (#125246): we also add the bin directories to the sysroot where rustc is located.
+    pub fn get_tools_search_paths(&self, self_contained: bool) -> Vec<PathBuf> {
+        let bin_path = filesearch::make_target_bin_path(&self.sysroot, config::host_tuple());
+        let fallback_sysroot_paths = filesearch::sysroot_candidates()
+            .into_iter()
+            // Ignore sysroot candidate if it was the same as the sysroot path we just used.
+            .filter(|sysroot| *sysroot != self.sysroot)
+            .map(|sysroot| filesearch::make_target_bin_path(&sysroot, config::host_tuple()));
+        let search_paths = std::iter::once(bin_path).chain(fallback_sysroot_paths);
+
+        if self_contained {
+            // The self-contained tools are expected to be e.g. in `bin/self-contained` in the
+            // sysroot's `rustlib` path, so we add such a subfolder to the bin path, and the
+            // fallback paths.
+            search_paths.flat_map(|path| [path.clone(), path.join("self-contained")]).collect()
+        } else {
+            search_paths.collect()
+        }
+    }
+
+    pub fn init_incr_comp_session(&self, session_dir: PathBuf, lock_file: flock::Lock) {
+        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: lock_file };
+    }
+
+    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) -> MappedReadGuard<'_, PathBuf> {
+        let incr_comp_session = self.incr_comp_session.borrow();
+        ReadGuard::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<MappedReadGuard<'_, PathBuf>> {
+        self.opts.incremental.as_ref().map(|_| self.incr_comp_session_dir())
+    }
+
+    /// 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(
+        &self,
+        get_crate_name: impl Fn() -> Symbol,
+        msg: impl Fn() -> String,
+    ) -> bool {
+        let mut ret = true;
+        if let Some((ref c, _)) = self.opts.unstable_opts.fuel {
+            if c == get_crate_name().as_str() {
+                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.dcx().can_emit_warnings() {
+                        // We only call `msg` in case we can actually emit warnings.
+                        // Otherwise, this could cause a `must_produce_diag` ICE
+                        // (issue #79546).
+                        self.dcx().emit_warn(errors::OptimisationFuelExhausted { msg: 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 == get_crate_name().as_str() {
+                assert_eq!(self.threads(), 1);
+                self.print_fuel.fetch_add(1, SeqCst);
+            }
+        }
+        ret
+    }
+
+    /// Is this edition 2015?
+    pub fn is_rust_2015(&self) -> bool {
+        self.edition().is_rust_2015()
+    }
+
+    /// Are we allowed to use features from the Rust 2018 edition?
+    pub fn at_least_rust_2018(&self) -> bool {
+        self.edition().at_least_rust_2018()
+    }
+
+    /// Are we allowed to use features from the Rust 2021 edition?
+    pub fn at_least_rust_2021(&self) -> bool {
+        self.edition().at_least_rust_2021()
+    }
+
+    /// Are we allowed to use features from the Rust 2024 edition?
+    pub fn at_least_rust_2024(&self) -> bool {
+        self.edition().at_least_rust_2024()
+    }
+
+    /// Returns `true` if we should use the PLT for shared library calls.
+    pub fn needs_plt(&self) -> bool {
+        // Check if the current target usually wants PLT to be enabled.
+        // The user can use the command line flag to override it.
+        let want_plt = self.target.plt_by_default;
+
+        let dbg_opts = &self.opts.unstable_opts;
+
+        let relro_level = self.opts.cg.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 use it unless the target doesn't want it by default or the full relro forces it on.
+        dbg_opts.plt.unwrap_or(want_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 and KernelAddressSanitizer 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::KERNELADDRESS | SanitizerSet::MEMORY | SanitizerSet::HWADDRESS)
+    }
+
+    pub fn diagnostic_width(&self) -> usize {
+        let default_column_width = 140;
+        if let Some(width) = self.opts.diagnostic_width {
+            width
+        } else if self.opts.unstable_opts.ui_testing {
+            default_column_width
+        } else {
+            termize::dimensions().map_or(default_column_width, |(w, _)| w)
+        }
+    }
+
+    /// Returns the default symbol visibility.
+    pub fn default_visibility(&self) -> SymbolVisibility {
+        self.opts
+            .unstable_opts
+            .default_visibility
+            .or(self.target.options.default_visibility)
+            .unwrap_or(SymbolVisibility::Interposable)
+    }
+}
+
+// JUSTIFICATION: defn of the suggested wrapper fns
+#[allow(rustc::bad_opt_access)]
+impl Session {
+    pub fn verbose_internals(&self) -> bool {
+        self.opts.unstable_opts.verbose_internals
+    }
+
+    pub fn print_llvm_stats(&self) -> bool {
+        self.opts.unstable_opts.print_codegen_stats
+    }
+
+    pub fn verify_llvm_ir(&self) -> bool {
+        self.opts.unstable_opts.verify_llvm_ir || option_env!("RUSTC_VERIFY_LLVM_IR").is_some()
+    }
+
+    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 => {
+                // The user explicitly asked for ThinLTO
+                return 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_local_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().as_usize() == 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 ub_checks(&self) -> bool {
+        self.opts.unstable_opts.ub_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<CodeModel> {
+        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 direct_access_external_data(&self) -> Option<bool> {
+        self.opts
+            .unstable_opts
+            .direct_access_external_data
+            .or(self.target.direct_access_external_data)
+    }
+
+    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=<yes/no>`
+        // 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
+    #[inline]
+    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) -> CodegenUnits {
+        if let Some(n) = self.opts.cli_forced_codegen_units {
+            return CodegenUnits::User(n);
+        }
+        if let Some(n) = self.target.default_codegen_units {
+            return CodegenUnits::Default(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 CodegenUnits::Default(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.
+        CodegenUnits::Default(16)
+    }
+
+    pub fn teach(&self, code: ErrCode) -> bool {
+        self.opts.unstable_opts.teach && self.dcx().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)
+    }
+
+    pub fn filename_display_preference(
+        &self,
+        scope: RemapPathScopeComponents,
+    ) -> FileNameDisplayPreference {
+        assert!(
+            scope.bits().count_ones() == 1,
+            "one and only one scope should be passed to `Session::filename_display_preference`"
+        );
+        if self.opts.unstable_opts.remap_path_scope.contains(scope) {
+            FileNameDisplayPreference::Remapped
+        } else {
+            FileNameDisplayPreference::Local
+        }
+    }
+}
+
+// JUSTIFICATION: part of session construction
+#[allow(rustc::bad_opt_access)]
+fn default_emitter(
+    sopts: &config::Options,
+    registry: rustc_errors::registry::Registry,
+    source_map: Lrc<SourceMap>,
+    bundle: Option<Lrc<FluentBundle>>,
+    fallback_bundle: LazyFallbackBundle,
+) -> Box<DynEmitter> {
+    let macro_backtrace = sopts.unstable_opts.macro_backtrace;
+    let track_diagnostics = sopts.unstable_opts.track_diagnostics;
+    let terminal_url = match sopts.unstable_opts.terminal_urls {
+        TerminalUrl::Auto => {
+            match (std::env::var("COLORTERM").as_deref(), std::env::var("TERM").as_deref()) {
+                (Ok("truecolor"), Ok("xterm-256color"))
+                    if sopts.unstable_features.is_nightly_build() =>
+                {
+                    TerminalUrl::Yes
+                }
+                _ => TerminalUrl::No,
+            }
+        }
+        t => t,
+    };
+    match sopts.error_format {
+        config::ErrorOutputType::HumanReadable(kind, color_config) => {
+            let short = kind.short();
+
+            if let HumanReadableErrorType::AnnotateSnippet = kind {
+                let emitter = AnnotateSnippetEmitter::new(
+                    Some(source_map),
+                    bundle,
+                    fallback_bundle,
+                    short,
+                    macro_backtrace,
+                );
+                Box::new(emitter.ui_testing(sopts.unstable_opts.ui_testing))
+            } else {
+                let emitter = HumanEmitter::new(stderr_destination(color_config), fallback_bundle)
+                    .fluent_bundle(bundle)
+                    .sm(Some(source_map))
+                    .short_message(short)
+                    .teach(sopts.unstable_opts.teach)
+                    .diagnostic_width(sopts.diagnostic_width)
+                    .macro_backtrace(macro_backtrace)
+                    .track_diagnostics(track_diagnostics)
+                    .terminal_url(terminal_url)
+                    .theme(if let HumanReadableErrorType::Unicode = kind {
+                        OutputTheme::Unicode
+                    } else {
+                        OutputTheme::Ascii
+                    })
+                    .ignored_directories_in_source_blocks(
+                        sopts.unstable_opts.ignore_directory_in_diagnostics_source_blocks.clone(),
+                    );
+                Box::new(emitter.ui_testing(sopts.unstable_opts.ui_testing))
+            }
+        }
+        config::ErrorOutputType::Json { pretty, json_rendered, color_config } => Box::new(
+            JsonEmitter::new(
+                Box::new(io::BufWriter::new(io::stderr())),
+                source_map,
+                fallback_bundle,
+                pretty,
+                json_rendered,
+                color_config,
+            )
+            .registry(Some(registry))
+            .fluent_bundle(bundle)
+            .ui_testing(sopts.unstable_opts.ui_testing)
+            .ignored_directories_in_source_blocks(
+                sopts.unstable_opts.ignore_directory_in_diagnostics_source_blocks.clone(),
+            )
+            .diagnostic_width(sopts.diagnostic_width)
+            .macro_backtrace(macro_backtrace)
+            .track_diagnostics(track_diagnostics)
+            .terminal_url(terminal_url),
+        ),
+    }
+}
+
+// JUSTIFICATION: literally session construction
+#[allow(rustc::bad_opt_access)]
+#[allow(rustc::untranslatable_diagnostic)] // FIXME: make this translatable
+pub fn build_session(
+    early_dcx: EarlyDiagCtxt,
+    sopts: config::Options,
+    io: CompilerIO,
+    bundle: Option<Lrc<rustc_errors::FluentBundle>>,
+    registry: rustc_errors::registry::Registry,
+    fluent_resources: Vec<&'static str>,
+    driver_lint_caps: FxHashMap<lint::LintId, lint::Level>,
+    target: Target,
+    sysroot: PathBuf,
+    cfg_version: &'static str,
+    ice_file: Option<PathBuf>,
+    using_internal_features: Arc<AtomicBool>,
+    expanded_args: Vec<String>,
+) -> 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()
+        .rfind(|&(key, _)| *key == "warnings")
+        .is_some_and(|&(_, level)| level == lint::Allow);
+    let cap_lints_allow = sopts.lint_cap.is_some_and(|cap| cap == lint::Allow);
+    let can_emit_warnings = !(warnings_allow || cap_lints_allow);
+
+    let host_triple = TargetTuple::from_tuple(config::host_tuple());
+    let (host, target_warnings) = Target::search(&host_triple, &sysroot).unwrap_or_else(|e| {
+        early_dcx.early_fatal(format!("Error loading host specification: {e}"))
+    });
+    for warning in target_warnings.warning_messages() {
+        early_dcx.early_warn(warning)
+    }
+
+    let fallback_bundle = fallback_fluent_bundle(
+        fluent_resources,
+        sopts.unstable_opts.translate_directionality_markers,
+    );
+    let source_map = rustc_span::source_map::get_source_map().unwrap();
+    let emitter =
+        default_emitter(&sopts, registry, Lrc::clone(&source_map), bundle, fallback_bundle);
+
+    let mut dcx =
+        DiagCtxt::new(emitter).with_flags(sopts.unstable_opts.dcx_flags(can_emit_warnings));
+    if let Some(ice_file) = ice_file {
+        dcx = dcx.with_ice_file(ice_file);
+    }
+
+    // Now that the proper handler has been constructed, drop early_dcx to
+    // prevent accidental use.
+    drop(early_dcx);
+
+    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_deref(),
+            &sopts.unstable_opts.self_profile_counter,
+        );
+        match profiler {
+            Ok(profiler) => Some(Arc::new(profiler)),
+            Err(e) => {
+                dcx.handle().emit_warn(errors::FailedToCreateProfiler { err: e.to_string() });
+                None
+            }
+        }
+    } else {
+        None
+    };
+
+    let mut psess = ParseSess::with_dcx(dcx, source_map);
+    psess.assume_incomplete_release = sopts.unstable_opts.assume_incomplete_release;
+
+    let host_triple = config::host_tuple();
+    let target_triple = sopts.target_triple.tuple();
+    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.
+        Lrc::clone(&host_tlib_path)
+    } else {
+        Lrc::new(SearchPath::from_sysroot_and_triple(&sysroot, target_triple))
+    };
+
+    let optimization_fuel = Lock::new(OptimizationFuel {
+        remaining: sopts.unstable_opts.fuel.as_ref().map_or(0, |&(_, i)| i),
+        out_of_fuel: false,
+    });
+    let print_fuel = AtomicU64::new(0);
+
+    let prof = SelfProfilerRef::new(
+        self_profiler,
+        sopts.unstable_opts.time_passes.then(|| sopts.unstable_opts.time_passes_format),
+    );
+
+    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.allow_asm { InlineAsmArch::from_str(&target.arch).ok() } else { None };
+
+    let sess = Session {
+        target,
+        host,
+        opts: sopts,
+        host_tlib_path,
+        target_tlib_path,
+        psess,
+        sysroot,
+        io,
+        incr_comp_session: RwLock::new(IncrCompSession::NotInitialized),
+        prof,
+        code_stats: Default::default(),
+        optimization_fuel,
+        print_fuel,
+        jobserver: jobserver::client(),
+        lint_store: None,
+        registered_lints: false,
+        driver_lint_caps,
+        ctfe_backtrace,
+        miri_unleashed_features: Lock::new(Default::default()),
+        asm_arch,
+        target_features: Default::default(),
+        unstable_target_features: Default::default(),
+        cfg_version,
+        using_internal_features,
+        expanded_args,
+    };
+
+    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
+#[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.dcx().emit_err(errors::LinkerPluginToWindowsNotSupported);
+    }
+
+    // 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.dcx().emit_err(errors::ProfileUseFileDoesNotExist { path });
+        }
+    }
+
+    // Do the same for sample profile data.
+    if let Some(ref path) = sess.opts.unstable_opts.profile_sample_use {
+        if !path.exists() {
+            sess.dcx().emit_err(errors::ProfileSampleUseFileDoesNotExist { path });
+        }
+    }
+
+    // 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.dcx().emit_err(errors::TargetRequiresUnwindTables);
+        }
+    }
+
+    // Sanitizers can only be used on platforms that we know have working sanitizer codegen.
+    let supported_sanitizers = sess.target.options.supported_sanitizers;
+    let mut unsupported_sanitizers = sess.opts.unstable_opts.sanitizer - supported_sanitizers;
+    // Niche: if `fixed-x18`, or effectively switching on `reserved-x18` flag, is enabled
+    // we should allow Shadow Call Stack sanitizer.
+    if sess.opts.unstable_opts.fixed_x18 && sess.target.arch == "aarch64" {
+        unsupported_sanitizers -= SanitizerSet::SHADOWCALLSTACK;
+    }
+    match unsupported_sanitizers.into_iter().count() {
+        0 => {}
+        1 => {
+            sess.dcx()
+                .emit_err(errors::SanitizerNotSupported { us: unsupported_sanitizers.to_string() });
+        }
+        _ => {
+            sess.dcx().emit_err(errors::SanitizersNotSupported {
+                us: unsupported_sanitizers.to_string(),
+            });
+        }
+    }
+
+    // Cannot mix and match mutually-exclusive sanitizers.
+    if let Some((first, second)) = sess.opts.unstable_opts.sanitizer.mutually_exclusive() {
+        sess.dcx().emit_err(errors::CannotMixAndMatchSanitizers {
+            first: first.to_string(),
+            second: second.to_string(),
+        });
+    }
+
+    // Cannot enable crt-static with sanitizers on Linux
+    if sess.crt_static(None)
+        && !sess.opts.unstable_opts.sanitizer.is_empty()
+        && !sess.target.is_like_msvc
+    {
+        sess.dcx().emit_err(errors::CannotEnableCrtStaticLinux);
+    }
+
+    // LLVM CFI requires LTO.
+    if sess.is_sanitizer_cfi_enabled()
+        && !(sess.lto() == config::Lto::Fat || sess.opts.cg.linker_plugin_lto.enabled())
+    {
+        sess.dcx().emit_err(errors::SanitizerCfiRequiresLto);
+    }
+
+    // KCFI requires panic=abort
+    if sess.is_sanitizer_kcfi_enabled() && sess.panic_strategy() != PanicStrategy::Abort {
+        sess.dcx().emit_err(errors::SanitizerKcfiRequiresPanicAbort);
+    }
+
+    // LLVM CFI using rustc LTO requires a single codegen unit.
+    if sess.is_sanitizer_cfi_enabled()
+        && sess.lto() == config::Lto::Fat
+        && (sess.codegen_units().as_usize() != 1)
+    {
+        sess.dcx().emit_err(errors::SanitizerCfiRequiresSingleCodegenUnit);
+    }
+
+    // Canonical jump tables requires CFI.
+    if sess.is_sanitizer_cfi_canonical_jump_tables_disabled() {
+        if !sess.is_sanitizer_cfi_enabled() {
+            sess.dcx().emit_err(errors::SanitizerCfiCanonicalJumpTablesRequiresCfi);
+        }
+    }
+
+    // LLVM CFI pointer generalization requires CFI or KCFI.
+    if sess.is_sanitizer_cfi_generalize_pointers_enabled() {
+        if !(sess.is_sanitizer_cfi_enabled() || sess.is_sanitizer_kcfi_enabled()) {
+            sess.dcx().emit_err(errors::SanitizerCfiGeneralizePointersRequiresCfi);
+        }
+    }
+
+    // LLVM CFI integer normalization requires CFI or KCFI.
+    if sess.is_sanitizer_cfi_normalize_integers_enabled() {
+        if !(sess.is_sanitizer_cfi_enabled() || sess.is_sanitizer_kcfi_enabled()) {
+            sess.dcx().emit_err(errors::SanitizerCfiNormalizeIntegersRequiresCfi);
+        }
+    }
+
+    // LTO unit splitting requires LTO.
+    if sess.is_split_lto_unit_enabled()
+        && !(sess.lto() == config::Lto::Fat
+            || sess.lto() == config::Lto::Thin
+            || sess.opts.cg.linker_plugin_lto.enabled())
+    {
+        sess.dcx().emit_err(errors::SplitLtoUnitRequiresLto);
+    }
+
+    // VFE requires LTO.
+    if sess.lto() != config::Lto::Fat {
+        if sess.opts.unstable_opts.virtual_function_elimination {
+            sess.dcx().emit_err(errors::UnstableVirtualFunctionElimination);
+        }
+    }
+
+    if sess.opts.unstable_opts.stack_protector != StackProtector::None {
+        if !sess.target.options.supports_stack_protector {
+            sess.dcx().emit_warn(errors::StackProtectorNotSupportedForTarget {
+                stack_protector: sess.opts.unstable_opts.stack_protector,
+                target_triple: &sess.opts.target_triple,
+            });
+        }
+    }
+
+    if sess.opts.unstable_opts.small_data_threshold.is_some() {
+        if sess.target.small_data_threshold_support() == SmallDataThresholdSupport::None {
+            sess.dcx().emit_warn(errors::SmallDataThresholdNotSupportedForTarget {
+                target_triple: &sess.opts.target_triple,
+            })
+        }
+    }
+
+    if sess.opts.unstable_opts.branch_protection.is_some() && sess.target.arch != "aarch64" {
+        sess.dcx().emit_err(errors::BranchProtectionRequiresAArch64);
+    }
+
+    if let Some(dwarf_version) = sess.opts.unstable_opts.dwarf_version {
+        if dwarf_version > 5 {
+            sess.dcx().emit_err(errors::UnsupportedDwarfVersion { dwarf_version });
+        }
+    }
+
+    if !sess.target.options.supported_split_debuginfo.contains(&sess.split_debuginfo())
+        && !sess.opts.unstable_opts.unstable_options
+    {
+        sess.dcx()
+            .emit_err(errors::SplitDebugInfoUnstablePlatform { debuginfo: sess.split_debuginfo() });
+    }
+
+    if sess.opts.unstable_opts.embed_source {
+        let dwarf_version =
+            sess.opts.unstable_opts.dwarf_version.unwrap_or(sess.target.default_dwarf_version);
+
+        if dwarf_version < 5 {
+            sess.dcx().emit_warn(errors::EmbedSourceInsufficientDwarfVersion { dwarf_version });
+        }
+
+        if sess.opts.debuginfo == DebugInfo::None {
+            sess.dcx().emit_warn(errors::EmbedSourceRequiresDebugInfo);
+        }
+    }
+
+    if sess.opts.unstable_opts.instrument_xray.is_some() && !sess.target.options.supports_xray {
+        sess.dcx().emit_err(errors::InstrumentationNotSupported { us: "XRay".to_string() });
+    }
+
+    if let Some(flavor) = sess.opts.cg.linker_flavor {
+        if let Some(compatible_list) = sess.target.linker_flavor.check_compatibility(flavor) {
+            let flavor = flavor.desc();
+            sess.dcx().emit_err(errors::IncompatibleLinkerFlavor { flavor, compatible_list });
+        }
+    }
+
+    if sess.opts.unstable_opts.function_return != FunctionReturn::default() {
+        if sess.target.arch != "x86" && sess.target.arch != "x86_64" {
+            sess.dcx().emit_err(errors::FunctionReturnRequiresX86OrX8664);
+        }
+    }
+
+    if let Some(regparm) = sess.opts.unstable_opts.regparm {
+        if regparm > 3 {
+            sess.dcx().emit_err(errors::UnsupportedRegparm { regparm });
+        }
+        if sess.target.arch != "x86" {
+            sess.dcx().emit_err(errors::UnsupportedRegparmArch);
+        }
+    }
+
+    // The code model check applies to `thunk` and `thunk-extern`, but not `thunk-inline`, so it is
+    // kept as a `match` to force a change if new ones are added, even if we currently only support
+    // `thunk-extern` like Clang.
+    match sess.opts.unstable_opts.function_return {
+        FunctionReturn::Keep => (),
+        FunctionReturn::ThunkExtern => {
+            // FIXME: In principle, the inherited base LLVM target code model could be large,
+            // but this only checks whether we were passed one explicitly (like Clang does).
+            if let Some(code_model) = sess.code_model()
+                && code_model == CodeModel::Large
+            {
+                sess.dcx().emit_err(errors::FunctionReturnThunkExternRequiresNonLargeCodeModel);
+            }
+        }
+    }
+
+    if sess.opts.cg.soft_float {
+        if sess.target.arch == "arm" && sess.target.abi == "eabihf" {
+            sess.dcx().emit_warn(errors::SoftFloatDeprecated);
+        } else {
+            // All `use_softfp` does is the equivalent of `-mfloat-abi` in GCC/clang, which only exists on ARM targets.
+            // We document this flag to only affect `*eabihf` targets, so let's show a warning for all other targets.
+            sess.dcx().emit_warn(errors::SoftFloatIgnored);
+        }
+    }
+}
+
+/// Holds data on the current incremental compilation session, if there is one.
+#[derive(Debug)]
+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. `_lock_file` is never directly used, but its presence
+    /// alone has an effect, because the file will unlock when the session is
+    /// dropped.
+    Active { session_directory: PathBuf, _lock_file: flock::Lock },
+    /// 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 },
+}
+
+/// A wrapper around an [`DiagCtxt`] that is used for early error emissions.
+pub struct EarlyDiagCtxt {
+    dcx: DiagCtxt,
+}
+
+impl EarlyDiagCtxt {
+    pub fn new(output: ErrorOutputType) -> Self {
+        let emitter = mk_emitter(output);
+        Self { dcx: DiagCtxt::new(emitter) }
+    }
+
+    /// Swap out the underlying dcx once we acquire the user's preference on error emission
+    /// format. Any errors prior to that will cause an abort and all stashed diagnostics of the
+    /// previous dcx will be emitted.
+    pub fn abort_if_error_and_set_error_format(&mut self, output: ErrorOutputType) {
+        self.dcx.handle().abort_if_errors();
+
+        let emitter = mk_emitter(output);
+        self.dcx = DiagCtxt::new(emitter);
+    }
+
+    #[allow(rustc::untranslatable_diagnostic)]
+    #[allow(rustc::diagnostic_outside_of_impl)]
+    pub fn early_note(&self, msg: impl Into<DiagMessage>) {
+        self.dcx.handle().note(msg)
+    }
+
+    #[allow(rustc::untranslatable_diagnostic)]
+    #[allow(rustc::diagnostic_outside_of_impl)]
+    pub fn early_help(&self, msg: impl Into<DiagMessage>) {
+        self.dcx.handle().struct_help(msg).emit()
+    }
+
+    #[allow(rustc::untranslatable_diagnostic)]
+    #[allow(rustc::diagnostic_outside_of_impl)]
+    #[must_use = "ErrorGuaranteed must be returned from `run_compiler` in order to exit with a non-zero status code"]
+    pub fn early_err(&self, msg: impl Into<DiagMessage>) -> ErrorGuaranteed {
+        self.dcx.handle().err(msg)
+    }
+
+    #[allow(rustc::untranslatable_diagnostic)]
+    #[allow(rustc::diagnostic_outside_of_impl)]
+    pub fn early_fatal(&self, msg: impl Into<DiagMessage>) -> ! {
+        self.dcx.handle().fatal(msg)
+    }
+
+    #[allow(rustc::untranslatable_diagnostic)]
+    #[allow(rustc::diagnostic_outside_of_impl)]
+    pub fn early_struct_fatal(&self, msg: impl Into<DiagMessage>) -> Diag<'_, FatalAbort> {
+        self.dcx.handle().struct_fatal(msg)
+    }
+
+    #[allow(rustc::untranslatable_diagnostic)]
+    #[allow(rustc::diagnostic_outside_of_impl)]
+    pub fn early_warn(&self, msg: impl Into<DiagMessage>) {
+        self.dcx.handle().warn(msg)
+    }
+
+    #[allow(rustc::untranslatable_diagnostic)]
+    #[allow(rustc::diagnostic_outside_of_impl)]
+    pub fn early_struct_warn(&self, msg: impl Into<DiagMessage>) -> Diag<'_, ()> {
+        self.dcx.handle().struct_warn(msg)
+    }
+}
+
+fn mk_emitter(output: ErrorOutputType) -> Box<DynEmitter> {
+    // FIXME(#100717): early errors aren't translated at the moment, so this is fine, but it will
+    // need to reference every crate that might emit an early error for translation to work.
+    let fallback_bundle =
+        fallback_fluent_bundle(vec![rustc_errors::DEFAULT_LOCALE_RESOURCE], false);
+    let emitter: Box<DynEmitter> = match output {
+        config::ErrorOutputType::HumanReadable(kind, color_config) => {
+            let short = kind.short();
+            Box::new(
+                HumanEmitter::new(stderr_destination(color_config), fallback_bundle)
+                    .theme(if let HumanReadableErrorType::Unicode = kind {
+                        OutputTheme::Unicode
+                    } else {
+                        OutputTheme::Ascii
+                    })
+                    .short_message(short),
+            )
+        }
+        config::ErrorOutputType::Json { pretty, json_rendered, color_config } => {
+            Box::new(JsonEmitter::new(
+                Box::new(io::BufWriter::new(io::stderr())),
+                Lrc::new(SourceMap::new(FilePathMapping::empty())),
+                fallback_bundle,
+                pretty,
+                json_rendered,
+                color_config,
+            ))
+        }
+    };
+    emitter
+}
+
+pub trait RemapFileNameExt {
+    type Output<'a>
+    where
+        Self: 'a;
+
+    /// Returns a possibly remapped filename based on the passed scope and remap cli options.
+    ///
+    /// One and only one scope should be passed to this method, it will panic otherwise.
+    fn for_scope(&self, sess: &Session, scope: RemapPathScopeComponents) -> Self::Output<'_>;
+}
+
+impl RemapFileNameExt for rustc_span::FileName {
+    type Output<'a> = rustc_span::FileNameDisplay<'a>;
+
+    fn for_scope(&self, sess: &Session, scope: RemapPathScopeComponents) -> Self::Output<'_> {
+        assert!(
+            scope.bits().count_ones() == 1,
+            "one and only one scope should be passed to for_scope"
+        );
+        if sess.opts.unstable_opts.remap_path_scope.contains(scope) {
+            self.prefer_remapped_unconditionaly()
+        } else {
+            self.prefer_local()
+        }
+    }
+}
+
+impl RemapFileNameExt for rustc_span::RealFileName {
+    type Output<'a> = &'a Path;
+
+    fn for_scope(&self, sess: &Session, scope: RemapPathScopeComponents) -> Self::Output<'_> {
+        assert!(
+            scope.bits().count_ones() == 1,
+            "one and only one scope should be passed to for_scope"
+        );
+        if sess.opts.unstable_opts.remap_path_scope.contains(scope) {
+            self.remapped_path_if_available()
+        } else {
+            self.local_path_if_available()
+        }
+    }
+}
diff --git a/compiler/rustc_session/src/utils.rs b/compiler/rustc_session/src/utils.rs
new file mode 100644
index 00000000000..9182789cf02
--- /dev/null
+++ b/compiler/rustc_session/src/utils.rs
@@ -0,0 +1,179 @@
+use std::path::{Path, PathBuf};
+use std::sync::OnceLock;
+
+use rustc_data_structures::profiling::VerboseTimingGuard;
+use rustc_fs_util::try_canonicalize;
+use rustc_macros::{Decodable, Encodable, HashStable_Generic};
+
+use crate::session::Session;
+
+impl Session {
+    pub fn timer(&self, what: &'static str) -> VerboseTimingGuard<'_> {
+        self.prof.verbose_generic_activity(what)
+    }
+    /// Used by `-Z self-profile`.
+    pub fn time<R>(&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<bool>,
+        /// Whether to link static library without throwing any object files away
+        whole_archive: Option<bool>,
+    },
+    /// 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<bool>,
+    },
+    /// 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<bool>,
+    },
+    /// Argument which is passed to linker, relative order with libraries and other arguments
+    /// is preserved
+    LinkArg,
+
+    /// Module imported from WebAssembly
+    WasmImportModule,
+
+    /// 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
+            | NativeLibKind::WasmImportModule => false,
+        }
+    }
+
+    pub fn is_statically_included(&self) -> bool {
+        matches!(self, NativeLibKind::Static { .. })
+    }
+
+    pub fn is_dllimport(&self) -> bool {
+        matches!(
+            self,
+            NativeLibKind::Dylib { .. } | NativeLibKind::RawDylib | NativeLibKind::Unspecified
+        )
+    }
+}
+
+#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Encodable, Decodable)]
+#[derive(HashStable_Generic)]
+pub struct NativeLib {
+    pub name: String,
+    pub new_name: Option<String>,
+    pub kind: NativeLibKind,
+    pub verbatim: Option<bool>,
+}
+
+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<PathBuf>,
+    original: PathBuf,
+}
+
+impl CanonicalizedPath {
+    pub fn new(path: &Path) -> Self {
+        Self { original: path.to_owned(), canonicalized: try_canonicalize(path).ok() }
+    }
+
+    pub fn canonicalized(&self) -> &PathBuf {
+        self.canonicalized.as_ref().unwrap_or(self.original())
+    }
+
+    pub fn original(&self) -> &PathBuf {
+        &self.original
+    }
+}
+
+/// Gets a list of extra command-line flags provided by the user, as strings.
+///
+/// This function is used during ICEs to show more information useful for
+/// debugging, since some ICEs only happens with non-default compiler flags
+/// (and the users don't always report them).
+pub fn extra_compiler_flags() -> Option<(Vec<String>, bool)> {
+    const ICE_REPORT_COMPILER_FLAGS: &[&str] = &["-Z", "-C", "--crate-type"];
+
+    const ICE_REPORT_COMPILER_FLAGS_EXCLUDE: &[&str] = &["metadata", "extra-filename"];
+
+    const ICE_REPORT_COMPILER_FLAGS_STRIP_VALUE: &[&str] = &["incremental"];
+
+    let mut args = std::env::args_os().map(|arg| arg.to_string_lossy().to_string());
+
+    let mut result = Vec::new();
+    let mut excluded_cargo_defaults = false;
+    while let Some(arg) = args.next() {
+        if let Some(a) = ICE_REPORT_COMPILER_FLAGS.iter().find(|a| arg.starts_with(*a)) {
+            let content = if arg.len() == a.len() {
+                // A space-separated option, like `-C incremental=foo` or `--crate-type rlib`
+                match args.next() {
+                    Some(arg) => arg,
+                    None => continue,
+                }
+            } else if arg.get(a.len()..a.len() + 1) == Some("=") {
+                // An equals option, like `--crate-type=rlib`
+                arg[a.len() + 1..].to_string()
+            } else {
+                // A non-space option, like `-Cincremental=foo`
+                arg[a.len()..].to_string()
+            };
+            let option = content.split_once('=').map(|s| s.0).unwrap_or(&content);
+            if ICE_REPORT_COMPILER_FLAGS_EXCLUDE.iter().any(|exc| option == *exc) {
+                excluded_cargo_defaults = true;
+            } else {
+                result.push(a.to_string());
+                match ICE_REPORT_COMPILER_FLAGS_STRIP_VALUE.iter().find(|s| option == **s) {
+                    Some(s) => result.push(format!("{s}=[REDACTED]")),
+                    None => result.push(content),
+                }
+            }
+        }
+    }
+
+    if !result.is_empty() { Some((result, excluded_cargo_defaults)) } else { None }
+}
+
+/// Returns whenever rustc was launched by Cargo as opposed to another build system.
+///
+/// To be used in diagnostics to avoid printing Cargo specific suggestions to other
+/// build systems (like Bazel, Buck2, Makefile, ...).
+pub fn was_invoked_from_cargo() -> bool {
+    static FROM_CARGO: OnceLock<bool> = OnceLock::new();
+
+    // To be able to detect Cargo, we use the simplest and least intrusive
+    // way: we check whenever the `CARGO_CRATE_NAME` env is set.
+    //
+    // Note that it is common in Makefiles to define the `CARGO` env even
+    // though we may not have been called by Cargo, so we avoid using it.
+    *FROM_CARGO.get_or_init(|| std::env::var_os("CARGO_CRATE_NAME").is_some())
+}
diff --git a/compiler/rustc_session/src/version.rs b/compiler/rustc_session/src/version.rs
new file mode 100644
index 00000000000..1696eaf902b
--- /dev/null
+++ b/compiler/rustc_session/src/version.rs
@@ -0,0 +1,29 @@
+use std::borrow::Cow;
+use std::fmt::{self, Display};
+
+use rustc_errors::IntoDiagArg;
+use rustc_macros::{Decodable, Encodable, HashStable_Generic, current_rustc_version};
+
+#[derive(Encodable, Decodable, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
+#[derive(HashStable_Generic)]
+pub struct RustcVersion {
+    pub major: u16,
+    pub minor: u16,
+    pub patch: u16,
+}
+
+impl RustcVersion {
+    pub const CURRENT: Self = current_rustc_version!();
+}
+
+impl Display for RustcVersion {
+    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(formatter, "{}.{}.{}", self.major, self.minor, self.patch)
+    }
+}
+
+impl IntoDiagArg for RustcVersion {
+    fn into_diag_arg(self) -> rustc_errors::DiagArgValue {
+        rustc_errors::DiagArgValue::Str(Cow::Owned(self.to_string()))
+    }
+}