about summary refs log tree commit diff
path: root/compiler/rustc_session
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_session')
-rw-r--r--compiler/rustc_session/Cargo.toml20
-rw-r--r--compiler/rustc_session/src/cgu_reuse_tracker.rs120
-rw-r--r--compiler/rustc_session/src/code_stats.rs193
-rw-r--r--compiler/rustc_session/src/config.rs2186
-rw-r--r--compiler/rustc_session/src/filesearch.rs171
-rw-r--r--compiler/rustc_session/src/lib.rs27
-rw-r--r--compiler/rustc_session/src/lint.rs425
-rw-r--r--compiler/rustc_session/src/lint/builtin.rs624
-rw-r--r--compiler/rustc_session/src/options.rs1078
-rw-r--r--compiler/rustc_session/src/output.rs217
-rw-r--r--compiler/rustc_session/src/parse.rs239
-rw-r--r--compiler/rustc_session/src/search_paths.rs95
-rw-r--r--compiler/rustc_session/src/session.rs1539
-rw-r--r--compiler/rustc_session/src/utils.rs32
14 files changed, 6966 insertions, 0 deletions
diff --git a/compiler/rustc_session/Cargo.toml b/compiler/rustc_session/Cargo.toml
new file mode 100644
index 00000000000..cdff1662fdb
--- /dev/null
+++ b/compiler/rustc_session/Cargo.toml
@@ -0,0 +1,20 @@
+[package]
+authors = ["The Rust Project Developers"]
+name = "rustc_session"
+version = "0.0.0"
+edition = "2018"
+
+[dependencies]
+bitflags = "1.2.1"
+getopts = "0.2"
+rustc_macros = { path = "../rustc_macros" }
+tracing = "0.1"
+rustc_errors = { path = "../rustc_errors" }
+rustc_feature = { path = "../rustc_feature" }
+rustc_target = { path = "../rustc_target" }
+rustc_serialize = { path = "../rustc_serialize" }
+rustc_data_structures = { path = "../rustc_data_structures" }
+rustc_span = { path = "../rustc_span" }
+rustc_fs_util = { path = "../rustc_fs_util" }
+num_cpus = "1.0"
+rustc_ast = { path = "../rustc_ast" }
diff --git a/compiler/rustc_session/src/cgu_reuse_tracker.rs b/compiler/rustc_session/src/cgu_reuse_tracker.rs
new file mode 100644
index 00000000000..0eec12aa03f
--- /dev/null
+++ b/compiler/rustc_session/src/cgu_reuse_tracker.rs
@@ -0,0 +1,120 @@
+//! Some facilities for tracking how codegen-units are reused during incremental
+//! compilation. This is used for incremental compilation tests and debug
+//! output.
+
+use rustc_data_structures::fx::FxHashMap;
+use rustc_span::{Span, Symbol};
+use std::sync::{Arc, Mutex};
+use tracing::debug;
+
+#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
+pub enum CguReuse {
+    No,
+    PreLto,
+    PostLto,
+}
+
+#[derive(Copy, Clone, Debug, PartialEq)]
+pub enum ComparisonKind {
+    Exact,
+    AtLeast,
+}
+
+struct TrackerData {
+    actual_reuse: FxHashMap<String, CguReuse>,
+    expected_reuse: FxHashMap<String, (String, SendSpan, CguReuse, ComparisonKind)>,
+}
+
+// Span does not implement `Send`, so we can't just store it in the shared
+// `TrackerData` object. Instead of splitting up `TrackerData` into shared and
+// non-shared parts (which would be complicated), we just mark the `Span` here
+// explicitly as `Send`. That's safe because the span data here is only ever
+// accessed from the main thread.
+struct SendSpan(Span);
+unsafe impl Send for SendSpan {}
+
+#[derive(Clone)]
+pub struct CguReuseTracker {
+    data: Option<Arc<Mutex<TrackerData>>>,
+}
+
+impl CguReuseTracker {
+    pub fn new() -> CguReuseTracker {
+        let data =
+            TrackerData { actual_reuse: Default::default(), expected_reuse: Default::default() };
+
+        CguReuseTracker { data: Some(Arc::new(Mutex::new(data))) }
+    }
+
+    pub fn new_disabled() -> CguReuseTracker {
+        CguReuseTracker { data: None }
+    }
+
+    pub fn set_actual_reuse(&self, cgu_name: &str, kind: CguReuse) {
+        if let Some(ref data) = self.data {
+            debug!("set_actual_reuse({:?}, {:?})", cgu_name, kind);
+
+            let prev_reuse = data.lock().unwrap().actual_reuse.insert(cgu_name.to_string(), kind);
+
+            if let Some(prev_reuse) = prev_reuse {
+                // The only time it is legal to overwrite reuse state is when
+                // we discover during ThinLTO that we can actually reuse the
+                // post-LTO version of a CGU.
+                assert_eq!(prev_reuse, CguReuse::PreLto);
+            }
+        }
+    }
+
+    pub fn set_expectation(
+        &self,
+        cgu_name: Symbol,
+        cgu_user_name: &str,
+        error_span: Span,
+        expected_reuse: CguReuse,
+        comparison_kind: ComparisonKind,
+    ) {
+        if let Some(ref data) = self.data {
+            debug!("set_expectation({:?}, {:?}, {:?})", cgu_name, expected_reuse, comparison_kind);
+            let mut data = data.lock().unwrap();
+
+            data.expected_reuse.insert(
+                cgu_name.to_string(),
+                (cgu_user_name.to_string(), SendSpan(error_span), expected_reuse, comparison_kind),
+            );
+        }
+    }
+
+    pub fn check_expected_reuse(&self, diag: &rustc_errors::Handler) {
+        if let Some(ref data) = self.data {
+            let data = data.lock().unwrap();
+
+            for (cgu_name, &(ref cgu_user_name, ref error_span, expected_reuse, comparison_kind)) in
+                &data.expected_reuse
+            {
+                if let Some(&actual_reuse) = data.actual_reuse.get(cgu_name) {
+                    let (error, at_least) = match comparison_kind {
+                        ComparisonKind::Exact => (expected_reuse != actual_reuse, false),
+                        ComparisonKind::AtLeast => (actual_reuse < expected_reuse, true),
+                    };
+
+                    if error {
+                        let at_least = if at_least { "at least " } else { "" };
+                        let msg = format!(
+                            "CGU-reuse for `{}` is `{:?}` but \
+                                           should be {}`{:?}`",
+                            cgu_user_name, actual_reuse, at_least, expected_reuse
+                        );
+                        diag.span_err(error_span.0, &msg);
+                    }
+                } else {
+                    let msg = format!(
+                        "CGU-reuse for `{}` (mangled: `{}`) was \
+                                       not recorded",
+                        cgu_user_name, cgu_name
+                    );
+                    diag.span_fatal(error_span.0, &msg).raise();
+                }
+            }
+        }
+    }
+}
diff --git a/compiler/rustc_session/src/code_stats.rs b/compiler/rustc_session/src/code_stats.rs
new file mode 100644
index 00000000000..c263da69c35
--- /dev/null
+++ b/compiler/rustc_session/src/code_stats.rs
@@ -0,0 +1,193 @@
+use rustc_data_structures::fx::FxHashSet;
+use rustc_data_structures::sync::Lock;
+use rustc_target::abi::{Align, Size};
+use std::cmp::{self, Ordering};
+
+#[derive(Clone, PartialEq, Eq, Hash, Debug)]
+pub struct VariantInfo {
+    pub name: Option<String>,
+    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(Clone, PartialEq, Eq, Hash, Debug)]
+pub struct FieldInfo {
+    pub name: String,
+    pub offset: u64,
+    pub size: u64,
+    pub align: u64,
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub enum DataTypeKind {
+    Struct,
+    Union,
+    Enum,
+    Closure,
+}
+
+#[derive(PartialEq, Eq, Hash, Debug)]
+pub struct TypeSizeInfo {
+    pub kind: DataTypeKind,
+    pub type_description: String,
+    pub align: u64,
+    pub overall_size: u64,
+    pub packed: bool,
+    pub opt_discr_size: Option<u64>,
+    pub variants: Vec<VariantInfo>,
+}
+
+#[derive(Default)]
+pub struct CodeStats {
+    type_sizes: Lock<FxHashSet<TypeSizeInfo>>,
+}
+
+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.
+        variants.sort_by(|info1, info2| info2.size.cmp(&info1.size));
+        let info = TypeSizeInfo {
+            kind,
+            type_description: type_desc.to_string(),
+            align: align.bytes(),
+            overall_size: overall_size.bytes(),
+            packed,
+            opt_discr_size: opt_discr_size.map(|s| s.bytes()),
+            variants,
+        };
+        self.type_sizes.borrow_mut().insert(info);
+    }
+
+    pub fn print_type_sizes(&self) {
+        let type_sizes = self.type_sizes.borrow();
+        let mut sorted: Vec<_> = type_sizes.iter().collect();
+
+        // Primary sort: large-to-small.
+        // Secondary sort: description (dictionary order)
+        sorted.sort_by(|info1, info2| {
+            // (reversing cmp order to get large-to-small ordering)
+            match info2.overall_size.cmp(&info1.overall_size) {
+                Ordering::Equal => info1.type_description.cmp(&info2.type_description),
+                other => other,
+            }
+        });
+
+        for info in &sorted {
+            println!(
+                "print-type-size type: `{}`: {} bytes, alignment: {} bytes",
+                info.type_description, info.overall_size, info.align
+            );
+            let indent = "    ";
+
+            let discr_size = if let Some(discr_size) = info.opt_discr_size {
+                println!("print-type-size {}discriminant: {} bytes", indent, discr_size);
+                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 info.kind {
+                DataTypeKind::Struct | DataTypeKind::Closure => true,
+                DataTypeKind::Enum | DataTypeKind::Union => false,
+            };
+            for (i, variant_info) in info.variants.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_owned(),
+                        None => i.to_string(),
+                    };
+                    println!(
+                        "print-type-size {}variant `{}`: {} bytes",
+                        indent,
+                        name,
+                        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.iter() {
+                    let FieldInfo { ref name, offset, size, align } = *field;
+
+                    if offset > min_offset {
+                        let pad = offset - min_offset;
+                        println!("print-type-size {}padding: {} bytes", indent, pad);
+                    }
+
+                    if offset < min_offset {
+                        // If this happens it's probably a union.
+                        println!(
+                            "print-type-size {}field `.{}`: {} bytes, \
+                                  offset: {} bytes, \
+                                  alignment: {} bytes",
+                            indent, name, size, offset, align
+                        );
+                    } else if info.packed || offset == min_offset {
+                        println!("print-type-size {}field `.{}`: {} bytes", indent, name, size);
+                    } else {
+                        // Include field alignment in output only if it caused padding injection
+                        println!(
+                            "print-type-size {}field `.{}`: {} bytes, \
+                                  alignment: {} bytes",
+                            indent, name, size, align
+                        );
+                    }
+
+                    min_offset = offset + size;
+                }
+            }
+
+            assert!(
+                max_variant_size <= info.overall_size,
+                "max_variant_size {} !<= {} overall_size",
+                max_variant_size,
+                info.overall_size
+            );
+            if max_variant_size < info.overall_size {
+                println!(
+                    "print-type-size {}end padding: {} bytes",
+                    indent,
+                    info.overall_size - max_variant_size
+                );
+            }
+        }
+    }
+}
diff --git a/compiler/rustc_session/src/config.rs b/compiler/rustc_session/src/config.rs
new file mode 100644
index 00000000000..1808a0ca59b
--- /dev/null
+++ b/compiler/rustc_session/src/config.rs
@@ -0,0 +1,2186 @@
+//! Contains infrastructure for configuring the compiler, including parsing
+//! command-line options.
+
+pub use crate::options::*;
+
+use crate::lint;
+use crate::search_paths::SearchPath;
+use crate::utils::NativeLibKind;
+use crate::{early_error, early_warn, Session};
+
+use rustc_data_structures::fx::FxHashSet;
+use rustc_data_structures::impl_stable_hash_via_hash;
+use rustc_data_structures::stable_hasher::{HashStable, StableHasher};
+
+use rustc_target::spec::{Target, TargetTriple};
+
+use crate::parse::CrateConfig;
+use rustc_feature::UnstableFeatures;
+use rustc_span::edition::{Edition, DEFAULT_EDITION, EDITION_NAME_LIST};
+use rustc_span::source_map::{FileName, FilePathMapping};
+use rustc_span::symbol::{sym, Symbol};
+use rustc_span::SourceFileHashAlgorithm;
+
+use rustc_errors::emitter::HumanReadableErrorType;
+use rustc_errors::{ColorConfig, HandlerFlags};
+
+use std::collections::btree_map::{
+    Iter as BTreeMapIter, Keys as BTreeMapKeysIter, Values as BTreeMapValuesIter,
+};
+use std::collections::{BTreeMap, BTreeSet};
+use std::fmt;
+use std::iter::{self, FromIterator};
+use std::path::{Path, PathBuf};
+use std::str::{self, FromStr};
+
+pub struct Config {
+    pub target: Target,
+    pub ptr_width: u32,
+}
+
+bitflags! {
+    #[derive(Default, Encodable, Decodable)]
+    pub struct SanitizerSet: u8 {
+        const ADDRESS = 1 << 0;
+        const LEAK    = 1 << 1;
+        const MEMORY  = 1 << 2;
+        const THREAD  = 1 << 3;
+    }
+}
+
+/// Formats a sanitizer set as a comma separated list of sanitizers' names.
+impl fmt::Display for SanitizerSet {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let mut first = true;
+        for s in *self {
+            let name = match s {
+                SanitizerSet::ADDRESS => "address",
+                SanitizerSet::LEAK => "leak",
+                SanitizerSet::MEMORY => "memory",
+                SanitizerSet::THREAD => "thread",
+                _ => panic!("unrecognized sanitizer {:?}", s),
+            };
+            if !first {
+                f.write_str(",")?;
+            }
+            f.write_str(name)?;
+            first = false;
+        }
+        Ok(())
+    }
+}
+
+impl IntoIterator for SanitizerSet {
+    type Item = SanitizerSet;
+    type IntoIter = std::vec::IntoIter<SanitizerSet>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        [SanitizerSet::ADDRESS, SanitizerSet::LEAK, SanitizerSet::MEMORY, SanitizerSet::THREAD]
+            .iter()
+            .copied()
+            .filter(|&s| self.contains(s))
+            .collect::<Vec<_>>()
+            .into_iter()
+    }
+}
+
+impl<CTX> HashStable<CTX> for SanitizerSet {
+    fn hash_stable(&self, ctx: &mut CTX, hasher: &mut StableHasher) {
+        self.bits().hash_stable(ctx, hasher);
+    }
+}
+
+/// The different settings that the `-Z 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,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Hash)]
+pub enum OptLevel {
+    No,         // -O0
+    Less,       // -O1
+    Default,    // -O2
+    Aggressive, // -O3
+    Size,       // -Os
+    SizeMin,    // -Oz
+}
+
+impl_stable_hash_via_hash!(OptLevel);
+
+/// This is what the `LtoCli` values get mapped to after resolving defaults and
+/// and taking other command line options into account.
+#[derive(Clone, PartialEq)]
+pub enum Lto {
+    /// Don't do any LTO whatsoever
+    No,
+
+    /// Do a full crate graph LTO with ThinLTO
+    Thin,
+
+    /// Do a local graph LTO with ThinLTO (only relevant for multiple codegen
+    /// units).
+    ThinLocal,
+
+    /// Do a full crate graph 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,
+}
+
+#[derive(Clone, PartialEq, Hash)]
+pub enum LinkerPluginLto {
+    LinkerPlugin(PathBuf),
+    LinkerPluginAuto,
+    Disabled,
+}
+
+impl LinkerPluginLto {
+    pub fn enabled(&self) -> bool {
+        match *self {
+            LinkerPluginLto::LinkerPlugin(_) | LinkerPluginLto::LinkerPluginAuto => true,
+            LinkerPluginLto::Disabled => false,
+        }
+    }
+}
+
+#[derive(Clone, PartialEq, Hash)]
+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)]
+#[derive(Encodable, Decodable)]
+pub enum SymbolManglingVersion {
+    Legacy,
+    V0,
+}
+
+impl_stable_hash_via_hash!(SymbolManglingVersion);
+
+#[derive(Clone, Copy, PartialEq, Hash)]
+pub enum DebugInfo {
+    None,
+    Limited,
+    Full,
+}
+
+#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
+#[derive(Encodable, Decodable)]
+pub enum OutputType {
+    Bitcode,
+    Assembly,
+    LlvmAssembly,
+    Mir,
+    Metadata,
+    Object,
+    Exe,
+    DepInfo,
+}
+
+impl_stable_hash_via_hash!(OutputType);
+
+impl OutputType {
+    fn is_compatible_with_codegen_units_and_single_output_file(&self) -> bool {
+        match *self {
+            OutputType::Exe | OutputType::DepInfo | OutputType::Metadata => true,
+            OutputType::Bitcode
+            | OutputType::Assembly
+            | OutputType::LlvmAssembly
+            | OutputType::Mir
+            | OutputType::Object => false,
+        }
+    }
+
+    fn shorthand(&self) -> &'static str {
+        match *self {
+            OutputType::Bitcode => "llvm-bc",
+            OutputType::Assembly => "asm",
+            OutputType::LlvmAssembly => "llvm-ir",
+            OutputType::Mir => "mir",
+            OutputType::Object => "obj",
+            OutputType::Metadata => "metadata",
+            OutputType::Exe => "link",
+            OutputType::DepInfo => "dep-info",
+        }
+    }
+
+    fn from_shorthand(shorthand: &str) -> Option<Self> {
+        Some(match shorthand {
+            "asm" => OutputType::Assembly,
+            "llvm-ir" => OutputType::LlvmAssembly,
+            "mir" => OutputType::Mir,
+            "llvm-bc" => OutputType::Bitcode,
+            "obj" => OutputType::Object,
+            "metadata" => OutputType::Metadata,
+            "link" => OutputType::Exe,
+            "dep-info" => OutputType::DepInfo,
+            _ => return None,
+        })
+    }
+
+    fn shorthands_display() -> String {
+        format!(
+            "`{}`, `{}`, `{}`, `{}`, `{}`, `{}`, `{}`, `{}`",
+            OutputType::Bitcode.shorthand(),
+            OutputType::Assembly.shorthand(),
+            OutputType::LlvmAssembly.shorthand(),
+            OutputType::Mir.shorthand(),
+            OutputType::Object.shorthand(),
+            OutputType::Metadata.shorthand(),
+            OutputType::Exe.shorthand(),
+            OutputType::DepInfo.shorthand(),
+        )
+    }
+
+    pub fn extension(&self) -> &'static str {
+        match *self {
+            OutputType::Bitcode => "bc",
+            OutputType::Assembly => "s",
+            OutputType::LlvmAssembly => "ll",
+            OutputType::Mir => "mir",
+            OutputType::Object => "o",
+            OutputType::Metadata => "rmeta",
+            OutputType::DepInfo => "d",
+            OutputType::Exe => "",
+        }
+    }
+}
+
+/// The type of diagnostics output to generate.
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum ErrorOutputType {
+    /// Output meant for the consumption of humans.
+    HumanReadable(HumanReadableErrorType),
+    /// Output that's consumed by other tools such as `rustfix` or the `RLS`.
+    Json {
+        /// Render the JSON in a human readable way (with indents and newlines).
+        pretty: bool,
+        /// The JSON output includes a `rendered` field that includes the rendered
+        /// human output.
+        json_rendered: HumanReadableErrorType,
+    },
+}
+
+impl Default for ErrorOutputType {
+    fn default() -> Self {
+        Self::HumanReadable(HumanReadableErrorType::Default(ColorConfig::Auto))
+    }
+}
+
+/// 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.
+#[derive(Clone, Hash)]
+pub struct OutputTypes(BTreeMap<OutputType, Option<PathBuf>>);
+
+impl_stable_hash_via_hash!(OutputTypes);
+
+impl OutputTypes {
+    pub fn new(entries: &[(OutputType, Option<PathBuf>)]) -> OutputTypes {
+        OutputTypes(BTreeMap::from_iter(entries.iter().map(|&(k, ref v)| (k, v.clone()))))
+    }
+
+    pub fn get(&self, key: &OutputType) -> Option<&Option<PathBuf>> {
+        self.0.get(key)
+    }
+
+    pub fn contains_key(&self, key: &OutputType) -> bool {
+        self.0.contains_key(key)
+    }
+
+    pub fn keys(&self) -> BTreeMapKeysIter<'_, OutputType, Option<PathBuf>> {
+        self.0.keys()
+    }
+
+    pub fn values(&self) -> BTreeMapValuesIter<'_, OutputType, Option<PathBuf>> {
+        self.0.values()
+    }
+
+    pub fn len(&self) -> usize {
+        self.0.len()
+    }
+
+    // Returns `true` if any of the output types require codegen or linking.
+    pub fn should_codegen(&self) -> bool {
+        self.0.keys().any(|k| match *k {
+            OutputType::Bitcode
+            | OutputType::Assembly
+            | OutputType::LlvmAssembly
+            | OutputType::Mir
+            | OutputType::Object
+            | OutputType::Exe => true,
+            OutputType::Metadata | OutputType::DepInfo => false,
+        })
+    }
+}
+
+/// 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,
+}
+
+#[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<String>),
+}
+
+impl Externs {
+    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 }
+    }
+
+    pub fn files(&self) -> Option<impl Iterator<Item = &String>> {
+        match &self.location {
+            ExternLocation::ExactPaths(set) => Some(set.iter()),
+            _ => None,
+        }
+    }
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum PrintRequest {
+    FileNames,
+    Sysroot,
+    TargetLibdir,
+    CrateName,
+    Cfg,
+    TargetList,
+    TargetCPUs,
+    TargetFeatures,
+    RelocationModels,
+    CodeModels,
+    TlsModels,
+    TargetSpec,
+    NativeStaticLibs,
+}
+
+#[derive(Copy, Clone)]
+pub enum BorrowckMode {
+    Mir,
+    Migrate,
+}
+
+impl BorrowckMode {
+    /// Returns whether we should run the MIR-based borrow check, but also fall back
+    /// on the AST borrow check if the MIR-based one errors.
+    pub fn migrate(self) -> bool {
+        match self {
+            BorrowckMode::Mir => false,
+            BorrowckMode::Migrate => true,
+        }
+    }
+}
+
+pub enum Input {
+    /// Load source code from a file.
+    File(PathBuf),
+    /// Load source code from a string.
+    Str {
+        /// A string that is shown in place of a filename.
+        name: FileName,
+        /// An anonymous string containing the source code.
+        input: String,
+    },
+}
+
+impl Input {
+    pub fn filestem(&self) -> &str {
+        match *self {
+            Input::File(ref ifile) => ifile.file_stem().unwrap().to_str().unwrap(),
+            Input::Str { .. } => "rust_out",
+        }
+    }
+
+    pub fn get_input(&mut self) -> Option<&mut String> {
+        match *self {
+            Input::File(_) => None,
+            Input::Str { ref mut input, .. } => Some(input),
+        }
+    }
+
+    pub fn source_name(&self) -> FileName {
+        match *self {
+            Input::File(ref ifile) => ifile.clone().into(),
+            Input::Str { ref name, .. } => name.clone(),
+        }
+    }
+}
+
+#[derive(Clone, Hash)]
+pub struct OutputFilenames {
+    pub out_directory: PathBuf,
+    filestem: String,
+    pub single_output_file: Option<PathBuf>,
+    pub outputs: OutputTypes,
+}
+
+impl_stable_hash_via_hash!(OutputFilenames);
+
+pub const RLINK_EXT: &str = "rlink";
+pub const RUST_CGU_EXT: &str = "rcgu";
+
+impl OutputFilenames {
+    pub fn new(
+        out_directory: PathBuf,
+        out_filestem: String,
+        single_output_file: Option<PathBuf>,
+        extra: String,
+        outputs: OutputTypes,
+    ) -> Self {
+        OutputFilenames {
+            out_directory,
+            single_output_file,
+            outputs,
+            filestem: format!("{}{}", out_filestem, extra),
+        }
+    }
+
+    pub fn path(&self, flavor: OutputType) -> PathBuf {
+        self.outputs
+            .get(&flavor)
+            .and_then(|p| p.to_owned())
+            .or_else(|| self.single_output_file.clone())
+            .unwrap_or_else(|| self.temp_path(flavor, None))
+    }
+
+    /// 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 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_str(".");
+                extension.push_str(RUST_CGU_EXT);
+                extension.push_str(".");
+            }
+
+            extension.push_str(ext);
+        }
+
+        self.with_extension(&extension)
+    }
+
+    pub fn with_extension(&self, extension: &str) -> PathBuf {
+        let mut path = self.out_directory.join(&self.filestem);
+        path.set_extension(extension);
+        path
+    }
+}
+
+pub fn host_triple() -> &'static str {
+    // Get the host triple out of the build environment. This ensures that our
+    // idea of the host triple is the same as for the set of libraries we've
+    // actually built.  We can't just take LLVM's host triple because they
+    // normalize all ix86 architectures to i386.
+    //
+    // Instead of grabbing the host triple (for the current host), we grab (at
+    // compile time) the target triple that this rustc is built with and
+    // calling that (at runtime) the host triple.
+    (option_env!("CFG_COMPILER_HOST_TRIPLE")).expect("CFG_COMPILER_HOST_TRIPLE")
+}
+
+impl Default for Options {
+    fn default() -> Options {
+        Options {
+            crate_types: Vec::new(),
+            optimize: OptLevel::No,
+            debuginfo: DebugInfo::None,
+            lint_opts: Vec::new(),
+            lint_cap: None,
+            describe_lints: false,
+            output_types: OutputTypes(BTreeMap::new()),
+            search_paths: vec![],
+            maybe_sysroot: None,
+            target_triple: TargetTriple::from_triple(host_triple()),
+            test: false,
+            incremental: None,
+            debugging_opts: basic_debugging_options(),
+            prints: Vec::new(),
+            borrowck_mode: BorrowckMode::Migrate,
+            cg: basic_codegen_options(),
+            error_format: ErrorOutputType::default(),
+            externs: Externs(BTreeMap::new()),
+            crate_name: None,
+            alt_std_name: None,
+            libs: Vec::new(),
+            unstable_features: UnstableFeatures::Disallow,
+            debug_assertions: true,
+            actually_rustdoc: false,
+            cli_forced_codegen_units: None,
+            cli_forced_thinlto_off: false,
+            remap_path_prefix: Vec::new(),
+            edition: DEFAULT_EDITION,
+            json_artifact_notifications: false,
+            pretty: None,
+        }
+    }
+}
+
+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.debugging_opts.dump_dep_graph
+            || self.debugging_opts.query_dep_graph
+    }
+
+    #[inline(always)]
+    pub fn enable_dep_node_debug_strs(&self) -> bool {
+        cfg!(debug_assertions)
+            && (self.debugging_opts.query_dep_graph || self.debugging_opts.incremental_info)
+    }
+
+    pub fn file_path_mapping(&self) -> FilePathMapping {
+        FilePathMapping::new(self.remap_path_prefix.clone())
+    }
+
+    /// Returns `true` if there will be an output file generated.
+    pub fn will_create_output_file(&self) -> bool {
+        !self.debugging_opts.parse_only && // The file is just being parsed
+            !self.debugging_opts.ls // The file is just being queried
+    }
+
+    #[inline]
+    pub fn share_generics(&self) -> bool {
+        match self.debugging_opts.share_generics {
+            Some(setting) => setting,
+            None => match self.optimize {
+                OptLevel::No | OptLevel::Less | OptLevel::Size | OptLevel::SizeMin => true,
+                OptLevel::Default | OptLevel::Aggressive => false,
+            },
+        }
+    }
+}
+
+impl DebuggingOptions {
+    pub fn diagnostic_handler_flags(&self, can_emit_warnings: bool) -> HandlerFlags {
+        HandlerFlags {
+            can_emit_warnings,
+            treat_err_as_bug: self.treat_err_as_bug,
+            dont_buffer_diagnostics: self.dont_buffer_diagnostics,
+            report_delayed_bugs: self.report_delayed_bugs,
+            macro_backtrace: self.macro_backtrace,
+            deduplicate_diagnostics: self.deduplicate_diagnostics,
+        }
+    }
+}
+
+// The type of entry function, so users can have their own entry functions
+#[derive(Copy, Clone, PartialEq, Hash, Debug)]
+pub enum EntryFnType {
+    Main,
+    Start,
+}
+
+impl_stable_hash_via_hash!(EntryFnType);
+
+#[derive(Copy, PartialEq, PartialOrd, Clone, Ord, Eq, Hash, Debug, Encodable, Decodable)]
+pub enum CrateType {
+    Executable,
+    Dylib,
+    Rlib,
+    Staticlib,
+    Cdylib,
+    ProcMacro,
+}
+
+impl_stable_hash_via_hash!(CrateType);
+
+#[derive(Clone, Hash)]
+pub enum Passes {
+    Some(Vec<String>),
+    All,
+}
+
+impl Passes {
+    pub fn is_empty(&self) -> bool {
+        match *self {
+            Passes::Some(ref v) => v.is_empty(),
+            Passes::All => false,
+        }
+    }
+}
+
+pub const fn default_lib_output() -> CrateType {
+    CrateType::Rlib
+}
+
+pub fn default_configuration(sess: &Session) -> CrateConfig {
+    let end = &sess.target.target.target_endian;
+    let arch = &sess.target.target.arch;
+    let wordsz = &sess.target.target.target_pointer_width;
+    let os = &sess.target.target.target_os;
+    let env = &sess.target.target.target_env;
+    let vendor = &sess.target.target.target_vendor;
+    let min_atomic_width = sess.target.target.min_atomic_width();
+    let max_atomic_width = sess.target.target.max_atomic_width();
+    let atomic_cas = sess.target.target.options.atomic_cas;
+
+    let mut ret = FxHashSet::default();
+    ret.reserve(6); // the minimum number of insertions
+    // Target bindings.
+    ret.insert((sym::target_os, Some(Symbol::intern(os))));
+    if let Some(ref fam) = sess.target.target.options.target_family {
+        ret.insert((sym::target_family, Some(Symbol::intern(fam))));
+        if fam == "windows" {
+            ret.insert((sym::windows, None));
+        } else if fam == "unix" {
+            ret.insert((sym::unix, None));
+        }
+    }
+    ret.insert((sym::target_arch, Some(Symbol::intern(arch))));
+    ret.insert((sym::target_endian, Some(Symbol::intern(end))));
+    ret.insert((sym::target_pointer_width, Some(Symbol::intern(wordsz))));
+    ret.insert((sym::target_env, Some(Symbol::intern(env))));
+    ret.insert((sym::target_vendor, Some(Symbol::intern(vendor))));
+    if sess.target.target.options.has_elf_tls {
+        ret.insert((sym::target_thread_local, None));
+    }
+    for &i in &[8, 16, 32, 64, 128] {
+        if i >= min_atomic_width && i <= max_atomic_width {
+            let mut insert_atomic = |s| {
+                ret.insert((sym::target_has_atomic_load_store, Some(Symbol::intern(s))));
+                if atomic_cas {
+                    ret.insert((sym::target_has_atomic, Some(Symbol::intern(s))));
+                }
+            };
+            let s = i.to_string();
+            insert_atomic(&s);
+            if &s == wordsz {
+                insert_atomic("ptr");
+            }
+        }
+    }
+
+    for s in sess.opts.debugging_opts.sanitizer {
+        let symbol = Symbol::intern(&s.to_string());
+        ret.insert((sym::sanitize, Some(symbol)));
+    }
+
+    if sess.opts.debug_assertions {
+        ret.insert((sym::debug_assertions, None));
+    }
+    if sess.opts.crate_types.contains(&CrateType::ProcMacro) {
+        ret.insert((sym::proc_macro, None));
+    }
+    ret
+}
+
+/// Converts the crate `cfg!` configuration from `String` to `Symbol`.
+/// `rustc_interface::interface::Config` accepts this in the compiler configuration,
+/// but the symbol interner is not yet set up then, so we must convert it later.
+pub fn to_crate_config(cfg: FxHashSet<(String, Option<String>)>) -> CrateConfig {
+    cfg.into_iter().map(|(a, b)| (Symbol::intern(&a), b.map(|b| Symbol::intern(&b)))).collect()
+}
+
+pub fn build_configuration(sess: &Session, mut user_cfg: CrateConfig) -> CrateConfig {
+    // Combine the configuration requested by the session (command line) with
+    // some default and generated configuration items.
+    let default_cfg = default_configuration(sess);
+    // If the user wants a test runner, then add the test cfg.
+    if sess.opts.test {
+        user_cfg.insert((sym::test, None));
+    }
+    user_cfg.extend(default_cfg.iter().cloned());
+    user_cfg
+}
+
+pub fn build_target_config(opts: &Options, error_format: ErrorOutputType) -> Config {
+    let target = Target::search(&opts.target_triple).unwrap_or_else(|e| {
+        early_error(
+            error_format,
+            &format!(
+                "Error loading target specification: {}. \
+            Use `--print target-list` for a list of built-in targets",
+                e
+            ),
+        )
+    });
+
+    let ptr_width = match &target.target_pointer_width[..] {
+        "16" => 16,
+        "32" => 32,
+        "64" => 64,
+        w => early_error(
+            error_format,
+            &format!(
+                "target specification was invalid: \
+             unrecognized target-pointer-width {}",
+                w
+            ),
+        ),
+    };
+
+    Config { target, ptr_width }
+}
+
+#[derive(Copy, Clone, PartialEq, Eq, Debug)]
+pub enum OptionStability {
+    Stable,
+    Unstable,
+}
+
+pub struct RustcOptGroup {
+    pub apply: Box<dyn Fn(&mut getopts::Options) -> &mut getopts::Options>,
+    pub name: &'static str,
+    pub stability: OptionStability,
+}
+
+impl RustcOptGroup {
+    pub fn is_stable(&self) -> bool {
+        self.stability == OptionStability::Stable
+    }
+
+    pub fn stable<F>(name: &'static str, f: F) -> RustcOptGroup
+    where
+        F: Fn(&mut getopts::Options) -> &mut getopts::Options + 'static,
+    {
+        RustcOptGroup { name, apply: Box::new(f), stability: OptionStability::Stable }
+    }
+
+    pub fn unstable<F>(name: &'static str, f: F) -> RustcOptGroup
+    where
+        F: Fn(&mut getopts::Options) -> &mut getopts::Options + 'static,
+    {
+        RustcOptGroup { name, apply: Box::new(f), stability: OptionStability::Unstable }
+    }
+}
+
+// The `opt` local module holds wrappers around the `getopts` API that
+// adds extra rustc-specific metadata to each option; such metadata
+// is exposed by .  The public
+// functions below ending with `_u` are the functions that return
+// *unstable* options, i.e., options that are only enabled when the
+// user also passes the `-Z unstable-options` debugging flag.
+mod opt {
+    // The `fn flag*` etc below are written so that we can use them
+    // in the future; do not warn about them not being used right now.
+    #![allow(dead_code)]
+
+    use super::RustcOptGroup;
+
+    pub type R = RustcOptGroup;
+    pub type S = &'static str;
+
+    fn stable<F>(name: S, f: F) -> R
+    where
+        F: Fn(&mut getopts::Options) -> &mut getopts::Options + 'static,
+    {
+        RustcOptGroup::stable(name, f)
+    }
+
+    fn unstable<F>(name: S, f: F) -> R
+    where
+        F: Fn(&mut getopts::Options) -> &mut getopts::Options + 'static,
+    {
+        RustcOptGroup::unstable(name, f)
+    }
+
+    fn longer(a: S, b: S) -> S {
+        if a.len() > b.len() { a } else { b }
+    }
+
+    pub fn opt_s(a: S, b: S, c: S, d: S) -> R {
+        stable(longer(a, b), move |opts| opts.optopt(a, b, c, d))
+    }
+    pub fn multi_s(a: S, b: S, c: S, d: S) -> R {
+        stable(longer(a, b), move |opts| opts.optmulti(a, b, c, d))
+    }
+    pub fn flag_s(a: S, b: S, c: S) -> R {
+        stable(longer(a, b), move |opts| opts.optflag(a, b, c))
+    }
+    pub fn flagopt_s(a: S, b: S, c: S, d: S) -> R {
+        stable(longer(a, b), move |opts| opts.optflagopt(a, b, c, d))
+    }
+    pub fn flagmulti_s(a: S, b: S, c: S) -> R {
+        stable(longer(a, b), move |opts| opts.optflagmulti(a, b, c))
+    }
+
+    pub fn opt(a: S, b: S, c: S, d: S) -> R {
+        unstable(longer(a, b), move |opts| opts.optopt(a, b, c, d))
+    }
+    pub fn multi(a: S, b: S, c: S, d: S) -> R {
+        unstable(longer(a, b), move |opts| opts.optmulti(a, b, c, d))
+    }
+    pub fn flag(a: S, b: S, c: S) -> R {
+        unstable(longer(a, b), move |opts| opts.optflag(a, b, c))
+    }
+    pub fn flagopt(a: S, b: S, c: S, d: S) -> R {
+        unstable(longer(a, b), move |opts| opts.optflagopt(a, b, c, d))
+    }
+    pub fn flagmulti(a: S, b: S, c: S) -> R {
+        unstable(longer(a, b), move |opts| opts.optflagmulti(a, b, c))
+    }
+}
+
+/// Returns the "short" subset of the rustc command line options,
+/// including metadata for each option, such as whether the option is
+/// part of the stable long-term interface for rustc.
+pub fn rustc_short_optgroups() -> Vec<RustcOptGroup> {
+    vec![
+        opt::flag_s("h", "help", "Display this message"),
+        opt::multi_s("", "cfg", "Configure the compilation environment", "SPEC"),
+        opt::multi_s(
+            "L",
+            "",
+            "Add a directory to the library search path. The
+                             optional KIND can be one of dependency, crate, native,
+                             framework, or all (the default).",
+            "[KIND=]PATH",
+        ),
+        opt::multi_s(
+            "l",
+            "",
+            "Link the generated crate(s) to the specified native
+                             library NAME. The optional KIND can be one of
+                             static, framework, or dylib (the default).",
+            "[KIND=]NAME",
+        ),
+        make_crate_type_option(),
+        opt::opt_s("", "crate-name", "Specify the name of the crate being built", "NAME"),
+        opt::opt_s(
+            "",
+            "edition",
+            "Specify which edition of the compiler to use when compiling code.",
+            EDITION_NAME_LIST,
+        ),
+        opt::multi_s(
+            "",
+            "emit",
+            "Comma separated list of types of output for \
+             the compiler to emit",
+            "[asm|llvm-bc|llvm-ir|obj|metadata|link|dep-info|mir]",
+        ),
+        opt::multi_s(
+            "",
+            "print",
+            "Compiler information to print on stdout",
+            "[crate-name|file-names|sysroot|target-libdir|cfg|target-list|\
+             target-cpus|target-features|relocation-models|\
+             code-models|tls-models|target-spec-json|native-static-libs]",
+        ),
+        opt::flagmulti_s("g", "", "Equivalent to -C debuginfo=2"),
+        opt::flagmulti_s("O", "", "Equivalent to -C opt-level=2"),
+        opt::opt_s("o", "", "Write output to <filename>", "FILENAME"),
+        opt::opt_s(
+            "",
+            "out-dir",
+            "Write output to compiler-chosen filename \
+             in <dir>",
+            "DIR",
+        ),
+        opt::opt_s(
+            "",
+            "explain",
+            "Provide a detailed explanation of an error \
+             message",
+            "OPT",
+        ),
+        opt::flag_s("", "test", "Build a test harness"),
+        opt::opt_s("", "target", "Target triple for which the code is compiled", "TARGET"),
+        opt::multi_s("W", "warn", "Set lint warnings", "OPT"),
+        opt::multi_s("A", "allow", "Set lint allowed", "OPT"),
+        opt::multi_s("D", "deny", "Set lint denied", "OPT"),
+        opt::multi_s("F", "forbid", "Set lint forbidden", "OPT"),
+        opt::multi_s(
+            "",
+            "cap-lints",
+            "Set the most restrictive lint level. \
+             More restrictive lints are capped at this \
+             level",
+            "LEVEL",
+        ),
+        opt::multi_s("C", "codegen", "Set a codegen option", "OPT[=VALUE]"),
+        opt::flag_s("V", "version", "Print version info and exit"),
+        opt::flag_s("v", "verbose", "Use verbose output"),
+    ]
+}
+
+/// Returns all rustc command line options, including metadata for
+/// each option, such as whether the option is part of the stable
+/// long-term interface for rustc.
+pub fn rustc_optgroups() -> Vec<RustcOptGroup> {
+    let mut opts = rustc_short_optgroups();
+    opts.extend(vec![
+        opt::multi_s(
+            "",
+            "extern",
+            "Specify where an external rust library is located",
+            "NAME[=PATH]",
+        ),
+        opt::opt_s("", "sysroot", "Override the system root", "PATH"),
+        opt::multi("Z", "", "Set internal debugging options", "FLAG"),
+        opt::opt_s(
+            "",
+            "error-format",
+            "How errors and other messages are produced",
+            "human|json|short",
+        ),
+        opt::multi_s("", "json", "Configure the JSON output of the compiler", "CONFIG"),
+        opt::opt_s(
+            "",
+            "color",
+            "Configure coloring of output:
+                                 auto   = colorize, if output goes to a tty (default);
+                                 always = always colorize output;
+                                 never  = never colorize output",
+            "auto|always|never",
+        ),
+        opt::opt(
+            "",
+            "pretty",
+            "Pretty-print the input instead of compiling;
+                  valid types are: `normal` (un-annotated source),
+                  `expanded` (crates expanded), or
+                  `expanded,identified` (fully parenthesized, AST nodes with IDs).",
+            "TYPE",
+        ),
+        opt::multi_s(
+            "",
+            "remap-path-prefix",
+            "Remap source names in all output (compiler messages and output files)",
+            "FROM=TO",
+        ),
+    ]);
+    opts
+}
+
+pub fn get_cmd_lint_options(
+    matches: &getopts::Matches,
+    error_format: ErrorOutputType,
+) -> (Vec<(String, lint::Level)>, bool, Option<lint::Level>) {
+    let mut lint_opts_with_position = vec![];
+    let mut describe_lints = false;
+
+    for &level in &[lint::Allow, lint::Warn, lint::Deny, lint::Forbid] {
+        for (passed_arg_pos, lint_name) in matches.opt_strs_pos(level.as_str()) {
+            let arg_pos = if let lint::Forbid = level {
+                // HACK: forbid is always specified last, so it can't be overridden.
+                // FIXME: remove this once <https://github.com/rust-lang/rust/issues/70819> is
+                // fixed and `forbid` works as expected.
+                usize::MAX
+            } else {
+                passed_arg_pos
+            };
+            if lint_name == "help" {
+                describe_lints = true;
+            } else {
+                lint_opts_with_position.push((arg_pos, lint_name.replace("-", "_"), level));
+            }
+        }
+    }
+
+    lint_opts_with_position.sort_by_key(|x| x.0);
+    let lint_opts = lint_opts_with_position
+        .iter()
+        .cloned()
+        .map(|(_, lint_name, level)| (lint_name, level))
+        .collect();
+
+    let lint_cap = matches.opt_str("cap-lints").map(|cap| {
+        lint::Level::from_str(&cap)
+            .unwrap_or_else(|| early_error(error_format, &format!("unknown lint level: `{}`", cap)))
+    });
+    (lint_opts, describe_lints, lint_cap)
+}
+
+/// Parses the `--color` flag.
+pub fn parse_color(matches: &getopts::Matches) -> ColorConfig {
+    match matches.opt_str("color").as_ref().map(|s| &s[..]) {
+        Some("auto") => ColorConfig::Auto,
+        Some("always") => ColorConfig::Always,
+        Some("never") => ColorConfig::Never,
+
+        None => ColorConfig::Auto,
+
+        Some(arg) => early_error(
+            ErrorOutputType::default(),
+            &format!(
+                "argument for `--color` must be auto, \
+                 always or never (instead was `{}`)",
+                arg
+            ),
+        ),
+    }
+}
+
+/// Parse the `--json` flag.
+///
+/// The first value returned is how to render JSON diagnostics, and the second
+/// is whether or not artifact notifications are enabled.
+pub fn parse_json(matches: &getopts::Matches) -> (HumanReadableErrorType, bool) {
+    let mut json_rendered: fn(ColorConfig) -> HumanReadableErrorType =
+        HumanReadableErrorType::Default;
+    let mut json_color = ColorConfig::Never;
+    let mut json_artifact_notifications = false;
+    for option in matches.opt_strs("json") {
+        // For now conservatively forbid `--color` with `--json` since `--json`
+        // won't actually be emitting any colors and anything colorized is
+        // embedded in a diagnostic message anyway.
+        if matches.opt_str("color").is_some() {
+            early_error(
+                ErrorOutputType::default(),
+                "cannot specify the `--color` option with `--json`",
+            );
+        }
+
+        for sub_option in option.split(',') {
+            match sub_option {
+                "diagnostic-short" => json_rendered = HumanReadableErrorType::Short,
+                "diagnostic-rendered-ansi" => json_color = ColorConfig::Always,
+                "artifacts" => json_artifact_notifications = true,
+                s => early_error(
+                    ErrorOutputType::default(),
+                    &format!("unknown `--json` option `{}`", s),
+                ),
+            }
+        }
+    }
+    (json_rendered(json_color), json_artifact_notifications)
+}
+
+/// Parses the `--error-format` flag.
+pub fn parse_error_format(
+    matches: &getopts::Matches,
+    color: ColorConfig,
+    json_rendered: HumanReadableErrorType,
+) -> ErrorOutputType {
+    // We need the `opts_present` check because the driver will send us Matches
+    // with only stable options if no unstable options are used. Since error-format
+    // is unstable, it will not be present. We have to use `opts_present` not
+    // `opt_present` because the latter will panic.
+    let error_format = if matches.opts_present(&["error-format".to_owned()]) {
+        match matches.opt_str("error-format").as_ref().map(|s| &s[..]) {
+            None | Some("human") => {
+                ErrorOutputType::HumanReadable(HumanReadableErrorType::Default(color))
+            }
+            Some("human-annotate-rs") => {
+                ErrorOutputType::HumanReadable(HumanReadableErrorType::AnnotateSnippet(color))
+            }
+            Some("json") => ErrorOutputType::Json { pretty: false, json_rendered },
+            Some("pretty-json") => ErrorOutputType::Json { pretty: true, json_rendered },
+            Some("short") => ErrorOutputType::HumanReadable(HumanReadableErrorType::Short(color)),
+
+            Some(arg) => early_error(
+                ErrorOutputType::HumanReadable(HumanReadableErrorType::Default(color)),
+                &format!(
+                    "argument for `--error-format` must be `human`, `json` or \
+                     `short` (instead was `{}`)",
+                    arg
+                ),
+            ),
+        }
+    } else {
+        ErrorOutputType::HumanReadable(HumanReadableErrorType::Default(color))
+    };
+
+    match error_format {
+        ErrorOutputType::Json { .. } => {}
+
+        // Conservatively require that the `--json` argument is coupled with
+        // `--error-format=json`. This means that `--json` is specified we
+        // should actually be emitting JSON blobs.
+        _ if !matches.opt_strs("json").is_empty() => {
+            early_error(
+                ErrorOutputType::default(),
+                "using `--json` requires also using `--error-format=json`",
+            );
+        }
+
+        _ => {}
+    }
+
+    error_format
+}
+
+fn parse_crate_edition(matches: &getopts::Matches) -> Edition {
+    let edition = match matches.opt_str("edition") {
+        Some(arg) => Edition::from_str(&arg).unwrap_or_else(|_| {
+            early_error(
+                ErrorOutputType::default(),
+                &format!(
+                    "argument for `--edition` must be one of: \
+                     {}. (instead was `{}`)",
+                    EDITION_NAME_LIST, arg
+                ),
+            )
+        }),
+        None => DEFAULT_EDITION,
+    };
+
+    if !edition.is_stable() && !nightly_options::is_nightly_build() {
+        early_error(
+            ErrorOutputType::default(),
+            &format!(
+                "edition {} is unstable and only \
+                     available for nightly builds of rustc.",
+                edition,
+            ),
+        )
+    }
+
+    edition
+}
+
+fn check_debug_option_stability(
+    debugging_opts: &DebuggingOptions,
+    error_format: ErrorOutputType,
+    json_rendered: HumanReadableErrorType,
+) {
+    if !debugging_opts.unstable_options {
+        if let ErrorOutputType::Json { pretty: true, json_rendered } = error_format {
+            early_error(
+                ErrorOutputType::Json { pretty: false, json_rendered },
+                "`--error-format=pretty-json` is unstable",
+            );
+        }
+        if let ErrorOutputType::HumanReadable(HumanReadableErrorType::AnnotateSnippet(_)) =
+            error_format
+        {
+            early_error(
+                ErrorOutputType::Json { pretty: false, json_rendered },
+                "`--error-format=human-annotate-rs` is unstable",
+            );
+        }
+    }
+}
+
+fn parse_output_types(
+    debugging_opts: &DebuggingOptions,
+    matches: &getopts::Matches,
+    error_format: ErrorOutputType,
+) -> OutputTypes {
+    let mut output_types = BTreeMap::new();
+    if !debugging_opts.parse_only {
+        for list in matches.opt_strs("emit") {
+            for output_type in list.split(',') {
+                let mut parts = output_type.splitn(2, '=');
+                let shorthand = parts.next().unwrap();
+                let output_type = OutputType::from_shorthand(shorthand).unwrap_or_else(|| {
+                    early_error(
+                        error_format,
+                        &format!(
+                            "unknown emission type: `{}` - expected one of: {}",
+                            shorthand,
+                            OutputType::shorthands_display(),
+                        ),
+                    )
+                });
+                let path = parts.next().map(PathBuf::from);
+                output_types.insert(output_type, path);
+            }
+        }
+    };
+    if output_types.is_empty() {
+        output_types.insert(OutputType::Exe, None);
+    }
+    OutputTypes(output_types)
+}
+
+fn should_override_cgus_and_disable_thinlto(
+    output_types: &OutputTypes,
+    matches: &getopts::Matches,
+    error_format: ErrorOutputType,
+    mut codegen_units: Option<usize>,
+) -> (bool, Option<usize>) {
+    let mut disable_thinlto = false;
+    // Issue #30063: if user requests LLVM-related output to one
+    // particular path, disable codegen-units.
+    let incompatible: Vec<_> = output_types
+        .0
+        .iter()
+        .map(|ot_path| ot_path.0)
+        .filter(|ot| !ot.is_compatible_with_codegen_units_and_single_output_file())
+        .map(|ot| ot.shorthand())
+        .collect();
+    if !incompatible.is_empty() {
+        match codegen_units {
+            Some(n) if n > 1 => {
+                if matches.opt_present("o") {
+                    for ot in &incompatible {
+                        early_warn(
+                            error_format,
+                            &format!(
+                                "`--emit={}` with `-o` incompatible with \
+                                 `-C codegen-units=N` for N > 1",
+                                ot
+                            ),
+                        );
+                    }
+                    early_warn(error_format, "resetting to default -C codegen-units=1");
+                    codegen_units = Some(1);
+                    disable_thinlto = true;
+                }
+            }
+            _ => {
+                codegen_units = Some(1);
+                disable_thinlto = true;
+            }
+        }
+    }
+
+    if codegen_units == Some(0) {
+        early_error(error_format, "value for codegen units must be a positive non-zero integer");
+    }
+
+    (disable_thinlto, codegen_units)
+}
+
+fn check_thread_count(debugging_opts: &DebuggingOptions, error_format: ErrorOutputType) {
+    if debugging_opts.threads == 0 {
+        early_error(error_format, "value for threads must be a positive non-zero integer");
+    }
+
+    if debugging_opts.threads > 1 && debugging_opts.fuel.is_some() {
+        early_error(error_format, "optimization fuel is incompatible with multiple threads");
+    }
+}
+
+fn collect_print_requests(
+    cg: &mut CodegenOptions,
+    dopts: &mut DebuggingOptions,
+    matches: &getopts::Matches,
+    error_format: ErrorOutputType,
+) -> Vec<PrintRequest> {
+    let mut prints = Vec::<PrintRequest>::new();
+    if cg.target_cpu.as_ref().map_or(false, |s| s == "help") {
+        prints.push(PrintRequest::TargetCPUs);
+        cg.target_cpu = None;
+    };
+    if cg.target_feature == "help" {
+        prints.push(PrintRequest::TargetFeatures);
+        cg.target_feature = String::new();
+    }
+
+    prints.extend(matches.opt_strs("print").into_iter().map(|s| match &*s {
+        "crate-name" => PrintRequest::CrateName,
+        "file-names" => PrintRequest::FileNames,
+        "sysroot" => PrintRequest::Sysroot,
+        "target-libdir" => PrintRequest::TargetLibdir,
+        "cfg" => PrintRequest::Cfg,
+        "target-list" => PrintRequest::TargetList,
+        "target-cpus" => PrintRequest::TargetCPUs,
+        "target-features" => PrintRequest::TargetFeatures,
+        "relocation-models" => PrintRequest::RelocationModels,
+        "code-models" => PrintRequest::CodeModels,
+        "tls-models" => PrintRequest::TlsModels,
+        "native-static-libs" => PrintRequest::NativeStaticLibs,
+        "target-spec-json" => {
+            if dopts.unstable_options {
+                PrintRequest::TargetSpec
+            } else {
+                early_error(
+                    error_format,
+                    "the `-Z unstable-options` flag must also be passed to \
+                     enable the target-spec-json print option",
+                );
+            }
+        }
+        req => early_error(error_format, &format!("unknown print request `{}`", req)),
+    }));
+
+    prints
+}
+
+fn parse_target_triple(matches: &getopts::Matches, error_format: ErrorOutputType) -> TargetTriple {
+    match matches.opt_str("target") {
+        Some(target) if target.ends_with(".json") => {
+            let path = Path::new(&target);
+            TargetTriple::from_path(&path).unwrap_or_else(|_| {
+                early_error(error_format, &format!("target file {:?} does not exist", path))
+            })
+        }
+        Some(target) => TargetTriple::TargetTriple(target),
+        _ => TargetTriple::from_triple(host_triple()),
+    }
+}
+
+fn parse_opt_level(
+    matches: &getopts::Matches,
+    cg: &CodegenOptions,
+    error_format: ErrorOutputType,
+) -> OptLevel {
+    // The `-O` and `-C opt-level` flags specify the same setting, so we want to be able
+    // to use them interchangeably. However, because they're technically different flags,
+    // we need to work out manually which should take precedence if both are supplied (i.e.
+    // the rightmost flag). We do this by finding the (rightmost) position of both flags and
+    // comparing them. Note that if a flag is not found, its position will be `None`, which
+    // always compared less than `Some(_)`.
+    let max_o = matches.opt_positions("O").into_iter().max();
+    let max_c = matches
+        .opt_strs_pos("C")
+        .into_iter()
+        .flat_map(
+            |(i, s)| {
+                if let Some("opt-level") = s.splitn(2, '=').next() { Some(i) } else { None }
+            },
+        )
+        .max();
+    if max_o > max_c {
+        OptLevel::Default
+    } else {
+        match cg.opt_level.as_ref() {
+            "0" => OptLevel::No,
+            "1" => OptLevel::Less,
+            "2" => OptLevel::Default,
+            "3" => OptLevel::Aggressive,
+            "s" => OptLevel::Size,
+            "z" => OptLevel::SizeMin,
+            arg => {
+                early_error(
+                    error_format,
+                    &format!(
+                        "optimization level needs to be \
+                            between 0-3, s or z (instead was `{}`)",
+                        arg
+                    ),
+                );
+            }
+        }
+    }
+}
+
+fn select_debuginfo(
+    matches: &getopts::Matches,
+    cg: &CodegenOptions,
+    error_format: ErrorOutputType,
+) -> DebugInfo {
+    let max_g = matches.opt_positions("g").into_iter().max();
+    let max_c = matches
+        .opt_strs_pos("C")
+        .into_iter()
+        .flat_map(
+            |(i, s)| {
+                if let Some("debuginfo") = s.splitn(2, '=').next() { Some(i) } else { None }
+            },
+        )
+        .max();
+    if max_g > max_c {
+        DebugInfo::Full
+    } else {
+        match cg.debuginfo {
+            0 => DebugInfo::None,
+            1 => DebugInfo::Limited,
+            2 => DebugInfo::Full,
+            arg => {
+                early_error(
+                    error_format,
+                    &format!(
+                        "debug info level needs to be between \
+                         0-2 (instead was `{}`)",
+                        arg
+                    ),
+                );
+            }
+        }
+    }
+}
+
+fn parse_libs(
+    matches: &getopts::Matches,
+    error_format: ErrorOutputType,
+) -> Vec<(String, Option<String>, NativeLibKind)> {
+    matches
+        .opt_strs("l")
+        .into_iter()
+        .map(|s| {
+            // Parse string of the form "[KIND=]lib[:new_name]",
+            // where KIND is one of "dylib", "framework", "static".
+            let mut parts = s.splitn(2, '=');
+            let kind = parts.next().unwrap();
+            let (name, kind) = match (parts.next(), kind) {
+                (None, name) => (name, NativeLibKind::Unspecified),
+                (Some(name), "dylib") => (name, NativeLibKind::Dylib),
+                (Some(name), "framework") => (name, NativeLibKind::Framework),
+                (Some(name), "static") => (name, NativeLibKind::StaticBundle),
+                (Some(name), "static-nobundle") => (name, NativeLibKind::StaticNoBundle),
+                (_, s) => {
+                    early_error(
+                        error_format,
+                        &format!(
+                            "unknown library kind `{}`, expected \
+                             one of dylib, framework, or static",
+                            s
+                        ),
+                    );
+                }
+            };
+            if kind == NativeLibKind::StaticNoBundle && !nightly_options::is_nightly_build() {
+                early_error(
+                    error_format,
+                    "the library kind 'static-nobundle' is only \
+                     accepted on the nightly compiler",
+                );
+            }
+            let mut name_parts = name.splitn(2, ':');
+            let name = name_parts.next().unwrap();
+            let new_name = name_parts.next();
+            (name.to_owned(), new_name.map(|n| n.to_owned()), kind)
+        })
+        .collect()
+}
+
+fn parse_borrowck_mode(dopts: &DebuggingOptions, error_format: ErrorOutputType) -> BorrowckMode {
+    match dopts.borrowck.as_ref() {
+        "migrate" => BorrowckMode::Migrate,
+        "mir" => BorrowckMode::Mir,
+        m => early_error(error_format, &format!("unknown borrowck mode `{}`", m)),
+    }
+}
+
+pub fn parse_externs(
+    matches: &getopts::Matches,
+    debugging_opts: &DebuggingOptions,
+    error_format: ErrorOutputType,
+) -> Externs {
+    let is_unstable_enabled = debugging_opts.unstable_options;
+    let mut externs: BTreeMap<String, ExternEntry> = BTreeMap::new();
+    for arg in matches.opt_strs("extern") {
+        let mut parts = arg.splitn(2, '=');
+        let name = parts
+            .next()
+            .unwrap_or_else(|| early_error(error_format, "--extern value must not be empty"));
+        let path = parts.next().map(|s| s.to_string());
+
+        let mut name_parts = name.splitn(2, ':');
+        let first_part = name_parts.next();
+        let second_part = name_parts.next();
+        let (options, name) = match (first_part, second_part) {
+            (Some(opts), Some(name)) => (Some(opts), name),
+            (Some(name), None) => (None, name),
+            (None, None) => early_error(error_format, "--extern name must not be empty"),
+            _ => unreachable!(),
+        };
+
+        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;
+        if let Some(opts) = options {
+            if !is_unstable_enabled {
+                early_error(
+                    error_format,
+                    "the `-Z unstable-options` flag must also be passed to \
+                     enable `--extern options",
+                );
+            }
+            for opt in opts.split(',') {
+                match opt {
+                    "priv" => is_private_dep = true,
+                    "noprelude" => {
+                        if let ExternLocation::ExactPaths(_) = &entry.location {
+                            add_prelude = false;
+                        } else {
+                            early_error(
+                                error_format,
+                                "the `noprelude` --extern option requires a file path",
+                            );
+                        }
+                    }
+                    _ => early_error(error_format, &format!("unknown --extern option `{}`", opt)),
+                }
+            }
+        }
+
+        // Crates start out being not private, and go to being private `priv`
+        // is specified.
+        entry.is_private_dep |= is_private_dep;
+        // If any flag is missing `noprelude`, then add to the prelude.
+        entry.add_prelude |= add_prelude;
+    }
+    Externs(externs)
+}
+
+fn parse_remap_path_prefix(
+    matches: &getopts::Matches,
+    error_format: ErrorOutputType,
+) -> Vec<(PathBuf, PathBuf)> {
+    matches
+        .opt_strs("remap-path-prefix")
+        .into_iter()
+        .map(|remap| {
+            let mut parts = remap.rsplitn(2, '='); // reverse iterator
+            let to = parts.next();
+            let from = parts.next();
+            match (from, to) {
+                (Some(from), Some(to)) => (PathBuf::from(from), PathBuf::from(to)),
+                _ => early_error(
+                    error_format,
+                    "--remap-path-prefix must contain '=' between FROM and TO",
+                ),
+            }
+        })
+        .collect()
+}
+
+pub fn build_session_options(matches: &getopts::Matches) -> Options {
+    let color = parse_color(matches);
+
+    let edition = parse_crate_edition(matches);
+
+    let (json_rendered, json_artifact_notifications) = parse_json(matches);
+
+    let error_format = parse_error_format(matches, color, json_rendered);
+
+    let unparsed_crate_types = matches.opt_strs("crate-type");
+    let crate_types = parse_crate_types_from_list(unparsed_crate_types)
+        .unwrap_or_else(|e| early_error(error_format, &e[..]));
+
+    let (lint_opts, describe_lints, lint_cap) = get_cmd_lint_options(matches, error_format);
+
+    let mut debugging_opts = build_debugging_options(matches, error_format);
+    check_debug_option_stability(&debugging_opts, error_format, json_rendered);
+
+    let output_types = parse_output_types(&debugging_opts, matches, error_format);
+
+    let mut cg = build_codegen_options(matches, error_format);
+    let (disable_thinlto, mut codegen_units) = should_override_cgus_and_disable_thinlto(
+        &output_types,
+        matches,
+        error_format,
+        cg.codegen_units,
+    );
+
+    check_thread_count(&debugging_opts, error_format);
+
+    let incremental = cg.incremental.as_ref().map(PathBuf::from);
+
+    if debugging_opts.profile && incremental.is_some() {
+        early_error(
+            error_format,
+            "can't instrument with gcov profiling when compiling incrementally",
+        );
+    }
+    if debugging_opts.profile {
+        match codegen_units {
+            Some(1) => {}
+            None => codegen_units = Some(1),
+            Some(_) => early_error(
+                error_format,
+                "can't instrument with gcov profiling with multiple codegen units",
+            ),
+        }
+    }
+
+    if cg.profile_generate.enabled() && cg.profile_use.is_some() {
+        early_error(
+            error_format,
+            "options `-C profile-generate` and `-C profile-use` are exclusive",
+        );
+    }
+
+    if debugging_opts.instrument_coverage {
+        if cg.profile_generate.enabled() || cg.profile_use.is_some() {
+            early_error(
+                error_format,
+                "option `-Z instrument-coverage` is not compatible with either `-C profile-use` \
+                or `-C profile-generate`",
+            );
+        }
+
+        // `-Z instrument-coverage` implies:
+        //   * `-Z 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.
+        //   * `-C link-dead-code` - so unexecuted code is still counted as zero, rather than be
+        //     optimized out. Note that instrumenting dead code can be explicitly disabled with:
+        //         `-Z instrument-coverage -C link-dead-code=no`.
+        debugging_opts.symbol_mangling_version = SymbolManglingVersion::V0;
+        if cg.link_dead_code == None {
+            // FIXME(richkadel): Investigate if the `instrument-coverage` implementation can
+            // inject ["zero counters"](https://llvm.org/docs/CoverageMappingFormat.html#counter)
+            // in the coverage map when "dead code" is removed, rather than forcing `link-dead-code`.
+            cg.link_dead_code = Some(true);
+        }
+    }
+
+    if !cg.embed_bitcode {
+        match cg.lto {
+            LtoCli::No | LtoCli::Unspecified => {}
+            LtoCli::Yes | LtoCli::NoParam | LtoCli::Thin | LtoCli::Fat => early_error(
+                error_format,
+                "options `-C embed-bitcode=no` and `-C lto` are incompatible",
+            ),
+        }
+    }
+
+    let prints = collect_print_requests(&mut cg, &mut debugging_opts, matches, error_format);
+
+    let cg = cg;
+
+    let sysroot_opt = matches.opt_str("sysroot").map(|m| PathBuf::from(&m));
+    let target_triple = parse_target_triple(matches, error_format);
+    let opt_level = parse_opt_level(matches, &cg, error_format);
+    // The `-g` and `-C debuginfo` flags specify the same setting, so we want to be able
+    // to use them interchangeably. See the note above (regarding `-O` and `-C opt-level`)
+    // for more details.
+    let debug_assertions = cg.debug_assertions.unwrap_or(opt_level == OptLevel::No);
+    let debuginfo = select_debuginfo(matches, &cg, error_format);
+
+    let mut search_paths = vec![];
+    for s in &matches.opt_strs("L") {
+        search_paths.push(SearchPath::from_cli_opt(&s[..], error_format));
+    }
+
+    let libs = parse_libs(matches, error_format);
+
+    let test = matches.opt_present("test");
+
+    let borrowck_mode = parse_borrowck_mode(&debugging_opts, error_format);
+
+    if !cg.remark.is_empty() && debuginfo == DebugInfo::None {
+        early_warn(error_format, "-C remark requires \"-C debuginfo=n\" to show source locations");
+    }
+
+    let externs = parse_externs(matches, &debugging_opts, error_format);
+
+    let crate_name = matches.opt_str("crate-name");
+
+    let remap_path_prefix = parse_remap_path_prefix(matches, error_format);
+
+    let pretty = parse_pretty(matches, &debugging_opts, error_format);
+
+    Options {
+        crate_types,
+        optimize: opt_level,
+        debuginfo,
+        lint_opts,
+        lint_cap,
+        describe_lints,
+        output_types,
+        search_paths,
+        maybe_sysroot: sysroot_opt,
+        target_triple,
+        test,
+        incremental,
+        debugging_opts,
+        prints,
+        borrowck_mode,
+        cg,
+        error_format,
+        externs,
+        crate_name,
+        alt_std_name: None,
+        libs,
+        unstable_features: UnstableFeatures::from_environment(),
+        debug_assertions,
+        actually_rustdoc: false,
+        cli_forced_codegen_units: codegen_units,
+        cli_forced_thinlto_off: disable_thinlto,
+        remap_path_prefix,
+        edition,
+        json_artifact_notifications,
+        pretty,
+    }
+}
+
+fn parse_pretty(
+    matches: &getopts::Matches,
+    debugging_opts: &DebuggingOptions,
+    efmt: ErrorOutputType,
+) -> Option<PpMode> {
+    let pretty = if debugging_opts.unstable_options {
+        matches.opt_default("pretty", "normal").map(|a| {
+            // stable pretty-print variants only
+            parse_pretty_inner(efmt, &a, false)
+        })
+    } else {
+        None
+    };
+
+    return if pretty.is_none() {
+        debugging_opts.unpretty.as_ref().map(|a| {
+            // extended with unstable pretty-print variants
+            parse_pretty_inner(efmt, &a, true)
+        })
+    } else {
+        pretty
+    };
+
+    fn parse_pretty_inner(efmt: ErrorOutputType, name: &str, extended: bool) -> PpMode {
+        use PpMode::*;
+        use PpSourceMode::*;
+        let first = match (name, extended) {
+            ("normal", _) => PpmSource(PpmNormal),
+            ("identified", _) => PpmSource(PpmIdentified),
+            ("everybody_loops", true) => PpmSource(PpmEveryBodyLoops),
+            ("expanded", _) => PpmSource(PpmExpanded),
+            ("expanded,identified", _) => PpmSource(PpmExpandedIdentified),
+            ("expanded,hygiene", _) => PpmSource(PpmExpandedHygiene),
+            ("hir", true) => PpmHir(PpmNormal),
+            ("hir,identified", true) => PpmHir(PpmIdentified),
+            ("hir,typed", true) => PpmHir(PpmTyped),
+            ("hir-tree", true) => PpmHirTree(PpmNormal),
+            ("mir", true) => PpmMir,
+            ("mir-cfg", true) => PpmMirCFG,
+            _ => {
+                if extended {
+                    early_error(
+                        efmt,
+                        &format!(
+                            "argument to `unpretty` must be one of `normal`, \
+                                        `expanded`, `identified`, `expanded,identified`, \
+                                        `expanded,hygiene`, `everybody_loops`, \
+                                        `hir`, `hir,identified`, `hir,typed`, `hir-tree`, \
+                                        `mir` or `mir-cfg`; got {}",
+                            name
+                        ),
+                    );
+                } else {
+                    early_error(
+                        efmt,
+                        &format!(
+                            "argument to `pretty` must be one of `normal`, \
+                                        `expanded`, `identified`, or `expanded,identified`; got {}",
+                            name
+                        ),
+                    );
+                }
+            }
+        };
+        tracing::debug!("got unpretty option: {:?}", first);
+        first
+    }
+}
+
+pub fn make_crate_type_option() -> RustcOptGroup {
+    opt::multi_s(
+        "",
+        "crate-type",
+        "Comma separated list of types of crates
+                                for the compiler to emit",
+        "[bin|lib|rlib|dylib|cdylib|staticlib|proc-macro]",
+    )
+}
+
+pub fn parse_crate_types_from_list(list_list: Vec<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 super::{ErrorOutputType, OptionStability, RustcOptGroup};
+    use crate::early_error;
+    use rustc_feature::UnstableFeatures;
+
+    pub fn is_unstable_enabled(matches: &getopts::Matches) -> bool {
+        is_nightly_build() && matches.opt_strs("Z").iter().any(|x| *x == "unstable-options")
+    }
+
+    pub fn is_nightly_build() -> bool {
+        UnstableFeatures::from_environment().is_nightly_build()
+    }
+
+    pub fn check_nightly_options(matches: &getopts::Matches, flags: &[RustcOptGroup]) {
+        let has_z_unstable_option = matches.opt_strs("Z").iter().any(|x| *x == "unstable-options");
+        let really_allows_unstable_options =
+            UnstableFeatures::from_environment().is_nightly_build();
+
+        for opt in flags.iter() {
+            if opt.stability == OptionStability::Stable {
+                continue;
+            }
+            if !matches.opt_present(opt.name) {
+                continue;
+            }
+            if opt.name != "Z" && !has_z_unstable_option {
+                early_error(
+                    ErrorOutputType::default(),
+                    &format!(
+                        "the `-Z unstable-options` flag must also be passed to enable \
+                         the flag `{}`",
+                        opt.name
+                    ),
+                );
+            }
+            if really_allows_unstable_options {
+                continue;
+            }
+            match opt.stability {
+                OptionStability::Unstable => {
+                    let msg = format!(
+                        "the option `{}` is only accepted on the \
+                         nightly compiler",
+                        opt.name
+                    );
+                    early_error(ErrorOutputType::default(), &msg);
+                }
+                OptionStability::Stable => {}
+            }
+        }
+    }
+}
+
+impl fmt::Display for CrateType {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        match *self {
+            CrateType::Executable => "bin".fmt(f),
+            CrateType::Dylib => "dylib".fmt(f),
+            CrateType::Rlib => "rlib".fmt(f),
+            CrateType::Staticlib => "staticlib".fmt(f),
+            CrateType::Cdylib => "cdylib".fmt(f),
+            CrateType::ProcMacro => "proc-macro".fmt(f),
+        }
+    }
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub enum PpSourceMode {
+    PpmNormal,
+    PpmEveryBodyLoops,
+    PpmExpanded,
+    PpmIdentified,
+    PpmExpandedIdentified,
+    PpmExpandedHygiene,
+    PpmTyped,
+}
+
+#[derive(Copy, Clone, PartialEq, Debug)]
+pub enum PpMode {
+    PpmSource(PpSourceMode),
+    PpmHir(PpSourceMode),
+    PpmHirTree(PpSourceMode),
+    PpmMir,
+    PpmMirCFG,
+}
+
+impl PpMode {
+    pub fn needs_ast_map(&self) -> bool {
+        use PpMode::*;
+        use PpSourceMode::*;
+        match *self {
+            PpmSource(PpmNormal | PpmIdentified) => false,
+
+            PpmSource(
+                PpmExpanded | PpmEveryBodyLoops | PpmExpandedIdentified | PpmExpandedHygiene,
+            )
+            | PpmHir(_)
+            | PpmHirTree(_)
+            | PpmMir
+            | PpmMirCFG => true,
+            PpmSource(PpmTyped) => panic!("invalid state"),
+        }
+    }
+
+    pub fn needs_analysis(&self) -> bool {
+        use PpMode::*;
+        match *self {
+            PpmMir | PpmMirCFG => true,
+            _ => false,
+        }
+    }
+}
+
+/// 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.
+crate mod dep_tracking {
+    use super::{
+        CFGuard, CrateType, DebugInfo, ErrorOutputType, LinkerPluginLto, LtoCli, OptLevel,
+        OutputTypes, Passes, SanitizerSet, SourceFileHashAlgorithm, SwitchWithOptPath,
+        SymbolManglingVersion,
+    };
+    use crate::lint;
+    use crate::utils::NativeLibKind;
+    use rustc_feature::UnstableFeatures;
+    use rustc_span::edition::Edition;
+    use rustc_target::spec::{CodeModel, MergeFunctions, PanicStrategy, RelocModel};
+    use rustc_target::spec::{RelroLevel, TargetTriple, TlsModel};
+    use std::collections::hash_map::DefaultHasher;
+    use std::collections::BTreeMap;
+    use std::hash::Hash;
+    use std::path::PathBuf;
+
+    pub trait DepTrackingHash {
+        fn hash(&self, hasher: &mut DefaultHasher, error_format: ErrorOutputType);
+    }
+
+    macro_rules! impl_dep_tracking_hash_via_hash {
+        ($t:ty) => {
+            impl DepTrackingHash for $t {
+                fn hash(&self, hasher: &mut DefaultHasher, _: ErrorOutputType) {
+                    Hash::hash(self, hasher);
+                }
+            }
+        };
+    }
+
+    macro_rules! impl_dep_tracking_hash_for_sortable_vec_of {
+        ($t:ty) => {
+            impl DepTrackingHash for Vec<$t> {
+                fn hash(&self, hasher: &mut DefaultHasher, error_format: ErrorOutputType) {
+                    let mut elems: Vec<&$t> = self.iter().collect();
+                    elems.sort();
+                    Hash::hash(&elems.len(), hasher);
+                    for (index, elem) in elems.iter().enumerate() {
+                        Hash::hash(&index, hasher);
+                        DepTrackingHash::hash(*elem, hasher, error_format);
+                    }
+                }
+            }
+        };
+    }
+
+    impl_dep_tracking_hash_via_hash!(bool);
+    impl_dep_tracking_hash_via_hash!(usize);
+    impl_dep_tracking_hash_via_hash!(u64);
+    impl_dep_tracking_hash_via_hash!(String);
+    impl_dep_tracking_hash_via_hash!(PathBuf);
+    impl_dep_tracking_hash_via_hash!(lint::Level);
+    impl_dep_tracking_hash_via_hash!(Option<bool>);
+    impl_dep_tracking_hash_via_hash!(Option<usize>);
+    impl_dep_tracking_hash_via_hash!(Option<String>);
+    impl_dep_tracking_hash_via_hash!(Option<(String, u64)>);
+    impl_dep_tracking_hash_via_hash!(Option<Vec<String>>);
+    impl_dep_tracking_hash_via_hash!(Option<MergeFunctions>);
+    impl_dep_tracking_hash_via_hash!(Option<RelocModel>);
+    impl_dep_tracking_hash_via_hash!(Option<CodeModel>);
+    impl_dep_tracking_hash_via_hash!(Option<TlsModel>);
+    impl_dep_tracking_hash_via_hash!(Option<PanicStrategy>);
+    impl_dep_tracking_hash_via_hash!(Option<RelroLevel>);
+    impl_dep_tracking_hash_via_hash!(Option<lint::Level>);
+    impl_dep_tracking_hash_via_hash!(Option<PathBuf>);
+    impl_dep_tracking_hash_via_hash!(CrateType);
+    impl_dep_tracking_hash_via_hash!(MergeFunctions);
+    impl_dep_tracking_hash_via_hash!(PanicStrategy);
+    impl_dep_tracking_hash_via_hash!(RelroLevel);
+    impl_dep_tracking_hash_via_hash!(Passes);
+    impl_dep_tracking_hash_via_hash!(OptLevel);
+    impl_dep_tracking_hash_via_hash!(LtoCli);
+    impl_dep_tracking_hash_via_hash!(DebugInfo);
+    impl_dep_tracking_hash_via_hash!(UnstableFeatures);
+    impl_dep_tracking_hash_via_hash!(OutputTypes);
+    impl_dep_tracking_hash_via_hash!(NativeLibKind);
+    impl_dep_tracking_hash_via_hash!(SanitizerSet);
+    impl_dep_tracking_hash_via_hash!(CFGuard);
+    impl_dep_tracking_hash_via_hash!(TargetTriple);
+    impl_dep_tracking_hash_via_hash!(Edition);
+    impl_dep_tracking_hash_via_hash!(LinkerPluginLto);
+    impl_dep_tracking_hash_via_hash!(SwitchWithOptPath);
+    impl_dep_tracking_hash_via_hash!(SymbolManglingVersion);
+    impl_dep_tracking_hash_via_hash!(Option<SourceFileHashAlgorithm>);
+
+    impl_dep_tracking_hash_for_sortable_vec_of!(String);
+    impl_dep_tracking_hash_for_sortable_vec_of!(PathBuf);
+    impl_dep_tracking_hash_for_sortable_vec_of!(CrateType);
+    impl_dep_tracking_hash_for_sortable_vec_of!((String, lint::Level));
+    impl_dep_tracking_hash_for_sortable_vec_of!((String, Option<String>, NativeLibKind));
+    impl_dep_tracking_hash_for_sortable_vec_of!((String, u64));
+
+    impl<T1, T2> DepTrackingHash for (T1, T2)
+    where
+        T1: DepTrackingHash,
+        T2: DepTrackingHash,
+    {
+        fn hash(&self, hasher: &mut DefaultHasher, error_format: ErrorOutputType) {
+            Hash::hash(&0, hasher);
+            DepTrackingHash::hash(&self.0, hasher, error_format);
+            Hash::hash(&1, hasher);
+            DepTrackingHash::hash(&self.1, hasher, error_format);
+        }
+    }
+
+    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) {
+            Hash::hash(&0, hasher);
+            DepTrackingHash::hash(&self.0, hasher, error_format);
+            Hash::hash(&1, hasher);
+            DepTrackingHash::hash(&self.1, hasher, error_format);
+            Hash::hash(&2, hasher);
+            DepTrackingHash::hash(&self.2, hasher, error_format);
+        }
+    }
+
+    // This is a stable hash because BTreeMap is a sorted container
+    pub fn stable_hash(
+        sub_hashes: BTreeMap<&'static str, &dyn DepTrackingHash>,
+        hasher: &mut DefaultHasher,
+        error_format: ErrorOutputType,
+    ) {
+        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);
+        }
+    }
+}
diff --git a/compiler/rustc_session/src/filesearch.rs b/compiler/rustc_session/src/filesearch.rs
new file mode 100644
index 00000000000..284fca652ec
--- /dev/null
+++ b/compiler/rustc_session/src/filesearch.rs
@@ -0,0 +1,171 @@
+#![allow(non_camel_case_types)]
+
+pub use self::FileMatch::*;
+
+use std::borrow::Cow;
+use std::env;
+use std::fs;
+use std::path::{Path, PathBuf};
+
+use crate::search_paths::{PathKind, SearchPath, SearchPathFile};
+use rustc_fs_util::fix_windows_verbatim_for_gcc;
+use tracing::debug;
+
+#[derive(Copy, Clone)]
+pub enum FileMatch {
+    FileMatches,
+    FileDoesntMatch,
+}
+
+// A module for searching for libraries
+
+#[derive(Clone)]
+pub struct FileSearch<'a> {
+    sysroot: &'a Path,
+    triple: &'a str,
+    search_paths: &'a [SearchPath],
+    tlib_path: &'a SearchPath,
+    kind: PathKind,
+}
+
+impl<'a> FileSearch<'a> {
+    pub fn search_paths(&self) -> impl Iterator<Item = &'a SearchPath> {
+        let kind = self.kind;
+        self.search_paths
+            .iter()
+            .filter(move |sp| sp.kind.matches(kind))
+            .chain(std::iter::once(self.tlib_path))
+    }
+
+    pub fn get_lib_path(&self) -> PathBuf {
+        make_target_lib_path(self.sysroot, self.triple)
+    }
+
+    pub fn get_self_contained_lib_path(&self) -> PathBuf {
+        self.get_lib_path().join("self-contained")
+    }
+
+    pub fn search<F>(&self, mut pick: F)
+    where
+        F: FnMut(&SearchPathFile, PathKind) -> FileMatch,
+    {
+        for search_path in self.search_paths() {
+            debug!("searching {}", search_path.dir.display());
+            fn is_rlib(spf: &SearchPathFile) -> bool {
+                if let Some(f) = &spf.file_name_str { f.ends_with(".rlib") } else { false }
+            }
+            // Reading metadata out of rlibs is faster, and if we find both
+            // an rlib and a dylib we only read one of the files of
+            // metadata, so in the name of speed, bring all rlib files to
+            // the front of the search list.
+            let files1 = search_path.files.iter().filter(|spf| is_rlib(&spf));
+            let files2 = search_path.files.iter().filter(|spf| !is_rlib(&spf));
+            for spf in files1.chain(files2) {
+                debug!("testing {}", spf.path.display());
+                let maybe_picked = pick(spf, search_path.kind);
+                match maybe_picked {
+                    FileMatches => {
+                        debug!("picked {}", spf.path.display());
+                    }
+                    FileDoesntMatch => {
+                        debug!("rejected {}", spf.path.display());
+                    }
+                }
+            }
+        }
+    }
+
+    pub fn new(
+        sysroot: &'a Path,
+        triple: &'a str,
+        search_paths: &'a Vec<SearchPath>,
+        tlib_path: &'a SearchPath,
+        kind: PathKind,
+    ) -> FileSearch<'a> {
+        debug!("using sysroot = {}, triple = {}", sysroot.display(), triple);
+        FileSearch { sysroot, triple, search_paths, tlib_path, kind }
+    }
+
+    // Returns just the directories within the search paths.
+    pub fn search_path_dirs(&self) -> Vec<PathBuf> {
+        self.search_paths().map(|sp| sp.dir.to_path_buf()).collect()
+    }
+
+    // Returns a list of directories where target-specific tool binaries are located.
+    pub fn get_tools_search_paths(&self, self_contained: bool) -> Vec<PathBuf> {
+        let mut p = PathBuf::from(self.sysroot);
+        p.push(find_libdir(self.sysroot).as_ref());
+        p.push(RUST_LIB_DIR);
+        p.push(&self.triple);
+        p.push("bin");
+        if self_contained { vec![p.clone(), p.join("self-contained")] } else { vec![p] }
+    }
+}
+
+pub fn relative_target_lib_path(sysroot: &Path, target_triple: &str) -> PathBuf {
+    let mut p = PathBuf::from(find_libdir(sysroot).as_ref());
+    assert!(p.is_relative());
+    p.push(RUST_LIB_DIR);
+    p.push(target_triple);
+    p.push("lib");
+    p
+}
+
+pub fn make_target_lib_path(sysroot: &Path, target_triple: &str) -> PathBuf {
+    sysroot.join(&relative_target_lib_path(sysroot, target_triple))
+}
+
+pub fn get_or_default_sysroot() -> PathBuf {
+    // Follow symlinks.  If the resolved path is relative, make it absolute.
+    fn canonicalize(path: PathBuf) -> PathBuf {
+        let path = fs::canonicalize(&path).unwrap_or(path);
+        // See comments on this target function, but the gist is that
+        // gcc chokes on verbatim paths which fs::canonicalize generates
+        // so we try to avoid those kinds of paths.
+        fix_windows_verbatim_for_gcc(&path)
+    }
+
+    match env::current_exe() {
+        Ok(exe) => {
+            let mut p = canonicalize(exe);
+            p.pop();
+            p.pop();
+            p
+        }
+        Err(e) => panic!("failed to get current_exe: {}", e),
+    }
+}
+
+// The name of the directory rustc expects libraries to be located.
+fn find_libdir(sysroot: &Path) -> Cow<'static, str> {
+    // FIXME: This is a quick hack to make the rustc binary able to locate
+    // Rust libraries in Linux environments where libraries might be installed
+    // to lib64/lib32. This would be more foolproof by basing the sysroot off
+    // of the directory where `librustc_driver` is located, rather than
+    // where the rustc binary is.
+    // If --libdir is set during configuration to the value other than
+    // "lib" (i.e., non-default), this value is used (see issue #16552).
+
+    #[cfg(target_pointer_width = "64")]
+    const PRIMARY_LIB_DIR: &str = "lib64";
+
+    #[cfg(target_pointer_width = "32")]
+    const PRIMARY_LIB_DIR: &str = "lib32";
+
+    const SECONDARY_LIB_DIR: &str = "lib";
+
+    match option_env!("CFG_LIBDIR_RELATIVE") {
+        Some(libdir) if libdir != "lib" => libdir.into(),
+        _ => {
+            if sysroot.join(PRIMARY_LIB_DIR).join(RUST_LIB_DIR).exists() {
+                PRIMARY_LIB_DIR.into()
+            } else {
+                SECONDARY_LIB_DIR.into()
+            }
+        }
+    }
+}
+
+// The name of rustc's own place to organize libraries.
+// Used to be "rustc", now the default is "rustlib"
+const RUST_LIB_DIR: &str = "rustlib";
diff --git a/compiler/rustc_session/src/lib.rs b/compiler/rustc_session/src/lib.rs
new file mode 100644
index 00000000000..c2ea141a06f
--- /dev/null
+++ b/compiler/rustc_session/src/lib.rs
@@ -0,0 +1,27 @@
+#![feature(crate_visibility_modifier)]
+#![feature(or_patterns)]
+
+#[macro_use]
+extern crate bitflags;
+#[macro_use]
+extern crate rustc_macros;
+
+pub mod cgu_reuse_tracker;
+pub mod utils;
+#[macro_use]
+pub mod lint;
+pub mod parse;
+
+mod code_stats;
+#[macro_use]
+pub mod config;
+pub mod filesearch;
+mod options;
+pub mod search_paths;
+
+mod session;
+pub use session::*;
+
+pub mod output;
+
+pub use getopts;
diff --git a/compiler/rustc_session/src/lint.rs b/compiler/rustc_session/src/lint.rs
new file mode 100644
index 00000000000..0dcbee08abe
--- /dev/null
+++ b/compiler/rustc_session/src/lint.rs
@@ -0,0 +1,425 @@
+pub use self::Level::*;
+use rustc_ast::node_id::{NodeId, NodeMap};
+use rustc_data_structures::stable_hasher::{HashStable, StableHasher, ToStableHashKey};
+use rustc_errors::{pluralize, Applicability, DiagnosticBuilder};
+use rustc_span::edition::Edition;
+use rustc_span::{sym, symbol::Ident, MultiSpan, Span, Symbol};
+
+pub mod builtin;
+
+/// Setting for how to handle a lint.
+#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]
+pub enum Level {
+    Allow,
+    Warn,
+    Deny,
+    Forbid,
+}
+
+rustc_data_structures::impl_stable_hash_via_hash!(Level);
+
+impl Level {
+    /// Converts a level to a lower-case string.
+    pub fn as_str(self) -> &'static str {
+        match self {
+            Level::Allow => "allow",
+            Level::Warn => "warn",
+            Level::Deny => "deny",
+            Level::Forbid => "forbid",
+        }
+    }
+
+    /// Converts a lower-case string to a level.
+    pub fn from_str(x: &str) -> Option<Level> {
+        match x {
+            "allow" => Some(Level::Allow),
+            "warn" => Some(Level::Warn),
+            "deny" => Some(Level::Deny),
+            "forbid" => Some(Level::Forbid),
+            _ => None,
+        }
+    }
+
+    /// Converts a symbol to a level.
+    pub fn from_symbol(x: Symbol) -> Option<Level> {
+        match x {
+            sym::allow => Some(Level::Allow),
+            sym::warn => Some(Level::Warn),
+            sym::deny => Some(Level::Deny),
+            sym::forbid => Some(Level::Forbid),
+            _ => None,
+        }
+    }
+}
+
+/// Specification of a single lint.
+#[derive(Copy, Clone, Debug)]
+pub struct Lint {
+    /// A string identifier for the lint.
+    ///
+    /// This identifies the lint in attributes and in command-line arguments.
+    /// In those contexts it is always lowercase, but this field is compared
+    /// in a way which is case-insensitive for ASCII characters. This allows
+    /// `declare_lint!()` invocations to follow the convention of upper-case
+    /// statics without repeating the name.
+    ///
+    /// The name is written with underscores, e.g., "unused_imports".
+    /// On the command line, underscores become dashes.
+    pub name: &'static str,
+
+    /// Default level for the lint.
+    pub default_level: Level,
+
+    /// Description of the lint or the issue it detects.
+    ///
+    /// e.g., "imports that are never used"
+    pub desc: &'static str,
+
+    /// Starting at the given edition, default to the given lint level. If this is `None`, then use
+    /// `default_level`.
+    pub edition_lint_opts: Option<(Edition, Level)>,
+
+    /// `true` if this lint is reported even inside expansions of external macros.
+    pub report_in_external_macro: bool,
+
+    pub future_incompatible: Option<FutureIncompatibleInfo>,
+
+    pub is_plugin: bool,
+
+    /// `Some` if this lint is feature gated, otherwise `None`.
+    pub feature_gate: Option<Symbol>,
+
+    pub crate_level_only: bool,
+}
+
+/// Extra information for a future incompatibility lint.
+#[derive(Copy, Clone, Debug)]
+pub struct FutureIncompatibleInfo {
+    /// e.g., a URL for an issue/PR/RFC or error code
+    pub reference: &'static str,
+    /// If this is an edition fixing lint, the edition in which
+    /// this lint becomes obsolete
+    pub edition: Option<Edition>,
+}
+
+impl Lint {
+    pub const fn default_fields_for_macro() -> Self {
+        Lint {
+            name: "",
+            default_level: Level::Forbid,
+            desc: "",
+            edition_lint_opts: None,
+            is_plugin: false,
+            report_in_external_macro: false,
+            future_incompatible: None,
+            feature_gate: None,
+            crate_level_only: false,
+        }
+    }
+
+    /// Gets the lint's name, with ASCII letters converted to lowercase.
+    pub fn name_lower(&self) -> String {
+        self.name.to_ascii_lowercase()
+    }
+
+    pub fn default_level(&self, edition: Edition) -> Level {
+        self.edition_lint_opts
+            .filter(|(e, _)| *e <= edition)
+            .map(|(_, l)| l)
+            .unwrap_or(self.default_level)
+    }
+}
+
+/// Identifies a lint known to the compiler.
+#[derive(Clone, Copy, Debug)]
+pub struct LintId {
+    // Identity is based on pointer equality of this field.
+    pub lint: &'static Lint,
+}
+
+impl PartialEq for LintId {
+    fn eq(&self, other: &LintId) -> bool {
+        std::ptr::eq(self.lint, other.lint)
+    }
+}
+
+impl Eq for LintId {}
+
+impl std::hash::Hash for LintId {
+    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
+        let ptr = self.lint as *const Lint;
+        ptr.hash(state);
+    }
+}
+
+impl LintId {
+    /// Gets the `LintId` for a `Lint`.
+    pub fn of(lint: &'static Lint) -> LintId {
+        LintId { lint }
+    }
+
+    pub fn lint_name_raw(&self) -> &'static str {
+        self.lint.name
+    }
+
+    /// Gets the name of the lint.
+    pub fn to_string(&self) -> String {
+        self.lint.name_lower()
+    }
+}
+
+impl<HCX> HashStable<HCX> for LintId {
+    #[inline]
+    fn hash_stable(&self, hcx: &mut HCX, hasher: &mut StableHasher) {
+        self.lint_name_raw().hash_stable(hcx, hasher);
+    }
+}
+
+impl<HCX> ToStableHashKey<HCX> for LintId {
+    type KeyType = &'static str;
+
+    #[inline]
+    fn to_stable_hash_key(&self, _: &HCX) -> &'static str {
+        self.lint_name_raw()
+    }
+}
+
+// This could be a closure, but then implementing derive trait
+// becomes hacky (and it gets allocated).
+#[derive(PartialEq)]
+pub enum BuiltinLintDiagnostics {
+    Normal,
+    BareTraitObject(Span, /* is_global */ bool),
+    AbsPathWithModule(Span),
+    ProcMacroDeriveResolutionFallback(Span),
+    MacroExpandedMacroExportsAccessedByAbsolutePaths(Span),
+    ElidedLifetimesInPaths(usize, Span, bool, Span, String),
+    UnknownCrateTypes(Span, String, String),
+    UnusedImports(String, Vec<(Span, String)>),
+    RedundantImport(Vec<(Span, bool)>, Ident),
+    DeprecatedMacro(Option<Symbol>, Span),
+    UnusedDocComment(Span),
+}
+
+/// Lints that are buffered up early on in the `Session` before the
+/// `LintLevels` is calculated.
+#[derive(PartialEq)]
+pub struct BufferedEarlyLint {
+    /// The span of code that we are linting on.
+    pub span: MultiSpan,
+
+    /// The lint message.
+    pub msg: String,
+
+    /// The `NodeId` of the AST node that generated the lint.
+    pub node_id: NodeId,
+
+    /// A lint Id that can be passed to
+    /// `rustc_lint::early::EarlyContextAndPass::check_id`.
+    pub lint_id: LintId,
+
+    /// Customization of the `DiagnosticBuilder<'_>` for the lint.
+    pub diagnostic: BuiltinLintDiagnostics,
+}
+
+#[derive(Default)]
+pub struct LintBuffer {
+    pub map: NodeMap<Vec<BufferedEarlyLint>>,
+}
+
+impl LintBuffer {
+    pub fn add_early_lint(&mut self, early_lint: BufferedEarlyLint) {
+        let arr = self.map.entry(early_lint.node_id).or_default();
+        if !arr.contains(&early_lint) {
+            arr.push(early_lint);
+        }
+    }
+
+    pub fn add_lint(
+        &mut self,
+        lint: &'static Lint,
+        node_id: NodeId,
+        span: MultiSpan,
+        msg: &str,
+        diagnostic: BuiltinLintDiagnostics,
+    ) {
+        let lint_id = LintId::of(lint);
+        let msg = msg.to_string();
+        self.add_early_lint(BufferedEarlyLint { lint_id, node_id, span, msg, diagnostic });
+    }
+
+    pub fn take(&mut self, id: NodeId) -> Vec<BufferedEarlyLint> {
+        self.map.remove(&id).unwrap_or_default()
+    }
+
+    pub fn buffer_lint(
+        &mut self,
+        lint: &'static Lint,
+        id: NodeId,
+        sp: impl Into<MultiSpan>,
+        msg: &str,
+    ) {
+        self.add_lint(lint, id, sp.into(), msg, BuiltinLintDiagnostics::Normal)
+    }
+
+    pub fn buffer_lint_with_diagnostic(
+        &mut self,
+        lint: &'static Lint,
+        id: NodeId,
+        sp: impl Into<MultiSpan>,
+        msg: &str,
+        diagnostic: BuiltinLintDiagnostics,
+    ) {
+        self.add_lint(lint, id, sp.into(), msg, diagnostic)
+    }
+}
+
+/// Declares a static item of type `&'static Lint`.
+#[macro_export]
+macro_rules! declare_lint {
+    ($vis: vis $NAME: ident, $Level: ident, $desc: expr) => (
+        $crate::declare_lint!(
+            $vis $NAME, $Level, $desc,
+        );
+    );
+    ($vis: vis $NAME: ident, $Level: ident, $desc: expr,
+     $(@future_incompatible = $fi:expr;)?
+     $(@feature_gate = $gate:expr;)?
+     $($v:ident),*) => (
+        $vis static $NAME: &$crate::lint::Lint = &$crate::lint::Lint {
+            name: stringify!($NAME),
+            default_level: $crate::lint::$Level,
+            desc: $desc,
+            edition_lint_opts: None,
+            is_plugin: false,
+            $($v: true,)*
+            $(future_incompatible: Some($fi),)*
+            $(feature_gate: Some($gate),)*
+            ..$crate::lint::Lint::default_fields_for_macro()
+        };
+    );
+    ($vis: vis $NAME: ident, $Level: ident, $desc: expr,
+     $lint_edition: expr => $edition_level: ident
+    ) => (
+        $vis static $NAME: &$crate::lint::Lint = &$crate::lint::Lint {
+            name: stringify!($NAME),
+            default_level: $crate::lint::$Level,
+            desc: $desc,
+            edition_lint_opts: Some(($lint_edition, $crate::lint::Level::$edition_level)),
+            report_in_external_macro: false,
+            is_plugin: false,
+        };
+    );
+}
+
+#[macro_export]
+macro_rules! declare_tool_lint {
+    (
+        $(#[$attr:meta])* $vis:vis $tool:ident ::$NAME:ident, $Level: ident, $desc: expr
+    ) => (
+        $crate::declare_tool_lint!{$(#[$attr])* $vis $tool::$NAME, $Level, $desc, false}
+    );
+    (
+        $(#[$attr:meta])* $vis:vis $tool:ident ::$NAME:ident, $Level:ident, $desc:expr,
+        report_in_external_macro: $rep:expr
+    ) => (
+         $crate::declare_tool_lint!{$(#[$attr])* $vis $tool::$NAME, $Level, $desc, $rep}
+    );
+    (
+        $(#[$attr:meta])* $vis:vis $tool:ident ::$NAME:ident, $Level:ident, $desc:expr,
+        $external:expr
+    ) => (
+        $(#[$attr])*
+        $vis static $NAME: &$crate::lint::Lint = &$crate::lint::Lint {
+            name: &concat!(stringify!($tool), "::", stringify!($NAME)),
+            default_level: $crate::lint::$Level,
+            desc: $desc,
+            edition_lint_opts: None,
+            report_in_external_macro: $external,
+            future_incompatible: None,
+            is_plugin: true,
+            feature_gate: None,
+            crate_level_only: false,
+        };
+    );
+}
+
+/// Declares a static `LintArray` and return it as an expression.
+#[macro_export]
+macro_rules! lint_array {
+    ($( $lint:expr ),* ,) => { lint_array!( $($lint),* ) };
+    ($( $lint:expr ),*) => {{
+        vec![$($lint),*]
+    }}
+}
+
+pub type LintArray = Vec<&'static Lint>;
+
+pub trait LintPass {
+    fn name(&self) -> &'static str;
+}
+
+/// Implements `LintPass for $ty` with the given list of `Lint` statics.
+#[macro_export]
+macro_rules! impl_lint_pass {
+    ($ty:ty => [$($lint:expr),* $(,)?]) => {
+        impl $crate::lint::LintPass for $ty {
+            fn name(&self) -> &'static str { stringify!($ty) }
+        }
+        impl $ty {
+            pub fn get_lints() -> $crate::lint::LintArray { $crate::lint_array!($($lint),*) }
+        }
+    };
+}
+
+/// Declares a type named `$name` which implements `LintPass`.
+/// To the right of `=>` a comma separated list of `Lint` statics is given.
+#[macro_export]
+macro_rules! declare_lint_pass {
+    ($(#[$m:meta])* $name:ident => [$($lint:expr),* $(,)?]) => {
+        $(#[$m])* #[derive(Copy, Clone)] pub struct $name;
+        $crate::impl_lint_pass!($name => [$($lint),*]);
+    };
+}
+
+pub fn add_elided_lifetime_in_path_suggestion(
+    sess: &crate::Session,
+    db: &mut DiagnosticBuilder<'_>,
+    n: usize,
+    path_span: Span,
+    incl_angl_brckt: bool,
+    insertion_span: Span,
+    anon_lts: String,
+) {
+    let (replace_span, suggestion) = if incl_angl_brckt {
+        (insertion_span, anon_lts)
+    } else {
+        // When possible, prefer a suggestion that replaces the whole
+        // `Path<T>` expression with `Path<'_, T>`, rather than inserting `'_, `
+        // at a point (which makes for an ugly/confusing label)
+        if let Ok(snippet) = sess.source_map().span_to_snippet(path_span) {
+            // But our spans can get out of whack due to macros; if the place we think
+            // we want to insert `'_` isn't even within the path expression's span, we
+            // should bail out of making any suggestion rather than panicking on a
+            // subtract-with-overflow or string-slice-out-out-bounds (!)
+            // FIXME: can we do better?
+            if insertion_span.lo().0 < path_span.lo().0 {
+                return;
+            }
+            let insertion_index = (insertion_span.lo().0 - path_span.lo().0) as usize;
+            if insertion_index > snippet.len() {
+                return;
+            }
+            let (before, after) = snippet.split_at(insertion_index);
+            (path_span, format!("{}{}{}", before, anon_lts, after))
+        } else {
+            (insertion_span, anon_lts)
+        }
+    };
+    db.span_suggestion(
+        replace_span,
+        &format!("indicate the anonymous lifetime{}", pluralize!(n)),
+        suggestion,
+        Applicability::MachineApplicable,
+    );
+}
diff --git a/compiler/rustc_session/src/lint/builtin.rs b/compiler/rustc_session/src/lint/builtin.rs
new file mode 100644
index 00000000000..2db4d2a7f51
--- /dev/null
+++ b/compiler/rustc_session/src/lint/builtin.rs
@@ -0,0 +1,624 @@
+//! Some lints that are built in to the compiler.
+//!
+//! These are the built-in lints that are emitted direct in the main
+//! compiler code, rather than using their own custom pass. Those
+//! lints are all available in `rustc_lint::builtin`.
+
+use crate::lint::FutureIncompatibleInfo;
+use crate::{declare_lint, declare_lint_pass};
+use rustc_span::edition::Edition;
+use rustc_span::symbol::sym;
+
+declare_lint! {
+    pub ILL_FORMED_ATTRIBUTE_INPUT,
+    Deny,
+    "ill-formed attribute inputs that were previously accepted and used in practice",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #57571 <https://github.com/rust-lang/rust/issues/57571>",
+        edition: None,
+    };
+    crate_level_only
+}
+
+declare_lint! {
+    pub CONFLICTING_REPR_HINTS,
+    Deny,
+    "conflicts between `#[repr(..)]` hints that were previously accepted and used in practice",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #68585 <https://github.com/rust-lang/rust/issues/68585>",
+        edition: None,
+    };
+}
+
+declare_lint! {
+    pub META_VARIABLE_MISUSE,
+    Allow,
+    "possible meta-variable misuse at macro definition"
+}
+
+declare_lint! {
+    pub INCOMPLETE_INCLUDE,
+    Deny,
+    "trailing content in included file"
+}
+
+declare_lint! {
+    pub ARITHMETIC_OVERFLOW,
+    Deny,
+    "arithmetic operation overflows"
+}
+
+declare_lint! {
+    pub UNCONDITIONAL_PANIC,
+    Deny,
+    "operation will cause a panic at runtime"
+}
+
+declare_lint! {
+    pub CONST_ERR,
+    Deny,
+    "constant evaluation detected erroneous expression",
+    report_in_external_macro
+}
+
+declare_lint! {
+    pub UNUSED_IMPORTS,
+    Warn,
+    "imports that are never used"
+}
+
+declare_lint! {
+    pub UNUSED_EXTERN_CRATES,
+    Allow,
+    "extern crates that are never used"
+}
+
+declare_lint! {
+    pub UNUSED_CRATE_DEPENDENCIES,
+    Allow,
+    "crate dependencies that are never used",
+    crate_level_only
+}
+
+declare_lint! {
+    pub UNUSED_QUALIFICATIONS,
+    Allow,
+    "detects unnecessarily qualified names"
+}
+
+declare_lint! {
+    pub UNKNOWN_LINTS,
+    Warn,
+    "unrecognized lint attribute"
+}
+
+declare_lint! {
+    pub UNUSED_VARIABLES,
+    Warn,
+    "detect variables which are not used in any way"
+}
+
+declare_lint! {
+    pub UNUSED_ASSIGNMENTS,
+    Warn,
+    "detect assignments that will never be read"
+}
+
+declare_lint! {
+    pub DEAD_CODE,
+    Warn,
+    "detect unused, unexported items"
+}
+
+declare_lint! {
+    pub UNUSED_ATTRIBUTES,
+    Warn,
+    "detects attributes that were not used by the compiler"
+}
+
+declare_lint! {
+    pub UNREACHABLE_CODE,
+    Warn,
+    "detects unreachable code paths",
+    report_in_external_macro
+}
+
+declare_lint! {
+    pub UNREACHABLE_PATTERNS,
+    Warn,
+    "detects unreachable patterns"
+}
+
+declare_lint! {
+    pub OVERLAPPING_PATTERNS,
+    Warn,
+    "detects overlapping patterns"
+}
+
+declare_lint! {
+    pub BINDINGS_WITH_VARIANT_NAME,
+    Warn,
+    "detects pattern bindings with the same name as one of the matched variants"
+}
+
+declare_lint! {
+    pub UNUSED_MACROS,
+    Warn,
+    "detects macros that were not used"
+}
+
+declare_lint! {
+    pub WARNINGS,
+    Warn,
+    "mass-change the level for lints which produce warnings"
+}
+
+declare_lint! {
+    pub UNUSED_FEATURES,
+    Warn,
+    "unused features found in crate-level `#[feature]` directives"
+}
+
+declare_lint! {
+    pub STABLE_FEATURES,
+    Warn,
+    "stable features found in `#[feature]` directive"
+}
+
+declare_lint! {
+    pub UNKNOWN_CRATE_TYPES,
+    Deny,
+    "unknown crate type found in `#[crate_type]` directive",
+    crate_level_only
+}
+
+declare_lint! {
+    pub TRIVIAL_CASTS,
+    Allow,
+    "detects trivial casts which could be removed"
+}
+
+declare_lint! {
+    pub TRIVIAL_NUMERIC_CASTS,
+    Allow,
+    "detects trivial casts of numeric types which could be removed"
+}
+
+declare_lint! {
+    pub PRIVATE_IN_PUBLIC,
+    Warn,
+    "detect private items in public interfaces not caught by the old implementation",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #34537 <https://github.com/rust-lang/rust/issues/34537>",
+        edition: None,
+    };
+}
+
+declare_lint! {
+    pub EXPORTED_PRIVATE_DEPENDENCIES,
+    Warn,
+    "public interface leaks type from a private dependency"
+}
+
+declare_lint! {
+    pub PUB_USE_OF_PRIVATE_EXTERN_CRATE,
+    Deny,
+    "detect public re-exports of private extern crates",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #34537 <https://github.com/rust-lang/rust/issues/34537>",
+        edition: None,
+    };
+}
+
+declare_lint! {
+    pub INVALID_TYPE_PARAM_DEFAULT,
+    Deny,
+    "type parameter default erroneously allowed in invalid location",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #36887 <https://github.com/rust-lang/rust/issues/36887>",
+        edition: None,
+    };
+}
+
+declare_lint! {
+    pub RENAMED_AND_REMOVED_LINTS,
+    Warn,
+    "lints that have been renamed or removed"
+}
+
+declare_lint! {
+    pub UNALIGNED_REFERENCES,
+    Allow,
+    "detects unaligned references to fields of packed structs",
+}
+
+declare_lint! {
+    pub SAFE_PACKED_BORROWS,
+    Warn,
+    "safe borrows of fields of packed structs were erroneously allowed",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #46043 <https://github.com/rust-lang/rust/issues/46043>",
+        edition: None,
+    };
+}
+
+declare_lint! {
+    pub PATTERNS_IN_FNS_WITHOUT_BODY,
+    Deny,
+    "patterns in functions without body were erroneously allowed",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #35203 <https://github.com/rust-lang/rust/issues/35203>",
+        edition: None,
+    };
+}
+
+declare_lint! {
+    pub LATE_BOUND_LIFETIME_ARGUMENTS,
+    Warn,
+    "detects generic lifetime arguments in path segments with late bound lifetime parameters",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #42868 <https://github.com/rust-lang/rust/issues/42868>",
+        edition: None,
+    };
+}
+
+declare_lint! {
+    pub ORDER_DEPENDENT_TRAIT_OBJECTS,
+    Deny,
+    "trait-object types were treated as different depending on marker-trait order",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #56484 <https://github.com/rust-lang/rust/issues/56484>",
+        edition: None,
+    };
+}
+
+declare_lint! {
+    pub COHERENCE_LEAK_CHECK,
+    Warn,
+    "distinct impls distinguished only by the leak-check code",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #56105 <https://github.com/rust-lang/rust/issues/56105>",
+        edition: None,
+    };
+}
+
+declare_lint! {
+    pub DEPRECATED,
+    Warn,
+    "detects use of deprecated items",
+    report_in_external_macro
+}
+
+declare_lint! {
+    pub UNUSED_UNSAFE,
+    Warn,
+    "unnecessary use of an `unsafe` block"
+}
+
+declare_lint! {
+    pub UNUSED_MUT,
+    Warn,
+    "detect mut variables which don't need to be mutable"
+}
+
+declare_lint! {
+    pub UNCONDITIONAL_RECURSION,
+    Warn,
+    "functions that cannot return without calling themselves"
+}
+
+declare_lint! {
+    pub SINGLE_USE_LIFETIMES,
+    Allow,
+    "detects lifetime parameters that are only used once"
+}
+
+declare_lint! {
+    pub UNUSED_LIFETIMES,
+    Allow,
+    "detects lifetime parameters that are never used"
+}
+
+declare_lint! {
+    pub TYVAR_BEHIND_RAW_POINTER,
+    Warn,
+    "raw pointer to an inference variable",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #46906 <https://github.com/rust-lang/rust/issues/46906>",
+        edition: Some(Edition::Edition2018),
+    };
+}
+
+declare_lint! {
+    pub ELIDED_LIFETIMES_IN_PATHS,
+    Allow,
+    "hidden lifetime parameters in types are deprecated",
+    crate_level_only
+}
+
+declare_lint! {
+    pub BARE_TRAIT_OBJECTS,
+    Warn,
+    "suggest using `dyn Trait` for trait objects"
+}
+
+declare_lint! {
+    pub ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE,
+    Allow,
+    "fully qualified paths that start with a module name \
+     instead of `crate`, `self`, or an extern crate name",
+     @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #53130 <https://github.com/rust-lang/rust/issues/53130>",
+        edition: Some(Edition::Edition2018),
+     };
+}
+
+declare_lint! {
+    pub ILLEGAL_FLOATING_POINT_LITERAL_PATTERN,
+    Warn,
+    "floating-point literals cannot be used in patterns",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #41620 <https://github.com/rust-lang/rust/issues/41620>",
+        edition: None,
+    };
+}
+
+declare_lint! {
+    pub UNSTABLE_NAME_COLLISIONS,
+    Warn,
+    "detects name collision with an existing but unstable method",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #48919 <https://github.com/rust-lang/rust/issues/48919>",
+        edition: None,
+        // Note: this item represents future incompatibility of all unstable functions in the
+        //       standard library, and thus should never be removed or changed to an error.
+    };
+}
+
+declare_lint! {
+    pub IRREFUTABLE_LET_PATTERNS,
+    Warn,
+    "detects irrefutable patterns in if-let and while-let statements"
+}
+
+declare_lint! {
+    pub UNUSED_LABELS,
+    Warn,
+    "detects labels that are never used"
+}
+
+declare_lint! {
+    pub BROKEN_INTRA_DOC_LINKS,
+    Warn,
+    "failures in resolving intra-doc link targets"
+}
+
+declare_lint! {
+    pub INVALID_CODEBLOCK_ATTRIBUTES,
+    Warn,
+    "codeblock attribute looks a lot like a known one"
+}
+
+declare_lint! {
+    pub MISSING_CRATE_LEVEL_DOCS,
+    Allow,
+    "detects crates with no crate-level documentation"
+}
+
+declare_lint! {
+    pub MISSING_DOC_CODE_EXAMPLES,
+    Allow,
+    "detects publicly-exported items without code samples in their documentation"
+}
+
+declare_lint! {
+    pub PRIVATE_DOC_TESTS,
+    Allow,
+    "detects code samples in docs of private items not documented by rustdoc"
+}
+
+declare_lint! {
+    pub WHERE_CLAUSES_OBJECT_SAFETY,
+    Warn,
+    "checks the object safety of where clauses",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #51443 <https://github.com/rust-lang/rust/issues/51443>",
+        edition: None,
+    };
+}
+
+declare_lint! {
+    pub PROC_MACRO_DERIVE_RESOLUTION_FALLBACK,
+    Warn,
+    "detects proc macro derives using inaccessible names from parent modules",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #50504 <https://github.com/rust-lang/rust/issues/50504>",
+        edition: None,
+    };
+}
+
+declare_lint! {
+    pub MACRO_USE_EXTERN_CRATE,
+    Allow,
+    "the `#[macro_use]` attribute is now deprecated in favor of using macros \
+     via the module system"
+}
+
+declare_lint! {
+    pub MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS,
+    Deny,
+    "macro-expanded `macro_export` macros from the current crate \
+     cannot be referred to by absolute paths",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #52234 <https://github.com/rust-lang/rust/issues/52234>",
+        edition: None,
+    };
+    crate_level_only
+}
+
+declare_lint! {
+    pub EXPLICIT_OUTLIVES_REQUIREMENTS,
+    Allow,
+    "outlives requirements can be inferred"
+}
+
+declare_lint! {
+    pub INDIRECT_STRUCTURAL_MATCH,
+    // defaulting to allow until rust-lang/rust#62614 is fixed.
+    Allow,
+    "pattern with const indirectly referencing non-structural-match type",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #62411 <https://github.com/rust-lang/rust/issues/62411>",
+        edition: None,
+    };
+}
+
+declare_lint! {
+    pub DEPRECATED_IN_FUTURE,
+    Allow,
+    "detects use of items that will be deprecated in a future version",
+    report_in_external_macro
+}
+
+declare_lint! {
+    pub AMBIGUOUS_ASSOCIATED_ITEMS,
+    Deny,
+    "ambiguous associated items",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #57644 <https://github.com/rust-lang/rust/issues/57644>",
+        edition: None,
+    };
+}
+
+declare_lint! {
+    pub MUTABLE_BORROW_RESERVATION_CONFLICT,
+    Warn,
+    "reservation of a two-phased borrow conflicts with other shared borrows",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #59159 <https://github.com/rust-lang/rust/issues/59159>",
+        edition: None,
+    };
+}
+
+declare_lint! {
+    pub SOFT_UNSTABLE,
+    Deny,
+    "a feature gate that doesn't break dependent crates",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #64266 <https://github.com/rust-lang/rust/issues/64266>",
+        edition: None,
+    };
+}
+
+declare_lint! {
+    pub INLINE_NO_SANITIZE,
+    Warn,
+    "detects incompatible use of `#[inline(always)]` and `#[no_sanitize(...)]`",
+}
+
+declare_lint! {
+    pub ASM_SUB_REGISTER,
+    Warn,
+    "using only a subset of a register for inline asm inputs",
+}
+
+declare_lint! {
+    pub UNSAFE_OP_IN_UNSAFE_FN,
+    Allow,
+    "unsafe operations in unsafe functions without an explicit unsafe block are deprecated",
+    @feature_gate = sym::unsafe_block_in_unsafe_fn;
+}
+
+declare_lint! {
+    pub CENUM_IMPL_DROP_CAST,
+    Warn,
+    "a C-like enum implementing Drop is cast",
+    @future_incompatible = FutureIncompatibleInfo {
+        reference: "issue #73333 <https://github.com/rust-lang/rust/issues/73333>",
+        edition: None,
+    };
+}
+
+declare_lint_pass! {
+    /// Does nothing as a lint pass, but registers some `Lint`s
+    /// that are used by other parts of the compiler.
+    HardwiredLints => [
+        ILLEGAL_FLOATING_POINT_LITERAL_PATTERN,
+        ARITHMETIC_OVERFLOW,
+        UNCONDITIONAL_PANIC,
+        UNUSED_IMPORTS,
+        UNUSED_EXTERN_CRATES,
+        UNUSED_CRATE_DEPENDENCIES,
+        UNUSED_QUALIFICATIONS,
+        UNKNOWN_LINTS,
+        UNUSED_VARIABLES,
+        UNUSED_ASSIGNMENTS,
+        DEAD_CODE,
+        UNREACHABLE_CODE,
+        UNREACHABLE_PATTERNS,
+        OVERLAPPING_PATTERNS,
+        BINDINGS_WITH_VARIANT_NAME,
+        UNUSED_MACROS,
+        WARNINGS,
+        UNUSED_FEATURES,
+        STABLE_FEATURES,
+        UNKNOWN_CRATE_TYPES,
+        TRIVIAL_CASTS,
+        TRIVIAL_NUMERIC_CASTS,
+        PRIVATE_IN_PUBLIC,
+        EXPORTED_PRIVATE_DEPENDENCIES,
+        PUB_USE_OF_PRIVATE_EXTERN_CRATE,
+        INVALID_TYPE_PARAM_DEFAULT,
+        CONST_ERR,
+        RENAMED_AND_REMOVED_LINTS,
+        UNALIGNED_REFERENCES,
+        SAFE_PACKED_BORROWS,
+        PATTERNS_IN_FNS_WITHOUT_BODY,
+        LATE_BOUND_LIFETIME_ARGUMENTS,
+        ORDER_DEPENDENT_TRAIT_OBJECTS,
+        COHERENCE_LEAK_CHECK,
+        DEPRECATED,
+        UNUSED_UNSAFE,
+        UNUSED_MUT,
+        UNCONDITIONAL_RECURSION,
+        SINGLE_USE_LIFETIMES,
+        UNUSED_LIFETIMES,
+        UNUSED_LABELS,
+        TYVAR_BEHIND_RAW_POINTER,
+        ELIDED_LIFETIMES_IN_PATHS,
+        BARE_TRAIT_OBJECTS,
+        ABSOLUTE_PATHS_NOT_STARTING_WITH_CRATE,
+        UNSTABLE_NAME_COLLISIONS,
+        IRREFUTABLE_LET_PATTERNS,
+        BROKEN_INTRA_DOC_LINKS,
+        INVALID_CODEBLOCK_ATTRIBUTES,
+        MISSING_CRATE_LEVEL_DOCS,
+        MISSING_DOC_CODE_EXAMPLES,
+        PRIVATE_DOC_TESTS,
+        WHERE_CLAUSES_OBJECT_SAFETY,
+        PROC_MACRO_DERIVE_RESOLUTION_FALLBACK,
+        MACRO_USE_EXTERN_CRATE,
+        MACRO_EXPANDED_MACRO_EXPORTS_ACCESSED_BY_ABSOLUTE_PATHS,
+        ILL_FORMED_ATTRIBUTE_INPUT,
+        CONFLICTING_REPR_HINTS,
+        META_VARIABLE_MISUSE,
+        DEPRECATED_IN_FUTURE,
+        AMBIGUOUS_ASSOCIATED_ITEMS,
+        MUTABLE_BORROW_RESERVATION_CONFLICT,
+        INDIRECT_STRUCTURAL_MATCH,
+        SOFT_UNSTABLE,
+        INLINE_NO_SANITIZE,
+        ASM_SUB_REGISTER,
+        UNSAFE_OP_IN_UNSAFE_FN,
+        INCOMPLETE_INCLUDE,
+        CENUM_IMPL_DROP_CAST,
+    ]
+}
+
+declare_lint! {
+    pub UNUSED_DOC_COMMENTS,
+    Warn,
+    "detects doc comments that aren't used by rustdoc"
+}
+
+declare_lint_pass!(UnusedDocComment => [UNUSED_DOC_COMMENTS]);
diff --git a/compiler/rustc_session/src/options.rs b/compiler/rustc_session/src/options.rs
new file mode 100644
index 00000000000..d05f1a3f34b
--- /dev/null
+++ b/compiler/rustc_session/src/options.rs
@@ -0,0 +1,1078 @@
+use crate::config::*;
+
+use crate::early_error;
+use crate::lint;
+use crate::search_paths::SearchPath;
+use crate::utils::NativeLibKind;
+
+use rustc_target::spec::{CodeModel, LinkerFlavor, MergeFunctions, PanicStrategy};
+use rustc_target::spec::{RelocModel, RelroLevel, TargetTriple, TlsModel};
+
+use rustc_feature::UnstableFeatures;
+use rustc_span::edition::Edition;
+use rustc_span::SourceFileHashAlgorithm;
+
+use std::collections::BTreeMap;
+
+use std::collections::hash_map::DefaultHasher;
+use std::hash::Hasher;
+use std::path::PathBuf;
+use std::str;
+
+macro_rules! hash_option {
+    ($opt_name:ident, $opt_expr:expr, $sub_hashes:expr, [UNTRACKED]) => {{}};
+    ($opt_name:ident, $opt_expr:expr, $sub_hashes:expr, [TRACKED]) => {{
+        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! top_level_options {
+    (pub struct Options { $(
+        $opt:ident : $t:ty [$dep_tracking_marker:ident $($warn_val:expr, $warn_text:expr)*],
+    )* } ) => (
+        #[derive(Clone)]
+        pub struct Options {
+            $(pub $opt: $t),*
+        }
+
+        impl Options {
+            pub fn dep_tracking_hash(&self) -> u64 {
+                let mut sub_hashes = BTreeMap::new();
+                $({
+                    hash_option!($opt,
+                                 &self.$opt,
+                                 &mut sub_hashes,
+                                 [$dep_tracking_marker $($warn_val,
+                                                         $warn_text,
+                                                         self.error_format)*]);
+                })*
+                let mut hasher = DefaultHasher::new();
+                dep_tracking::stable_hash(sub_hashes,
+                                          &mut hasher,
+                                          self.error_format);
+                hasher.finish()
+            }
+        }
+    );
+}
+
+// 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.
+//
+// [UNTRACKED]
+// Incremental compilation is not influenced by this option.
+//
+// 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.
+top_level_options!(
+    pub struct Options {
+        // The crate config requested for the session, which may be combined
+        // with additional crate configurations during the compile process.
+        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],
+        lint_opts: Vec<(String, lint::Level)> [TRACKED],
+        lint_cap: Option<lint::Level> [TRACKED],
+        describe_lints: bool [UNTRACKED],
+        output_types: OutputTypes [TRACKED],
+        search_paths: Vec<SearchPath> [UNTRACKED],
+        libs: Vec<(String, Option<String>, NativeLibKind)> [TRACKED],
+        maybe_sysroot: Option<PathBuf> [UNTRACKED],
+
+        target_triple: TargetTriple [TRACKED],
+
+        test: bool [TRACKED],
+        error_format: ErrorOutputType [UNTRACKED],
+
+        // If `Some`, enable incremental compilation, using the given
+        // directory to store intermediate results.
+        incremental: Option<PathBuf> [UNTRACKED],
+
+        debugging_opts: DebuggingOptions [TRACKED],
+        prints: Vec<PrintRequest> [UNTRACKED],
+        // Determines which borrow checker(s) to run. This is the parsed, sanitized
+        // version of `debugging_opts.borrowck`, which is just a plain string.
+        borrowck_mode: BorrowckMode [UNTRACKED],
+        cg: CodegenOptions [TRACKED],
+        externs: Externs [UNTRACKED],
+        crate_name: Option<String> [TRACKED],
+        // An optional name to use as the crate for std during std injection,
+        // written `extern crate name as std`. Defaults to `std`. Used by
+        // out-of-tree drivers.
+        alt_std_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],
+
+        // 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.
+        cli_forced_codegen_units: Option<usize> [UNTRACKED],
+        cli_forced_thinlto_off: bool [UNTRACKED],
+
+        // Remap source path prefixes in all output (messages, object files, debug, etc.).
+        remap_path_prefix: Vec<(PathBuf, PathBuf)> [UNTRACKED],
+
+        edition: Edition [TRACKED],
+
+        // `true` if we're emitting JSON blobs about each artifact produced
+        // by the compiler.
+        json_artifact_notifications: bool [TRACKED],
+
+        pretty: Option<PpMode> [UNTRACKED],
+    }
+);
+
+/// 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, $setter_name:ident, $defaultfn:ident,
+     $buildfn:ident, $prefix:expr, $outputname:expr,
+     $stat:ident, $mod_desc:ident, $mod_set:ident,
+     $($opt:ident : $t:ty = (
+        $init:expr,
+        $parse:ident,
+        [$dep_tracking_marker:ident $(($dep_warn_val:expr, $dep_warn_text:expr))*],
+        $desc:expr)
+     ),* ,) =>
+(
+    #[derive(Clone)]
+    pub struct $struct_name { $(pub $opt: $t),* }
+
+    pub fn $defaultfn() -> $struct_name {
+        $struct_name { $($opt: $init),* }
+    }
+
+    pub fn $buildfn(matches: &getopts::Matches, error_format: ErrorOutputType) -> $struct_name
+    {
+        let mut op = $defaultfn();
+        for option in matches.opt_strs($prefix) {
+            let mut iter = option.splitn(2, '=');
+            let key = iter.next().unwrap();
+            let value = iter.next();
+            let option_to_lookup = key.replace("-", "_");
+            let mut found = false;
+            for &(candidate, setter, type_desc, _) in $stat {
+                if option_to_lookup != candidate { continue }
+                if !setter(&mut op, value) {
+                    match value {
+                        None => {
+                            early_error(error_format, &format!("{0} option `{1}` requires \
+                                                                {2} ({3} {1}=<value>)",
+                                                               $outputname, key,
+                                                               type_desc, $prefix))
+                        }
+                        Some(value) => {
+                            early_error(error_format, &format!("incorrect value `{}` for {} \
+                                                                option `{}` - {} was expected",
+                                                               value, $outputname,
+                                                               key, type_desc))
+                        }
+                    }
+                }
+                found = true;
+                break;
+            }
+            if !found {
+                early_error(error_format, &format!("unknown {} option: `{}`",
+                                                   $outputname, key));
+            }
+        }
+        return op;
+    }
+
+    impl dep_tracking::DepTrackingHash for $struct_name {
+        fn hash(&self, hasher: &mut DefaultHasher, error_format: ErrorOutputType) {
+            let mut sub_hashes = BTreeMap::new();
+            $({
+                hash_option!($opt,
+                             &self.$opt,
+                             &mut sub_hashes,
+                             [$dep_tracking_marker $($dep_warn_val,
+                                                     $dep_warn_text,
+                                                     error_format)*]);
+            })*
+            dep_tracking::stable_hash(sub_hashes, hasher, error_format);
+        }
+    }
+
+    pub type $setter_name = fn(&mut $struct_name, v: Option<&str>) -> bool;
+    pub const $stat: &[(&str, $setter_name, &str, &str)] =
+        &[ $( (stringify!($opt), $mod_set::$opt, $mod_desc::$parse, $desc) ),* ];
+
+    #[allow(non_upper_case_globals, dead_code)]
+    mod $mod_desc {
+        pub const parse_no_flag: &str = "no value";
+        pub const parse_bool: &str = "one of: `y`, `yes`, `on`, `n`, `no`, or `off`";
+        pub const parse_opt_bool: &str = parse_bool;
+        pub const parse_string: &str = "a string";
+        pub const parse_opt_string: &str = parse_string;
+        pub const parse_string_push: &str = parse_string;
+        pub const parse_opt_pathbuf: &str = "a path";
+        pub const parse_pathbuf_push: &str = parse_opt_pathbuf;
+        pub const parse_list: &str = "a space-separated list of strings";
+        pub const parse_opt_list: &str = parse_list;
+        pub const parse_opt_comma_list: &str = "a comma-separated list of strings";
+        pub const parse_uint: &str = "a number";
+        pub const parse_opt_uint: &str = parse_uint;
+        pub const parse_threads: &str = parse_uint;
+        pub const parse_passes: &str = "a space-separated list of passes, or `all`";
+        pub const parse_panic_strategy: &str = "either `unwind` or `abort`";
+        pub const parse_relro_level: &str = "one of: `full`, `partial`, or `off`";
+        pub const parse_sanitizers: &str = "comma separated list of sanitizers: `address`, `leak`, `memory` or `thread`";
+        pub const parse_sanitizer_memory_track_origins: &str = "0, 1, or 2";
+        pub const parse_cfguard: &str =
+            "either a boolean (`yes`, `no`, `on`, `off`, etc), `checks`, or `nochecks`";
+        pub const parse_strip: &str = "either `none`, `debuginfo`, or `symbols`";
+        pub const parse_linker_flavor: &str = ::rustc_target::spec::LinkerFlavor::one_of();
+        pub const parse_optimization_fuel: &str = "crate=integer";
+        pub const parse_unpretty: &str = "`string` or `string=string`";
+        pub const parse_treat_err_as_bug: &str = "either no value or a number bigger than 0";
+        pub const parse_lto: &str =
+            "either a boolean (`yes`, `no`, `on`, `off`, etc), `thin`, `fat`, or omitted";
+        pub const parse_linker_plugin_lto: &str =
+            "either a boolean (`yes`, `no`, `on`, `off`, etc), or the path to the linker plugin";
+        pub const parse_switch_with_opt_path: &str =
+            "an optional path to the profiling data output directory";
+        pub const parse_merge_functions: &str = "one of: `disabled`, `trampolines`, or `aliases`";
+        pub const parse_symbol_mangling_version: &str = "either `legacy` or `v0` (RFC 2603)";
+        pub const parse_src_file_hash: &str = "either `md5` or `sha1`";
+        pub const parse_relocation_model: &str =
+            "one of supported relocation models (`rustc --print relocation-models`)";
+        pub const parse_code_model: &str =
+            "one of supported code models (`rustc --print code-models`)";
+        pub const parse_tls_model: &str =
+            "one of supported TLS models (`rustc --print tls-models`)";
+        pub const parse_target_feature: &str = parse_string;
+    }
+
+    #[allow(dead_code)]
+    mod $mod_set {
+        use super::*;
+        use std::str::FromStr;
+
+        // Sometimes different options need to build a common structure.
+        // That structure can 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 };
+        }
+
+        $(
+            pub fn $opt(cg: &mut $struct_name, v: Option<&str>) -> bool {
+                $parse(&mut redirect_field!(cg.$opt), v)
+            }
+        )*
+
+        /// This is for boolean options that don't take a value and start with
+        /// `no-`. This style of option is deprecated.
+        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.
+        fn parse_bool(slot: &mut bool, v: Option<&str>) -> bool {
+            match v {
+                Some("y") | Some("yes") | Some("on") | None => { *slot = true; true }
+                Some("n") | Some("no") | Some("off") => { *slot = false; true }
+                _ => false,
+            }
+        }
+
+        /// Use this for any boolean option that lacks a static default. (The
+        /// actions taken when such an option is not specified will depend on
+        /// other factors, such as other options, or target options.)
+        fn parse_opt_bool(slot: &mut Option<bool>, v: Option<&str>) -> bool {
+            match v {
+                Some("y") | Some("yes") | Some("on") | None => { *slot = Some(true); true }
+                Some("n") | Some("no") | Some("off") => { *slot = Some(false); true }
+                _ => false,
+            }
+        }
+
+        /// Use this for any string option that has a static default.
+        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.
+        fn parse_opt_string(slot: &mut Option<String>, v: Option<&str>) -> bool {
+            match v {
+                Some(s) => { *slot = Some(s.to_string()); true },
+                None => false,
+            }
+        }
+
+        fn parse_opt_pathbuf(slot: &mut Option<PathBuf>, v: Option<&str>) -> bool {
+            match v {
+                Some(s) => { *slot = Some(PathBuf::from(s)); true },
+                None => false,
+            }
+        }
+
+        fn parse_string_push(slot: &mut Vec<String>, v: Option<&str>) -> bool {
+            match v {
+                Some(s) => { slot.push(s.to_string()); true },
+                None => false,
+            }
+        }
+
+        fn parse_pathbuf_push(slot: &mut Vec<PathBuf>, v: Option<&str>) -> bool {
+            match v {
+                Some(s) => { slot.push(PathBuf::from(s)); true },
+                None => false,
+            }
+        }
+
+        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,
+            }
+        }
+
+        fn parse_opt_list(slot: &mut Option<Vec<String>>, v: Option<&str>)
+                      -> bool {
+            match v {
+                Some(s) => {
+                    let v = s.split_whitespace().map(|s| s.to_string()).collect();
+                    *slot = Some(v);
+                    true
+                },
+                None => false,
+            }
+        }
+
+        fn parse_opt_comma_list(slot: &mut Option<Vec<String>>, v: Option<&str>)
+                      -> bool {
+            match v {
+                Some(s) => {
+                    let v = s.split(',').map(|s| s.to_string()).collect();
+                    *slot = Some(v);
+                    true
+                },
+                None => false,
+            }
+        }
+
+        fn parse_threads(slot: &mut usize, v: Option<&str>) -> bool {
+            match v.and_then(|s| s.parse().ok()) {
+                Some(0) => { *slot = ::num_cpus::get(); true },
+                Some(i) => { *slot = i; true },
+                None => false
+            }
+        }
+
+        /// Use this for any uint option that has a static default.
+        fn parse_uint(slot: &mut usize, v: Option<&str>) -> bool {
+            match v.and_then(|s| s.parse().ok()) {
+                Some(i) => { *slot = i; true },
+                None => false
+            }
+        }
+
+        /// Use this for any uint option that lacks a static default.
+        fn parse_opt_uint(slot: &mut Option<usize>, v: Option<&str>) -> bool {
+            match v {
+                Some(s) => { *slot = s.parse().ok(); slot.is_some() }
+                None => false
+            }
+        }
+
+        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 = Passes::Some(passes);
+                        true
+                    } else {
+                        false
+                    }
+                }
+            }
+        }
+
+        fn parse_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
+        }
+
+        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
+        }
+
+        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,
+                        "leak" => SanitizerSet::LEAK,
+                        "memory" => SanitizerSet::MEMORY,
+                        "thread" => SanitizerSet::THREAD,
+                        _ => return false,
+                    }
+                }
+                true
+            } else {
+                false
+            }
+        }
+
+        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,
+            }
+        }
+
+        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
+        }
+
+        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
+        }
+
+        fn parse_linker_flavor(slote: &mut Option<LinkerFlavor>, v: Option<&str>) -> bool {
+            match v.and_then(LinkerFlavor::from_str) {
+                Some(lf) => *slote = Some(lf),
+                _ => return false,
+            }
+            true
+        }
+
+        fn parse_optimization_fuel(slot: &mut Option<(String, u64)>, v: Option<&str>) -> bool {
+            match v {
+                None => false,
+                Some(s) => {
+                    let parts = s.split('=').collect::<Vec<_>>();
+                    if parts.len() != 2 { return false; }
+                    let crate_name = parts[0].to_string();
+                    let fuel = parts[1].parse::<u64>();
+                    if fuel.is_err() { return false; }
+                    *slot = Some((crate_name, fuel.unwrap()));
+                    true
+                }
+            }
+        }
+
+        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,
+            }
+        }
+
+        fn parse_treat_err_as_bug(slot: &mut Option<usize>, v: Option<&str>) -> bool {
+            match v {
+                Some(s) => { *slot = s.parse().ok().filter(|&x| x != 0); slot.unwrap_or(0) != 0 }
+                None => { *slot = Some(1); true }
+            }
+        }
+
+        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
+        }
+
+        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
+        }
+
+        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
+        }
+
+        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
+        }
+
+        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
+        }
+
+        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
+        }
+
+        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
+        }
+
+        fn parse_symbol_mangling_version(
+            slot: &mut SymbolManglingVersion,
+            v: Option<&str>,
+        ) -> bool {
+            *slot = match v {
+                Some("legacy") => SymbolManglingVersion::Legacy,
+                Some("v0") => SymbolManglingVersion::V0,
+                _ => return false,
+            };
+            true
+        }
+
+        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
+        }
+
+        fn parse_target_feature(slot: &mut String, v: Option<&str>) -> bool {
+            match v {
+                Some(s) => {
+                    if !slot.is_empty() {
+                        slot.push_str(",");
+                    }
+                    slot.push_str(s);
+                    true
+                }
+                None => false,
+            }
+        }
+    }
+) }
+
+options! {CodegenOptions, CodegenSetter, basic_codegen_options,
+          build_codegen_options, "C", "codegen",
+          CG_OPTIONS, cg_type_desc, cgsetters,
+
+    // This list is in alphabetical order.
+    //
+    // If you add a new option, please update:
+    // - src/librustc_interface/tests.rs
+    // - src/doc/rustc/src/codegen-options/index.md
+
+    ar: String = (String::new(), parse_string, [UNTRACKED],
+        "this option is deprecated and does nothing"),
+    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_uint, [UNTRACKED],
+        "divide crate into N units to optimize in parallel"),
+    control_flow_guard: CFGuard = (CFGuard::Disabled, parse_cfguard, [TRACKED],
+        "use Windows Control Flow Guard (default: no)"),
+    debug_assertions: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "explicitly enable the `cfg(debug_assertions)` directive"),
+    debuginfo: usize = (0, parse_uint, [TRACKED],
+        "debug info emission level (0 = no debug info, 1 = line tables only, \
+        2 = full debug info with variable and type information; default: 0)"),
+    default_linker_libraries: bool = (false, parse_bool, [UNTRACKED],
+        "allow the linker to link its default libraries (default: no)"),
+    embed_bitcode: bool = (true, parse_bool, [TRACKED],
+        "emit bitcode in rlibs (default: yes)"),
+    extra_filename: String = (String::new(), parse_string, [UNTRACKED],
+        "extra data to put in each output filename"),
+    force_frame_pointers: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "force use of the frame pointers"),
+    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"),
+    inline_threshold: Option<usize> = (None, parse_opt_uint, [TRACKED],
+        "set the threshold for inlining a function"),
+    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)"),
+    link_dead_code: Option<bool> = (None, parse_opt_bool, [UNTRACKED],
+        "keep dead code at link time (useful for code coverage) (default: no)"),
+    linker: Option<PathBuf> = (None, parse_opt_pathbuf, [UNTRACKED],
+        "system linker to link outputs with"),
+    linker_flavor: Option<LinkerFlavor> = (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)"),
+    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"),
+    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)"),
+    overflow_checks: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "use overflow checks for integer arithmetic"),
+    panic: Option<PanicStrategy> = (None, parse_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"),
+    relocation_model: Option<RelocModel> = (None, parse_relocation_model, [TRACKED],
+        "control generation of position-independent code (PIC) \
+        (`rustc --print relocation-models` for details)"),
+    remark: Passes = (Passes::Some(Vec::new()), parse_passes, [UNTRACKED],
+        "print remarks for these optimization passes (space separated, or \"all\")"),
+    rpath: bool = (false, parse_bool, [UNTRACKED],
+        "set rpath values in libs/exes (default: no)"),
+    save_temps: bool = (false, parse_bool, [UNTRACKED],
+        "save all temporary output files during compilation (default: no)"),
+    soft_float: bool = (false, parse_bool, [TRACKED],
+        "use soft float ABI (*eabihf targets only) (default: no)"),
+    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."),
+
+    // This list is in alphabetical order.
+    //
+    // If you add a new option, please update:
+    // - src/librustc_interface/tests.rs
+    // - src/doc/rustc/src/codegen-options/index.md
+}
+
+options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
+          build_debugging_options, "Z", "debugging",
+          DB_OPTIONS, db_type_desc, dbsetters,
+
+    // This list is in alphabetical order.
+    //
+    // If you add a new option, please update:
+    // - src/librustc_interface/tests.rs
+
+    allow_features: Option<Vec<String>> = (None, parse_opt_comma_list, [TRACKED],
+        "only allow the listed language features to be enabled in code (space separated)"),
+    always_encode_mir: bool = (false, parse_bool, [TRACKED],
+        "encode MIR of all functions into the crate metadata (default: no)"),
+    asm_comments: bool = (false, parse_bool, [TRACKED],
+        "generate comments into the assembly (may change behavior) (default: no)"),
+    ast_json: bool = (false, parse_bool, [UNTRACKED],
+        "print the AST as JSON and halt (default: no)"),
+    ast_json_noexpand: bool = (false, parse_bool, [UNTRACKED],
+        "print the pre-expansion AST as JSON and halt (default: no)"),
+    binary_dep_depinfo: bool = (false, parse_bool, [TRACKED],
+        "include artifacts (sysroot, crate dependencies) used during compilation in dep-info \
+        (default: no)"),
+    borrowck: String = ("migrate".to_string(), parse_string, [UNTRACKED],
+        "select which borrowck is used (`mir` or `migrate`) (default: `migrate`)"),
+    borrowck_stats: bool = (false, parse_bool, [UNTRACKED],
+        "gather borrowck statistics (default: no)"),
+    cgu_partitioning_strategy: Option<String> = (None, parse_opt_string, [TRACKED],
+        "the codegen unit partitioning strategy to use"),
+    chalk: bool = (false, parse_bool, [TRACKED],
+        "enable the experimental Chalk-based trait solving engine"),
+    codegen_backend: Option<String> = (None, parse_opt_string, [TRACKED],
+        "the backend to use"),
+    crate_attr: Vec<String> = (Vec::new(), parse_string_push, [TRACKED],
+        "inject the given attribute in the crate"),
+    debug_macros: bool = (false, parse_bool, [TRACKED],
+        "emit line numbers debug info inside macros (default: no)"),
+    deduplicate_diagnostics: bool = (true, parse_bool, [UNTRACKED],
+        "deduplicate identical diagnostics (default: yes)"),
+    dep_info_omit_d_target: bool = (false, parse_bool, [TRACKED],
+        "in dep-info output, omit targets for tracking dependencies of the dep-info files \
+        themselves (default: no)"),
+    dep_tasks: bool = (false, parse_bool, [UNTRACKED],
+        "print tasks that execute and the color their dep node gets (requires debug build) \
+        (default: no)"),
+    dont_buffer_diagnostics: bool = (false, parse_bool, [UNTRACKED],
+        "emit diagnostics rather than buffering (breaks NLL error downgrading, sorting) \
+        (default: no)"),
+    dual_proc_macros: bool = (false, parse_bool, [TRACKED],
+        "load proc macros for both target and host, but only link to the target (default: no)"),
+    dump_dep_graph: bool = (false, parse_bool, [UNTRACKED],
+        "dump the dependency graph to $RUST_DEP_GRAPH (default: /tmp/dep_graph.gv) \
+        (default: no)"),
+    dump_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_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)"),
+    emit_stack_sizes: bool = (false, parse_bool, [UNTRACKED],
+        "emit a section containing stack size metadata (default: no)"),
+    fewer_names: bool = (false, parse_bool, [TRACKED],
+        "reduce memory use by retaining fewer names within compilation artifacts (LLVM-IR) \
+        (default: no)"),
+    force_overflow_checks: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "force overflow checks on or off"),
+    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"),
+    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)"),
+    incremental_ignore_spans: bool = (false, parse_bool, [UNTRACKED],
+        "ignore spans during ICH computation -- used for testing (default: no)"),
+    incremental_info: bool = (false, parse_bool, [UNTRACKED],
+        "print high-level information about incremental reuse (or the lack thereof) \
+        (default: no)"),
+    incremental_verify_ich: bool = (false, parse_bool, [UNTRACKED],
+        "verify incr. comp. hashes of green query instances (default: no)"),
+    inline_in_all_cgus: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "control whether `#[inline]` functions are in all CGUs"),
+    input_stats: bool = (false, parse_bool, [UNTRACKED],
+        "gather statistics about the input (default: no)"),
+    insert_sideeffect: bool = (false, parse_bool, [TRACKED],
+        "fix undefined behavior when a thread doesn't eventually make progress \
+        (such as entering an empty infinite loop) by inserting llvm.sideeffect \
+        (default: no)"),
+    instrument_coverage: bool = (false, parse_bool, [TRACKED],
+        "instrument the generated code to support LLVM source-based code coverage \
+        reports (note, the compiler build config must include `profiler = true`, \
+        and is mutually exclusive with `-C profile-generate`/`-C profile-use`); \
+        implies `-C link-dead-code` (unless explicitly disabled)` and \
+        `-Z symbol-mangling-version=v0`; and disables/overrides some optimization \
+        options (default: no)"),
+    instrument_mcount: bool = (false, parse_bool, [TRACKED],
+        "insert function instrument code for mcount-based tracing (default: no)"),
+    keep_hygiene_data: bool = (false, parse_bool, [UNTRACKED],
+        "keep hygiene data after analysis (default: no)"),
+    link_native_libraries: bool = (true, parse_bool, [UNTRACKED],
+        "link native libraries in the linker invocation (default: yes)"),
+    link_self_contained: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "control whether to link Rust provided C objects/libraries or rely
+         on C toolchain installed in the system"),
+    link_only: bool = (false, parse_bool, [TRACKED],
+        "link the `.rlink` file generated by `-Z no-link` (default: no)"),
+    llvm_time_trace: bool = (false, parse_bool, [UNTRACKED],
+        "generate JSON tracing data file from LLVM data (default: no)"),
+    ls: bool = (false, parse_bool, [UNTRACKED],
+        "list the symbols defined by a library crate (default: no)"),
+    macro_backtrace: bool = (false, parse_bool, [UNTRACKED],
+        "show macro backtraces (default: no)"),
+    merge_functions: Option<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)"),
+    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_opt_level: usize = (1, parse_uint, [TRACKED],
+        "MIR optimization level (0-3; default: 1)"),
+    mutable_noalias: bool = (false, parse_bool, [TRACKED],
+        "emit noalias metadata for mutable references (default: no)"),
+    new_llvm_pass_manager: bool = (false, parse_bool, [TRACKED],
+        "use new LLVM pass manager (default: no)"),
+    nll_facts: bool = (false, parse_bool, [UNTRACKED],
+        "dump facts from NLL analysis into side files (default: no)"),
+    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],
+        "run all passes except codegen; no output"),
+    no_generate_arange_section: bool = (false, parse_no_flag, [TRACKED],
+        "omit DWARF address ranges that give faster lookups"),
+    no_interleave_lints: bool = (false, parse_no_flag, [UNTRACKED],
+        "execute lints separately; allows benchmarking individual lints"),
+    no_leak_check: bool = (false, parse_no_flag, [UNTRACKED],
+        "disable the 'leak check' for subtyping; unsound, but useful for tests"),
+    no_link: bool = (false, parse_no_flag, [TRACKED],
+        "compile without linking"),
+    no_parallel_llvm: bool = (false, parse_no_flag, [UNTRACKED],
+        "run LLVM in non-parallel mode (while keeping codegen-units and ThinLTO)"),
+    no_profiler_runtime: bool = (false, parse_no_flag, [TRACKED],
+        "prevent automatic injection of the profiler_builtins crate"),
+    osx_rpath_install_name: bool = (false, parse_bool, [TRACKED],
+        "pass `-install_name @rpath/...` to the macOS linker (default: no)"),
+    panic_abort_tests: bool = (false, parse_bool, [TRACKED],
+        "support compiling tests with panic=abort (default: no)"),
+    parse_only: bool = (false, parse_bool, [UNTRACKED],
+        "parse only; do not compile, assemble, or link (default: no)"),
+    perf_stats: bool = (false, parse_bool, [UNTRACKED],
+        "print some performance-related statistics (default: no)"),
+    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)"),
+    polonius: bool = (false, parse_bool, [UNTRACKED],
+        "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)"),
+    print_fuel: Option<String> = (None, parse_opt_string, [TRACKED],
+        "make rustc print the total optimization fuel used by a crate"),
+    print_link_args: bool = (false, parse_bool, [UNTRACKED],
+        "print the arguments passed to the linker (default: no)"),
+    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"),
+    print_type_sizes: bool = (false, parse_bool, [UNTRACKED],
+        "print layout information for each type encountered (default: no)"),
+    profile: bool = (false, parse_bool, [TRACKED],
+        "insert profiling code (default: no)"),
+    profile_emit: Option<PathBuf> = (None, parse_opt_pathbuf, [TRACKED],
+        "file path to emit profiling data at runtime when using 'profile' \
+        (default based on relative source path)"),
+    query_dep_graph: bool = (false, parse_bool, [UNTRACKED],
+        "enable queries of the dependency graph for regression testing (default: no)"),
+    query_stats: bool = (false, parse_bool, [UNTRACKED],
+        "print some statistics about the query system (default: no)"),
+    relro_level: Option<RelroLevel> = (None, parse_relro_level, [TRACKED],
+        "choose which RELRO level to use"),
+    report_delayed_bugs: bool = (false, parse_bool, [TRACKED],
+        "immediately print bugs registered with `delay_span_bug` (default: no)"),
+    // The default historical behavior was to always run dsymutil, so we're
+    // preserving that temporarily, but we're likely to switch the default
+    // soon.
+    run_dsymutil: bool = (true, parse_bool, [TRACKED],
+        "if on Mac, run `dsymutil` and delete intermediate object files (default: yes)"),
+    sanitizer: SanitizerSet = (SanitizerSet::empty(), parse_sanitizers, [TRACKED],
+        "use a sanitizer"),
+    sanitizer_memory_track_origins: usize = (0, parse_sanitizer_memory_track_origins, [TRACKED],
+        "enable origins tracking in MemorySanitizer"),
+    sanitizer_recover: SanitizerSet = (SanitizerSet::empty(), parse_sanitizers, [TRACKED],
+        "enable recovery for selected sanitizers"),
+    saturating_float_casts: Option<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)"),
+    save_analysis: bool = (false, parse_bool, [UNTRACKED],
+        "write syntax and type analysis (in JSON format) information, in \
+        addition to normal output (default: no)"),
+    self_profile: SwitchWithOptPath = (SwitchWithOptPath::Disabled,
+        parse_switch_with_opt_path, [UNTRACKED],
+        "run the self profiler and output the raw event data"),
+    // keep this in sync with the event filter names in librustc_data_structures/profiling.rs
+    self_profile_events: Option<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, query-keys, function-args, args, llvm"),
+    share_generics: Option<bool> = (None, parse_opt_bool, [TRACKED],
+        "make the current crate share its generic instantiations"),
+    show_span: Option<String> = (None, parse_opt_string, [TRACKED],
+        "show spans for compiler debugging (expr|pat|ty)"),
+    span_debug: bool = (false, parse_bool, [UNTRACKED],
+        "forward proc_macro::Span's `Debug` impl to `Span`"),
+    // o/w tests have closure@path
+    span_free_formats: bool = (false, parse_bool, [UNTRACKED],
+        "exclude spans when debug-printing compiler state (default: no)"),
+    src_hash_algorithm: Option<SourceFileHashAlgorithm> = (None, parse_src_file_hash, [TRACKED],
+        "hash algorithm of source files in debug info (`md5`, or `sha1`)"),
+    strip: Strip = (Strip::None, parse_strip, [UNTRACKED],
+        "tell the linker which information to strip (`none` (default), `debuginfo` or `symbols`)"),
+    symbol_mangling_version: SymbolManglingVersion = (SymbolManglingVersion::Legacy,
+        parse_symbol_mangling_version, [TRACKED],
+        "which mangling version to use for symbol names"),
+    teach: bool = (false, parse_bool, [TRACKED],
+        "show extended diagnostic help (default: no)"),
+    terminal_width: Option<usize> = (None, parse_opt_uint, [UNTRACKED],
+        "set the current terminal width"),
+    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.
+    threads: usize = (1, parse_threads, [UNTRACKED],
+        "use a thread pool with N threads"),
+    time: bool = (false, parse_bool, [UNTRACKED],
+        "measure time of rustc processes (default: no)"),
+    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)"),
+    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)"),
+    treat_err_as_bug: Option<usize> = (None, parse_treat_err_as_bug, [TRACKED],
+        "treat error number `val` that occurs as bug"),
+    ui_testing: bool = (false, parse_bool, [UNTRACKED],
+        "emit compiler diagnostics in a form suitable for UI testing (default: no)"),
+    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;
+        valid types are any of the types for `--pretty`, as well as:
+        `expanded`, `expanded,identified`,
+        `expanded,hygiene` (with internal representations),
+        `everybody_loops` (all function bodies replaced with `loop {}`),
+        `hir` (the HIR), `hir,identified`,
+        `hir,typed` (HIR with types for each node),
+        `hir-tree` (dump the raw HIR),
+        `mir` (the MIR), or `mir-cfg` (graphviz formatted MIR)"),
+    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"),
+    validate_mir: bool = (false, parse_bool, [UNTRACKED],
+        "validate MIR after each transformation"),
+    verbose: bool = (false, parse_bool, [UNTRACKED],
+        "in general, enable more debug printouts (default: no)"),
+    verify_llvm_ir: bool = (false, parse_bool, [TRACKED],
+        "verify LLVM IR (default: no)"),
+
+    // This list is in alphabetical order.
+    //
+    // If you add a new option, please update:
+    // - src/librustc_interface/tests.rs
+}
diff --git a/compiler/rustc_session/src/output.rs b/compiler/rustc_session/src/output.rs
new file mode 100644
index 00000000000..bf9c96c6c94
--- /dev/null
+++ b/compiler/rustc_session/src/output.rs
@@ -0,0 +1,217 @@
+//! Related to out filenames of compilation (e.g. save analysis, binaries).
+use crate::config::{CrateType, Input, OutputFilenames, OutputType};
+use crate::Session;
+use rustc_ast as ast;
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+use std::path::{Path, PathBuf};
+
+pub fn out_filename(
+    sess: &Session,
+    crate_type: CrateType,
+    outputs: &OutputFilenames,
+    crate_name: &str,
+) -> PathBuf {
+    let default_filename = filename_for_input(sess, crate_type, crate_name, outputs);
+    let out_filename = outputs
+        .outputs
+        .get(&OutputType::Exe)
+        .and_then(|s| s.to_owned())
+        .or_else(|| outputs.single_output_file.clone())
+        .unwrap_or(default_filename);
+
+    check_file_is_writeable(&out_filename, sess);
+
+    out_filename
+}
+
+/// Make sure files are writeable.  Mac, FreeBSD, and Windows system linkers
+/// check this already -- however, the Linux linker will happily overwrite a
+/// read-only file.  We should be consistent.
+pub fn check_file_is_writeable(file: &Path, sess: &Session) {
+    if !is_writeable(file) {
+        sess.fatal(&format!(
+            "output file {} is not writeable -- check its \
+                            permissions",
+            file.display()
+        ));
+    }
+}
+
+fn is_writeable(p: &Path) -> bool {
+    match p.metadata() {
+        Err(..) => true,
+        Ok(m) => !m.permissions().readonly(),
+    }
+}
+
+pub fn find_crate_name(sess: &Session, attrs: &[ast::Attribute], input: &Input) -> String {
+    let validate = |s: String, span: Option<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 =
+        sess.find_by_name(attrs, sym::crate_name).and_then(|at| at.value_str().map(|s| (at, s)));
+
+    if let Some(ref s) = sess.opts.crate_name {
+        if let Some((attr, name)) = attr_crate_name {
+            if name.as_str() != *s {
+                let msg = format!(
+                    "`--crate-name` and `#[crate_name]` are \
+                                   required to match, but `{}` != `{}`",
+                    s, name
+                );
+                sess.span_err(attr.span, &msg);
+            }
+        }
+        return validate(s.clone(), None);
+    }
+
+    if let Some((attr, s)) = attr_crate_name {
+        return validate(s.to_string(), Some(attr.span));
+    }
+    if let Input::File(ref path) = *input {
+        if let Some(s) = path.file_stem().and_then(|s| s.to_str()) {
+            if s.starts_with('-') {
+                let msg = format!(
+                    "crate names cannot start with a `-`, but \
+                                   `{}` has a leading hyphen",
+                    s
+                );
+                sess.err(&msg);
+            } else {
+                return validate(s.replace("-", "_"), None);
+            }
+        }
+    }
+
+    "rust_out".to_string()
+}
+
+pub fn validate_crate_name(sess: &Session, s: &str, sp: Option<Span>) {
+    let mut err_count = 0;
+    {
+        let mut say = |s: &str| {
+            match sp {
+                Some(sp) => sess.span_err(sp, s),
+                None => sess.err(s),
+            }
+            err_count += 1;
+        };
+        if s.is_empty() {
+            say("crate name must not be empty");
+        }
+        for c in s.chars() {
+            if c.is_alphanumeric() {
+                continue;
+            }
+            if c == '_' {
+                continue;
+            }
+            say(&format!("invalid character `{}` in crate name: `{}`", c, s));
+        }
+    }
+
+    if err_count > 0 {
+        sess.abort_if_errors();
+    }
+}
+
+pub fn filename_for_metadata(
+    sess: &Session,
+    crate_name: &str,
+    outputs: &OutputFilenames,
+) -> PathBuf {
+    let libname = format!("{}{}", crate_name, sess.opts.cg.extra_filename);
+
+    let out_filename = outputs
+        .single_output_file
+        .clone()
+        .unwrap_or_else(|| outputs.out_directory.join(&format!("lib{}.rmeta", libname)));
+
+    check_file_is_writeable(&out_filename, sess);
+
+    out_filename
+}
+
+pub fn filename_for_input(
+    sess: &Session,
+    crate_type: CrateType,
+    crate_name: &str,
+    outputs: &OutputFilenames,
+) -> PathBuf {
+    let libname = format!("{}{}", crate_name, sess.opts.cg.extra_filename);
+
+    match crate_type {
+        CrateType::Rlib => outputs.out_directory.join(&format!("lib{}.rlib", libname)),
+        CrateType::Cdylib | CrateType::ProcMacro | CrateType::Dylib => {
+            let (prefix, suffix) =
+                (&sess.target.target.options.dll_prefix, &sess.target.target.options.dll_suffix);
+            outputs.out_directory.join(&format!("{}{}{}", prefix, libname, suffix))
+        }
+        CrateType::Staticlib => {
+            let (prefix, suffix) = (
+                &sess.target.target.options.staticlib_prefix,
+                &sess.target.target.options.staticlib_suffix,
+            );
+            outputs.out_directory.join(&format!("{}{}{}", prefix, libname, suffix))
+        }
+        CrateType::Executable => {
+            let suffix = &sess.target.target.options.exe_suffix;
+            let out_filename = outputs.path(OutputType::Exe);
+            if suffix.is_empty() { out_filename } else { out_filename.with_extension(&suffix[1..]) }
+        }
+    }
+}
+
+/// Returns default crate type for target
+///
+/// Default crate type is used when crate type isn't provided neither
+/// through cmd line arguments nor through crate attributes
+///
+/// It is CrateType::Executable for all platforms but iOS as there is no
+/// way to run iOS binaries anyway without jailbreaking and
+/// interaction with Rust code through static library is the only
+/// option for now
+pub fn default_output_for_target(sess: &Session) -> CrateType {
+    if !sess.target.target.options.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 {
+    match crate_type {
+        CrateType::Cdylib | CrateType::Dylib | CrateType::ProcMacro => {
+            if !sess.target.target.options.dynamic_linking {
+                return true;
+            }
+            if sess.crt_static(Some(crate_type))
+                && !sess.target.target.options.crt_static_allows_dylibs
+            {
+                return true;
+            }
+        }
+        _ => {}
+    }
+    if sess.target.target.options.only_cdylib {
+        match crate_type {
+            CrateType::ProcMacro | CrateType::Dylib => return true,
+            _ => {}
+        }
+    }
+    if !sess.target.target.options.executables {
+        if crate_type == CrateType::Executable {
+            return true;
+        }
+    }
+
+    false
+}
diff --git a/compiler/rustc_session/src/parse.rs b/compiler/rustc_session/src/parse.rs
new file mode 100644
index 00000000000..6f10d0c4b89
--- /dev/null
+++ b/compiler/rustc_session/src/parse.rs
@@ -0,0 +1,239 @@
+//! Contains `ParseSess` which holds state living beyond what one `Parser` might.
+//! It also serves as an input to the parser itself.
+
+use crate::lint::{BufferedEarlyLint, BuiltinLintDiagnostics, Lint, LintId};
+use rustc_ast::node_id::NodeId;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_data_structures::sync::{Lock, Lrc, OnceCell};
+use rustc_errors::{emitter::SilentEmitter, ColorConfig, Handler};
+use rustc_errors::{error_code, Applicability, DiagnosticBuilder};
+use rustc_feature::{find_feature_issue, GateIssue, UnstableFeatures};
+use rustc_span::edition::Edition;
+use rustc_span::hygiene::ExpnId;
+use rustc_span::source_map::{FilePathMapping, SourceMap};
+use rustc_span::{MultiSpan, Span, Symbol};
+
+use std::path::PathBuf;
+use std::str;
+
+/// The set of keys (and, optionally, values) that define the compilation
+/// environment of the crate, used to drive conditional compilation.
+pub type CrateConfig = FxHashSet<(Symbol, Option<Symbol>)>;
+
+/// 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 `active.rs`.
+    pub fn gate(&self, feature: Symbol, span: Span) {
+        self.spans.borrow_mut().entry(feature).or_default().push(span);
+    }
+
+    /// Ungate the last span under the given `feature`.
+    /// Panics if the given `span` wasn't the last one.
+    ///
+    /// Using this is discouraged unless you have a really good reason to.
+    pub fn ungate_last(&self, feature: Symbol, span: Span) {
+        let removed_span = self.spans.borrow_mut().entry(feature).or_default().pop().unwrap();
+        debug_assert_eq!(span, removed_span);
+    }
+
+    /// Is the provided `feature` gate ungated currently?
+    ///
+    /// Using this is discouraged unless you have a really good reason to.
+    pub fn is_ungated(&self, feature: Symbol) -> bool {
+        self.spans.borrow().get(&feature).map_or(true, |spans| spans.is_empty())
+    }
+
+    /// Prepend the given set of `spans` onto the set in `self`.
+    pub fn merge(&self, mut spans: FxHashMap<Symbol, Vec<Span>>) {
+        let mut inner = self.spans.borrow_mut();
+        for (gate, mut gate_spans) in inner.drain() {
+            spans.entry(gate).or_default().append(&mut gate_spans);
+        }
+        *inner = spans;
+    }
+}
+
+#[derive(Default)]
+pub struct SymbolGallery {
+    /// All symbols occurred and their first occurrence span.
+    pub symbols: Lock<FxHashMap<Symbol, Span>>,
+}
+
+impl SymbolGallery {
+    /// Insert a symbol and its span into symbol gallery.
+    /// If the symbol has occurred before, ignore the new occurance.
+    pub fn insert(&self, symbol: Symbol, span: Span) {
+        self.symbols.lock().entry(symbol).or_insert(span);
+    }
+}
+
+/// Construct a diagnostic for a language feature error due to the given `span`.
+/// The `feature`'s `Symbol` is the one you used in `active.rs` and `rustc_span::symbols`.
+pub fn feature_err<'a>(
+    sess: &'a ParseSess,
+    feature: Symbol,
+    span: impl Into<MultiSpan>,
+    explain: &str,
+) -> DiagnosticBuilder<'a> {
+    feature_err_issue(sess, feature, span, GateIssue::Language, explain)
+}
+
+/// Construct a diagnostic for a feature gate error.
+///
+/// This variant allows you to control whether it is a library or language feature.
+/// Almost always, you want to use this for a language feature. If so, prefer `feature_err`.
+pub fn feature_err_issue<'a>(
+    sess: &'a ParseSess,
+    feature: Symbol,
+    span: impl Into<MultiSpan>,
+    issue: GateIssue,
+    explain: &str,
+) -> DiagnosticBuilder<'a> {
+    let mut err = sess.span_diagnostic.struct_span_err_with_code(span, explain, error_code!(E0658));
+
+    if let Some(n) = find_feature_issue(feature, issue) {
+        err.note(&format!(
+            "see issue #{} <https://github.com/rust-lang/rust/issues/{}> for more information",
+            n, n,
+        ));
+    }
+
+    // #23973: do not suggest `#![feature(...)]` if we are in beta/stable
+    if sess.unstable_features.is_nightly_build() {
+        err.help(&format!("add `#![feature({})]` to the crate attributes to enable", feature));
+    }
+
+    err
+}
+
+/// Info about a parsing session.
+pub struct ParseSess {
+    pub span_diagnostic: Handler,
+    pub unstable_features: UnstableFeatures,
+    pub config: CrateConfig,
+    pub edition: Edition,
+    /// Places where raw identifiers were used. This is used for feature-gating raw identifiers.
+    pub raw_identifier_spans: Lock<Vec<Span>>,
+    /// Used to determine and report recursive module inclusions.
+    pub included_mod_stack: Lock<Vec<PathBuf>>,
+    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<FxHashMap<Span, Span>>,
+    pub injected_crate_name: OnceCell<Symbol>,
+    pub gated_spans: GatedSpans,
+    pub symbol_gallery: SymbolGallery,
+    /// The parser has reached `Eof` due to an unclosed brace. Used to silence unnecessary errors.
+    pub reached_eof: Lock<bool>,
+    /// Environment variables accessed during the build and their values when they exist.
+    pub env_depinfo: Lock<FxHashSet<(Symbol, Option<Symbol>)>>,
+    /// All the type ascriptions expressions that have had a suggestion for likely path typo.
+    pub type_ascription_path_suggestions: Lock<FxHashSet<Span>>,
+}
+
+impl ParseSess {
+    pub fn new(file_path_mapping: FilePathMapping) -> Self {
+        let sm = Lrc::new(SourceMap::new(file_path_mapping));
+        let handler = Handler::with_tty_emitter(ColorConfig::Auto, true, None, Some(sm.clone()));
+        ParseSess::with_span_handler(handler, sm)
+    }
+
+    pub fn with_span_handler(handler: Handler, source_map: Lrc<SourceMap>) -> Self {
+        Self {
+            span_diagnostic: handler,
+            unstable_features: UnstableFeatures::from_environment(),
+            config: FxHashSet::default(),
+            edition: ExpnId::root().expn_data().edition,
+            raw_identifier_spans: Lock::new(Vec::new()),
+            included_mod_stack: Lock::new(vec![]),
+            source_map,
+            buffered_lints: Lock::new(vec![]),
+            ambiguous_block_expr_parse: Lock::new(FxHashMap::default()),
+            injected_crate_name: OnceCell::new(),
+            gated_spans: GatedSpans::default(),
+            symbol_gallery: SymbolGallery::default(),
+            reached_eof: Lock::new(false),
+            env_depinfo: Default::default(),
+            type_ascription_path_suggestions: Default::default(),
+        }
+    }
+
+    pub fn with_silent_emitter() -> Self {
+        let sm = Lrc::new(SourceMap::new(FilePathMapping::empty()));
+        let handler = Handler::with_emitter(false, None, Box::new(SilentEmitter));
+        ParseSess::with_span_handler(handler, sm)
+    }
+
+    #[inline]
+    pub fn source_map(&self) -> &SourceMap {
+        &self.source_map
+    }
+
+    pub fn clone_source_map(&self) -> Lrc<SourceMap> {
+        self.source_map.clone()
+    }
+
+    pub fn buffer_lint(
+        &self,
+        lint: &'static Lint,
+        span: impl Into<MultiSpan>,
+        node_id: NodeId,
+        msg: &str,
+    ) {
+        self.buffered_lints.with_lock(|buffered_lints| {
+            buffered_lints.push(BufferedEarlyLint {
+                span: span.into(),
+                node_id,
+                msg: msg.into(),
+                lint_id: LintId::of(lint),
+                diagnostic: BuiltinLintDiagnostics::Normal,
+            });
+        });
+    }
+
+    pub fn buffer_lint_with_diagnostic(
+        &self,
+        lint: &'static Lint,
+        span: impl Into<MultiSpan>,
+        node_id: NodeId,
+        msg: &str,
+        diagnostic: BuiltinLintDiagnostics,
+    ) {
+        self.buffered_lints.with_lock(|buffered_lints| {
+            buffered_lints.push(BufferedEarlyLint {
+                span: span.into(),
+                node_id,
+                msg: msg.into(),
+                lint_id: LintId::of(lint),
+                diagnostic,
+            });
+        });
+    }
+
+    /// Extend an error with a suggestion to wrap an expression with parentheses to allow the
+    /// parser to continue parsing the following operation as part of the same expression.
+    pub fn expr_parentheses_needed(
+        &self,
+        err: &mut DiagnosticBuilder<'_>,
+        span: Span,
+        alt_snippet: Option<String>,
+    ) {
+        if let Some(snippet) = self.source_map().span_to_snippet(span).ok().or(alt_snippet) {
+            err.span_suggestion(
+                span,
+                "parentheses are required to parse this as an expression",
+                format!("({})", snippet),
+                Applicability::MachineApplicable,
+            );
+        }
+    }
+}
diff --git a/compiler/rustc_session/src/search_paths.rs b/compiler/rustc_session/src/search_paths.rs
new file mode 100644
index 00000000000..e12364b7dac
--- /dev/null
+++ b/compiler/rustc_session/src/search_paths.rs
@@ -0,0 +1,95 @@
+use crate::filesearch::make_target_lib_path;
+use crate::{config, early_error};
+use std::path::{Path, PathBuf};
+
+#[derive(Clone, Debug)]
+pub struct SearchPath {
+    pub kind: PathKind,
+    pub dir: PathBuf,
+    pub files: Vec<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 `Option<String>` containing the
+// `PathBuf`'s filename. The prefix and suffix checking is much faster on the
+// `Option<String>` than the `PathBuf`. (It's an `Option` because
+// `Path::file_name` can fail; if that happens then all subsequent checking
+// will also fail, which is fine.)
+#[derive(Clone, Debug)]
+pub struct SearchPathFile {
+    pub path: PathBuf,
+    pub file_name_str: Option<String>,
+}
+
+impl SearchPathFile {
+    fn new(path: PathBuf) -> SearchPathFile {
+        let file_name_str = path.file_name().and_then(|f| f.to_str()).map(|s| s.to_string());
+        SearchPathFile { path, file_name_str }
+    }
+}
+
+#[derive(PartialEq, Clone, Copy, Debug, Hash, Eq, Encodable, Decodable)]
+pub enum PathKind {
+    Native,
+    Crate,
+    Dependency,
+    Framework,
+    ExternFlag,
+    All,
+}
+
+rustc_data_structures::impl_stable_hash_via_hash!(PathKind);
+
+impl PathKind {
+    pub fn matches(&self, kind: PathKind) -> bool {
+        match (self, kind) {
+            (PathKind::All, _) | (_, PathKind::All) => true,
+            _ => *self == kind,
+        }
+    }
+}
+
+impl SearchPath {
+    pub fn from_cli_opt(path: &str, output: config::ErrorOutputType) -> Self {
+        let (kind, path) = if path.starts_with("native=") {
+            (PathKind::Native, &path["native=".len()..])
+        } else if path.starts_with("crate=") {
+            (PathKind::Crate, &path["crate=".len()..])
+        } else if path.starts_with("dependency=") {
+            (PathKind::Dependency, &path["dependency=".len()..])
+        } else if path.starts_with("framework=") {
+            (PathKind::Framework, &path["framework=".len()..])
+        } else if path.starts_with("all=") {
+            (PathKind::All, &path["all=".len()..])
+        } else {
+            (PathKind::All, path)
+        };
+        if path.is_empty() {
+            early_error(output, "empty search path given via `-L`");
+        }
+
+        let dir = PathBuf::from(path);
+        Self::new(kind, dir)
+    }
+
+    pub fn from_sysroot_and_triple(sysroot: &Path, triple: &str) -> Self {
+        Self::new(PathKind::All, make_target_lib_path(sysroot, triple))
+    }
+
+    fn new(kind: PathKind, dir: PathBuf) -> Self {
+        // Get the files within the directory.
+        let files = match std::fs::read_dir(&dir) {
+            Ok(files) => files
+                .filter_map(|e| e.ok().map(|e| SearchPathFile::new(e.path())))
+                .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..c006e593e47
--- /dev/null
+++ b/compiler/rustc_session/src/session.rs
@@ -0,0 +1,1539 @@
+use crate::cgu_reuse_tracker::CguReuseTracker;
+use crate::code_stats::CodeStats;
+pub use crate::code_stats::{DataTypeKind, FieldInfo, SizeKind, VariantInfo};
+use crate::config::{self, CrateType, OutputType, PrintRequest, SanitizerSet, SwitchWithOptPath};
+use crate::filesearch;
+use crate::lint;
+use crate::parse::ParseSess;
+use crate::search_paths::{PathKind, SearchPath};
+
+pub use rustc_ast::attr::MarkedAttrs;
+pub use rustc_ast::crate_disambiguator::CrateDisambiguator;
+pub use rustc_ast::Attribute;
+use rustc_data_structures::flock;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_data_structures::jobserver::{self, Client};
+use rustc_data_structures::profiling::{duration_to_secs_str, SelfProfiler, SelfProfilerRef};
+use rustc_data_structures::sync::{
+    self, AtomicU64, AtomicUsize, Lock, Lrc, OnceCell, OneThread, Ordering, Ordering::SeqCst,
+};
+use rustc_errors::annotate_snippet_emitter_writer::AnnotateSnippetEmitterWriter;
+use rustc_errors::emitter::{Emitter, EmitterWriter, HumanReadableErrorType};
+use rustc_errors::json::JsonEmitter;
+use rustc_errors::registry::Registry;
+use rustc_errors::{Applicability, DiagnosticBuilder, DiagnosticId, ErrorReported};
+use rustc_span::edition::Edition;
+use rustc_span::source_map::{FileLoader, MultiSpan, RealFileLoader, SourceMap, Span};
+use rustc_span::{sym, SourceFileHashAlgorithm, Symbol};
+use rustc_target::asm::InlineAsmArch;
+use rustc_target::spec::{CodeModel, PanicStrategy, RelocModel, RelroLevel};
+use rustc_target::spec::{Target, TargetTriple, TlsModel};
+
+use std::cell::{self, RefCell};
+use std::env;
+use std::fmt;
+use std::io::Write;
+use std::num::NonZeroU32;
+use std::ops::{Div, Mul};
+use std::path::PathBuf;
+use std::str::FromStr;
+use std::sync::Arc;
+use std::time::Duration;
+
+pub struct OptimizationFuel {
+    /// If `-zfuel=crate=n` is specified, initially set to `n`, otherwise `0`.
+    remaining: u64,
+    /// We're rejecting all further optimizations.
+    out_of_fuel: bool,
+}
+
+/// The behavior of the CTFE engine when an error occurs with regards to backtraces.
+#[derive(Clone, Copy)]
+pub enum CtfeBacktrace {
+    /// Do nothing special, return the error as usual without a backtrace.
+    Disabled,
+    /// Capture a backtrace at the point the error is created and return it in the error
+    /// (to be printed later if/when the error ever actually gets shown to the user).
+    Capture,
+    /// Capture a backtrace at the point the error is created and immediately print it out.
+    Immediate,
+}
+
+/// New-type wrapper around `usize` for representing limits. Ensures that comparisons against
+/// limits are consistent throughout the compiler.
+#[derive(Clone, Copy, Debug)]
+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.
+    pub fn value_within_limit(&self, value: usize) -> bool {
+        value <= self.0
+    }
+}
+
+impl fmt::Display for Limit {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        write!(f, "{}", self.0)
+    }
+}
+
+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)
+    }
+}
+
+/// Represents the data associated with a compilation
+/// session for a single crate.
+pub struct Session {
+    pub target: config::Config,
+    pub host: Target,
+    pub opts: config::Options,
+    pub host_tlib_path: SearchPath,
+    /// `None` if the host and target are the same.
+    pub target_tlib_path: Option<SearchPath>,
+    pub parse_sess: ParseSess,
+    pub sysroot: PathBuf,
+    /// The name of the root source file of the crate, in the local file system.
+    /// `None` means that there is no source file.
+    pub local_crate_source_file: Option<PathBuf>,
+    /// The directory the compiler has been executed in plus a flag indicating
+    /// if the value stored here has been affected by path remapping.
+    pub working_dir: (PathBuf, bool),
+
+    /// Set of `(DiagnosticId, Option<Span>, message)` tuples tracking
+    /// (sub)diagnostics that have been set once, but should not be set again,
+    /// in order to avoid redundantly verbose output (Issue #24690, #44953).
+    pub one_time_diagnostics: Lock<FxHashSet<(DiagnosticMessageId, Option<Span>, String)>>,
+    crate_types: OnceCell<Vec<CrateType>>,
+    /// The `crate_disambiguator` is constructed out of all the `-C metadata`
+    /// arguments passed to the compiler. Its value together with the crate-name
+    /// forms a unique global identifier for the crate. It is used to allow
+    /// multiple crates with the same name to coexist. See the
+    /// `rustc_codegen_llvm::back::symbol_names` module for more information.
+    pub crate_disambiguator: OnceCell<CrateDisambiguator>,
+
+    features: OnceCell<rustc_feature::Features>,
+
+    /// The maximum recursion limit for potentially infinitely recursive
+    /// operations such as auto-dereference and monomorphization.
+    pub recursion_limit: OnceCell<Limit>,
+
+    /// The maximum length of types during monomorphization.
+    pub type_length_limit: OnceCell<Limit>,
+
+    /// The maximum blocks a const expression can evaluate.
+    pub const_eval_limit: OnceCell<Limit>,
+
+    incr_comp_session: OneThread<RefCell<IncrCompSession>>,
+    /// Used for incremental compilation tests. Will only be populated if
+    /// `-Zquery-dep-graph` is specified.
+    pub cgu_reuse_tracker: CguReuseTracker,
+
+    /// Used by `-Z self-profile`.
+    pub prof: SelfProfilerRef,
+
+    /// Some measurements that are being gathered during compilation.
+    pub perf_stats: PerfStats,
+
+    /// Data about code being compiled, gathered during compilation.
+    pub code_stats: CodeStats,
+
+    /// If `-zfuel=crate=n` is specified, `Some(crate)`.
+    optimization_fuel_crate: Option<String>,
+
+    /// Tracks fuel info if `-zfuel=crate=n` is specified.
+    optimization_fuel: Lock<OptimizationFuel>,
+
+    // The next two are public because the driver needs to read them.
+    /// If `-zprint-fuel=crate`, `Some(crate)`.
+    pub print_fuel_crate: Option<String>,
+    /// Always set to zero and incremented so that we can print fuel expended by a crate.
+    pub print_fuel: AtomicU64,
+
+    /// Loaded up early on in the initialization of this `Session` to avoid
+    /// false positives about a job server in our environment.
+    pub jobserver: Client,
+
+    /// Cap lint level specified by a driver specifically.
+    pub driver_lint_caps: FxHashMap<lint::LintId, lint::Level>,
+
+    /// `Span`s of trait methods that weren't found to avoid emitting object safety errors
+    pub trait_methods_not_found: Lock<FxHashSet<Span>>,
+
+    /// Mapping from ident span to path span for paths that don't exist as written, but that
+    /// exist under `std`. For example, wrote `str::from_utf8` instead of `std::str::from_utf8`.
+    pub confused_type_with_std_module: Lock<FxHashMap<Span, Span>>,
+
+    /// Path for libraries that will take preference over libraries shipped by Rust.
+    /// Used by windows-gnu targets to priortize system mingw-w64 libraries.
+    pub system_library_path: OneThread<RefCell<Option<Option<PathBuf>>>>,
+
+    /// 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.
+    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>)>>,
+
+    /// Base directory containing the `src/` for the Rust standard library, and
+    /// potentially `rustc` as well, if we can can find it. Right now it's always
+    /// `$sysroot/lib/rustlib/src/rust` (i.e. the `rustup` `rust-src` component).
+    ///
+    /// This directory is what the virtual `/rustc/$hash` is translated back to,
+    /// if Rust was built with path remapping to `/rustc/$hash` enabled
+    /// (the `rust.remap-debuginfo` option in `config.toml`).
+    pub real_rust_source_base_dir: Option<PathBuf>,
+
+    /// Architecture to use for interpreting asm!.
+    pub asm_arch: Option<InlineAsmArch>,
+
+    /// Set of enabled features for the current target.
+    pub target_features: FxHashSet<Symbol>,
+
+    known_attrs: Lock<MarkedAttrs>,
+    used_attrs: Lock<MarkedAttrs>,
+}
+
+pub struct PerfStats {
+    /// The accumulated time spent on computing symbol hashes.
+    pub symbol_hash_time: Lock<Duration>,
+    /// Total number of values canonicalized queries constructed.
+    pub queries_canonicalized: AtomicUsize,
+    /// Number of times this query is invoked.
+    pub normalize_generic_arg_after_erasing_regions: AtomicUsize,
+    /// Number of times this query is invoked.
+    pub normalize_projection_ty: AtomicUsize,
+}
+
+/// Enum to support dispatch of one-time diagnostics (in `Session.diag_once`).
+enum DiagnosticBuilderMethod {
+    Note,
+    SpanNote,
+    SpanSuggestion(String), // suggestion
+                            // Add more variants as needed to support one-time diagnostics.
+}
+
+/// Diagnostic message ID, used by `Session.one_time_diagnostics` to avoid
+/// emitting the same message more than once.
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum DiagnosticMessageId {
+    ErrorId(u16), // EXXXX error code as integer
+    LintId(lint::LintId),
+    StabilityId(Option<NonZeroU32>), // issue number
+}
+
+impl From<&'static lint::Lint> for DiagnosticMessageId {
+    fn from(lint: &'static lint::Lint) -> Self {
+        DiagnosticMessageId::LintId(lint::LintId::of(lint))
+    }
+}
+
+impl Session {
+    pub fn miri_unleashed_feature(&self, span: Span, feature_gate: Option<Symbol>) {
+        self.miri_unleashed_features.lock().push((span, feature_gate));
+    }
+
+    fn check_miri_unleashed_features(&self) {
+        let unleashed_features = self.miri_unleashed_features.lock();
+        if !unleashed_features.is_empty() {
+            let mut must_err = false;
+            // Create a diagnostic pointing at where things got unleashed.
+            let mut diag = self.struct_warn("skipping const checks");
+            for &(span, feature_gate) in unleashed_features.iter() {
+                // FIXME: `span_label` doesn't do anything, so we use "help" as a hack.
+                if let Some(feature_gate) = feature_gate {
+                    diag.span_help(span, &format!("skipping check for `{}` feature", feature_gate));
+                    // The unleash flag must *not* be used to just "hack around" feature gates.
+                    must_err = true;
+                } else {
+                    diag.span_help(span, "skipping check that does not even have a feature gate");
+                }
+            }
+            diag.emit();
+            // If we should err, make sure we did.
+            if must_err && !self.has_errors() {
+                // We have skipped a feature gate, and not run into other errors... reject.
+                self.err(
+                    "`-Zunleash-the-miri-inside-of-you` may not be used to circumvent feature \
+                     gates, except when testing error paths in the CTFE engine",
+                );
+            }
+        }
+    }
+
+    /// Invoked all the way at the end to finish off diagnostics printing.
+    pub fn finish_diagnostics(&self, registry: &Registry) {
+        self.check_miri_unleashed_features();
+        self.diagnostic().print_error_count(registry);
+    }
+
+    pub fn local_crate_disambiguator(&self) -> CrateDisambiguator {
+        self.crate_disambiguator.get().copied().unwrap()
+    }
+
+    pub fn crate_types(&self) -> &[CrateType] {
+        self.crate_types.get().unwrap().as_slice()
+    }
+
+    pub fn init_crate_types(&self, crate_types: Vec<CrateType>) {
+        self.crate_types.set(crate_types).expect("`crate_types` was initialized twice")
+    }
+
+    pub fn recursion_limit(&self) -> Limit {
+        self.recursion_limit.get().copied().unwrap()
+    }
+
+    pub fn type_length_limit(&self) -> Limit {
+        self.type_length_limit.get().copied().unwrap()
+    }
+
+    pub fn const_eval_limit(&self) -> Limit {
+        self.const_eval_limit.get().copied().unwrap()
+    }
+
+    pub fn struct_span_warn<S: Into<MultiSpan>>(&self, sp: S, msg: &str) -> DiagnosticBuilder<'_> {
+        self.diagnostic().struct_span_warn(sp, msg)
+    }
+    pub fn struct_span_warn_with_code<S: Into<MultiSpan>>(
+        &self,
+        sp: S,
+        msg: &str,
+        code: DiagnosticId,
+    ) -> DiagnosticBuilder<'_> {
+        self.diagnostic().struct_span_warn_with_code(sp, msg, code)
+    }
+    pub fn struct_warn(&self, msg: &str) -> DiagnosticBuilder<'_> {
+        self.diagnostic().struct_warn(msg)
+    }
+    pub fn struct_span_err<S: Into<MultiSpan>>(&self, sp: S, msg: &str) -> DiagnosticBuilder<'_> {
+        self.diagnostic().struct_span_err(sp, msg)
+    }
+    pub fn struct_span_err_with_code<S: Into<MultiSpan>>(
+        &self,
+        sp: S,
+        msg: &str,
+        code: DiagnosticId,
+    ) -> DiagnosticBuilder<'_> {
+        self.diagnostic().struct_span_err_with_code(sp, msg, code)
+    }
+    // FIXME: This method should be removed (every error should have an associated error code).
+    pub fn struct_err(&self, msg: &str) -> DiagnosticBuilder<'_> {
+        self.diagnostic().struct_err(msg)
+    }
+    pub fn struct_err_with_code(&self, msg: &str, code: DiagnosticId) -> DiagnosticBuilder<'_> {
+        self.diagnostic().struct_err_with_code(msg, code)
+    }
+    pub fn struct_span_fatal<S: Into<MultiSpan>>(&self, sp: S, msg: &str) -> DiagnosticBuilder<'_> {
+        self.diagnostic().struct_span_fatal(sp, msg)
+    }
+    pub fn struct_span_fatal_with_code<S: Into<MultiSpan>>(
+        &self,
+        sp: S,
+        msg: &str,
+        code: DiagnosticId,
+    ) -> DiagnosticBuilder<'_> {
+        self.diagnostic().struct_span_fatal_with_code(sp, msg, code)
+    }
+    pub fn struct_fatal(&self, msg: &str) -> DiagnosticBuilder<'_> {
+        self.diagnostic().struct_fatal(msg)
+    }
+
+    pub fn span_fatal<S: Into<MultiSpan>>(&self, sp: S, msg: &str) -> ! {
+        self.diagnostic().span_fatal(sp, msg).raise()
+    }
+    pub fn span_fatal_with_code<S: Into<MultiSpan>>(
+        &self,
+        sp: S,
+        msg: &str,
+        code: DiagnosticId,
+    ) -> ! {
+        self.diagnostic().span_fatal_with_code(sp, msg, code).raise()
+    }
+    pub fn fatal(&self, msg: &str) -> ! {
+        self.diagnostic().fatal(msg).raise()
+    }
+    pub fn span_err_or_warn<S: Into<MultiSpan>>(&self, is_warning: bool, sp: S, msg: &str) {
+        if is_warning {
+            self.span_warn(sp, msg);
+        } else {
+            self.span_err(sp, msg);
+        }
+    }
+    pub fn span_err<S: Into<MultiSpan>>(&self, sp: S, msg: &str) {
+        self.diagnostic().span_err(sp, msg)
+    }
+    pub fn span_err_with_code<S: Into<MultiSpan>>(&self, sp: S, msg: &str, code: DiagnosticId) {
+        self.diagnostic().span_err_with_code(sp, &msg, code)
+    }
+    pub fn err(&self, msg: &str) {
+        self.diagnostic().err(msg)
+    }
+    pub fn err_count(&self) -> usize {
+        self.diagnostic().err_count()
+    }
+    pub fn has_errors(&self) -> bool {
+        self.diagnostic().has_errors()
+    }
+    pub fn has_errors_or_delayed_span_bugs(&self) -> bool {
+        self.diagnostic().has_errors_or_delayed_span_bugs()
+    }
+    pub fn abort_if_errors(&self) {
+        self.diagnostic().abort_if_errors();
+    }
+    pub fn compile_status(&self) -> Result<(), ErrorReported> {
+        if self.has_errors() {
+            self.diagnostic().emit_stashed_diagnostics();
+            Err(ErrorReported)
+        } else {
+            Ok(())
+        }
+    }
+    // FIXME(matthewjasper) Remove this method, it should never be needed.
+    pub fn track_errors<F, T>(&self, f: F) -> Result<T, ErrorReported>
+    where
+        F: FnOnce() -> T,
+    {
+        let old_count = self.err_count();
+        let result = f();
+        let errors = self.err_count() - old_count;
+        if errors == 0 { Ok(result) } else { Err(ErrorReported) }
+    }
+    pub fn span_warn<S: Into<MultiSpan>>(&self, sp: S, msg: &str) {
+        self.diagnostic().span_warn(sp, msg)
+    }
+    pub fn span_warn_with_code<S: Into<MultiSpan>>(&self, sp: S, msg: &str, code: DiagnosticId) {
+        self.diagnostic().span_warn_with_code(sp, msg, code)
+    }
+    pub fn warn(&self, msg: &str) {
+        self.diagnostic().warn(msg)
+    }
+    pub fn opt_span_warn<S: Into<MultiSpan>>(&self, opt_sp: Option<S>, msg: &str) {
+        match opt_sp {
+            Some(sp) => self.span_warn(sp, msg),
+            None => self.warn(msg),
+        }
+    }
+    /// Delay a span_bug() call until abort_if_errors()
+    #[track_caller]
+    pub fn delay_span_bug<S: Into<MultiSpan>>(&self, sp: S, msg: &str) {
+        self.diagnostic().delay_span_bug(sp, msg)
+    }
+    pub fn note_without_error(&self, msg: &str) {
+        self.diagnostic().note_without_error(msg)
+    }
+    pub fn span_note_without_error<S: Into<MultiSpan>>(&self, sp: S, msg: &str) {
+        self.diagnostic().span_note_without_error(sp, msg)
+    }
+    pub fn struct_note_without_error(&self, msg: &str) -> DiagnosticBuilder<'_> {
+        self.diagnostic().struct_note_without_error(msg)
+    }
+
+    pub fn diagnostic(&self) -> &rustc_errors::Handler {
+        &self.parse_sess.span_diagnostic
+    }
+
+    /// Analogous to calling methods on the given `DiagnosticBuilder`, but
+    /// deduplicates on lint ID, span (if any), and message for this `Session`
+    fn diag_once<'a, 'b>(
+        &'a self,
+        diag_builder: &'b mut DiagnosticBuilder<'a>,
+        method: DiagnosticBuilderMethod,
+        msg_id: DiagnosticMessageId,
+        message: &str,
+        span_maybe: Option<Span>,
+    ) {
+        let id_span_message = (msg_id, span_maybe, message.to_owned());
+        let fresh = self.one_time_diagnostics.borrow_mut().insert(id_span_message);
+        if fresh {
+            match method {
+                DiagnosticBuilderMethod::Note => {
+                    diag_builder.note(message);
+                }
+                DiagnosticBuilderMethod::SpanNote => {
+                    let span = span_maybe.expect("`span_note` needs a span");
+                    diag_builder.span_note(span, message);
+                }
+                DiagnosticBuilderMethod::SpanSuggestion(suggestion) => {
+                    let span = span_maybe.expect("`span_suggestion_*` needs a span");
+                    diag_builder.span_suggestion(
+                        span,
+                        message,
+                        suggestion,
+                        Applicability::Unspecified,
+                    );
+                }
+            }
+        }
+    }
+
+    pub fn diag_span_note_once<'a, 'b>(
+        &'a self,
+        diag_builder: &'b mut DiagnosticBuilder<'a>,
+        msg_id: DiagnosticMessageId,
+        span: Span,
+        message: &str,
+    ) {
+        self.diag_once(
+            diag_builder,
+            DiagnosticBuilderMethod::SpanNote,
+            msg_id,
+            message,
+            Some(span),
+        );
+    }
+
+    pub fn diag_note_once<'a, 'b>(
+        &'a self,
+        diag_builder: &'b mut DiagnosticBuilder<'a>,
+        msg_id: DiagnosticMessageId,
+        message: &str,
+    ) {
+        self.diag_once(diag_builder, DiagnosticBuilderMethod::Note, msg_id, message, None);
+    }
+
+    pub fn diag_span_suggestion_once<'a, 'b>(
+        &'a self,
+        diag_builder: &'b mut DiagnosticBuilder<'a>,
+        msg_id: DiagnosticMessageId,
+        span: Span,
+        message: &str,
+        suggestion: String,
+    ) {
+        self.diag_once(
+            diag_builder,
+            DiagnosticBuilderMethod::SpanSuggestion(suggestion),
+            msg_id,
+            message,
+            Some(span),
+        );
+    }
+
+    #[inline]
+    pub fn source_map(&self) -> &SourceMap {
+        self.parse_sess.source_map()
+    }
+    pub fn verbose(&self) -> bool {
+        self.opts.debugging_opts.verbose
+    }
+    pub fn time_passes(&self) -> bool {
+        self.opts.debugging_opts.time_passes || self.opts.debugging_opts.time
+    }
+    pub fn instrument_mcount(&self) -> bool {
+        self.opts.debugging_opts.instrument_mcount
+    }
+    pub fn time_llvm_passes(&self) -> bool {
+        self.opts.debugging_opts.time_llvm_passes
+    }
+    pub fn meta_stats(&self) -> bool {
+        self.opts.debugging_opts.meta_stats
+    }
+    pub fn asm_comments(&self) -> bool {
+        self.opts.debugging_opts.asm_comments
+    }
+    pub fn verify_llvm_ir(&self) -> bool {
+        self.opts.debugging_opts.verify_llvm_ir || option_env!("RUSTC_VERIFY_LLVM_IR").is_some()
+    }
+    pub fn borrowck_stats(&self) -> bool {
+        self.opts.debugging_opts.borrowck_stats
+    }
+    pub fn print_llvm_passes(&self) -> bool {
+        self.opts.debugging_opts.print_llvm_passes
+    }
+    pub fn binary_dep_depinfo(&self) -> bool {
+        self.opts.debugging_opts.binary_dep_depinfo
+    }
+
+    /// Gets the features enabled for the current compilation session.
+    /// DO NOT USE THIS METHOD if there is a TyCtxt available, as it circumvents
+    /// dependency tracking. Use tcx.features() instead.
+    #[inline]
+    pub fn features_untracked(&self) -> &rustc_feature::Features {
+        self.features.get().unwrap()
+    }
+
+    pub fn init_features(&self, features: rustc_feature::Features) {
+        match self.features.set(features) {
+            Ok(()) => {}
+            Err(_) => panic!("`features` was initialized twice"),
+        }
+    }
+
+    /// 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.target.options.requires_lto {
+            return config::Lto::Fat;
+        }
+
+        // If the user specified something, return that. If they only said `-C
+        // lto` and we've for whatever reason forced off ThinLTO via the CLI,
+        // then ensure we can't use a ThinLTO.
+        match self.opts.cg.lto {
+            config::LtoCli::Unspecified => {
+                // The compiler was invoked without the `-Clto` flag. Fall
+                // through to the default handling
+            }
+            config::LtoCli::No => {
+                // The user explicitly opted out of any kind of LTO
+                return config::Lto::No;
+            }
+            config::LtoCli::Yes | config::LtoCli::Fat | config::LtoCli::NoParam => {
+                // All of these mean fat LTO
+                return config::Lto::Fat;
+            }
+            config::LtoCli::Thin => {
+                return if self.opts.cli_forced_thinlto_off {
+                    config::Lto::Fat
+                } else {
+                    config::Lto::Thin
+                };
+            }
+        }
+
+        // Ok at this point the target doesn't require anything and the user
+        // hasn't asked for anything. Our next decision is whether or not
+        // we enable "auto" ThinLTO where we use multiple codegen units and
+        // then do ThinLTO over those codegen units. The logic below will
+        // either return `No` or `ThinLocal`.
+
+        // If processing command line options determined that we're incompatible
+        // with ThinLTO (e.g., `-C lto --emit llvm-ir`) then return that option.
+        if self.opts.cli_forced_thinlto_off {
+            return config::Lto::No;
+        }
+
+        // If `-Z thinlto` specified process that, but note that this is mostly
+        // a deprecated option now that `-C lto=thin` exists.
+        if let Some(enabled) = self.opts.debugging_opts.thinlto {
+            if enabled {
+                return config::Lto::ThinLocal;
+            } else {
+                return config::Lto::No;
+            }
+        }
+
+        // If there's only one codegen unit and LTO isn't enabled then there's
+        // no need for ThinLTO so just return false.
+        if self.codegen_units() == 1 {
+            return config::Lto::No;
+        }
+
+        // Now we're in "defaults" territory. By default we enable ThinLTO for
+        // optimized compiles (anything greater than O0).
+        match self.opts.optimize {
+            config::OptLevel::No => config::Lto::No,
+            _ => config::Lto::ThinLocal,
+        }
+    }
+
+    /// Returns the panic strategy for this compile session. If the user explicitly selected one
+    /// using '-C panic', use that, otherwise use the panic strategy defined by the target.
+    pub fn panic_strategy(&self) -> PanicStrategy {
+        self.opts.cg.panic.unwrap_or(self.target.target.options.panic_strategy)
+    }
+    pub fn fewer_names(&self) -> bool {
+        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.debugging_opts.sanitizer.intersects(SanitizerSet::ADDRESS | SanitizerSet::MEMORY);
+
+        self.opts.debugging_opts.fewer_names || !more_names
+    }
+
+    pub fn unstable_options(&self) -> bool {
+        self.opts.debugging_opts.unstable_options
+    }
+    pub fn overflow_checks(&self) -> bool {
+        self.opts
+            .cg
+            .overflow_checks
+            .or(self.opts.debugging_opts.force_overflow_checks)
+            .unwrap_or(self.opts.debug_assertions)
+    }
+
+    /// Check whether this compile session and crate type use static crt.
+    pub fn crt_static(&self, crate_type: Option<CrateType>) -> bool {
+        if !self.target.target.options.crt_static_respected {
+            // If the target does not opt in to crt-static support, use its default.
+            return self.target.target.options.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");
+
+        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.target.options.crt_static_default
+        }
+    }
+
+    pub fn relocation_model(&self) -> RelocModel {
+        self.opts.cg.relocation_model.unwrap_or(self.target.target.options.relocation_model)
+    }
+
+    pub fn code_model(&self) -> Option<CodeModel> {
+        self.opts.cg.code_model.or(self.target.target.options.code_model)
+    }
+
+    pub fn tls_model(&self) -> TlsModel {
+        self.opts.debugging_opts.tls_model.unwrap_or(self.target.target.options.tls_model)
+    }
+
+    pub fn must_not_eliminate_frame_pointers(&self) -> bool {
+        // "mcount" function relies on stack pointer.
+        // See <https://sourceware.org/binutils/docs/gprof/Implementation.html>.
+        if self.instrument_mcount() {
+            true
+        } else if let Some(x) = self.opts.cg.force_frame_pointers {
+            x
+        } else {
+            !self.target.target.options.eliminate_frame_pointer
+        }
+    }
+
+    pub fn must_emit_unwind_tables(&self) -> bool {
+        // This is used to control the emission of the `uwtable` attribute on
+        // LLVM functions.
+        //
+        // At the very least, unwind tables are needed when compiling with
+        // `-C panic=unwind`.
+        //
+        // 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.
+        if self.panic_strategy() == PanicStrategy::Unwind {
+            true
+        } else if self.target.target.options.requires_uwtable {
+            true
+        } else {
+            self.opts.cg.force_unwind_tables.unwrap_or(false)
+        }
+    }
+
+    /// Returns the symbol name for the registrar function,
+    /// given the crate `Svh` and the function `DefIndex`.
+    pub fn generate_plugin_registrar_symbol(&self, disambiguator: CrateDisambiguator) -> String {
+        format!("__rustc_plugin_registrar_{}__", disambiguator.to_fingerprint().to_hex())
+    }
+
+    pub fn generate_proc_macro_decls_symbol(&self, disambiguator: CrateDisambiguator) -> String {
+        format!("__rustc_proc_macro_decls_{}__", disambiguator.to_fingerprint().to_hex())
+    }
+
+    pub fn target_filesearch(&self, kind: PathKind) -> filesearch::FileSearch<'_> {
+        filesearch::FileSearch::new(
+            &self.sysroot,
+            self.opts.target_triple.triple(),
+            &self.opts.search_paths,
+            // `target_tlib_path == None` means it's the same as `host_tlib_path`.
+            self.target_tlib_path.as_ref().unwrap_or(&self.host_tlib_path),
+            kind,
+        )
+    }
+    pub fn host_filesearch(&self, kind: PathKind) -> filesearch::FileSearch<'_> {
+        filesearch::FileSearch::new(
+            &self.sysroot,
+            config::host_triple(),
+            &self.opts.search_paths,
+            &self.host_tlib_path,
+            kind,
+        )
+    }
+
+    pub fn set_incr_session_load_dep_graph(&self, load: bool) {
+        let mut incr_comp_session = self.incr_comp_session.borrow_mut();
+
+        if let IncrCompSession::Active { ref mut load_dep_graph, .. } = *incr_comp_session {
+            *load_dep_graph = load;
+        }
+    }
+
+    pub fn incr_session_load_dep_graph(&self) -> bool {
+        let incr_comp_session = self.incr_comp_session.borrow();
+        match *incr_comp_session {
+            IncrCompSession::Active { load_dep_graph, .. } => load_dep_graph,
+            _ => false,
+        }
+    }
+
+    pub fn init_incr_comp_session(
+        &self,
+        session_dir: PathBuf,
+        lock_file: flock::Lock,
+        load_dep_graph: bool,
+    ) {
+        let mut incr_comp_session = self.incr_comp_session.borrow_mut();
+
+        if let IncrCompSession::NotInitialized = *incr_comp_session {
+        } else {
+            panic!("Trying to initialize IncrCompSession `{:?}`", *incr_comp_session)
+        }
+
+        *incr_comp_session =
+            IncrCompSession::Active { session_directory: session_dir, lock_file, load_dep_graph };
+    }
+
+    pub fn finalize_incr_comp_session(&self, new_directory_path: PathBuf) {
+        let mut incr_comp_session = self.incr_comp_session.borrow_mut();
+
+        if let IncrCompSession::Active { .. } = *incr_comp_session {
+        } else {
+            panic!("trying to finalize `IncrCompSession` `{:?}`", *incr_comp_session);
+        }
+
+        // Note: this will also drop the lock file, thus unlocking the directory.
+        *incr_comp_session = IncrCompSession::Finalized { session_directory: new_directory_path };
+    }
+
+    pub fn mark_incr_comp_session_as_invalid(&self) {
+        let mut incr_comp_session = self.incr_comp_session.borrow_mut();
+
+        let session_directory = match *incr_comp_session {
+            IncrCompSession::Active { ref session_directory, .. } => session_directory.clone(),
+            IncrCompSession::InvalidBecauseOfErrors { .. } => return,
+            _ => panic!("trying to invalidate `IncrCompSession` `{:?}`", *incr_comp_session),
+        };
+
+        // Note: this will also drop the lock file, thus unlocking the directory.
+        *incr_comp_session = IncrCompSession::InvalidBecauseOfErrors { session_directory };
+    }
+
+    pub fn incr_comp_session_dir(&self) -> cell::Ref<'_, PathBuf> {
+        let incr_comp_session = self.incr_comp_session.borrow();
+        cell::Ref::map(incr_comp_session, |incr_comp_session| match *incr_comp_session {
+            IncrCompSession::NotInitialized => panic!(
+                "trying to get session directory from `IncrCompSession`: {:?}",
+                *incr_comp_session,
+            ),
+            IncrCompSession::Active { ref session_directory, .. }
+            | IncrCompSession::Finalized { ref session_directory }
+            | IncrCompSession::InvalidBecauseOfErrors { ref session_directory } => {
+                session_directory
+            }
+        })
+    }
+
+    pub fn incr_comp_session_dir_opt(&self) -> Option<cell::Ref<'_, PathBuf>> {
+        self.opts.incremental.as_ref().map(|_| self.incr_comp_session_dir())
+    }
+
+    pub fn print_perf_stats(&self) {
+        println!(
+            "Total time spent computing symbol hashes:      {}",
+            duration_to_secs_str(*self.perf_stats.symbol_hash_time.lock())
+        );
+        println!(
+            "Total queries canonicalized:                   {}",
+            self.perf_stats.queries_canonicalized.load(Ordering::Relaxed)
+        );
+        println!(
+            "normalize_generic_arg_after_erasing_regions:   {}",
+            self.perf_stats.normalize_generic_arg_after_erasing_regions.load(Ordering::Relaxed)
+        );
+        println!(
+            "normalize_projection_ty:                       {}",
+            self.perf_stats.normalize_projection_ty.load(Ordering::Relaxed)
+        );
+    }
+
+    /// We want to know if we're allowed to do an optimization for crate foo from -z fuel=foo=n.
+    /// This expends fuel if applicable, and records fuel if applicable.
+    pub fn consider_optimizing<T: Fn() -> String>(&self, crate_name: &str, msg: T) -> bool {
+        let mut ret = true;
+        if let Some(ref c) = self.optimization_fuel_crate {
+            if c == crate_name {
+                assert_eq!(self.threads(), 1);
+                let mut fuel = self.optimization_fuel.lock();
+                ret = fuel.remaining != 0;
+                if fuel.remaining == 0 && !fuel.out_of_fuel {
+                    self.warn(&format!("optimization-fuel-exhausted: {}", msg()));
+                    fuel.out_of_fuel = true;
+                } else if fuel.remaining > 0 {
+                    fuel.remaining -= 1;
+                }
+            }
+        }
+        if let Some(ref c) = self.print_fuel_crate {
+            if c == crate_name {
+                assert_eq!(self.threads(), 1);
+                self.print_fuel.fetch_add(1, SeqCst);
+            }
+        }
+        ret
+    }
+
+    /// Returns the number of query threads that should be used for this
+    /// compilation
+    pub fn threads(&self) -> usize {
+        self.opts.debugging_opts.threads
+    }
+
+    /// Returns the number of codegen units that should be used for this
+    /// compilation
+    pub fn codegen_units(&self) -> usize {
+        if let Some(n) = self.opts.cli_forced_codegen_units {
+            return n;
+        }
+        if let Some(n) = self.target.target.options.default_codegen_units {
+            return n as usize;
+        }
+
+        // If incremental compilation is turned on, we default to a high number
+        // codegen units in order to reduce the "collateral damage" small
+        // changes cause.
+        if self.opts.incremental.is_some() {
+            return 256;
+        }
+
+        // Why is 16 codegen units the default all the time?
+        //
+        // The main reason for enabling multiple codegen units by default is to
+        // leverage the ability for the codegen backend to do codegen and
+        // optimization in parallel. This allows us, especially for large crates, to
+        // make good use of all available resources on the machine once we've
+        // hit that stage of compilation. Large crates especially then often
+        // take a long time in codegen/optimization and this helps us amortize that
+        // cost.
+        //
+        // Note that a high number here doesn't mean that we'll be spawning a
+        // large number of threads in parallel. The backend of rustc contains
+        // global rate limiting through the `jobserver` crate so we'll never
+        // overload the system with too much work, but rather we'll only be
+        // optimizing when we're otherwise cooperating with other instances of
+        // rustc.
+        //
+        // Rather a high number here means that we should be able to keep a lot
+        // of idle cpus busy. By ensuring that no codegen unit takes *too* long
+        // to build we'll be guaranteed that all cpus will finish pretty closely
+        // to one another and we should make relatively optimal use of system
+        // resources
+        //
+        // Note that the main cost of codegen units is that it prevents LLVM
+        // from inlining across codegen units. Users in general don't have a lot
+        // of control over how codegen units are split up so it's our job in the
+        // compiler to ensure that undue performance isn't lost when using
+        // codegen units (aka we can't require everyone to slap `#[inline]` on
+        // everything).
+        //
+        // If we're compiling at `-O0` then the number doesn't really matter too
+        // much because performance doesn't matter and inlining is ok to lose.
+        // In debug mode we just want to try to guarantee that no cpu is stuck
+        // doing work that could otherwise be farmed to others.
+        //
+        // In release mode, however (O1 and above) performance does indeed
+        // matter! To recover the loss in performance due to inlining we'll be
+        // enabling ThinLTO by default (the function for which is just below).
+        // This will ensure that we recover any inlining wins we otherwise lost
+        // through codegen unit partitioning.
+        //
+        // ---
+        //
+        // Ok that's a lot of words but the basic tl;dr; is that we want a high
+        // number here -- but not too high. Additionally we're "safe" to have it
+        // always at the same number at all optimization levels.
+        //
+        // As a result 16 was chosen here! Mostly because it was a power of 2
+        // and most benchmarks agreed it was roughly a local optimum. Not very
+        // scientific.
+        16
+    }
+
+    pub fn teach(&self, code: &DiagnosticId) -> bool {
+        self.opts.debugging_opts.teach && self.diagnostic().must_teach(code)
+    }
+
+    pub fn rust_2015(&self) -> bool {
+        self.opts.edition == Edition::Edition2015
+    }
+
+    /// Are we allowed to use features from the Rust 2018 edition?
+    pub fn rust_2018(&self) -> bool {
+        self.opts.edition >= Edition::Edition2018
+    }
+
+    pub fn edition(&self) -> Edition {
+        self.opts.edition
+    }
+
+    /// Returns `true` if we cannot skip the PLT for shared library calls.
+    pub fn needs_plt(&self) -> bool {
+        // Check if the current target usually needs PLT to be enabled.
+        // The user can use the command line flag to override it.
+        let needs_plt = self.target.target.options.needs_plt;
+
+        let dbg_opts = &self.opts.debugging_opts;
+
+        let relro_level = dbg_opts.relro_level.unwrap_or(self.target.target.options.relro_level);
+
+        // Only enable this optimization by default if full relro is also enabled.
+        // In this case, lazy binding was already unavailable, so nothing is lost.
+        // This also ensures `-Wl,-z,now` is supported by the linker.
+        let full_relro = RelroLevel::Full == relro_level;
+
+        // If user didn't explicitly forced us to use / skip the PLT,
+        // then try to skip it where possible.
+        dbg_opts.plt.unwrap_or(needs_plt || !full_relro)
+    }
+
+    /// Checks if LLVM lifetime markers should be emitted.
+    pub fn emit_lifetime_markers(&self) -> bool {
+        self.opts.optimize != config::OptLevel::No
+        // AddressSanitizer uses lifetimes to detect use after scope bugs.
+        // MemorySanitizer uses lifetimes to detect use of uninitialized stack variables.
+        || self.opts.debugging_opts.sanitizer.intersects(SanitizerSet::ADDRESS | SanitizerSet::MEMORY)
+    }
+
+    pub fn mark_attr_known(&self, attr: &Attribute) {
+        self.known_attrs.lock().mark(attr)
+    }
+
+    pub fn is_attr_known(&self, attr: &Attribute) -> bool {
+        self.known_attrs.lock().is_marked(attr)
+    }
+
+    pub fn mark_attr_used(&self, attr: &Attribute) {
+        self.used_attrs.lock().mark(attr)
+    }
+
+    pub fn is_attr_used(&self, attr: &Attribute) -> bool {
+        self.used_attrs.lock().is_marked(attr)
+    }
+
+    /// Returns `true` if the attribute's path matches the argument. If it matches, then the
+    /// attribute is marked as used.
+
+    /// Returns `true` if the attribute's path matches the argument. If it
+    /// matches, then the attribute is marked as used.
+    ///
+    /// This method should only be used by rustc, other tools can use
+    /// `Attribute::has_name` instead, because only rustc is supposed to report
+    /// the `unused_attributes` lint. (`MetaItem` and `NestedMetaItem` are
+    /// produced by lowering an `Attribute` and don't have identity, so they
+    /// only have the `has_name` method, and you need to mark the original
+    /// `Attribute` as used when necessary.)
+    pub fn check_name(&self, attr: &Attribute, name: Symbol) -> bool {
+        let matches = attr.has_name(name);
+        if matches {
+            self.mark_attr_used(attr);
+        }
+        matches
+    }
+
+    pub fn is_proc_macro_attr(&self, attr: &Attribute) -> bool {
+        [sym::proc_macro, sym::proc_macro_attribute, sym::proc_macro_derive]
+            .iter()
+            .any(|kind| self.check_name(attr, *kind))
+    }
+
+    pub fn contains_name(&self, attrs: &[Attribute], name: Symbol) -> bool {
+        attrs.iter().any(|item| self.check_name(item, name))
+    }
+
+    pub fn find_by_name<'a>(
+        &'a self,
+        attrs: &'a [Attribute],
+        name: Symbol,
+    ) -> Option<&'a Attribute> {
+        attrs.iter().find(|attr| self.check_name(attr, name))
+    }
+
+    pub fn filter_by_name<'a>(
+        &'a self,
+        attrs: &'a [Attribute],
+        name: Symbol,
+    ) -> impl Iterator<Item = &'a Attribute> {
+        attrs.iter().filter(move |attr| self.check_name(attr, name))
+    }
+
+    pub fn first_attr_value_str_by_name(
+        &self,
+        attrs: &[Attribute],
+        name: Symbol,
+    ) -> Option<Symbol> {
+        attrs.iter().find(|at| self.check_name(at, name)).and_then(|at| at.value_str())
+    }
+}
+
+fn default_emitter(
+    sopts: &config::Options,
+    registry: rustc_errors::registry::Registry,
+    source_map: Lrc<SourceMap>,
+    emitter_dest: Option<Box<dyn Write + Send>>,
+) -> Box<dyn Emitter + sync::Send> {
+    let macro_backtrace = sopts.debugging_opts.macro_backtrace;
+    match (sopts.error_format, emitter_dest) {
+        (config::ErrorOutputType::HumanReadable(kind), dst) => {
+            let (short, color_config) = kind.unzip();
+
+            if let HumanReadableErrorType::AnnotateSnippet(_) = kind {
+                let emitter =
+                    AnnotateSnippetEmitterWriter::new(Some(source_map), short, macro_backtrace);
+                Box::new(emitter.ui_testing(sopts.debugging_opts.ui_testing))
+            } else {
+                let emitter = match dst {
+                    None => EmitterWriter::stderr(
+                        color_config,
+                        Some(source_map),
+                        short,
+                        sopts.debugging_opts.teach,
+                        sopts.debugging_opts.terminal_width,
+                        macro_backtrace,
+                    ),
+                    Some(dst) => EmitterWriter::new(
+                        dst,
+                        Some(source_map),
+                        short,
+                        false, // no teach messages when writing to a buffer
+                        false, // no colors when writing to a buffer
+                        None,  // no terminal width
+                        macro_backtrace,
+                    ),
+                };
+                Box::new(emitter.ui_testing(sopts.debugging_opts.ui_testing))
+            }
+        }
+        (config::ErrorOutputType::Json { pretty, json_rendered }, None) => Box::new(
+            JsonEmitter::stderr(
+                Some(registry),
+                source_map,
+                pretty,
+                json_rendered,
+                sopts.debugging_opts.terminal_width,
+                macro_backtrace,
+            )
+            .ui_testing(sopts.debugging_opts.ui_testing),
+        ),
+        (config::ErrorOutputType::Json { pretty, json_rendered }, Some(dst)) => Box::new(
+            JsonEmitter::new(
+                dst,
+                Some(registry),
+                source_map,
+                pretty,
+                json_rendered,
+                sopts.debugging_opts.terminal_width,
+                macro_backtrace,
+            )
+            .ui_testing(sopts.debugging_opts.ui_testing),
+        ),
+    }
+}
+
+pub enum DiagnosticOutput {
+    Default,
+    Raw(Box<dyn Write + Send>),
+}
+
+pub fn build_session(
+    sopts: config::Options,
+    local_crate_source_file: Option<PathBuf>,
+    registry: rustc_errors::registry::Registry,
+    diagnostics_output: DiagnosticOutput,
+    driver_lint_caps: FxHashMap<lint::LintId, lint::Level>,
+    file_loader: Option<Box<dyn FileLoader + Send + Sync + 'static>>,
+) -> Session {
+    // FIXME: This is not general enough to make the warning lint completely override
+    // normal diagnostic warnings, since the warning lint can also be denied and changed
+    // later via the source code.
+    let warnings_allow = sopts
+        .lint_opts
+        .iter()
+        .filter(|&&(ref key, _)| *key == "warnings")
+        .map(|&(_, ref level)| *level == lint::Allow)
+        .last()
+        .unwrap_or(false);
+    let cap_lints_allow = sopts.lint_cap.map_or(false, |cap| cap == lint::Allow);
+    let can_emit_warnings = !(warnings_allow || cap_lints_allow);
+
+    let write_dest = match diagnostics_output {
+        DiagnosticOutput::Default => None,
+        DiagnosticOutput::Raw(write) => Some(write),
+    };
+
+    let target_cfg = config::build_target_config(&sopts, sopts.error_format);
+    let host_triple = TargetTriple::from_triple(config::host_triple());
+    let host = Target::search(&host_triple).unwrap_or_else(|e| {
+        early_error(sopts.error_format, &format!("Error loading host specification: {}", e))
+    });
+
+    let loader = file_loader.unwrap_or(Box::new(RealFileLoader));
+    let hash_kind = sopts.debugging_opts.src_hash_algorithm.unwrap_or_else(|| {
+        if target_cfg.target.options.is_like_msvc {
+            SourceFileHashAlgorithm::Sha1
+        } else {
+            SourceFileHashAlgorithm::Md5
+        }
+    });
+    let source_map = Lrc::new(SourceMap::with_file_loader_and_hash_kind(
+        loader,
+        sopts.file_path_mapping(),
+        hash_kind,
+    ));
+    let emitter = default_emitter(&sopts, registry, source_map.clone(), write_dest);
+
+    let span_diagnostic = rustc_errors::Handler::with_emitter_and_flags(
+        emitter,
+        sopts.debugging_opts.diagnostic_handler_flags(can_emit_warnings),
+    );
+
+    let self_profiler = if let SwitchWithOptPath::Enabled(ref d) = sopts.debugging_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_ref().map(|s| &s[..]),
+            &sopts.debugging_opts.self_profile_events,
+        );
+        match profiler {
+            Ok(profiler) => Some(Arc::new(profiler)),
+            Err(e) => {
+                early_warn(sopts.error_format, &format!("failed to create profiler: {}", e));
+                None
+            }
+        }
+    } else {
+        None
+    };
+
+    let parse_sess = ParseSess::with_span_handler(span_diagnostic, source_map);
+    let sysroot = match &sopts.maybe_sysroot {
+        Some(sysroot) => sysroot.clone(),
+        None => filesearch::get_or_default_sysroot(),
+    };
+
+    let host_triple = config::host_triple();
+    let target_triple = sopts.target_triple.triple();
+    let host_tlib_path = SearchPath::from_sysroot_and_triple(&sysroot, host_triple);
+    let target_tlib_path = if host_triple == target_triple {
+        None
+    } else {
+        Some(SearchPath::from_sysroot_and_triple(&sysroot, target_triple))
+    };
+
+    let file_path_mapping = sopts.file_path_mapping();
+
+    let local_crate_source_file =
+        local_crate_source_file.map(|path| file_path_mapping.map_prefix(path).0);
+
+    let optimization_fuel_crate = sopts.debugging_opts.fuel.as_ref().map(|i| i.0.clone());
+    let optimization_fuel = Lock::new(OptimizationFuel {
+        remaining: sopts.debugging_opts.fuel.as_ref().map(|i| i.1).unwrap_or(0),
+        out_of_fuel: false,
+    });
+    let print_fuel_crate = sopts.debugging_opts.print_fuel.clone();
+    let print_fuel = AtomicU64::new(0);
+
+    let working_dir = env::current_dir().unwrap_or_else(|e| {
+        parse_sess.span_diagnostic.fatal(&format!("Current directory is invalid: {}", e)).raise()
+    });
+    let working_dir = file_path_mapping.map_prefix(working_dir);
+
+    let cgu_reuse_tracker = if sopts.debugging_opts.query_dep_graph {
+        CguReuseTracker::new()
+    } else {
+        CguReuseTracker::new_disabled()
+    };
+
+    let prof = SelfProfilerRef::new(
+        self_profiler,
+        sopts.debugging_opts.time_passes || sopts.debugging_opts.time,
+        sopts.debugging_opts.time_passes,
+    );
+
+    let ctfe_backtrace = Lock::new(match env::var("RUSTC_CTFE_BACKTRACE") {
+        Ok(ref val) if val == "immediate" => CtfeBacktrace::Immediate,
+        Ok(ref val) if val != "0" => CtfeBacktrace::Capture,
+        _ => CtfeBacktrace::Disabled,
+    });
+
+    // Try to find a directory containing the Rust `src`, for more details see
+    // the doc comment on the `real_rust_source_base_dir` field.
+    let real_rust_source_base_dir = {
+        // This is the location used by the `rust-src` `rustup` component.
+        let mut candidate = sysroot.join("lib/rustlib/src/rust");
+        if let Ok(metadata) = candidate.symlink_metadata() {
+            // Replace the symlink rustbuild creates, with its destination.
+            // We could try to use `fs::canonicalize` instead, but that might
+            // produce unnecessarily verbose path.
+            if metadata.file_type().is_symlink() {
+                if let Ok(symlink_dest) = std::fs::read_link(&candidate) {
+                    candidate = symlink_dest;
+                }
+            }
+        }
+
+        // Only use this directory if it has a file we can expect to always find.
+        if candidate.join("library/std/src/lib.rs").is_file() { Some(candidate) } else { None }
+    };
+
+    let asm_arch = if target_cfg.target.options.allow_asm {
+        InlineAsmArch::from_str(&target_cfg.target.arch).ok()
+    } else {
+        None
+    };
+
+    let sess = Session {
+        target: target_cfg,
+        host,
+        opts: sopts,
+        host_tlib_path,
+        target_tlib_path,
+        parse_sess,
+        sysroot,
+        local_crate_source_file,
+        working_dir,
+        one_time_diagnostics: Default::default(),
+        crate_types: OnceCell::new(),
+        crate_disambiguator: OnceCell::new(),
+        features: OnceCell::new(),
+        recursion_limit: OnceCell::new(),
+        type_length_limit: OnceCell::new(),
+        const_eval_limit: OnceCell::new(),
+        incr_comp_session: OneThread::new(RefCell::new(IncrCompSession::NotInitialized)),
+        cgu_reuse_tracker,
+        prof,
+        perf_stats: PerfStats {
+            symbol_hash_time: Lock::new(Duration::from_secs(0)),
+            queries_canonicalized: AtomicUsize::new(0),
+            normalize_generic_arg_after_erasing_regions: AtomicUsize::new(0),
+            normalize_projection_ty: AtomicUsize::new(0),
+        },
+        code_stats: Default::default(),
+        optimization_fuel_crate,
+        optimization_fuel,
+        print_fuel_crate,
+        print_fuel,
+        jobserver: jobserver::client(),
+        driver_lint_caps,
+        trait_methods_not_found: Lock::new(Default::default()),
+        confused_type_with_std_module: Lock::new(Default::default()),
+        system_library_path: OneThread::new(RefCell::new(Default::default())),
+        ctfe_backtrace,
+        miri_unleashed_features: Lock::new(Default::default()),
+        real_rust_source_base_dir,
+        asm_arch,
+        target_features: FxHashSet::default(),
+        known_attrs: Lock::new(MarkedAttrs::new()),
+        used_attrs: Lock::new(MarkedAttrs::new()),
+    };
+
+    validate_commandline_args_with_session_available(&sess);
+
+    sess
+}
+
+// If it is useful to have a Session available already for validating a
+// commandline argument, you can do so here.
+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.target.options.is_like_windows
+    {
+        sess.err(
+            "Linker plugin based LTO is not supported together with \
+                  `-C prefer-dynamic` when targeting Windows-like targets",
+        );
+    }
+
+    // Make sure that any given profiling data actually exists so LLVM can't
+    // decide to silently skip PGO.
+    if let Some(ref path) = sess.opts.cg.profile_use {
+        if !path.exists() {
+            sess.err(&format!(
+                "File `{}` passed to `-C profile-use` does not exist.",
+                path.display()
+            ));
+        }
+    }
+
+    // Unwind tables cannot be disabled if the target requires them.
+    if let Some(include_uwtables) = sess.opts.cg.force_unwind_tables {
+        if sess.panic_strategy() == PanicStrategy::Unwind && !include_uwtables {
+            sess.err(
+                "panic=unwind requires unwind tables, they cannot be disabled \
+                     with `-C force-unwind-tables=no`.",
+            );
+        }
+
+        if sess.target.target.options.requires_uwtable && !include_uwtables {
+            sess.err(
+                "target requires unwind tables, they cannot be disabled with \
+                     `-C force-unwind-tables=no`.",
+            );
+        }
+    }
+
+    // PGO does not work reliably with panic=unwind on Windows. Let's make it
+    // an error to combine the two for now. It always runs into an assertions
+    // if LLVM is built with assertions, but without assertions it sometimes
+    // does not crash and will probably generate a corrupted binary.
+    // We should only display this error if we're actually going to run PGO.
+    // If we're just supposed to print out some data, don't show the error (#61002).
+    if sess.opts.cg.profile_generate.enabled()
+        && sess.target.target.options.is_like_msvc
+        && sess.panic_strategy() == PanicStrategy::Unwind
+        && sess.opts.prints.iter().all(|&p| p == PrintRequest::NativeStaticLibs)
+    {
+        sess.err(
+            "Profile-guided optimization does not yet work in conjunction \
+                  with `-Cpanic=unwind` on Windows when targeting MSVC. \
+                  See issue #61002 <https://github.com/rust-lang/rust/issues/61002> \
+                  for more information.",
+        );
+    }
+
+    // FIXME(richkadel): See `src/test/run-make-fulldeps/instrument-coverage/Makefile`. After
+    // compiling with `-Zinstrument-coverage`, the resulting binary generates a segfault during
+    // the program's exit process (likely while attempting to generate the coverage stats in
+    // the "*.profraw" file). An investigation to resolve the problem on Windows is ongoing,
+    // but until this is resolved, the option is disabled on Windows, and the test is skipped
+    // when targeting `MSVC`.
+    if sess.opts.debugging_opts.instrument_coverage && sess.target.target.options.is_like_msvc {
+        sess.warn(
+            "Rust source-based code coverage instrumentation (with `-Z instrument-coverage`) \
+            is not yet supported on Windows when targeting MSVC. The resulting binaries will \
+            still be instrumented for experimentation purposes, but may not execute correctly.",
+        );
+    }
+
+    const ASAN_SUPPORTED_TARGETS: &[&str] = &[
+        "aarch64-fuchsia",
+        "aarch64-unknown-linux-gnu",
+        "x86_64-apple-darwin",
+        "x86_64-fuchsia",
+        "x86_64-unknown-freebsd",
+        "x86_64-unknown-linux-gnu",
+    ];
+    const LSAN_SUPPORTED_TARGETS: &[&str] =
+        &["aarch64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu"];
+    const MSAN_SUPPORTED_TARGETS: &[&str] =
+        &["aarch64-unknown-linux-gnu", "x86_64-unknown-freebsd", "x86_64-unknown-linux-gnu"];
+    const TSAN_SUPPORTED_TARGETS: &[&str] = &[
+        "aarch64-unknown-linux-gnu",
+        "x86_64-apple-darwin",
+        "x86_64-unknown-freebsd",
+        "x86_64-unknown-linux-gnu",
+    ];
+
+    // Sanitizers can only be used on some tested platforms.
+    for s in sess.opts.debugging_opts.sanitizer {
+        let supported_targets = match s {
+            SanitizerSet::ADDRESS => ASAN_SUPPORTED_TARGETS,
+            SanitizerSet::LEAK => LSAN_SUPPORTED_TARGETS,
+            SanitizerSet::MEMORY => MSAN_SUPPORTED_TARGETS,
+            SanitizerSet::THREAD => TSAN_SUPPORTED_TARGETS,
+            _ => panic!("unrecognized sanitizer {}", s),
+        };
+        if !supported_targets.contains(&&*sess.opts.target_triple.triple()) {
+            sess.err(&format!(
+                "`-Zsanitizer={}` only works with targets: {}",
+                s,
+                supported_targets.join(", ")
+            ));
+        }
+        let conflicting = sess.opts.debugging_opts.sanitizer - s;
+        if !conflicting.is_empty() {
+            sess.err(&format!(
+                "`-Zsanitizer={}` is incompatible with `-Zsanitizer={}`",
+                s, conflicting,
+            ));
+            // Don't report additional errors.
+            break;
+        }
+    }
+}
+
+/// Holds data on the current incremental compilation session, if there is one.
+#[derive(Debug)]
+pub enum IncrCompSession {
+    /// This is the state the session will be in until the incr. comp. dir is
+    /// needed.
+    NotInitialized,
+    /// This is the state during which the session directory is private and can
+    /// be modified.
+    Active { session_directory: PathBuf, lock_file: flock::Lock, load_dep_graph: bool },
+    /// This is the state after the session directory has been finalized. In this
+    /// state, the contents of the directory must not be modified any more.
+    Finalized { session_directory: PathBuf },
+    /// This is an error state that is reached when some compilation error has
+    /// occurred. It indicates that the contents of the session directory must
+    /// not be used, since they might be invalid.
+    InvalidBecauseOfErrors { session_directory: PathBuf },
+}
+
+pub fn early_error(output: config::ErrorOutputType, msg: &str) -> ! {
+    let emitter: Box<dyn Emitter + sync::Send> = match output {
+        config::ErrorOutputType::HumanReadable(kind) => {
+            let (short, color_config) = kind.unzip();
+            Box::new(EmitterWriter::stderr(color_config, None, short, false, None, false))
+        }
+        config::ErrorOutputType::Json { pretty, json_rendered } => {
+            Box::new(JsonEmitter::basic(pretty, json_rendered, None, false))
+        }
+    };
+    let handler = rustc_errors::Handler::with_emitter(true, None, emitter);
+    handler.struct_fatal(msg).emit();
+    rustc_errors::FatalError.raise();
+}
+
+pub fn early_warn(output: config::ErrorOutputType, msg: &str) {
+    let emitter: Box<dyn Emitter + sync::Send> = match output {
+        config::ErrorOutputType::HumanReadable(kind) => {
+            let (short, color_config) = kind.unzip();
+            Box::new(EmitterWriter::stderr(color_config, None, short, false, None, false))
+        }
+        config::ErrorOutputType::Json { pretty, json_rendered } => {
+            Box::new(JsonEmitter::basic(pretty, json_rendered, None, false))
+        }
+    };
+    let handler = rustc_errors::Handler::with_emitter(true, None, emitter);
+    handler.struct_warn(msg).emit();
+}
+
+pub type CompileResult = Result<(), ErrorReported>;
diff --git a/compiler/rustc_session/src/utils.rs b/compiler/rustc_session/src/utils.rs
new file mode 100644
index 00000000000..15447c01d1e
--- /dev/null
+++ b/compiler/rustc_session/src/utils.rs
@@ -0,0 +1,32 @@
+use crate::session::Session;
+use rustc_data_structures::profiling::VerboseTimingGuard;
+
+impl Session {
+    pub fn timer<'a>(&'a self, what: &'static str) -> VerboseTimingGuard<'a> {
+        self.prof.verbose_generic_activity(what)
+    }
+    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)]
+pub enum NativeLibKind {
+    /// Static library (e.g. `libfoo.a` on Linux or `foo.lib` on Windows/MSVC) included
+    /// when linking a final binary, but not when archiving an rlib.
+    StaticNoBundle,
+    /// Static library (e.g. `libfoo.a` on Linux or `foo.lib` on Windows/MSVC) included
+    /// when linking a final binary, but also included when archiving an rlib.
+    StaticBundle,
+    /// 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,
+    /// Dynamic library (e.g. `foo.dll` on Windows) without a corresponding import library.
+    RawDylib,
+    /// A macOS-specific kind of dynamic libraries.
+    Framework,
+    /// The library kind wasn't specified, `Dylib` is currently used as a default.
+    Unspecified,
+}
+
+rustc_data_structures::impl_stable_hash_via_hash!(NativeLibKind);