about summary refs log tree commit diff
path: root/compiler/rustc_interface/src
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_interface/src')
-rw-r--r--compiler/rustc_interface/src/callbacks.rs61
-rw-r--r--compiler/rustc_interface/src/interface.rs248
-rw-r--r--compiler/rustc_interface/src/lib.rs20
-rw-r--r--compiler/rustc_interface/src/passes.rs1076
-rw-r--r--compiler/rustc_interface/src/proc_macro_decls.rs37
-rw-r--r--compiler/rustc_interface/src/queries.rs415
-rw-r--r--compiler/rustc_interface/src/tests.rs797
-rw-r--r--compiler/rustc_interface/src/util.rs901
8 files changed, 3555 insertions, 0 deletions
diff --git a/compiler/rustc_interface/src/callbacks.rs b/compiler/rustc_interface/src/callbacks.rs
new file mode 100644
index 00000000000..3b852b8ccf9
--- /dev/null
+++ b/compiler/rustc_interface/src/callbacks.rs
@@ -0,0 +1,61 @@
+//! Throughout the compiler tree, there are several places which want to have
+//! access to state or queries while being inside crates that are dependencies
+//! of `rustc_middle`. To facilitate this, we have the
+//! `rustc_data_structures::AtomicRef` type, which allows us to setup a global
+//! static which can then be set in this file at program startup.
+//!
+//! See `SPAN_DEBUG` for an example of how to set things up.
+//!
+//! The functions in this file should fall back to the default set in their
+//! origin crate when the `TyCtxt` is not present in TLS.
+
+use rustc_errors::{Diagnostic, TRACK_DIAGNOSTICS};
+use rustc_middle::ty::tls;
+use std::fmt;
+
+/// This is a callback from `rustc_ast` as it cannot access the implicit state
+/// in `rustc_middle` otherwise.
+fn span_debug(span: rustc_span::Span, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+    tls::with_opt(|tcx| {
+        if let Some(tcx) = tcx {
+            rustc_span::debug_with_source_map(span, f, tcx.sess.source_map())
+        } else {
+            rustc_span::default_span_debug(span, f)
+        }
+    })
+}
+
+/// This is a callback from `rustc_ast` as it cannot access the implicit state
+/// in `rustc_middle` otherwise. It is used to when diagnostic messages are
+/// emitted and stores them in the current query, if there is one.
+fn track_diagnostic(diagnostic: &Diagnostic) {
+    tls::with_context_opt(|icx| {
+        if let Some(icx) = icx {
+            if let Some(ref diagnostics) = icx.diagnostics {
+                let mut diagnostics = diagnostics.lock();
+                diagnostics.extend(Some(diagnostic.clone()));
+            }
+        }
+    })
+}
+
+/// This is a callback from `rustc_hir` as it cannot access the implicit state
+/// in `rustc_middle` otherwise.
+fn def_id_debug(def_id: rustc_hir::def_id::DefId, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+    write!(f, "DefId({}:{}", def_id.krate, def_id.index.index())?;
+    tls::with_opt(|opt_tcx| {
+        if let Some(tcx) = opt_tcx {
+            write!(f, " ~ {}", tcx.def_path_debug_str(def_id))?;
+        }
+        Ok(())
+    })?;
+    write!(f, ")")
+}
+
+/// Sets up the callbacks in prior crates which we want to refer to the
+/// TyCtxt in.
+pub fn setup_callbacks() {
+    rustc_span::SPAN_DEBUG.swap(&(span_debug as fn(_, &mut fmt::Formatter<'_>) -> _));
+    rustc_hir::def_id::DEF_ID_DEBUG.swap(&(def_id_debug as fn(_, &mut fmt::Formatter<'_>) -> _));
+    TRACK_DIAGNOSTICS.swap(&(track_diagnostic as fn(&_)));
+}
diff --git a/compiler/rustc_interface/src/interface.rs b/compiler/rustc_interface/src/interface.rs
new file mode 100644
index 00000000000..8393826aa12
--- /dev/null
+++ b/compiler/rustc_interface/src/interface.rs
@@ -0,0 +1,248 @@
+pub use crate::passes::BoxedResolver;
+use crate::util;
+
+use rustc_ast::token;
+use rustc_ast::{self as ast, MetaItemKind};
+use rustc_codegen_ssa::traits::CodegenBackend;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_data_structures::sync::Lrc;
+use rustc_data_structures::OnDrop;
+use rustc_errors::registry::Registry;
+use rustc_errors::{ErrorReported, Handler};
+use rustc_lint::LintStore;
+use rustc_middle::ty;
+use rustc_parse::new_parser_from_source_str;
+use rustc_query_impl::QueryCtxt;
+use rustc_session::config::{self, ErrorOutputType, Input, OutputFilenames};
+use rustc_session::early_error;
+use rustc_session::lint;
+use rustc_session::parse::{CrateConfig, ParseSess};
+use rustc_session::{DiagnosticOutput, Session};
+use rustc_span::source_map::{FileLoader, FileName};
+use std::path::PathBuf;
+use std::result;
+use std::sync::{Arc, Mutex};
+
+pub type Result<T> = result::Result<T, ErrorReported>;
+
+/// Represents a compiler session.
+///
+/// Can be used to run `rustc_interface` queries.
+/// Created by passing [`Config`] to [`run_compiler`].
+pub struct Compiler {
+    pub(crate) sess: Lrc<Session>,
+    codegen_backend: Lrc<Box<dyn CodegenBackend>>,
+    pub(crate) input: Input,
+    pub(crate) input_path: Option<PathBuf>,
+    pub(crate) output_dir: Option<PathBuf>,
+    pub(crate) output_file: Option<PathBuf>,
+    pub(crate) register_lints: Option<Box<dyn Fn(&Session, &mut LintStore) + Send + Sync>>,
+    pub(crate) override_queries:
+        Option<fn(&Session, &mut ty::query::Providers, &mut ty::query::Providers)>,
+}
+
+impl Compiler {
+    pub fn session(&self) -> &Lrc<Session> {
+        &self.sess
+    }
+    pub fn codegen_backend(&self) -> &Lrc<Box<dyn CodegenBackend>> {
+        &self.codegen_backend
+    }
+    pub fn input(&self) -> &Input {
+        &self.input
+    }
+    pub fn output_dir(&self) -> &Option<PathBuf> {
+        &self.output_dir
+    }
+    pub fn output_file(&self) -> &Option<PathBuf> {
+        &self.output_file
+    }
+    pub fn register_lints(&self) -> &Option<Box<dyn Fn(&Session, &mut LintStore) + Send + Sync>> {
+        &self.register_lints
+    }
+    pub fn build_output_filenames(
+        &self,
+        sess: &Session,
+        attrs: &[ast::Attribute],
+    ) -> OutputFilenames {
+        util::build_output_filenames(
+            &self.input,
+            &self.output_dir,
+            &self.output_file,
+            &attrs,
+            &sess,
+        )
+    }
+}
+
+/// Converts strings provided as `--cfg [cfgspec]` into a `crate_cfg`.
+pub fn parse_cfgspecs(cfgspecs: Vec<String>) -> FxHashSet<(String, Option<String>)> {
+    rustc_span::create_default_session_if_not_set_then(move |_| {
+        let cfg = cfgspecs
+            .into_iter()
+            .map(|s| {
+                let sess = ParseSess::with_silent_emitter();
+                let filename = FileName::cfg_spec_source_code(&s);
+                let mut parser = new_parser_from_source_str(&sess, filename, s.to_string());
+
+                macro_rules! error {
+                    ($reason: expr) => {
+                        early_error(
+                            ErrorOutputType::default(),
+                            &format!(concat!("invalid `--cfg` argument: `{}` (", $reason, ")"), s),
+                        );
+                    };
+                }
+
+                match &mut parser.parse_meta_item() {
+                    Ok(meta_item) if parser.token == token::Eof => {
+                        if meta_item.path.segments.len() != 1 {
+                            error!("argument key must be an identifier");
+                        }
+                        match &meta_item.kind {
+                            MetaItemKind::List(..) => {
+                                error!(r#"expected `key` or `key="value"`"#);
+                            }
+                            MetaItemKind::NameValue(lit) if !lit.kind.is_str() => {
+                                error!("argument value must be a string");
+                            }
+                            MetaItemKind::NameValue(..) | MetaItemKind::Word => {
+                                let ident = meta_item.ident().expect("multi-segment cfg key");
+                                return (ident.name, meta_item.value_str());
+                            }
+                        }
+                    }
+                    Ok(..) => {}
+                    Err(err) => err.cancel(),
+                }
+
+                error!(r#"expected `key` or `key="value"`"#);
+            })
+            .collect::<CrateConfig>();
+        cfg.into_iter().map(|(a, b)| (a.to_string(), b.map(|b| b.to_string()))).collect()
+    })
+}
+
+/// The compiler configuration
+pub struct Config {
+    /// Command line options
+    pub opts: config::Options,
+
+    /// cfg! configuration in addition to the default ones
+    pub crate_cfg: FxHashSet<(String, Option<String>)>,
+
+    pub input: Input,
+    pub input_path: Option<PathBuf>,
+    pub output_dir: Option<PathBuf>,
+    pub output_file: Option<PathBuf>,
+    pub file_loader: Option<Box<dyn FileLoader + Send + Sync>>,
+    pub diagnostic_output: DiagnosticOutput,
+
+    /// Set to capture stderr output during compiler execution
+    pub stderr: Option<Arc<Mutex<Vec<u8>>>>,
+
+    pub lint_caps: FxHashMap<lint::LintId, lint::Level>,
+
+    /// This is a callback from the driver that is called when [`ParseSess`] is created.
+    pub parse_sess_created: Option<Box<dyn FnOnce(&mut ParseSess) + Send>>,
+
+    /// This is a callback from the driver that is called when we're registering lints;
+    /// it is called during plugin registration when we have the LintStore in a non-shared state.
+    ///
+    /// Note that if you find a Some here you probably want to call that function in the new
+    /// function being registered.
+    pub register_lints: Option<Box<dyn Fn(&Session, &mut LintStore) + Send + Sync>>,
+
+    /// This is a callback from the driver that is called just after we have populated
+    /// the list of queries.
+    ///
+    /// The second parameter is local providers and the third parameter is external providers.
+    pub override_queries:
+        Option<fn(&Session, &mut ty::query::Providers, &mut ty::query::Providers)>,
+
+    /// This is a callback from the driver that is called to create a codegen backend.
+    pub make_codegen_backend:
+        Option<Box<dyn FnOnce(&config::Options) -> Box<dyn CodegenBackend> + Send>>,
+
+    /// Registry of diagnostics codes.
+    pub registry: Registry,
+}
+
+pub fn create_compiler_and_run<R>(config: Config, f: impl FnOnce(&Compiler) -> R) -> R {
+    let registry = &config.registry;
+    let (mut sess, codegen_backend) = util::create_session(
+        config.opts,
+        config.crate_cfg,
+        config.diagnostic_output,
+        config.file_loader,
+        config.input_path.clone(),
+        config.lint_caps,
+        config.make_codegen_backend,
+        registry.clone(),
+    );
+
+    if let Some(parse_sess_created) = config.parse_sess_created {
+        parse_sess_created(
+            &mut Lrc::get_mut(&mut sess)
+                .expect("create_session() should never share the returned session")
+                .parse_sess,
+        );
+    }
+
+    let compiler = Compiler {
+        sess,
+        codegen_backend,
+        input: config.input,
+        input_path: config.input_path,
+        output_dir: config.output_dir,
+        output_file: config.output_file,
+        register_lints: config.register_lints,
+        override_queries: config.override_queries,
+    };
+
+    rustc_span::with_source_map(compiler.sess.parse_sess.clone_source_map(), move || {
+        let r = {
+            let _sess_abort_error = OnDrop(|| {
+                compiler.sess.finish_diagnostics(registry);
+            });
+
+            f(&compiler)
+        };
+
+        let prof = compiler.sess.prof.clone();
+        prof.generic_activity("drop_compiler").run(move || drop(compiler));
+        r
+    })
+}
+
+pub fn run_compiler<R: Send>(mut config: Config, f: impl FnOnce(&Compiler) -> R + Send) -> R {
+    tracing::trace!("run_compiler");
+    let stderr = config.stderr.take();
+    util::setup_callbacks_and_run_in_thread_pool_with_globals(
+        config.opts.edition,
+        config.opts.debugging_opts.threads,
+        &stderr,
+        || create_compiler_and_run(config, f),
+    )
+}
+
+pub fn try_print_query_stack(handler: &Handler, num_frames: Option<usize>) {
+    eprintln!("query stack during panic:");
+
+    // Be careful relying on global state here: this code is called from
+    // a panic hook, which means that the global `Handler` may be in a weird
+    // state if it was responsible for triggering the panic.
+    let i = ty::tls::with_context_opt(|icx| {
+        if let Some(icx) = icx {
+            QueryCtxt::from_tcx(icx.tcx).try_print_query_stack(icx.query, handler, num_frames)
+        } else {
+            0
+        }
+    });
+
+    if num_frames == None || num_frames >= Some(i) {
+        eprintln!("end of query stack");
+    } else {
+        eprintln!("we're just showing a limited slice of the query stack");
+    }
+}
diff --git a/compiler/rustc_interface/src/lib.rs b/compiler/rustc_interface/src/lib.rs
new file mode 100644
index 00000000000..c7424b9e2a1
--- /dev/null
+++ b/compiler/rustc_interface/src/lib.rs
@@ -0,0 +1,20 @@
+#![feature(bool_to_option)]
+#![feature(box_patterns)]
+#![feature(internal_output_capture)]
+#![feature(nll)]
+#![feature(once_cell)]
+#![recursion_limit = "256"]
+
+mod callbacks;
+pub mod interface;
+mod passes;
+mod proc_macro_decls;
+mod queries;
+pub mod util;
+
+pub use interface::{run_compiler, Config};
+pub use passes::{DEFAULT_EXTERN_QUERY_PROVIDERS, DEFAULT_QUERY_PROVIDERS};
+pub use queries::Queries;
+
+#[cfg(test)]
+mod tests;
diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs
new file mode 100644
index 00000000000..5db027fb5b4
--- /dev/null
+++ b/compiler/rustc_interface/src/passes.rs
@@ -0,0 +1,1076 @@
+use crate::interface::{Compiler, Result};
+use crate::proc_macro_decls;
+use crate::util;
+
+use rustc_ast::mut_visit::MutVisitor;
+use rustc_ast::{self as ast, visit};
+use rustc_codegen_ssa::back::link::emit_metadata;
+use rustc_codegen_ssa::traits::CodegenBackend;
+use rustc_data_structures::parallel;
+use rustc_data_structures::sync::{par_iter, Lrc, OnceCell, ParallelIterator, WorkerLocal};
+use rustc_data_structures::temp_dir::MaybeTempDir;
+use rustc_errors::{ErrorReported, PResult};
+use rustc_expand::base::ExtCtxt;
+use rustc_hir::def_id::{StableCrateId, LOCAL_CRATE};
+use rustc_hir::Crate;
+use rustc_lint::LintStore;
+use rustc_metadata::creader::CStore;
+use rustc_middle::arena::Arena;
+use rustc_middle::dep_graph::DepGraph;
+use rustc_middle::middle;
+use rustc_middle::middle::cstore::{MetadataLoader, MetadataLoaderDyn};
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::{self, GlobalCtxt, ResolverOutputs, TyCtxt};
+use rustc_mir as mir;
+use rustc_mir_build as mir_build;
+use rustc_parse::{parse_crate_from_file, parse_crate_from_source_str};
+use rustc_passes::{self, hir_stats, layout_test};
+use rustc_plugin_impl as plugin;
+use rustc_query_impl::{OnDiskCache, Queries as TcxQueries};
+use rustc_resolve::{Resolver, ResolverArenas};
+use rustc_serialize::json;
+use rustc_session::config::{CrateType, Input, OutputFilenames, OutputType, PpMode, PpSourceMode};
+use rustc_session::lint;
+use rustc_session::output::{filename_for_input, filename_for_metadata};
+use rustc_session::search_paths::PathKind;
+use rustc_session::Session;
+use rustc_span::symbol::{Ident, Symbol};
+use rustc_span::FileName;
+use rustc_trait_selection::traits;
+use rustc_typeck as typeck;
+use tempfile::Builder as TempFileBuilder;
+use tracing::{info, warn};
+
+use std::any::Any;
+use std::cell::RefCell;
+use std::ffi::OsString;
+use std::io::{self, BufWriter, Write};
+use std::lazy::SyncLazy;
+use std::marker::PhantomPinned;
+use std::path::PathBuf;
+use std::pin::Pin;
+use std::rc::Rc;
+use std::{env, fs, iter};
+
+pub fn parse<'a>(sess: &'a Session, input: &Input) -> PResult<'a, ast::Crate> {
+    let krate = sess.time("parse_crate", || match input {
+        Input::File(file) => parse_crate_from_file(file, &sess.parse_sess),
+        Input::Str { input, name } => {
+            parse_crate_from_source_str(name.clone(), input.clone(), &sess.parse_sess)
+        }
+    })?;
+
+    if sess.opts.debugging_opts.ast_json_noexpand {
+        println!("{}", json::as_json(&krate));
+    }
+
+    if sess.opts.debugging_opts.input_stats {
+        eprintln!("Lines of code:             {}", sess.source_map().count_lines());
+        eprintln!("Pre-expansion node count:  {}", count_nodes(&krate));
+    }
+
+    if let Some(ref s) = sess.opts.debugging_opts.show_span {
+        rustc_ast_passes::show_span::run(sess.diagnostic(), s, &krate);
+    }
+
+    if sess.opts.debugging_opts.hir_stats {
+        hir_stats::print_ast_stats(&krate, "PRE EXPANSION AST STATS");
+    }
+
+    Ok(krate)
+}
+
+fn count_nodes(krate: &ast::Crate) -> usize {
+    let mut counter = rustc_ast_passes::node_count::NodeCounter::new();
+    visit::walk_crate(&mut counter, krate);
+    counter.count
+}
+
+pub use boxed_resolver::BoxedResolver;
+mod boxed_resolver {
+    use super::*;
+
+    pub struct BoxedResolver(Pin<Box<BoxedResolverInner>>);
+
+    struct BoxedResolverInner {
+        session: Lrc<Session>,
+        resolver_arenas: Option<ResolverArenas<'static>>,
+        resolver: Option<Resolver<'static>>,
+        _pin: PhantomPinned,
+    }
+
+    // Note: Drop order is important to prevent dangling references. Resolver must be dropped first,
+    // then resolver_arenas and session.
+    impl Drop for BoxedResolverInner {
+        fn drop(&mut self) {
+            self.resolver.take();
+            self.resolver_arenas.take();
+        }
+    }
+
+    impl BoxedResolver {
+        pub(super) fn new(
+            session: Lrc<Session>,
+            make_resolver: impl for<'a> FnOnce(&'a Session, &'a ResolverArenas<'a>) -> Resolver<'a>,
+        ) -> BoxedResolver {
+            let mut boxed_resolver = Box::new(BoxedResolverInner {
+                session,
+                resolver_arenas: Some(Resolver::arenas()),
+                resolver: None,
+                _pin: PhantomPinned,
+            });
+            // SAFETY: `make_resolver` takes a resolver arena with an arbitrary lifetime and
+            // returns a resolver with the same lifetime as the arena. We ensure that the arena
+            // outlives the resolver in the drop impl and elsewhere so these transmutes are sound.
+            unsafe {
+                let resolver = make_resolver(
+                    std::mem::transmute::<&Session, &Session>(&boxed_resolver.session),
+                    std::mem::transmute::<&ResolverArenas<'_>, &ResolverArenas<'_>>(
+                        boxed_resolver.resolver_arenas.as_ref().unwrap(),
+                    ),
+                );
+                boxed_resolver.resolver = Some(resolver);
+                BoxedResolver(Pin::new_unchecked(boxed_resolver))
+            }
+        }
+
+        pub fn access<F: for<'a> FnOnce(&mut Resolver<'a>) -> R, R>(&mut self, f: F) -> R {
+            // SAFETY: The resolver doesn't need to be pinned.
+            let mut resolver = unsafe {
+                self.0.as_mut().map_unchecked_mut(|boxed_resolver| &mut boxed_resolver.resolver)
+            };
+            f((&mut *resolver).as_mut().unwrap())
+        }
+
+        pub fn to_resolver_outputs(resolver: Rc<RefCell<BoxedResolver>>) -> ResolverOutputs {
+            match Rc::try_unwrap(resolver) {
+                Ok(resolver) => {
+                    let mut resolver = resolver.into_inner();
+                    // SAFETY: The resolver doesn't need to be pinned.
+                    let mut resolver = unsafe {
+                        resolver
+                            .0
+                            .as_mut()
+                            .map_unchecked_mut(|boxed_resolver| &mut boxed_resolver.resolver)
+                    };
+                    resolver.take().unwrap().into_outputs()
+                }
+                Err(resolver) => resolver.borrow_mut().access(|resolver| resolver.clone_outputs()),
+            }
+        }
+    }
+}
+
+pub fn create_resolver(
+    sess: Lrc<Session>,
+    metadata_loader: Box<MetadataLoaderDyn>,
+    krate: &ast::Crate,
+    crate_name: &str,
+) -> BoxedResolver {
+    tracing::trace!("create_resolver");
+    BoxedResolver::new(sess, move |sess, resolver_arenas| {
+        Resolver::new(sess, &krate, &crate_name, metadata_loader, &resolver_arenas)
+    })
+}
+
+pub fn register_plugins<'a>(
+    sess: &'a Session,
+    metadata_loader: &'a dyn MetadataLoader,
+    register_lints: impl Fn(&Session, &mut LintStore),
+    mut krate: ast::Crate,
+    crate_name: &str,
+) -> Result<(ast::Crate, Lrc<LintStore>)> {
+    krate = sess.time("attributes_injection", || {
+        rustc_builtin_macros::cmdline_attrs::inject(
+            krate,
+            &sess.parse_sess,
+            &sess.opts.debugging_opts.crate_attr,
+        )
+    });
+
+    let (krate, features) = rustc_expand::config::features(sess, krate);
+    // these need to be set "early" so that expansion sees `quote` if enabled.
+    sess.init_features(features);
+
+    let crate_types = util::collect_crate_types(sess, &krate.attrs);
+    sess.init_crate_types(crate_types);
+
+    let stable_crate_id = StableCrateId::new(
+        crate_name,
+        sess.crate_types().contains(&CrateType::Executable),
+        sess.opts.cg.metadata.clone(),
+    );
+    sess.stable_crate_id.set(stable_crate_id).expect("not yet initialized");
+    rustc_incremental::prepare_session_directory(sess, &crate_name, stable_crate_id)?;
+
+    if sess.opts.incremental.is_some() {
+        sess.time("incr_comp_garbage_collect_session_directories", || {
+            if let Err(e) = rustc_incremental::garbage_collect_session_directories(sess) {
+                warn!(
+                    "Error while trying to garbage collect incremental \
+                     compilation cache directory: {}",
+                    e
+                );
+            }
+        });
+    }
+
+    let mut lint_store = rustc_lint::new_lint_store(
+        sess.opts.debugging_opts.no_interleave_lints,
+        sess.unstable_options(),
+    );
+    register_lints(&sess, &mut lint_store);
+
+    let registrars =
+        sess.time("plugin_loading", || plugin::load::load_plugins(sess, metadata_loader, &krate));
+    sess.time("plugin_registration", || {
+        let mut registry = plugin::Registry { lint_store: &mut lint_store };
+        for registrar in registrars {
+            registrar(&mut registry);
+        }
+    });
+
+    let lint_store = Lrc::new(lint_store);
+    sess.init_lint_store(lint_store.clone());
+
+    Ok((krate, lint_store))
+}
+
+fn pre_expansion_lint(
+    sess: &Session,
+    lint_store: &LintStore,
+    krate: &ast::Crate,
+    crate_name: &str,
+) {
+    sess.prof.generic_activity_with_arg("pre_AST_expansion_lint_checks", crate_name).run(|| {
+        rustc_lint::check_ast_crate(
+            sess,
+            lint_store,
+            &krate,
+            true,
+            None,
+            rustc_lint::BuiltinCombinedPreExpansionLintPass::new(),
+        );
+    });
+}
+
+/// Runs the "early phases" of the compiler: initial `cfg` processing, loading compiler plugins,
+/// syntax expansion, secondary `cfg` expansion, synthesis of a test
+/// harness if one is to be provided, injection of a dependency on the
+/// standard library and prelude, and name resolution.
+pub fn configure_and_expand(
+    sess: &Session,
+    lint_store: &LintStore,
+    mut krate: ast::Crate,
+    crate_name: &str,
+    resolver: &mut Resolver<'_>,
+) -> Result<ast::Crate> {
+    tracing::trace!("configure_and_expand");
+    pre_expansion_lint(sess, lint_store, &krate, crate_name);
+    rustc_builtin_macros::register_builtin_macros(resolver);
+
+    krate = sess.time("crate_injection", || {
+        let alt_std_name = sess.opts.alt_std_name.as_ref().map(|s| Symbol::intern(s));
+        rustc_builtin_macros::standard_library_imports::inject(krate, resolver, &sess, alt_std_name)
+    });
+
+    util::check_attr_crate_type(&sess, &krate.attrs, &mut resolver.lint_buffer());
+
+    // Expand all macros
+    krate = sess.time("macro_expand_crate", || {
+        // Windows dlls do not have rpaths, so they don't know how to find their
+        // dependencies. It's up to us to tell the system where to find all the
+        // dependent dlls. Note that this uses cfg!(windows) as opposed to
+        // targ_cfg because syntax extensions are always loaded for the host
+        // compiler, not for the target.
+        //
+        // This is somewhat of an inherently racy operation, however, as
+        // multiple threads calling this function could possibly continue
+        // extending PATH far beyond what it should. To solve this for now we
+        // just don't add any new elements to PATH which are already there
+        // within PATH. This is basically a targeted fix at #17360 for rustdoc
+        // which runs rustc in parallel but has been seen (#33844) to cause
+        // problems with PATH becoming too long.
+        let mut old_path = OsString::new();
+        if cfg!(windows) {
+            old_path = env::var_os("PATH").unwrap_or(old_path);
+            let mut new_path = sess.host_filesearch(PathKind::All).search_path_dirs();
+            for path in env::split_paths(&old_path) {
+                if !new_path.contains(&path) {
+                    new_path.push(path);
+                }
+            }
+            env::set_var(
+                "PATH",
+                &env::join_paths(
+                    new_path.iter().filter(|p| env::join_paths(iter::once(p)).is_ok()),
+                )
+                .unwrap(),
+            );
+        }
+
+        // Create the config for macro expansion
+        let features = sess.features_untracked();
+        let recursion_limit =
+            rustc_middle::middle::limits::get_recursion_limit(&krate.attrs, &sess);
+        let cfg = rustc_expand::expand::ExpansionConfig {
+            features: Some(&features),
+            recursion_limit,
+            trace_mac: sess.opts.debugging_opts.trace_macros,
+            should_test: sess.opts.test,
+            span_debug: sess.opts.debugging_opts.span_debug,
+            proc_macro_backtrace: sess.opts.debugging_opts.proc_macro_backtrace,
+            ..rustc_expand::expand::ExpansionConfig::default(crate_name.to_string())
+        };
+
+        let extern_mod_loaded = |ident: Ident, attrs, items, span| {
+            let krate = ast::Crate { attrs, items, span, proc_macros: vec![] };
+            pre_expansion_lint(sess, lint_store, &krate, &ident.name.as_str());
+            (krate.attrs, krate.items)
+        };
+        let mut ecx = ExtCtxt::new(&sess, cfg, resolver, Some(&extern_mod_loaded));
+
+        // Expand macros now!
+        let krate = sess.time("expand_crate", || ecx.monotonic_expander().expand_crate(krate));
+
+        // The rest is error reporting
+
+        sess.time("check_unused_macros", || {
+            ecx.check_unused_macros();
+        });
+
+        let mut missing_fragment_specifiers: Vec<_> = ecx
+            .sess
+            .parse_sess
+            .missing_fragment_specifiers
+            .borrow()
+            .iter()
+            .map(|(span, node_id)| (*span, *node_id))
+            .collect();
+        missing_fragment_specifiers.sort_unstable_by_key(|(span, _)| *span);
+
+        let recursion_limit_hit = ecx.reduced_recursion_limit.is_some();
+
+        for (span, node_id) in missing_fragment_specifiers {
+            let lint = lint::builtin::MISSING_FRAGMENT_SPECIFIER;
+            let msg = "missing fragment specifier";
+            resolver.lint_buffer().buffer_lint(lint, node_id, span, msg);
+        }
+        if cfg!(windows) {
+            env::set_var("PATH", &old_path);
+        }
+
+        if recursion_limit_hit {
+            // If we hit a recursion limit, exit early to avoid later passes getting overwhelmed
+            // with a large AST
+            Err(ErrorReported)
+        } else {
+            Ok(krate)
+        }
+    })?;
+
+    sess.time("maybe_building_test_harness", || {
+        rustc_builtin_macros::test_harness::inject(&sess, resolver, &mut krate)
+    });
+
+    if let Some(PpMode::Source(PpSourceMode::EveryBodyLoops)) = sess.opts.pretty {
+        tracing::debug!("replacing bodies with loop {{}}");
+        util::ReplaceBodyWithLoop::new(resolver).visit_crate(&mut krate);
+    }
+
+    let has_proc_macro_decls = sess.time("AST_validation", || {
+        rustc_ast_passes::ast_validation::check_crate(sess, &krate, resolver.lint_buffer())
+    });
+
+    let crate_types = sess.crate_types();
+    let is_proc_macro_crate = crate_types.contains(&CrateType::ProcMacro);
+
+    // For backwards compatibility, we don't try to run proc macro injection
+    // if rustdoc is run on a proc macro crate without '--crate-type proc-macro' being
+    // specified. This should only affect users who manually invoke 'rustdoc', as
+    // 'cargo doc' will automatically pass the proper '--crate-type' flags.
+    // However, we do emit a warning, to let such users know that they should
+    // start passing '--crate-type proc-macro'
+    if has_proc_macro_decls && sess.opts.actually_rustdoc && !is_proc_macro_crate {
+        let mut msg = sess.diagnostic().struct_warn(
+            &"Trying to document proc macro crate \
+            without passing '--crate-type proc-macro to rustdoc",
+        );
+
+        msg.warn("The generated documentation may be incorrect");
+        msg.emit()
+    } else {
+        krate = sess.time("maybe_create_a_macro_crate", || {
+            let num_crate_types = crate_types.len();
+            let is_test_crate = sess.opts.test;
+            rustc_builtin_macros::proc_macro_harness::inject(
+                &sess,
+                resolver,
+                krate,
+                is_proc_macro_crate,
+                has_proc_macro_decls,
+                is_test_crate,
+                num_crate_types,
+                sess.diagnostic(),
+            )
+        });
+    }
+
+    // Done with macro expansion!
+
+    if sess.opts.debugging_opts.input_stats {
+        eprintln!("Post-expansion node count: {}", count_nodes(&krate));
+    }
+
+    if sess.opts.debugging_opts.hir_stats {
+        hir_stats::print_ast_stats(&krate, "POST EXPANSION AST STATS");
+    }
+
+    if sess.opts.debugging_opts.ast_json {
+        println!("{}", json::as_json(&krate));
+    }
+
+    resolver.resolve_crate(&krate);
+
+    // Needs to go *after* expansion to be able to check the results of macro expansion.
+    sess.time("complete_gated_feature_checking", || {
+        rustc_ast_passes::feature_gate::check_crate(&krate, sess);
+    });
+
+    // Add all buffered lints from the `ParseSess` to the `Session`.
+    sess.parse_sess.buffered_lints.with_lock(|buffered_lints| {
+        info!("{} parse sess buffered_lints", buffered_lints.len());
+        for early_lint in buffered_lints.drain(..) {
+            resolver.lint_buffer().add_early_lint(early_lint);
+        }
+    });
+
+    Ok(krate)
+}
+
+pub fn lower_to_hir<'res, 'tcx>(
+    sess: &'tcx Session,
+    lint_store: &LintStore,
+    resolver: &'res mut Resolver<'_>,
+    krate: Rc<ast::Crate>,
+    arena: &'tcx rustc_ast_lowering::Arena<'tcx>,
+) -> &'tcx Crate<'tcx> {
+    // Lower AST to HIR.
+    let hir_crate = rustc_ast_lowering::lower_crate(
+        sess,
+        &*krate,
+        resolver,
+        rustc_parse::nt_to_tokenstream,
+        arena,
+    );
+
+    if sess.opts.debugging_opts.hir_stats {
+        hir_stats::print_hir_stats(&hir_crate);
+    }
+
+    sess.time("early_lint_checks", || {
+        rustc_lint::check_ast_crate(
+            sess,
+            lint_store,
+            &krate,
+            false,
+            Some(std::mem::take(resolver.lint_buffer())),
+            rustc_lint::BuiltinCombinedEarlyLintPass::new(),
+        )
+    });
+
+    // Drop AST to free memory
+    sess.time("drop_ast", || std::mem::drop(krate));
+
+    // Discard hygiene data, which isn't required after lowering to HIR.
+    if !sess.opts.debugging_opts.keep_hygiene_data {
+        rustc_span::hygiene::clear_syntax_context_map();
+    }
+
+    hir_crate
+}
+
+// Returns all the paths that correspond to generated files.
+fn generated_output_paths(
+    sess: &Session,
+    outputs: &OutputFilenames,
+    exact_name: bool,
+    crate_name: &str,
+) -> Vec<PathBuf> {
+    let mut out_filenames = Vec::new();
+    for output_type in sess.opts.output_types.keys() {
+        let file = outputs.path(*output_type);
+        match *output_type {
+            // If the filename has been overridden using `-o`, it will not be modified
+            // by appending `.rlib`, `.exe`, etc., so we can skip this transformation.
+            OutputType::Exe if !exact_name => {
+                for crate_type in sess.crate_types().iter() {
+                    let p = filename_for_input(sess, *crate_type, crate_name, outputs);
+                    out_filenames.push(p);
+                }
+            }
+            OutputType::DepInfo if sess.opts.debugging_opts.dep_info_omit_d_target => {
+                // Don't add the dep-info output when omitting it from dep-info targets
+            }
+            _ => {
+                out_filenames.push(file);
+            }
+        }
+    }
+    out_filenames
+}
+
+// Runs `f` on every output file path and returns the first non-None result, or None if `f`
+// returns None for every file path.
+fn check_output<F, T>(output_paths: &[PathBuf], f: F) -> Option<T>
+where
+    F: Fn(&PathBuf) -> Option<T>,
+{
+    for output_path in output_paths {
+        if let Some(result) = f(output_path) {
+            return Some(result);
+        }
+    }
+    None
+}
+
+fn output_contains_path(output_paths: &[PathBuf], input_path: &PathBuf) -> bool {
+    let input_path = input_path.canonicalize().ok();
+    if input_path.is_none() {
+        return false;
+    }
+    let check = |output_path: &PathBuf| {
+        if output_path.canonicalize().ok() == input_path { Some(()) } else { None }
+    };
+    check_output(output_paths, check).is_some()
+}
+
+fn output_conflicts_with_dir(output_paths: &[PathBuf]) -> Option<PathBuf> {
+    let check = |output_path: &PathBuf| output_path.is_dir().then(|| output_path.clone());
+    check_output(output_paths, check)
+}
+
+fn escape_dep_filename(filename: &String) -> String {
+    // Apparently clang and gcc *only* escape spaces:
+    // https://llvm.org/klaus/clang/commit/9d50634cfc268ecc9a7250226dd5ca0e945240d4
+    filename.replace(" ", "\\ ")
+}
+
+// Makefile comments only need escaping newlines and `\`.
+// The result can be unescaped by anything that can unescape `escape_default` and friends.
+fn escape_dep_env(symbol: Symbol) -> String {
+    let s = symbol.as_str();
+    let mut escaped = String::with_capacity(s.len());
+    for c in s.chars() {
+        match c {
+            '\n' => escaped.push_str(r"\n"),
+            '\r' => escaped.push_str(r"\r"),
+            '\\' => escaped.push_str(r"\\"),
+            _ => escaped.push(c),
+        }
+    }
+    escaped
+}
+
+fn write_out_deps(
+    sess: &Session,
+    boxed_resolver: &RefCell<BoxedResolver>,
+    outputs: &OutputFilenames,
+    out_filenames: &[PathBuf],
+) {
+    // Write out dependency rules to the dep-info file if requested
+    if !sess.opts.output_types.contains_key(&OutputType::DepInfo) {
+        return;
+    }
+    let deps_filename = outputs.path(OutputType::DepInfo);
+
+    let result = (|| -> io::Result<()> {
+        // Build a list of files used to compile the output and
+        // write Makefile-compatible dependency rules
+        let mut files: Vec<String> = sess
+            .source_map()
+            .files()
+            .iter()
+            .filter(|fmap| fmap.is_real_file())
+            .filter(|fmap| !fmap.is_imported())
+            .map(|fmap| escape_dep_filename(&fmap.name.prefer_local().to_string()))
+            .collect();
+
+        // Account for explicitly marked-to-track files
+        // (e.g. accessed in proc macros).
+        let file_depinfo = sess.parse_sess.file_depinfo.borrow();
+        let extra_tracked_files = file_depinfo.iter().map(|path_sym| {
+            let path = PathBuf::from(&*path_sym.as_str());
+            let file = FileName::from(path);
+            escape_dep_filename(&file.prefer_local().to_string())
+        });
+        files.extend(extra_tracked_files);
+
+        if let Some(ref backend) = sess.opts.debugging_opts.codegen_backend {
+            files.push(backend.to_string());
+        }
+
+        if sess.binary_dep_depinfo() {
+            boxed_resolver.borrow_mut().access(|resolver| {
+                for cnum in resolver.cstore().crates_untracked() {
+                    let source = resolver.cstore().crate_source_untracked(cnum);
+                    if let Some((path, _)) = source.dylib {
+                        files.push(escape_dep_filename(&path.display().to_string()));
+                    }
+                    if let Some((path, _)) = source.rlib {
+                        files.push(escape_dep_filename(&path.display().to_string()));
+                    }
+                    if let Some((path, _)) = source.rmeta {
+                        files.push(escape_dep_filename(&path.display().to_string()));
+                    }
+                }
+            });
+        }
+
+        let mut file = BufWriter::new(fs::File::create(&deps_filename)?);
+        for path in out_filenames {
+            writeln!(file, "{}: {}\n", path.display(), files.join(" "))?;
+        }
+
+        // Emit a fake target for each input file to the compilation. This
+        // prevents `make` from spitting out an error if a file is later
+        // deleted. For more info see #28735
+        for path in files {
+            writeln!(file, "{}:", path)?;
+        }
+
+        // Emit special comments with information about accessed environment variables.
+        let env_depinfo = sess.parse_sess.env_depinfo.borrow();
+        if !env_depinfo.is_empty() {
+            let mut envs: Vec<_> = env_depinfo
+                .iter()
+                .map(|(k, v)| (escape_dep_env(*k), v.map(escape_dep_env)))
+                .collect();
+            envs.sort_unstable();
+            writeln!(file)?;
+            for (k, v) in envs {
+                write!(file, "# env-dep:{}", k)?;
+                if let Some(v) = v {
+                    write!(file, "={}", v)?;
+                }
+                writeln!(file)?;
+            }
+        }
+
+        Ok(())
+    })();
+
+    match result {
+        Ok(_) => {
+            if sess.opts.json_artifact_notifications {
+                sess.parse_sess
+                    .span_diagnostic
+                    .emit_artifact_notification(&deps_filename, "dep-info");
+            }
+        }
+        Err(e) => sess.fatal(&format!(
+            "error writing dependencies to `{}`: {}",
+            deps_filename.display(),
+            e
+        )),
+    }
+}
+
+pub fn prepare_outputs(
+    sess: &Session,
+    compiler: &Compiler,
+    krate: &ast::Crate,
+    boxed_resolver: &RefCell<BoxedResolver>,
+    crate_name: &str,
+) -> Result<OutputFilenames> {
+    let _timer = sess.timer("prepare_outputs");
+
+    // FIXME: rustdoc passes &[] instead of &krate.attrs here
+    let outputs = util::build_output_filenames(
+        &compiler.input,
+        &compiler.output_dir,
+        &compiler.output_file,
+        &krate.attrs,
+        sess,
+    );
+
+    let output_paths =
+        generated_output_paths(sess, &outputs, compiler.output_file.is_some(), &crate_name);
+
+    // Ensure the source file isn't accidentally overwritten during compilation.
+    if let Some(ref input_path) = compiler.input_path {
+        if sess.opts.will_create_output_file() {
+            if output_contains_path(&output_paths, input_path) {
+                sess.err(&format!(
+                    "the input file \"{}\" would be overwritten by the generated \
+                        executable",
+                    input_path.display()
+                ));
+                return Err(ErrorReported);
+            }
+            if let Some(dir_path) = output_conflicts_with_dir(&output_paths) {
+                sess.err(&format!(
+                    "the generated executable for the input file \"{}\" conflicts with the \
+                        existing directory \"{}\"",
+                    input_path.display(),
+                    dir_path.display()
+                ));
+                return Err(ErrorReported);
+            }
+        }
+    }
+
+    write_out_deps(sess, boxed_resolver, &outputs, &output_paths);
+
+    let only_dep_info = sess.opts.output_types.contains_key(&OutputType::DepInfo)
+        && sess.opts.output_types.len() == 1;
+
+    if !only_dep_info {
+        if let Some(ref dir) = compiler.output_dir {
+            if fs::create_dir_all(dir).is_err() {
+                sess.err("failed to find or create the directory specified by `--out-dir`");
+                return Err(ErrorReported);
+            }
+        }
+    }
+
+    Ok(outputs)
+}
+
+pub static DEFAULT_QUERY_PROVIDERS: SyncLazy<Providers> = SyncLazy::new(|| {
+    let providers = &mut Providers::default();
+    providers.analysis = analysis;
+    proc_macro_decls::provide(providers);
+    plugin::build::provide(providers);
+    rustc_middle::hir::provide(providers);
+    mir::provide(providers);
+    mir_build::provide(providers);
+    rustc_privacy::provide(providers);
+    typeck::provide(providers);
+    ty::provide(providers);
+    traits::provide(providers);
+    rustc_passes::provide(providers);
+    rustc_resolve::provide(providers);
+    rustc_traits::provide(providers);
+    rustc_ty_utils::provide(providers);
+    rustc_metadata::provide(providers);
+    rustc_lint::provide(providers);
+    rustc_symbol_mangling::provide(providers);
+    rustc_codegen_ssa::provide(providers);
+    *providers
+});
+
+pub static DEFAULT_EXTERN_QUERY_PROVIDERS: SyncLazy<Providers> = SyncLazy::new(|| {
+    let mut extern_providers = *DEFAULT_QUERY_PROVIDERS;
+    rustc_metadata::provide_extern(&mut extern_providers);
+    rustc_codegen_ssa::provide_extern(&mut extern_providers);
+    extern_providers
+});
+
+pub struct QueryContext<'tcx> {
+    gcx: &'tcx GlobalCtxt<'tcx>,
+}
+
+impl<'tcx> QueryContext<'tcx> {
+    pub fn enter<F, R>(&mut self, f: F) -> R
+    where
+        F: FnOnce(TyCtxt<'tcx>) -> R,
+    {
+        let icx = ty::tls::ImplicitCtxt::new(self.gcx);
+        ty::tls::enter_context(&icx, |_| f(icx.tcx))
+    }
+}
+
+pub fn create_global_ctxt<'tcx>(
+    compiler: &'tcx Compiler,
+    lint_store: Lrc<LintStore>,
+    krate: Rc<ast::Crate>,
+    dep_graph: DepGraph,
+    resolver: Rc<RefCell<BoxedResolver>>,
+    outputs: OutputFilenames,
+    crate_name: &str,
+    queries: &'tcx OnceCell<TcxQueries<'tcx>>,
+    global_ctxt: &'tcx OnceCell<GlobalCtxt<'tcx>>,
+    arena: &'tcx WorkerLocal<Arena<'tcx>>,
+    hir_arena: &'tcx WorkerLocal<rustc_ast_lowering::Arena<'tcx>>,
+) -> QueryContext<'tcx> {
+    // We're constructing the HIR here; we don't care what we will
+    // read, since we haven't even constructed the *input* to
+    // incr. comp. yet.
+    dep_graph.assert_ignored();
+
+    let sess = &compiler.session();
+    let krate = resolver
+        .borrow_mut()
+        .access(|resolver| lower_to_hir(sess, &lint_store, resolver, krate, hir_arena));
+    let resolver_outputs = BoxedResolver::to_resolver_outputs(resolver);
+
+    let query_result_on_disk_cache = rustc_incremental::load_query_result_cache(sess);
+
+    let codegen_backend = compiler.codegen_backend();
+    let mut local_providers = *DEFAULT_QUERY_PROVIDERS;
+    codegen_backend.provide(&mut local_providers);
+
+    let mut extern_providers = *DEFAULT_EXTERN_QUERY_PROVIDERS;
+    codegen_backend.provide(&mut extern_providers);
+    codegen_backend.provide_extern(&mut extern_providers);
+
+    if let Some(callback) = compiler.override_queries {
+        callback(sess, &mut local_providers, &mut extern_providers);
+    }
+
+    let queries = queries.get_or_init(|| {
+        TcxQueries::new(local_providers, extern_providers, query_result_on_disk_cache)
+    });
+
+    let gcx = sess.time("setup_global_ctxt", || {
+        global_ctxt.get_or_init(move || {
+            TyCtxt::create_global_ctxt(
+                sess,
+                lint_store,
+                arena,
+                resolver_outputs,
+                krate,
+                dep_graph,
+                queries.on_disk_cache.as_ref().map(OnDiskCache::as_dyn),
+                queries.as_dyn(),
+                &crate_name,
+                outputs,
+            )
+        })
+    });
+
+    QueryContext { gcx }
+}
+
+/// Runs the resolution, type-checking, region checking and other
+/// miscellaneous analysis passes on the crate.
+fn analysis(tcx: TyCtxt<'_>, (): ()) -> Result<()> {
+    rustc_passes::hir_id_validator::check_crate(tcx);
+
+    let sess = tcx.sess;
+    let mut entry_point = None;
+
+    sess.time("misc_checking_1", || {
+        parallel!(
+            {
+                entry_point = sess.time("looking_for_entry_point", || tcx.entry_fn(()));
+
+                sess.time("looking_for_plugin_registrar", || tcx.ensure().plugin_registrar_fn(()));
+
+                sess.time("looking_for_derive_registrar", || {
+                    tcx.ensure().proc_macro_decls_static(())
+                });
+
+                CStore::from_tcx(tcx).report_unused_deps(tcx);
+            },
+            {
+                par_iter(&tcx.hir().krate().modules).for_each(|(&module, _)| {
+                    tcx.ensure().check_mod_loops(module);
+                    tcx.ensure().check_mod_attrs(module);
+                    tcx.ensure().check_mod_naked_functions(module);
+                    tcx.ensure().check_mod_unstable_api_usage(module);
+                    tcx.ensure().check_mod_const_bodies(module);
+                });
+            },
+            {
+                // We force these querie to run,
+                // since they might not otherwise get called.
+                // This marks the corresponding crate-level attributes
+                // as used, and ensures that their values are valid.
+                tcx.ensure().limits(());
+            }
+        );
+    });
+
+    // passes are timed inside typeck
+    typeck::check_crate(tcx)?;
+
+    sess.time("misc_checking_2", || {
+        parallel!(
+            {
+                sess.time("match_checking", || {
+                    tcx.par_body_owners(|def_id| {
+                        tcx.ensure().check_match(def_id.to_def_id());
+                    });
+                });
+            },
+            {
+                sess.time("liveness_and_intrinsic_checking", || {
+                    par_iter(&tcx.hir().krate().modules).for_each(|(&module, _)| {
+                        // this must run before MIR dump, because
+                        // "not all control paths return a value" is reported here.
+                        //
+                        // maybe move the check to a MIR pass?
+                        tcx.ensure().check_mod_liveness(module);
+                        tcx.ensure().check_mod_intrinsics(module);
+                    });
+                });
+            }
+        );
+    });
+
+    sess.time("MIR_borrow_checking", || {
+        tcx.par_body_owners(|def_id| tcx.ensure().mir_borrowck(def_id));
+    });
+
+    sess.time("MIR_effect_checking", || {
+        for def_id in tcx.body_owners() {
+            tcx.ensure().thir_check_unsafety(def_id);
+            if !tcx.sess.opts.debugging_opts.thir_unsafeck {
+                mir::transform::check_unsafety::check_unsafety(tcx, def_id);
+            }
+
+            if tcx.hir().body_const_context(def_id).is_some() {
+                tcx.ensure()
+                    .mir_drops_elaborated_and_const_checked(ty::WithOptConstParam::unknown(def_id));
+            }
+        }
+    });
+
+    sess.time("layout_testing", || layout_test::test_layout(tcx));
+
+    // Avoid overwhelming user with errors if borrow checking failed.
+    // I'm not sure how helpful this is, to be honest, but it avoids a
+    // lot of annoying errors in the ui tests (basically,
+    // lint warnings and so on -- kindck used to do this abort, but
+    // kindck is gone now). -nmatsakis
+    if sess.has_errors() {
+        return Err(ErrorReported);
+    }
+
+    sess.time("misc_checking_3", || {
+        parallel!(
+            {
+                tcx.ensure().privacy_access_levels(());
+
+                parallel!(
+                    {
+                        tcx.ensure().check_private_in_public(());
+                    },
+                    {
+                        sess.time("death_checking", || rustc_passes::dead::check_crate(tcx));
+                    },
+                    {
+                        sess.time("unused_lib_feature_checking", || {
+                            rustc_passes::stability::check_unused_or_stable_features(tcx)
+                        });
+                    },
+                    {
+                        sess.time("lint_checking", || {
+                            rustc_lint::check_crate(tcx, || {
+                                rustc_lint::BuiltinCombinedLateLintPass::new()
+                            });
+                        });
+                    }
+                );
+            },
+            {
+                sess.time("privacy_checking_modules", || {
+                    par_iter(&tcx.hir().krate().modules).for_each(|(&module, _)| {
+                        tcx.ensure().check_mod_privacy(module);
+                    });
+                });
+            }
+        );
+    });
+
+    Ok(())
+}
+
+fn encode_and_write_metadata(
+    tcx: TyCtxt<'_>,
+    outputs: &OutputFilenames,
+) -> (middle::cstore::EncodedMetadata, bool) {
+    #[derive(PartialEq, Eq, PartialOrd, Ord)]
+    enum MetadataKind {
+        None,
+        Uncompressed,
+        Compressed,
+    }
+
+    let metadata_kind = tcx
+        .sess
+        .crate_types()
+        .iter()
+        .map(|ty| match *ty {
+            CrateType::Executable | CrateType::Staticlib | CrateType::Cdylib => MetadataKind::None,
+
+            CrateType::Rlib => MetadataKind::Uncompressed,
+
+            CrateType::Dylib | CrateType::ProcMacro => MetadataKind::Compressed,
+        })
+        .max()
+        .unwrap_or(MetadataKind::None);
+
+    let metadata = match metadata_kind {
+        MetadataKind::None => middle::cstore::EncodedMetadata::new(),
+        MetadataKind::Uncompressed | MetadataKind::Compressed => tcx.encode_metadata(),
+    };
+
+    let _prof_timer = tcx.sess.prof.generic_activity("write_crate_metadata");
+
+    let need_metadata_file = tcx.sess.opts.output_types.contains_key(&OutputType::Metadata);
+    if need_metadata_file {
+        let crate_name = &tcx.crate_name(LOCAL_CRATE).as_str();
+        let out_filename = filename_for_metadata(tcx.sess, crate_name, outputs);
+        // To avoid races with another rustc process scanning the output directory,
+        // we need to write the file somewhere else and atomically move it to its
+        // final destination, with an `fs::rename` call. In order for the rename to
+        // always succeed, the temporary file needs to be on the same filesystem,
+        // which is why we create it inside the output directory specifically.
+        let metadata_tmpdir = TempFileBuilder::new()
+            .prefix("rmeta")
+            .tempdir_in(out_filename.parent().unwrap())
+            .unwrap_or_else(|err| tcx.sess.fatal(&format!("couldn't create a temp dir: {}", err)));
+        let metadata_tmpdir = MaybeTempDir::new(metadata_tmpdir, tcx.sess.opts.cg.save_temps);
+        let metadata_filename = emit_metadata(tcx.sess, &metadata.raw_data, &metadata_tmpdir);
+        if let Err(e) = util::non_durable_rename(&metadata_filename, &out_filename) {
+            tcx.sess.fatal(&format!("failed to write {}: {}", out_filename.display(), e));
+        }
+        if tcx.sess.opts.json_artifact_notifications {
+            tcx.sess
+                .parse_sess
+                .span_diagnostic
+                .emit_artifact_notification(&out_filename, "metadata");
+        }
+    }
+
+    let need_metadata_module = metadata_kind == MetadataKind::Compressed;
+
+    (metadata, need_metadata_module)
+}
+
+/// Runs the codegen backend, after which the AST and analysis can
+/// be discarded.
+pub fn start_codegen<'tcx>(
+    codegen_backend: &dyn CodegenBackend,
+    tcx: TyCtxt<'tcx>,
+    outputs: &OutputFilenames,
+) -> Box<dyn Any> {
+    info!("Pre-codegen\n{:?}", tcx.debug_stats());
+
+    let (metadata, need_metadata_module) = encode_and_write_metadata(tcx, outputs);
+
+    let codegen = tcx.sess.time("codegen_crate", move || {
+        codegen_backend.codegen_crate(tcx, metadata, need_metadata_module)
+    });
+
+    // Don't run these test assertions when not doing codegen. Compiletest tries to build
+    // build-fail tests in check mode first and expects it to not give an error in that case.
+    if tcx.sess.opts.output_types.should_codegen() {
+        rustc_incremental::assert_module_sources::assert_module_sources(tcx);
+        rustc_symbol_mangling::test::report_symbol_names(tcx);
+    }
+
+    info!("Post-codegen\n{:?}", tcx.debug_stats());
+
+    if tcx.sess.opts.output_types.contains_key(&OutputType::Mir) {
+        if let Err(e) = mir::transform::dump_mir::emit_mir(tcx, outputs) {
+            tcx.sess.err(&format!("could not emit MIR: {}", e));
+            tcx.sess.abort_if_errors();
+        }
+    }
+
+    codegen
+}
diff --git a/compiler/rustc_interface/src/proc_macro_decls.rs b/compiler/rustc_interface/src/proc_macro_decls.rs
new file mode 100644
index 00000000000..88cf6275ebb
--- /dev/null
+++ b/compiler/rustc_interface/src/proc_macro_decls.rs
@@ -0,0 +1,37 @@
+use rustc_hir as hir;
+use rustc_hir::def_id::LocalDefId;
+use rustc_hir::itemlikevisit::ItemLikeVisitor;
+use rustc_middle::ty::query::Providers;
+use rustc_middle::ty::TyCtxt;
+use rustc_span::symbol::sym;
+
+fn proc_macro_decls_static(tcx: TyCtxt<'_>, (): ()) -> Option<LocalDefId> {
+    let mut finder = Finder { tcx, decls: None };
+    tcx.hir().krate().visit_all_item_likes(&mut finder);
+
+    finder.decls.map(|id| tcx.hir().local_def_id(id))
+}
+
+struct Finder<'tcx> {
+    tcx: TyCtxt<'tcx>,
+    decls: Option<hir::HirId>,
+}
+
+impl<'v> ItemLikeVisitor<'v> for Finder<'_> {
+    fn visit_item(&mut self, item: &hir::Item<'_>) {
+        let attrs = self.tcx.hir().attrs(item.hir_id());
+        if self.tcx.sess.contains_name(attrs, sym::rustc_proc_macro_decls) {
+            self.decls = Some(item.hir_id());
+        }
+    }
+
+    fn visit_trait_item(&mut self, _trait_item: &hir::TraitItem<'_>) {}
+
+    fn visit_impl_item(&mut self, _impl_item: &hir::ImplItem<'_>) {}
+
+    fn visit_foreign_item(&mut self, _foreign_item: &hir::ForeignItem<'_>) {}
+}
+
+pub(crate) fn provide(providers: &mut Providers) {
+    *providers = Providers { proc_macro_decls_static, ..*providers };
+}
diff --git a/compiler/rustc_interface/src/queries.rs b/compiler/rustc_interface/src/queries.rs
new file mode 100644
index 00000000000..8a0964e6b9f
--- /dev/null
+++ b/compiler/rustc_interface/src/queries.rs
@@ -0,0 +1,415 @@
+use crate::interface::{Compiler, Result};
+use crate::passes::{self, BoxedResolver, QueryContext};
+
+use rustc_ast as ast;
+use rustc_codegen_ssa::traits::CodegenBackend;
+use rustc_data_structures::svh::Svh;
+use rustc_data_structures::sync::{Lrc, OnceCell, WorkerLocal};
+use rustc_errors::ErrorReported;
+use rustc_hir::def_id::LOCAL_CRATE;
+use rustc_incremental::DepGraphFuture;
+use rustc_lint::LintStore;
+use rustc_middle::arena::Arena;
+use rustc_middle::dep_graph::DepGraph;
+use rustc_middle::ty::{GlobalCtxt, TyCtxt};
+use rustc_query_impl::Queries as TcxQueries;
+use rustc_serialize::json;
+use rustc_session::config::{self, OutputFilenames, OutputType};
+use rustc_session::{output::find_crate_name, Session};
+use rustc_span::symbol::sym;
+use std::any::Any;
+use std::cell::{Ref, RefCell, RefMut};
+use std::rc::Rc;
+
+/// Represent the result of a query.
+///
+/// This result can be stolen with the [`take`] method and generated with the [`compute`] method.
+///
+/// [`take`]: Self::take
+/// [`compute`]: Self::compute
+pub struct Query<T> {
+    result: RefCell<Option<Result<T>>>,
+}
+
+impl<T> Query<T> {
+    fn compute<F: FnOnce() -> Result<T>>(&self, f: F) -> Result<&Query<T>> {
+        let mut result = self.result.borrow_mut();
+        if result.is_none() {
+            *result = Some(f());
+        }
+        result.as_ref().unwrap().as_ref().map(|_| self).map_err(|err| *err)
+    }
+
+    /// Takes ownership of the query result. Further attempts to take or peek the query
+    /// result will panic unless it is generated by calling the `compute` method.
+    pub fn take(&self) -> T {
+        self.result.borrow_mut().take().expect("missing query result").unwrap()
+    }
+
+    /// Borrows the query result using the RefCell. Panics if the result is stolen.
+    pub fn peek(&self) -> Ref<'_, T> {
+        Ref::map(self.result.borrow(), |r| {
+            r.as_ref().unwrap().as_ref().expect("missing query result")
+        })
+    }
+
+    /// Mutably borrows the query result using the RefCell. Panics if the result is stolen.
+    pub fn peek_mut(&self) -> RefMut<'_, T> {
+        RefMut::map(self.result.borrow_mut(), |r| {
+            r.as_mut().unwrap().as_mut().expect("missing query result")
+        })
+    }
+}
+
+impl<T> Default for Query<T> {
+    fn default() -> Self {
+        Query { result: RefCell::new(None) }
+    }
+}
+
+pub struct Queries<'tcx> {
+    compiler: &'tcx Compiler,
+    gcx: OnceCell<GlobalCtxt<'tcx>>,
+    queries: OnceCell<TcxQueries<'tcx>>,
+
+    arena: WorkerLocal<Arena<'tcx>>,
+    hir_arena: WorkerLocal<rustc_ast_lowering::Arena<'tcx>>,
+
+    dep_graph_future: Query<Option<DepGraphFuture>>,
+    parse: Query<ast::Crate>,
+    crate_name: Query<String>,
+    register_plugins: Query<(ast::Crate, Lrc<LintStore>)>,
+    expansion: Query<(Rc<ast::Crate>, Rc<RefCell<BoxedResolver>>, Lrc<LintStore>)>,
+    dep_graph: Query<DepGraph>,
+    prepare_outputs: Query<OutputFilenames>,
+    global_ctxt: Query<QueryContext<'tcx>>,
+    ongoing_codegen: Query<Box<dyn Any>>,
+}
+
+impl<'tcx> Queries<'tcx> {
+    pub fn new(compiler: &'tcx Compiler) -> Queries<'tcx> {
+        Queries {
+            compiler,
+            gcx: OnceCell::new(),
+            queries: OnceCell::new(),
+            arena: WorkerLocal::new(|_| Arena::default()),
+            hir_arena: WorkerLocal::new(|_| rustc_ast_lowering::Arena::default()),
+            dep_graph_future: Default::default(),
+            parse: Default::default(),
+            crate_name: Default::default(),
+            register_plugins: Default::default(),
+            expansion: Default::default(),
+            dep_graph: Default::default(),
+            prepare_outputs: Default::default(),
+            global_ctxt: Default::default(),
+            ongoing_codegen: Default::default(),
+        }
+    }
+
+    fn session(&self) -> &Lrc<Session> {
+        &self.compiler.sess
+    }
+    fn codegen_backend(&self) -> &Lrc<Box<dyn CodegenBackend>> {
+        &self.compiler.codegen_backend()
+    }
+
+    fn dep_graph_future(&self) -> Result<&Query<Option<DepGraphFuture>>> {
+        self.dep_graph_future.compute(|| {
+            let sess = self.session();
+            Ok(sess.opts.build_dep_graph().then(|| rustc_incremental::load_dep_graph(sess)))
+        })
+    }
+
+    pub fn parse(&self) -> Result<&Query<ast::Crate>> {
+        self.parse.compute(|| {
+            passes::parse(self.session(), &self.compiler.input).map_err(|mut parse_error| {
+                parse_error.emit();
+                ErrorReported
+            })
+        })
+    }
+
+    pub fn register_plugins(&self) -> Result<&Query<(ast::Crate, Lrc<LintStore>)>> {
+        self.register_plugins.compute(|| {
+            let crate_name = self.crate_name()?.peek().clone();
+            let krate = self.parse()?.take();
+
+            let empty: &(dyn Fn(&Session, &mut LintStore) + Sync + Send) = &|_, _| {};
+            let result = passes::register_plugins(
+                self.session(),
+                &*self.codegen_backend().metadata_loader(),
+                self.compiler.register_lints.as_deref().unwrap_or_else(|| empty),
+                krate,
+                &crate_name,
+            )?;
+
+            // Compute the dependency graph (in the background). We want to do
+            // this as early as possible, to give the DepGraph maximum time to
+            // load before dep_graph() is called, but it also can't happen
+            // until after rustc_incremental::prepare_session_directory() is
+            // called, which happens within passes::register_plugins().
+            self.dep_graph_future().ok();
+
+            Ok(result)
+        })
+    }
+
+    pub fn crate_name(&self) -> Result<&Query<String>> {
+        self.crate_name.compute(|| {
+            Ok({
+                let parse_result = self.parse()?;
+                let krate = parse_result.peek();
+                // parse `#[crate_name]` even if `--crate-name` was passed, to make sure it matches.
+                find_crate_name(self.session(), &krate.attrs, &self.compiler.input)
+            })
+        })
+    }
+
+    pub fn expansion(
+        &self,
+    ) -> Result<&Query<(Rc<ast::Crate>, Rc<RefCell<BoxedResolver>>, Lrc<LintStore>)>> {
+        tracing::trace!("expansion");
+        self.expansion.compute(|| {
+            let crate_name = self.crate_name()?.peek().clone();
+            let (krate, lint_store) = self.register_plugins()?.take();
+            let _timer = self.session().timer("configure_and_expand");
+            let sess = self.session();
+            let mut resolver = passes::create_resolver(
+                sess.clone(),
+                self.codegen_backend().metadata_loader(),
+                &krate,
+                &crate_name,
+            );
+            let krate = resolver.access(|resolver| {
+                passes::configure_and_expand(&sess, &lint_store, krate, &crate_name, resolver)
+            })?;
+            Ok((Rc::new(krate), Rc::new(RefCell::new(resolver)), lint_store))
+        })
+    }
+
+    fn dep_graph(&self) -> Result<&Query<DepGraph>> {
+        self.dep_graph.compute(|| {
+            let sess = self.session();
+            let future_opt = self.dep_graph_future()?.take();
+            let dep_graph = future_opt
+                .and_then(|future| {
+                    let (prev_graph, prev_work_products) =
+                        sess.time("blocked_on_dep_graph_loading", || future.open().open(sess));
+
+                    rustc_incremental::build_dep_graph(sess, prev_graph, prev_work_products)
+                })
+                .unwrap_or_else(DepGraph::new_disabled);
+            Ok(dep_graph)
+        })
+    }
+
+    pub fn prepare_outputs(&self) -> Result<&Query<OutputFilenames>> {
+        self.prepare_outputs.compute(|| {
+            let (krate, boxed_resolver, _) = &*self.expansion()?.peek();
+            let crate_name = self.crate_name()?.peek();
+            passes::prepare_outputs(
+                self.session(),
+                self.compiler,
+                krate,
+                &*boxed_resolver,
+                &crate_name,
+            )
+        })
+    }
+
+    pub fn global_ctxt(&'tcx self) -> Result<&Query<QueryContext<'tcx>>> {
+        self.global_ctxt.compute(|| {
+            let crate_name = self.crate_name()?.peek().clone();
+            let outputs = self.prepare_outputs()?.peek().clone();
+            let dep_graph = self.dep_graph()?.peek().clone();
+            let (krate, resolver, lint_store) = self.expansion()?.take();
+            Ok(passes::create_global_ctxt(
+                self.compiler,
+                lint_store,
+                krate,
+                dep_graph,
+                resolver,
+                outputs,
+                &crate_name,
+                &self.queries,
+                &self.gcx,
+                &self.arena,
+                &self.hir_arena,
+            ))
+        })
+    }
+
+    pub fn ongoing_codegen(&'tcx self) -> Result<&Query<Box<dyn Any>>> {
+        self.ongoing_codegen.compute(|| {
+            let outputs = self.prepare_outputs()?;
+            self.global_ctxt()?.peek_mut().enter(|tcx| {
+                tcx.analysis(()).ok();
+
+                // Don't do code generation if there were any errors
+                self.session().compile_status()?;
+
+                // Hook for UI tests.
+                Self::check_for_rustc_errors_attr(tcx);
+
+                Ok(passes::start_codegen(&***self.codegen_backend(), tcx, &*outputs.peek()))
+            })
+        })
+    }
+
+    /// Check for the `#[rustc_error]` annotation, which forces an error in codegen. This is used
+    /// to write UI tests that actually test that compilation succeeds without reporting
+    /// an error.
+    fn check_for_rustc_errors_attr(tcx: TyCtxt<'_>) {
+        let def_id = match tcx.entry_fn(()) {
+            Some((def_id, _)) => def_id,
+            _ => return,
+        };
+
+        let attrs = &*tcx.get_attrs(def_id);
+        let attrs = attrs.iter().filter(|attr| tcx.sess.check_name(attr, sym::rustc_error));
+        for attr in attrs {
+            match attr.meta_item_list() {
+                // Check if there is a `#[rustc_error(delay_span_bug_from_inside_query)]`.
+                Some(list)
+                    if list.iter().any(|list_item| {
+                        matches!(
+                            list_item.ident().map(|i| i.name),
+                            Some(sym::delay_span_bug_from_inside_query)
+                        )
+                    }) =>
+                {
+                    tcx.ensure().trigger_delay_span_bug(def_id);
+                }
+
+                // Bare `#[rustc_error]`.
+                None => {
+                    tcx.sess.span_fatal(
+                        tcx.def_span(def_id),
+                        "fatal error triggered by #[rustc_error]",
+                    );
+                }
+
+                // Some other attribute.
+                Some(_) => {
+                    tcx.sess.span_warn(
+                        tcx.def_span(def_id),
+                        "unexpected annotation used with `#[rustc_error(...)]!",
+                    );
+                }
+            }
+        }
+    }
+
+    pub fn linker(&'tcx self) -> Result<Linker> {
+        let sess = self.session().clone();
+        let codegen_backend = self.codegen_backend().clone();
+
+        let dep_graph = self.dep_graph()?.peek().clone();
+        let prepare_outputs = self.prepare_outputs()?.take();
+        let crate_hash = self.global_ctxt()?.peek_mut().enter(|tcx| tcx.crate_hash(LOCAL_CRATE));
+        let ongoing_codegen = self.ongoing_codegen()?.take();
+
+        Ok(Linker {
+            sess,
+            codegen_backend,
+
+            dep_graph,
+            prepare_outputs,
+            crate_hash,
+            ongoing_codegen,
+        })
+    }
+}
+
+pub struct Linker {
+    // compilation inputs
+    sess: Lrc<Session>,
+    codegen_backend: Lrc<Box<dyn CodegenBackend>>,
+
+    // compilation outputs
+    dep_graph: DepGraph,
+    prepare_outputs: OutputFilenames,
+    crate_hash: Svh,
+    ongoing_codegen: Box<dyn Any>,
+}
+
+impl Linker {
+    pub fn link(self) -> Result<()> {
+        let (codegen_results, work_products) =
+            self.codegen_backend.join_codegen(self.ongoing_codegen, &self.sess)?;
+
+        self.sess.compile_status()?;
+
+        let sess = &self.sess;
+        let dep_graph = self.dep_graph;
+        sess.time("serialize_work_products", || {
+            rustc_incremental::save_work_product_index(&sess, &dep_graph, work_products)
+        });
+
+        let prof = self.sess.prof.clone();
+        prof.generic_activity("drop_dep_graph").run(move || drop(dep_graph));
+
+        // Now that we won't touch anything in the incremental compilation directory
+        // any more, we can finalize it (which involves renaming it)
+        rustc_incremental::finalize_session_directory(&self.sess, self.crate_hash);
+
+        if !self
+            .sess
+            .opts
+            .output_types
+            .keys()
+            .any(|&i| i == OutputType::Exe || i == OutputType::Metadata)
+        {
+            return Ok(());
+        }
+
+        if sess.opts.debugging_opts.no_link {
+            // FIXME: use a binary format to encode the `.rlink` file
+            let rlink_data = json::encode(&codegen_results).map_err(|err| {
+                sess.fatal(&format!("failed to encode rlink: {}", err));
+            })?;
+            let rlink_file = self.prepare_outputs.with_extension(config::RLINK_EXT);
+            std::fs::write(&rlink_file, rlink_data).map_err(|err| {
+                sess.fatal(&format!("failed to write file {}: {}", rlink_file.display(), err));
+            })?;
+            return Ok(());
+        }
+
+        let _timer = sess.prof.verbose_generic_activity("link_crate");
+        self.codegen_backend.link(&self.sess, codegen_results, &self.prepare_outputs)
+    }
+}
+
+impl Compiler {
+    pub fn enter<F, T>(&self, f: F) -> T
+    where
+        F: for<'tcx> FnOnce(&'tcx Queries<'tcx>) -> T,
+    {
+        let mut _timer = None;
+        let queries = Queries::new(&self);
+        let ret = f(&queries);
+
+        // NOTE: intentionally does not compute the global context if it hasn't been built yet,
+        // since that likely means there was a parse error.
+        if let Some(Ok(gcx)) = &mut *queries.global_ctxt.result.borrow_mut() {
+            // We assume that no queries are run past here. If there are new queries
+            // after this point, they'll show up as "<unknown>" in self-profiling data.
+            {
+                let _prof_timer =
+                    queries.session().prof.generic_activity("self_profile_alloc_query_strings");
+                gcx.enter(rustc_query_impl::alloc_self_profile_query_strings);
+            }
+
+            if self.session().opts.debugging_opts.query_stats {
+                gcx.enter(rustc_query_impl::print_stats);
+            }
+
+            self.session()
+                .time("serialize_dep_graph", || gcx.enter(rustc_incremental::save_dep_graph));
+        }
+
+        _timer = Some(self.session().timer("free_global_ctxt"));
+
+        ret
+    }
+}
diff --git a/compiler/rustc_interface/src/tests.rs b/compiler/rustc_interface/src/tests.rs
new file mode 100644
index 00000000000..b8961434006
--- /dev/null
+++ b/compiler/rustc_interface/src/tests.rs
@@ -0,0 +1,797 @@
+use crate::interface::parse_cfgspecs;
+
+use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::{emitter::HumanReadableErrorType, registry, ColorConfig};
+use rustc_session::config::InstrumentCoverage;
+use rustc_session::config::Strip;
+use rustc_session::config::{build_configuration, build_session_options, to_crate_config};
+use rustc_session::config::{rustc_optgroups, ErrorOutputType, ExternLocation, Options, Passes};
+use rustc_session::config::{CFGuard, ExternEntry, LinkerPluginLto, LtoCli, SwitchWithOptPath};
+use rustc_session::config::{
+    Externs, OutputType, OutputTypes, SymbolManglingVersion, WasiExecModel,
+};
+use rustc_session::lint::Level;
+use rustc_session::search_paths::SearchPath;
+use rustc_session::utils::{CanonicalizedPath, NativeLib, NativeLibKind};
+use rustc_session::{build_session, getopts, DiagnosticOutput, Session};
+use rustc_span::edition::{Edition, DEFAULT_EDITION};
+use rustc_span::symbol::sym;
+use rustc_span::SourceFileHashAlgorithm;
+use rustc_target::spec::{CodeModel, LinkerFlavor, MergeFunctions, PanicStrategy};
+use rustc_target::spec::{RelocModel, RelroLevel, SanitizerSet, SplitDebuginfo, TlsModel};
+
+use std::collections::{BTreeMap, BTreeSet};
+use std::iter::FromIterator;
+use std::num::NonZeroUsize;
+use std::path::{Path, PathBuf};
+
+type CfgSpecs = FxHashSet<(String, Option<String>)>;
+
+fn build_session_options_and_crate_config(matches: getopts::Matches) -> (Options, CfgSpecs) {
+    let sessopts = build_session_options(&matches);
+    let cfg = parse_cfgspecs(matches.opt_strs("cfg"));
+    (sessopts, cfg)
+}
+
+fn mk_session(matches: getopts::Matches) -> (Session, CfgSpecs) {
+    let registry = registry::Registry::new(&[]);
+    let (sessopts, cfg) = build_session_options_and_crate_config(matches);
+    let sess = build_session(
+        sessopts,
+        None,
+        registry,
+        DiagnosticOutput::Default,
+        Default::default(),
+        None,
+        None,
+    );
+    (sess, cfg)
+}
+
+fn new_public_extern_entry<S, I>(locations: I) -> ExternEntry
+where
+    S: Into<String>,
+    I: IntoIterator<Item = S>,
+{
+    let locations: BTreeSet<CanonicalizedPath> =
+        locations.into_iter().map(|s| CanonicalizedPath::new(Path::new(&s.into()))).collect();
+
+    ExternEntry {
+        location: ExternLocation::ExactPaths(locations),
+        is_private_dep: false,
+        add_prelude: true,
+    }
+}
+
+fn optgroups() -> getopts::Options {
+    let mut opts = getopts::Options::new();
+    for group in rustc_optgroups() {
+        (group.apply)(&mut opts);
+    }
+    return opts;
+}
+
+fn mk_map<K: Ord, V>(entries: Vec<(K, V)>) -> BTreeMap<K, V> {
+    BTreeMap::from_iter(entries.into_iter())
+}
+
+fn assert_same_clone(x: &Options) {
+    assert_eq!(x.dep_tracking_hash(true), x.clone().dep_tracking_hash(true));
+    assert_eq!(x.dep_tracking_hash(false), x.clone().dep_tracking_hash(false));
+}
+
+fn assert_same_hash(x: &Options, y: &Options) {
+    assert_eq!(x.dep_tracking_hash(true), y.dep_tracking_hash(true));
+    assert_eq!(x.dep_tracking_hash(false), y.dep_tracking_hash(false));
+    // Check clone
+    assert_same_clone(x);
+    assert_same_clone(y);
+}
+
+fn assert_different_hash(x: &Options, y: &Options) {
+    assert_ne!(x.dep_tracking_hash(true), y.dep_tracking_hash(true));
+    assert_ne!(x.dep_tracking_hash(false), y.dep_tracking_hash(false));
+    // Check clone
+    assert_same_clone(x);
+    assert_same_clone(y);
+}
+
+fn assert_non_crate_hash_different(x: &Options, y: &Options) {
+    assert_eq!(x.dep_tracking_hash(true), y.dep_tracking_hash(true));
+    assert_ne!(x.dep_tracking_hash(false), y.dep_tracking_hash(false));
+    // Check clone
+    assert_same_clone(x);
+    assert_same_clone(y);
+}
+
+// When the user supplies --test we should implicitly supply --cfg test
+#[test]
+fn test_switch_implies_cfg_test() {
+    rustc_span::create_default_session_globals_then(|| {
+        let matches = optgroups().parse(&["--test".to_string()]).unwrap();
+        let (sess, cfg) = mk_session(matches);
+        let cfg = build_configuration(&sess, to_crate_config(cfg));
+        assert!(cfg.contains(&(sym::test, None)));
+    });
+}
+
+// When the user supplies --test and --cfg test, don't implicitly add another --cfg test
+#[test]
+fn test_switch_implies_cfg_test_unless_cfg_test() {
+    rustc_span::create_default_session_globals_then(|| {
+        let matches = optgroups().parse(&["--test".to_string(), "--cfg=test".to_string()]).unwrap();
+        let (sess, cfg) = mk_session(matches);
+        let cfg = build_configuration(&sess, to_crate_config(cfg));
+        let mut test_items = cfg.iter().filter(|&&(name, _)| name == sym::test);
+        assert!(test_items.next().is_some());
+        assert!(test_items.next().is_none());
+    });
+}
+
+#[test]
+fn test_can_print_warnings() {
+    rustc_span::create_default_session_globals_then(|| {
+        let matches = optgroups().parse(&["-Awarnings".to_string()]).unwrap();
+        let (sess, _) = mk_session(matches);
+        assert!(!sess.diagnostic().can_emit_warnings());
+    });
+
+    rustc_span::create_default_session_globals_then(|| {
+        let matches =
+            optgroups().parse(&["-Awarnings".to_string(), "-Dwarnings".to_string()]).unwrap();
+        let (sess, _) = mk_session(matches);
+        assert!(sess.diagnostic().can_emit_warnings());
+    });
+
+    rustc_span::create_default_session_globals_then(|| {
+        let matches = optgroups().parse(&["-Adead_code".to_string()]).unwrap();
+        let (sess, _) = mk_session(matches);
+        assert!(sess.diagnostic().can_emit_warnings());
+    });
+}
+
+#[test]
+fn test_output_types_tracking_hash_different_paths() {
+    let mut v1 = Options::default();
+    let mut v2 = Options::default();
+    let mut v3 = Options::default();
+
+    v1.output_types = OutputTypes::new(&[(OutputType::Exe, Some(PathBuf::from("./some/thing")))]);
+    v2.output_types = OutputTypes::new(&[(OutputType::Exe, Some(PathBuf::from("/some/thing")))]);
+    v3.output_types = OutputTypes::new(&[(OutputType::Exe, None)]);
+
+    assert_non_crate_hash_different(&v1, &v2);
+    assert_non_crate_hash_different(&v1, &v3);
+    assert_non_crate_hash_different(&v2, &v3);
+}
+
+#[test]
+fn test_output_types_tracking_hash_different_construction_order() {
+    let mut v1 = Options::default();
+    let mut v2 = Options::default();
+
+    v1.output_types = OutputTypes::new(&[
+        (OutputType::Exe, Some(PathBuf::from("./some/thing"))),
+        (OutputType::Bitcode, Some(PathBuf::from("./some/thing.bc"))),
+    ]);
+
+    v2.output_types = OutputTypes::new(&[
+        (OutputType::Bitcode, Some(PathBuf::from("./some/thing.bc"))),
+        (OutputType::Exe, Some(PathBuf::from("./some/thing"))),
+    ]);
+
+    assert_same_hash(&v1, &v2);
+}
+
+#[test]
+fn test_externs_tracking_hash_different_construction_order() {
+    let mut v1 = Options::default();
+    let mut v2 = Options::default();
+    let mut v3 = Options::default();
+
+    v1.externs = Externs::new(mk_map(vec![
+        (String::from("a"), new_public_extern_entry(vec!["b", "c"])),
+        (String::from("d"), new_public_extern_entry(vec!["e", "f"])),
+    ]));
+
+    v2.externs = Externs::new(mk_map(vec![
+        (String::from("d"), new_public_extern_entry(vec!["e", "f"])),
+        (String::from("a"), new_public_extern_entry(vec!["b", "c"])),
+    ]));
+
+    v3.externs = Externs::new(mk_map(vec![
+        (String::from("a"), new_public_extern_entry(vec!["b", "c"])),
+        (String::from("d"), new_public_extern_entry(vec!["f", "e"])),
+    ]));
+
+    assert_same_hash(&v1, &v2);
+    assert_same_hash(&v1, &v3);
+    assert_same_hash(&v2, &v3);
+}
+
+#[test]
+fn test_lints_tracking_hash_different_values() {
+    let mut v1 = Options::default();
+    let mut v2 = Options::default();
+    let mut v3 = Options::default();
+
+    v1.lint_opts = vec![
+        (String::from("a"), Level::Allow),
+        (String::from("b"), Level::Warn),
+        (String::from("c"), Level::Deny),
+        (String::from("d"), Level::Forbid),
+    ];
+
+    v2.lint_opts = vec![
+        (String::from("a"), Level::Allow),
+        (String::from("b"), Level::Warn),
+        (String::from("X"), Level::Deny),
+        (String::from("d"), Level::Forbid),
+    ];
+
+    v3.lint_opts = vec![
+        (String::from("a"), Level::Allow),
+        (String::from("b"), Level::Warn),
+        (String::from("c"), Level::Forbid),
+        (String::from("d"), Level::Deny),
+    ];
+
+    assert_non_crate_hash_different(&v1, &v2);
+    assert_non_crate_hash_different(&v1, &v3);
+    assert_non_crate_hash_different(&v2, &v3);
+}
+
+#[test]
+fn test_lints_tracking_hash_different_construction_order() {
+    let mut v1 = Options::default();
+    let mut v2 = Options::default();
+
+    v1.lint_opts = vec![
+        (String::from("a"), Level::Allow),
+        (String::from("b"), Level::Warn),
+        (String::from("c"), Level::Deny),
+        (String::from("d"), Level::Forbid),
+    ];
+
+    v2.lint_opts = vec![
+        (String::from("a"), Level::Allow),
+        (String::from("c"), Level::Deny),
+        (String::from("b"), Level::Warn),
+        (String::from("d"), Level::Forbid),
+    ];
+
+    // The hash should be order-dependent
+    assert_non_crate_hash_different(&v1, &v2);
+}
+
+#[test]
+fn test_lint_cap_hash_different() {
+    let mut v1 = Options::default();
+    let mut v2 = Options::default();
+    let v3 = Options::default();
+
+    v1.lint_cap = Some(Level::Forbid);
+    v2.lint_cap = Some(Level::Allow);
+
+    assert_non_crate_hash_different(&v1, &v2);
+    assert_non_crate_hash_different(&v1, &v3);
+    assert_non_crate_hash_different(&v2, &v3);
+}
+
+#[test]
+fn test_search_paths_tracking_hash_different_order() {
+    let mut v1 = Options::default();
+    let mut v2 = Options::default();
+    let mut v3 = Options::default();
+    let mut v4 = Options::default();
+
+    const JSON: ErrorOutputType = ErrorOutputType::Json {
+        pretty: false,
+        json_rendered: HumanReadableErrorType::Default(ColorConfig::Never),
+    };
+
+    // Reference
+    v1.search_paths.push(SearchPath::from_cli_opt("native=abc", JSON));
+    v1.search_paths.push(SearchPath::from_cli_opt("crate=def", JSON));
+    v1.search_paths.push(SearchPath::from_cli_opt("dependency=ghi", JSON));
+    v1.search_paths.push(SearchPath::from_cli_opt("framework=jkl", JSON));
+    v1.search_paths.push(SearchPath::from_cli_opt("all=mno", JSON));
+
+    v2.search_paths.push(SearchPath::from_cli_opt("native=abc", JSON));
+    v2.search_paths.push(SearchPath::from_cli_opt("dependency=ghi", JSON));
+    v2.search_paths.push(SearchPath::from_cli_opt("crate=def", JSON));
+    v2.search_paths.push(SearchPath::from_cli_opt("framework=jkl", JSON));
+    v2.search_paths.push(SearchPath::from_cli_opt("all=mno", JSON));
+
+    v3.search_paths.push(SearchPath::from_cli_opt("crate=def", JSON));
+    v3.search_paths.push(SearchPath::from_cli_opt("framework=jkl", JSON));
+    v3.search_paths.push(SearchPath::from_cli_opt("native=abc", JSON));
+    v3.search_paths.push(SearchPath::from_cli_opt("dependency=ghi", JSON));
+    v3.search_paths.push(SearchPath::from_cli_opt("all=mno", JSON));
+
+    v4.search_paths.push(SearchPath::from_cli_opt("all=mno", JSON));
+    v4.search_paths.push(SearchPath::from_cli_opt("native=abc", JSON));
+    v4.search_paths.push(SearchPath::from_cli_opt("crate=def", JSON));
+    v4.search_paths.push(SearchPath::from_cli_opt("dependency=ghi", JSON));
+    v4.search_paths.push(SearchPath::from_cli_opt("framework=jkl", JSON));
+
+    assert_same_hash(&v1, &v2);
+    assert_same_hash(&v1, &v3);
+    assert_same_hash(&v1, &v4);
+}
+
+#[test]
+fn test_native_libs_tracking_hash_different_values() {
+    let mut v1 = Options::default();
+    let mut v2 = Options::default();
+    let mut v3 = Options::default();
+    let mut v4 = Options::default();
+    let mut v5 = Options::default();
+
+    // Reference
+    v1.libs = vec![
+        NativeLib {
+            name: String::from("a"),
+            new_name: None,
+            kind: NativeLibKind::Static { bundle: None, whole_archive: None },
+            verbatim: None,
+        },
+        NativeLib {
+            name: String::from("b"),
+            new_name: None,
+            kind: NativeLibKind::Framework { as_needed: None },
+            verbatim: None,
+        },
+        NativeLib {
+            name: String::from("c"),
+            new_name: None,
+            kind: NativeLibKind::Unspecified,
+            verbatim: None,
+        },
+    ];
+
+    // Change label
+    v2.libs = vec![
+        NativeLib {
+            name: String::from("a"),
+            new_name: None,
+            kind: NativeLibKind::Static { bundle: None, whole_archive: None },
+            verbatim: None,
+        },
+        NativeLib {
+            name: String::from("X"),
+            new_name: None,
+            kind: NativeLibKind::Framework { as_needed: None },
+            verbatim: None,
+        },
+        NativeLib {
+            name: String::from("c"),
+            new_name: None,
+            kind: NativeLibKind::Unspecified,
+            verbatim: None,
+        },
+    ];
+
+    // Change kind
+    v3.libs = vec![
+        NativeLib {
+            name: String::from("a"),
+            new_name: None,
+            kind: NativeLibKind::Static { bundle: None, whole_archive: None },
+            verbatim: None,
+        },
+        NativeLib {
+            name: String::from("b"),
+            new_name: None,
+            kind: NativeLibKind::Static { bundle: None, whole_archive: None },
+            verbatim: None,
+        },
+        NativeLib {
+            name: String::from("c"),
+            new_name: None,
+            kind: NativeLibKind::Unspecified,
+            verbatim: None,
+        },
+    ];
+
+    // Change new-name
+    v4.libs = vec![
+        NativeLib {
+            name: String::from("a"),
+            new_name: None,
+            kind: NativeLibKind::Static { bundle: None, whole_archive: None },
+            verbatim: None,
+        },
+        NativeLib {
+            name: String::from("b"),
+            new_name: Some(String::from("X")),
+            kind: NativeLibKind::Framework { as_needed: None },
+            verbatim: None,
+        },
+        NativeLib {
+            name: String::from("c"),
+            new_name: None,
+            kind: NativeLibKind::Unspecified,
+            verbatim: None,
+        },
+    ];
+
+    // Change verbatim
+    v5.libs = vec![
+        NativeLib {
+            name: String::from("a"),
+            new_name: None,
+            kind: NativeLibKind::Static { bundle: None, whole_archive: None },
+            verbatim: None,
+        },
+        NativeLib {
+            name: String::from("b"),
+            new_name: None,
+            kind: NativeLibKind::Framework { as_needed: None },
+            verbatim: Some(true),
+        },
+        NativeLib {
+            name: String::from("c"),
+            new_name: None,
+            kind: NativeLibKind::Unspecified,
+            verbatim: None,
+        },
+    ];
+
+    assert_different_hash(&v1, &v2);
+    assert_different_hash(&v1, &v3);
+    assert_different_hash(&v1, &v4);
+    assert_different_hash(&v1, &v5);
+}
+
+#[test]
+fn test_native_libs_tracking_hash_different_order() {
+    let mut v1 = Options::default();
+    let mut v2 = Options::default();
+    let mut v3 = Options::default();
+
+    // Reference
+    v1.libs = vec![
+        NativeLib {
+            name: String::from("a"),
+            new_name: None,
+            kind: NativeLibKind::Static { bundle: None, whole_archive: None },
+            verbatim: None,
+        },
+        NativeLib {
+            name: String::from("b"),
+            new_name: None,
+            kind: NativeLibKind::Framework { as_needed: None },
+            verbatim: None,
+        },
+        NativeLib {
+            name: String::from("c"),
+            new_name: None,
+            kind: NativeLibKind::Unspecified,
+            verbatim: None,
+        },
+    ];
+
+    v2.libs = vec![
+        NativeLib {
+            name: String::from("b"),
+            new_name: None,
+            kind: NativeLibKind::Framework { as_needed: None },
+            verbatim: None,
+        },
+        NativeLib {
+            name: String::from("a"),
+            new_name: None,
+            kind: NativeLibKind::Static { bundle: None, whole_archive: None },
+            verbatim: None,
+        },
+        NativeLib {
+            name: String::from("c"),
+            new_name: None,
+            kind: NativeLibKind::Unspecified,
+            verbatim: None,
+        },
+    ];
+
+    v3.libs = vec![
+        NativeLib {
+            name: String::from("c"),
+            new_name: None,
+            kind: NativeLibKind::Unspecified,
+            verbatim: None,
+        },
+        NativeLib {
+            name: String::from("a"),
+            new_name: None,
+            kind: NativeLibKind::Static { bundle: None, whole_archive: None },
+            verbatim: None,
+        },
+        NativeLib {
+            name: String::from("b"),
+            new_name: None,
+            kind: NativeLibKind::Framework { as_needed: None },
+            verbatim: None,
+        },
+    ];
+
+    // The hash should be order-dependent
+    assert_different_hash(&v1, &v2);
+    assert_different_hash(&v1, &v3);
+    assert_different_hash(&v2, &v3);
+}
+
+#[test]
+fn test_codegen_options_tracking_hash() {
+    let reference = Options::default();
+    let mut opts = Options::default();
+
+    macro_rules! untracked {
+        ($name: ident, $non_default_value: expr) => {
+            assert_ne!(opts.cg.$name, $non_default_value);
+            opts.cg.$name = $non_default_value;
+            assert_same_hash(&reference, &opts);
+        };
+    }
+
+    // Make sure that changing an [UNTRACKED] option leaves the hash unchanged.
+    // This list is in alphabetical order.
+    untracked!(ar, String::from("abc"));
+    untracked!(codegen_units, Some(42));
+    untracked!(default_linker_libraries, true);
+    untracked!(extra_filename, String::from("extra-filename"));
+    untracked!(incremental, Some(String::from("abc")));
+    // `link_arg` is omitted because it just forwards to `link_args`.
+    untracked!(link_args, vec![String::from("abc"), String::from("def")]);
+    untracked!(link_self_contained, Some(true));
+    untracked!(linker, Some(PathBuf::from("linker")));
+    untracked!(linker_flavor, Some(LinkerFlavor::Gcc));
+    untracked!(no_stack_check, true);
+    untracked!(remark, Passes::Some(vec![String::from("pass1"), String::from("pass2")]));
+    untracked!(rpath, true);
+    untracked!(save_temps, true);
+
+    macro_rules! tracked {
+        ($name: ident, $non_default_value: expr) => {
+            opts = reference.clone();
+            assert_ne!(opts.cg.$name, $non_default_value);
+            opts.cg.$name = $non_default_value;
+            assert_different_hash(&reference, &opts);
+        };
+    }
+
+    // Make sure that changing a [TRACKED] option changes the hash.
+    // This list is in alphabetical order.
+    tracked!(code_model, Some(CodeModel::Large));
+    tracked!(control_flow_guard, CFGuard::Checks);
+    tracked!(debug_assertions, Some(true));
+    tracked!(debuginfo, 0xdeadbeef);
+    tracked!(embed_bitcode, false);
+    tracked!(force_frame_pointers, Some(false));
+    tracked!(force_unwind_tables, Some(true));
+    tracked!(inline_threshold, Some(0xf007ba11));
+    tracked!(linker_plugin_lto, LinkerPluginLto::LinkerPluginAuto);
+    tracked!(link_dead_code, Some(true));
+    tracked!(llvm_args, vec![String::from("1"), String::from("2")]);
+    tracked!(lto, LtoCli::Fat);
+    tracked!(metadata, vec![String::from("A"), String::from("B")]);
+    tracked!(no_prepopulate_passes, true);
+    tracked!(no_redzone, Some(true));
+    tracked!(no_vectorize_loops, true);
+    tracked!(no_vectorize_slp, true);
+    tracked!(opt_level, "3".to_string());
+    tracked!(overflow_checks, Some(true));
+    tracked!(panic, Some(PanicStrategy::Abort));
+    tracked!(passes, vec![String::from("1"), String::from("2")]);
+    tracked!(prefer_dynamic, true);
+    tracked!(profile_generate, SwitchWithOptPath::Enabled(None));
+    tracked!(profile_use, Some(PathBuf::from("abc")));
+    tracked!(relocation_model, Some(RelocModel::Pic));
+    tracked!(soft_float, true);
+    tracked!(split_debuginfo, Some(SplitDebuginfo::Packed));
+    tracked!(target_cpu, Some(String::from("abc")));
+    tracked!(target_feature, String::from("all the features, all of them"));
+}
+
+#[test]
+fn test_top_level_options_tracked_no_crate() {
+    let reference = Options::default();
+    let mut opts;
+
+    macro_rules! tracked {
+        ($name: ident, $non_default_value: expr) => {
+            opts = reference.clone();
+            assert_ne!(opts.$name, $non_default_value);
+            opts.$name = $non_default_value;
+            // The crate hash should be the same
+            assert_eq!(reference.dep_tracking_hash(true), opts.dep_tracking_hash(true));
+            // The incremental hash should be different
+            assert_ne!(reference.dep_tracking_hash(false), opts.dep_tracking_hash(false));
+        };
+    }
+
+    // Make sure that changing a [TRACKED_NO_CRATE_HASH] option leaves the crate hash unchanged but changes the incremental hash.
+    // This list is in alphabetical order.
+    tracked!(remap_path_prefix, vec![("/home/bors/rust".into(), "src".into())]);
+    tracked!(
+        real_rust_source_base_dir,
+        Some("/home/bors/rust/.rustup/toolchains/nightly/lib/rustlib/src/rust".into())
+    );
+}
+
+#[test]
+fn test_debugging_options_tracking_hash() {
+    let reference = Options::default();
+    let mut opts = Options::default();
+
+    macro_rules! untracked {
+        ($name: ident, $non_default_value: expr) => {
+            assert_ne!(opts.debugging_opts.$name, $non_default_value);
+            opts.debugging_opts.$name = $non_default_value;
+            assert_same_hash(&reference, &opts);
+        };
+    }
+
+    // Make sure that changing an [UNTRACKED] option leaves the hash unchanged.
+    // This list is in alphabetical order.
+    untracked!(ast_json, true);
+    untracked!(ast_json_noexpand, true);
+    untracked!(borrowck, String::from("other"));
+    untracked!(deduplicate_diagnostics, false);
+    untracked!(dep_tasks, true);
+    untracked!(dont_buffer_diagnostics, true);
+    untracked!(dump_dep_graph, true);
+    untracked!(dump_mir, Some(String::from("abc")));
+    untracked!(dump_mir_dataflow, true);
+    untracked!(dump_mir_dir, String::from("abc"));
+    untracked!(dump_mir_exclude_pass_number, true);
+    untracked!(dump_mir_graphviz, true);
+    untracked!(emit_future_incompat_report, true);
+    untracked!(emit_stack_sizes, true);
+    untracked!(future_incompat_test, true);
+    untracked!(hir_stats, true);
+    untracked!(identify_regions, true);
+    untracked!(incremental_ignore_spans, true);
+    untracked!(incremental_info, true);
+    untracked!(incremental_verify_ich, true);
+    untracked!(input_stats, true);
+    untracked!(keep_hygiene_data, true);
+    untracked!(link_native_libraries, false);
+    untracked!(llvm_time_trace, true);
+    untracked!(ls, true);
+    untracked!(macro_backtrace, true);
+    untracked!(meta_stats, true);
+    untracked!(nll_facts, true);
+    untracked!(no_analysis, true);
+    untracked!(no_interleave_lints, true);
+    untracked!(no_leak_check, true);
+    untracked!(no_parallel_llvm, true);
+    untracked!(parse_only, true);
+    untracked!(perf_stats, true);
+    // `pre_link_arg` is omitted because it just forwards to `pre_link_args`.
+    untracked!(pre_link_args, vec![String::from("abc"), String::from("def")]);
+    untracked!(profile_closures, true);
+    untracked!(print_link_args, true);
+    untracked!(print_llvm_passes, true);
+    untracked!(print_mono_items, Some(String::from("abc")));
+    untracked!(print_type_sizes, true);
+    untracked!(proc_macro_backtrace, true);
+    untracked!(query_dep_graph, true);
+    untracked!(query_stats, true);
+    untracked!(save_analysis, true);
+    untracked!(self_profile, SwitchWithOptPath::Enabled(None));
+    untracked!(self_profile_events, Some(vec![String::new()]));
+    untracked!(span_debug, true);
+    untracked!(span_free_formats, true);
+    untracked!(strip, Strip::Debuginfo);
+    untracked!(terminal_width, Some(80));
+    untracked!(threads, 99);
+    untracked!(time, true);
+    untracked!(time_llvm_passes, true);
+    untracked!(time_passes, true);
+    untracked!(trace_macros, true);
+    untracked!(trim_diagnostic_paths, false);
+    untracked!(ui_testing, true);
+    untracked!(unpretty, Some("expanded".to_string()));
+    untracked!(unstable_options, true);
+    untracked!(validate_mir, true);
+    untracked!(verbose, true);
+
+    macro_rules! tracked {
+        ($name: ident, $non_default_value: expr) => {
+            opts = reference.clone();
+            assert_ne!(opts.debugging_opts.$name, $non_default_value);
+            opts.debugging_opts.$name = $non_default_value;
+            assert_different_hash(&reference, &opts);
+        };
+    }
+
+    // Make sure that changing a [TRACKED] option changes the hash.
+    // This list is in alphabetical order.
+    tracked!(allow_features, Some(vec![String::from("lang_items")]));
+    tracked!(always_encode_mir, true);
+    tracked!(assume_incomplete_release, true);
+    tracked!(asm_comments, true);
+    tracked!(binary_dep_depinfo, true);
+    tracked!(chalk, true);
+    tracked!(codegen_backend, Some("abc".to_string()));
+    tracked!(crate_attr, vec!["abc".to_string()]);
+    tracked!(debug_macros, true);
+    tracked!(dep_info_omit_d_target, true);
+    tracked!(dual_proc_macros, true);
+    tracked!(fewer_names, Some(true));
+    tracked!(force_overflow_checks, Some(true));
+    tracked!(force_unstable_if_unmarked, true);
+    tracked!(fuel, Some(("abc".to_string(), 99)));
+    tracked!(function_sections, Some(false));
+    tracked!(human_readable_cgu_names, true);
+    tracked!(inline_in_all_cgus, Some(true));
+    tracked!(inline_mir, Some(true));
+    tracked!(inline_mir_threshold, Some(123));
+    tracked!(inline_mir_hint_threshold, Some(123));
+    tracked!(instrument_coverage, Some(InstrumentCoverage::All));
+    tracked!(instrument_mcount, true);
+    tracked!(link_only, true);
+    tracked!(llvm_plugins, vec![String::from("plugin_name")]);
+    tracked!(merge_functions, Some(MergeFunctions::Disabled));
+    tracked!(mir_emit_retag, true);
+    tracked!(mir_opt_level, Some(4));
+    tracked!(move_size_limit, Some(4096));
+    tracked!(mutable_noalias, Some(true));
+    tracked!(new_llvm_pass_manager, Some(true));
+    tracked!(no_generate_arange_section, true);
+    tracked!(no_link, true);
+    tracked!(no_profiler_runtime, true);
+    tracked!(osx_rpath_install_name, true);
+    tracked!(panic_abort_tests, true);
+    tracked!(plt, Some(true));
+    tracked!(polonius, true);
+    tracked!(precise_enum_drop_elaboration, false);
+    tracked!(print_fuel, Some("abc".to_string()));
+    tracked!(profile, true);
+    tracked!(profile_emit, Some(PathBuf::from("abc")));
+    tracked!(profiler_runtime, "abc".to_string());
+    tracked!(relax_elf_relocations, Some(true));
+    tracked!(relro_level, Some(RelroLevel::Full));
+    tracked!(simulate_remapped_rust_src_base, Some(PathBuf::from("/rustc/abc")));
+    tracked!(report_delayed_bugs, true);
+    tracked!(sanitizer, SanitizerSet::ADDRESS);
+    tracked!(sanitizer_memory_track_origins, 2);
+    tracked!(sanitizer_recover, SanitizerSet::ADDRESS);
+    tracked!(saturating_float_casts, Some(true));
+    tracked!(share_generics, Some(true));
+    tracked!(show_span, Some(String::from("abc")));
+    tracked!(src_hash_algorithm, Some(SourceFileHashAlgorithm::Sha1));
+    tracked!(symbol_mangling_version, Some(SymbolManglingVersion::V0));
+    tracked!(teach, true);
+    tracked!(thinlto, Some(true));
+    tracked!(thir_unsafeck, true);
+    tracked!(tune_cpu, Some(String::from("abc")));
+    tracked!(tls_model, Some(TlsModel::GeneralDynamic));
+    tracked!(trap_unreachable, Some(false));
+    tracked!(treat_err_as_bug, NonZeroUsize::new(1));
+    tracked!(unleash_the_miri_inside_of_you, true);
+    tracked!(use_ctors_section, Some(true));
+    tracked!(verify_llvm_ir, true);
+    tracked!(wasi_exec_model, Some(WasiExecModel::Reactor));
+
+    macro_rules! tracked_no_crate_hash {
+        ($name: ident, $non_default_value: expr) => {
+            opts = reference.clone();
+            assert_ne!(opts.debugging_opts.$name, $non_default_value);
+            opts.debugging_opts.$name = $non_default_value;
+            assert_non_crate_hash_different(&reference, &opts);
+        };
+    }
+    tracked_no_crate_hash!(no_codegen, true);
+}
+
+#[test]
+fn test_edition_parsing() {
+    // test default edition
+    let options = Options::default();
+    assert!(options.edition == DEFAULT_EDITION);
+
+    let matches = optgroups().parse(&["--edition=2018".to_string()]).unwrap();
+    let (sessopts, _) = build_session_options_and_crate_config(matches);
+    assert!(sessopts.edition == Edition::Edition2018)
+}
diff --git a/compiler/rustc_interface/src/util.rs b/compiler/rustc_interface/src/util.rs
new file mode 100644
index 00000000000..8b41a0ff176
--- /dev/null
+++ b/compiler/rustc_interface/src/util.rs
@@ -0,0 +1,901 @@
+use rustc_ast::mut_visit::{visit_clobber, MutVisitor, *};
+use rustc_ast::ptr::P;
+use rustc_ast::{self as ast, AttrVec, BlockCheckMode};
+use rustc_codegen_ssa::traits::CodegenBackend;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+#[cfg(parallel_compiler)]
+use rustc_data_structures::jobserver;
+use rustc_data_structures::sync::Lrc;
+use rustc_errors::registry::Registry;
+use rustc_metadata::dynamic_lib::DynamicLibrary;
+#[cfg(parallel_compiler)]
+use rustc_middle::ty::tls;
+#[cfg(parallel_compiler)]
+use rustc_query_impl::QueryCtxt;
+use rustc_resolve::{self, Resolver};
+use rustc_session as session;
+use rustc_session::config::{self, CrateType};
+use rustc_session::config::{ErrorOutputType, Input, OutputFilenames};
+use rustc_session::lint::{self, BuiltinLintDiagnostics, LintBuffer};
+use rustc_session::parse::CrateConfig;
+use rustc_session::{early_error, filesearch, output, DiagnosticOutput, Session};
+use rustc_span::edition::Edition;
+use rustc_span::lev_distance::find_best_match_for_name;
+use rustc_span::source_map::FileLoader;
+use rustc_span::symbol::{sym, Symbol};
+use smallvec::SmallVec;
+use std::env;
+use std::env::consts::{DLL_PREFIX, DLL_SUFFIX};
+use std::io;
+use std::lazy::SyncOnceCell;
+use std::mem;
+use std::ops::DerefMut;
+#[cfg(not(parallel_compiler))]
+use std::panic;
+use std::path::{Path, PathBuf};
+use std::sync::atomic::{AtomicBool, Ordering};
+use std::sync::{Arc, Mutex};
+use std::thread;
+use tracing::info;
+
+/// Adds `target_feature = "..."` cfgs for a variety of platform
+/// specific features (SSE, NEON etc.).
+///
+/// This is performed by checking whether a set of permitted features
+/// is available on the target machine, by querying LLVM.
+pub fn add_configuration(
+    cfg: &mut CrateConfig,
+    sess: &mut Session,
+    codegen_backend: &dyn CodegenBackend,
+) {
+    let tf = sym::target_feature;
+
+    let target_features = codegen_backend.target_features(sess);
+    sess.target_features.extend(target_features.iter().cloned());
+
+    cfg.extend(target_features.into_iter().map(|feat| (tf, Some(feat))));
+
+    if sess.crt_static(None) {
+        cfg.insert((tf, Some(sym::crt_dash_static)));
+    }
+}
+
+pub fn create_session(
+    sopts: config::Options,
+    cfg: FxHashSet<(String, Option<String>)>,
+    diagnostic_output: DiagnosticOutput,
+    file_loader: Option<Box<dyn FileLoader + Send + Sync + 'static>>,
+    input_path: Option<PathBuf>,
+    lint_caps: FxHashMap<lint::LintId, lint::Level>,
+    make_codegen_backend: Option<
+        Box<dyn FnOnce(&config::Options) -> Box<dyn CodegenBackend> + Send>,
+    >,
+    descriptions: Registry,
+) -> (Lrc<Session>, Lrc<Box<dyn CodegenBackend>>) {
+    let codegen_backend = if let Some(make_codegen_backend) = make_codegen_backend {
+        make_codegen_backend(&sopts)
+    } else {
+        get_codegen_backend(
+            &sopts.maybe_sysroot,
+            sopts.debugging_opts.codegen_backend.as_ref().map(|name| &name[..]),
+        )
+    };
+
+    // target_override is documented to be called before init(), so this is okay
+    let target_override = codegen_backend.target_override(&sopts);
+
+    let mut sess = session::build_session(
+        sopts,
+        input_path,
+        descriptions,
+        diagnostic_output,
+        lint_caps,
+        file_loader,
+        target_override,
+    );
+
+    codegen_backend.init(&sess);
+
+    let mut cfg = config::build_configuration(&sess, config::to_crate_config(cfg));
+    add_configuration(&mut cfg, &mut sess, &*codegen_backend);
+    sess.parse_sess.config = cfg;
+
+    (Lrc::new(sess), Lrc::new(codegen_backend))
+}
+
+const STACK_SIZE: usize = 8 * 1024 * 1024;
+
+fn get_stack_size() -> Option<usize> {
+    // FIXME: Hacks on hacks. If the env is trying to override the stack size
+    // then *don't* set it explicitly.
+    env::var_os("RUST_MIN_STACK").is_none().then_some(STACK_SIZE)
+}
+
+/// Like a `thread::Builder::spawn` followed by a `join()`, but avoids the need
+/// for `'static` bounds.
+#[cfg(not(parallel_compiler))]
+pub fn scoped_thread<F: FnOnce() -> R + Send, R: Send>(cfg: thread::Builder, f: F) -> R {
+    struct Ptr(*mut ());
+    unsafe impl Send for Ptr {}
+    unsafe impl Sync for Ptr {}
+
+    let mut f = Some(f);
+    let run = Ptr(&mut f as *mut _ as *mut ());
+    let mut result = None;
+    let result_ptr = Ptr(&mut result as *mut _ as *mut ());
+
+    let thread = cfg.spawn(move || {
+        let run = unsafe { (*(run.0 as *mut Option<F>)).take().unwrap() };
+        let result = unsafe { &mut *(result_ptr.0 as *mut Option<R>) };
+        *result = Some(run());
+    });
+
+    match thread.unwrap().join() {
+        Ok(()) => result.unwrap(),
+        Err(p) => panic::resume_unwind(p),
+    }
+}
+
+#[cfg(not(parallel_compiler))]
+pub fn setup_callbacks_and_run_in_thread_pool_with_globals<F: FnOnce() -> R + Send, R: Send>(
+    edition: Edition,
+    _threads: usize,
+    stderr: &Option<Arc<Mutex<Vec<u8>>>>,
+    f: F,
+) -> R {
+    let mut cfg = thread::Builder::new().name("rustc".to_string());
+
+    if let Some(size) = get_stack_size() {
+        cfg = cfg.stack_size(size);
+    }
+
+    crate::callbacks::setup_callbacks();
+
+    let main_handler = move || {
+        rustc_span::create_session_globals_then(edition, || {
+            io::set_output_capture(stderr.clone());
+            f()
+        })
+    };
+
+    scoped_thread(cfg, main_handler)
+}
+
+/// Creates a new thread and forwards information in thread locals to it.
+/// The new thread runs the deadlock handler.
+/// Must only be called when a deadlock is about to happen.
+#[cfg(parallel_compiler)]
+unsafe fn handle_deadlock() {
+    let registry = rustc_rayon_core::Registry::current();
+
+    let context = tls::get_tlv();
+    assert!(context != 0);
+    rustc_data_structures::sync::assert_sync::<tls::ImplicitCtxt<'_, '_>>();
+    let icx: &tls::ImplicitCtxt<'_, '_> = &*(context as *const tls::ImplicitCtxt<'_, '_>);
+
+    let session_globals = rustc_span::with_session_globals(|sg| sg as *const _);
+    let session_globals = &*session_globals;
+    thread::spawn(move || {
+        tls::enter_context(icx, |_| {
+            rustc_span::set_session_globals_then(session_globals, || {
+                tls::with(|tcx| QueryCtxt::from_tcx(tcx).deadlock(&registry))
+            })
+        });
+    });
+}
+
+#[cfg(parallel_compiler)]
+pub fn setup_callbacks_and_run_in_thread_pool_with_globals<F: FnOnce() -> R + Send, R: Send>(
+    edition: Edition,
+    threads: usize,
+    stderr: &Option<Arc<Mutex<Vec<u8>>>>,
+    f: F,
+) -> R {
+    crate::callbacks::setup_callbacks();
+
+    let mut config = rayon::ThreadPoolBuilder::new()
+        .thread_name(|_| "rustc".to_string())
+        .acquire_thread_handler(jobserver::acquire_thread)
+        .release_thread_handler(jobserver::release_thread)
+        .num_threads(threads)
+        .deadlock_handler(|| unsafe { handle_deadlock() });
+
+    if let Some(size) = get_stack_size() {
+        config = config.stack_size(size);
+    }
+
+    let with_pool = move |pool: &rayon::ThreadPool| pool.install(f);
+
+    rustc_span::create_session_globals_then(edition, || {
+        rustc_span::with_session_globals(|session_globals| {
+            // The main handler runs for each Rayon worker thread and sets up
+            // the thread local rustc uses. `session_globals` is captured and set
+            // on the new threads.
+            let main_handler = move |thread: rayon::ThreadBuilder| {
+                rustc_span::set_session_globals_then(session_globals, || {
+                    io::set_output_capture(stderr.clone());
+                    thread.run()
+                })
+            };
+
+            config.build_scoped(main_handler, with_pool).unwrap()
+        })
+    })
+}
+
+fn load_backend_from_dylib(path: &Path) -> fn() -> Box<dyn CodegenBackend> {
+    let lib = DynamicLibrary::open(path).unwrap_or_else(|err| {
+        let err = format!("couldn't load codegen backend {:?}: {:?}", path, err);
+        early_error(ErrorOutputType::default(), &err);
+    });
+    unsafe {
+        match lib.symbol("__rustc_codegen_backend") {
+            Ok(f) => {
+                mem::forget(lib);
+                mem::transmute::<*mut u8, _>(f)
+            }
+            Err(e) => {
+                let err = format!(
+                    "couldn't load codegen backend as it \
+                                   doesn't export the `__rustc_codegen_backend` \
+                                   symbol: {:?}",
+                    e
+                );
+                early_error(ErrorOutputType::default(), &err);
+            }
+        }
+    }
+}
+
+/// Get the codegen backend based on the name and specified sysroot.
+///
+/// A name of `None` indicates that the default backend should be used.
+pub fn get_codegen_backend(
+    maybe_sysroot: &Option<PathBuf>,
+    backend_name: Option<&str>,
+) -> Box<dyn CodegenBackend> {
+    static LOAD: SyncOnceCell<unsafe fn() -> Box<dyn CodegenBackend>> = SyncOnceCell::new();
+
+    let load = LOAD.get_or_init(|| {
+        #[cfg(feature = "llvm")]
+        const DEFAULT_CODEGEN_BACKEND: &str = "llvm";
+
+        #[cfg(not(feature = "llvm"))]
+        const DEFAULT_CODEGEN_BACKEND: &str = "cranelift";
+
+        match backend_name.unwrap_or(DEFAULT_CODEGEN_BACKEND) {
+            filename if filename.contains('.') => load_backend_from_dylib(filename.as_ref()),
+            #[cfg(feature = "llvm")]
+            "llvm" => rustc_codegen_llvm::LlvmCodegenBackend::new,
+            backend_name => get_codegen_sysroot(maybe_sysroot, backend_name),
+        }
+    });
+
+    // SAFETY: In case of a builtin codegen backend this is safe. In case of an external codegen
+    // backend we hope that the backend links against the same rustc_driver version. If this is not
+    // the case, we get UB.
+    unsafe { load() }
+}
+
+// This is used for rustdoc, but it uses similar machinery to codegen backend
+// loading, so we leave the code here. It is potentially useful for other tools
+// that want to invoke the rustc binary while linking to rustc as well.
+pub fn rustc_path<'a>() -> Option<&'a Path> {
+    static RUSTC_PATH: SyncOnceCell<Option<PathBuf>> = SyncOnceCell::new();
+
+    const BIN_PATH: &str = env!("RUSTC_INSTALL_BINDIR");
+
+    RUSTC_PATH.get_or_init(|| get_rustc_path_inner(BIN_PATH)).as_ref().map(|v| &**v)
+}
+
+fn get_rustc_path_inner(bin_path: &str) -> Option<PathBuf> {
+    sysroot_candidates().iter().find_map(|sysroot| {
+        let candidate = sysroot.join(bin_path).join(if cfg!(target_os = "windows") {
+            "rustc.exe"
+        } else {
+            "rustc"
+        });
+        candidate.exists().then_some(candidate)
+    })
+}
+
+fn sysroot_candidates() -> Vec<PathBuf> {
+    let target = session::config::host_triple();
+    let mut sysroot_candidates = vec![filesearch::get_or_default_sysroot()];
+    let path = current_dll_path().and_then(|s| s.canonicalize().ok());
+    if let Some(dll) = path {
+        // use `parent` twice to chop off the file name and then also the
+        // directory containing the dll which should be either `lib` or `bin`.
+        if let Some(path) = dll.parent().and_then(|p| p.parent()) {
+            // The original `path` pointed at the `rustc_driver` crate's dll.
+            // Now that dll should only be in one of two locations. The first is
+            // in the compiler's libdir, for example `$sysroot/lib/*.dll`. The
+            // other is the target's libdir, for example
+            // `$sysroot/lib/rustlib/$target/lib/*.dll`.
+            //
+            // We don't know which, so let's assume that if our `path` above
+            // ends in `$target` we *could* be in the target libdir, and always
+            // assume that we may be in the main libdir.
+            sysroot_candidates.push(path.to_owned());
+
+            if path.ends_with(target) {
+                sysroot_candidates.extend(
+                    path.parent() // chop off `$target`
+                        .and_then(|p| p.parent()) // chop off `rustlib`
+                        .and_then(|p| p.parent()) // chop off `lib`
+                        .map(|s| s.to_owned()),
+                );
+            }
+        }
+    }
+
+    return sysroot_candidates;
+
+    #[cfg(unix)]
+    fn current_dll_path() -> Option<PathBuf> {
+        use std::ffi::{CStr, OsStr};
+        use std::os::unix::prelude::*;
+
+        unsafe {
+            let addr = current_dll_path as usize as *mut _;
+            let mut info = mem::zeroed();
+            if libc::dladdr(addr, &mut info) == 0 {
+                info!("dladdr failed");
+                return None;
+            }
+            if info.dli_fname.is_null() {
+                info!("dladdr returned null pointer");
+                return None;
+            }
+            let bytes = CStr::from_ptr(info.dli_fname).to_bytes();
+            let os = OsStr::from_bytes(bytes);
+            Some(PathBuf::from(os))
+        }
+    }
+
+    #[cfg(windows)]
+    fn current_dll_path() -> Option<PathBuf> {
+        use std::ffi::OsString;
+        use std::os::windows::prelude::*;
+        use std::ptr;
+
+        use winapi::um::libloaderapi::{
+            GetModuleFileNameW, GetModuleHandleExW, GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
+        };
+
+        unsafe {
+            let mut module = ptr::null_mut();
+            let r = GetModuleHandleExW(
+                GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
+                current_dll_path as usize as *mut _,
+                &mut module,
+            );
+            if r == 0 {
+                info!("GetModuleHandleExW failed: {}", io::Error::last_os_error());
+                return None;
+            }
+            let mut space = Vec::with_capacity(1024);
+            let r = GetModuleFileNameW(module, space.as_mut_ptr(), space.capacity() as u32);
+            if r == 0 {
+                info!("GetModuleFileNameW failed: {}", io::Error::last_os_error());
+                return None;
+            }
+            let r = r as usize;
+            if r >= space.capacity() {
+                info!("our buffer was too small? {}", io::Error::last_os_error());
+                return None;
+            }
+            space.set_len(r);
+            let os = OsString::from_wide(&space);
+            Some(PathBuf::from(os))
+        }
+    }
+}
+
+pub fn get_codegen_sysroot(
+    maybe_sysroot: &Option<PathBuf>,
+    backend_name: &str,
+) -> fn() -> Box<dyn CodegenBackend> {
+    // For now we only allow this function to be called once as it'll dlopen a
+    // few things, which seems to work best if we only do that once. In
+    // general this assertion never trips due to the once guard in `get_codegen_backend`,
+    // but there's a few manual calls to this function in this file we protect
+    // against.
+    static LOADED: AtomicBool = AtomicBool::new(false);
+    assert!(
+        !LOADED.fetch_or(true, Ordering::SeqCst),
+        "cannot load the default codegen backend twice"
+    );
+
+    let target = session::config::host_triple();
+    let sysroot_candidates = sysroot_candidates();
+
+    let sysroot = maybe_sysroot
+        .iter()
+        .chain(sysroot_candidates.iter())
+        .map(|sysroot| {
+            filesearch::make_target_lib_path(&sysroot, &target).with_file_name("codegen-backends")
+        })
+        .find(|f| {
+            info!("codegen backend candidate: {}", f.display());
+            f.exists()
+        });
+    let sysroot = sysroot.unwrap_or_else(|| {
+        let candidates = sysroot_candidates
+            .iter()
+            .map(|p| p.display().to_string())
+            .collect::<Vec<_>>()
+            .join("\n* ");
+        let err = format!(
+            "failed to find a `codegen-backends` folder \
+                           in the sysroot candidates:\n* {}",
+            candidates
+        );
+        early_error(ErrorOutputType::default(), &err);
+    });
+    info!("probing {} for a codegen backend", sysroot.display());
+
+    let d = sysroot.read_dir().unwrap_or_else(|e| {
+        let err = format!(
+            "failed to load default codegen backend, couldn't \
+                           read `{}`: {}",
+            sysroot.display(),
+            e
+        );
+        early_error(ErrorOutputType::default(), &err);
+    });
+
+    let mut file: Option<PathBuf> = None;
+
+    let expected_names = &[
+        format!("rustc_codegen_{}-{}", backend_name, release_str().expect("CFG_RELEASE")),
+        format!("rustc_codegen_{}", backend_name),
+    ];
+    for entry in d.filter_map(|e| e.ok()) {
+        let path = entry.path();
+        let filename = match path.file_name().and_then(|s| s.to_str()) {
+            Some(s) => s,
+            None => continue,
+        };
+        if !(filename.starts_with(DLL_PREFIX) && filename.ends_with(DLL_SUFFIX)) {
+            continue;
+        }
+        let name = &filename[DLL_PREFIX.len()..filename.len() - DLL_SUFFIX.len()];
+        if !expected_names.iter().any(|expected| expected == name) {
+            continue;
+        }
+        if let Some(ref prev) = file {
+            let err = format!(
+                "duplicate codegen backends found\n\
+                               first:  {}\n\
+                               second: {}\n\
+            ",
+                prev.display(),
+                path.display()
+            );
+            early_error(ErrorOutputType::default(), &err);
+        }
+        file = Some(path.clone());
+    }
+
+    match file {
+        Some(ref s) => load_backend_from_dylib(s),
+        None => {
+            let err = format!("unsupported builtin codegen backend `{}`", backend_name);
+            early_error(ErrorOutputType::default(), &err);
+        }
+    }
+}
+
+pub(crate) fn check_attr_crate_type(
+    sess: &Session,
+    attrs: &[ast::Attribute],
+    lint_buffer: &mut LintBuffer,
+) {
+    // Unconditionally collect crate types from attributes to make them used
+    for a in attrs.iter() {
+        if sess.check_name(a, sym::crate_type) {
+            if let Some(n) = a.value_str() {
+                if categorize_crate_type(n).is_some() {
+                    return;
+                }
+
+                if let ast::MetaItemKind::NameValue(spanned) = a.meta().unwrap().kind {
+                    let span = spanned.span;
+                    let lev_candidate = find_best_match_for_name(
+                        &CRATE_TYPES.iter().map(|(k, _)| *k).collect::<Vec<_>>(),
+                        n,
+                        None,
+                    );
+                    if let Some(candidate) = lev_candidate {
+                        lint_buffer.buffer_lint_with_diagnostic(
+                            lint::builtin::UNKNOWN_CRATE_TYPES,
+                            ast::CRATE_NODE_ID,
+                            span,
+                            "invalid `crate_type` value",
+                            BuiltinLintDiagnostics::UnknownCrateTypes(
+                                span,
+                                "did you mean".to_string(),
+                                format!("\"{}\"", candidate),
+                            ),
+                        );
+                    } else {
+                        lint_buffer.buffer_lint(
+                            lint::builtin::UNKNOWN_CRATE_TYPES,
+                            ast::CRATE_NODE_ID,
+                            span,
+                            "invalid `crate_type` value",
+                        );
+                    }
+                }
+            }
+        }
+    }
+}
+
+const CRATE_TYPES: &[(Symbol, CrateType)] = &[
+    (sym::rlib, CrateType::Rlib),
+    (sym::dylib, CrateType::Dylib),
+    (sym::cdylib, CrateType::Cdylib),
+    (sym::lib, config::default_lib_output()),
+    (sym::staticlib, CrateType::Staticlib),
+    (sym::proc_dash_macro, CrateType::ProcMacro),
+    (sym::bin, CrateType::Executable),
+];
+
+fn categorize_crate_type(s: Symbol) -> Option<CrateType> {
+    Some(CRATE_TYPES.iter().find(|(key, _)| *key == s)?.1)
+}
+
+pub fn collect_crate_types(session: &Session, attrs: &[ast::Attribute]) -> Vec<CrateType> {
+    // Unconditionally collect crate types from attributes to make them used
+    let attr_types: Vec<CrateType> = attrs
+        .iter()
+        .filter_map(|a| {
+            if session.check_name(a, sym::crate_type) {
+                match a.value_str() {
+                    Some(s) => categorize_crate_type(s),
+                    _ => None,
+                }
+            } else {
+                None
+            }
+        })
+        .collect();
+
+    // If we're generating a test executable, then ignore all other output
+    // styles at all other locations
+    if session.opts.test {
+        return vec![CrateType::Executable];
+    }
+
+    // Only check command line flags if present. If no types are specified by
+    // command line, then reuse the empty `base` Vec to hold the types that
+    // will be found in crate attributes.
+    let mut base = session.opts.crate_types.clone();
+    if base.is_empty() {
+        base.extend(attr_types);
+        if base.is_empty() {
+            base.push(output::default_output_for_target(session));
+        } else {
+            base.sort();
+            base.dedup();
+        }
+    }
+
+    base.retain(|crate_type| {
+        let res = !output::invalid_output_for_target(session, *crate_type);
+
+        if !res {
+            session.warn(&format!(
+                "dropping unsupported crate type `{}` for target `{}`",
+                *crate_type, session.opts.target_triple
+            ));
+        }
+
+        res
+    });
+
+    base
+}
+
+pub fn build_output_filenames(
+    input: &Input,
+    odir: &Option<PathBuf>,
+    ofile: &Option<PathBuf>,
+    attrs: &[ast::Attribute],
+    sess: &Session,
+) -> OutputFilenames {
+    match *ofile {
+        None => {
+            // "-" as input file will cause the parser to read from stdin so we
+            // have to make up a name
+            // We want to toss everything after the final '.'
+            let dirpath = (*odir).as_ref().cloned().unwrap_or_default();
+
+            // If a crate name is present, we use it as the link name
+            let stem = sess
+                .opts
+                .crate_name
+                .clone()
+                .or_else(|| rustc_attr::find_crate_name(&sess, attrs).map(|n| n.to_string()))
+                .unwrap_or_else(|| input.filestem().to_owned());
+
+            OutputFilenames::new(
+                dirpath,
+                stem,
+                None,
+                sess.opts.cg.extra_filename.clone(),
+                sess.opts.output_types.clone(),
+            )
+        }
+
+        Some(ref out_file) => {
+            let unnamed_output_types =
+                sess.opts.output_types.values().filter(|a| a.is_none()).count();
+            let ofile = if unnamed_output_types > 1 {
+                sess.warn(
+                    "due to multiple output types requested, the explicitly specified \
+                     output file name will be adapted for each output type",
+                );
+                None
+            } else {
+                if !sess.opts.cg.extra_filename.is_empty() {
+                    sess.warn("ignoring -C extra-filename flag due to -o flag");
+                }
+                Some(out_file.clone())
+            };
+            if *odir != None {
+                sess.warn("ignoring --out-dir flag due to -o flag");
+            }
+
+            OutputFilenames::new(
+                out_file.parent().unwrap_or_else(|| Path::new("")).to_path_buf(),
+                out_file.file_stem().unwrap_or_default().to_str().unwrap().to_string(),
+                ofile,
+                sess.opts.cg.extra_filename.clone(),
+                sess.opts.output_types.clone(),
+            )
+        }
+    }
+}
+
+#[cfg(not(target_os = "linux"))]
+pub fn non_durable_rename(src: &Path, dst: &Path) -> std::io::Result<()> {
+    std::fs::rename(src, dst)
+}
+
+/// This function attempts to bypass the auto_da_alloc heuristic implemented by some filesystems
+/// such as btrfs and ext4. When renaming over a file that already exists then they will "helpfully"
+/// write back the source file before committing the rename in case a developer forgot some of
+/// the fsyncs in the open/write/fsync(file)/rename/fsync(dir) dance for atomic file updates.
+///
+/// To avoid triggering this heuristic we delete the destination first, if it exists.
+/// The cost of an extra syscall is much lower than getting descheduled for the sync IO.
+#[cfg(target_os = "linux")]
+pub fn non_durable_rename(src: &Path, dst: &Path) -> std::io::Result<()> {
+    let _ = std::fs::remove_file(dst);
+    std::fs::rename(src, dst)
+}
+
+/// Replaces function bodies with `loop {}` (an infinite loop). This gets rid of
+/// all semantic errors in the body while still satisfying the return type,
+/// except in certain cases, see below for more.
+///
+/// This pass is known as `everybody_loops`. Very punny.
+///
+/// As of March 2021, `everybody_loops` is only used for the
+/// `-Z unpretty=everybody_loops` debugging option.
+///
+/// FIXME: Currently the `everybody_loops` transformation is not applied to:
+///  * `const fn`; support could be added, but hasn't. Originally `const fn`
+///    was skipped due to issue #43636 that `loop` was not supported for
+///    const evaluation.
+///  * `impl Trait`, due to issue #43869 that functions returning impl Trait cannot be diverging.
+///    Solving this may require `!` to implement every trait, which relies on the an even more
+///    ambitious form of the closed RFC #1637. See also [#34511].
+///
+/// [#34511]: https://github.com/rust-lang/rust/issues/34511#issuecomment-322340401
+pub struct ReplaceBodyWithLoop<'a, 'b> {
+    within_static_or_const: bool,
+    nested_blocks: Option<Vec<ast::Block>>,
+    resolver: &'a mut Resolver<'b>,
+}
+
+impl<'a, 'b> ReplaceBodyWithLoop<'a, 'b> {
+    pub fn new(resolver: &'a mut Resolver<'b>) -> ReplaceBodyWithLoop<'a, 'b> {
+        ReplaceBodyWithLoop { within_static_or_const: false, nested_blocks: None, resolver }
+    }
+
+    fn run<R, F: FnOnce(&mut Self) -> R>(&mut self, is_const: bool, action: F) -> R {
+        let old_const = mem::replace(&mut self.within_static_or_const, is_const);
+        let old_blocks = self.nested_blocks.take();
+        let ret = action(self);
+        self.within_static_or_const = old_const;
+        self.nested_blocks = old_blocks;
+        ret
+    }
+
+    fn should_ignore_fn(ret_ty: &ast::FnRetTy) -> bool {
+        if let ast::FnRetTy::Ty(ref ty) = ret_ty {
+            fn involves_impl_trait(ty: &ast::Ty) -> bool {
+                match ty.kind {
+                    ast::TyKind::ImplTrait(..) => true,
+                    ast::TyKind::Slice(ref subty)
+                    | ast::TyKind::Array(ref subty, _)
+                    | ast::TyKind::Ptr(ast::MutTy { ty: ref subty, .. })
+                    | ast::TyKind::Rptr(_, ast::MutTy { ty: ref subty, .. })
+                    | ast::TyKind::Paren(ref subty) => involves_impl_trait(subty),
+                    ast::TyKind::Tup(ref tys) => any_involves_impl_trait(tys.iter()),
+                    ast::TyKind::Path(_, ref path) => {
+                        path.segments.iter().any(|seg| match seg.args.as_deref() {
+                            None => false,
+                            Some(&ast::GenericArgs::AngleBracketed(ref data)) => {
+                                data.args.iter().any(|arg| match arg {
+                                    ast::AngleBracketedArg::Arg(arg) => match arg {
+                                        ast::GenericArg::Type(ty) => involves_impl_trait(ty),
+                                        ast::GenericArg::Lifetime(_)
+                                        | ast::GenericArg::Const(_) => false,
+                                    },
+                                    ast::AngleBracketedArg::Constraint(c) => match c.kind {
+                                        ast::AssocTyConstraintKind::Bound { .. } => true,
+                                        ast::AssocTyConstraintKind::Equality { ref ty } => {
+                                            involves_impl_trait(ty)
+                                        }
+                                    },
+                                })
+                            }
+                            Some(&ast::GenericArgs::Parenthesized(ref data)) => {
+                                any_involves_impl_trait(data.inputs.iter())
+                                    || ReplaceBodyWithLoop::should_ignore_fn(&data.output)
+                            }
+                        })
+                    }
+                    _ => false,
+                }
+            }
+
+            fn any_involves_impl_trait<'a, I: Iterator<Item = &'a P<ast::Ty>>>(mut it: I) -> bool {
+                it.any(|subty| involves_impl_trait(subty))
+            }
+
+            involves_impl_trait(ty)
+        } else {
+            false
+        }
+    }
+
+    fn is_sig_const(sig: &ast::FnSig) -> bool {
+        matches!(sig.header.constness, ast::Const::Yes(_))
+            || ReplaceBodyWithLoop::should_ignore_fn(&sig.decl.output)
+    }
+}
+
+impl<'a> MutVisitor for ReplaceBodyWithLoop<'a, '_> {
+    fn visit_item_kind(&mut self, i: &mut ast::ItemKind) {
+        let is_const = match i {
+            ast::ItemKind::Static(..) | ast::ItemKind::Const(..) => true,
+            ast::ItemKind::Fn(box ast::FnKind(_, ref sig, _, _)) => Self::is_sig_const(sig),
+            _ => false,
+        };
+        self.run(is_const, |s| noop_visit_item_kind(i, s))
+    }
+
+    fn flat_map_trait_item(&mut self, i: P<ast::AssocItem>) -> SmallVec<[P<ast::AssocItem>; 1]> {
+        let is_const = match i.kind {
+            ast::AssocItemKind::Const(..) => true,
+            ast::AssocItemKind::Fn(box ast::FnKind(_, ref sig, _, _)) => Self::is_sig_const(sig),
+            _ => false,
+        };
+        self.run(is_const, |s| noop_flat_map_assoc_item(i, s))
+    }
+
+    fn flat_map_impl_item(&mut self, i: P<ast::AssocItem>) -> SmallVec<[P<ast::AssocItem>; 1]> {
+        self.flat_map_trait_item(i)
+    }
+
+    fn visit_anon_const(&mut self, c: &mut ast::AnonConst) {
+        self.run(true, |s| noop_visit_anon_const(c, s))
+    }
+
+    fn visit_block(&mut self, b: &mut P<ast::Block>) {
+        fn stmt_to_block(
+            rules: ast::BlockCheckMode,
+            s: Option<ast::Stmt>,
+            resolver: &mut Resolver<'_>,
+        ) -> ast::Block {
+            ast::Block {
+                stmts: s.into_iter().collect(),
+                rules,
+                id: resolver.next_node_id(),
+                span: rustc_span::DUMMY_SP,
+                tokens: None,
+            }
+        }
+
+        fn block_to_stmt(b: ast::Block, resolver: &mut Resolver<'_>) -> ast::Stmt {
+            let expr = P(ast::Expr {
+                id: resolver.next_node_id(),
+                kind: ast::ExprKind::Block(P(b), None),
+                span: rustc_span::DUMMY_SP,
+                attrs: AttrVec::new(),
+                tokens: None,
+            });
+
+            ast::Stmt {
+                id: resolver.next_node_id(),
+                kind: ast::StmtKind::Expr(expr),
+                span: rustc_span::DUMMY_SP,
+            }
+        }
+
+        let empty_block = stmt_to_block(BlockCheckMode::Default, None, self.resolver);
+        let loop_expr = P(ast::Expr {
+            kind: ast::ExprKind::Loop(P(empty_block), None),
+            id: self.resolver.next_node_id(),
+            span: rustc_span::DUMMY_SP,
+            attrs: AttrVec::new(),
+            tokens: None,
+        });
+
+        let loop_stmt = ast::Stmt {
+            id: self.resolver.next_node_id(),
+            span: rustc_span::DUMMY_SP,
+            kind: ast::StmtKind::Expr(loop_expr),
+        };
+
+        if self.within_static_or_const {
+            noop_visit_block(b, self)
+        } else {
+            visit_clobber(b.deref_mut(), |b| {
+                let mut stmts = vec![];
+                for s in b.stmts {
+                    let old_blocks = self.nested_blocks.replace(vec![]);
+
+                    stmts.extend(self.flat_map_stmt(s).into_iter().filter(|s| s.is_item()));
+
+                    // we put a Some in there earlier with that replace(), so this is valid
+                    let new_blocks = self.nested_blocks.take().unwrap();
+                    self.nested_blocks = old_blocks;
+                    stmts.extend(new_blocks.into_iter().map(|b| block_to_stmt(b, self.resolver)));
+                }
+
+                let mut new_block = ast::Block { stmts, ..b };
+
+                if let Some(old_blocks) = self.nested_blocks.as_mut() {
+                    //push our fresh block onto the cache and yield an empty block with `loop {}`
+                    if !new_block.stmts.is_empty() {
+                        old_blocks.push(new_block);
+                    }
+
+                    stmt_to_block(b.rules, Some(loop_stmt), &mut self.resolver)
+                } else {
+                    //push `loop {}` onto the end of our fresh block and yield that
+                    new_block.stmts.push(loop_stmt);
+
+                    new_block
+                }
+            })
+        }
+    }
+}
+
+/// Returns a version string such as "1.46.0 (04488afe3 2020-08-24)"
+pub fn version_str() -> Option<&'static str> {
+    option_env!("CFG_VERSION")
+}
+
+/// Returns a version string such as "0.12.0-dev".
+pub fn release_str() -> Option<&'static str> {
+    option_env!("CFG_RELEASE")
+}
+
+/// Returns the full SHA1 hash of HEAD of the Git repo from which rustc was built.
+pub fn commit_hash_str() -> Option<&'static str> {
+    option_env!("CFG_VER_HASH")
+}
+
+/// Returns the "commit date" of HEAD of the Git repo from which rustc was built as a static string.
+pub fn commit_date_str() -> Option<&'static str> {
+    option_env!("CFG_VER_DATE")
+}