diff --git a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
index 8808ad2dcd135..451ef186fea73 100644
--- a/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
+++ b/compiler/rustc_codegen_ssa/src/codegen_attrs.rs
@@ -268,7 +268,7 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: DefId) -> CodegenFnAttrs {
             if !tcx.is_closure(did.to_def_id())
                 && tcx.fn_sig(did).unsafety() == hir::Unsafety::Normal
             {
-                if tcx.sess.target.is_like_wasm || tcx.sess.opts.actually_rustdoc {
+                if tcx.sess.target.is_like_wasm {
                     // The `#[target_feature]` attribute is allowed on
                     // WebAssembly targets on all functions, including safe
                     // ones. Other targets require that `#[target_feature]` is
@@ -282,10 +282,6 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, did: DefId) -> CodegenFnAttrs {
                     // deterministic trap. There is no undefined behavior when
                     // executing WebAssembly so `#[target_feature]` is allowed
                     // on safe functions (but again, only for WebAssembly)
-                    //
-                    // Note that this is also allowed if `actually_rustdoc` so
-                    // if a target is documenting some wasm-specific code then
-                    // it's not spuriously denied.
                 } else if !tcx.features().target_feature_11 {
                     let mut err = feature_err(
                         &tcx.sess.parse_sess,
diff --git a/compiler/rustc_codegen_ssa/src/target_features.rs b/compiler/rustc_codegen_ssa/src/target_features.rs
index 739963fffd132..67e2751b0e4a6 100644
--- a/compiler/rustc_codegen_ssa/src/target_features.rs
+++ b/compiler/rustc_codegen_ssa/src/target_features.rs
@@ -293,24 +293,6 @@ const WASM_ALLOWED_FEATURES: &[(&str, Option<Symbol>)] = &[
 
 const BPF_ALLOWED_FEATURES: &[(&str, Option<Symbol>)] = &[("alu32", Some(sym::bpf_target_feature))];
 
-/// When rustdoc is running, provide a list of all known features so that all their respective
-/// primitives may be documented.
-///
-/// IMPORTANT: If you're adding another feature list above, make sure to add it to this iterator!
-pub fn all_known_features() -> impl Iterator<Item = (&'static str, Option<Symbol>)> {
-    std::iter::empty()
-        .chain(ARM_ALLOWED_FEATURES.iter())
-        .chain(AARCH64_ALLOWED_FEATURES.iter())
-        .chain(X86_ALLOWED_FEATURES.iter())
-        .chain(HEXAGON_ALLOWED_FEATURES.iter())
-        .chain(POWERPC_ALLOWED_FEATURES.iter())
-        .chain(MIPS_ALLOWED_FEATURES.iter())
-        .chain(RISCV_ALLOWED_FEATURES.iter())
-        .chain(WASM_ALLOWED_FEATURES.iter())
-        .chain(BPF_ALLOWED_FEATURES.iter())
-        .cloned()
-}
-
 pub fn supported_target_features(sess: &Session) -> &'static [(&'static str, Option<Symbol>)] {
     match &*sess.target.arch {
         "arm" => ARM_ALLOWED_FEATURES,
@@ -462,16 +444,7 @@ pub(crate) fn provide(providers: &mut Providers) {
     *providers = Providers {
         supported_target_features: |tcx, cnum| {
             assert_eq!(cnum, LOCAL_CRATE);
-            if tcx.sess.opts.actually_rustdoc {
-                // rustdoc needs to be able to document functions that use all the features, so
-                // whitelist them all
-                all_known_features().map(|(a, b)| (a.to_string(), b)).collect()
-            } else {
-                supported_target_features(tcx.sess)
-                    .iter()
-                    .map(|&(a, b)| (a.to_string(), b))
-                    .collect()
-            }
+            supported_target_features(tcx.sess).iter().map(|&(a, b)| (a.to_string(), b)).collect()
         },
         asm_target_features,
         ..*providers
diff --git a/compiler/rustc_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs
index 535812fb0e228..f3229dfa33ae2 100644
--- a/compiler/rustc_errors/src/lib.rs
+++ b/compiler/rustc_errors/src/lib.rs
@@ -511,7 +511,7 @@ impl Drop for HandlerInner {
         self.emit_stashed_diagnostics();
 
         if !self.has_errors() {
-            let bugs = std::mem::replace(&mut self.delayed_span_bugs, Vec::new());
+            let bugs = std::mem::take(&mut self.delayed_span_bugs);
             self.flush_delayed(bugs, "no errors encountered even though `delay_span_bug` issued");
         }
 
@@ -521,7 +521,7 @@ impl Drop for HandlerInner {
         // lints can be `#[allow]`'d, potentially leading to this triggering.
         // Also, "good path" should be replaced with a better naming.
         if !self.has_any_message() && !self.suppressed_expected_diag {
-            let bugs = std::mem::replace(&mut self.delayed_good_path_bugs, Vec::new());
+            let bugs = std::mem::take(&mut self.delayed_good_path_bugs);
             self.flush_delayed(
                 bugs,
                 "no warnings or errors encountered even though `delayed_good_path_bugs` issued",
@@ -661,6 +661,19 @@ impl Handler {
         inner.stashed_diagnostics = Default::default();
     }
 
+    /// Avoid failing compilation in the presence of delayed bugs.
+    ///
+    /// NOTE: *do not* call this function from rustc. It is only meant to be called from rustdoc.
+    /// Rustdoc documents multiple targets at once, meaning there may be multiple versions
+    /// of the same function in scope at the same time, which isn't legal Rust otherwise. See
+    /// <https://doc.rust-lang.org/beta/rustdoc/advanced-features.html#interactions-between-platform-specific-docs>
+    /// for details
+    pub fn reset_delayed_bugs(&self) {
+        let mut inner = self.inner.borrow_mut();
+        inner.delayed_span_bugs = Default::default();
+        inner.delayed_good_path_bugs = Default::default();
+    }
+
     /// Stash a given diagnostic with the given `Span` and [`StashKey`] as the key.
     /// Retrieve a stashed diagnostic with `steal_diagnostic`.
     pub fn stash_diagnostic(&self, span: Span, key: StashKey, diag: Diagnostic) {
@@ -1231,7 +1244,7 @@ impl Handler {
 
     pub fn flush_delayed(&self) {
         let mut inner = self.inner.lock();
-        let bugs = std::mem::replace(&mut inner.delayed_span_bugs, Vec::new());
+        let bugs = std::mem::take(&mut inner.delayed_span_bugs);
         inner.flush_delayed(bugs, "no errors encountered even though `delay_span_bug` issued");
     }
 }
diff --git a/compiler/rustc_hir_analysis/src/check/check.rs b/compiler/rustc_hir_analysis/src/check/check.rs
index abc1c2d7b8d17..31cea96abef26 100644
--- a/compiler/rustc_hir_analysis/src/check/check.rs
+++ b/compiler/rustc_hir_analysis/src/check/check.rs
@@ -219,14 +219,6 @@ fn check_opaque(tcx: TyCtxt<'_>, id: hir::ItemId) {
         return;
     };
 
-    // HACK(jynelson): trying to infer the type of `impl trait` breaks documenting
-    // `async-std` (and `pub async fn` in general).
-    // Since rustdoc doesn't care about the concrete type behind `impl Trait`, just don't look at it!
-    // See https://github.com/rust-lang/rust/issues/75100
-    if tcx.sess.opts.actually_rustdoc {
-        return;
-    }
-
     let substs = InternalSubsts::identity_for_item(tcx, item.owner_id.to_def_id());
     let span = tcx.def_span(item.owner_id.def_id);
 
diff --git a/compiler/rustc_privacy/src/lib.rs b/compiler/rustc_privacy/src/lib.rs
index 9a5d3cceb914e..85618745331b5 100644
--- a/compiler/rustc_privacy/src/lib.rs
+++ b/compiler/rustc_privacy/src/lib.rs
@@ -692,11 +692,7 @@ impl<'tcx> Visitor<'tcx> for EmbargoVisitor<'tcx> {
             // The interface is empty.
             hir::ItemKind::GlobalAsm(..) => {}
             hir::ItemKind::OpaqueTy(ref opaque) => {
-                // HACK(jynelson): trying to infer the type of `impl trait` breaks `async-std` (and `pub async fn` in general)
-                // Since rustdoc never needs to do codegen and doesn't care about link-time reachability,
-                // mark this as unreachable.
-                // See https://github.com/rust-lang/rust/issues/75100
-                if !opaque.in_trait && !self.tcx.sess.opts.actually_rustdoc {
+                if !opaque.in_trait {
                     // FIXME: This is some serious pessimization intended to workaround deficiencies
                     // in the reachability pass (`middle/reachable.rs`). Types are marked as link-time
                     // reachable if they are returned via `impl Trait`, even from private functions.
diff --git a/compiler/rustc_trait_selection/src/traits/query/normalize.rs b/compiler/rustc_trait_selection/src/traits/query/normalize.rs
index 27247271d1f4d..756668c4870cb 100644
--- a/compiler/rustc_trait_selection/src/traits/query/normalize.rs
+++ b/compiler/rustc_trait_selection/src/traits/query/normalize.rs
@@ -262,14 +262,10 @@ impl<'cx, 'tcx> FallibleTypeFolder<'tcx> for QueryNormalizer<'cx, 'tcx> {
                 let result = tcx.normalize_projection_ty(c_data)?;
                 // We don't expect ambiguity.
                 if result.is_ambiguous() {
-                    // Rustdoc normalizes possibly not well-formed types, so only
-                    // treat this as a bug if we're not in rustdoc.
-                    if !tcx.sess.opts.actually_rustdoc {
-                        tcx.sess.delay_span_bug(
-                            DUMMY_SP,
-                            format!("unexpected ambiguity: {:?} {:?}", c_data, result),
-                        );
-                    }
+                    tcx.sess.delay_span_bug(
+                        DUMMY_SP,
+                        format!("unexpected ambiguity: {:?} {:?}", c_data, result),
+                    );
                     return Err(NoSolution);
                 }
                 let InferOk { value: result, obligations } =
@@ -313,14 +309,10 @@ impl<'cx, 'tcx> FallibleTypeFolder<'tcx> for QueryNormalizer<'cx, 'tcx> {
                 let result = tcx.normalize_projection_ty(c_data)?;
                 // We don't expect ambiguity.
                 if result.is_ambiguous() {
-                    // Rustdoc normalizes possibly not well-formed types, so only
-                    // treat this as a bug if we're not in rustdoc.
-                    if !tcx.sess.opts.actually_rustdoc {
-                        tcx.sess.delay_span_bug(
-                            DUMMY_SP,
-                            format!("unexpected ambiguity: {:?} {:?}", c_data, result),
-                        );
-                    }
+                    tcx.sess.delay_span_bug(
+                        DUMMY_SP,
+                        format!("unexpected ambiguity: {:?} {:?}", c_data, result),
+                    );
                     return Err(NoSolution);
                 }
                 let InferOk { value: result, obligations } =
diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs
index 10b606f425ea4..5db0fd8db3b61 100644
--- a/src/librustdoc/core.rs
+++ b/src/librustdoc/core.rs
@@ -6,11 +6,12 @@ use rustc_errors::emitter::{Emitter, EmitterWriter};
 use rustc_errors::json::JsonEmitter;
 use rustc_feature::UnstableFeatures;
 use rustc_hir::def::{Namespace, Res};
-use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet, LocalDefId};
+use rustc_hir::def_id::{DefId, DefIdMap, DefIdSet};
 use rustc_hir::intravisit::{self, Visitor};
 use rustc_hir::{HirId, Path, TraitCandidate};
 use rustc_interface::interface;
 use rustc_middle::hir::nested_filter;
+use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs;
 use rustc_middle::ty::{ParamEnv, Ty, TyCtxt};
 use rustc_resolve as resolve;
 use rustc_session::config::{self, CrateType, ErrorOutputType};
@@ -22,7 +23,6 @@ use rustc_span::{source_map, Span, Symbol};
 use std::cell::RefCell;
 use std::mem;
 use std::rc::Rc;
-use std::sync::LazyLock;
 
 use crate::clean::inline::build_external_trait;
 use crate::clean::{self, ItemId};
@@ -283,11 +283,10 @@ pub(crate) fn create_config(
             providers.lint_mod = |_, _| {};
             // Prevent `rustc_hir_analysis::check_crate` from calling `typeck` on all bodies.
             providers.typeck_item_bodies = |_, _| {};
+            providers.check_mod_item_types =
+                |_, _| panic!("check_mod_item_types should not get called at all in rustdoc");
             // hack so that `used_trait_imports` won't try to call typeck
-            providers.used_trait_imports = |_, _| {
-                static EMPTY_SET: LazyLock<UnordSet<LocalDefId>> = LazyLock::new(UnordSet::default);
-                &EMPTY_SET
-            };
+            providers.used_trait_imports = |_, _| Box::leak(Box::new(UnordSet::default()));
             // In case typeck does end up being called, don't ICE in case there were name resolution errors
             providers.typeck = move |tcx, def_id| {
                 // Closures' tables come from their outermost function,
@@ -304,6 +303,27 @@ pub(crate) fn create_config(
                 EmitIgnoredResolutionErrors::new(tcx).visit_body(body);
                 (rustc_interface::DEFAULT_QUERY_PROVIDERS.typeck)(tcx, def_id)
             };
+            providers.type_of = |tcx, def_id| {
+                let did = def_id.expect_local();
+                use rustc_hir::*;
+
+                let hir_id = tcx.hir().local_def_id_to_hir_id(did);
+
+                match tcx.hir().get(hir_id) {
+                    Node::Item(item) => match item.kind {
+                        ItemKind::OpaqueTy(_) => return tcx.ty_error(),
+                        _ => {}
+                    },
+                    _ => {}
+                }
+                (rustc_interface::DEFAULT_QUERY_PROVIDERS.type_of)(tcx, def_id)
+            };
+            providers.codegen_fn_attrs = |_, did| {
+                assert!(did.is_local());
+                CodegenFnAttrs::new()
+            };
+            providers.supported_target_features =
+                |_, _| panic!("supported_target_features should not get called by rustdoc");
         }),
         make_codegen_backend: None,
         registry: rustc_driver::diagnostics_registry(),
@@ -326,14 +346,8 @@ pub(crate) fn run_global_ctxt(
     // typeck function bodies or run the default rustc lints.
     // (see `override_queries` in the `config`)
 
-    // HACK(jynelson) this calls an _extremely_ limited subset of `typeck`
+    // HACK(jynelson) this calls an _extremely_ limited subset of what the `analysis` query does
     // and might break if queries change their assumptions in the future.
-
-    // NOTE: This is copy/pasted from typeck/lib.rs and should be kept in sync with those changes.
-    tcx.sess.time("item_types_checking", || {
-        tcx.hir().for_each_module(|module| tcx.ensure().check_mod_item_types(module))
-    });
-    tcx.sess.abort_if_errors();
     tcx.sess.time("missing_docs", || {
         rustc_lint::check_crate(tcx, rustc_lint::builtin::MissingDoc::new);
     });
diff --git a/src/librustdoc/lib.rs b/src/librustdoc/lib.rs
index 86454e1f2eb73..460c47207bafc 100644
--- a/src/librustdoc/lib.rs
+++ b/src/librustdoc/lib.rs
@@ -862,6 +862,12 @@ fn main_args(at_args: &[String]) -> MainResult {
                     }),
                 }
             })
-        })
+        })?;
+        // than ICEing. Rustdoc documents multiple targets at once, meaning there may be multiple versions
+        // of the same function in scope at the same time, which isn't legal Rust otherwise. See
+        // https://doc.rust-lang.org/beta/rustdoc/advanced-features.html#interactions-between-platform-specific-docs
+        // for details
+        sess.diagnostic().reset_delayed_bugs();
+        Ok(())
     })
 }
diff --git a/tests/rustdoc-ui/issue-79494.rs b/tests/rustdoc-ui/issue-79494.rs
index fc39424b793f6..bcce46778469a 100644
--- a/tests/rustdoc-ui/issue-79494.rs
+++ b/tests/rustdoc-ui/issue-79494.rs
@@ -1,5 +1,6 @@
 // only-x86_64-unknown-linux-gnu
+// check-pass
 
 #![feature(const_transmute)]
 
-const ZST: &[u8] = unsafe { std::mem::transmute(1usize) }; //~ ERROR cannot transmute between types of different sizes, or dependently-sized types
+const ZST: &[u8] = unsafe { std::mem::transmute(1usize) };
diff --git a/tests/rustdoc-ui/issue-79494.stderr b/tests/rustdoc-ui/issue-79494.stderr
deleted file mode 100644
index 7ed5ed3824716..0000000000000
--- a/tests/rustdoc-ui/issue-79494.stderr
+++ /dev/null
@@ -1,12 +0,0 @@
-error[E0512]: cannot transmute between types of different sizes, or dependently-sized types
-  --> $DIR/issue-79494.rs:5:29
-   |
-LL | const ZST: &[u8] = unsafe { std::mem::transmute(1usize) };
-   |                             ^^^^^^^^^^^^^^^^^^^
-   |
-   = note: source type: `usize` (64 bits)
-   = note: target type: `&[u8]` (128 bits)
-
-error: aborting due to previous error
-
-For more information about this error, try `rustc --explain E0512`.
diff --git a/tests/rustdoc/issue-96381.rs b/tests/rustdoc-ui/issue-96381.rs
similarity index 62%
rename from tests/rustdoc/issue-96381.rs
rename to tests/rustdoc-ui/issue-96381.rs
index f0f123f85a03f..a36acf472ce97 100644
--- a/tests/rustdoc/issue-96381.rs
+++ b/tests/rustdoc-ui/issue-96381.rs
@@ -1,14 +1,13 @@
-// should-fail
-
 #![allow(unused)]
 
+// check-pass
+
 trait Foo<T>: Sized {
     fn bar(i: i32, t: T, s: &Self) -> (T, i32);
 }
 
 impl Foo<usize> for () {
     fn bar(i: _, t: _, s: _) -> _ {
-        //~^ ERROR the placeholder `_` is not allowed within types on item signatures for functions
         (1, 2)
     }
 }
diff --git a/tests/rustdoc-ui/track-diagnostics.rs b/tests/rustdoc-ui/track-diagnostics.rs
index fcc50a7aba07d..5736d7d4f18f7 100644
--- a/tests/rustdoc-ui/track-diagnostics.rs
+++ b/tests/rustdoc-ui/track-diagnostics.rs
@@ -1,9 +1,5 @@
 // compile-flags: -Z track-diagnostics
-// error-pattern: created at
-
-// Normalize the emitted location so this doesn't need
-// updating everytime someone adds or removes a line.
-// normalize-stderr-test ".rs:\d+:\d+" -> ".rs:LL:CC"
+// check-pass
 
 struct A;
 struct B;
diff --git a/tests/rustdoc-ui/track-diagnostics.stderr b/tests/rustdoc-ui/track-diagnostics.stderr
deleted file mode 100644
index ec30318625311..0000000000000
--- a/tests/rustdoc-ui/track-diagnostics.stderr
+++ /dev/null
@@ -1,10 +0,0 @@
-error[E0308]: mismatched types
-  --> $DIR/track-diagnostics.rs:LL:CC
-   |
-LL | const S: A = B;
-   |              ^ expected struct `A`, found struct `B`
--Ztrack-diagnostics: created at compiler/rustc_infer/src/infer/error_reporting/mod.rs:LL:CC
-
-error: aborting due to previous error
-
-For more information about this error, try `rustc --explain E0308`.