[llvm] [llvm-cov] Support LCOV exclusion markers (PR #220767)

Yaxun Liu via llvm-commits llvm-commits at lists.llvm.org
Fri Sep 25 14:59:07 PDT 2026


https://github.com/yxsamliu updated https://github.com/llvm/llvm-project/pull/220767

>From 72a5f6a88b0d55672b06256824cba7fc7530628f Mon Sep 17 00:00:00 2001
From: "Yaxun (Sam) Liu" <yaxun.liu at amd.com>
Date: Wed, 2 Sep 2026 15:32:01 -0400
Subject: [PATCH 1/4] [llvm-cov] Support LCOV exclusion markers

LCOV exclusion markers are an established source-level convention for
omitting selected lines and regions from coverage reports without changing
instrumentation. They are useful for generated, defensive, or unreachable
source, but llvm-cov currently counts these lines even when other coverage
tools omit them.

Add the opt-in `--respect-lcov-exclusion-markers` option to recognize
`LCOV_EXCL_LINE` and `LCOV_EXCL_START`/`LCOV_EXCL_STOP`. Apply the
exclusions consistently to source views, exported records, summaries, and
coverage-based function filters.

Only read source files required by the requested output, while retaining
the additional files needed for expansions and global JSON function data.
Report malformed or unmatched exclusion ranges as errors.

Implement this in llvm-cov instead of a Python post-processing script so
all views and export formats use the same coverage model. Use source
comments instead of a Clang pragma because this is report-time policy and
must not change instrumentation or profile data.
---
 llvm/docs/CommandGuide/llvm-cov.md            |  31 +-
 llvm/docs/ReleaseNotes.md                     |   4 +
 .../Inputs/lcov-exclusion-boundaries.cpp      |  15 +
 .../Inputs/lcov-exclusion-boundaries.h        |   1 +
 .../Inputs/lcov-exclusion-boundaries.yaml     |  51 +++
 .../Inputs/lcov-exclusion-markers.cpp         |  30 ++
 .../Inputs/lcov-exclusion-markers.proftext    |  60 ++++
 .../Inputs/lcov-exclusion-markers.yaml        | 117 +++++++
 .../llvm-cov/lcov-exclusion-markers.test      | 268 ++++++++++++++++
 llvm/tools/llvm-cov/CMakeLists.txt            |   1 +
 llvm/tools/llvm-cov/CodeCoverage.cpp          | 130 +++++++-
 llvm/tools/llvm-cov/CoverageExclusions.cpp    | 291 ++++++++++++++++++
 llvm/tools/llvm-cov/CoverageExclusions.h      |  51 +++
 llvm/tools/llvm-cov/CoverageExporterJson.cpp  |  58 +++-
 llvm/tools/llvm-cov/CoverageExporterLcov.cpp  |  49 ++-
 llvm/tools/llvm-cov/CoverageFilters.cpp       |  42 +--
 llvm/tools/llvm-cov/CoverageFilters.h         |  25 +-
 llvm/tools/llvm-cov/CoverageReport.cpp        |  15 +-
 llvm/tools/llvm-cov/CoverageSummaryInfo.cpp   |  30 +-
 llvm/tools/llvm-cov/CoverageSummaryInfo.h     |   8 +-
 llvm/tools/llvm-cov/CoverageViewOptions.h     |   4 +
 21 files changed, 1194 insertions(+), 87 deletions(-)
 create mode 100644 llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-boundaries.cpp
 create mode 100644 llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-boundaries.h
 create mode 100644 llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-boundaries.yaml
 create mode 100644 llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-markers.cpp
 create mode 100644 llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-markers.proftext
 create mode 100644 llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-markers.yaml
 create mode 100644 llvm/test/tools/llvm-cov/lcov-exclusion-markers.test
 create mode 100644 llvm/tools/llvm-cov/CoverageExclusions.cpp
 create mode 100644 llvm/tools/llvm-cov/CoverageExclusions.h

diff --git a/llvm/docs/CommandGuide/llvm-cov.md b/llvm/docs/CommandGuide/llvm-cov.md
index 11508c00d25988..c96db49839eb00 100644
--- a/llvm/docs/CommandGuide/llvm-cov.md
+++ b/llvm/docs/CommandGuide/llvm-cov.md
@@ -289,6 +289,16 @@ Skip source code files with file paths that match the given regular expression.
 Only include source code files with file paths that match the given regular expression.
 :::
 
+:::{option} -respect-lcov-exclusion-markers
+Exclude lines containing `LCOV_EXCL_LINE` and ranges delimited by
+`LCOV_EXCL_START` and `LCOV_EXCL_STOP`. The start-marker line is excluded; the
+stop-marker line is not. Unmatched or overlapping range markers are errors.
+Unlike LCOV, a function record is excluded only when all of its code regions
+in the reported file are excluded.
+Markers are read from source files at report time, and an unavailable source
+file needed for the requested output is an error. Defaults to false.
+:::
+
 :::{option} -format=<FORMAT>
 Use the specified output format. The supported formats are: "text", "html".
 :::
@@ -449,6 +459,16 @@ Show statistics for all function instantiations. Defaults to false.
 Skip source code files with file paths that match the given regular expression.
 :::
 
+:::{option} -respect-lcov-exclusion-markers
+Exclude lines containing `LCOV_EXCL_LINE` and ranges delimited by
+`LCOV_EXCL_START` and `LCOV_EXCL_STOP`. The start-marker line is excluded; the
+stop-marker line is not. Unmatched or overlapping range markers are errors.
+Unlike LCOV, a function record is excluded only when all of its code regions
+in the reported file are excluded.
+Markers are read from source files at report time, and an unavailable source
+file needed for the requested output is an error. Defaults to false.
+:::
+
 :::{option} -compilation-dir=<dir>
 Directory used as a base for relative coverage mapping paths. Only applicable
 when binaries have been compiled with one of `-fcoverage-prefix-map`
@@ -530,6 +550,16 @@ format rather than text.
 Skip source code files with file paths that match the given regular expression.
 :::
 
+:::{option} -respect-lcov-exclusion-markers
+Exclude lines containing `LCOV_EXCL_LINE` and ranges delimited by
+`LCOV_EXCL_START` and `LCOV_EXCL_STOP`. The start-marker line is excluded; the
+stop-marker line is not. Unmatched or overlapping range markers are errors.
+Unlike LCOV, a function record is excluded only when all of its code regions
+in the reported file are excluded.
+Markers are read from source files at report time, and an unavailable source
+file needed for the requested output is an error. Defaults to false.
+:::
+
 :::{option} -skip-expansions
 Skip exporting macro expansion coverage data.
 :::
@@ -596,4 +626,3 @@ output file typically bears the {program}`.covmapping` extension.
 
 The {program}`.covmapping` files can be read back by `llvm-cov` just as
 ordinary binary files.
-
diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index 739cf86ad54bb0..96c710c04d3852 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -281,6 +281,10 @@ Makes programs 10x faster by doing Special New Thing.
 * llvm-rc now supports `/showIncludes` to report header and resource-file
   dependencies in a format compatible with Ninja's `deps = msvc` mode.
 
+* llvm-cov now supports an opt-in `-respect-lcov-exclusion-markers` option for
+  excluding coverage associated with `LCOV_EXCL_LINE` and
+  `LCOV_EXCL_START`/`LCOV_EXCL_STOP` source markers.
+
 ### Changes to LLDB
 
 * `platform.plugin.wasm.runtime-args` now precede the port argument on the Wasm
diff --git a/llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-boundaries.cpp b/llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-boundaries.cpp
new file mode 100644
index 00000000000000..42979a6c06ccb4
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-boundaries.cpp
@@ -0,0 +1,15 @@
+#include "lcov-exclusion-boundaries.h"
+
+int resumes_after_excluded_line() {
+  int x = 0; // LCOV_EXCL_LINE
+  return x;
+}
+
+int partially_excluded_function() { // LCOV_EXCL_LINE
+  return 1;
+}
+
+int main() {
+  return resumes_after_excluded_line() + partially_excluded_function() +
+         excluded_header_function();
+}
diff --git a/llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-boundaries.h b/llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-boundaries.h
new file mode 100644
index 00000000000000..2febfe24f09108
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-boundaries.h
@@ -0,0 +1 @@
+inline int excluded_header_function() { return 1; } // LCOV_EXCL_LINE
diff --git a/llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-boundaries.yaml b/llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-boundaries.yaml
new file mode 100644
index 00000000000000..1e236e0eb4d94b
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-boundaries.yaml
@@ -0,0 +1,51 @@
+--- !ELF
+FileHeader:
+  Class:           ELFCLASS64
+  Data:            ELFDATA2LSB
+  Type:            ET_REL
+  Machine:         EM_X86_64
+Sections:
+  - Name:            __llvm_prf_names
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_GNU_RETAIN ]
+    AddressAlign:    0x1
+    Content:         64005F5A3237726573756D65735F61667465725F6578636C756465645F6C696E6576015F5A32377061727469616C6C795F6578636C756465645F66756E6374696F6E76016D61696E015F5A32346578636C756465645F6865616465725F66756E6374696F6E76
+  - Name:            __llvm_covfun
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_GNU_RETAIN ]
+    AddressAlign:    0x8
+    Content:         1410D82230C83DA3090000001800000000000000B8A4D3D4445A894601010001010323030200000064CFD217CFC1D31C090000001800000000000000B8A4D3D4445A8946010100010108230202000000FAD58DE7366495DB090000001800000000000000B8A4D3D4445A894601010001010C0C03020000001DB713FBA875A80C090000001800000000000000B8A4D3D4445A8946010200010101270034
+  - Name:            __llvm_covmap
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_GNU_RETAIN ]
+    AddressAlign:    0x8
+    Content:         00000000820000000000000006000000037F00042F7372633D6C6C766D2F746573742F746F6F6C732F6C6C766D2D636F762F496E707574732F6C636F762D6578636C7573696F6E2D626F756E6461726965732E6370703B6C6C766D2F746573742F746F6F6C732F6C6C766D2D636F762F496E707574732F6C636F762D6578636C7573696F6E2D626F756E6461726965732E680000
+Symbols:
+  - Name:            __covrec_CA875A8FB13B71Du
+    Type:            STT_OBJECT
+    Section:         __llvm_covfun
+    Binding:         STB_WEAK
+    Value:           0x78
+    Size:            0x25
+    Other:           [ STV_HIDDEN ]
+  - Name:            __covrec_A33DC83022D81014u
+    Type:            STT_OBJECT
+    Section:         __llvm_covfun
+    Binding:         STB_WEAK
+    Size:            0x25
+    Other:           [ STV_HIDDEN ]
+  - Name:            __covrec_1CD3C1CF17D2CF64u
+    Type:            STT_OBJECT
+    Section:         __llvm_covfun
+    Binding:         STB_WEAK
+    Value:           0x28
+    Size:            0x25
+    Other:           [ STV_HIDDEN ]
+  - Name:            __covrec_DB956436E78DD5FAu
+    Type:            STT_OBJECT
+    Section:         __llvm_covfun
+    Binding:         STB_WEAK
+    Value:           0x50
+    Size:            0x25
+    Other:           [ STV_HIDDEN ]
+...
diff --git a/llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-markers.cpp b/llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-markers.cpp
new file mode 100644
index 00000000000000..a4493ff679a4df
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-markers.cpp
@@ -0,0 +1,30 @@
+#define EXCLUDED_MACRO(x) ((x) ? 1 : 0) // LCOV_EXCL_LINE
+
+int excluded_line(int x) {
+  return x ? 1 : 0; // LCOV_EXCL_LINE
+}
+
+int partially_excluded_block(int x) {
+  // LCOV_EXCL_START
+  if (x)
+    return 1;
+  return 0; // LCOV_EXCL_STOP
+}
+
+static int excluded_function(int x) { return x; } // LCOV_EXCL_LINE
+
+int included(int x) {
+  if (x)
+    return 1;
+  return 0;
+}
+
+template <typename T> T partially_excluded_template(T x) {
+  return x ? 1 : 0; // LCOV_EXCL_LINE
+}
+
+int main() {
+  return excluded_line(0) + partially_excluded_block(0) +
+         excluded_function(0) + included(0) + EXCLUDED_MACRO(0) +
+         partially_excluded_template(0) + partially_excluded_template(0L);
+}
diff --git a/llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-markers.proftext b/llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-markers.proftext
new file mode 100644
index 00000000000000..5ddda419926d17
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-markers.proftext
@@ -0,0 +1,60 @@
+_Z13excluded_linei
+# Func Hash:
+1549
+# Num Counters:
+2
+# Counter Values:
+1
+0
+_Z24partially_excluded_blocki
+# Func Hash:
+172590168
+# Num Counters:
+2
+# Counter Values:
+1
+0
+
+_Z27partially_excluded_templateIiET_S0_
+# Func Hash:
+1549
+# Num Counters:
+2
+# Counter Values:
+1
+0
+
+_Z27partially_excluded_templateIlET_S0_
+# Func Hash:
+1549
+# Num Counters:
+2
+# Counter Values:
+1
+0
+
+_Z8includedi
+# Func Hash:
+172590168
+# Num Counters:
+2
+# Counter Values:
+1
+0
+
+lcov-exclusion-markers.cpp:_ZL17excluded_functioni
+# Func Hash:
+24
+# Num Counters:
+1
+# Counter Values:
+1
+
+main
+# Func Hash:
+1549
+# Num Counters:
+2
+# Counter Values:
+1
+0
diff --git a/llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-markers.yaml b/llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-markers.yaml
new file mode 100644
index 00000000000000..edac51f2cf741f
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/Inputs/lcov-exclusion-markers.yaml
@@ -0,0 +1,117 @@
+--- !ELF
+FileHeader:
+  Class:           ELFCLASS64
+  Data:            ELFDATA2LSB
+  OSABI:           ELFOSABI_GNU
+  Type:            ET_REL
+  Machine:         EM_X86_64
+  SectionHeaderStringTable: .strtab
+Sections:
+  - Name:            __llvm_covfun
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_GNU_RETAIN ]
+    AddressAlign:    0x8
+    Content:         852459B47E052C102A0000000D060000000000009DB6DEAA6BEC153A01010101050601031A020201010A000B200502000A000B05000D008E8080800805000E000F0200120013
+  - Name:            '__llvm_covfun (1)'
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_GNU_RETAIN ]
+    AddressAlign:    0x8
+    Content:         8C516F6CEB01F6A7380000005884490A000000009DB6DEAA6BEC153A01010101050801072505021001010015010107000820050200070008050009018580808008050105000D02000E018380808008020103000B
+  - Name:            '__llvm_covfun (2)'
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_GNU_RETAIN ]
+    AddressAlign:    0x8
+    Content:         76A985DA90A8203E330000005884490A000000009DB6DEAA6BEC153A0101010105070110150402010107000820050200070008050009018580808008050105000D02000E018380808008020103000B
+  - Name:            '__llvm_covfun (3)'
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_GNU_RETAIN ]
+    AddressAlign:    0x8
+    Content:         FAD58DE7366495DB2D0000000D060000000000009DB6DEAA6BEC153A02010101010502011A0C04020C022F003D0501011B002801001C001F200002001C001F05002200230200260027
+  - Name:            '__llvm_covfun (4)'
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_GNU_RETAIN ]
+    AddressAlign:    0x8
+    Content:         448C940821B709AE0900000018000000000000009DB6DEAA6BEC153A01010001010E250032
+  - Name:            '__llvm_covfun (5)'
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_GNU_RETAIN ]
+    AddressAlign:    0x8
+    Content:         3081B7E9EA3ECCA22A0000000D060000000000009DB6DEAA6BEC153A01010101050601163A020201010A000B200502000A000B05000D008E8080800805000E000F0200120013
+  - Name:            '__llvm_covfun (6)'
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_GNU_RETAIN ]
+    AddressAlign:    0x8
+    Content:         F1CF6393485D5A9D2A0000000D060000000000009DB6DEAA6BEC153A01010101050601163A020201010A000B200502000A000B05000D008E8080800805000E000F0200120013
+  - Name:            __llvm_covmap
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_GNU_RETAIN ]
+    AddressAlign:    0x8
+    Content:         00000000230000000000000006000000022000042F746D701A6C636F762D6578636C7573696F6E2D6D61726B6572732E63707000
+  - Name:            __llvm_prf_names
+    Type:            SHT_PROGBITS
+    Flags:           [ SHF_ALLOC, SHF_GNU_RETAIN ]
+    AddressAlign:    0x1
+    Content:         C501005F5A31336578636C756465645F6C696E6569015F5A32347061727469616C6C795F6578636C756465645F626C6F636B69015F5A38696E636C7564656469016D61696E016C636F762D6578636C7573696F6E2D6D61726B6572732E6370703A5F5A4C31376578636C756465645F66756E6374696F6E69015F5A32377061727469616C6C795F6578636C756465645F74656D706C617465496945545F53305F015F5A32377061727469616C6C795F6578636C756465645F74656D706C617465496C45545F53305F
+  - Type:            SectionHeaderTable
+    Sections:
+      - Name:            .strtab
+      - Name:            __llvm_covfun
+      - Name:            '__llvm_covfun (1)'
+      - Name:            '__llvm_covfun (2)'
+      - Name:            '__llvm_covfun (3)'
+      - Name:            '__llvm_covfun (4)'
+      - Name:            '__llvm_covfun (5)'
+      - Name:            '__llvm_covfun (6)'
+      - Name:            __llvm_covmap
+      - Name:            __llvm_prf_names
+      - Name:            .symtab
+Symbols:
+  - Name:            __llvm_covmap
+    Type:            STT_SECTION
+    Section:         __llvm_covmap
+  - Name:            __llvm_prf_names
+    Type:            STT_SECTION
+    Section:         __llvm_prf_names
+  - Name:            __covrec_102C057EB4592485u
+    Type:            STT_OBJECT
+    Section:         __llvm_covfun
+    Binding:         STB_WEAK
+    Size:            0x46
+    Other:           [ STV_HIDDEN ]
+  - Name:            __covrec_A7F601EB6C6F518Cu
+    Type:            STT_OBJECT
+    Section:         '__llvm_covfun (1)'
+    Binding:         STB_WEAK
+    Size:            0x54
+    Other:           [ STV_HIDDEN ]
+  - Name:            __covrec_3E20A890DA85A976u
+    Type:            STT_OBJECT
+    Section:         '__llvm_covfun (2)'
+    Binding:         STB_WEAK
+    Size:            0x4F
+    Other:           [ STV_HIDDEN ]
+  - Name:            __covrec_DB956436E78DD5FAu
+    Type:            STT_OBJECT
+    Section:         '__llvm_covfun (3)'
+    Binding:         STB_WEAK
+    Size:            0x49
+    Other:           [ STV_HIDDEN ]
+  - Name:            __covrec_AE09B72108948C44u
+    Type:            STT_OBJECT
+    Section:         '__llvm_covfun (4)'
+    Binding:         STB_WEAK
+    Size:            0x25
+    Other:           [ STV_HIDDEN ]
+  - Name:            __covrec_A2CC3EEAE9B78130u
+    Type:            STT_OBJECT
+    Section:         '__llvm_covfun (5)'
+    Binding:         STB_WEAK
+    Size:            0x46
+    Other:           [ STV_HIDDEN ]
+  - Name:            __covrec_9D5A5D489363CFF1u
+    Type:            STT_OBJECT
+    Section:         '__llvm_covfun (6)'
+    Binding:         STB_WEAK
+    Size:            0x46
+    Other:           [ STV_HIDDEN ]
+...
diff --git a/llvm/test/tools/llvm-cov/lcov-exclusion-markers.test b/llvm/test/tools/llvm-cov/lcov-exclusion-markers.test
new file mode 100644
index 00000000000000..157474b632cb86
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/lcov-exclusion-markers.test
@@ -0,0 +1,268 @@
+# RUN: yaml2obj %S/Inputs/lcov-exclusion-markers.yaml -o %t.o
+# RUN: llvm-profdata merge %S/Inputs/lcov-exclusion-markers.proftext -o %t.profdata
+# RUN: llvm-cov show %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%S/Inputs %S/Inputs/lcov-exclusion-markers.cpp \
+# RUN:   | FileCheck %s --check-prefix=DEFAULT-SHOW
+# RUN: llvm-cov show %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%S/Inputs --respect-lcov-exclusion-markers \
+# RUN:   %S/Inputs/lcov-exclusion-markers.cpp \
+# RUN:   > %t.show
+# RUN: FileCheck %s --check-prefix=FILTERED-SHOW < %t.show
+# RUN: not grep -E '23\| +[1-9]' %t.show
+
+# DEFAULT-SHOW: 1|      1|#define EXCLUDED_MACRO(x) ((x) ? 1 : 0) // LCOV_EXCL_LINE
+# DEFAULT-SHOW: 4|      1|  return x ? 1 : 0; // LCOV_EXCL_LINE
+# DEFAULT-SHOW: 7|      1|int partially_excluded_block(int x) {
+# DEFAULT-SHOW: 9|      1|  if (x)
+# DEFAULT-SHOW: 10|      0|    return 1;
+# DEFAULT-SHOW: 11|      1|  return 0; // LCOV_EXCL_STOP
+# FILTERED-SHOW: 1|       |#define EXCLUDED_MACRO(x) ((x) ? 1 : 0) // LCOV_EXCL_LINE
+# FILTERED-SHOW: 3|      1|int excluded_line(int x) {
+# FILTERED-SHOW-NEXT: 4|       |  return x ? 1 : 0; // LCOV_EXCL_LINE
+# FILTERED-SHOW: 8|       |  // LCOV_EXCL_START
+# FILTERED-SHOW-NEXT: 9|       |  if (x)
+# FILTERED-SHOW-NEXT: 10|       |    return 1;
+# FILTERED-SHOW-NEXT: 11|      1|  return 0; // LCOV_EXCL_STOP
+# FILTERED-SHOW: 14|       |static int excluded_function(int x) { return x; } // LCOV_EXCL_LINE
+# FILTERED-SHOW: 17|      1|  if (x)
+# FILTERED-SHOW: 23|       |  return x ? 1 : 0; // LCOV_EXCL_LINE
+
+# RUN: llvm-cov show %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%S/Inputs --respect-lcov-exclusion-markers \
+# RUN:   --name=excluded_function %S/Inputs/lcov-exclusion-markers.cpp \
+# RUN:   | count 0
+# RUN: llvm-cov show %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%S/Inputs --respect-lcov-exclusion-markers \
+# RUN:   --line-coverage-lt=90 %S/Inputs/lcov-exclusion-markers.cpp \
+# RUN:   | FileCheck %s --check-prefix=FILTERED-THRESHOLD
+# RUN: llvm-cov show %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%S/Inputs --respect-lcov-exclusion-markers \
+# RUN:   --region-coverage-lt=90 %S/Inputs/lcov-exclusion-markers.cpp \
+# RUN:   | FileCheck %s --check-prefix=FILTERED-THRESHOLD
+# RUN: rm -rf %t.filtered-dir
+# RUN: llvm-cov show %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%S/Inputs --respect-lcov-exclusion-markers \
+# RUN:   --line-coverage-lt=90 -output-dir=%t.filtered-dir \
+# RUN:   %S/Inputs/lcov-exclusion-markers.cpp
+# RUN: FileCheck %s --check-prefix=FILTERED-INDEX \
+# RUN:   --input-file=%t.filtered-dir/index.txt
+
+# FILTERED-THRESHOLD-NOT: _Z24partially_excluded_blocki:
+# FILTERED-THRESHOLD: _Z8includedi:
+# FILTERED-INDEX: /tmp/lcov-exclusion-markers.cpp{{ +}}4{{ +}}1{{ +}}75.00%{{ +}}1{{ +}}0{{ +}}100.00%{{ +}}5{{ +}}1{{ +}}80.00%
+
+# RUN: llvm-cov report %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%S/Inputs \
+# RUN:   --show-branch-summary %S/Inputs/lcov-exclusion-markers.cpp \
+# RUN:   | FileCheck %s --check-prefix=DEFAULT-REPORT
+# RUN: llvm-cov report %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%S/Inputs --respect-lcov-exclusion-markers \
+# RUN:   --show-branch-summary \
+# RUN:   %S/Inputs/lcov-exclusion-markers.cpp \
+# RUN:   | FileCheck %s --check-prefix=FILTERED-REPORT
+# RUN: llvm-cov report %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%S/Inputs --respect-lcov-exclusion-markers \
+# RUN:   --show-functions --show-branch-summary \
+# RUN:   %S/Inputs/lcov-exclusion-markers.cpp \
+# RUN:   | FileCheck %s --check-prefix=FILTERED-FUNCTIONS
+
+# DEFAULT-REPORT: TOTAL{{ +}}22{{ +}}5{{ +}}77.27%{{ +}}6{{ +}}0{{ +}}100.00%{{ +}}22{{ +}}2{{ +}}90.91%{{ +}}9{{ +}}4{{ +}}55.56%
+# FILTERED-REPORT: TOTAL{{ +}}9{{ +}}1{{ +}}88.89%{{ +}}5{{ +}}0{{ +}}100.00%{{ +}}17{{ +}}1{{ +}}94.12%{{ +}}2{{ +}}1{{ +}}50.00%
+# FILTERED-FUNCTIONS: _Z13excluded_linei{{ +}}1{{ +}}0{{ +}}100.00%{{ +}}2{{ +}}0{{ +}}100.00%{{ +}}0{{ +}}0{{ +}}0.00%
+# FILTERED-FUNCTIONS-NOT: excluded_function
+# FILTERED-FUNCTIONS: _Z24partially_excluded_blocki{{ +}}2{{ +}}0{{ +}}100.00%{{ +}}3{{ +}}0{{ +}}100.00%{{ +}}0{{ +}}0{{ +}}0.00%
+# FILTERED-FUNCTIONS: _Z27partially_excluded_templateIiET_S0_{{ +}}1{{ +}}0{{ +}}100.00%{{ +}}2{{ +}}0{{ +}}100.00%{{ +}}0{{ +}}0{{ +}}0.00%
+
+# RUN: llvm-cov export %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%S/Inputs --respect-lcov-exclusion-markers \
+# RUN:   %S/Inputs/lcov-exclusion-markers.cpp > %t.json
+# RUN: FileCheck %s --check-prefix=JSON < %t.json
+# RUN: not grep 'excluded_function' %t.json
+
+# JSON: "branches":{{\[\[}}17,7,17,8,0,1,0,0,4
+# JSON: "summary":{"lines":{"count":17,"covered":16
+# JSON: "functions":{"count":5,"covered":5
+# JSON: "regions":{"count":9,"covered":8,"notcovered":1
+# JSON: "branches":{"count":2,"covered":1,"notcovered":1
+# JSON: "name":"_Z13excluded_linei","regions":{{\[\[}}3,26,5,2,1,0,0,0
+# JSON: "name":"_Z24partially_excluded_blocki","regions":{{\[\[}}7,37,12,2,1,0,0,0{{.*}}11,3,11,11,1,0,0,0
+# JSON: "name":"_Z27partially_excluded_templateIiET_S0_"
+
+# RUN: llvm-cov export -format=lcov %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%S/Inputs --respect-lcov-exclusion-markers \
+# RUN:   %S/Inputs/lcov-exclusion-markers.cpp > %t.lcov
+# RUN: FileCheck %s --check-prefix=LCOV < %t.lcov
+# RUN: not grep -E '^(DA:1,|DA:4,|DA:[89],|DA:10,|DA:14,|DA:23,|BRDA:1,|BRDA:4,|BRDA:9,|BRDA:23,|FN:14,|FNDA:.*excluded_function)' %t.lcov
+
+# LCOV: FN:3,_Z13excluded_linei
+# LCOV: FN:7,_Z24partially_excluded_blocki
+# LCOV: FN:16,_Z8includedi
+# LCOV: FN:22,_Z27partially_excluded_templateIiET_S0_
+# LCOV: FNF:5
+# LCOV-NEXT: FNH:5
+# LCOV: DA:3,1
+# LCOV: DA:11,1
+# LCOV: DA:16,1
+# LCOV: BRDA:17,0,0,0
+# LCOV-NEXT: BRDA:17,0,1,1
+# LCOV-NEXT: BRF:2
+# LCOV-NEXT: BRH:1
+# LCOV-NEXT: LF:17
+# LCOV-NEXT: LH:16
+
+# RUN: split-file %s %t.sources
+# RUN: not llvm-cov show %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%t.sources/unmatched \
+# RUN:   --respect-lcov-exclusion-markers \
+# RUN:   %t.sources/unmatched/lcov-exclusion-markers.cpp 2>&1 \
+# RUN:   | FileCheck %s --check-prefix=UNMATCHED
+# RUN: not llvm-cov show %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%t.sources/orphan \
+# RUN:   --respect-lcov-exclusion-markers \
+# RUN:   %t.sources/orphan/lcov-exclusion-markers.cpp 2>&1 \
+# RUN:   | FileCheck %s --check-prefix=ORPHAN
+# RUN: not llvm-cov show %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%t.sources/overlap \
+# RUN:   --respect-lcov-exclusion-markers \
+# RUN:   %t.sources/overlap/lcov-exclusion-markers.cpp 2>&1 \
+# RUN:   | FileCheck %s --check-prefix=OVERLAP
+# RUN: llvm-cov show %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%t.sources/stop-line \
+# RUN:   --respect-lcov-exclusion-markers \
+# RUN:   %t.sources/stop-line/lcov-exclusion-markers.cpp \
+# RUN:   | FileCheck %s --check-prefix=STOP-LINE
+# RUN: llvm-cov report %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%t.sources/missing \
+# RUN:   | FileCheck %s --check-prefix=DEFAULT-REPORT
+# RUN: not llvm-cov report %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%t.sources/missing \
+# RUN:   --respect-lcov-exclusion-markers 2>&1 \
+# RUN:   | FileCheck %s --check-prefix=MISSING
+
+# RUN: rm -rf %t.selected-source
+# RUN: mkdir -p %t.selected-source
+# RUN: cp %S/Inputs/prevent_false_instantiations.cpp %t.selected-source/
+# RUN: llvm-profdata merge %S/Inputs/prevent_false_instantiations.proftext \
+# RUN:   -o %t.selected-source.profdata
+# RUN: llvm-cov show %S/Inputs/prevent_false_instantiations.covmapping \
+# RUN:   -instr-profile=%t.selected-source.profdata \
+# RUN:   -path-equivalence=/tmp/false_instantiations/./,%t.selected-source/ \
+# RUN:   --respect-lcov-exclusion-markers \
+# RUN:   %t.selected-source/prevent_false_instantiations.cpp > /dev/null
+# RUN: llvm-cov show %S/Inputs/prevent_false_instantiations.covmapping \
+# RUN:   -instr-profile=%t.selected-source.profdata \
+# RUN:   -path-equivalence=/tmp/false_instantiations/./,%t.selected-source/ \
+# RUN:   --respect-lcov-exclusion-markers --name=func1 \
+# RUN:   %t.selected-source/prevent_false_instantiations.cpp > /dev/null
+# RUN: llvm-cov export -format=lcov \
+# RUN:   %S/Inputs/prevent_false_instantiations.covmapping \
+# RUN:   -instr-profile=%t.selected-source.profdata \
+# RUN:   -path-equivalence=/tmp/false_instantiations/./,%t.selected-source/ \
+# RUN:   --respect-lcov-exclusion-markers --skip-branches \
+# RUN:   %t.selected-source/prevent_false_instantiations.cpp > /dev/null
+# RUN: not llvm-cov show %S/Inputs/prevent_false_instantiations.covmapping \
+# RUN:   -instr-profile=%t.selected-source.profdata \
+# RUN:   -path-equivalence=/tmp/false_instantiations/./,%t.selected-source/ \
+# RUN:   --respect-lcov-exclusion-markers --show-expansions \
+# RUN:   %t.selected-source/prevent_false_instantiations.cpp 2>&1 \
+# RUN:   | FileCheck %s --check-prefix=MISSING-REFERENCE
+# RUN: llvm-cov report %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%t.sources/missing \
+# RUN:   -ignore-filename-regex='lcov-exclusion-markers\.cpp$' \
+# RUN:   --respect-lcov-exclusion-markers > /dev/null
+# RUN: llvm-cov export -format=lcov %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%t.sources/missing \
+# RUN:   -ignore-filename-regex='lcov-exclusion-markers\.cpp$' \
+# RUN:   --respect-lcov-exclusion-markers > /dev/null
+# RUN: llvm-cov export %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%t.sources/missing \
+# RUN:   -ignore-filename-regex='lcov-exclusion-markers\.cpp$' \
+# RUN:   --respect-lcov-exclusion-markers --skip-functions > /dev/null
+# RUN: llvm-cov export %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%t.sources/missing \
+# RUN:   -ignore-filename-regex='lcov-exclusion-markers\.cpp$' \
+# RUN:   --respect-lcov-exclusion-markers --summary-only > /dev/null
+# RUN: not llvm-cov export %t.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=/tmp,%t.sources/missing \
+# RUN:   -ignore-filename-regex='lcov-exclusion-markers\.cpp$' \
+# RUN:   --respect-lcov-exclusion-markers 2>&1 \
+# RUN:   | FileCheck %s --check-prefix=MISSING
+
+# RUN: yaml2obj %S/Inputs/lcov-exclusion-boundaries.yaml -o %t.boundaries.o
+# RUN: llvm-cov show %t.boundaries.o --empty-profile \
+# RUN:   -path-equivalence=/src/llvm/test/tools/llvm-cov/Inputs,%S/Inputs \
+# RUN:   --respect-lcov-exclusion-markers \
+# RUN:   %S/Inputs/lcov-exclusion-boundaries.cpp \
+# RUN:   | FileCheck %s --check-prefix=BOUNDARY-SHOW
+# RUN: llvm-cov export -format=lcov %t.boundaries.o --empty-profile \
+# RUN:   -path-equivalence=/src/llvm/test/tools/llvm-cov/Inputs,%S/Inputs \
+# RUN:   --respect-lcov-exclusion-markers \
+# RUN:   %S/Inputs/lcov-exclusion-boundaries.cpp \
+# RUN:   | FileCheck %s --check-prefix=BOUNDARY-LCOV
+# RUN: llvm-cov export %t.boundaries.o --empty-profile \
+# RUN:   -path-equivalence=/src/llvm/test/tools/llvm-cov/Inputs,%S/Inputs \
+# RUN:   --respect-lcov-exclusion-markers \
+# RUN:   %S/Inputs/lcov-exclusion-boundaries.cpp \
+# RUN:   | FileCheck %s --check-prefix=BOUNDARY-JSON
+
+# UNMATCHED: LCOV_EXCL_START at line 1 has no matching LCOV_EXCL_STOP
+# ORPHAN: LCOV_EXCL_STOP at line 1 has no matching LCOV_EXCL_START
+# OVERLAP: overlapping LCOV_EXCL_START at line 2; previous block started at line 1
+# STOP-LINE: 11|       |  return 0; // LCOV_EXCL_STOP LCOV_EXCL_LINE
+# MISSING: error: {{.*}}lcov-exclusion-markers.cpp: No such file or directory
+# MISSING-REFERENCE: error: {{.*}}prevent_false_instantiations.h: No such file or directory
+
+# BOUNDARY-SHOW: 4|       |  int x = 0; // LCOV_EXCL_LINE
+# BOUNDARY-SHOW-NEXT: 5|      0|  return x;
+# BOUNDARY-SHOW: 8|       |int partially_excluded_function() { // LCOV_EXCL_LINE
+# BOUNDARY-SHOW-NEXT: 9|      0|  return 1;
+
+# BOUNDARY-LCOV: FN:3,_Z27resumes_after_excluded_linev
+# BOUNDARY-LCOV: FN:8,_Z27partially_excluded_functionv
+# BOUNDARY-LCOV-NOT: excluded_header_function
+# BOUNDARY-LCOV: DA:5,0
+# BOUNDARY-LCOV: DA:9,0
+# BOUNDARY-LCOV: LF:9
+
+# BOUNDARY-JSON-NOT: excluded_header_function
+
+#--- unmatched/lcov-exclusion-markers.cpp
+// LCOV_EXCL_START
+#--- orphan/lcov-exclusion-markers.cpp
+// LCOV_EXCL_STOP
+#--- overlap/lcov-exclusion-markers.cpp
+// LCOV_EXCL_START
+// LCOV_EXCL_START
+// LCOV_EXCL_STOP
+#--- stop-line/lcov-exclusion-markers.cpp
+#define EXCLUDED_MACRO(x) ((x) ? 1 : 0) // LCOV_EXCL_LINE
+
+int excluded_line(int x) {
+  return x ? 1 : 0; // LCOV_EXCL_LINE
+}
+
+int partially_excluded_block(int x) {
+  // LCOV_EXCL_START
+  if (x)
+    return 1;
+  return 0; // LCOV_EXCL_STOP LCOV_EXCL_LINE
+}
+
+static int excluded_function(int x) { return x; } // LCOV_EXCL_LINE
+
+int included(int x) {
+  if (x)
+    return 1;
+  return 0;
+}
+
+template <typename T> T partially_excluded_template(T x) {
+  return x ? 1 : 0; // LCOV_EXCL_LINE
+}
+
+int main() {
+  return excluded_line(0) + partially_excluded_block(0) +
+         excluded_function(0) + included(0) + EXCLUDED_MACRO(0) +
+         partially_excluded_template(0) + partially_excluded_template(0L);
+}
+#--- missing/placeholder
diff --git a/llvm/tools/llvm-cov/CMakeLists.txt b/llvm/tools/llvm-cov/CMakeLists.txt
index 6602a2319cb57c..d2b1577c011459 100644
--- a/llvm/tools/llvm-cov/CMakeLists.txt
+++ b/llvm/tools/llvm-cov/CMakeLists.txt
@@ -11,6 +11,7 @@ add_llvm_tool(llvm-cov
   llvm-cov.cpp
   gcov.cpp
   CodeCoverage.cpp
+  CoverageExclusions.cpp
   CoverageExporterJson.cpp
   CoverageExporterLcov.cpp
   CoverageFilters.cpp
diff --git a/llvm/tools/llvm-cov/CodeCoverage.cpp b/llvm/tools/llvm-cov/CodeCoverage.cpp
index 892d25750004f4..00d3bf08101ae3 100644
--- a/llvm/tools/llvm-cov/CodeCoverage.cpp
+++ b/llvm/tools/llvm-cov/CodeCoverage.cpp
@@ -12,6 +12,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "CoverageExclusions.h"
 #include "CoverageExporterJson.h"
 #include "CoverageExporterLcov.h"
 #include "CoverageFilters.h"
@@ -20,6 +21,7 @@
 #include "CoverageViewOptions.h"
 #include "RenderingSupport.h"
 #include "SourceCoverageView.h"
+#include "llvm/ADT/SmallSet.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Debuginfod/BuildIDFetcher.h"
@@ -71,6 +73,12 @@ class CodeCoverageTool {
   int run(Command Cmd, int argc, const char **argv);
 
 private:
+  enum class SourceExclusionScope {
+    SelectedFiles,
+    SelectedFilesAndReferences,
+    AllFiles,
+  };
+
   /// Print the error message to the error output stream.
   void error(const Twine &Message, StringRef Whence = "");
 
@@ -129,6 +137,11 @@ class CodeCoverageTool {
   /// If a demangler is available, demangle all symbol names.
   void demangleSymbols(const CoverageMapping &Coverage);
 
+  /// Load LCOV exclusion markers from source files required by the output.
+  bool loadSourceExclusions(const CoverageMapping &Coverage,
+                            ArrayRef<std::string> SelectedSourceFiles,
+                            SourceExclusionScope Scope);
+
   /// Write out a source file view to the filesystem.
   void writeSourceFileView(StringRef SourceFile, CoverageMapping *Coverage,
                            CoveragePrinter *Printer, bool ShowFilenames);
@@ -148,6 +161,8 @@ class CodeCoverageTool {
   CoverageViewOptions ViewOpts;
   CoverageFiltersMatchAll Filters;
   CoverageFilters FilenameFilters;
+  CoverageExclusions SourceExclusions;
+  bool FiltersRequireSourceExclusions = false;
 
   /// True if InputSourceFiles are provided.
   bool HadSourceFiles = false;
@@ -302,13 +317,64 @@ CodeCoverageTool::getSourceFile(StringRef SourceFile) {
   return *LoadedSourceFiles.back().second;
 }
 
+bool CodeCoverageTool::loadSourceExclusions(
+    const CoverageMapping &Coverage, ArrayRef<std::string> SelectedSourceFiles,
+    SourceExclusionScope Scope) {
+  if (!ViewOpts.RespectLcovExclusionMarkers)
+    return true;
+
+  SmallVector<StringRef> Files;
+  SmallSet<StringRef, 16> Seen;
+  auto AddFile = [&](StringRef Filename) {
+    if (Seen.insert(Filename).second)
+      Files.push_back(Filename);
+  };
+
+  if (Scope == SourceExclusionScope::AllFiles) {
+    for (StringRef Filename : Coverage.getUniqueSourceFiles())
+      AddFile(Filename);
+  } else {
+    if (SelectedSourceFiles.empty()) {
+      for (StringRef Filename : Coverage.getUniqueSourceFiles())
+        if (!FilenameFilters.matchesFilename(Filename))
+          AddFile(Filename);
+    } else {
+      for (StringRef Filename : SelectedSourceFiles)
+        AddFile(Filename);
+    }
+
+    if (Scope == SourceExclusionScope::SelectedFilesAndReferences) {
+      size_t NumSelectedFiles = Files.size();
+      for (size_t I = 0; I != NumSelectedFiles; ++I) {
+        StringRef SourceFile = Files[I];
+        for (const auto &Function : Coverage.getCoveredFunctions(SourceFile))
+          for (StringRef Filename : Function.Filenames)
+            AddFile(Filename);
+      }
+    }
+  }
+
+  for (StringRef Filename : Files) {
+    auto SourceBuffer = getSourceFile(Filename);
+    if (!SourceBuffer)
+      return false;
+    if (Error E = SourceExclusions.parse(Filename, SourceBuffer->getBuffer())) {
+      error(toString(std::move(E)), Filename);
+      return false;
+    }
+  }
+  ViewOpts.SourceExclusions = &SourceExclusions;
+  return true;
+}
+
 void CodeCoverageTool::attachExpansionSubViews(
     SourceCoverageView &View, ArrayRef<ExpansionRecord> Expansions,
     const CoverageMapping &Coverage) {
   if (!ViewOpts.ShowExpandedRegions)
     return;
   for (const auto &Expansion : Expansions) {
-    auto ExpansionCoverage = Coverage.getCoverageForExpansion(Expansion);
+    auto ExpansionCoverage = applyCoverageExclusions(
+        ViewOpts.SourceExclusions, Coverage.getCoverageForExpansion(Expansion));
     if (ExpansionCoverage.empty())
       continue;
     auto SourceBuffer = getSourceFile(ExpansionCoverage.getFilename());
@@ -369,7 +435,8 @@ void CodeCoverageTool::attachMCDCSubViews(SourceCoverageView &View,
 std::unique_ptr<SourceCoverageView>
 CodeCoverageTool::createFunctionView(const FunctionRecord &Function,
                                      const CoverageMapping &Coverage) {
-  auto FunctionCoverage = Coverage.getCoverageForFunction(Function);
+  auto FunctionCoverage = applyCoverageExclusions(
+      ViewOpts.SourceExclusions, Coverage.getCoverageForFunction(Function));
   if (FunctionCoverage.empty())
     return nullptr;
   auto SourceBuffer = getSourceFile(FunctionCoverage.getFilename());
@@ -395,7 +462,8 @@ CodeCoverageTool::createSourceFileView(StringRef SourceFile,
   auto SourceBuffer = getSourceFile(SourceFile);
   if (!SourceBuffer)
     return nullptr;
-  auto FileCoverage = Coverage.getCoverageForFile(SourceFile);
+  auto FileCoverage = applyCoverageExclusions(
+      ViewOpts.SourceExclusions, Coverage.getCoverageForFile(SourceFile));
   if (FileCoverage.empty())
     return nullptr;
 
@@ -416,12 +484,18 @@ CodeCoverageTool::createSourceFileView(StringRef SourceFile,
       continue;
 
     for (const FunctionRecord *Function : Group.getInstantiations()) {
+      if (ViewOpts.SourceExclusions &&
+          ViewOpts.SourceExclusions->isFunctionExcluded(*Function, SourceFile))
+        continue;
+
       std::unique_ptr<SourceCoverageView> SubView{nullptr};
 
       StringRef Funcname = DC.demangle(Function->Name);
 
       if (Function->ExecutionCount > 0) {
-        auto SubViewCoverage = Coverage.getCoverageForFunction(*Function);
+        auto SubViewCoverage =
+            applyCoverageExclusions(ViewOpts.SourceExclusions,
+                                    Coverage.getCoverageForFunction(*Function));
         auto SubViewExpansions = SubViewCoverage.getExpansions();
         auto SubViewBranches = SubViewCoverage.getBranches();
         auto SubViewMCDCRecords = SubViewCoverage.getMCDCRecords();
@@ -811,6 +885,11 @@ int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) {
       "check-binary-ids", cl::desc("Fail if an object couldn't be found for a "
                                    "binary ID in the profile"));
 
+  cl::opt<bool> RespectLcovExclusionMarkers(
+      "respect-lcov-exclusion-markers", cl::Optional,
+      cl::desc("Exclude coverage for LCOV_EXCL_LINE and "
+               "LCOV_EXCL_START/LCOV_EXCL_STOP source markers"));
+
   auto commandLineParser = [&, this](int argc, const char **argv) -> int {
     cl::ParseCommandLineOptions(argc, argv, "LLVM code coverage tool\n");
     ViewOpts.Debug = DebugDump;
@@ -927,6 +1006,7 @@ int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) {
         RegionCoverageGtFilter.getNumOccurrences() ||
         LineCoverageLtFilter.getNumOccurrences() ||
         LineCoverageGtFilter.getNumOccurrences()) {
+      FiltersRequireSourceExclusions = true;
       auto StatFilterer = std::make_unique<CoverageFilters>();
       if (RegionCoverageLtFilter.getNumOccurrences())
         StatFilterer->push_back(std::make_unique<RegionCoverageFilter>(
@@ -984,6 +1064,7 @@ int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) {
     ViewOpts.ExportSummaryOnly = SummaryOnly;
     ViewOpts.NumThreads = NumThreads;
     ViewOpts.CompilationDirectory = CompilationDirectory;
+    ViewOpts.RespectLcovExclusionMarkers = RespectLcovExclusionMarkers;
 
     return 0;
   };
@@ -1187,6 +1268,14 @@ int CodeCoverageTool::doShow(int argc, const char **argv,
         SourceFiles.push_back(std::string(Filename));
     }
 
+  SourceExclusionScope ExclusionScope =
+      ViewOpts.ShowExpandedRegions || ViewOpts.hasOutputDirectory() ||
+              FiltersRequireSourceExclusions
+          ? SourceExclusionScope::SelectedFilesAndReferences
+          : SourceExclusionScope::SelectedFiles;
+  if (!loadSourceExclusions(*Coverage, SourceFiles, ExclusionScope))
+    return 1;
+
   // Create an index out of the source files.
   if (ViewOpts.hasOutputDirectory()) {
     if (Error E = Printer->createIndexFile(SourceFiles, *Coverage, Filters)) {
@@ -1201,7 +1290,10 @@ int CodeCoverageTool::doShow(int argc, const char **argv,
         FilenameFunctionMap;
     for (const auto &SourceFile : SourceFiles)
       for (const auto &Function : Coverage->getCoveredFunctions(SourceFile))
-        if (Filters.matches(*Coverage, Function))
+        if ((!ViewOpts.SourceExclusions ||
+             !ViewOpts.SourceExclusions->isFunctionExcluded(Function,
+                                                            SourceFile)) &&
+            Filters.matches(*Coverage, Function, ViewOpts.SourceExclusions))
           FilenameFunctionMap[SourceFile].push_back(&Function);
 
     // Only print filter matching functions for each file.
@@ -1292,6 +1384,16 @@ int CodeCoverageTool::doReport(int argc, const char **argv,
   if (!Coverage)
     return 1;
 
+  if (ShowFunctionSummaries && SourceFiles.empty()) {
+    error("source files must be specified when -show-functions=true is "
+          "specified");
+    return 1;
+  }
+
+  if (!loadSourceExclusions(*Coverage, SourceFiles,
+                            SourceExclusionScope::SelectedFilesAndReferences))
+    return 1;
+
   CoverageReport Report(ViewOpts, *Coverage);
   if (!ShowFunctionSummaries) {
     if (SourceFiles.empty())
@@ -1299,12 +1401,6 @@ int CodeCoverageTool::doReport(int argc, const char **argv,
     else
       Report.renderFileReports(llvm::outs(), SourceFiles);
   } else {
-    if (SourceFiles.empty()) {
-      error("source files must be specified when -show-functions=true is "
-            "specified");
-      return 1;
-    }
-
     Report.renderFunctionReports(SourceFiles, DC, llvm::outs());
   }
   return 0;
@@ -1368,6 +1464,18 @@ int CodeCoverageTool::doExport(int argc, const char **argv,
     return 1;
   }
 
+  SourceExclusionScope ExclusionScope;
+  if (ViewOpts.Format == CoverageViewOptions::OutputFormat::Text &&
+      !ViewOpts.ExportSummaryOnly && !ViewOpts.SkipFunctions)
+    ExclusionScope = SourceExclusionScope::AllFiles;
+  else if (ViewOpts.Format == CoverageViewOptions::OutputFormat::Lcov &&
+           ViewOpts.SkipBranches)
+    ExclusionScope = SourceExclusionScope::SelectedFiles;
+  else
+    ExclusionScope = SourceExclusionScope::SelectedFilesAndReferences;
+  if (!loadSourceExclusions(*Coverage, SourceFiles, ExclusionScope))
+    return 1;
+
   std::unique_ptr<CoverageExporter> Exporter;
 
   switch (ViewOpts.Format) {
diff --git a/llvm/tools/llvm-cov/CoverageExclusions.cpp b/llvm/tools/llvm-cov/CoverageExclusions.cpp
new file mode 100644
index 00000000000000..822c760a451968
--- /dev/null
+++ b/llvm/tools/llvm-cov/CoverageExclusions.cpp
@@ -0,0 +1,291 @@
+//===- CoverageExclusions.cpp - Source coverage exclusions ---------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "CoverageExclusions.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/FormatVariadic.h"
+#include <algorithm>
+#include <cctype>
+#include <limits>
+#include <optional>
+#include <tuple>
+
+using namespace llvm;
+using namespace coverage;
+
+namespace {
+
+constexpr StringLiteral ExcludeLineMarker = "LCOV_EXCL_LINE";
+constexpr StringLiteral ExcludeStartMarker = "LCOV_EXCL_START";
+constexpr StringLiteral ExcludeStopMarker = "LCOV_EXCL_STOP";
+
+bool isMarkerBoundary(char C) {
+  return !std::isalnum(static_cast<unsigned char>(C)) && C != '_';
+}
+
+bool containsMarker(StringRef Line, StringRef Marker) {
+  size_t Pos = 0;
+  while ((Pos = Line.find(Marker, Pos)) != StringRef::npos) {
+    bool StartBoundary = Pos == 0 || isMarkerBoundary(Line[Pos - 1]);
+    size_t End = Pos + Marker.size();
+    bool EndBoundary = End == Line.size() || isMarkerBoundary(Line[End]);
+    if (StartBoundary && EndBoundary)
+      return true;
+    Pos = End;
+  }
+  return false;
+}
+
+const CoverageExclusions::LineRange *
+findContainingRange(ArrayRef<CoverageExclusions::LineRange> Ranges,
+                    unsigned Line) {
+  auto It =
+      llvm::upper_bound(Ranges, Line, [](unsigned Line, const auto &Range) {
+        return Line < Range.first;
+      });
+  if (It == Ranges.begin() || Line > std::prev(It)->second)
+    return nullptr;
+  return &*std::prev(It);
+}
+
+std::vector<CoverageExclusions::LineRange>
+mergeLineRanges(std::vector<CoverageExclusions::LineRange> Ranges) {
+  llvm::sort(Ranges);
+  std::vector<CoverageExclusions::LineRange> Merged;
+  for (const auto &[Begin, End] : Ranges) {
+    if (Merged.empty() || Begin > Merged.back().second + 1)
+      Merged.emplace_back(Begin, End);
+    else
+      Merged.back().second = std::max(Merged.back().second, End);
+  }
+  return Merged;
+}
+
+Expected<std::vector<CoverageExclusions::LineRange>>
+parseLineRanges(StringRef Source) {
+  std::vector<CoverageExclusions::LineRange> Ranges;
+  std::optional<unsigned> BlockStart;
+  SmallVector<StringRef, 0> Lines;
+  Source.split(Lines, '\n', /*MaxSplit=*/-1, /*KeepEmpty=*/true);
+
+  for (unsigned I = 0; I < Lines.size(); ++I) {
+    unsigned Line = I + 1;
+    StringRef Text = Lines[I].rtrim("\r");
+    if (containsMarker(Text, ExcludeStartMarker)) {
+      if (BlockStart)
+        return createStringError(
+            inconvertibleErrorCode(),
+            formatv("overlapping LCOV_EXCL_START at line {0}; previous block "
+                    "started at line {1}",
+                    Line, *BlockStart));
+      BlockStart = Line;
+      continue;
+    }
+    if (containsMarker(Text, ExcludeStopMarker)) {
+      if (!BlockStart)
+        return createStringError(inconvertibleErrorCode(),
+                                 formatv("LCOV_EXCL_STOP at line {0} has no "
+                                         "matching LCOV_EXCL_START",
+                                         Line));
+      if (*BlockStart < Line)
+        Ranges.emplace_back(*BlockStart, Line - 1);
+      BlockStart.reset();
+    }
+    if (containsMarker(Text, ExcludeLineMarker))
+      Ranges.emplace_back(Line, Line);
+  }
+
+  if (BlockStart)
+    return createStringError(
+        inconvertibleErrorCode(),
+        formatv("LCOV_EXCL_START at line {0} has no matching LCOV_EXCL_STOP",
+                *BlockStart));
+
+  return mergeLineRanges(std::move(Ranges));
+}
+
+class CoverageDataExclusionFilter : public CoverageData {
+public:
+  CoverageDataExclusionFilter(CoverageData &&Data,
+                              ArrayRef<CoverageExclusions::LineRange> Ranges)
+      : CoverageData(std::move(Data)), Ranges(Ranges) {}
+
+  CoverageData apply() {
+    if (Ranges.empty())
+      return std::move(*this);
+
+    filterSegments();
+    filterRecords();
+    return std::move(*this);
+  }
+
+private:
+  enum class SegmentPriority : unsigned {
+    Resume,
+    Original,
+    ExclusionStart,
+  };
+
+  struct OrderedSegment {
+    CoverageSegment Segment;
+    SegmentPriority Priority;
+  };
+
+  ArrayRef<CoverageExclusions::LineRange> Ranges;
+
+  bool isExcluded(unsigned Line) const {
+    return findContainingRange(Ranges, Line) != nullptr;
+  }
+
+  void addRangeBoundaries(const CoverageExclusions::LineRange &Range,
+                          std::vector<CoverageSegment> &OriginalSegments,
+                          std::vector<OrderedSegment> &FilteredSegments) const {
+    const auto [Begin, End] = Range;
+    FilteredSegments.push_back({CoverageSegment(Begin, 1,
+                                                /*IsRegionEntry=*/true),
+                                SegmentPriority::ExclusionStart});
+
+    if (End == std::numeric_limits<unsigned>::max())
+      return;
+
+    LineColPair ResumeLoc(End + 1, 1);
+    auto Resume =
+        llvm::lower_bound(OriginalSegments, ResumeLoc,
+                          [](const CoverageSegment &Segment, LineColPair Loc) {
+                            return LineColPair(Segment.Line, Segment.Col) < Loc;
+                          });
+    if (Resume != OriginalSegments.end() &&
+        LineColPair(Resume->Line, Resume->Col) == ResumeLoc) {
+      auto ResumeEnd = std::upper_bound(
+          Resume, OriginalSegments.end(), ResumeLoc,
+          [](LineColPair Loc, const CoverageSegment &Segment) {
+            return Loc < LineColPair(Segment.Line, Segment.Col);
+          });
+      auto CountedResume = llvm::find_if(
+          make_range(Resume, ResumeEnd),
+          [](const CoverageSegment &Segment) { return Segment.HasCount; });
+      if (CountedResume != ResumeEnd)
+        CountedResume->IsRegionEntry = true;
+      return;
+    }
+
+    const CoverageSegment *Wrapped =
+        Resume == OriginalSegments.begin() ? nullptr : &*std::prev(Resume);
+    if (Wrapped && Wrapped->HasCount) {
+      FilteredSegments.push_back(
+          {CoverageSegment(ResumeLoc.first, ResumeLoc.second, Wrapped->Count,
+                           /*IsRegionEntry=*/true, Wrapped->IsGapRegion),
+           SegmentPriority::Resume});
+      return;
+    }
+
+    FilteredSegments.push_back(
+        {CoverageSegment(ResumeLoc.first, ResumeLoc.second,
+                         /*IsRegionEntry=*/false),
+         SegmentPriority::Resume});
+  }
+
+  void filterSegments() {
+    std::vector<CoverageSegment> OriginalSegments = std::move(Segments);
+    std::vector<OrderedSegment> FilteredSegments;
+    FilteredSegments.reserve(OriginalSegments.size() + Ranges.size() * 2);
+
+    for (const auto &Range : Ranges)
+      addRangeBoundaries(Range, OriginalSegments, FilteredSegments);
+
+    for (const CoverageSegment &Segment : OriginalSegments)
+      if (!isExcluded(Segment.Line))
+        FilteredSegments.push_back({Segment, SegmentPriority::Original});
+
+    // At a shared location, resume the prior count before original segments
+    // and end coverage after them.
+    llvm::stable_sort(FilteredSegments, [](const OrderedSegment &LHS,
+                                           const OrderedSegment &RHS) {
+      return std::tie(LHS.Segment.Line, LHS.Segment.Col, LHS.Priority) <
+             std::tie(RHS.Segment.Line, RHS.Segment.Col, RHS.Priority);
+    });
+    Segments.clear();
+    for (const OrderedSegment &Entry : FilteredSegments)
+      Segments.push_back(Entry.Segment);
+  }
+
+  void filterRecords() {
+    std::vector<ExpansionRecord> FilteredExpansions;
+    FilteredExpansions.reserve(Expansions.size());
+    for (const ExpansionRecord &Expansion : Expansions)
+      if (!isExcluded(Expansion.Region.LineStart))
+        FilteredExpansions.push_back(Expansion);
+    Expansions.swap(FilteredExpansions);
+
+    llvm::erase_if(BranchRegions, [&](const CountedRegion &Region) {
+      return isExcluded(Region.LineStart);
+    });
+    llvm::erase_if(MCDCRecords, [&](const MCDCRecord &Record) {
+      return isExcluded(Record.getDecisionRegion().LineStart);
+    });
+  }
+};
+
+} // namespace
+
+Error CoverageExclusions::parse(StringRef Filename, StringRef Source) {
+  auto Ranges = parseLineRanges(Source);
+  if (!Ranges)
+    return Ranges.takeError();
+  ExcludedLineRanges[Filename] = std::move(*Ranges);
+  return Error::success();
+}
+
+ArrayRef<CoverageExclusions::LineRange>
+CoverageExclusions::getRanges(StringRef Filename) const {
+  auto It = ExcludedLineRanges.find(Filename);
+  if (It == ExcludedLineRanges.end())
+    return {};
+  return It->second;
+}
+
+bool CoverageExclusions::isLineExcluded(StringRef Filename,
+                                        unsigned Line) const {
+  return findContainingRange(getRanges(Filename), Line) != nullptr;
+}
+
+bool CoverageExclusions::isRegionExcluded(
+    const FunctionRecord &Function, const CounterMappingRegion &Region) const {
+  return Region.FileID < Function.Filenames.size() &&
+         isLineExcluded(Function.Filenames[Region.FileID], Region.LineStart);
+}
+
+bool CoverageExclusions::isFunctionExcluded(const FunctionRecord &Function,
+                                            StringRef Filename) const {
+  bool HasCodeRegion = false;
+  for (const CountedRegion &Region : Function.CountedRegions) {
+    if (Region.Kind != CounterMappingRegion::CodeRegion ||
+        Region.FileID >= Function.Filenames.size() ||
+        (!Filename.empty() && Function.Filenames[Region.FileID] != Filename))
+      continue;
+    HasCodeRegion = true;
+    const LineRange *Range = findContainingRange(
+        getRanges(Function.Filenames[Region.FileID]), Region.LineStart);
+    if (!Range || Region.LineEnd > Range->second)
+      return false;
+  }
+  return HasCodeRegion;
+}
+
+CoverageData CoverageExclusions::apply(CoverageData Coverage) const {
+  ArrayRef<LineRange> Ranges = getRanges(Coverage.getFilename());
+  return CoverageDataExclusionFilter(std::move(Coverage), Ranges).apply();
+}
+
+CoverageData llvm::applyCoverageExclusions(const CoverageExclusions *Exclusions,
+                                           CoverageData Coverage) {
+  if (Exclusions)
+    return Exclusions->apply(std::move(Coverage));
+  return Coverage;
+}
diff --git a/llvm/tools/llvm-cov/CoverageExclusions.h b/llvm/tools/llvm-cov/CoverageExclusions.h
new file mode 100644
index 00000000000000..46574648ed4228
--- /dev/null
+++ b/llvm/tools/llvm-cov/CoverageExclusions.h
@@ -0,0 +1,51 @@
+//===- CoverageExclusions.h - Source coverage exclusions -------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_COV_COVERAGEEXCLUSIONS_H
+#define LLVM_COV_COVERAGEEXCLUSIONS_H
+
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ProfileData/Coverage/CoverageMapping.h"
+#include "llvm/Support/Error.h"
+#include <utility>
+#include <vector>
+
+namespace llvm {
+
+class CoverageExclusions {
+public:
+  using LineRange = std::pair<unsigned, unsigned>;
+
+  Error parse(StringRef Filename, StringRef Source);
+
+  bool isLineExcluded(StringRef Filename, unsigned Line) const;
+
+  bool isRegionExcluded(const coverage::FunctionRecord &Function,
+                        const coverage::CounterMappingRegion &Region) const;
+
+  bool isFunctionExcluded(const coverage::FunctionRecord &Function,
+                          StringRef Filename = {}) const;
+
+  coverage::CoverageData apply(coverage::CoverageData Coverage) const;
+
+private:
+  ArrayRef<LineRange> getRanges(StringRef Filename) const;
+
+  StringMap<std::vector<LineRange>> ExcludedLineRanges;
+};
+
+/// Apply \p Exclusions when present, otherwise return \p Coverage unchanged.
+coverage::CoverageData
+applyCoverageExclusions(const CoverageExclusions *Exclusions,
+                        coverage::CoverageData Coverage);
+
+} // namespace llvm
+
+#endif // LLVM_COV_COVERAGEEXCLUSIONS_H
diff --git a/llvm/tools/llvm-cov/CoverageExporterJson.cpp b/llvm/tools/llvm-cov/CoverageExporterJson.cpp
index 4dbd6ea64da944..0bb70ea7446efd 100644
--- a/llvm/tools/llvm-cov/CoverageExporterJson.cpp
+++ b/llvm/tools/llvm-cov/CoverageExporterJson.cpp
@@ -52,6 +52,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "CoverageExporterJson.h"
+#include "CoverageExclusions.h"
 #include "CoverageReport.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Support/JSON.h"
@@ -204,41 +205,56 @@ void renderMCDCRecord(json::OStream &JOS, const coverage::MCDCRecord &Record,
 }
 
 void renderRegions(json::OStream &JOS,
-                   ArrayRef<coverage::CountedRegion> Regions) {
+                   ArrayRef<coverage::CountedRegion> Regions,
+                   const coverage::FunctionRecord &Function,
+                   const CoverageViewOptions &Options) {
   JOS.array([&] {
     for (const auto &Region : Regions)
-      renderRegion(JOS, Region);
+      if (!Options.SourceExclusions ||
+          !Options.SourceExclusions->isRegionExcluded(Function, Region))
+        renderRegion(JOS, Region);
   });
 }
 
 void renderBranchRegions(json::OStream &JOS,
-                         ArrayRef<coverage::CountedRegion> Regions) {
+                         ArrayRef<coverage::CountedRegion> Regions,
+                         const coverage::FunctionRecord &Function,
+                         const CoverageViewOptions &Options) {
   JOS.array([&] {
     for (const auto &Region : Regions)
-      if (!Region.TrueFolded || !Region.FalseFolded)
+      if ((!Options.SourceExclusions ||
+           !Options.SourceExclusions->isRegionExcluded(Function, Region)) &&
+          (!Region.TrueFolded || !Region.FalseFolded))
         renderBranch(JOS, Region);
   });
 }
 
 void renderMCDCRecords(json::OStream &JOS,
                        ArrayRef<coverage::MCDCRecord> Records,
+                       const coverage::FunctionRecord &Function,
                        const CoverageViewOptions &Options) {
   JOS.array([&] {
     for (auto &Record : Records)
-      renderMCDCRecord(JOS, Record, Options);
+      if (!Options.SourceExclusions ||
+          !Options.SourceExclusions->isRegionExcluded(
+              Function, Record.getDecisionRegion()))
+        renderMCDCRecord(JOS, Record, Options);
   });
 }
 
 std::vector<llvm::coverage::CountedRegion>
 collectNestedBranches(const coverage::CoverageMapping &Coverage,
-                      ArrayRef<llvm::coverage::ExpansionRecord> Expansions) {
+                      ArrayRef<llvm::coverage::ExpansionRecord> Expansions,
+                      const CoverageViewOptions &Options) {
   std::vector<llvm::coverage::CountedRegion> Branches;
   for (const auto &Expansion : Expansions) {
-    auto ExpansionCoverage = Coverage.getCoverageForExpansion(Expansion);
+    auto ExpansionCoverage = applyCoverageExclusions(
+        Options.SourceExclusions, Coverage.getCoverageForExpansion(Expansion));
 
     // Recursively collect branches from nested expansions.
     auto NestedExpansions = ExpansionCoverage.getExpansions();
-    auto NestedExBranches = collectNestedBranches(Coverage, NestedExpansions);
+    auto NestedExBranches =
+        collectNestedBranches(Coverage, NestedExpansions, Options);
     append_range(Branches, NestedExBranches);
 
     // Add branches from this level of expansion.
@@ -253,7 +269,8 @@ collectNestedBranches(const coverage::CoverageMapping &Coverage,
 
 void renderExpansion(json::OStream &JOS,
                      const coverage::CoverageMapping &Coverage,
-                     const coverage::ExpansionRecord &Expansion) {
+                     const coverage::ExpansionRecord &Expansion,
+                     const CoverageViewOptions &Options) {
   std::vector<llvm::coverage::ExpansionRecord> Expansions = {Expansion};
   JOS.object([&] {
     JOS.attributeArray("filenames", [&] {
@@ -262,7 +279,11 @@ void renderExpansion(json::OStream &JOS,
     });
     // Enumerate the branch coverage information for the expansion.
     JOS.attributeBegin("branches");
-    renderBranchRegions(JOS, collectNestedBranches(Coverage, Expansions));
+    JOS.array([&] {
+      for (const auto &Branch :
+           collectNestedBranches(Coverage, Expansions, Options))
+        renderBranch(JOS, Branch);
+    });
     JOS.attributeEnd();
     // Mark the beginning and end of this expansion in the source file.
     JOS.attributeBegin("source_region");
@@ -270,7 +291,8 @@ void renderExpansion(json::OStream &JOS,
     JOS.attributeEnd();
     // Enumerate the coverage information for the expansion.
     JOS.attributeBegin("target_regions");
-    renderRegions(JOS, Expansion.Function.CountedRegions);
+    renderRegions(JOS, Expansion.Function.CountedRegions, Expansion.Function,
+                  Options);
     JOS.attributeEnd();
   });
 }
@@ -331,7 +353,8 @@ void renderFile(json::OStream &JOS, const coverage::CoverageMapping &Coverage,
     JOS.attribute("filename", Filename);
     if (!Options.ExportSummaryOnly) {
       // Calculate and render detailed coverage information for given file.
-      auto FileCoverage = Coverage.getCoverageForFile(Filename);
+      auto FileCoverage = applyCoverageExclusions(
+          Options.SourceExclusions, Coverage.getCoverageForFile(Filename));
       JOS.attributeArray("branches", [&] {
         for (const auto &Branch : FileCoverage.getBranches())
           renderBranch(JOS, Branch);
@@ -339,7 +362,7 @@ void renderFile(json::OStream &JOS, const coverage::CoverageMapping &Coverage,
       if (!Options.SkipExpansions) {
         JOS.attributeArray("expansions", [&] {
           for (const auto &Expansion : FileCoverage.getExpansions())
-            renderExpansion(JOS, Coverage, Expansion);
+            renderExpansion(JOS, Coverage, Expansion, Options);
         });
       }
       JOS.attributeArray("mcdc_records", [&] {
@@ -405,9 +428,12 @@ void renderFunctions(
     const CoverageViewOptions &Options) {
   JOS.array([&] {
     for (const auto &F : Functions) {
+      if (Options.SourceExclusions &&
+          Options.SourceExclusions->isFunctionExcluded(F))
+        continue;
       JOS.object([&] {
         JOS.attributeBegin("branches");
-        renderBranchRegions(JOS, F.CountedBranchRegions);
+        renderBranchRegions(JOS, F.CountedBranchRegions, F, Options);
         JOS.attributeEnd();
 
         JOS.attribute("count", clamp_uint64_to_int64(F.ExecutionCount));
@@ -418,13 +444,13 @@ void renderFunctions(
         });
 
         JOS.attributeBegin("mcdc_records");
-        renderMCDCRecords(JOS, F.MCDCRecords, Options);
+        renderMCDCRecords(JOS, F.MCDCRecords, F, Options);
         JOS.attributeEnd();
 
         JOS.attribute("name", F.Name);
 
         JOS.attributeBegin("regions");
-        renderRegions(JOS, F.CountedRegions);
+        renderRegions(JOS, F.CountedRegions, F, Options);
         JOS.attributeEnd();
       });
     }
diff --git a/llvm/tools/llvm-cov/CoverageExporterLcov.cpp b/llvm/tools/llvm-cov/CoverageExporterLcov.cpp
index 96be9693a70476..1208ed78e76f4a 100644
--- a/llvm/tools/llvm-cov/CoverageExporterLcov.cpp
+++ b/llvm/tools/llvm-cov/CoverageExporterLcov.cpp
@@ -40,6 +40,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "CoverageExporterLcov.h"
+#include "CoverageExclusions.h"
 #include "CoverageReport.h"
 
 using namespace llvm;
@@ -74,13 +75,21 @@ void renderFunctionSummary(raw_ostream &OS,
 
 void renderFunctions(
     raw_ostream &OS,
-    const iterator_range<coverage::FunctionRecordIterator> &Functions) {
+    const iterator_range<coverage::FunctionRecordIterator> &Functions,
+    StringRef Filename, const CoverageViewOptions &Options) {
   for (const auto &F : Functions) {
+    if (Options.SourceExclusions &&
+        Options.SourceExclusions->isFunctionExcluded(F, Filename))
+      continue;
     auto StartLine = F.CountedRegions.front().LineStart;
     OS << "FN:" << StartLine << ',' << F.Name << '\n';
   }
-  for (const auto &F : Functions)
+  for (const auto &F : Functions) {
+    if (Options.SourceExclusions &&
+        Options.SourceExclusions->isFunctionExcluded(F, Filename))
+      continue;
     OS << "FNDA:" << F.ExecutionCount << ',' << F.Name << '\n';
+  }
 }
 
 void renderLineExecutionCounts(raw_ostream &OS,
@@ -99,18 +108,20 @@ std::vector<NestedCountedRegion>
 collectNestedBranches(const coverage::CoverageMapping &Coverage,
                       ArrayRef<llvm::coverage::ExpansionRecord> Expansions,
                       std::vector<LineColPair> &NestedPath,
-                      unsigned &PositionCounter) {
+                      unsigned &PositionCounter,
+                      const CoverageViewOptions &Options) {
   std::vector<NestedCountedRegion> Branches;
   for (const auto &Expansion : Expansions) {
-    auto ExpansionCoverage = Coverage.getCoverageForExpansion(Expansion);
+    auto ExpansionCoverage = applyCoverageExclusions(
+        Options.SourceExclusions, Coverage.getCoverageForExpansion(Expansion));
 
     // Track the path to the nested expansions.
     NestedPath.push_back(Expansion.Region.startLoc());
 
     // Recursively collect branches from nested expansions.
     auto NestedExpansions = ExpansionCoverage.getExpansions();
-    auto NestedExBranches = collectNestedBranches(Coverage, NestedExpansions,
-                                                  NestedPath, PositionCounter);
+    auto NestedExBranches = collectNestedBranches(
+        Coverage, NestedExpansions, NestedPath, PositionCounter, Options);
     append_range(Branches, NestedExBranches);
 
     // Add branches from this level of expansion.
@@ -181,7 +192,8 @@ void combineInstanceCounts(std::vector<NestedCountedRegion> &Branches) {
 void renderBranchExecutionCounts(raw_ostream &OS,
                                  const coverage::CoverageMapping &Coverage,
                                  const coverage::CoverageData &FileCoverage,
-                                 bool UnifyInstances) {
+                                 bool UnifyInstances,
+                                 const CoverageViewOptions &Options) {
 
   std::vector<NestedCountedRegion> Branches;
 
@@ -190,8 +202,9 @@ void renderBranchExecutionCounts(raw_ostream &OS,
   // Recursively collect branches for all file expansions.
   std::vector<LineColPair> NestedPath;
   unsigned PositionCounter = 0;
-  std::vector<NestedCountedRegion> ExBranches = collectNestedBranches(
-      Coverage, FileCoverage.getExpansions(), NestedPath, PositionCounter);
+  std::vector<NestedCountedRegion> ExBranches =
+      collectNestedBranches(Coverage, FileCoverage.getExpansions(), NestedPath,
+                            PositionCounter, Options);
 
   // Append Expansion Branches to Source Branches.
   appendNestedCountedRegions(ExBranches, Branches);
@@ -250,20 +263,24 @@ void renderBranchSummary(raw_ostream &OS, const FileCoverageSummary &Summary) {
 void renderFile(raw_ostream &OS, const coverage::CoverageMapping &Coverage,
                 const std::string &Filename,
                 const FileCoverageSummary &FileReport, bool ExportSummaryOnly,
-                bool SkipFunctions, bool SkipBranches, bool UnifyInstances) {
+                bool SkipFunctions, bool SkipBranches, bool UnifyInstances,
+                const CoverageViewOptions &Options) {
   OS << "SF:" << Filename << '\n';
 
   if (!ExportSummaryOnly && !SkipFunctions) {
-    renderFunctions(OS, Coverage.getCoveredFunctions(Filename));
+    renderFunctions(OS, Coverage.getCoveredFunctions(Filename), Filename,
+                    Options);
   }
   renderFunctionSummary(OS, FileReport);
 
   if (!ExportSummaryOnly) {
     // Calculate and render detailed coverage information for given file.
-    auto FileCoverage = Coverage.getCoverageForFile(Filename);
+    auto FileCoverage = applyCoverageExclusions(
+        Options.SourceExclusions, Coverage.getCoverageForFile(Filename));
     renderLineExecutionCounts(OS, FileCoverage);
     if (!SkipBranches)
-      renderBranchExecutionCounts(OS, Coverage, FileCoverage, UnifyInstances);
+      renderBranchExecutionCounts(OS, Coverage, FileCoverage, UnifyInstances,
+                                  Options);
   }
   if (!SkipBranches)
     renderBranchSummary(OS, FileReport);
@@ -276,10 +293,10 @@ void renderFiles(raw_ostream &OS, const coverage::CoverageMapping &Coverage,
                  ArrayRef<std::string> SourceFiles,
                  ArrayRef<FileCoverageSummary> FileReports,
                  bool ExportSummaryOnly, bool SkipFunctions, bool SkipBranches,
-                 bool UnifyInstances) {
+                 bool UnifyInstances, const CoverageViewOptions &Options) {
   for (unsigned I = 0, E = SourceFiles.size(); I < E; ++I)
     renderFile(OS, Coverage, SourceFiles[I], FileReports[I], ExportSummaryOnly,
-               SkipFunctions, SkipBranches, UnifyInstances);
+               SkipFunctions, SkipBranches, UnifyInstances, Options);
 }
 
 } // end anonymous namespace
@@ -299,5 +316,5 @@ void CoverageExporterLcov::renderRoot(ArrayRef<std::string> SourceFiles) {
                                                         SourceFiles, Options);
   renderFiles(OS, Coverage, SourceFiles, FileReports, Options.ExportSummaryOnly,
               Options.SkipFunctions, Options.SkipBranches,
-              Options.UnifyFunctionInstantiations);
+              Options.UnifyFunctionInstantiations, Options);
 }
diff --git a/llvm/tools/llvm-cov/CoverageFilters.cpp b/llvm/tools/llvm-cov/CoverageFilters.cpp
index 17fe18934b5b41..27b9470a21a1ee 100644
--- a/llvm/tools/llvm-cov/CoverageFilters.cpp
+++ b/llvm/tools/llvm-cov/CoverageFilters.cpp
@@ -17,16 +17,16 @@
 
 using namespace llvm;
 
-bool NameCoverageFilter::matches(
-    const coverage::CoverageMapping &,
-    const coverage::FunctionRecord &Function) const {
+bool NameCoverageFilter::matches(const coverage::CoverageMapping &,
+                                 const coverage::FunctionRecord &Function,
+                                 const CoverageExclusions *) const {
   StringRef FuncName = Function.Name;
   return FuncName.contains(Name);
 }
 
-bool NameRegexCoverageFilter::matches(
-    const coverage::CoverageMapping &,
-    const coverage::FunctionRecord &Function) const {
+bool NameRegexCoverageFilter::matches(const coverage::CoverageMapping &,
+                                      const coverage::FunctionRecord &Function,
+                                      const CoverageExclusions *) const {
   return llvm::Regex(Regex).match(Function.Name);
 }
 
@@ -36,22 +36,22 @@ bool NameRegexCoverageFilter::matchesFilename(StringRef Filename) const {
 }
 
 bool NameAllowlistCoverageFilter::matches(
-    const coverage::CoverageMapping &,
-    const coverage::FunctionRecord &Function) const {
+    const coverage::CoverageMapping &, const coverage::FunctionRecord &Function,
+    const CoverageExclusions *) const {
   return Allowlist.inSection("llvmcov", "allowlist_fun", Function.Name);
 }
 
-bool RegionCoverageFilter::matches(
-    const coverage::CoverageMapping &CM,
-    const coverage::FunctionRecord &Function) const {
-  return PassesThreshold(FunctionCoverageSummary::get(CM, Function)
+bool RegionCoverageFilter::matches(const coverage::CoverageMapping &CM,
+                                   const coverage::FunctionRecord &Function,
+                                   const CoverageExclusions *Exclusions) const {
+  return PassesThreshold(FunctionCoverageSummary::get(CM, Function, Exclusions)
                              .RegionCoverage.getPercentCovered());
 }
 
-bool LineCoverageFilter::matches(
-    const coverage::CoverageMapping &CM,
-    const coverage::FunctionRecord &Function) const {
-  return PassesThreshold(FunctionCoverageSummary::get(CM, Function)
+bool LineCoverageFilter::matches(const coverage::CoverageMapping &CM,
+                                 const coverage::FunctionRecord &Function,
+                                 const CoverageExclusions *Exclusions) const {
+  return PassesThreshold(FunctionCoverageSummary::get(CM, Function, Exclusions)
                              .LineCoverage.getPercentCovered());
 }
 
@@ -60,9 +60,10 @@ void CoverageFilters::push_back(std::unique_ptr<CoverageFilter> Filter) {
 }
 
 bool CoverageFilters::matches(const coverage::CoverageMapping &CM,
-                              const coverage::FunctionRecord &Function) const {
+                              const coverage::FunctionRecord &Function,
+                              const CoverageExclusions *Exclusions) const {
   for (const auto &Filter : Filters) {
-    if (Filter->matches(CM, Function))
+    if (Filter->matches(CM, Function, Exclusions))
       return true;
   }
   return false;
@@ -78,9 +79,10 @@ bool CoverageFilters::matchesFilename(StringRef Filename) const {
 
 bool CoverageFiltersMatchAll::matches(
     const coverage::CoverageMapping &CM,
-    const coverage::FunctionRecord &Function) const {
+    const coverage::FunctionRecord &Function,
+    const CoverageExclusions *Exclusions) const {
   for (const auto &Filter : Filters) {
-    if (!Filter->matches(CM, Function))
+    if (!Filter->matches(CM, Function, Exclusions))
       return false;
   }
   return true;
diff --git a/llvm/tools/llvm-cov/CoverageFilters.h b/llvm/tools/llvm-cov/CoverageFilters.h
index 39a19a0e65c5c4..e63780d5b122e3 100644
--- a/llvm/tools/llvm-cov/CoverageFilters.h
+++ b/llvm/tools/llvm-cov/CoverageFilters.h
@@ -18,6 +18,7 @@
 #include <vector>
 
 namespace llvm {
+class CoverageExclusions;
 class SpecialCaseList;
 
 namespace coverage {
@@ -32,7 +33,8 @@ class CoverageFilter {
 
   /// Return true if the function passes the requirements of this filter.
   virtual bool matches(const coverage::CoverageMapping &CM,
-                       const coverage::FunctionRecord &Function) const {
+                       const coverage::FunctionRecord &Function,
+                       const CoverageExclusions *Exclusions = nullptr) const {
     return true;
   }
 
@@ -50,7 +52,8 @@ class NameCoverageFilter : public CoverageFilter {
   NameCoverageFilter(StringRef Name) : Name(Name) {}
 
   bool matches(const coverage::CoverageMapping &CM,
-               const coverage::FunctionRecord &Function) const override;
+               const coverage::FunctionRecord &Function,
+               const CoverageExclusions *Exclusions = nullptr) const override;
 };
 
 /// Matches functions whose name matches a certain regular expression.
@@ -71,7 +74,8 @@ class NameRegexCoverageFilter : public CoverageFilter {
       : Regex(Regex), Type(Type) {}
 
   bool matches(const coverage::CoverageMapping &CM,
-               const coverage::FunctionRecord &Function) const override;
+               const coverage::FunctionRecord &Function,
+               const CoverageExclusions *Exclusions = nullptr) const override;
 
   bool matchesFilename(StringRef Filename) const override;
 };
@@ -86,7 +90,8 @@ class NameAllowlistCoverageFilter : public CoverageFilter {
       : Allowlist(Allowlist) {}
 
   bool matches(const coverage::CoverageMapping &CM,
-               const coverage::FunctionRecord &Function) const override;
+               const coverage::FunctionRecord &Function,
+               const CoverageExclusions *Exclusions = nullptr) const override;
 };
 
 /// Matches numbers that pass a certain threshold.
@@ -123,7 +128,8 @@ class RegionCoverageFilter : public CoverageFilter,
       : StatisticThresholdFilter(Op, Threshold) {}
 
   bool matches(const coverage::CoverageMapping &CM,
-               const coverage::FunctionRecord &Function) const override;
+               const coverage::FunctionRecord &Function,
+               const CoverageExclusions *Exclusions = nullptr) const override;
 };
 
 /// Matches functions whose line coverage percentage
@@ -135,7 +141,8 @@ class LineCoverageFilter : public CoverageFilter,
       : StatisticThresholdFilter(Op, Threshold) {}
 
   bool matches(const coverage::CoverageMapping &CM,
-               const coverage::FunctionRecord &Function) const override;
+               const coverage::FunctionRecord &Function,
+               const CoverageExclusions *Exclusions = nullptr) const override;
 };
 
 /// A collection of filters.
@@ -152,7 +159,8 @@ class CoverageFilters : public CoverageFilter {
   bool empty() const { return Filters.empty(); }
 
   bool matches(const coverage::CoverageMapping &CM,
-               const coverage::FunctionRecord &Function) const override;
+               const coverage::FunctionRecord &Function,
+               const CoverageExclusions *Exclusions = nullptr) const override;
 
   bool matchesFilename(StringRef Filename) const override;
 };
@@ -163,7 +171,8 @@ class CoverageFilters : public CoverageFilter {
 class CoverageFiltersMatchAll : public CoverageFilters {
 public:
   bool matches(const coverage::CoverageMapping &CM,
-               const coverage::FunctionRecord &Function) const override;
+               const coverage::FunctionRecord &Function,
+               const CoverageExclusions *Exclusions = nullptr) const override;
 };
 
 } // namespace llvm
diff --git a/llvm/tools/llvm-cov/CoverageReport.cpp b/llvm/tools/llvm-cov/CoverageReport.cpp
index 9b754d613370cf..72253f6f9239ba 100644
--- a/llvm/tools/llvm-cov/CoverageReport.cpp
+++ b/llvm/tools/llvm-cov/CoverageReport.cpp
@@ -11,6 +11,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "CoverageReport.h"
+#include "CoverageExclusions.h"
 #include "RenderingSupport.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/Support/Format.h"
@@ -428,7 +429,11 @@ void CoverageReport::renderFunctionReports(ArrayRef<std::string> Files,
     OS << "\n";
     FunctionCoverageSummary Totals("TOTAL");
     for (const auto &F : Functions) {
-      auto Function = FunctionCoverageSummary::get(Coverage, F);
+      if (Options.SourceExclusions &&
+          Options.SourceExclusions->isFunctionExcluded(F, Filename))
+        continue;
+      auto Function =
+          FunctionCoverageSummary::get(Coverage, F, Options.SourceExclusions);
       ++Totals.ExecutionCount;
       Totals.RegionCoverage += Function.RegionCoverage;
       Totals.LineCoverage += Function.LineCoverage;
@@ -451,9 +456,13 @@ void CoverageReport::prepareSingleFileReport(const StringRef Filename,
   for (const auto &Group : Coverage->getInstantiationGroups(Filename)) {
     std::vector<FunctionCoverageSummary> InstantiationSummaries;
     for (const coverage::FunctionRecord *F : Group.getInstantiations()) {
-      if (!Filters->matches(*Coverage, *F))
+      if (Options.SourceExclusions &&
+          Options.SourceExclusions->isFunctionExcluded(*F, Filename))
+        continue;
+      if (!Filters->matches(*Coverage, *F, Options.SourceExclusions))
         continue;
-      auto InstantiationSummary = FunctionCoverageSummary::get(*Coverage, *F);
+      auto InstantiationSummary =
+          FunctionCoverageSummary::get(*Coverage, *F, Options.SourceExclusions);
       FileReport->addInstantiation(InstantiationSummary);
       InstantiationSummaries.push_back(InstantiationSummary);
     }
diff --git a/llvm/tools/llvm-cov/CoverageSummaryInfo.cpp b/llvm/tools/llvm-cov/CoverageSummaryInfo.cpp
index be9aef8416e8da..33ff7c0ec18eec 100644
--- a/llvm/tools/llvm-cov/CoverageSummaryInfo.cpp
+++ b/llvm/tools/llvm-cov/CoverageSummaryInfo.cpp
@@ -12,6 +12,7 @@
 //===----------------------------------------------------------------------===//
 
 #include "CoverageSummaryInfo.h"
+#include "CoverageExclusions.h"
 
 using namespace llvm;
 using namespace coverage;
@@ -38,12 +39,14 @@ static auto sumBranches(const ArrayRef<CountedRegion> &Branches) {
 
 static BranchCoverageInfo
 sumBranchExpansions(const CoverageMapping &CM,
-                    ArrayRef<ExpansionRecord> Expansions) {
+                    ArrayRef<ExpansionRecord> Expansions,
+                    const CoverageExclusions *Exclusions) {
   BranchCoverageInfo BranchCoverage;
   for (const auto &Expansion : Expansions) {
-    auto CE = CM.getCoverageForExpansion(Expansion);
+    auto CE = applyCoverageExclusions(Exclusions,
+                                      CM.getCoverageForExpansion(Expansion));
     BranchCoverage += sumBranches(CE.getBranches());
-    BranchCoverage += sumBranchExpansions(CM, CE.getExpansions());
+    BranchCoverage += sumBranchExpansions(CM, CE.getExpansions(), Exclusions);
   }
   return BranchCoverage;
 }
@@ -98,16 +101,23 @@ CoverageDataSummary::CoverageDataSummary(const CoverageData &CD,
 
 FunctionCoverageSummary
 FunctionCoverageSummary::get(const CoverageMapping &CM,
-                             const coverage::FunctionRecord &Function) {
-  CoverageData CD = CM.getCoverageForFunction(Function);
+                             const coverage::FunctionRecord &Function,
+                             const CoverageExclusions *Exclusions) {
+  CoverageData CD =
+      applyCoverageExclusions(Exclusions, CM.getCoverageForFunction(Function));
+
+  SmallVector<CountedRegion> CodeRegions;
+  llvm::copy_if(Function.CountedRegions, std::back_inserter(CodeRegions),
+                [&](const CountedRegion &Region) {
+                  return !Exclusions ||
+                         !Exclusions->isRegionExcluded(Function, Region);
+                });
 
   auto Summary =
       FunctionCoverageSummary(Function.Name, Function.ExecutionCount);
-
-  Summary += CoverageDataSummary(CD, Function.CountedRegions);
-
-  // Compute the branch coverage, including branches from expansions.
-  Summary.BranchCoverage += sumBranchExpansions(CM, CD.getExpansions());
+  Summary += CoverageDataSummary(CD, CodeRegions);
+  Summary.BranchCoverage +=
+      sumBranchExpansions(CM, CD.getExpansions(), Exclusions);
 
   return Summary;
 }
diff --git a/llvm/tools/llvm-cov/CoverageSummaryInfo.h b/llvm/tools/llvm-cov/CoverageSummaryInfo.h
index d9210676c41bf3..4a30e83e8788d2 100644
--- a/llvm/tools/llvm-cov/CoverageSummaryInfo.h
+++ b/llvm/tools/llvm-cov/CoverageSummaryInfo.h
@@ -19,6 +19,8 @@
 
 namespace llvm {
 
+class CoverageExclusions;
+
 /// Provides information about region coverage for a function/file.
 class RegionCoverageInfo {
   /// The number of regions that were executed at least once.
@@ -252,8 +254,10 @@ struct FunctionCoverageSummary : CoverageDataSummary {
 
   /// Compute the code coverage summary for the given function coverage
   /// mapping record.
-  static FunctionCoverageSummary get(const coverage::CoverageMapping &CM,
-                                     const coverage::FunctionRecord &Function);
+  static FunctionCoverageSummary
+  get(const coverage::CoverageMapping &CM,
+      const coverage::FunctionRecord &Function,
+      const CoverageExclusions *Exclusions = nullptr);
 
   /// Compute the code coverage summary for an instantiation group \p Group,
   /// given a list of summaries for each instantiation in \p Summaries.
diff --git a/llvm/tools/llvm-cov/CoverageViewOptions.h b/llvm/tools/llvm-cov/CoverageViewOptions.h
index 4826d4adb53bf3..a2f13c64714b6b 100644
--- a/llvm/tools/llvm-cov/CoverageViewOptions.h
+++ b/llvm/tools/llvm-cov/CoverageViewOptions.h
@@ -15,6 +15,8 @@
 
 namespace llvm {
 
+class CoverageExclusions;
+
 /// The options for displaying the code coverage information.
 struct CoverageViewOptions {
   enum class OutputFormat {
@@ -49,6 +51,7 @@ struct CoverageViewOptions {
   bool SkipFunctions;
   bool SkipBranches;
   bool BinaryCounters;
+  bool RespectLcovExclusionMarkers = false;
   OutputFormat Format;
   BranchOutputType ShowBranches;
   std::string ShowOutputDirectory;
@@ -60,6 +63,7 @@ struct CoverageViewOptions {
   std::string CompilationDirectory;
   float HighCovWatermark;
   float LowCovWatermark;
+  const CoverageExclusions *SourceExclusions = nullptr;
 
   /// Change the output's stream color if the colors are enabled.
   ColoredRawOstream colored_ostream(raw_ostream &OS,

>From e3d5fd1fb0a4e3faf02ef3aa1ace22e2ce5d0193 Mon Sep 17 00:00:00 2001
From: "Yaxun (Sam) Liu" <yaxun.liu at amd.com>
Date: Wed, 2 Sep 2026 22:49:06 -0400
Subject: [PATCH 2/4] [llvm-cov] Make exclusion test path portable

Coverage reports use native path separators. Accept both forward and backward slashes in the output-directory check so the test passes on Windows.
---
 llvm/test/tools/llvm-cov/lcov-exclusion-markers.test | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/test/tools/llvm-cov/lcov-exclusion-markers.test b/llvm/test/tools/llvm-cov/lcov-exclusion-markers.test
index 157474b632cb86..1de872e9d39120 100644
--- a/llvm/test/tools/llvm-cov/lcov-exclusion-markers.test
+++ b/llvm/test/tools/llvm-cov/lcov-exclusion-markers.test
@@ -49,7 +49,7 @@
 
 # FILTERED-THRESHOLD-NOT: _Z24partially_excluded_blocki:
 # FILTERED-THRESHOLD: _Z8includedi:
-# FILTERED-INDEX: /tmp/lcov-exclusion-markers.cpp{{ +}}4{{ +}}1{{ +}}75.00%{{ +}}1{{ +}}0{{ +}}100.00%{{ +}}5{{ +}}1{{ +}}80.00%
+# FILTERED-INDEX: {{/|\\}}tmp{{/|\\}}lcov-exclusion-markers.cpp{{ +}}4{{ +}}1{{ +}}75.00%{{ +}}1{{ +}}0{{ +}}100.00%{{ +}}5{{ +}}1{{ +}}80.00%
 
 # RUN: llvm-cov report %t.o -instr-profile=%t.profdata \
 # RUN:   -path-equivalence=/tmp,%S/Inputs \

>From b1b52ff6d6ca31f786698dd3606668763e62ffde Mon Sep 17 00:00:00 2001
From: "Yaxun (Sam) Liu" <yaxun.liu at amd.com>
Date: Wed, 2 Sep 2026 23:53:58 -0400
Subject: [PATCH 3/4] [llvm-cov] Make missing-file checks portable

Windows spells the system error with a lowercase "no". Accept both capitalizations in the missing-file checks.
---
 llvm/test/tools/llvm-cov/lcov-exclusion-markers.test | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/llvm/test/tools/llvm-cov/lcov-exclusion-markers.test b/llvm/test/tools/llvm-cov/lcov-exclusion-markers.test
index 1de872e9d39120..06098f67076bb7 100644
--- a/llvm/test/tools/llvm-cov/lcov-exclusion-markers.test
+++ b/llvm/test/tools/llvm-cov/lcov-exclusion-markers.test
@@ -209,8 +209,8 @@
 # ORPHAN: LCOV_EXCL_STOP at line 1 has no matching LCOV_EXCL_START
 # OVERLAP: overlapping LCOV_EXCL_START at line 2; previous block started at line 1
 # STOP-LINE: 11|       |  return 0; // LCOV_EXCL_STOP LCOV_EXCL_LINE
-# MISSING: error: {{.*}}lcov-exclusion-markers.cpp: No such file or directory
-# MISSING-REFERENCE: error: {{.*}}prevent_false_instantiations.h: No such file or directory
+# MISSING: error: {{.*}}lcov-exclusion-markers.cpp: {{[Nn]o}} such file or directory
+# MISSING-REFERENCE: error: {{.*}}prevent_false_instantiations.h: {{[Nn]o}} such file or directory
 
 # BOUNDARY-SHOW: 4|       |  int x = 0; // LCOV_EXCL_LINE
 # BOUNDARY-SHOW-NEXT: 5|      0|  return x;

>From aad9a68181dfe80b31881a654f600926dc3c274e Mon Sep 17 00:00:00 2001
From: "Yaxun (Sam) Liu" <yaxun.liu at amd.com>
Date: Fri, 25 Sep 2026 17:57:57 -0400
Subject: [PATCH 4/4] [llvm-cov] Test MC/DC records with LCOV exclusions

---
 .../tools/llvm-cov/lcov-exclusion-mcdc.test   | 66 +++++++++++++++++++
 1 file changed, 66 insertions(+)
 create mode 100644 llvm/test/tools/llvm-cov/lcov-exclusion-mcdc.test

diff --git a/llvm/test/tools/llvm-cov/lcov-exclusion-mcdc.test b/llvm/test/tools/llvm-cov/lcov-exclusion-mcdc.test
new file mode 100644
index 00000000000000..4277f0665c3112
--- /dev/null
+++ b/llvm/test/tools/llvm-cov/lcov-exclusion-mcdc.test
@@ -0,0 +1,66 @@
+# The source below matches Inputs/mcdc-general.cpp except for the marker on
+# line 12. Keep its decision lines aligned with mcdc-general.o.
+# RUN: split-file %s %t.sources
+# RUN: llvm-profdata merge %S/Inputs/mcdc-general.proftext -o %t.profdata
+# RUN: llvm-cov report %S/Inputs/mcdc-general.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=.,%t.sources --show-mcdc-summary \
+# RUN:   %t.sources/mcdc-general.cpp | FileCheck %s --check-prefix=BASE
+# RUN: llvm-cov report %S/Inputs/mcdc-general.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=.,%t.sources --show-mcdc-summary \
+# RUN:   --respect-lcov-exclusion-markers %t.sources/mcdc-general.cpp \
+# RUN:   | FileCheck %s --check-prefix=FILTERED
+# RUN: llvm-cov show %S/Inputs/mcdc-general.o -instr-profile=%t.profdata \
+# RUN:   -path-equivalence=.,%t.sources --show-mcdc \
+# RUN:   --respect-lcov-exclusion-markers %t.sources/mcdc-general.cpp \
+# RUN:   | FileCheck %s --check-prefix=SHOW
+# RUN: llvm-cov export --format=text %S/Inputs/mcdc-general.o \
+# RUN:   -instr-profile=%t.profdata -path-equivalence=.,%t.sources \
+# RUN:   --respect-lcov-exclusion-markers %t.sources/mcdc-general.cpp \
+# RUN:   | FileCheck %s --check-prefix=JSON
+
+# BASE: TOTAL{{.*}}24{{ +}}2{{ +}}91.67%{{ +}}12{{ +}}2{{ +}}83.33%
+# FILTERED: TOTAL{{.*}}16{{ +}}2{{ +}}87.50%{{ +}}8{{ +}}2{{ +}}75.00%
+# SHOW: 12|       |  if ((a && b) || (c && d)) // LCOV_EXCL_LINE
+# SHOW-NOT: MC/DC Decision Region (12:
+# SHOW: MC/DC Decision Region (15:7)
+# SHOW: MC/DC Decision Region (18:7)
+# JSON: "mcdc_records":{{\[\[}}15,7
+# JSON: "mcdc":{"count":8,"covered":6,"notcovered":2
+
+#--- mcdc-general.cpp
+#include <stdio.h>
+
+
+
+
+
+
+
+
+void test(bool a, bool b, bool c, bool d) {
+
+  if ((a && b) || (c && d)) // LCOV_EXCL_LINE
+    printf("test1 decision true\n");
+
+  if (b && c) if (a && d)
+    printf("test2 decision true\n");
+
+  if ((c && d) &&
+      (a && b))
+    printf("test3 decision true\n");
+}
+
+int main()
+{
+    test(false,false,false,false);
+    test(true,false,true,false);
+    test(true,false,true,true);
+    test(true,true,false,false);
+
+    test(true,false,false,false);
+    test(true,true,true,true);
+    test(false,true,true,false);
+
+    (void)0;
+    return 0;
+}



More information about the llvm-commits mailing list