From 888d0b4c9691ffcba7cb326a1cd1fc73feb38f57 Mon Sep 17 00:00:00 2001
From: Rich Kadel <richkadel@google.com>
Date: Sun, 25 Apr 2021 13:34:03 -0700
Subject: [PATCH 1/2] Derived Eq no longer shows uncovered

The Eq trait has a special hidden function. MIR `InstrumentCoverage`
would add this function to the coverage map, but it is never called, so
the `Eq` trait would always appear uncovered.

Fixes: #83601

The fix required creating a new function attribute `no_coverage` to mark
functions that should be ignored by `InstrumentCoverage` and the
coverage `mapgen` (during codegen).

While testing, I also noticed two other issues:

* spanview debug file output ICEd on a function with no body. The
workaround for this is included in this PR.
* `assert_*!()` macro coverage can appear covered if followed by another
`assert_*!()` macro. Normally they appear uncovered. I submitted a new
Issue #84561, and added a coverage test to demonstrate this issue.
---
 .../src/deriving/cmp/eq.rs                    |  3 +-
 .../src/coverageinfo/mapgen.rs                |  5 +++
 compiler/rustc_feature/src/builtin_attrs.rs   |  1 +
 .../src/middle/codegen_fn_attrs.rs            |  4 +++
 .../rustc_mir/src/transform/coverage/mod.rs   |  6 ++++
 compiler/rustc_span/src/symbol.rs             |  1 +
 compiler/rustc_typeck/src/collect.rs          |  2 ++
 library/core/src/cmp.rs                       |  1 +
 .../expected_show_coverage.issue-83601.txt    | 22 ++++++++++++
 .../expected_show_coverage.issue-84561.txt    | 34 +++++++++++++++++++
 .../expected_show_coverage.partial_eq.txt     |  2 +-
 .../run-make-fulldeps/coverage/issue-83601.rs | 14 ++++++++
 .../run-make-fulldeps/coverage/issue-84561.rs | 26 ++++++++++++++
 13 files changed, 119 insertions(+), 2 deletions(-)
 create mode 100644 src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.issue-83601.txt
 create mode 100644 src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.issue-84561.txt
 create mode 100644 src/test/run-make-fulldeps/coverage/issue-83601.rs
 create mode 100644 src/test/run-make-fulldeps/coverage/issue-84561.rs

diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs
index 79f35ad5819f1..53e9681b437fb 100644
--- a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs
@@ -16,9 +16,10 @@ pub fn expand_deriving_eq(
     push: &mut dyn FnMut(Annotatable),
 ) {
     let inline = cx.meta_word(span, sym::inline);
+    let no_coverage = cx.meta_word(span, sym::no_coverage);
     let hidden = rustc_ast::attr::mk_nested_word_item(Ident::new(sym::hidden, span));
     let doc = rustc_ast::attr::mk_list_item(Ident::new(sym::doc, span), vec![hidden]);
-    let attrs = vec![cx.attribute(inline), cx.attribute(doc)];
+    let attrs = vec![cx.attribute(inline), cx.attribute(no_coverage), cx.attribute(doc)];
     let trait_def = TraitDef {
         span,
         attributes: Vec::new(),
diff --git a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs
index 2ac814bf22838..1faaa7e86f619 100644
--- a/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs
+++ b/compiler/rustc_codegen_llvm/src/coverageinfo/mapgen.rs
@@ -8,6 +8,7 @@ use rustc_codegen_ssa::traits::{ConstMethods, CoverageInfoMethods};
 use rustc_data_structures::fx::{FxHashMap, FxHashSet, FxIndexSet};
 use rustc_hir::def_id::{DefId, DefIdSet, LOCAL_CRATE};
 use rustc_llvm::RustString;
+use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
 use rustc_middle::mir::coverage::CodeRegion;
 use rustc_span::Symbol;
 
@@ -280,6 +281,10 @@ fn add_unused_functions<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>) {
 
     let mut unused_def_ids_by_file: FxHashMap<Symbol, Vec<DefId>> = FxHashMap::default();
     for &non_codegenned_def_id in all_def_ids.difference(codegenned_def_ids) {
+        let codegen_fn_attrs = tcx.codegen_fn_attrs(non_codegenned_def_id);
+        if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_COVERAGE) {
+            continue;
+        }
         // Make sure the non-codegenned (unused) function has a file_name
         if let Some(non_codegenned_file_name) = tcx.covered_file_name(non_codegenned_def_id) {
             let def_ids =
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index b8a0b8debcd31..b35072933d96d 100644
--- a/compiler/rustc_feature/src/builtin_attrs.rs
+++ b/compiler/rustc_feature/src/builtin_attrs.rs
@@ -264,6 +264,7 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
 
     // Code generation:
     ungated!(inline, AssumedUsed, template!(Word, List: "always|never")),
+    ungated!(no_coverage, AssumedUsed, template!(Word)),
     ungated!(cold, AssumedUsed, template!(Word)),
     ungated!(no_builtins, AssumedUsed, template!(Word)),
     ungated!(target_feature, AssumedUsed, template!(List: r#"enable = "name""#)),
diff --git a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs
index 7024d9a3d21db..93e7aeaffce37 100644
--- a/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs
+++ b/compiler/rustc_middle/src/middle/codegen_fn_attrs.rs
@@ -89,6 +89,10 @@ bitflags! {
         /// #[cmse_nonsecure_entry]: with a TrustZone-M extension, declare a
         /// function as an entry function from Non-Secure code.
         const CMSE_NONSECURE_ENTRY      = 1 << 14;
+        /// `#[no_coverage]`: indicates that the function should be ignored by
+        /// the MIR `InstrumentCoverage` pass and not added to the coverage map
+        /// during codegen.
+        const NO_COVERAGE               = 1 << 15;
     }
 }
 
diff --git a/compiler/rustc_mir/src/transform/coverage/mod.rs b/compiler/rustc_mir/src/transform/coverage/mod.rs
index 3ef03ec21addd..eaeb44289cfb2 100644
--- a/compiler/rustc_mir/src/transform/coverage/mod.rs
+++ b/compiler/rustc_mir/src/transform/coverage/mod.rs
@@ -23,6 +23,7 @@ use rustc_index::vec::IndexVec;
 use rustc_middle::hir;
 use rustc_middle::hir::map::blocks::FnLikeNode;
 use rustc_middle::ich::StableHashingContext;
+use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags;
 use rustc_middle::mir::coverage::*;
 use rustc_middle::mir::{
     self, BasicBlock, BasicBlockData, Coverage, SourceInfo, Statement, StatementKind, Terminator,
@@ -87,6 +88,11 @@ impl<'tcx> MirPass<'tcx> for InstrumentCoverage {
             _ => {}
         }
 
+        let codegen_fn_attrs = tcx.codegen_fn_attrs(mir_source.def_id());
+        if codegen_fn_attrs.flags.contains(CodegenFnAttrFlags::NO_COVERAGE) {
+            return;
+        }
+
         trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());
         Instrumentor::new(&self.name(), tcx, mir_body).inject_counters();
         trace!("InstrumentCoverage starting for {:?}", mir_source.def_id());
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index 4be187c5208cd..cb6c72b1317b2 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -781,6 +781,7 @@ symbols! {
         no,
         no_builtins,
         no_core,
+        no_coverage,
         no_crate_inject,
         no_debug,
         no_default_passes,
diff --git a/compiler/rustc_typeck/src/collect.rs b/compiler/rustc_typeck/src/collect.rs
index 46ee82454326a..0528f8812f920 100644
--- a/compiler/rustc_typeck/src/collect.rs
+++ b/compiler/rustc_typeck/src/collect.rs
@@ -2724,6 +2724,8 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
             codegen_fn_attrs.flags |= CodegenFnAttrFlags::NAKED;
         } else if tcx.sess.check_name(attr, sym::no_mangle) {
             codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_MANGLE;
+        } else if tcx.sess.check_name(attr, sym::no_coverage) {
+            codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_COVERAGE;
         } else if tcx.sess.check_name(attr, sym::rustc_std_internal_symbol) {
             codegen_fn_attrs.flags |= CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL;
         } else if tcx.sess.check_name(attr, sym::used) {
diff --git a/library/core/src/cmp.rs b/library/core/src/cmp.rs
index cdb6006b1b354..1f7ae7e3773c4 100644
--- a/library/core/src/cmp.rs
+++ b/library/core/src/cmp.rs
@@ -274,6 +274,7 @@ pub trait Eq: PartialEq<Self> {
     //
     // This should never be implemented by hand.
     #[doc(hidden)]
+    #[cfg_attr(not(bootstrap), no_coverage)]
     #[inline]
     #[stable(feature = "rust1", since = "1.0.0")]
     fn assert_receiver_is_total_eq(&self) {}
diff --git a/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.issue-83601.txt b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.issue-83601.txt
new file mode 100644
index 0000000000000..46f3add9427b6
--- /dev/null
+++ b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.issue-83601.txt
@@ -0,0 +1,22 @@
+    1|       |// Shows that rust-lang/rust/83601 is resolved
+    2|       |
+    3|      3|#[derive(Debug, PartialEq, Eq)]
+                              ^2
+  ------------------
+  | <issue_83601::Foo as core::cmp::PartialEq>::eq:
+  |    3|      2|#[derive(Debug, PartialEq, Eq)]
+  ------------------
+  | Unexecuted instantiation: <issue_83601::Foo as core::cmp::PartialEq>::ne
+  ------------------
+    4|       |struct Foo(u32);
+    5|       |
+    6|      1|fn main() {
+    7|      1|    let bar = Foo(1);
+    8|      0|    assert_eq!(bar, Foo(1));
+    9|      1|    let baz = Foo(0);
+   10|      0|    assert_ne!(baz, Foo(1));
+   11|      1|    println!("{:?}", Foo(1));
+   12|      1|    println!("{:?}", bar);
+   13|      1|    println!("{:?}", baz);
+   14|      1|}
+
diff --git a/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.issue-84561.txt b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.issue-84561.txt
new file mode 100644
index 0000000000000..faafa828817ee
--- /dev/null
+++ b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.issue-84561.txt
@@ -0,0 +1,34 @@
+    1|       |// FIXME(#84561): function-like macros produce unintuitive coverage results.
+    2|       |// This test demonstrates some of the problems.
+    3|       |
+    4|      9|#[derive(Debug, PartialEq, Eq)]
+                       ^5
+  ------------------
+  | <issue_84561::Foo as core::cmp::PartialEq>::eq:
+  |    4|      9|#[derive(Debug, PartialEq, Eq)]
+  ------------------
+  | Unexecuted instantiation: <issue_84561::Foo as core::cmp::PartialEq>::ne
+  ------------------
+    5|       |struct Foo(u32);
+    6|       |
+    7|      1|fn main() {
+    8|      1|    let bar = Foo(1);
+    9|      0|    assert_eq!(bar, Foo(1));
+   10|      1|    let baz = Foo(0);
+   11|      0|    assert_ne!(baz, Foo(1));
+   12|      1|    println!("{:?}", Foo(1));
+   13|      1|    println!("{:?}", bar);
+   14|      1|    println!("{:?}", baz);
+   15|       |
+   16|      1|    assert_eq!(Foo(1), Foo(1));
+   17|      1|    assert_ne!(Foo(0), Foo(1));
+   18|      0|    assert_eq!(Foo(2), Foo(2));
+   19|      1|    let bar = Foo(1);
+   20|      1|    assert_ne!(Foo(0), Foo(3));
+   21|      1|    assert_ne!(Foo(0), Foo(4));
+   22|      1|    assert_eq!(Foo(3), Foo(3));
+   23|      0|    assert_ne!(Foo(0), Foo(5));
+   24|      1|    println!("{:?}", bar);
+   25|      1|    println!("{:?}", Foo(1));
+   26|      1|}
+
diff --git a/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.partial_eq.txt b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.partial_eq.txt
index 4e4dde46b344b..fc26665334950 100644
--- a/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.partial_eq.txt
+++ b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.partial_eq.txt
@@ -2,7 +2,7 @@
     2|       |// structure of this test.
     3|       |
     4|      2|#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
-                       ^0            ^0      ^0 ^0  ^1       ^1 ^0^0
+                       ^0            ^0      ^0     ^1       ^1 ^0^0
   ------------------
   | Unexecuted instantiation: <partial_eq::Version as core::cmp::PartialEq>::ne
   ------------------
diff --git a/src/test/run-make-fulldeps/coverage/issue-83601.rs b/src/test/run-make-fulldeps/coverage/issue-83601.rs
new file mode 100644
index 0000000000000..0b72a81947cc7
--- /dev/null
+++ b/src/test/run-make-fulldeps/coverage/issue-83601.rs
@@ -0,0 +1,14 @@
+// Shows that rust-lang/rust/83601 is resolved
+
+#[derive(Debug, PartialEq, Eq)]
+struct Foo(u32);
+
+fn main() {
+    let bar = Foo(1);
+    assert_eq!(bar, Foo(1));
+    let baz = Foo(0);
+    assert_ne!(baz, Foo(1));
+    println!("{:?}", Foo(1));
+    println!("{:?}", bar);
+    println!("{:?}", baz);
+}
diff --git a/src/test/run-make-fulldeps/coverage/issue-84561.rs b/src/test/run-make-fulldeps/coverage/issue-84561.rs
new file mode 100644
index 0000000000000..dc183685e3d11
--- /dev/null
+++ b/src/test/run-make-fulldeps/coverage/issue-84561.rs
@@ -0,0 +1,26 @@
+// FIXME(#84561): function-like macros produce unintuitive coverage results.
+// This test demonstrates some of the problems.
+
+#[derive(Debug, PartialEq, Eq)]
+struct Foo(u32);
+
+fn main() {
+    let bar = Foo(1);
+    assert_eq!(bar, Foo(1));
+    let baz = Foo(0);
+    assert_ne!(baz, Foo(1));
+    println!("{:?}", Foo(1));
+    println!("{:?}", bar);
+    println!("{:?}", baz);
+
+    assert_eq!(Foo(1), Foo(1));
+    assert_ne!(Foo(0), Foo(1));
+    assert_eq!(Foo(2), Foo(2));
+    let bar = Foo(1);
+    assert_ne!(Foo(0), Foo(3));
+    assert_ne!(Foo(0), Foo(4));
+    assert_eq!(Foo(3), Foo(3));
+    assert_ne!(Foo(0), Foo(5));
+    println!("{:?}", bar);
+    println!("{:?}", Foo(1));
+}

From 3a5df48021b3d29ca55a712d9677cfe043966c6e Mon Sep 17 00:00:00 2001
From: Rich Kadel <richkadel@google.com>
Date: Mon, 26 Apr 2021 21:25:30 -0700
Subject: [PATCH 2/2] adds feature gating of `no_coverage` at either crate-  or
 function-level

---
 .../src/deriving/cmp/eq.rs                    | 11 +++++-
 compiler/rustc_feature/src/active.rs          |  4 +++
 compiler/rustc_feature/src/builtin_attrs.rs   |  8 ++++-
 compiler/rustc_typeck/src/collect.rs          | 28 ++++++++++++++-
 library/core/src/cmp.rs                       |  1 +
 .../expected_show_coverage.issue-84561.txt    | 34 -------------------
 .../expected_show_coverage.no_cov_crate.txt   | 18 ++++++++++
 .../expected_show_coverage.no_cov_func.txt    | 19 +++++++++++
 .../run-make-fulldeps/coverage/issue-84561.rs | 26 --------------
 .../coverage/no_cov_crate.rs                  | 17 ++++++++++
 .../run-make-fulldeps/coverage/no_cov_func.rs | 18 ++++++++++
 .../feature-gates/feature-gate-no_coverage.rs |  8 +++++
 .../feature-gate-no_coverage.stderr           | 13 +++++++
 13 files changed, 142 insertions(+), 63 deletions(-)
 delete mode 100644 src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.issue-84561.txt
 create mode 100644 src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.no_cov_crate.txt
 create mode 100644 src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.no_cov_func.txt
 delete mode 100644 src/test/run-make-fulldeps/coverage/issue-84561.rs
 create mode 100644 src/test/run-make-fulldeps/coverage/no_cov_crate.rs
 create mode 100644 src/test/run-make-fulldeps/coverage/no_cov_func.rs
 create mode 100644 src/test/ui/feature-gates/feature-gate-no_coverage.rs
 create mode 100644 src/test/ui/feature-gates/feature-gate-no_coverage.stderr

diff --git a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs
index 53e9681b437fb..5a4e7fd9d07b4 100644
--- a/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs
+++ b/compiler/rustc_builtin_macros/src/deriving/cmp/eq.rs
@@ -16,10 +16,19 @@ pub fn expand_deriving_eq(
     push: &mut dyn FnMut(Annotatable),
 ) {
     let inline = cx.meta_word(span, sym::inline);
+    let no_coverage_ident =
+        rustc_ast::attr::mk_nested_word_item(Ident::new(sym::no_coverage, span));
+    let no_coverage_feature =
+        rustc_ast::attr::mk_list_item(Ident::new(sym::feature, span), vec![no_coverage_ident]);
     let no_coverage = cx.meta_word(span, sym::no_coverage);
     let hidden = rustc_ast::attr::mk_nested_word_item(Ident::new(sym::hidden, span));
     let doc = rustc_ast::attr::mk_list_item(Ident::new(sym::doc, span), vec![hidden]);
-    let attrs = vec![cx.attribute(inline), cx.attribute(no_coverage), cx.attribute(doc)];
+    let attrs = vec![
+        cx.attribute(inline),
+        cx.attribute(no_coverage_feature),
+        cx.attribute(no_coverage),
+        cx.attribute(doc),
+    ];
     let trait_def = TraitDef {
         span,
         attributes: Vec::new(),
diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs
index 7ae7e0094c6d1..d5963d34dfaa5 100644
--- a/compiler/rustc_feature/src/active.rs
+++ b/compiler/rustc_feature/src/active.rs
@@ -649,6 +649,10 @@ declare_features! (
     /// Allows `extern "wasm" fn`
     (active, wasm_abi, "1.53.0", Some(83788), None),
 
+    /// Allows function attribute `#[no_coverage]`, to bypass coverage
+    /// instrumentation of that function.
+    (active, no_coverage, "1.53.0", Some(84605), None),
+
     /// Allows trait bounds in `const fn`.
     (active, const_fn_trait_bound, "1.53.0", Some(57563), None),
 
diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs
index b35072933d96d..5474fea9c7857 100644
--- a/compiler/rustc_feature/src/builtin_attrs.rs
+++ b/compiler/rustc_feature/src/builtin_attrs.rs
@@ -264,7 +264,6 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
 
     // Code generation:
     ungated!(inline, AssumedUsed, template!(Word, List: "always|never")),
-    ungated!(no_coverage, AssumedUsed, template!(Word)),
     ungated!(cold, AssumedUsed, template!(Word)),
     ungated!(no_builtins, AssumedUsed, template!(Word)),
     ungated!(target_feature, AssumedUsed, template!(List: r#"enable = "name""#)),
@@ -274,6 +273,13 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
         template!(List: "address, memory, thread"),
         experimental!(no_sanitize)
     ),
+    ungated!(
+        // Not exclusively gated at the crate level (though crate-level is
+        // supported). The feature can alternatively be enabled on individual
+        // functions.
+        no_coverage, AssumedUsed,
+        template!(Word),
+    ),
 
     // FIXME: #14408 assume docs are used since rustdoc looks at them.
     ungated!(doc, AssumedUsed, template!(List: "hidden|inline|...", NameValueStr: "string")),
diff --git a/compiler/rustc_typeck/src/collect.rs b/compiler/rustc_typeck/src/collect.rs
index 0528f8812f920..190c9d35934f9 100644
--- a/compiler/rustc_typeck/src/collect.rs
+++ b/compiler/rustc_typeck/src/collect.rs
@@ -2661,6 +2661,8 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
     let mut inline_span = None;
     let mut link_ordinal_span = None;
     let mut no_sanitize_span = None;
+    let mut no_coverage_feature_enabled = false;
+    let mut no_coverage_attr = None;
     for attr in attrs.iter() {
         if tcx.sess.check_name(attr, sym::cold) {
             codegen_fn_attrs.flags |= CodegenFnAttrFlags::COLD;
@@ -2724,8 +2726,15 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
             codegen_fn_attrs.flags |= CodegenFnAttrFlags::NAKED;
         } else if tcx.sess.check_name(attr, sym::no_mangle) {
             codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_MANGLE;
+        } else if attr.has_name(sym::feature) {
+            if let Some(list) = attr.meta_item_list() {
+                if list.iter().any(|nested_meta_item| nested_meta_item.has_name(sym::no_coverage)) {
+                    tcx.sess.mark_attr_used(attr);
+                    no_coverage_feature_enabled = true;
+                }
+            }
         } else if tcx.sess.check_name(attr, sym::no_coverage) {
-            codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_COVERAGE;
+            no_coverage_attr = Some(attr);
         } else if tcx.sess.check_name(attr, sym::rustc_std_internal_symbol) {
             codegen_fn_attrs.flags |= CodegenFnAttrFlags::RUSTC_STD_INTERNAL_SYMBOL;
         } else if tcx.sess.check_name(attr, sym::used) {
@@ -2936,6 +2945,23 @@ fn codegen_fn_attrs(tcx: TyCtxt<'_>, id: DefId) -> CodegenFnAttrs {
         }
     }
 
+    if let Some(no_coverage_attr) = no_coverage_attr {
+        if tcx.sess.features_untracked().no_coverage || no_coverage_feature_enabled {
+            codegen_fn_attrs.flags |= CodegenFnAttrFlags::NO_COVERAGE
+        } else {
+            let mut err = feature_err(
+                &tcx.sess.parse_sess,
+                sym::no_coverage,
+                no_coverage_attr.span,
+                "the `#[no_coverage]` attribute is an experimental feature",
+            );
+            if tcx.sess.parse_sess.unstable_features.is_nightly_build() {
+                err.help("or, alternatively, add `#[feature(no_coverage)]` to the function");
+            }
+            err.emit();
+        }
+    }
+
     codegen_fn_attrs.inline = attrs.iter().fold(InlineAttr::None, |ia, attr| {
         if !attr.has_name(sym::inline) {
             return ia;
diff --git a/library/core/src/cmp.rs b/library/core/src/cmp.rs
index 1f7ae7e3773c4..0a3e5789e8bed 100644
--- a/library/core/src/cmp.rs
+++ b/library/core/src/cmp.rs
@@ -274,6 +274,7 @@ pub trait Eq: PartialEq<Self> {
     //
     // This should never be implemented by hand.
     #[doc(hidden)]
+    #[cfg_attr(not(bootstrap), feature(no_coverage))]
     #[cfg_attr(not(bootstrap), no_coverage)]
     #[inline]
     #[stable(feature = "rust1", since = "1.0.0")]
diff --git a/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.issue-84561.txt b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.issue-84561.txt
deleted file mode 100644
index faafa828817ee..0000000000000
--- a/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.issue-84561.txt
+++ /dev/null
@@ -1,34 +0,0 @@
-    1|       |// FIXME(#84561): function-like macros produce unintuitive coverage results.
-    2|       |// This test demonstrates some of the problems.
-    3|       |
-    4|      9|#[derive(Debug, PartialEq, Eq)]
-                       ^5
-  ------------------
-  | <issue_84561::Foo as core::cmp::PartialEq>::eq:
-  |    4|      9|#[derive(Debug, PartialEq, Eq)]
-  ------------------
-  | Unexecuted instantiation: <issue_84561::Foo as core::cmp::PartialEq>::ne
-  ------------------
-    5|       |struct Foo(u32);
-    6|       |
-    7|      1|fn main() {
-    8|      1|    let bar = Foo(1);
-    9|      0|    assert_eq!(bar, Foo(1));
-   10|      1|    let baz = Foo(0);
-   11|      0|    assert_ne!(baz, Foo(1));
-   12|      1|    println!("{:?}", Foo(1));
-   13|      1|    println!("{:?}", bar);
-   14|      1|    println!("{:?}", baz);
-   15|       |
-   16|      1|    assert_eq!(Foo(1), Foo(1));
-   17|      1|    assert_ne!(Foo(0), Foo(1));
-   18|      0|    assert_eq!(Foo(2), Foo(2));
-   19|      1|    let bar = Foo(1);
-   20|      1|    assert_ne!(Foo(0), Foo(3));
-   21|      1|    assert_ne!(Foo(0), Foo(4));
-   22|      1|    assert_eq!(Foo(3), Foo(3));
-   23|      0|    assert_ne!(Foo(0), Foo(5));
-   24|      1|    println!("{:?}", bar);
-   25|      1|    println!("{:?}", Foo(1));
-   26|      1|}
-
diff --git a/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.no_cov_crate.txt b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.no_cov_crate.txt
new file mode 100644
index 0000000000000..c4a7b0cc7e9f3
--- /dev/null
+++ b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.no_cov_crate.txt
@@ -0,0 +1,18 @@
+    1|       |// Enables `no_coverage` on the entire crate
+    2|       |#![feature(no_coverage)]
+    3|       |
+    4|       |#[no_coverage]
+    5|       |fn do_not_add_coverage_1() {
+    6|       |    println!("called but not covered");
+    7|       |}
+    8|       |
+    9|       |#[no_coverage]
+   10|       |fn do_not_add_coverage_2() {
+   11|       |    println!("called but not covered");
+   12|       |}
+   13|       |
+   14|      1|fn main() {
+   15|      1|    do_not_add_coverage_1();
+   16|      1|    do_not_add_coverage_2();
+   17|      1|}
+
diff --git a/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.no_cov_func.txt b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.no_cov_func.txt
new file mode 100644
index 0000000000000..16eaf7c858c19
--- /dev/null
+++ b/src/test/run-make-fulldeps/coverage-reports/expected_show_coverage.no_cov_func.txt
@@ -0,0 +1,19 @@
+    1|       |// Enables `no_coverage` on individual functions
+    2|       |
+    3|       |#[feature(no_coverage)]
+    4|       |#[no_coverage]
+    5|       |fn do_not_add_coverage_1() {
+    6|       |    println!("called but not covered");
+    7|       |}
+    8|       |
+    9|       |#[no_coverage]
+   10|       |#[feature(no_coverage)]
+   11|       |fn do_not_add_coverage_2() {
+   12|       |    println!("called but not covered");
+   13|       |}
+   14|       |
+   15|      1|fn main() {
+   16|      1|    do_not_add_coverage_1();
+   17|      1|    do_not_add_coverage_2();
+   18|      1|}
+
diff --git a/src/test/run-make-fulldeps/coverage/issue-84561.rs b/src/test/run-make-fulldeps/coverage/issue-84561.rs
deleted file mode 100644
index dc183685e3d11..0000000000000
--- a/src/test/run-make-fulldeps/coverage/issue-84561.rs
+++ /dev/null
@@ -1,26 +0,0 @@
-// FIXME(#84561): function-like macros produce unintuitive coverage results.
-// This test demonstrates some of the problems.
-
-#[derive(Debug, PartialEq, Eq)]
-struct Foo(u32);
-
-fn main() {
-    let bar = Foo(1);
-    assert_eq!(bar, Foo(1));
-    let baz = Foo(0);
-    assert_ne!(baz, Foo(1));
-    println!("{:?}", Foo(1));
-    println!("{:?}", bar);
-    println!("{:?}", baz);
-
-    assert_eq!(Foo(1), Foo(1));
-    assert_ne!(Foo(0), Foo(1));
-    assert_eq!(Foo(2), Foo(2));
-    let bar = Foo(1);
-    assert_ne!(Foo(0), Foo(3));
-    assert_ne!(Foo(0), Foo(4));
-    assert_eq!(Foo(3), Foo(3));
-    assert_ne!(Foo(0), Foo(5));
-    println!("{:?}", bar);
-    println!("{:?}", Foo(1));
-}
diff --git a/src/test/run-make-fulldeps/coverage/no_cov_crate.rs b/src/test/run-make-fulldeps/coverage/no_cov_crate.rs
new file mode 100644
index 0000000000000..300570db7e8f7
--- /dev/null
+++ b/src/test/run-make-fulldeps/coverage/no_cov_crate.rs
@@ -0,0 +1,17 @@
+// Enables `no_coverage` on the entire crate
+#![feature(no_coverage)]
+
+#[no_coverage]
+fn do_not_add_coverage_1() {
+    println!("called but not covered");
+}
+
+#[no_coverage]
+fn do_not_add_coverage_2() {
+    println!("called but not covered");
+}
+
+fn main() {
+    do_not_add_coverage_1();
+    do_not_add_coverage_2();
+}
diff --git a/src/test/run-make-fulldeps/coverage/no_cov_func.rs b/src/test/run-make-fulldeps/coverage/no_cov_func.rs
new file mode 100644
index 0000000000000..e19a2c4a87200
--- /dev/null
+++ b/src/test/run-make-fulldeps/coverage/no_cov_func.rs
@@ -0,0 +1,18 @@
+// Enables `no_coverage` on individual functions
+
+#[feature(no_coverage)]
+#[no_coverage]
+fn do_not_add_coverage_1() {
+    println!("called but not covered");
+}
+
+#[no_coverage]
+#[feature(no_coverage)]
+fn do_not_add_coverage_2() {
+    println!("called but not covered");
+}
+
+fn main() {
+    do_not_add_coverage_1();
+    do_not_add_coverage_2();
+}
diff --git a/src/test/ui/feature-gates/feature-gate-no_coverage.rs b/src/test/ui/feature-gates/feature-gate-no_coverage.rs
new file mode 100644
index 0000000000000..c6b79f9a43171
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-no_coverage.rs
@@ -0,0 +1,8 @@
+#![crate_type = "lib"]
+
+#[no_coverage]
+#[feature(no_coverage)] // does not have to be enabled before `#[no_coverage]`
+fn no_coverage_is_enabled_on_this_function() {}
+
+#[no_coverage] //~ ERROR the `#[no_coverage]` attribute is an experimental feature
+fn requires_feature_no_coverage() {}
diff --git a/src/test/ui/feature-gates/feature-gate-no_coverage.stderr b/src/test/ui/feature-gates/feature-gate-no_coverage.stderr
new file mode 100644
index 0000000000000..04627be4aaf65
--- /dev/null
+++ b/src/test/ui/feature-gates/feature-gate-no_coverage.stderr
@@ -0,0 +1,13 @@
+error[E0658]: the `#[no_coverage]` attribute is an experimental feature
+  --> $DIR/feature-gate-no_coverage.rs:7:1
+   |
+LL | #[no_coverage]
+   | ^^^^^^^^^^^^^^
+   |
+   = note: see issue #84605 <https://github.com/rust-lang/rust/issues/84605> for more information
+   = help: add `#![feature(no_coverage)]` to the crate attributes to enable
+   = help: or, alternatively, add `#[feature(no_coverage)]` to the function
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0658`.