diff --git a/compiler/rustc_builtin_macros/src/cmdline_attrs.rs b/compiler/rustc_builtin_macros/src/cmdline_attrs.rs
index db05c00d211..2b6fcc169be 100644
--- a/compiler/rustc_builtin_macros/src/cmdline_attrs.rs
+++ b/compiler/rustc_builtin_macros/src/cmdline_attrs.rs
@@ -6,7 +6,7 @@ use rustc_ast::{self as ast, AttrItem, AttrStyle};
 use rustc_session::parse::ParseSess;
 use rustc_span::FileName;
 
-pub fn inject(mut krate: ast::Crate, parse_sess: &ParseSess, attrs: &[String]) -> ast::Crate {
+pub fn inject(krate: &mut ast::Crate, parse_sess: &ParseSess, attrs: &[String]) {
     for raw_attr in attrs {
         let mut parser = rustc_parse::new_parser_from_source_str(
             parse_sess,
@@ -36,6 +36,4 @@ pub fn inject(mut krate: ast::Crate, parse_sess: &ParseSess, attrs: &[String]) -
             start_span.to(end_span),
         ));
     }
-
-    krate
 }
diff --git a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs
index a73fed6ccb2..378d5f39f4a 100644
--- a/compiler/rustc_builtin_macros/src/proc_macro_harness.rs
+++ b/compiler/rustc_builtin_macros/src/proc_macro_harness.rs
@@ -43,14 +43,14 @@ struct CollectProcMacros<'a> {
 }
 
 pub fn inject(
+    krate: &mut ast::Crate,
     sess: &Session,
     resolver: &mut dyn ResolverExpand,
-    mut krate: ast::Crate,
     is_proc_macro_crate: bool,
     has_proc_macro_decls: bool,
     is_test_crate: bool,
     handler: &rustc_errors::Handler,
-) -> ast::Crate {
+) {
     let ecfg = ExpansionConfig::default("proc_macro".to_string());
     let mut cx = ExtCtxt::new(sess, ecfg, resolver, None);
 
@@ -64,22 +64,20 @@ pub fn inject(
     };
 
     if has_proc_macro_decls || is_proc_macro_crate {
-        visit::walk_crate(&mut collect, &krate);
+        visit::walk_crate(&mut collect, krate);
     }
     let macros = collect.macros;
 
     if !is_proc_macro_crate {
-        return krate;
+        return;
     }
 
     if is_test_crate {
-        return krate;
+        return;
     }
 
     let decls = mk_decls(&mut cx, &macros);
     krate.items.push(decls);
-
-    krate
 }
 
 impl<'a> CollectProcMacros<'a> {
diff --git a/compiler/rustc_builtin_macros/src/standard_library_imports.rs b/compiler/rustc_builtin_macros/src/standard_library_imports.rs
index caed40d9fa8..6493c6f13d5 100644
--- a/compiler/rustc_builtin_macros/src/standard_library_imports.rs
+++ b/compiler/rustc_builtin_macros/src/standard_library_imports.rs
@@ -9,17 +9,19 @@ use rustc_span::DUMMY_SP;
 use thin_vec::thin_vec;
 
 pub fn inject(
-    mut krate: ast::Crate,
+    krate: &mut ast::Crate,
+    pre_configured_attrs: &[ast::Attribute],
     resolver: &mut dyn ResolverExpand,
     sess: &Session,
-) -> ast::Crate {
+) -> usize {
+    let orig_num_items = krate.items.len();
     let edition = sess.parse_sess.edition;
 
     // the first name in this list is the crate name of the crate with the prelude
-    let names: &[Symbol] = if attr::contains_name(&krate.attrs, sym::no_core) {
-        return krate;
-    } else if attr::contains_name(&krate.attrs, sym::no_std) {
-        if attr::contains_name(&krate.attrs, sym::compiler_builtins) {
+    let names: &[Symbol] = if attr::contains_name(pre_configured_attrs, sym::no_core) {
+        return 0;
+    } else if attr::contains_name(pre_configured_attrs, sym::no_std) {
+        if attr::contains_name(pre_configured_attrs, sym::compiler_builtins) {
             &[sym::core]
         } else {
             &[sym::core, sym::compiler_builtins]
@@ -88,6 +90,5 @@ pub fn inject(
     );
 
     krate.items.insert(0, use_item);
-
-    krate
+    krate.items.len() - orig_num_items
 }
diff --git a/compiler/rustc_builtin_macros/src/test_harness.rs b/compiler/rustc_builtin_macros/src/test_harness.rs
index 2d491b2dac8..43ab6c04428 100644
--- a/compiler/rustc_builtin_macros/src/test_harness.rs
+++ b/compiler/rustc_builtin_macros/src/test_harness.rs
@@ -37,7 +37,7 @@ struct TestCtxt<'a> {
 
 /// Traverse the crate, collecting all the test functions, eliding any
 /// existing main functions, and synthesizing a main test harness
-pub fn inject(sess: &Session, resolver: &mut dyn ResolverExpand, krate: &mut ast::Crate) {
+pub fn inject(krate: &mut ast::Crate, sess: &Session, resolver: &mut dyn ResolverExpand) {
     let span_diagnostic = sess.diagnostic();
     let panic_strategy = sess.panic_strategy();
     let platform_panic_strategy = sess.target.panic_strategy;
diff --git a/compiler/rustc_driver_impl/src/lib.rs b/compiler/rustc_driver_impl/src/lib.rs
index 8634c644176..14d6569271e 100644
--- a/compiler/rustc_driver_impl/src/lib.rs
+++ b/compiler/rustc_driver_impl/src/lib.rs
@@ -353,7 +353,7 @@ fn run_compiler(
 
             {
                 let plugins = queries.register_plugins()?;
-                let (_, lint_store) = &*plugins.borrow();
+                let (.., lint_store) = &*plugins.borrow();
 
                 // Lint plugins are registered; now we can process command line flags.
                 if sess.opts.describe_lints {
diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs
index 6eb0d24eb97..d32af10914e 100644
--- a/compiler/rustc_expand/src/base.rs
+++ b/compiler/rustc_expand/src/base.rs
@@ -1002,6 +1002,7 @@ pub struct ExpansionData {
 pub struct ExtCtxt<'a> {
     pub sess: &'a Session,
     pub ecfg: expand::ExpansionConfig<'a>,
+    pub num_standard_library_imports: usize,
     pub reduced_recursion_limit: Option<Limit>,
     pub root_path: PathBuf,
     pub resolver: &'a mut dyn ResolverExpand,
@@ -1030,6 +1031,7 @@ impl<'a> ExtCtxt<'a> {
         ExtCtxt {
             sess,
             ecfg,
+            num_standard_library_imports: 0,
             reduced_recursion_limit: None,
             resolver,
             lint_store,
diff --git a/compiler/rustc_expand/src/config.rs b/compiler/rustc_expand/src/config.rs
index d6cb173ba9b..a78dc0678d5 100644
--- a/compiler/rustc_expand/src/config.rs
+++ b/compiler/rustc_expand/src/config.rs
@@ -24,7 +24,6 @@ use rustc_session::Session;
 use rustc_span::edition::{Edition, ALL_EDITIONS};
 use rustc_span::symbol::{sym, Symbol};
 use rustc_span::{Span, DUMMY_SP};
-use thin_vec::ThinVec;
 
 /// A folder that strips out items that do not belong in the current configuration.
 pub struct StripUnconfigured<'a> {
@@ -37,7 +36,7 @@ pub struct StripUnconfigured<'a> {
     pub lint_node_id: NodeId,
 }
 
-fn get_features(sess: &Session, krate_attrs: &[ast::Attribute]) -> Features {
+pub fn features(sess: &Session, krate_attrs: &[Attribute]) -> Features {
     fn feature_removed(sess: &Session, span: Span, reason: Option<&str>) {
         sess.emit_err(FeatureRemoved {
             span,
@@ -191,39 +190,16 @@ fn get_features(sess: &Session, krate_attrs: &[ast::Attribute]) -> Features {
     features
 }
 
-/// `cfg_attr`-process the crate's attributes and compute the crate's features.
-pub fn features(
-    sess: &Session,
-    mut krate: ast::Crate,
-    lint_node_id: NodeId,
-) -> (ast::Crate, Features) {
-    let mut strip_unconfigured =
-        StripUnconfigured { sess, features: None, config_tokens: false, lint_node_id };
-
-    let unconfigured_attrs = krate.attrs.clone();
-    let diag = &sess.parse_sess.span_diagnostic;
-    let err_count = diag.err_count();
-    let features = match strip_unconfigured.configure_krate_attrs(krate.attrs) {
-        None => {
-            // The entire crate is unconfigured.
-            krate.attrs = ast::AttrVec::new();
-            krate.items = ThinVec::new();
-            Features::default()
-        }
-        Some(attrs) => {
-            krate.attrs = attrs;
-            let features = get_features(sess, &krate.attrs);
-            if err_count == diag.err_count() {
-                // Avoid reconfiguring malformed `cfg_attr`s.
-                strip_unconfigured.features = Some(&features);
-                // Run configuration again, this time with features available
-                // so that we can perform feature-gating.
-                strip_unconfigured.configure_krate_attrs(unconfigured_attrs);
-            }
-            features
-        }
+pub fn pre_configure_attrs(sess: &Session, attrs: &[Attribute]) -> ast::AttrVec {
+    let strip_unconfigured = StripUnconfigured {
+        sess,
+        features: None,
+        config_tokens: false,
+        lint_node_id: ast::CRATE_NODE_ID,
     };
-    (krate, features)
+    let attrs: ast::AttrVec =
+        attrs.iter().flat_map(|attr| strip_unconfigured.process_cfg_attr(attr)).collect();
+    if strip_unconfigured.in_cfg(&attrs) { attrs } else { ast::AttrVec::new() }
 }
 
 #[macro_export]
@@ -254,11 +230,6 @@ impl<'a> StripUnconfigured<'a> {
         }
     }
 
-    fn configure_krate_attrs(&self, mut attrs: ast::AttrVec) -> Option<ast::AttrVec> {
-        attrs.flat_map_in_place(|attr| self.process_cfg_attr(attr));
-        self.in_cfg(&attrs).then_some(attrs)
-    }
-
     /// Performs cfg-expansion on `stream`, producing a new `AttrTokenStream`.
     /// This is only used during the invocation of `derive` proc-macros,
     /// which require that we cfg-expand their entire input.
@@ -281,7 +252,7 @@ impl<'a> StripUnconfigured<'a> {
             .iter()
             .flat_map(|tree| match tree.clone() {
                 AttrTokenTree::Attributes(mut data) => {
-                    data.attrs.flat_map_in_place(|attr| self.process_cfg_attr(attr));
+                    data.attrs.flat_map_in_place(|attr| self.process_cfg_attr(&attr));
 
                     if self.in_cfg(&data.attrs) {
                         data.tokens = LazyAttrTokenStream::new(
@@ -319,12 +290,16 @@ impl<'a> StripUnconfigured<'a> {
     /// the syntax of any `cfg_attr` is incorrect.
     fn process_cfg_attrs<T: HasAttrs>(&self, node: &mut T) {
         node.visit_attrs(|attrs| {
-            attrs.flat_map_in_place(|attr| self.process_cfg_attr(attr));
+            attrs.flat_map_in_place(|attr| self.process_cfg_attr(&attr));
         });
     }
 
-    fn process_cfg_attr(&self, attr: Attribute) -> Vec<Attribute> {
-        if attr.has_name(sym::cfg_attr) { self.expand_cfg_attr(attr, true) } else { vec![attr] }
+    fn process_cfg_attr(&self, attr: &Attribute) -> Vec<Attribute> {
+        if attr.has_name(sym::cfg_attr) {
+            self.expand_cfg_attr(attr, true)
+        } else {
+            vec![attr.clone()]
+        }
     }
 
     /// Parse and expand a single `cfg_attr` attribute into a list of attributes
@@ -334,9 +309,9 @@ impl<'a> StripUnconfigured<'a> {
     /// Gives a compiler warning when the `cfg_attr` contains no attributes and
     /// is in the original source file. Gives a compiler error if the syntax of
     /// the attribute is incorrect.
-    pub(crate) fn expand_cfg_attr(&self, attr: Attribute, recursive: bool) -> Vec<Attribute> {
+    pub(crate) fn expand_cfg_attr(&self, attr: &Attribute, recursive: bool) -> Vec<Attribute> {
         let Some((cfg_predicate, expanded_attrs)) =
-            rustc_parse::parse_cfg_attr(&attr, &self.sess.parse_sess) else {
+            rustc_parse::parse_cfg_attr(attr, &self.sess.parse_sess) else {
                 return vec![];
             };
 
@@ -365,10 +340,10 @@ impl<'a> StripUnconfigured<'a> {
             //  `#[cfg_attr(false, cfg_attr(true, some_attr))]`.
             expanded_attrs
                 .into_iter()
-                .flat_map(|item| self.process_cfg_attr(self.expand_cfg_attr_item(&attr, item)))
+                .flat_map(|item| self.process_cfg_attr(&self.expand_cfg_attr_item(attr, item)))
                 .collect()
         } else {
-            expanded_attrs.into_iter().map(|item| self.expand_cfg_attr_item(&attr, item)).collect()
+            expanded_attrs.into_iter().map(|item| self.expand_cfg_attr_item(attr, item)).collect()
         }
     }
 
diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs
index 4092a192e0c..ec40911545f 100644
--- a/compiler/rustc_expand/src/expand.rs
+++ b/compiler/rustc_expand/src/expand.rs
@@ -1038,6 +1038,9 @@ trait InvocationCollectorNode: HasAttrs + HasNodeId + Sized {
     ) -> Result<Self::OutputTy, Self> {
         Ok(noop_flat_map(node, collector))
     }
+    fn expand_cfg_false(&mut self, collector: &mut InvocationCollector<'_, '_>, span: Span) {
+        collector.cx.emit_err(RemoveNodeNotSupported { span, descr: Self::descr() });
+    }
 }
 
 impl InvocationCollectorNode for P<ast::Item> {
@@ -1378,6 +1381,11 @@ impl InvocationCollectorNode for ast::Crate {
     fn noop_visit<V: MutVisitor>(&mut self, visitor: &mut V) {
         noop_visit_crate(self, visitor)
     }
+    fn expand_cfg_false(&mut self, collector: &mut InvocationCollector<'_, '_>, _span: Span) {
+        self.attrs.clear();
+        // Standard prelude imports are left in the crate for backward compatibility.
+        self.items.truncate(collector.cx.num_standard_library_imports);
+    }
 }
 
 impl InvocationCollectorNode for P<ast::Ty> {
@@ -1688,7 +1696,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
         res
     }
 
-    fn expand_cfg_attr(&self, node: &mut impl HasAttrs, attr: ast::Attribute, pos: usize) {
+    fn expand_cfg_attr(&self, node: &mut impl HasAttrs, attr: &ast::Attribute, pos: usize) {
         node.visit_attrs(|attrs| {
             // Repeated `insert` calls is inefficient, but the number of
             // insertions is almost always 0 or 1 in practice.
@@ -1712,7 +1720,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
                         Default::default()
                     }
                     sym::cfg_attr => {
-                        self.expand_cfg_attr(&mut node, attr, pos);
+                        self.expand_cfg_attr(&mut node, &attr, pos);
                         continue;
                     }
                     _ => {
@@ -1756,11 +1764,11 @@ impl<'a, 'b> InvocationCollector<'a, 'b> {
                             continue;
                         }
 
-                        self.cx.emit_err(RemoveNodeNotSupported { span, descr: Node::descr() });
+                        node.expand_cfg_false(self, span);
                         continue;
                     }
                     sym::cfg_attr => {
-                        self.expand_cfg_attr(node, attr, pos);
+                        self.expand_cfg_attr(node, &attr, pos);
                         continue;
                     }
                     _ => visit_clobber(node, |node| {
diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs
index 71bdd4df95b..7623c5f7327 100644
--- a/compiler/rustc_interface/src/passes.rs
+++ b/compiler/rustc_interface/src/passes.rs
@@ -3,7 +3,6 @@ use crate::interface::{Compiler, Result};
 use crate::proc_macro_decls;
 use crate::util;
 
-use ast::CRATE_NODE_ID;
 use rustc_ast::{self as ast, visit};
 use rustc_borrowck as mir_borrowck;
 use rustc_codegen_ssa::traits::CodegenBackend;
@@ -76,22 +75,14 @@ pub fn register_plugins<'a>(
     sess: &'a Session,
     metadata_loader: &'a dyn MetadataLoader,
     register_lints: impl Fn(&Session, &mut LintStore),
-    mut krate: ast::Crate,
+    pre_configured_attrs: &[ast::Attribute],
     crate_name: Symbol,
-) -> Result<(ast::Crate, LintStore)> {
-    krate = sess.time("attributes_injection", || {
-        rustc_builtin_macros::cmdline_attrs::inject(
-            krate,
-            &sess.parse_sess,
-            &sess.opts.unstable_opts.crate_attr,
-        )
-    });
-
-    let (krate, features) = rustc_expand::config::features(sess, krate, CRATE_NODE_ID);
+) -> Result<LintStore> {
     // these need to be set "early" so that expansion sees `quote` if enabled.
+    let features = rustc_expand::config::features(sess, pre_configured_attrs);
     sess.init_features(features);
 
-    let crate_types = util::collect_crate_types(sess, &krate.attrs);
+    let crate_types = util::collect_crate_types(sess, pre_configured_attrs);
     sess.init_crate_types(crate_types);
 
     let stable_crate_id = StableCrateId::new(
@@ -117,8 +108,9 @@ pub fn register_plugins<'a>(
     let mut lint_store = rustc_lint::new_lint_store(sess.enable_internal_lints());
     register_lints(sess, &mut lint_store);
 
-    let registrars =
-        sess.time("plugin_loading", || plugin::load::load_plugins(sess, metadata_loader, &krate));
+    let registrars = sess.time("plugin_loading", || {
+        plugin::load::load_plugins(sess, metadata_loader, pre_configured_attrs)
+    });
     sess.time("plugin_registration", || {
         let mut registry = plugin::Registry { lint_store: &mut lint_store };
         for registrar in registrars {
@@ -126,7 +118,7 @@ pub fn register_plugins<'a>(
         }
     });
 
-    Ok((krate, lint_store))
+    Ok(lint_store)
 }
 
 fn pre_expansion_lint<'a>(
@@ -173,19 +165,29 @@ impl LintStoreExpand for LintStoreExpandImpl<'_> {
 /// harness if one is to be provided, injection of a dependency on the
 /// standard library and prelude, and name resolution.
 #[instrument(level = "trace", skip(krate, resolver))]
-fn configure_and_expand(mut krate: ast::Crate, resolver: &mut Resolver<'_, '_>) -> ast::Crate {
+fn configure_and_expand(
+    mut krate: ast::Crate,
+    pre_configured_attrs: &[ast::Attribute],
+    resolver: &mut Resolver<'_, '_>,
+) -> ast::Crate {
     let tcx = resolver.tcx();
     let sess = tcx.sess;
     let lint_store = unerased_lint_store(tcx);
     let crate_name = tcx.crate_name(LOCAL_CRATE);
-    pre_expansion_lint(sess, lint_store, tcx.registered_tools(()), &krate, crate_name);
+    let lint_check_node = (&krate, pre_configured_attrs);
+    pre_expansion_lint(sess, lint_store, tcx.registered_tools(()), lint_check_node, crate_name);
     rustc_builtin_macros::register_builtin_macros(resolver);
 
-    krate = sess.time("crate_injection", || {
-        rustc_builtin_macros::standard_library_imports::inject(krate, resolver, sess)
+    let num_standard_library_imports = sess.time("crate_injection", || {
+        rustc_builtin_macros::standard_library_imports::inject(
+            &mut krate,
+            pre_configured_attrs,
+            resolver,
+            sess,
+        )
     });
 
-    util::check_attr_crate_type(sess, &krate.attrs, &mut resolver.lint_buffer());
+    util::check_attr_crate_type(sess, pre_configured_attrs, &mut resolver.lint_buffer());
 
     // Expand all macros
     krate = sess.time("macro_expand_crate", || {
@@ -222,7 +224,7 @@ fn configure_and_expand(mut krate: ast::Crate, resolver: &mut Resolver<'_, '_>)
 
         // Create the config for macro expansion
         let features = sess.features_untracked();
-        let recursion_limit = get_recursion_limit(&krate.attrs, sess);
+        let recursion_limit = get_recursion_limit(pre_configured_attrs, sess);
         let cfg = rustc_expand::expand::ExpansionConfig {
             features: Some(features),
             recursion_limit,
@@ -235,6 +237,7 @@ fn configure_and_expand(mut krate: ast::Crate, resolver: &mut Resolver<'_, '_>)
 
         let lint_store = LintStoreExpandImpl(lint_store);
         let mut ecx = ExtCtxt::new(sess, cfg, resolver, Some(&lint_store));
+        ecx.num_standard_library_imports = num_standard_library_imports;
         // Expand macros now!
         let krate = sess.time("expand_crate", || ecx.monotonic_expander().expand_crate(krate));
 
@@ -263,7 +266,7 @@ fn configure_and_expand(mut krate: ast::Crate, resolver: &mut Resolver<'_, '_>)
     });
 
     sess.time("maybe_building_test_harness", || {
-        rustc_builtin_macros::test_harness::inject(sess, resolver, &mut krate)
+        rustc_builtin_macros::test_harness::inject(&mut krate, sess, resolver)
     });
 
     let has_proc_macro_decls = sess.time("AST_validation", || {
@@ -287,12 +290,12 @@ fn configure_and_expand(mut krate: ast::Crate, resolver: &mut Resolver<'_, '_>)
         sess.emit_warning(errors::ProcMacroCratePanicAbort);
     }
 
-    krate = sess.time("maybe_create_a_macro_crate", || {
+    sess.time("maybe_create_a_macro_crate", || {
         let is_test_crate = sess.opts.test;
         rustc_builtin_macros::proc_macro_harness::inject(
+            &mut krate,
             sess,
             resolver,
-            krate,
             is_proc_macro_crate,
             has_proc_macro_decls,
             is_test_crate,
@@ -356,7 +359,7 @@ fn early_lint_checks(tcx: TyCtxt<'_>, (): ()) {
         tcx.registered_tools(()),
         Some(lint_buffer),
         rustc_lint::BuiltinCombinedEarlyLintPass::new(),
-        &**krate,
+        (&**krate, &*krate.attrs),
     )
 }
 
@@ -557,9 +560,9 @@ fn resolver_for_lowering<'tcx>(
 ) -> &'tcx Steal<(ty::ResolverAstLowering, Lrc<ast::Crate>)> {
     let arenas = Resolver::arenas();
     let _ = tcx.registered_tools(()); // Uses `crate_for_resolver`.
-    let krate = tcx.crate_for_resolver(()).steal();
-    let mut resolver = Resolver::new(tcx, &krate, &arenas);
-    let krate = configure_and_expand(krate, &mut resolver);
+    let (krate, pre_configured_attrs) = tcx.crate_for_resolver(()).steal();
+    let mut resolver = Resolver::new(tcx, &pre_configured_attrs, krate.spans.inner_span, &arenas);
+    let krate = configure_and_expand(krate, &pre_configured_attrs, &mut resolver);
 
     // Make sure we don't mutate the cstore from here on.
     tcx.untracked().cstore.leak();
diff --git a/compiler/rustc_interface/src/queries.rs b/compiler/rustc_interface/src/queries.rs
index 58ad044b399..d2293780836 100644
--- a/compiler/rustc_interface/src/queries.rs
+++ b/compiler/rustc_interface/src/queries.rs
@@ -88,8 +88,9 @@ pub struct Queries<'tcx> {
 
     dep_graph_future: Query<Option<DepGraphFuture>>,
     parse: Query<ast::Crate>,
+    pre_configure: Query<(ast::Crate, ast::AttrVec)>,
     crate_name: Query<Symbol>,
-    register_plugins: Query<(ast::Crate, Lrc<LintStore>)>,
+    register_plugins: Query<(ast::Crate, ast::AttrVec, Lrc<LintStore>)>,
     dep_graph: Query<DepGraph>,
     // This just points to what's in `gcx_cell`.
     gcx: Query<&'tcx GlobalCtxt<'tcx>>,
@@ -106,6 +107,7 @@ impl<'tcx> Queries<'tcx> {
             hir_arena: WorkerLocal::new(|_| rustc_hir::Arena::default()),
             dep_graph_future: Default::default(),
             parse: Default::default(),
+            pre_configure: Default::default(),
             crate_name: Default::default(),
             register_plugins: Default::default(),
             dep_graph: Default::default(),
@@ -133,17 +135,36 @@ impl<'tcx> Queries<'tcx> {
             .compute(|| passes::parse(self.session()).map_err(|mut parse_error| parse_error.emit()))
     }
 
-    pub fn register_plugins(&self) -> Result<QueryResult<'_, (ast::Crate, Lrc<LintStore>)>> {
+    pub fn pre_configure(&self) -> Result<QueryResult<'_, (ast::Crate, ast::AttrVec)>> {
+        self.pre_configure.compute(|| {
+            let mut krate = self.parse()?.steal();
+
+            let sess = self.session();
+            rustc_builtin_macros::cmdline_attrs::inject(
+                &mut krate,
+                &sess.parse_sess,
+                &sess.opts.unstable_opts.crate_attr,
+            );
+
+            let pre_configured_attrs =
+                rustc_expand::config::pre_configure_attrs(sess, &krate.attrs);
+            Ok((krate, pre_configured_attrs))
+        })
+    }
+
+    pub fn register_plugins(
+        &self,
+    ) -> Result<QueryResult<'_, (ast::Crate, ast::AttrVec, Lrc<LintStore>)>> {
         self.register_plugins.compute(|| {
             let crate_name = *self.crate_name()?.borrow();
-            let krate = self.parse()?.steal();
+            let (krate, pre_configured_attrs) = self.pre_configure()?.steal();
 
             let empty: &(dyn Fn(&Session, &mut LintStore) + Sync + Send) = &|_, _| {};
-            let (krate, lint_store) = passes::register_plugins(
+            let lint_store = passes::register_plugins(
                 self.session(),
                 &*self.codegen_backend().metadata_loader(),
                 self.compiler.register_lints.as_deref().unwrap_or_else(|| empty),
-                krate,
+                &pre_configured_attrs,
                 crate_name,
             )?;
 
@@ -154,17 +175,17 @@ impl<'tcx> Queries<'tcx> {
             // called, which happens within passes::register_plugins().
             self.dep_graph_future().ok();
 
-            Ok((krate, Lrc::new(lint_store)))
+            Ok((krate, pre_configured_attrs, Lrc::new(lint_store)))
         })
     }
 
     fn crate_name(&self) -> Result<QueryResult<'_, Symbol>> {
         self.crate_name.compute(|| {
             Ok({
-                let parse_result = self.parse()?;
-                let krate = parse_result.borrow();
+                let pre_configure_result = self.pre_configure()?;
+                let (_, pre_configured_attrs) = &*pre_configure_result.borrow();
                 // parse `#[crate_name]` even if `--crate-name` was passed, to make sure it matches.
-                find_crate_name(self.session(), &krate.attrs)
+                find_crate_name(self.session(), pre_configured_attrs)
             })
         })
     }
@@ -188,7 +209,7 @@ impl<'tcx> Queries<'tcx> {
     pub fn global_ctxt(&'tcx self) -> Result<QueryResult<'_, &'tcx GlobalCtxt<'tcx>>> {
         self.gcx.compute(|| {
             let crate_name = *self.crate_name()?.borrow();
-            let (krate, lint_store) = self.register_plugins()?.steal();
+            let (krate, pre_configured_attrs, lint_store) = self.register_plugins()?.steal();
 
             let sess = self.session();
 
@@ -215,7 +236,7 @@ impl<'tcx> Queries<'tcx> {
                 feed.crate_name(crate_name);
 
                 let feed = tcx.feed_unit_query();
-                feed.crate_for_resolver(tcx.arena.alloc(Steal::new(krate)));
+                feed.crate_for_resolver(tcx.arena.alloc(Steal::new((krate, pre_configured_attrs))));
                 feed.metadata_loader(
                     tcx.arena.alloc(Steal::new(self.codegen_backend().metadata_loader())),
                 );
diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs
index 337a19dd024..9b0d8d6c072 100644
--- a/compiler/rustc_lint/src/early.rs
+++ b/compiler/rustc_lint/src/early.rs
@@ -341,7 +341,7 @@ pub trait EarlyCheckNode<'a>: Copy {
         'a: 'b;
 }
 
-impl<'a> EarlyCheckNode<'a> for &'a ast::Crate {
+impl<'a> EarlyCheckNode<'a> for (&'a ast::Crate, &'a [ast::Attribute]) {
     fn id(self) -> ast::NodeId {
         ast::CRATE_NODE_ID
     }
@@ -349,15 +349,15 @@ impl<'a> EarlyCheckNode<'a> for &'a ast::Crate {
     where
         'a: 'b,
     {
-        &self.attrs
+        &self.1
     }
     fn check<'b, T: EarlyLintPass>(self, cx: &mut EarlyContextAndPass<'b, T>)
     where
         'a: 'b,
     {
-        lint_callback!(cx, check_crate, self);
-        ast_visit::walk_crate(cx, self);
-        lint_callback!(cx, check_crate_post, self);
+        lint_callback!(cx, check_crate, self.0);
+        ast_visit::walk_crate(cx, self.0);
+        lint_callback!(cx, check_crate_post, self.0);
     }
 }
 
diff --git a/compiler/rustc_middle/src/arena.rs b/compiler/rustc_middle/src/arena.rs
index 72907fba5e6..9f16ecbdaa9 100644
--- a/compiler/rustc_middle/src/arena.rs
+++ b/compiler/rustc_middle/src/arena.rs
@@ -36,7 +36,7 @@ macro_rules! arena_types {
             )>,
             [] output_filenames: std::sync::Arc<rustc_session::config::OutputFilenames>,
             [] metadata_loader: rustc_data_structures::steal::Steal<Box<rustc_session::cstore::MetadataLoaderDyn>>,
-            [] crate_for_resolver: rustc_data_structures::steal::Steal<rustc_ast::ast::Crate>,
+            [] crate_for_resolver: rustc_data_structures::steal::Steal<(rustc_ast::Crate, rustc_ast::AttrVec)>,
             [] resolutions: rustc_middle::ty::ResolverGlobalCtxt,
             [decode] unsafety_check_result: rustc_middle::mir::UnsafetyCheckResult,
             [decode] code_region: rustc_middle::mir::coverage::CodeRegion,
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index f740ec51080..9203dd59a7e 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -2116,7 +2116,7 @@ rustc_queries! {
         desc { "raw operations for metadata file access" }
     }
 
-    query crate_for_resolver((): ()) -> &'tcx Steal<rustc_ast::ast::Crate> {
+    query crate_for_resolver((): ()) -> &'tcx Steal<(rustc_ast::Crate, rustc_ast::AttrVec)> {
         feedable
         no_hash
         desc { "the ast before macro expansion and name resolution" }
diff --git a/compiler/rustc_plugin_impl/src/load.rs b/compiler/rustc_plugin_impl/src/load.rs
index 8e75e969ae0..27e5cb9f0d0 100644
--- a/compiler/rustc_plugin_impl/src/load.rs
+++ b/compiler/rustc_plugin_impl/src/load.rs
@@ -3,7 +3,7 @@
 use crate::errors::{LoadPluginError, MalformedPluginAttribute};
 use crate::Registry;
 use libloading::Library;
-use rustc_ast::Crate;
+use rustc_ast::Attribute;
 use rustc_metadata::locator;
 use rustc_session::cstore::MetadataLoader;
 use rustc_session::Session;
@@ -20,11 +20,11 @@ type PluginRegistrarFn = fn(&mut Registry<'_>);
 pub fn load_plugins(
     sess: &Session,
     metadata_loader: &dyn MetadataLoader,
-    krate: &Crate,
+    attrs: &[Attribute],
 ) -> Vec<PluginRegistrarFn> {
     let mut plugins = Vec::new();
 
-    for attr in &krate.attrs {
+    for attr in attrs {
         if !attr.has_name(sym::plugin) {
             continue;
         }
diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs
index 0a99295b59a..aa50699890c 100644
--- a/compiler/rustc_resolve/src/lib.rs
+++ b/compiler/rustc_resolve/src/lib.rs
@@ -1180,7 +1180,8 @@ impl<'tcx> Resolver<'_, 'tcx> {
 impl<'a, 'tcx> Resolver<'a, 'tcx> {
     pub fn new(
         tcx: TyCtxt<'tcx>,
-        krate: &Crate,
+        attrs: &[ast::Attribute],
+        crate_span: Span,
         arenas: &'a ResolverArenas<'a>,
     ) -> Resolver<'a, 'tcx> {
         let root_def_id = CRATE_DEF_ID.to_def_id();
@@ -1189,8 +1190,8 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
             None,
             ModuleKind::Def(DefKind::Mod, root_def_id, kw::Empty),
             ExpnId::root(),
-            krate.spans.inner_span,
-            attr::contains_name(&krate.attrs, sym::no_implicit_prelude),
+            crate_span,
+            attr::contains_name(attrs, sym::no_implicit_prelude),
             &mut module_map,
         );
         let empty_module = arenas.new_module(
@@ -1222,9 +1223,9 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> {
             .map(|(name, _)| (Ident::from_str(name), Default::default()))
             .collect();
 
-        if !attr::contains_name(&krate.attrs, sym::no_core) {
+        if !attr::contains_name(attrs, sym::no_core) {
             extern_prelude.insert(Ident::with_dummy_span(sym::core), Default::default());
-            if !attr::contains_name(&krate.attrs, sym::no_std) {
+            if !attr::contains_name(attrs, sym::no_std) {
                 extern_prelude.insert(Ident::with_dummy_span(sym::std), Default::default());
             }
         }
diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs
index c540682d8db..48707d37a10 100644
--- a/compiler/rustc_resolve/src/macros.rs
+++ b/compiler/rustc_resolve/src/macros.rs
@@ -112,8 +112,8 @@ fn fast_print_path(path: &ast::Path) -> Symbol {
 
 pub(crate) fn registered_tools(tcx: TyCtxt<'_>, (): ()) -> RegisteredTools {
     let mut registered_tools = RegisteredTools::default();
-    let krate = tcx.crate_for_resolver(()).borrow();
-    for attr in attr::filter_by_name(&krate.attrs, sym::register_tool) {
+    let (_, pre_configured_attrs) = &*tcx.crate_for_resolver(()).borrow();
+    for attr in attr::filter_by_name(pre_configured_attrs, sym::register_tool) {
         for nested_meta in attr.meta_item_list().unwrap_or_default() {
             match nested_meta.ident() {
                 Some(ident) => {
diff --git a/tests/ui/cfg/auxiliary/cfg_false_lib.rs b/tests/ui/cfg/auxiliary/cfg_false_lib.rs
new file mode 100644
index 00000000000..3c011d72b02
--- /dev/null
+++ b/tests/ui/cfg/auxiliary/cfg_false_lib.rs
@@ -0,0 +1,6 @@
+// It is unclear whether a fully unconfigured crate should link to standard library,
+// or what its `no_std`/`no_core`/`compiler_builtins` status, more precisely.
+// Currently the usual standard library prelude is added to such crates,
+// and therefore they link to libstd.
+
+#![cfg(FALSE)]
diff --git a/tests/ui/cfg/cfg-false-feature.rs b/tests/ui/cfg/cfg-false-feature.rs
new file mode 100644
index 00000000000..21ea3ec79b4
--- /dev/null
+++ b/tests/ui/cfg/cfg-false-feature.rs
@@ -0,0 +1,20 @@
+// It is unclear which features should be in effect in a fully unconfigured crate (issue #104633).
+// Currently none on the features are in effect, so we get the feature gates reported.
+
+// check-pass
+// compile-flags: --crate-type lib
+
+#![feature(decl_macro)]
+#![cfg(FALSE)]
+#![feature(box_syntax)]
+
+macro mac() {} //~ WARN `macro` is experimental
+               //~| WARN unstable syntax can change at any point in the future
+
+trait A = Clone; //~ WARN trait aliases are experimental
+                 //~| WARN unstable syntax can change at any point in the future
+
+fn main() {
+    let box _ = Box::new(0); //~ WARN box pattern syntax is experimental
+                             //~| WARN unstable syntax can change at any point in the future
+}
diff --git a/tests/ui/cfg/cfg-false-feature.stderr b/tests/ui/cfg/cfg-false-feature.stderr
new file mode 100644
index 00000000000..14673fbdb14
--- /dev/null
+++ b/tests/ui/cfg/cfg-false-feature.stderr
@@ -0,0 +1,35 @@
+warning: trait aliases are experimental
+  --> $DIR/cfg-false-feature.rs:14:1
+   |
+LL | trait A = Clone;
+   | ^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #41517 <https://github.com/rust-lang/rust/issues/41517> for more information
+   = help: add `#![feature(trait_alias)]` to the crate attributes to enable
+   = warning: unstable syntax can change at any point in the future, causing a hard error!
+   = note: for more information, see issue #65860 <https://github.com/rust-lang/rust/issues/65860>
+
+warning: `macro` is experimental
+  --> $DIR/cfg-false-feature.rs:11:1
+   |
+LL | macro mac() {}
+   | ^^^^^^^^^^^^^^
+   |
+   = note: see issue #39412 <https://github.com/rust-lang/rust/issues/39412> for more information
+   = help: add `#![feature(decl_macro)]` to the crate attributes to enable
+   = warning: unstable syntax can change at any point in the future, causing a hard error!
+   = note: for more information, see issue #65860 <https://github.com/rust-lang/rust/issues/65860>
+
+warning: box pattern syntax is experimental
+  --> $DIR/cfg-false-feature.rs:18:9
+   |
+LL |     let box _ = Box::new(0);
+   |         ^^^^^
+   |
+   = note: see issue #29641 <https://github.com/rust-lang/rust/issues/29641> for more information
+   = help: add `#![feature(box_patterns)]` to the crate attributes to enable
+   = warning: unstable syntax can change at any point in the future, causing a hard error!
+   = note: for more information, see issue #65860 <https://github.com/rust-lang/rust/issues/65860>
+
+warning: 3 warnings emitted
+
diff --git a/tests/ui/cfg/cfg_false_no_std.rs b/tests/ui/cfg/cfg_false_no_std.rs
new file mode 100644
index 00000000000..319ea078187
--- /dev/null
+++ b/tests/ui/cfg/cfg_false_no_std.rs
@@ -0,0 +1,11 @@
+// Currently no error because the panic handler is supplied by libstd linked though the empty
+// library, but the desirable behavior is unclear (see comments in cfg_false_lib.rs).
+
+// check-pass
+// aux-build: cfg_false_lib.rs
+
+#![no_std]
+
+extern crate cfg_false_lib as _;
+
+fn main() {}