public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] Add condition coverage profiling
@ 2022-10-12 10:16 Jørgen Kvalsvik
  2022-10-18  0:17 ` Hans-Peter Nilsson
  2022-10-25  6:33 ` Ping " Jørgen Kvalsvik
  0 siblings, 2 replies; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-10-12 10:16 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, Jørgen Kvalsvik

This patch adds support in gcc+gcov for modified condition/decision
coverage (MC/DC) with the -fprofile-conditions flag. MC/DC is a type of
test/code coverage and it is particularly important in the avation and
automotive industries for safety-critical applications. MC/DC it is
required for or recommended by:

    * DO-178C for the most critical software (Level A) in avionics
    * IEC 61508 for SIL 4
    * ISO 26262-6 for ASIL D

From the SQLite webpage:

    Two methods of measuring test coverage were described above:
    "statement" and "branch" coverage. There are many other test
    coverage metrics besides these two. Another popular metric is
    "Modified Condition/Decision Coverage" or MC/DC. Wikipedia defines
    MC/DC as follows:

        * Each decision tries every possible outcome.
        * Each condition in a decision takes on every possible outcome.
        * Each entry and exit point is invoked.
        * Each condition in a decision is shown to independently affect
          the outcome of the decision.

    In the C programming language where && and || are "short-circuit"
    operators, MC/DC and branch coverage are very nearly the same thing.
    The primary difference is in boolean vector tests. One can test for
    any of several bits in bit-vector and still obtain 100% branch test
    coverage even though the second element of MC/DC - the requirement
    that each condition in a decision take on every possible outcome -
    might not be satisfied.

    https://sqlite.org/testing.html#mcdc

Wahlen, Heimdahl, and De Silva "Efficient Test Coverage Measurement for
MC/DC" describes an algorithm for adding instrumentation by carrying
over information from the AST, but my algorithm analyses the the control
flow graph to instrument for coverage. This has the benefit of being
programming language independent and faithful to compiler decisions
and transformations, although I have only tested it on constructs in C
and C++, see testsuite/gcc.misc-tests and testsuite/g++.dg.

Like Wahlen et al this implementation records coverage in fixed-size
bitsets which gcov knows how to interpret. This is very fast, but
introduces a limit on the number of terms in a single boolean
expression, the number of bits in a gcov_unsigned_type (which is
typedef'd to uint64_t), so for most practical purposes this would be
acceptable. This limitation is in the implementation and not the
algorithm, so support for more conditions can be added by also
introducing arbitrary-sized bitsets.

For space overhead, the instrumentation needs two accumulators
(gcov_unsigned_type) per condition in the program which will be written
to the gcov file. In addition, every function gets a pair of local
accumulators, but these accmulators are reused between conditions in the
same function.

For time overhead, there is a zeroing of the local accumulators for
every condition and one or two bitwise operation on every edge taken in
the an expression.

In action it looks pretty similar to the branch coverage. The -g short
opt carries no significance, but was chosen because it was an available
option with the upper-case free too.

gcov --conditions:

        3:   17:void fn (int a, int b, int c, int d) {
        3:   18:    if ((a && (b || c)) && d)
condition outcomes covered 3/8
condition  0 not covered (true false)
condition  1 not covered (true)
condition  2 not covered (true)
condition  3 not covered (true)
        1:   19:        x = 1;
        -:   20:    else
        2:   21:        x = 2;
        3:   22:}

gcov --conditions --json-format:

    "conditions": [
        {
            "not_covered_false": [
                0
            ],
            "count": 8,
            "covered": 3,
            "not_covered_true": [
                0,
                1,
                2,
                3
            ]
        }
    ],

Some expressions, mostly those without else-blocks, are effectively
"rewritten" in the CFG construction making the algorithm unable to
distinguish them:

and.c:

    if (a && b && c)
        x = 1;

ifs.c:

    if (a)
        if (b)
            if (c)
                x = 1;

gcc will build the same graph for both these programs, and gcov will
report boths as 3-term expressions. It is vital that it is not
interpreted the other way around (which is consistent with the shape of
the graph) because otherwise the masking would be wrong for the and.c
program which is a more severe error. While surprising, users would
probably expect some minor rewriting of semantically-identical
expressions.

and.c.gcov:
    #####:    2:    if (a && b && c)
decisions covered 6/6
    #####:    3:        x = 1;

ifs.c.gcov:
    #####:    2:    if (a)
    #####:    3:        if (b)
    #####:    4:            if (c)
    #####:    5:                x = 1;
condition outcomes covered 6/6

Adding else clauses alters the program (ifs.c can have 3 elses, and.c
only 1) and coverage becomes less surprising

ifs.c.gcov:
    #####:    2:    if (a)
condition outcomes covered 2/2
    #####:    4:    {
    #####:    4:        if (b)
condition outcomes covered 2/2
              5:        {
    #####:    6:            if (c)
condition outcomes covered 2/2
    #####:    7:                x = 1;
    #####:    8:        }
    #####:    9:        else
    #####:   10:            x = 2;
    #####:   11:    }
    #####:   12:    else
    #####:   13:        x = 3;

Since the algorithm works on CFGs, it cannot detect some ternary
operator introduced conditionals. For example, int x = a ? 0 : 1 in
gimple becomes _x = (_a == 0). From source you would expect coverage,
but it gets neither branch nor condition coverage. For completeness, it
could be achieved by scanning all gimple statements for such
comparisons, and insert an extra instruction for recording the outcome.

The test suite contains a lot of small programs functions. Some of these
were designed by hand to test for specific behaviours and graph shapes,
and some are previously-failed test cases in other programs adapted into
the test suite.

Alternative author email: Jørgen Kvalsvik <j@lambda.is>

gcc/ChangeLog:

	* builtins.cc (expand_builtin_fork_or_exec): Check
	profile_condition_flag.
	* collect2.cc (main): Add -fno-profile-conditions to OBSTACK.
	* common.opt: Add new options -fprofile-conditions and
	* doc/gcov.texi: Add --conditions documentation.
	* doc/invoke.texi: Add -fprofile-conditions documentation.
	* gcc.cc: Link gcov on -fprofile-conditions.
	* gcov-counter.def (GCOV_COUNTER_CONDS): New.
	* gcov-dump.cc (tag_conditions): New.
	* gcov-io.h (GCOV_TAG_CONDS): New.
	(GCOV_TAG_CONDS_LENGTH): Likewise.
	(GCOV_TAG_CONDS_NUM): Likewise.
	* gcov.cc (class condition_info): New.
	(condition_info::condition_info): New.
	(condition_info::popcount): New.
	(struct coverage_info): New.
	(add_condition_counts): New.
	(output_conditions): New.
	(print_usage): Add -g, --conditions.
	(process_args): Likewise.
	(output_intermediate_json_line): Output conditions.
	(read_graph_file): Read conditions counters.
	(read_count_file): Read conditions counters.
	(file_summary): Print conditions.
	(accumulate_line_info): Accumulate conditions.
	(output_line_details): Print conditions.
	* ipa-inline.cc (can_early_inline_edge_p): Check
	profile_condition_flag.
	* ipa-split.cc (pass_split_functions::gate): Likewise.
	* passes.cc (finish_optimization_passes): Likewise.
	* profile.cc (find_conditions): New declaration.
	(cov_length): Likewise.
	(cov_blocks): Likewise.
	(cov_masks): Likewise.
	(cov_free): Likewise.
	(instrument_decisions): New.
	(read_thunk_profile): Control output to file.
	(branch_prob): Call find_conditions, instrument_decisions.
	(init_branch_prob): Add total_num_conds.
	(end_branch_prob): Likewise.
	* tree-profile.cc (struct conds_ctx): New.
	(CONDITIONS_MAX_TERMS): New.
	(EDGE_CONDITION): New.
	(cmp_index_map): New.
	(index_of): New.
	(block_conditional_p): New.
	(edge_conditional_p): New.
	(single): New.
	(single_edge): New.
	(contract_edge): New.
	(contract_edge_up): New.
	(merge_split_outcome): New.
	(ancestors_of): New.
	(struct outcomes): New.
	(conditional_succs): New.
	(condition_index): New.
	(masking_vectors): New.
	(cond_reachable_from): New.
	(neighborhood): New.
	(isolate_expression): New.
	(emit_bitwise_op): New.
	(make_index_map_visit): New.
	(make_index_map): New.
	(collect_conditions): New.
	(yes): New.
	(struct condcov): New.
	(cov_length): New.
	(cov_blocks): New.
	(cov_masks): New.
	(cov_free): New.
	(find_conditions): New.
	(instrument_decisions): New.
	(tree_profiling): Check profile_condition_flag.
	(pass_ipa_tree_profile::gate): Likewise.

libgcc/ChangeLog:

	* libgcov-merge.c (__gcov_merge_ior): New dummy function.

gcc/testsuite/ChangeLog:

	* lib/gcov.exp: Add condition coverage test function.
	* g++.dg/gcov/gcov-18.C: New test.
	* gcc.misc-tests/gcov-19.c: New test.
	* gcc.misc-tests/gcov-20.c: New test.
	* gcc.misc-tests/gcov-21.c: New test.
---
 gcc/builtins.cc                        |    2 +-
 gcc/collect2.cc                        |    7 +-
 gcc/common.opt                         |    8 +
 gcc/doc/gcov.texi                      |   36 +
 gcc/doc/invoke.texi                    |   19 +
 gcc/gcc.cc                             |    4 +-
 gcc/gcov-counter.def                   |    3 +
 gcc/gcov-dump.cc                       |   24 +
 gcc/gcov-io.h                          |    3 +
 gcc/gcov.cc                            |  200 +++-
 gcc/ipa-inline.cc                      |    2 +-
 gcc/ipa-split.cc                       |    3 +-
 gcc/passes.cc                          |    3 +-
 gcc/profile.cc                         |   84 +-
 gcc/testsuite/g++.dg/gcov/gcov-18.C    |  234 +++++
 gcc/testsuite/gcc.misc-tests/gcov-19.c | 1250 ++++++++++++++++++++++++
 gcc/testsuite/gcc.misc-tests/gcov-20.c |   22 +
 gcc/testsuite/gcc.misc-tests/gcov-21.c |   16 +
 gcc/testsuite/lib/gcov.exp             |  191 +++-
 gcc/tree-profile.cc                    | 1050 +++++++++++++++++++-
 libgcc/libgcov-merge.c                 |    5 +
 21 files changed, 3138 insertions(+), 28 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-18.C
 create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-19.c
 create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-20.c
 create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-21.c

diff --git a/gcc/builtins.cc b/gcc/builtins.cc
index 5f319b28030..972ae7e2040 100644
--- a/gcc/builtins.cc
+++ b/gcc/builtins.cc
@@ -5884,7 +5884,7 @@ expand_builtin_fork_or_exec (tree fn, tree exp, rtx target, int ignore)
   tree call;
 
   /* If we are not profiling, just call the function.  */
-  if (!profile_arc_flag)
+  if (!profile_arc_flag && !profile_condition_flag)
     return NULL_RTX;
 
   /* Otherwise call the wrapper.  This should be equivalent for the rest of
diff --git a/gcc/collect2.cc b/gcc/collect2.cc
index d81c7f28f16..0cd8bf4a3a3 100644
--- a/gcc/collect2.cc
+++ b/gcc/collect2.cc
@@ -1032,9 +1032,9 @@ main (int argc, char **argv)
       lto_mode = LTO_MODE_LTO;
   }
 
-  /* -fno-profile-arcs -fno-test-coverage -fno-branch-probabilities
-     -fno-exceptions -w -fno-whole-program */
-  num_c_args += 6;
+  /* -fno-profile-arcs -fno-profile-conditions -fno-test-coverage
+     -fno-branch-probabilities -fno-exceptions -w -fno-whole-program */
+  num_c_args += 7;
 
   c_argv = XCNEWVEC (char *, num_c_args);
   c_ptr = CONST_CAST2 (const char **, char **, c_argv);
@@ -1230,6 +1230,7 @@ main (int argc, char **argv)
     }
   obstack_free (&temporary_obstack, temporary_firstobj);
   *c_ptr++ = "-fno-profile-arcs";
+  *c_ptr++ = "-fno-profile-conditions";
   *c_ptr++ = "-fno-test-coverage";
   *c_ptr++ = "-fno-branch-probabilities";
   *c_ptr++ = "-fno-exceptions";
diff --git a/gcc/common.opt b/gcc/common.opt
index bce3e514f65..5d0682e283a 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -859,6 +859,10 @@ Wcoverage-invalid-line-number
 Common Var(warn_coverage_invalid_linenum) Init(1) Warning
 Warn in case a function ends earlier than it begins due to an invalid linenum macros.
 
+Wcoverage-too-many-conditions
+Common Var(warn_too_many_conditions) Init(1) Warning
+Warn when a conditional has too many terms and coverage gives up.
+
 Wmissing-profile
 Common Var(warn_missing_profile) Init(1) Warning
 Warn in case profiles in -fprofile-use do not exist.
@@ -2318,6 +2322,10 @@ fprofile-arcs
 Common Var(profile_arc_flag)
 Insert arc-based program profiling code.
 
+fprofile-conditions
+Common Var(profile_condition_flag)
+Insert condition coverage profiling code.
+
 fprofile-dir=
 Common Joined RejectNegative Var(profile_data_prefix)
 Set the top-level directory for storing the profile data.
diff --git a/gcc/doc/gcov.texi b/gcc/doc/gcov.texi
index a1f7d26e610..7f2c0d00d94 100644
--- a/gcc/doc/gcov.texi
+++ b/gcc/doc/gcov.texi
@@ -124,6 +124,7 @@ gcov [@option{-v}|@option{--version}] [@option{-h}|@option{--help}]
      [@option{-a}|@option{--all-blocks}]
      [@option{-b}|@option{--branch-probabilities}]
      [@option{-c}|@option{--branch-counts}]
+     [@option{-g}|@option{--conditions}]
      [@option{-d}|@option{--display-progress}]
      [@option{-f}|@option{--function-summaries}]
      [@option{-j}|@option{--json-format}]
@@ -169,6 +170,13 @@ be shown, unless the @option{-u} option is given.
 Write branch frequencies as the number of branches taken, rather than
 the percentage of branches taken.
 
+@item -g
+@itemx --conditions
+Write condition coverage to the output file, and write condition summary info
+to the standard output.  This option allows you to see if the conditions in
+your program at least once had an independent effect on the outcome of the
+boolean expression (modified condition/decision coverage).
+
 @item -d
 @itemx --display-progress
 Display the progress on the standard output.
@@ -293,6 +301,7 @@ Each @var{line} has the following form:
 @{
   "branches": ["$branch"],
   "count": 2,
+  "conditions": ["$condition"],
   "line_number": 15,
   "unexecuted_block": false,
   "function_name": "foo",
@@ -341,6 +350,33 @@ Fields of the @var{branch} element have following semantics:
 @var{throw}: true when the branch is an exceptional branch
 @end itemize
 
+Each @var{condition} element have the following semantics:
+
+@smallexample
+@{
+  "count": 4,
+  "covered": 2,
+  "not_covered_false": [],
+  "not_covered_true": [0, 1],
+@}
+@end smallexample
+
+Fields of the @var{condition} element have following semantics:
+
+@itemize @bullet
+@item
+@var{count}: number of conditions in this expression
+
+@item
+@var{covered}: number of covered conditions in this expression
+
+@item
+@var{not_covered_true}: terms, by index, not seen as true in this expression
+
+@item
+@var{not_covered_false}: terms, by index, not seen as false in this expression
+@end itemize
+
 @item -H
 @itemx --human-readable
 Write counts in human readable format (like 24.6k).
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index a9ecc4426a4..62d48df369d 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -614,6 +614,7 @@ Objective-C and Objective-C++ Dialects}.
 @item Program Instrumentation Options
 @xref{Instrumentation Options,,Program Instrumentation Options}.
 @gccoptlist{-p  -pg  -fprofile-arcs  --coverage  -ftest-coverage @gol
+-fprofile-conditions @gol
 -fprofile-abs-path @gol
 -fprofile-dir=@var{path}  -fprofile-generate  -fprofile-generate=@var{path} @gol
 -fprofile-info-section  -fprofile-info-section=@var{name} @gol
@@ -6140,6 +6141,13 @@ error.  @option{-Wno-coverage-invalid-line-number} can be used to disable the
 warning or @option{-Wno-error=coverage-invalid-line-number} can be used to
 disable the error.
 
+@item -Wno-coverage-too-many-conditions
+@opindex Wno-coverage-too-many-conditions
+@opindex Wcoverage-too-many-conditions
+Warn in case a condition have too many terms and GCC gives up coverage.
+Coverage is given up when there are more terms in the conditional than there
+are bits in a @code{gcov_type_unsigned}.  This warning is enabled by default.
+
 @item -Wno-cpp @r{(C, Objective-C, C++, Objective-C++ and Fortran only)}
 @opindex Wno-cpp
 @opindex Wcpp
@@ -15839,6 +15847,13 @@ E.g. @code{gcc a.c b.c -o binary} would generate @file{binary-a.gcda} and
 
 @xref{Cross-profiling}.
 
+@item -fprofile-conditions
+@opindex fprofile-conditions
+Add code so that program conditions are instrumented.  During execution the
+program records what terms in a conditional contributes to a decision.  The
+data may be used to verify that all terms in a booleans are tested and have an
+effect on the outcome of a condition.
+
 @cindex @command{gcov}
 @item --coverage
 @opindex coverage
@@ -15899,6 +15914,10 @@ executed.  When an arc is the only exit or only entrance to a block, the
 instrumentation code can be added to the block; otherwise, a new basic
 block must be created to hold the instrumentation code.
 
+With @option{-fprofile-conditions}, for each conditional in your program GCC
+creates a bitset and records the boolean values that have an independent effect
+on the outcome of that expression.
+
 @need 2000
 @item -ftest-coverage
 @opindex ftest-coverage
diff --git a/gcc/gcc.cc b/gcc/gcc.cc
index afb23cd07b0..601e48377de 100644
--- a/gcc/gcc.cc
+++ b/gcc/gcc.cc
@@ -1145,7 +1145,7 @@ proper position among the other output files.  */
 	%:include(libgomp.spec)%(link_gomp)}\
     %{fgnu-tm:%:include(libitm.spec)%(link_itm)}\
     %(mflib) " STACK_SPLIT_SPEC "\
-    %{fprofile-arcs|fprofile-generate*|coverage:-lgcov} " SANITIZER_SPEC " \
+    %{fprofile-arcs|fprofile-conditions|fprofile-generate*|coverage:-lgcov} " SANITIZER_SPEC " \
     %{!nostdlib:%{!r:%{!nodefaultlibs:%(link_ssp) %(link_gcc_c_sequence)}}}\
     %{!nostdlib:%{!r:%{!nostartfiles:%E}}} %{T*}  \n%(post_link) }}}}}}"
 #endif
@@ -1262,7 +1262,7 @@ static const char *cc1_options =
  %{!fsyntax-only:%{S:%W{o*}%{!o*:-o %w%b.s}}}\
  %{fsyntax-only:-o %j} %{-param*}\
  %{coverage:-fprofile-arcs -ftest-coverage}\
- %{fprofile-arcs|fprofile-generate*|coverage:\
+ %{fprofile-arcs|fprofile-conditions|fprofile-generate*|coverage:\
    %{!fprofile-update=single:\
      %{pthread:-fprofile-update=prefer-atomic}}}";
 
diff --git a/gcc/gcov-counter.def b/gcc/gcov-counter.def
index 6d2182bd3db..96563a59a45 100644
--- a/gcc/gcov-counter.def
+++ b/gcc/gcov-counter.def
@@ -49,3 +49,6 @@ DEF_GCOV_COUNTER(GCOV_COUNTER_IOR, "ior", _ior)
 
 /* Time profile collecting first run of a function */
 DEF_GCOV_COUNTER(GCOV_TIME_PROFILER, "time_profiler", _time_profile)
+
+/* Conditions.  The counter is interpreted as a bit-set.  */
+DEF_GCOV_COUNTER(GCOV_COUNTER_CONDS, "conditions", _ior)
diff --git a/gcc/gcov-dump.cc b/gcc/gcov-dump.cc
index 03023bfb226..6dc1df6e3e1 100644
--- a/gcc/gcov-dump.cc
+++ b/gcc/gcov-dump.cc
@@ -38,6 +38,7 @@ static void print_version (void);
 static void tag_function (const char *, unsigned, int, unsigned);
 static void tag_blocks (const char *, unsigned, int, unsigned);
 static void tag_arcs (const char *, unsigned, int, unsigned);
+static void tag_conditions (const char *, unsigned, int, unsigned);
 static void tag_lines (const char *, unsigned, int, unsigned);
 static void tag_counters (const char *, unsigned, int, unsigned);
 static void tag_summary (const char *, unsigned, int, unsigned);
@@ -77,6 +78,7 @@ static const tag_format_t tag_table[] =
   {GCOV_TAG_FUNCTION, "FUNCTION", tag_function},
   {GCOV_TAG_BLOCKS, "BLOCKS", tag_blocks},
   {GCOV_TAG_ARCS, "ARCS", tag_arcs},
+  {GCOV_TAG_CONDS, "CONDITIONS", tag_conditions},
   {GCOV_TAG_LINES, "LINES", tag_lines},
   {GCOV_TAG_OBJECT_SUMMARY, "OBJECT_SUMMARY", tag_summary},
   {0, NULL, NULL}
@@ -392,6 +394,28 @@ tag_arcs (const char *filename ATTRIBUTE_UNUSED,
     }
 }
 
+static void
+tag_conditions (const char *filename ATTRIBUTE_UNUSED,
+		unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED,
+		unsigned depth)
+{
+  unsigned n_conditions = GCOV_TAG_CONDS_NUM (length);
+
+  printf (" %u conditionals", n_conditions);
+  if (flag_dump_contents)
+    {
+      for (unsigned ix = 0; ix != n_conditions; ix++)
+	{
+	  const unsigned blockno = gcov_read_unsigned ();
+	  const unsigned nterms = gcov_read_unsigned ();
+
+	  printf ("\n");
+	  print_prefix (filename, depth, gcov_position ());
+	  printf (VALUE_PADDING_PREFIX "block %u:", blockno);
+	  printf (" %u", nterms);
+	}
+    }
+}
 static void
 tag_lines (const char *filename ATTRIBUTE_UNUSED,
 	   unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED,
diff --git a/gcc/gcov-io.h b/gcc/gcov-io.h
index e91cd736556..a9b52cc6abc 100644
--- a/gcc/gcov-io.h
+++ b/gcc/gcov-io.h
@@ -261,6 +261,9 @@ typedef uint64_t gcov_type_unsigned;
 #define GCOV_TAG_ARCS		 ((gcov_unsigned_t)0x01430000)
 #define GCOV_TAG_ARCS_LENGTH(NUM)  (1 + (NUM) * 2 * GCOV_WORD_SIZE)
 #define GCOV_TAG_ARCS_NUM(LENGTH)  (((LENGTH / GCOV_WORD_SIZE) - 1) / 2)
+#define GCOV_TAG_CONDS             ((gcov_unsigned_t)0x01470000)
+#define GCOV_TAG_CONDS_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE)
+#define GCOV_TAG_CONDS_NUM(LENGTH) (((LENGTH) / GCOV_WORD_SIZE) / 2)
 #define GCOV_TAG_LINES		 ((gcov_unsigned_t)0x01450000)
 #define GCOV_TAG_COUNTER_BASE 	 ((gcov_unsigned_t)0x01a10000)
 #define GCOV_TAG_COUNTER_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE)
diff --git a/gcc/gcov.cc b/gcc/gcov.cc
index 9cf1071166f..bc2932e7890 100644
--- a/gcc/gcov.cc
+++ b/gcc/gcov.cc
@@ -79,6 +79,7 @@ using namespace std;
 class function_info;
 class block_info;
 class source_info;
+class condition_info;
 
 /* Describes an arc between two basic blocks.  */
 
@@ -132,6 +133,28 @@ public:
   vector<unsigned> lines;
 };
 
+class condition_info
+{
+public:
+  condition_info ();
+
+  int popcount () const;
+
+  gcov_type_unsigned truev;
+  gcov_type_unsigned falsev;
+
+  unsigned n_terms;
+};
+
+condition_info::condition_info (): truev (0), falsev (0), n_terms (0)
+{
+}
+
+int condition_info::popcount () const
+{
+    return __builtin_popcountll (truev) + __builtin_popcountll (falsev);
+}
+
 /* Describes a basic block. Contains lists of arcs to successor and
    predecessor blocks.  */
 
@@ -165,6 +188,8 @@ public:
   /* Block is a landing pad for longjmp or throw.  */
   unsigned is_nonlocal_return : 1;
 
+  condition_info conditions;
+
   vector<block_location_info> locations;
 
   struct
@@ -275,6 +300,8 @@ public:
   vector<block_info> blocks;
   unsigned blocks_executed;
 
+  vector<condition_info*> conditions;
+
   /* Raw arc coverage counts.  */
   vector<gcov_type> counts;
 
@@ -351,6 +378,9 @@ struct coverage_info
   int branches_executed;
   int branches_taken;
 
+  int conditions;
+  int conditions_covered;
+
   int calls;
   int calls_executed;
 
@@ -550,6 +580,10 @@ static int multiple_files = 0;
 
 static int flag_branches = 0;
 
+/* Output conditions (modified condition/decision coverage) */
+
+static int flag_conditions = 0;
+
 /* Show unconditional branches too.  */
 static int flag_unconditional = 0;
 
@@ -656,6 +690,7 @@ static int read_count_file (void);
 static void solve_flow_graph (function_info *);
 static void find_exception_blocks (function_info *);
 static void add_branch_counts (coverage_info *, const arc_info *);
+static void add_condition_counts (coverage_info *, const block_info *);
 static void add_line_counts (coverage_info *, function_info *);
 static void executed_summary (unsigned, unsigned);
 static void function_summary (const coverage_info *);
@@ -664,6 +699,7 @@ static const char *format_gcov (gcov_type, gcov_type, int);
 static void accumulate_line_counts (source_info *);
 static void output_gcov_file (const char *, source_info *);
 static int output_branch_count (FILE *, int, const arc_info *);
+static void output_conditions (FILE *, const block_info *);
 static void output_lines (FILE *, const source_info *);
 static string make_gcov_file_name (const char *, const char *);
 static char *mangle_name (const char *);
@@ -928,6 +964,7 @@ print_usage (int error_p)
   fnotice (file, "  -b, --branch-probabilities      Include branch probabilities in output\n");
   fnotice (file, "  -c, --branch-counts             Output counts of branches taken\n\
                                     rather than percentages\n");
+  fnotice (file, "  -g, --conditions                Include condition/decision coverage in output\n");
   fnotice (file, "  -d, --display-progress          Display progress information\n");
   fnotice (file, "  -D, --debug			    Display debugging dumps\n");
   fnotice (file, "  -f, --function-summaries        Output summaries for each function\n");
@@ -980,6 +1017,7 @@ static const struct option options[] =
   { "all-blocks",           no_argument,       NULL, 'a' },
   { "branch-probabilities", no_argument,       NULL, 'b' },
   { "branch-counts",        no_argument,       NULL, 'c' },
+  { "conditions",           no_argument,       NULL, 'g' },
   { "json-format",	    no_argument,       NULL, 'j' },
   { "human-readable",	    no_argument,       NULL, 'H' },
   { "no-output",            no_argument,       NULL, 'n' },
@@ -1008,7 +1046,7 @@ process_args (int argc, char **argv)
 {
   int opt;
 
-  const char *opts = "abcdDfhHijklmno:pqrs:tuvwx";
+  const char *opts = "abcdDfghHijklmno:pqrs:tuvwx";
   while ((opt = getopt_long (argc, argv, opts, options, NULL)) != -1)
     {
       switch (opt)
@@ -1025,6 +1063,9 @@ process_args (int argc, char **argv)
 	case 'f':
 	  flag_function_summary = 1;
 	  break;
+	case 'g':
+	  flag_conditions = 1;
+	  break;
 	case 'h':
 	  print_usage (false);
 	  /* print_usage will exit.  */
@@ -1132,6 +1173,45 @@ output_intermediate_json_line (json::array *object,
 	  }
       }
 
+  json::array *conditions = new json::array ();
+  lineo->set ("conditions", conditions);
+  if (flag_conditions)
+  {
+    vector<block_info *>::const_iterator it;
+    for (it = line->blocks.begin (); it != line->blocks.end (); it++)
+      {
+	const condition_info& info = (*it)->conditions;
+	if (info.n_terms == 0)
+	    continue;
+
+	const int count = 2 * info.n_terms;
+	const int covered = info.popcount ();
+
+	json::object *cond = new json::object ();
+	cond->set ("count", new json::integer_number (count));
+	cond->set ("covered", new json::integer_number (covered));
+
+	json::array *mtrue = new json::array ();
+	json::array *mfalse = new json::array ();
+	cond->set ("not_covered_true", mtrue);
+	cond->set ("not_covered_false", mfalse);
+
+	if (count != covered)
+	  {
+	    for (unsigned i = 0; i < info.n_terms; i++)
+	      {
+		gcov_type_unsigned index = 1;
+		index <<= i;
+		if (!(index & info.truev))
+		    mtrue->append (new json::integer_number (i));
+		if (!(index & info.falsev))
+		    mfalse->append (new json::integer_number (i));
+	      }
+	  }
+	conditions->append (cond);
+      }
+  }
+
   object->append (lineo);
 }
 
@@ -1956,6 +2036,28 @@ read_graph_file (void)
 		  }
 	    }
 	}
+      else if (fn && tag == GCOV_TAG_CONDS)
+	{
+	  unsigned num_dests = GCOV_TAG_CONDS_NUM (length);
+
+	  if (!fn->conditions.empty ())
+	    fnotice (stderr, "%s:already seen conditions for '%s'\n",
+		     bbg_file_name, fn->get_name ());
+	  else
+	    fn->conditions.resize (num_dests);
+
+	  for (unsigned i = 0; i < num_dests; ++i)
+	    {
+	      unsigned idx = gcov_read_unsigned ();
+
+	      if (idx >= fn->blocks.size ())
+		goto corrupt;
+
+	      condition_info *info = &fn->blocks[idx].conditions;
+	      info->n_terms = gcov_read_unsigned ();
+	      fn->conditions[i] = info;
+	    }
+	}
       else if (fn && tag == GCOV_TAG_LINES)
 	{
 	  unsigned blockno = gcov_read_unsigned ();
@@ -2086,11 +2188,26 @@ read_count_file (void)
 	      goto cleanup;
 	    }
 	}
-      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn)
+      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_CONDS) && fn)
 	{
+	  length = abs (read_length);
+	  if (length != GCOV_TAG_COUNTER_LENGTH (2 * fn->conditions.size ()))
+	      goto mismatch;
+
+	  if (read_length > 0)
+	    {
+	      for (ix = 0; ix != fn->conditions.size (); ix++)
+		{
+		  fn->conditions[ix]->truev  |= gcov_read_counter ();
+		  fn->conditions[ix]->falsev |= gcov_read_counter ();
+		}
+	    }
+	}
+      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn)
+      {
 	  length = abs (read_length);
 	  if (length != GCOV_TAG_COUNTER_LENGTH (fn->counts.size ()))
-	    goto mismatch;
+	      goto mismatch;
 
 	  if (read_length > 0)
 	    for (ix = 0; ix != fn->counts.size (); ix++)
@@ -2430,6 +2547,13 @@ add_branch_counts (coverage_info *coverage, const arc_info *arc)
     }
 }
 
+static void
+add_condition_counts (coverage_info *coverage, const block_info *block)
+{
+  coverage->conditions += 2 * block->conditions.n_terms;
+  coverage->conditions_covered += block->conditions.popcount ();
+}
+
 /* Format COUNT, if flag_human_readable_numbers is set, return it human
    readable format.  */
 
@@ -2533,6 +2657,18 @@ file_summary (const coverage_info *coverage)
 		 coverage->calls);
       else
 	fnotice (stdout, "No calls\n");
+
+    }
+
+  if (flag_conditions)
+    {
+      if (coverage->conditions)
+	fnotice (stdout, "Decisions covered:%s of %d\n",
+		 format_gcov (coverage->conditions_covered,
+			      coverage->conditions, 2),
+		 coverage->conditions);
+      else
+	fnotice (stdout, "No decisions\n");
     }
 }
 
@@ -2767,6 +2903,12 @@ static void accumulate_line_info (line_info *line, source_info *src,
 	 it != line->branches.end (); it++)
       add_branch_counts (&src->coverage, *it);
 
+  if (add_coverage)
+    for (vector<block_info *>::iterator it = line->blocks.begin ();
+	 it != line->blocks.end (); it++)
+      add_condition_counts (&src->coverage, *it);
+
+
   if (!line->blocks.empty ())
     {
       /* The user expects the line count to be the number of times
@@ -2868,6 +3010,33 @@ accumulate_line_counts (source_info *src)
       }
 }
 
+static void
+output_conditions (FILE *gcov_file, const block_info *binfo)
+{
+    const condition_info& info = binfo->conditions;
+    if (info.n_terms == 0)
+	return;
+
+    const int expected = 2 * info.n_terms;
+    const int got = info.popcount ();
+
+    fnotice (gcov_file, "condition outcomes covered %d/%d\n", got, expected);
+    if (expected == got)
+	return;
+
+    for (unsigned i = 0; i < info.n_terms; i++)
+    {
+	gcov_type_unsigned index = 1;
+	index <<= i;
+	if ((index & info.truev & info.falsev))
+	    continue;
+
+	const char *t = (index & info.truev) ? "" : "true";
+	const char *f = (index & info.falsev) ? "" : " false";
+	fnotice (gcov_file, "condition %2u not covered (%s%s)\n", i, t, f + !t[0]);
+    }
+}
+
 /* Output information about ARC number IX.  Returns nonzero if
    anything is output.  */
 
@@ -3078,16 +3247,29 @@ output_line_details (FILE *f, const line_info *line, unsigned line_num)
 	  if (flag_branches)
 	    for (arc = (*it)->succ; arc; arc = arc->succ_next)
 	      jx += output_branch_count (f, jx, arc);
+
+	  if (flag_conditions)
+	      output_conditions (f, *it);
 	}
     }
-  else if (flag_branches)
+  else
     {
-      int ix;
+      if (flag_branches)
+	{
+	  int ix;
+
+	  ix = 0;
+	  for (vector<arc_info *>::const_iterator it = line->branches.begin ();
+		  it != line->branches.end (); it++)
+	      ix += output_branch_count (f, ix, (*it));
+	}
 
-      ix = 0;
-      for (vector<arc_info *>::const_iterator it = line->branches.begin ();
-	   it != line->branches.end (); it++)
-	ix += output_branch_count (f, ix, (*it));
+      if (flag_conditions)
+	{
+	  for (vector<block_info *>::const_iterator it = line->blocks.begin ();
+	       it != line->blocks.end (); it++)
+	      output_conditions (f, *it);
+	}
     }
 }
 
diff --git a/gcc/ipa-inline.cc b/gcc/ipa-inline.cc
index 14969198cde..3e37305843e 100644
--- a/gcc/ipa-inline.cc
+++ b/gcc/ipa-inline.cc
@@ -646,7 +646,7 @@ can_early_inline_edge_p (struct cgraph_edge *e)
 			 "  edge not inlinable: not in SSA form\n");
       return false;
     }
-  else if (profile_arc_flag
+  else if ((profile_arc_flag || profile_condition_flag)
 	   && ((lookup_attribute ("no_profile_instrument_function",
 				 DECL_ATTRIBUTES (caller->decl)) == NULL_TREE)
 	       != (lookup_attribute ("no_profile_instrument_function",
diff --git a/gcc/ipa-split.cc b/gcc/ipa-split.cc
index 16734617d03..07d2b17ab12 100644
--- a/gcc/ipa-split.cc
+++ b/gcc/ipa-split.cc
@@ -1929,7 +1929,8 @@ pass_split_functions::gate (function *)
   /* When doing profile feedback, we want to execute the pass after profiling
      is read.  So disable one in early optimization.  */
   return (flag_partial_inlining
-	  && !profile_arc_flag && !flag_branch_probabilities);
+      && !profile_arc_flag && !flag_branch_probabilities
+      && !profile_condition_flag);
 }
 
 } // anon namespace
diff --git a/gcc/passes.cc b/gcc/passes.cc
index 78a07f8691a..3457ad1c5b3 100644
--- a/gcc/passes.cc
+++ b/gcc/passes.cc
@@ -352,7 +352,8 @@ finish_optimization_passes (void)
   gcc::dump_manager *dumps = m_ctxt->get_dumps ();
 
   timevar_push (TV_DUMP);
-  if (profile_arc_flag || flag_test_coverage || flag_branch_probabilities)
+  if (profile_arc_flag || profile_condition_flag || flag_test_coverage
+      || flag_branch_probabilities)
     {
       dumps->dump_start (pass_profile_1->static_pass_number, NULL);
       end_branch_prob ();
diff --git a/gcc/profile.cc b/gcc/profile.cc
index 96121d60711..f7f6d1f7197 100644
--- a/gcc/profile.cc
+++ b/gcc/profile.cc
@@ -66,9 +66,19 @@ along with GCC; see the file COPYING3.  If not see
 #include "cfgloop.h"
 #include "sreal.h"
 #include "file-prefix-map.h"
+#include "stringpool.h"
 
 #include "profile.h"
 
+struct condcov;
+struct condcov *find_conditions (struct function*);
+unsigned cov_length (const struct condcov*);
+array_slice<basic_block> cov_blocks (struct condcov*, unsigned);
+array_slice<gcov_type_unsigned > cov_masks (struct condcov*, unsigned);
+void cov_free (struct condcov*);
+int instrument_decisions (array_slice<basic_block>, unsigned, tree*,
+			  gcov_type_unsigned*);
+
 /* Map from BBs/edges to gcov counters.  */
 vec<gcov_type> bb_gcov_counts;
 hash_map<edge,gcov_type> *edge_gcov_counts;
@@ -100,6 +110,7 @@ static int total_num_passes;
 static int total_num_times_called;
 static int total_hist_br_prob[20];
 static int total_num_branches;
+static int total_num_conds;
 
 /* Forward declarations.  */
 static void find_spanning_tree (struct edge_list *);
@@ -1155,6 +1166,12 @@ read_thunk_profile (struct cgraph_node *node)
    the flow graph that are needed to reconstruct the dynamic behavior of the
    flow graph.  This data is written to the gcno file for gcov.
 
+   When FLAG_PROFILE_CONDITIONS is nonzero, this functions instruments the
+   edges in the control flow graph to track what conditions are evaluated to in
+   order to determine what conditions are covered and have an independent
+   effect on the outcome (modified condition/decision coverage).  This data is
+   written to the gcno file for gcov.
+
    When FLAG_BRANCH_PROBABILITIES is nonzero, this function reads auxiliary
    information from the gcda file containing edge count information from
    previous executions of the function being compiled.  In this case, the
@@ -1173,6 +1190,7 @@ branch_prob (bool thunk)
   struct edge_list *el;
   histogram_values values = histogram_values ();
   unsigned cfg_checksum, lineno_checksum;
+  bool output_to_file;
 
   total_num_times_called++;
 
@@ -1397,10 +1415,18 @@ branch_prob (bool thunk)
 
   /* Write the data from which gcov can reconstruct the basic block
      graph and function line numbers (the gcno file).  */
+  output_to_file = false;
   if (coverage_begin_function (lineno_checksum, cfg_checksum))
     {
       gcov_position_t offset;
 
+      /* The condition coverage needs a deeper analysis to identify expressions
+       * of conditions, which means it is not yet ready to write to the gcno
+       * file.  It will write its entries later, but needs to know if it do it
+       * in the first place, which is controlled by the return value of
+       * coverage_begin_function. */
+      output_to_file = true;
+
       /* Basic block flags */
       offset = gcov_write_tag (GCOV_TAG_BLOCKS);
       gcov_write_unsigned (n_basic_blocks_for_fn (cfun));
@@ -1512,29 +1538,74 @@ branch_prob (bool thunk)
 
   remove_fake_edges ();
 
+  if (profile_condition_flag || profile_arc_flag)
+      gimple_init_gcov_profiler ();
+
+  if (profile_condition_flag)
+    {
+      struct condcov *cov = find_conditions (cfun);
+      gcc_assert (cov);
+      const unsigned nconds = cov_length (cov);
+      total_num_conds += nconds;
+
+      if (coverage_counter_alloc (GCOV_COUNTER_CONDS, 2 * nconds))
+	{
+	  /* Add two extra variables to the function for the local
+	     accumulators, which are zero'd on the entry of a new conditional.
+	     The local accumulators are shared between decisions in order to
+	     use less stack space. */
+	  tree accu[2] = {
+	    build_decl (UNKNOWN_LOCATION, VAR_DECL,
+			get_identifier ("__accu_t"), get_gcov_type ()),
+	    build_decl (UNKNOWN_LOCATION, VAR_DECL,
+			get_identifier ("__accu_f"), get_gcov_type ()),
+	  };
+
+	  gcov_position_t offset {};
+	  if (output_to_file)
+	      offset = gcov_write_tag (GCOV_TAG_CONDS);
+
+	  for (unsigned i = 0; i < nconds; ++i)
+	    {
+	      array_slice<basic_block> expr = cov_blocks (cov, i);
+	      array_slice<gcov_type_unsigned> masks = cov_masks (cov, i);
+	      gcc_assert (expr.is_valid ());
+	      gcc_assert (masks.is_valid ());
+
+	      int terms = instrument_decisions (expr, i, accu, masks.begin ());
+	      if (output_to_file)
+		{
+		  gcov_write_unsigned (expr.front ()->index);
+		  gcov_write_unsigned (terms);
+		}
+	    }
+	  if (output_to_file)
+	      gcov_write_length (offset);
+	}
+      cov_free (cov);
+    }
+
   /* For each edge not on the spanning tree, add counting code.  */
   if (profile_arc_flag
       && coverage_counter_alloc (GCOV_COUNTER_ARCS, num_instrumented))
     {
       unsigned n_instrumented;
 
-      gimple_init_gcov_profiler ();
-
       n_instrumented = instrument_edges (el);
 
       gcc_assert (n_instrumented == num_instrumented);
 
       if (flag_profile_values)
 	instrument_values (values);
-
-      /* Commit changes done by instrumentation.  */
-      gsi_commit_edge_inserts ();
     }
 
   free_aux_for_edges ();
 
   values.release ();
   free_edge_list (el);
+  /* Commit changes done by instrumentation.  */
+  gsi_commit_edge_inserts ();
+
   coverage_end_function (lineno_checksum, cfg_checksum);
   if (flag_branch_probabilities
       && (profile_status_for_fn (cfun) == PROFILE_READ))
@@ -1664,6 +1735,7 @@ init_branch_prob (void)
   total_num_passes = 0;
   total_num_times_called = 0;
   total_num_branches = 0;
+  total_num_conds = 0;
   for (i = 0; i < 20; i++)
     total_hist_br_prob[i] = 0;
 }
@@ -1703,5 +1775,7 @@ end_branch_prob (void)
 		     (total_hist_br_prob[i] + total_hist_br_prob[19-i]) * 100
 		     / total_num_branches, 5*i, 5*i+5);
 	}
+      fprintf (dump_file, "Total number of conditions: %d\n",
+	       total_num_conds);
     }
 }
diff --git a/gcc/testsuite/g++.dg/gcov/gcov-18.C b/gcc/testsuite/g++.dg/gcov/gcov-18.C
new file mode 100644
index 00000000000..310ed5297c0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gcov/gcov-18.C
@@ -0,0 +1,234 @@
+/* { dg-options "--coverage -fprofile-conditions -std=c++11" } */
+/* { dg-do run { target native } } */
+
+#include <vector>
+#include <stdexcept>
+
+class nontrivial_destructor
+{
+public:
+    explicit nontrivial_destructor (int v) : val (v) {}
+    ~nontrivial_destructor () {}
+
+    explicit operator bool() const { return bool(val); }
+
+    int val;
+};
+
+int identity (int x) { return x; }
+int throws (int) { throw std::runtime_error("exception"); }
+
+int throw_if (int x)
+{
+    if (x) /* conditions(1/2) true(0) */
+	   /* conditions(end) */
+	throw std::runtime_error("exception");
+    return x;
+}
+
+/* used for side effects to insert nodes in conditional bodies etc. */
+int x = 0;
+
+/* conditionals work in the presence of non-trivial destructors */
+void mcdc001a (int a)
+{
+    nontrivial_destructor v (a);
+
+    if (v.val > 0) /* conditions(2/2) */
+	x = v.val;
+    else
+	x = -v.val;
+}
+
+/* non-trivial destructor in-loop temporary */
+nontrivial_destructor
+mcdc002a (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+	nontrivial_destructor tmp (a);
+	if (tmp.val % b) /* conditions(2/2) */
+	    return nontrivial_destructor (0);
+	x += i;
+    } /* conditions(suppress) */
+      /* conditions(end) */
+
+    return nontrivial_destructor (a * b);
+}
+
+/* conditional in constructor */
+void mcdc003a (int a)
+{
+    class C
+    {
+    public:
+	explicit C (int e) : v (e)
+	{
+	    if (e) /* conditions(1/2) false(0) */
+		v = x - e;
+	}
+	int v;
+    };
+
+    C c (a);
+    if (c.v > 2) /* conditions(1/2) true(0) */
+		 /* conditions(end) */
+	x = c.v + a;
+}
+
+/* conditional in destructor */
+void mcdc004a (int a)
+{
+    class C
+    {
+    public:
+	explicit C (int e) : v (e) {}
+	~C ()
+	{
+	    if (v) /* conditions(2/2) */
+		x = 2 * v;
+	}
+	int v;
+    };
+
+    C c (a);
+    x = 1; // arbitrary action between ctor+dtor
+}
+
+/* conditional in try */
+void mcdc005a (int a)
+{
+    try
+    {
+	if (a) /* conditions(1/2) true(0) */
+	       /* conditions(end) */
+	    x = 2 * identity (a);
+	else
+	    x = 1;
+    }
+    catch (...)
+    {
+	x = 0;
+    }
+}
+
+/* conditional in catch */
+void mcdc006a (int a) {
+    try
+    {
+	throws (a);
+    }
+    catch (std::exception&)
+    {
+	if (a) /* conditions(1/2) false(0) */
+	       /* conditions(end) */
+	    x = identity (a);
+	else
+	    x = 0;
+    }
+}
+
+void mcdc006b (int a)
+{
+    if (a) /* conditions(1/2) true(0) */
+	   /* conditions(end) */
+	throws (a);
+    else
+	x = 1;
+}
+
+void mcdc006c (int a) try
+{
+    throws (a);
+}
+catch (...) {
+    if (a) /* conditions(2/2) */
+	x = 5;
+}
+
+/* temporary with destructor as term */
+void mcdc007a (int a, int b)
+{
+    x = a && nontrivial_destructor (b); /* conditions(3/4) false(1) destructor() */
+}
+
+void mcdc007b (int a, int b)
+{
+    if (a || throw_if (b)) /* conditions(3/4) true(1) destructor() */
+	x = -1;
+    else
+	x = 1;
+}
+
+void mcdc007c (int a, int b)
+{
+    if (throw_if (a) || throw_if (b)) /* conditions(2/4) true(0 1) destructor() */
+	x = -1;
+    else
+	x = 1;
+}
+
+/* destructor with delete */
+void mcdc008a (int a)
+{
+    class C
+    {
+    public:
+        int size = 5;
+        int* ptr = nullptr;
+
+        explicit C (int v) : size (v + 5), ptr (new int[size]) /* conditions(suppress) */
+							       /* conditions(end) */
+        {
+	    for (int i = 0; i < size; i++) /* conditions(2/2) */
+		ptr[i] = i + 1;
+        }
+        ~C()
+        {
+	    // delete with implicit nullptr check
+	    delete ptr; /* conditions(1/2) false(0) */
+			/* conditions(end) */
+	}
+    };
+
+    C c (a);
+    if (c.ptr[a + 1]) /* conditions(1/2) false(0) */
+	x = a;
+}
+
+int
+main (void)
+{
+    mcdc001a (0);
+    mcdc001a (1);
+
+    mcdc002a (1, 1);
+    mcdc002a (1, 2);
+
+    mcdc003a (1);
+
+    mcdc004a (0);
+    mcdc004a (1);
+
+    mcdc005a (0);
+
+    mcdc006a (1);
+
+    mcdc006b (0);
+
+    mcdc006c (0);
+    mcdc006c (1);
+
+    mcdc007a (0, 0);
+    mcdc007a (1, 1);
+
+    mcdc007b (0, 0);
+    mcdc007b (1, 0);
+
+    mcdc007c (0, 0);
+
+    mcdc008a (1);
+
+}
+
+/* { dg-final { run-gcov conditions { --conditions gcov-18.C } } } */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-19.c b/gcc/testsuite/gcc.misc-tests/gcov-19.c
new file mode 100644
index 00000000000..1adff7c76f4
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-19.c
@@ -0,0 +1,1250 @@
+/* { dg-options "-fprofile-conditions -ftest-coverage" } */
+/* { dg-do run { target native } } */
+
+/* some side effect to stop branches from being pruned */
+int x = 0;
+
+/* || works */
+void
+mcdc001a (int a, int b)
+{
+    if (a || b) /* conditions(1/4) true(0) false(0 1) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc001b (int a, int b)
+{
+    if (a || b) /* conditions(3/4) true(0) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc001c (int a, int b)
+{
+    if (a || b) /* conditions(4/4) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc001d (int a, int b, int c)
+{
+    if (a || b || c) /* conditions(2/6) false(0 1 2) true(2) */
+		     /* conditions(end) */
+	x = 1;
+}
+
+/* && works */
+void
+mcdc002a (int a, int b)
+{
+    if (a && b) /* conditions(1/4) true(0 1) false(0) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc002b (int a, int b)
+{
+    if (a && b) /* conditions(3/4) false(0) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc002c (int a, int b)
+{
+    if (a && b) /* conditions(4/4) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc002d (int a, int b, int c)
+{
+    if (a && b && c) /* conditions(4/6) false(0 2) */
+		     /* conditions(end) */
+	x = 1;
+}
+
+/* negation works */
+void
+mcdc003a (int a, int b)
+{
+    if (!a || !b) /* conditions(2/4) false(0 1) */
+		  /* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+/* single conditionals with and without else */
+void
+mcdc004a (int a)
+{
+    if (a) /* conditions(1/2) true(0) */
+	   /* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc004b (int a)
+{
+    if (a) /* conditions(2/2) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc004c (int a)
+{
+    if (a) /* conditions(1/2) false(0) */
+	   /* conditions(end) */
+	x = 1;
+}
+
+void
+mcdc004d (int a, int b, int c)
+{
+    /* With no else this is interpreted as (a && (b || c)) */
+    if (a)  /* conditions(3/6) true(2) false(1 2)*/
+    {
+	if (b || c)
+	    x = a + b + c;
+    }
+}
+
+void
+mcdc004e (int a, int b, int c)
+{
+    /* With the else, this is interpreted as 2 expressions */
+    if (a)  /* conditions(2/2) */
+    {
+	if (b || c) /* conditions(1/4) true(1) false(0 1) */
+	    x = a + b + c;
+    }
+    else
+    {
+	x = c;
+    }
+}
+
+/* mixing && and || works */
+void
+mcdc005a (int a, int b, int c)
+{
+    if ((a && b) || c) /* conditions(1/6) true(0 1) false(0 1 2) */
+		       /* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc005b (int a, int b, int c, int d)
+{
+    /* This is where masking MC/DC gets unintuitive:
+
+       1 1 0 0 => covers 1 (d = 0) as && 0 masks everything to the left
+       1 0 0 0 => covers 2 (b = 0, c = 0) as (a && 0) masks a and d is never
+       evaluated. */
+    if ((a && (b || c)) && d) /* conditions(3/8) true(0 1 2 3) false(0) */
+			      /* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc005c (int a, int b, int c, int d)
+{
+    if (a || (b && c) || d) /* conditions(2/8) true(0 3) false(0 1 2 3) */
+			    /* conditions(end) */
+        x = a + b + c + d;
+}
+
+void
+mcdc005d (int a, int b, int c, int d)
+{
+    /* This test is quite significant - it has a single input
+       (1, 0, 0, 0) and tests specifically for when a multi-term left operand
+       is masked. d = 0 should mask a || b and for the input there are no other
+       sources for masking a (since b = 0). */
+    if ((a || b) && (c || d)) /* conditions(2/8) true(0 1 2 3) false(0 1) */
+			      /* conditions(end) */
+	x = a + b;
+    else
+	x = c + d;
+}
+
+/* nested conditionals */
+void
+mcdc006a (int a, int b, int c, int d, int e)
+{
+    if (a) /* conditions(2/2) */
+    {
+	if (b && c) /* conditions(3/4) false(1) */
+		    /* conditions(end) */
+	    x = 1;
+	else
+	    x = 2;
+    }
+    else
+    {
+	if (c || d) /* conditions(2/4) true(0 1) */
+		    /* conditions(end) */
+	    x = 3;
+	else
+	    x = 4;
+    }
+}
+
+void
+mcdc006b (int a, int b, int c)
+{
+    if (a) /* conditions(6/6) */
+	if (b)
+	    if (c)
+		x = a + b + c;
+}
+
+void
+mcdc006c (int a, int b, int c)
+{
+    if (a) /* conditions(2/2) */
+    {
+	if (b) /*conditions(2/2) */
+	{
+	    if (c) /* conditions(2/2) */
+	    {
+		x = a + b + c;
+	    }
+	}
+	else
+	{
+	    x = b;
+	}
+    }
+    else
+    {
+	x = a;
+    }
+}
+
+/* else/if */
+void
+mcdc007a (int a, int b, int c, int d)
+{
+    if (a) /* conditions(2/2) */
+    {
+	if (b) /* conditions(1/2) true(0) */
+	       /* conditions(end) */
+	    x = 1;
+	else
+	    x = 2;
+    }
+    else if (c) /* conditions(2/2) */
+    {
+	if (d) /* conditions(1/2) true(0) */
+	       /* conditions(end) */
+	    x = 3;
+	else
+	    x = 4;
+    }
+}
+
+void
+mcdc007b (int a, int b, int c)
+{
+    goto begin;
+then:
+    x = 1;
+    return;
+begin:
+    /* Evaluates to if (a || b || c) x = 1 */
+    if (a)	    /* conditions(5/6) true(2) */
+		    /* conditions(end) */
+	goto then;
+    else if (b)
+	goto then;
+    else if (c)
+	goto then;
+}
+
+void
+mcdc007c (int a, int b, int c)
+{
+    goto begin;
+then1:
+    x = 1;
+    return;
+then2:
+    x = 1;
+    return;
+then3:
+    x = 1;
+    return;
+begin:
+    /* similar to if (a || b || c) x = 1 */
+    if (a) /* conditions(2/2) */
+	goto then1;
+    else if (b) /* conditions(2/2) */
+	goto then2;
+    else if (c) /* conditions(1/2) true(0) */
+		/* conditions(end) */
+	goto then3;
+}
+
+/* while loop */
+void
+mcdc008a (int a)
+{
+    while (a < 10) /* conditions(2/2) */
+	x = a++;
+}
+
+void
+mcdc008b (int a)
+{
+    while (a > 10) /* conditions(1/2) true(0) */
+		   /* conditions(end) */
+	x = a--;
+}
+
+void
+mcdc008c (int a)
+{
+    // should work, even with no body
+    while (a) /* conditions(2/2) */
+	break;
+}
+
+void
+mcdc008d (int a, int b, int c, int d)
+{
+    /* multi-term loop conditional */
+    while ((a && (b || c)) && d) /* conditions(8/8) */
+	a = b = c = d = 0;
+}
+
+void
+mcdc009a (int a, int b)
+{
+    while (a > 0 && b > 0) /* conditions(3/4) false(1) */
+			   /* conditions(end) */
+	x = a--;
+}
+
+/* for loop */
+void
+mcdc010a(int a, int b)
+{
+    for (int i = 0; i < b; i++) /* conditions(2/2) */
+    {
+	if (a < b) /* conditions(2/2) */
+	    x = 1;
+	else
+	    x = a += 2;
+    }
+}
+
+void
+mcdc010b ()
+{
+    for (int a = 0; a <= 1; ++a) /* conditions(2/2) */
+    {
+	x = a;
+    }
+}
+
+int always (int x) { (void) x; return 1; }
+
+/* no-condition infinite loops */
+void
+mcdc010c (int a)
+{
+    for (;;)
+    {
+	if (always(a)) /* conditions(1/2) false(0) */
+		       /* conditions(end) */
+	{
+	    x = a;
+	    break;
+	}
+	x += a + 1;
+    }
+}
+
+/* conditionals without control flow constructs work */
+void
+mcdc011a (int a, int b, int c)
+{
+    x = (a && b) || c; /* conditions(5/6) false(1) */
+		       /* conditions(end) */
+}
+
+/* sequential expressions are handled independently */
+void
+mcdc012a (int a, int b, int c)
+{
+    if (a || b) /* conditions(3/4) true(0) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+
+    if (c) /* conditions(2/2) */
+	x = 1;
+}
+
+/*
+ * cannot ever satisfy MC/DC, even with all input combinations, because not all
+ * variables independently affect the decision
+ */
+void
+mcdc013a (int a, int b, int c)
+{
+    (void)b;
+    /*
+     * Specification: (a && b) || c
+     *
+     * But the expression was implemented wrong. This has branch coverage, but
+     * not MC/DC
+     */
+    if ((a && !c) || c) /* conditions(5/6) false(1) */
+	/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc014a ()
+{
+    int conds[64] = { 0 };
+    /* conditions(64/128) true(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63) */
+    x = conds[ 0] || conds[ 1] || conds[ 2] || conds[ 3] || conds[ 4] ||
+	conds[ 5] || conds[ 6] || conds[ 7] || conds[ 8] || conds[ 9] ||
+	conds[10] || conds[11] || conds[12] || conds[13] || conds[14] ||
+	conds[15] || conds[16] || conds[17] || conds[18] || conds[19] ||
+	conds[20] || conds[21] || conds[22] || conds[23] || conds[24] ||
+	conds[25] || conds[26] || conds[27] || conds[28] || conds[29] ||
+	conds[30] || conds[31] || conds[32] || conds[33] || conds[34] ||
+	conds[35] || conds[36] || conds[37] || conds[38] || conds[39] ||
+	conds[40] || conds[41] || conds[42] || conds[43] || conds[44] ||
+	conds[45] || conds[46] || conds[47] || conds[48] || conds[49] ||
+	conds[50] || conds[51] || conds[52] || conds[53] || conds[54] ||
+	conds[55] || conds[56] || conds[57] || conds[58] || conds[59] ||
+	conds[60] || conds[61] || conds[62] || conds[63]
+	;  /* conditions(end) */
+}
+
+/* early returns */
+void
+mcdc015a (int a, int b)
+{
+    if (a) /* conditions(2/2) */
+	return;
+
+    if (b) /* conditions(1/2) true(0) */
+	   /* conditions(end) */
+	x = 1;
+}
+
+void
+mcdc015b (int a, int b)
+{
+    for (int i = 5; i > a; i--) /* conditions(2/2) */
+    {
+	if (i == b) /* conditions(2/2) */
+	    return;
+	x = i;
+    }
+}
+
+void
+mcdc015c (int a, int b)
+{
+    for (int i = 5; i > a; i--) /* conditions(2/2) */
+    {
+	if (i == b) /* conditions(2/2) */
+	{
+	    x = 0;
+	    return;
+	}
+	else
+	{
+	    x = 1;
+	    return;
+	}
+
+	x = i;
+    }
+}
+
+
+/* check nested loops */
+void
+mcdc016a (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+	for (int k = 0; k < b; k++) /* conditions(2/2) */
+	    x = i + k;
+}
+
+void
+mcdc016b (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+	if (a > 5) /* conditions(2/2) */
+	    break;
+
+	for (int k = 0; k < b; k++) /* conditions(2/2) */
+	    x = i + k;
+    }
+}
+
+void
+mcdc016c (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+	if (a > 5) /* conditions(1/2) true(0) */
+		   /* conditions(end) */
+	    return;
+
+	for (int k = 0; k < b; k++) /* conditions(2/2) */
+	    x = i + k;
+    }
+}
+
+void
+mcdc016d (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+	for (int k = 0; k < 5; k++) /* conditions(2/2) */
+	{
+	    if (b > 5) /* conditions(1/2) true(0) */
+		       /* conditions(end) */
+		return;
+	    x = i + k;
+	}
+
+    }
+}
+
+/* do-while loops */
+void
+mcdc017a (int a)
+{
+    do
+    {
+	a--;
+    } while (a > 0); /* conditions(2/2) */
+}
+
+void
+noop () {}
+
+void
+mcdc017b (int a, int b)
+{
+    do
+    {
+	/*
+	 * This call is important; it can add more nodes to the body in the
+	 * CFG, which has changes how close exits and breaks are to the loop
+	 * conditional.
+	 */
+	noop ();
+	a--;
+	if (b) /* conditions(2/2) */
+	    break;
+
+    } while (a > 0); /* conditions(2/2) */
+}
+
+void
+mcdc017c (int a, int b)
+{
+    int left = 0;
+    int right = 0;
+    int n = a + b;
+    do
+    {
+	if (a) /* conditions(1/2) false(0) */
+	       /* conditions(end) */
+	{
+	    left = a > left ? b : left; /* conditions(2/2) */
+	}
+	if (b) /* conditions(1/2) false(0) */
+	{
+	    right = b > right ? a : right; /* conditions(2/2) */
+	}
+    } while (n-- > 0); /* conditions(2/2) */
+}
+
+int id  (int x) { return  x; }
+int inv (int x) { return !x; }
+
+/* collection of odd cases lifted-and-adapted from real-world code */
+int mcdc018a (int a, int b, int c, int d, int e, int f, int g, int len)
+{
+    int n;
+    /* adapted from zlib/gz_read */
+    do
+    {
+	n = -1;
+	if (n > len) /* conditions(2/2) */
+	    n = len;
+
+	if (b) /* conditions(2/2) */
+	{
+	    if (b < 5) /* conditions(2/2) */
+		x = 1;
+	    noop();
+	}
+	else if (c && d) /* conditions(3/4) false(1) */
+	{
+	    x = 2;
+	    break;
+	}
+	else if (e || f) /* conditions(2/4) false(0 1) */
+			 /* conditions(end) */
+	{
+	    if (id(g)) /* conditions(2/2) */
+		return 0;
+	    continue;
+	}
+    } while (a-- > 0); /* conditions(2/2) */
+
+    return 1;
+}
+
+void
+mcdc018b (int a, int b, int c)
+{
+    int n;
+    while (a) /* conditions(2/2) */
+    {
+	/* else block does not make a difference for the problem, but ensures
+	   loop termination. */
+	if (b) /* conditions(2/2) */
+	    n = c ? 0 : 0; // does not show up in CFG (embedded in the block)
+	else
+	    n = 0;
+	a = n;
+    }
+}
+
+/* Adapted from zlib/compress2 */
+void
+mcdc018c (int a, int b)
+{
+    int err;
+    do
+    {
+	a = inv (a);
+	err = a;
+    } while (err); /* conditions(1/2) true(0) */
+		   /* conditions(end) */
+
+    a = id (a);
+    if (a) /* conditions(1/2) true(0) */
+	x *= a + 1;
+}
+
+/* too many conditions, coverage gives up */
+void
+mcdc019a ()
+{
+    int conds[65] = { 0 };
+    #pragma GCC diagnostic push
+    #pragma GCC diagnostic ignored "-Wcoverage-too-many-conditions"
+    x = conds[ 0] || conds[ 1] || conds[ 2] || conds[ 3] || conds[ 4] ||
+	conds[ 5] || conds[ 6] || conds[ 7] || conds[ 8] || conds[ 9] ||
+	conds[10] || conds[11] || conds[12] || conds[13] || conds[14] ||
+	conds[15] || conds[16] || conds[17] || conds[18] || conds[19] ||
+	conds[20] || conds[21] || conds[22] || conds[23] || conds[24] ||
+	conds[25] || conds[26] || conds[27] || conds[28] || conds[29] ||
+	conds[30] || conds[31] || conds[32] || conds[33] || conds[34] ||
+	conds[35] || conds[36] || conds[37] || conds[38] || conds[39] ||
+	conds[40] || conds[41] || conds[42] || conds[43] || conds[44] ||
+	conds[45] || conds[46] || conds[47] || conds[48] || conds[49] ||
+	conds[50] || conds[51] || conds[52] || conds[53] || conds[54] ||
+	conds[55] || conds[56] || conds[57] || conds[58] || conds[59] ||
+	conds[60] || conds[61] || conds[62] || conds[63] || conds[64]
+	;
+    #pragma GCC diagnostic pop
+}
+
+/* ternary */
+void
+mcdc020a (int a)
+{
+    // special case, this can be reduced to:
+    // _1 = argc != 0;
+    // e = (int) _1;
+    x = a ? 1 : 0;
+
+    // changing to different int makes branch
+    x = a ? 2 : 1; /* conditions(2/2) */
+}
+
+void
+mcdc020b (int a, int b)
+{
+    x = (a || b) ? 1 : 0; /* conditions(3/4) true(1) */
+}
+
+void
+mcdc020c (int a, int b)
+{
+    x = a ? 0
+	: b ? 1 /* conditions(2/2) */
+	: 2;    /* conditions(1/2) false(0) */
+		/* conditions(end) */
+}
+
+/* Infinite loop (no exit-edge), this should not be called, but it should
+   compile fine */
+void
+mcdc021a ()
+{
+    while (1)
+	;
+}
+
+/* Computed goto can give all sorts of problems, including difficult path
+   contractions. */
+void
+mcdc021b ()
+{
+  void *op = &&dest;
+dest:
+  if (op) /* conditions(0/2) true(0) false(0) */
+	  /* conditions(end) */
+    goto * 0;
+}
+
+int __sigsetjmp ();
+
+/* This should compile, but not called. */
+void
+mcdc021c ()
+{
+  while (x) /* conditions(0/2) true(0) false(0)*/
+	    /* conditions(end) */
+     __sigsetjmp ();
+}
+
+/* If edges are not properly contracted the a && id (b) will be interpreted as
+   two independent expressions. */
+void
+mcdc021d (int a, int b, int c, int d)
+{
+    if (a && id (b)) /* conditions(1/4) true(0 1) false(0) */
+		     /* conditions(end) */
+	x = 1;
+    else if (c && id (d)) /* conditions(1/4) true(0 1) false(0) */
+			  /* conditions(end) */
+	x = 2;
+    else
+	x = 3;
+}
+
+/* Adapted from linux arch/x86/tools/relocs.c
+   With poor edge contracting this became an infinite loop. */
+void
+mcdc022a (int a, int b)
+{
+    for (int i = 0; i < 5; i++) /* conditions(2/2) */
+    {
+	x = i;
+	for (int j = i; j < 5; j++) /* conditions(2/2) */
+	{
+	    if (id (id (a)) || id (b)) /* conditions(3/4) true(0) */
+				       /* conditions(end) */
+		continue;
+	    b = inv(b);
+	}
+    }
+}
+
+int
+mcdc022b (int a)
+{
+    int devt;
+    if (a) /* conditions(2/2) */
+    {
+	x = a * 2;
+	if (x != a / 10 || x != a % 10) /* conditions(1/4) true(1) false(0 1) */
+					/* conditions(end) */
+	    return 0;
+    } else {
+	devt = id (a);
+	if (devt) /* conditions(1/2) true(0) */
+		  /* conditions(end) */
+	    return 0;
+    }
+
+    return devt;
+}
+
+/* Adapted from linux arch/x86/events/intel/ds.c
+
+   It broken sorting so that the entry block was not the first node after
+   sorting. */
+void
+mcdc022c (int a)
+{
+    if (!a) /* conditions(2/2) */
+	return;
+
+    for (int i = 0; i < 5; i++) /* conditions(2/2) */
+    {
+	if (id (a + i) || inv (a - 1)) /* conditions(1/4) false(0 1) true(1) */
+				       /* conditions(end) */
+	    x = a + i;
+	if (inv (a)) /* conditions(1/2) true(0) */
+		     /* conditions(end) */
+	    break;
+    }
+}
+
+void
+mcdc022d (int a)
+{
+    int i;
+    for (i = 0; i < id (a); i++) /* conditions(1/2) false(0) */
+    {
+	if (!inv (a)) /* conditions(1/2) false(0)*/
+		      /* conditions(end) */
+	    break;
+    }
+
+    if (i < a) /* conditions(1/2) false(0) */
+	       /* conditions(end) */
+	x = a + 1;
+}
+
+/* 023 specifically tests that masking works correctly, which gets complicated
+   fast with a mix of operators and deep subexpressions.  These tests violates
+   the style guide slightly to emphasize the nesting.  They all share the same
+   implementation and only one input is given to each function to obtain clean
+   coverage results. */
+void
+mcdc023a (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
+	  int l, int m, int n)
+{
+    // [a m n] = 0, [b, ...] = 1
+    // a is masked by b and the remaining terms should be short circuited
+    if (/* conditions(1/24) true(0 2 3 4 5 6 7 8 9 10 11) false(0 1 2 3 4 5 6 7 8 9 10 11) */
+	/* conditions(end) */
+	   (a || b)
+	|| (   ((c && d) || (e && (f || g) && h))
+	    && (k || l)
+	    && (m || n)))
+	x = a + b;
+    else
+	x = b + c;
+}
+
+void
+mcdc023b (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
+	  int l, int m, int n)
+{
+    // [a b d h] = 0, [c, ...] = 1
+    // h = 0 => false but does not mask (a || b) or (c && d). d = 0 masks c.
+    if (/* conditions(4/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 4 5 6 8 9 10 11) */
+	/* conditions(end) */
+	   (a || b)
+	|| (   ((c && d) || (e && (f || g) && h))
+	    && (k || l)
+	    && (m || n)))
+	x = a + b;
+    else
+	x = b + c;
+}
+
+void
+mcdc023c (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
+	  int l, int m, int n)
+{
+    /* [m n a b] = 0, [...] = 1
+       n,m = 0 should mask all other terms than a, b */
+    if (/* conditions(4/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 3 4 5 6 7 8 9) */
+	/* conditions(end) */
+	   (a || b)
+	|| (   ((c && d) || (e && (f || g) && h))
+	    && (k || l)
+	    && (m || n)))
+	x = a + b;
+    else
+	x = b + c;
+}
+
+void
+mcdc023d (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
+	  int l, int m, int n)
+{
+    /* [a b] = 0, [h, ...] = 1
+       n,m = 0 should mask all other terms than a, b */
+    if (/* conditions(4/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 3 4 5 6 7 10 11) */
+	/* conditions(end) */
+	   (a || b)
+	|| (   ((c && d) || (e && (f || g) && h))
+	    && (k || l)
+	    && (m || n)))
+	x = a + b;
+    else
+	x = b + c;
+}
+
+void
+mcdc023e (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
+	  int l, int m, int n)
+{
+    /* [a b d] = 0, [c h, ...] = 1
+       h = 1 should mask c, d, leave other terms intact.
+       If [k l m n] were false then h itself would be masked.
+       [a b] are masked as collateral by [m n]. */
+    if (/* conditions(5/24) true(0 1 2 3 6 9 11) false(0 1 2 3 4 5 6 7 8 9 10 11) */
+	/* conditions(end) */
+	   (a || b)
+	|| (   ((c && d) || (e && (f || g) && h))
+	    && (k || l)
+	    && (m || n)))
+	x = a + b;
+    else
+	x = b + c;
+}
+
+void
+mcdc023f (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
+	  int l, int m, int n)
+{
+    /* [a b c f g] = 0, [e, ...] = 1
+       [f g] = 0 should mask e, leave [c d] intact. */
+    if (/* conditions(5/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(3 4 7 8 9 10 11) */
+	/* conditions(end) */
+	   (a || b)
+	|| (   ((c && d) || (e && (f || g) && h))
+	    && (k || l)
+	    && (m || n)))
+	x = a + b;
+    else
+	x = b + c;
+}
+
+void
+mcdc023g (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
+	  int l, int m, int n)
+{
+    /* [a b d f g] = 0, [e c, ...] = 1
+       Same as 023f but with [c d] flipped so d masks c rather than c
+       short-circuits.  This should not be lost. */
+    if (/* conditions(5/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 4 7 8 9 10 11) */
+	/* conditions(end) */
+	   (a || b)
+	|| (   ((c && d) || (e && (f || g) && h))
+	    && (k || l)
+	    && (m || n)))
+	x = a + b;
+    else
+	x = b + c;
+}
+
+void
+mcdc024a (int a, int b)
+{
+    if (a && b) /* conditions(1/4) true(0 1) false(1) */
+		/* conditions(end) */
+    {
+label1:
+	x = 1;
+    }
+    else
+    {
+	x = 2;
+    }
+
+    if (a || b) /* conditions(2/4) true(0 1) */
+		/* conditions(end) */
+    {
+label2:
+	x = 1;
+    }
+    else
+    {
+	x = 2;
+    }
+}
+
+void
+mcdc024b (int a, int b)
+{
+
+    if (a && b) /* conditions(1/4) true(0 1) false(1) */
+		/* conditions(end) */
+    {
+	x = 1;
+    }
+    else
+    {
+label1:
+	x = 2;
+    }
+
+    if (a || b) /* conditions(2/4) true(0 1) */
+		/* conditions(end) */
+    {
+	x = 1;
+    }
+    else
+    {
+label2:
+	x = 2;
+    }
+}
+
+void
+mcdc024c (int a, int b)
+{
+    if (a && b) /* conditions(1/4) true(0 1) false(1) */
+		/* conditions(end) */
+    {
+label1:
+	x = 1;
+    }
+    else
+    {
+label2:
+	x = 2;
+    }
+
+    if (a || b) /* conditions(2/4) true(0 1) */
+		/* conditions(end) */
+    {
+label3:
+	x = 1;
+    }
+    else
+    {
+label4:
+	x = 2;
+    }
+}
+
+int main ()
+{
+    mcdc001a (0, 1);
+
+    mcdc001b (0, 1);
+    mcdc001b (0, 0);
+
+    mcdc001c (0, 1);
+    mcdc001c (0, 0);
+    mcdc001c (1, 1);
+
+    mcdc001d (1, 1, 1);
+    mcdc001d (0, 1, 0);
+
+    mcdc002a (1, 0);
+
+    mcdc002b (1, 0);
+    mcdc002b (1, 1);
+
+    mcdc002c (0, 0);
+    mcdc002c (1, 1);
+    mcdc002c (1, 0);
+
+    mcdc002d (1, 1, 1);
+    mcdc002d (1, 0, 0);
+
+    mcdc003a (0, 0);
+    mcdc003a (1, 0);
+
+    mcdc004a (0);
+    mcdc004b (0);
+    mcdc004b (1);
+    mcdc004c (1);
+
+    mcdc004d (0, 0, 0);
+    mcdc004d (1, 1, 1);
+
+    mcdc004e (0, 0, 0);
+    mcdc004e (1, 1, 1);
+
+    mcdc005a (1, 0, 1);
+
+    mcdc005b (1, 1, 0, 0);
+    mcdc005b (1, 0, 0, 0);
+
+    mcdc005c (0, 1, 1, 0);
+
+    mcdc005d (1, 0, 0, 0);
+
+    mcdc006a (0, 0, 0, 0, 0);
+    mcdc006a (1, 0, 0, 0, 0);
+    mcdc006a (1, 1, 1, 0, 0);
+
+    mcdc006b (0, 0, 0);
+    mcdc006b (1, 0, 0);
+    mcdc006b (1, 1, 0);
+    mcdc006b (1, 1, 1);
+
+    mcdc006c (0, 0, 0);
+    mcdc006c (1, 0, 0);
+    mcdc006c (1, 1, 0);
+    mcdc006c (1, 1, 1);
+
+    mcdc007a (0, 0, 0, 0);
+    mcdc007a (1, 0, 0, 0);
+    mcdc007a (0, 0, 1, 0);
+
+    mcdc007b (0, 0, 0);
+    mcdc007b (0, 1, 1);
+    mcdc007b (1, 0, 1);
+
+    mcdc007c (0, 0, 0);
+    mcdc007c (0, 1, 1);
+    mcdc007c (1, 0, 1);
+
+    mcdc008a (0);
+
+    mcdc008b (0);
+
+    mcdc008c (0);
+    mcdc008c (1);
+
+    mcdc008d (0, 0, 0, 0);
+    mcdc008d (1, 0, 0, 0);
+    mcdc008d (1, 0, 1, 0);
+    mcdc008d (1, 0, 1, 1);
+    mcdc008d (1, 1, 1, 1);
+
+    mcdc009a (0, 0);
+    mcdc009a (1, 1);
+
+    mcdc010a (0, 0);
+    mcdc010a (0, 9);
+    mcdc010a (2, 1);
+
+    mcdc010b ();
+
+    mcdc010c (1);
+
+    mcdc011a (0, 0, 0);
+    mcdc011a (1, 1, 0);
+    mcdc011a (1, 0, 1);
+
+    mcdc012a (0, 0, 0);
+    mcdc012a (0, 1, 1);
+
+    mcdc013a (0, 0, 0);
+    mcdc013a (0, 0, 1);
+    mcdc013a (0, 1, 0);
+    mcdc013a (0, 1, 1);
+    mcdc013a (1, 0, 0);
+    mcdc013a (1, 0, 1);
+    mcdc013a (1, 1, 0);
+    mcdc013a (1, 1, 1);
+
+    mcdc014a ();
+
+    mcdc015a (0, 0);
+    mcdc015a (1, 0);
+
+    mcdc015b (0, 0);
+    mcdc015b (0, 1);
+    mcdc015b (6, 1);
+
+    mcdc015c (0, 0);
+    mcdc015c (0, 5);
+    mcdc015c (6, 1);
+
+    mcdc016a (5, 5);
+
+    mcdc016b (5, 5);
+    mcdc016b (6, 5);
+
+    mcdc016c (5, 5);
+
+    mcdc016d (1, 0);
+
+    mcdc017a (0);
+    mcdc017a (2);
+
+    mcdc017b (2, 0);
+    mcdc017b (0, 1);
+
+    mcdc017c (1, 1);
+
+    mcdc018a (0, 0, 1, 1, 0, 0, 0, 0);
+    mcdc018a (0, 1, 0, 0, 0, 0, 1, -2);
+    mcdc018a (0, 6, 0, 0, 0, 0, 1, -2);
+    mcdc018a (0, 6, 0, 0, 0, 0, 1, -2);
+    mcdc018a (0, 0, 0, 1, 0, 1, 1, 0);
+    mcdc018a (1, 0, 0, 0, 1, 1, 0, 0);
+
+    mcdc018b (1, 0, 0);
+    mcdc018b (1, 1, 0);
+
+    mcdc018c (1, 1);
+
+    mcdc019a ();
+
+    mcdc020a (0);
+    mcdc020a (1);
+
+    mcdc020b (0, 0);
+    mcdc020b (1, 0);
+
+    mcdc020c (0, 1);
+    mcdc020c (1, 1);
+
+    mcdc021d (1, 0, 1, 0);
+
+    mcdc022a (0, 0);
+
+    mcdc022b (0);
+    mcdc022b (1);
+
+    mcdc022c (0);
+    mcdc022c (1);
+
+    mcdc022d (1);
+
+    mcdc023a (0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+    mcdc023b (0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1);
+    mcdc023c (0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0);
+    mcdc023d (0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1);
+    mcdc023e (0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+    mcdc023f (0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1);
+    mcdc023g (0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1);
+
+    mcdc024a (0, 0);
+    mcdc024b (0, 0);
+    mcdc024c (0, 0);
+}
+
+/* { dg-final { run-gcov conditions { --conditions gcov-19.c } } } */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-20.c b/gcc/testsuite/gcc.misc-tests/gcov-20.c
new file mode 100644
index 00000000000..847dae495db
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-20.c
@@ -0,0 +1,22 @@
+/* { dg-options "-fprofile-conditions -ftest-coverage -fprofile-update=atomic" } */
+/* { dg-do run { target native } } */
+
+/* some side effect to stop branches from being pruned */
+int x = 0;
+
+void
+conditions_atomic001 (int a, int b)
+{
+    if (a || b) /* conditions(1/4) true(0) false(0 1) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+int main ()
+{
+    conditions_atomic001 (0, 1);
+}
+
+/* { dg-final { run-gcov conditions { --conditions gcov-20.c } } } */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-21.c b/gcc/testsuite/gcc.misc-tests/gcov-21.c
new file mode 100644
index 00000000000..978be3276a2
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-21.c
@@ -0,0 +1,16 @@
+/* { dg-options "-fprofile-conditions" } */
+
+/* https://gcc.gnu.org/pipermail/gcc-patches/2022-April/592927.html */
+char trim_filename_name;
+int r;
+
+void trim_filename() {
+    if (trim_filename_name)
+	r = 123;
+    while (trim_filename_name)
+	;
+}
+
+int main ()
+{
+}
diff --git a/gcc/testsuite/lib/gcov.exp b/gcc/testsuite/lib/gcov.exp
index 9d5b2cdb86b..69168d67d03 100644
--- a/gcc/testsuite/lib/gcov.exp
+++ b/gcc/testsuite/lib/gcov.exp
@@ -174,6 +174,184 @@ proc verify-branches { testname testcase file } {
     return $failed
 }
 
+#
+# verify-conditions -- check that conditions are checked as expected
+#
+# TESTNAME is the name of the test, including unique flags.
+# TESTCASE is the name of the test file.
+# FILE is the name of the gcov output file.
+#
+# Checks are based on comments in the source file. Condition coverage comes
+# with with two types of output, a summary and a list of the uncovered
+# conditions. Both must be checked to pass the test
+#
+# To check for conditions, add a comment the line of a conditional:
+# /* conditions(n/m) true(0 1) false(1) */
+#
+# where n/m are the covered and total conditions in the expression. The true()
+# and false() take the indices expected *not* covered.
+#
+# This means that all coverage statements should have been seen:
+# /* conditions(end) */
+#
+# If all conditions are covered i.e. n == m, then conditions(end) can be
+# omitted. If either true() or false() are empty they can be omitted too.
+#
+# C++ can insert conditionals in the CFG that are not present in source code.
+# These must be manually suppressed since unexpected and unhandled conditions
+# are an error (to help combat regressions). Output can be suppressed with
+# conditions(suppress) and conditions(end). suppress should usually be on a
+# closing brace.
+#
+# Some expressions, when using unnamed temporaries as operands, will have
+# destructors in expressions. The coverage of the destructor will be reported
+# on the same line as the expression itself, but suppress() would also swallow
+# the expected tested-for messages. To handle these, use the destructor() [1]
+# which will suppress everything from and including the second "conditions
+# covered".
+#
+# [1] it is important that the destructor() is *on the same line* as the
+#     conditions(m/n)
+proc verify-conditions { testname testcase file } {
+    set failed 0
+    set suppress 0
+    set destructor 0
+    set should ""
+    set shouldt ""
+    set shouldf ""
+    set shouldall ""
+    set fd [open $file r]
+    set n 0
+    set keywords {"end" "suppress"}
+    while {[gets $fd line] >= 0} {
+	regexp "^\[^:\]+: *(\[0-9\]+):" "$line" all n
+	set prefix "$testname line $n"
+
+	if {![regexp "condition" $line]} {
+	    continue
+	}
+
+	# Missing coverage for both true and false will cause a failure, but
+	# only count it once for the report.
+	set ok 1
+	if [regexp {conditions *\(([0-9a-z/]+)\)} "$line" all e] {
+	    # *Very* coarse sanity check: conditions() should either be a
+	    # keyword or n/m, anything else means a buggy test case. end is
+	    # optional for cases where all conditions are covered, since it
+	    # only expects a single line of output.
+	    if {([lsearch -exact $keywords $e] >= 0 || [regexp {\d+/\d+} "$e"]) == 0} {
+		fail "$prefix: expected conditions (n/m), (suppress) or (end); was ($e)"
+		incr failed
+		continue
+	    }
+
+	    # Any keyword means a new context. Set the error flag if not all
+	    # expected output has been seen, and reset the state.
+
+	    if {[llength $shouldt] != 0} {
+		fail "$prefix: expected 'not covered (true)' for terms: $shouldt"
+		set ok 0
+	    }
+
+	    if {[llength $shouldf] != 0} {
+		fail "$prefix: expected 'not covered (false)' for terms: $shouldf"
+		set ok 0
+	    }
+
+	    if {$shouldall ne ""} {
+		fail "$prefix: coverage summary not found; expected $shouldall"
+		set ok 0
+	    }
+
+	    set suppress 0
+	    set destructor 0
+	    set should ""
+	    set shouldt ""
+	    set shouldf ""
+	    set shouldall ""
+	    set newt ""
+	    set newf ""
+
+	    if [regexp {destructor\(\)} "$line"] {
+		set destructor 1
+	    }
+
+	    if [regexp {(\d+)/(\d+)} "$e" all i k] {
+		regexp {true\(([0-9 ]+)\)}  "$line" all newt
+		regexp {false\(([0-9 ]+)\)} "$line" all newf
+
+		# Sanity check - if the true() and false() vectors should have
+		# m-n elements to cover all uncovered conditions. Because of
+		# masking it can sometimes be surprising what terms are
+		# independent, so this makes for more robust test at the cost
+		# of being slightly more annoying to write.
+		set nterms [expr [llength $newt] + [llength $newf]]
+		set nexpected [expr {$k - $i}]
+		if {$nterms != $nexpected} {
+		    fail "$prefix: expected $nexpected uncovered terms; got $nterms"
+		    set ok 0
+		}
+		set shouldall $e
+		set shouldt $newt
+		set shouldf $newf
+	    } elseif {$e == "end"} {
+		# no-op - state has already been reset, and errors flagged
+	    } elseif {$e == "suppress"} {
+		set suppress 1
+	    } else {
+		# this should be unreachable, 
+		fail "$prefix: unhandled control ($e), should be unreachable"
+		set ok 0
+	    }
+	} elseif {$suppress == 1} {
+	    # ignore everything in a suppress block. C++ especially can insert
+	    # conditionals in exceptions and destructors which would otherwise
+	    # be considered unhandled.
+	    continue
+	} elseif [regexp {condition +(\d+) not covered \((.*)\)} "$line" all cond condv] {
+	    foreach v {true false} {
+		if [regexp $v $condv] {
+		    if {"$v" == "true"} {
+			set should shouldt
+		    } else {
+			set should shouldf
+		    }
+
+		    set i [lsearch [set $should] $cond]
+		    if {$i != -1} {
+			set $should [lreplace [set $should] $i $i]
+		    } else {
+			fail "$testname line $n: unexpected uncovered term $cond ($v)"
+			set ok 0
+		    }
+		}
+	    }
+	} elseif [regexp {condition outcomes covered (\d+/\d+)} "$line" all cond] {
+	    # the destructor-generated "conditions covered" lines will be
+	    # written after all expression-related output. Handle these by
+	    # turning on suppression if the destructor-suppression is
+	    # requested.
+	    if {$shouldall == "" && $destructor == 1} {
+		set suppress 1
+		continue
+	    }
+
+	    if {$cond == $shouldall} {
+		set shouldall ""
+	    } else {
+		fail "$testname line $n: unexpected summary $cond"
+		set ok 0
+	    }
+	}
+
+	if {$ok != 1} {
+	    incr failed
+	}
+    }
+    close $fd
+    return $failed
+}
+
 #
 # verify-calls -- check that call return percentages are as expected
 #
@@ -321,6 +499,7 @@ proc run-gcov { args } {
     set gcov_args ""
     set gcov_verify_calls 0
     set gcov_verify_branches 0
+    set gcov_verify_conditions 0
     set gcov_verify_lines 1
     set gcov_verify_intermediate 0
     set gcov_remove_gcda 0
@@ -331,10 +510,13 @@ proc run-gcov { args } {
 	  set gcov_verify_calls 1
 	} elseif { $a == "branches" } {
 	  set gcov_verify_branches 1
+	} elseif { $a == "conditions" } {
+	  set gcov_verify_conditions 1
 	} elseif { $a == "intermediate" } {
 	  set gcov_verify_intermediate 1
 	  set gcov_verify_calls 0
 	  set gcov_verify_branches 0
+	  set gcov_verify_conditions 0
 	  set gcov_verify_lines 0
 	} elseif { $a == "remove-gcda" } {
 	  set gcov_remove_gcda 1
@@ -404,6 +586,11 @@ proc run-gcov { args } {
     } else {
 	set bfailed 0
     }
+    if { $gcov_verify_conditions } {
+	set cdfailed [verify-conditions $testname $testcase $testcase.gcov]
+    } else {
+	set cdfailed 0
+    }
     if { $gcov_verify_calls } {
 	set cfailed [verify-calls $testname $testcase $testcase.gcov]
     } else {
@@ -418,12 +605,12 @@ proc run-gcov { args } {
 
     # Report whether the gcov test passed or failed.  If there were
     # multiple failures then the message is a summary.
-    set tfailed [expr $lfailed + $bfailed + $cfailed + $ifailed]
+    set tfailed [expr $lfailed + $bfailed + $cdfailed + $cfailed + $ifailed]
     if { $xfailed } {
 	setup_xfail "*-*-*"
     }
     if { $tfailed > 0 } {
-	fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cfailed in return percentages, $ifailed in intermediate format"
+	fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cdfailed in condition/decision, $cfailed in return percentages, $ifailed in intermediate format"
 	if { $xfailed } {
 	    clean-gcov $testcase
 	}
diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
index 2beb49241f2..0b537d64d97 100644
--- a/gcc/tree-profile.cc
+++ b/gcc/tree-profile.cc
@@ -58,6 +58,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "alloc-pool.h"
 #include "symbol-summary.h"
 #include "symtab-thunks.h"
+#include "cfganal.h"
+#include "cfgloop.h"
 
 static GTY(()) tree gcov_type_node;
 static GTY(()) tree tree_interval_profiler_fn;
@@ -73,6 +75,1048 @@ static GTY(()) tree ic_tuple_var;
 static GTY(()) tree ic_tuple_counters_field;
 static GTY(()) tree ic_tuple_callee_field;
 
+namespace
+{
+/* Some context and reused instances between function calls.  Large embedded
+   buffers are used to up-front request enough memory for most programs and
+   merge them into a single allocation at the cost of using more memory in the
+   average case.  Some numbers from linux v5.13 which is assumed to be a
+   reasonably diverse code base: 75% of the functions in linux have less than
+   16 nodes in the CFG and approx 2.5% have more than 64 nodes.  The functions
+   that go beyond a few dozen nodes tend to be very large (>100) and so 64
+   seems like a good balance.
+
+   This is really just a performance balance of the cost of allocation and
+   wasted memory. */
+struct conds_ctx
+{
+    /* Bitmap of the processed blocks.  Bit n set means basic_block->index has
+       been processed either explicitly or as a part of an expression. */
+    auto_sbitmap marks;
+
+    /* This is both a reusable shared allocation which is also used to return
+       single expressions, which means it for most code should only hold a
+       couple of elements. */
+    auto_vec<basic_block, 32> blocks;
+
+    /* Map from basic_block->index to an ordering so that for a single
+       expression (a || b && c) => index_map[a] < index_map[b] < index_map[c].
+       The values do not have to be consecutive and can be interleaved by
+       values from other expressions, so comparisons only make sense for blocks
+       that belong to the same expression. */
+    auto_vec<int, 64> index_map;
+
+    /* Pre-allocate bitmaps and vectors for per-function book keeping.  This is
+       pure instance reuse and the bitmaps carry no data between function
+       calls. */
+    auto_vec<basic_block, 64> B1;
+    auto_vec<basic_block, 64> B2;
+    auto_sbitmap G1;
+    auto_sbitmap G2;
+    auto_sbitmap G3;
+
+    explicit conds_ctx (unsigned size) noexcept (true) : marks (size),
+    G1 (size), G2 (size), G3 (size)
+    {
+	bitmap_clear (marks);
+    }
+
+    /* Mark a node as processed so nodes are not processed twice for example in
+       loops, gotos. */
+    void mark (const basic_block b) noexcept (true)
+    {
+	gcc_assert (!bitmap_bit_p (marks, b->index));
+	bitmap_set_bit (marks, b->index);
+    }
+
+    /* Mark nodes as processed so they are not processed twice. */
+    void mark (const vec<basic_block>& bs) noexcept (true)
+    {
+	for (const basic_block b : bs)
+	    mark (b);
+    }
+
+    /* Check if all nodes are marked.  A successful run should visit & mark
+       every reachable node exactly once. */
+    bool all_marked (const vec<basic_block>& reachable) const noexcept (true)
+    {
+	for (const basic_block b : reachable)
+	    if (!bitmap_bit_p (marks, b->index))
+		return false;
+	return true;
+    }
+};
+
+/* Only instrument terms with fewer than number of bits in a (wide) gcov
+   integer, which is probably 64.  The algorithm itself does not impose this
+   limitation, but it makes for a simpler implementation.
+
+   * Allocating the output data structure (coverage_counter_alloc ()) can
+     assume pairs of gcov_type_unsigned and not use a separate length field.
+   * A pair gcov_type_unsigned can be used as accumulators.
+   * Updating accumulators is can use the bitwise operations |=, &= and not
+     custom operators that work for arbitrary-sized bit-sets.
+
+   Most real-world code should be unaffected by this, but it is possible
+   (especially for generated code) to exceed this limit.
+ */
+#define CONDITIONS_MAX_TERMS (sizeof (gcov_type_unsigned) * BITS_PER_UNIT)
+#define EDGE_CONDITION (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE)
+
+/* Compare two basic blocks by their order in the expression i.e. for (a || b)
+   then cmp_index_map (a, b, ...) < 0.  The result is undefined if lhs, rhs
+   belong to different expressions. */
+int
+cmp_index_map (const void *lhs, const void *rhs, void *index_map)
+{
+    const_basic_block l = *(const basic_block*) lhs;
+    const_basic_block r = *(const basic_block*) rhs;
+    const vec<int>* im = (const vec<int>*) index_map;
+    return (*im)[l->index] - (*im)[r->index];
+}
+
+/* Find the index of needle in blocks; return -1 if not found. This has two
+   uses, sometimes for the index and sometimes for set member checks.  Sets are
+   typically very small (number of conditions, >8 is uncommon) so linear search
+   should be very fast. */
+int
+index_of (const basic_block needle, array_slice<basic_block> blocks)
+{
+    for (size_t i = 0; i < blocks.size (); i++)
+	if (blocks[i] == needle)
+	    return int (i);
+    return -1;
+}
+
+/* Returns true if this is a conditional node, i.e. it has outgoing true and
+   false edges. */
+bool
+block_conditional_p (const basic_block b)
+{
+    unsigned t = 0;
+    unsigned f = 0;
+    for (edge e : b->succs)
+    {
+	t |= (e->flags & EDGE_TRUE_VALUE);
+	f |= (e->flags & EDGE_FALSE_VALUE);
+    }
+    return t && f;
+}
+
+/* Check if the edge is a conditional. */
+bool
+edge_conditional_p (const edge e)
+{
+    return e->flags & EDGE_CONDITION;
+}
+
+/* Special cases of the single_*_p and single_*_edge functions in basic-block.h
+   that don't consider exception handling or other complex edges.  This helps
+   create a view of the CFG with only normal edges - if a basic block has both
+   an outgoing fallthrough and exceptional edge [1], it should be considered a
+   single-successor.
+
+   [1] if this is not possible, these functions can be removed and replaced by
+       their basic-block.h cousins. */
+bool
+single (const vec<edge, va_gc> *edges)
+{
+    int n = EDGE_COUNT (edges);
+    if (n == 0)
+	return false;
+
+    for (edge e : edges)
+	if (e->flags & EDGE_COMPLEX)
+	    n -= 1;
+
+    return n == 1;
+}
+
+/* Get the single, non-complex edge.  Behavior is undefined edges have more
+   than 1 non-complex edges. */
+edge
+single_edge (const vec<edge, va_gc> *edges)
+{
+    for (edge e : edges)
+    {
+	if (e->flags & EDGE_COMPLEX)
+	    continue;
+	return e;
+    }
+    return NULL;
+}
+
+/* Sometimes, for example with function calls and C++ destructors, the CFG gets
+   extra nodes that are essentially single-entry-single-exit in the middle of
+   boolean expressions.  For example:
+
+      x || can_throw (y)
+
+               A
+              /|
+             / |
+            B  |
+            |  |
+            C  |
+           / \ |
+          /   \|
+         F     T
+
+   Without the extra node inserted by the function + exception it becomes a
+   proper 2-term graph, not 2 single-term graphs.
+
+               A
+              /|
+             C |
+            / \|
+           F   T
+
+   contract_edge ignores the series of intermediate nodes and makes a virtual
+   edge A -> C without having to construct a new simplified CFG explicitly.  It
+   gets more complicated as non-conditional edges is how the body of the
+   then/else blocks are separated from the boolean expression, so only edges
+   that are inserted because of function calls in the expression itself must be
+   merged.
+
+   Only chains of single-exit single-entry nodes that end with a condition
+   should be contracted. */
+edge
+contract_edge (edge e)
+{
+    edge source = e;
+    while (true)
+    {
+	basic_block dest = e->dest;
+	if (!single (dest->preds))
+	    return source;
+	if (e->flags & EDGE_DFS_BACK)
+	    return source;
+	if (block_conditional_p (dest))
+	    return e;
+
+	e = single_edge (dest->succs);
+	if (!e)
+	    return source;
+    }
+}
+
+/* This is the predecessor dual of contract_edge; it collapses the predecessor
+   blocks between two operands in a boolean expression. */
+edge
+contract_edge_up (edge e)
+{
+    while (true)
+    {
+	basic_block src = e->src;
+	if (edge_conditional_p (e))
+	    return e;
+	if (!single (src->preds))
+	    return e;
+	e = single_edge (src->preds);
+    }
+}
+
+/* "Undo" an edge split.  Sometimes the sink of a boolean expression will be
+   split into multiple blocks to accurately track line coverage, for example
+   when there is a goto-label at the top of the then/else block:
+
+    if (a && b)
+    {
+	l1:
+	...
+    }
+    else
+    {
+	l2:
+	...
+    }
+
+    and the corresponding CFG where a1 and b1 are created in edge splits to the
+    same destination (F):
+
+    a
+    |\
+    | a1
+    b  \
+    |\  |
+    | b1|
+    |  \|
+    T   F
+
+    This function recognizes this shape and returns the "merges" the split
+    outcome block by returning their common successor.  In all other cases it is
+    the identity function.
+*/
+basic_block
+merge_split_outcome (basic_block b)
+{
+    if (!single (b->succs))
+	return b;
+    if (!single (b->preds))
+	return b;
+
+    const unsigned flag = single_edge (b->preds)->flags & EDGE_CONDITION;
+    if (!flag)
+	return b;
+
+    edge e = single_edge (b->succs);
+    for (edge pred : e->dest->preds)
+    {
+	if (!single (pred->src->preds))
+	    return b;
+	if (!(single_edge (pred->src->preds)->flags & flag))
+	    return b;
+    }
+    return e->dest;
+}
+
+
+/* Find the set {ancestors(p) intersect G} where ancestors is the recursive set
+   of predecessors for p.  Limiting to the ancestors that are also in G (see
+   cond_reachable_from) and by q is an optimization as ancestors outside G have
+   no effect when isolating expressions.
+
+   dfs_enumerate_from () does not work as the filter function needs edge
+   information and dfs_enumerate_from () only considers blocks. */
+void
+ancestors_of (basic_block p, basic_block q, const sbitmap G, sbitmap ancestors)
+{
+    if (!bitmap_bit_p (G, p->index))
+	return;
+
+    bitmap_set_bit (ancestors, p->index);
+    bitmap_set_bit (ancestors, q->index);
+    if (p == q)
+	return;
+
+    auto_vec<basic_block, 16> stack;
+    stack.safe_push (p);
+
+    while (!stack.is_empty())
+    {
+	basic_block b = stack.pop ();
+	if (single (b->preds))
+	{
+	    edge e = single_edge (b->preds);
+	    e = contract_edge_up (e);
+	    b = e->dest;
+	}
+
+	for (edge e : b->preds)
+	{
+	    basic_block src = e->src;
+	    if (bitmap_bit_p (ancestors, e->src->index))
+		continue;
+	    if (!bitmap_bit_p (G, e->src->index))
+		continue;
+	    bitmap_set_bit (ancestors, src->index);
+	    stack.safe_push (src);
+	}
+    }
+}
+
+/* A simple struct for storing/returning outcome block pairs.  Either both
+   blocks are set or both are NULL. */
+struct outcomes
+{
+    basic_block t = NULL;
+    basic_block f = NULL;
+
+    operator bool () const noexcept (true)
+    {
+	return t && f;
+    }
+};
+
+/* Get the true/false successors of a basic block. If b is not a conditional
+   block both edges are NULL. */
+outcomes
+conditional_succs (const basic_block b)
+{
+    outcomes c;
+    for (edge e : b->succs)
+    {
+	if (e->flags & EDGE_TRUE_VALUE)
+	    c.t = merge_split_outcome (e->dest);
+	if (e->flags & EDGE_FALSE_VALUE)
+	    c.f = merge_split_outcome (e->dest);
+    }
+
+    gcc_assert ((c.t && c.f) || (!c.t && !c.f));
+    return c;
+}
+
+/* Get the index or offset of a conditional flag, 0 for true and 1 for false.
+   These indices carry no semantics but must be consistent as they are used to
+   index into data structures in code generation and gcov. */
+unsigned
+condition_index (unsigned flag)
+{
+    return (flag & EDGE_CONDITION) == EDGE_TRUE_VALUE ? 0 : 1;
+}
+
+/* Compute the masking vector.
+
+   Masking and short circuiting are deeply connected - masking occurs when
+   control flow reaches a state that is also reachable with short circuiting.
+   In fact, masking corresponds to short circuiting in the CFG for the reversed
+   expression.  This means we can find the limits, the last term in preceeding
+   subexpressions, by following the edges that short circuit to the same
+   outcome.
+
+   In the simplest case a || b:
+
+   a
+   |\
+   | b
+   |/ \
+   T   F
+
+   T has has multiple incoming edges and is the outcome of a short circuit,
+   with top = a, bot = b.  The top node (a) is masked when the edge (b, T) is
+   taken.
+
+   The names "top" and "bot" refer to a pair of nodes with a shared
+   destination. The top is always the node corresponding to the left-most
+   operand of the two it holds that index_map[top] < index_map[bot].
+
+   Now consider (a && b) || (c && d) and its masking vectors:
+
+   a
+   |\
+   b \
+   |\|
+   | c
+   | |\
+   | d \
+   |/ \|
+   T   F
+
+   a[0] = {}
+   a[1] = {}
+   b[0] = {a}
+   b[1] = {}
+   c[0] = {}
+   c[1] = {}
+   d[0] = {c}
+   d[1] = {a,b}
+
+   Note that 0 and 1 are indices and not boolean values - a[0] is the index in
+   the masking vector when a takes the true edge.
+
+   b[0] and d[0] are identical to the a || b example, and d[1] is the bot in
+   the triangle [d, b] -> T.  b is the top node in the [d, b] relationship and
+   last term in (a && b).  To find the other terms masked we use the fact that
+   all nodes in an expression have outgoing edges to either the outcome or some
+   other node in the expression.  The "bot" node is also the last term in a
+   masked subexpression, so the problem becomes finding the subgraph where all
+   paths end up in the successors to bot.
+
+   We find the terms by marking the outcomes (in this case c, T) and walk the
+   predecessors starting at top (in this case b) and masking nodes when both
+   successors are marked.
+
+   The masking vector is represented as two bitfields per term in the
+   expression with the index corresponding to the term in the source
+   expression.  a || b && c becomes the term vector [a b c] and the masking
+   vectors [a[0] a[1] b[0] ...].  The kth bit of a masking vector is set if the
+   the kth term is masked by taking the edge. */
+void
+masking_vectors (conds_ctx& ctx, array_slice<basic_block> blocks,
+		 array_slice<gcov_type_unsigned> masks)
+{
+    gcc_assert (blocks.is_valid ());
+    gcc_assert (!blocks.empty ());
+    gcc_assert (masks.is_valid ());
+
+    sbitmap marks = ctx.G1;
+    sbitmap expr = ctx.G2;
+    vec<basic_block>& queue = ctx.B1;
+    vec<basic_block>& body = ctx.B2;
+    const vec<int>& index_map = ctx.index_map;
+    bitmap_clear (expr);
+
+    for (const basic_block b : blocks)
+	bitmap_set_bit (expr, b->index);
+
+    /* Set up for the iteration - include two outcome nodes in the traversal and
+       ignore the leading term since it cannot mask anything.  The algorithm is
+       not sensitive to the traversal order. */
+    body.truncate (0);
+    body.reserve (blocks.size () + 2);
+    for (const basic_block b : blocks)
+	body.quick_push (b);
+
+    outcomes out = conditional_succs (blocks.back ());
+    body.quick_push (out.t);
+    body.quick_push (out.f);
+    body[0] = body.pop ();
+
+    for (const basic_block b : body)
+    {
+	for (edge e1 : b->preds)
+	for (edge e2 : b->preds)
+	{
+	    const basic_block top = e1->src;
+	    const basic_block bot = e2->src;
+	    const unsigned cond = e1->flags & e2->flags & (EDGE_CONDITION);
+
+	    if (!cond)
+		continue;
+	    if (e1 == e2)
+		continue;
+	    if (!bitmap_bit_p (expr, top->index))
+		continue;
+	    if (!bitmap_bit_p (expr, bot->index))
+		continue;
+	    if (index_map[top->index] > index_map[bot->index])
+		continue;
+
+	    outcomes out = conditional_succs (top);
+	    gcc_assert (out);
+	    bitmap_clear (marks);
+	    bitmap_set_bit (marks, out.t->index);
+	    bitmap_set_bit (marks, out.f->index);
+	    queue.truncate (0);
+	    queue.safe_push (top);
+
+	    // The edge bot -> outcome triggers the masking
+	    const int m = 2*index_of (bot, blocks) + condition_index (cond);
+	    while (!queue.is_empty())
+	    {
+		basic_block q = queue.pop ();
+		/* q may have been processed & completed by being added to the
+		   queue multiple times, so check that there is still work to
+		   do before continuing. */
+		if (bitmap_bit_p (marks, q->index))
+		    continue;
+
+		outcomes succs = conditional_succs (q);
+		if (!bitmap_bit_p (marks, succs.t->index))
+		    continue;
+		if (!bitmap_bit_p (marks, succs.f->index))
+		    continue;
+
+		const int index = index_of (q, blocks);
+		gcc_assert (index != -1);
+		masks[m] |= gcov_type_unsigned (1) << index;
+		bitmap_set_bit (marks, q->index);
+
+		for (edge e : q->preds)
+		{
+		    e = contract_edge_up (e);
+		    if (!edge_conditional_p (e))
+			continue;
+		    if (e->flags & EDGE_DFS_BACK)
+			continue;
+		    if (bitmap_bit_p (marks, e->src->index))
+			continue;
+		    if (!bitmap_bit_p (expr, e->src->index))
+			continue;
+		    queue.safe_push (e->src);
+		}
+	    }
+	}
+    }
+}
+
+/* Find the nodes reachable from p by following only (possibly contracted)
+   condition edges dominated by p and ignore DFS back edges.  From a high level
+   this is partitioning the CFG into subgraphs by removing all non-condition
+   edges and selecting a single connected subgraph.  This creates a cut C = (G,
+   G') where G is the returned explicitly by this function.
+
+   It is assumed that all paths from p go through q (q post-dominates p).  p
+   must always be the first term in an expression and a condition node.
+
+   If |G| = 1 then this is a single term expression. If |G| > 1 then either
+   this is a multi-term expression or the first block in the then/else block is
+   a conditional expression as well.
+
+   Only nodes dominated by p is added - under optimization some blocks may be
+   merged and multiple independent conditions may share the same outcome
+   (making successors misidentified as a right operands), but true right-hand
+   operands are always dominated by the first term.
+
+   The function outputs both a bitmap and a vector as both are useful to the
+   caller. */
+void
+cond_reachable_from (basic_block p, basic_block q, sbitmap expr,
+		     vec<basic_block>& out)
+{
+    out.safe_push (p);
+    bitmap_set_bit (expr, p->index);
+    for (unsigned pos = 0; pos < out.length (); pos++)
+    {
+	for (edge e : out[pos]->succs)
+	{
+	    basic_block dest = contract_edge (e)->dest;
+	    if (dest == q)
+		continue;
+	    if (!dominated_by_p (CDI_DOMINATORS, dest, p))
+		continue;
+	    if (!block_conditional_p (dest))
+		continue;
+	    if (bitmap_bit_p (expr, dest->index))
+		continue;
+	    if (e->flags & EDGE_DFS_BACK)
+		continue;
+
+	    bitmap_set_bit (expr, dest->index);
+	    out.safe_push (dest);
+	}
+    }
+}
+
+/* Find the neighborhood of the graph G = [blocks, blocks+n), the
+   successors of nodes in G that are not also in G.  In the cut C = (G, G')
+   these are the nodes in G' with incoming edges that cross the span. */
+void
+neighborhood (const vec<basic_block>& blocks, sbitmap G, vec<basic_block>& out)
+{
+    for (const basic_block b : blocks)
+    {
+	for (edge e : b->succs)
+	{
+	    basic_block dest = contract_edge (e)->dest;
+	    if (bitmap_bit_p (G, dest->index))
+		continue;
+	    if (!out.contains (dest))
+		out.safe_push (dest);
+	}
+    }
+
+    /* Fix the neighborhood by correcting edge splits to the outcome nodes. */
+    for (unsigned i = 0; i != out.length (); i++)
+    {
+	basic_block prev = out[i];
+	basic_block next = merge_split_outcome (prev);
+	if (next->index != prev->index)
+	{
+	    bitmap_set_bit (G, prev->index);
+	    out[i] = next;
+	}
+    }
+}
+
+/* Find and isolate the expression starting at p.
+
+   Make a cut C = (G, G') following only condition edges.  G is a superset of
+   the expression B, but the walk may include expressions from the then/else
+   blocks if they start with conditions.  Only the subgraph B is the ancestor
+   of *both* the then/else outcome, which means B is the intersection of the
+   ancestors of the nodes in the neighborhood N(G). */
+void
+isolate_expression (conds_ctx &ctx, basic_block p, vec<basic_block>& out)
+{
+    sbitmap expr = ctx.G1;
+    sbitmap reachable = ctx.G2;
+    sbitmap ancestors = ctx.G3;
+    bitmap_clear (expr);
+    bitmap_clear (reachable);
+
+    vec<basic_block>& G = ctx.B1;
+    vec<basic_block>& NG = ctx.B2;
+    G.truncate (0);
+    NG.truncate (0);
+
+    basic_block post = get_immediate_dominator (CDI_POST_DOMINATORS, p);
+    cond_reachable_from (p, post, reachable, G);
+    if (G.length () == 1)
+    {
+	out.safe_push (p);
+	return;
+    }
+
+    neighborhood (G, reachable, NG);
+    bitmap_copy (expr, reachable);
+
+    for (const basic_block neighbor : NG)
+    {
+	bitmap_clear (ancestors);
+	for (edge e : neighbor->preds)
+	    ancestors_of (e->src, p, reachable, ancestors);
+	bitmap_and (expr, expr, ancestors);
+    }
+
+    for (const basic_block b : G)
+	if (bitmap_bit_p (expr, b->index))
+	    out.safe_push (b);
+    out.sort (cmp_index_map, &ctx.index_map);
+}
+
+/* Emit lhs = op1 <op> op2 on edges.  This emits non-atomic instructions and
+   should only be used on the local accumulators. */
+void
+emit_bitwise_op (edge e, tree lhs, tree op1, tree_code op, tree op2)
+{
+    tree tmp;
+    gassign *read;
+    gassign *bitw;
+    gimple *write;
+
+    tmp = make_temp_ssa_name (gcov_type_node, NULL, "__conditions_tmp");
+    read = gimple_build_assign (tmp, op1);
+    tmp = make_temp_ssa_name (gcov_type_node, NULL, "__conditions_tmp");
+    bitw = gimple_build_assign (tmp, op, gimple_assign_lhs (read), op2);
+    write = gimple_build_assign (lhs, gimple_assign_lhs (bitw));
+
+    gsi_insert_on_edge (e, read);
+    gsi_insert_on_edge (e, bitw);
+    gsi_insert_on_edge (e, write);
+}
+
+/* Visitor for make_index_map. */
+void
+make_index_map_visit (basic_block b, vec<basic_block>& L, vec<int>& marks)
+{
+    if (marks[b->index])
+	return;
+
+    for (edge e : b->succs)
+	if (!(e->flags & EDGE_DFS_BACK))
+	    make_index_map_visit (e->dest, L, marks);
+
+    marks[b->index] = 1;
+    L.quick_push (b);
+}
+
+/* Find a topological sorting of the blocks in a function so that left operands
+   are before right operands including subexpressions.  Sorting on block index
+   does not guarantee this property and the syntactical order of terms is very
+   important to the condition coverage.  The sorting algorithm is from Cormen
+   et al (2001) but with back-edges ignored and thus there is no need for
+   temporary marks (for cycle detection).
+
+   It is important to select unvisited nodes in DFS order to ensure the
+   roots/leading terms of boolean expressions are visited first (the other
+   terms being covered by the recursive step), but the visiting order of
+   individual boolean expressions carries no significance.
+
+   For the expression (a || (b && c) || d) the blocks should be [a b c d]. */
+void
+make_index_map (const vec<basic_block>& blocks, int max_index,
+		vec<basic_block>& L, vec<int>& index_map)
+{
+    L.truncate (0);
+    L.reserve (max_index);
+
+    /* Use of the output map as a temporary for tracking visited status. */
+    index_map.truncate (0);
+    index_map.safe_grow_cleared (max_index);
+    for (const basic_block b : blocks)
+	make_index_map_visit (b, L, index_map);
+
+    /* Insert canaries - if there are unreachable nodes (for example infinite
+       loops) then the unreachable nodes should never be needed for comparison,
+       and L.length () < max_index.  An index mapping should also never be
+       recorded twice. */
+    for (unsigned i = 0; i < index_map.length (); i++)
+	index_map[i] = -1;
+
+    gcc_assert (blocks.length () == L.length ());
+    L.reverse ();
+    const unsigned nblocks = L.length ();
+    for (unsigned i = 0; i < nblocks; i++)
+    {
+	gcc_assert (L[i]->index != -1);
+	index_map[L[i]->index] = int (i);
+    }
+}
+
+/* Walk the CFG and collect conditionals.
+
+   1. Collect a candidate set G by walking from the root following all
+      (contracted) condition edges.
+   2. This creates a cut C = (G, G'); find the neighborhood N(G).
+   3. For every node in N(G), follow the edges across the cut and collect all
+      ancestors (that are also in G).
+   4. The intersection of all these ancestor sets is the boolean expression B
+      that starts in root.
+
+   Walking is not guaranteed to find nodes in the order of the expression, it
+   might find (a || b) && c as [a c b], so the result must be sorted by the
+   index map. */
+const vec<basic_block>&
+collect_conditions (conds_ctx& ctx, const basic_block block)
+{
+    vec<basic_block>& blocks = ctx.blocks;
+    blocks.truncate (0);
+
+    if (bitmap_bit_p (ctx.marks, block->index))
+	return blocks;
+
+    if (!block_conditional_p (block))
+    {
+	ctx.mark (block);
+	return blocks;
+    }
+
+    isolate_expression (ctx, block, blocks);
+    ctx.mark (blocks);
+
+    if (blocks.length () > CONDITIONS_MAX_TERMS)
+    {
+	location_t loc = gimple_location (gsi_stmt (gsi_last_bb (block)));
+	warning_at (loc, OPT_Wcoverage_too_many_conditions,
+		    "Too many conditions (found %u); giving up coverage",
+		    blocks.length ());
+	blocks.truncate (0);
+    }
+    return blocks;
+}
+
+/* Used for dfs_enumerate_from () to include all reachable nodes. */
+bool
+yes (const_basic_block, const void *)
+{
+    return true;
+}
+
+}
+
+struct condcov {
+    explicit condcov (unsigned nblocks) noexcept (true) : ctx (nblocks) {}
+    auto_vec<int, 128> m_index;
+    auto_vec<basic_block, 256> m_blocks;
+    auto_vec<gcov_type_unsigned, 512> m_masks;
+    conds_ctx ctx;
+};
+
+unsigned
+cov_length (const struct condcov* cov)
+{
+    if (cov->m_index.is_empty ())
+	return 0;
+    return cov->m_index.length () - 1;
+}
+
+array_slice<basic_block>
+cov_blocks (struct condcov* cov, unsigned n)
+{
+    if (n >= cov->m_index.length ())
+	return array_slice<basic_block>::invalid ();
+
+    basic_block *begin = cov->m_blocks.begin () + cov->m_index[n];
+    basic_block *end = cov->m_blocks.begin () + cov->m_index[n + 1];
+    return array_slice<basic_block> (begin, end - begin);
+}
+
+array_slice<gcov_type_unsigned>
+cov_masks (struct condcov* cov, unsigned n)
+{
+    if (n >= cov->m_index.length ())
+	return array_slice<gcov_type_unsigned>::invalid ();
+
+    gcov_type_unsigned *begin = cov->m_masks.begin () + 2*cov->m_index[n];
+    gcov_type_unsigned *end = cov->m_masks.begin () + 2*cov->m_index[n + 1];
+    return array_slice<gcov_type_unsigned> (begin, end - begin);
+}
+
+void
+cov_free (struct condcov* cov)
+{
+    delete cov;
+}
+
+/* Condition coverage (MC/DC)
+
+   Algorithm
+   ---------
+   Whalen, Heimdahl, De Silva in "Efficient Test Coverage Measurement for
+   MC/DC" describe an algorithm for modified condition/decision coverage based
+   on AST analysis.  This algorithm analyses the control flow graph to analyze
+   expressions and compute masking vectors, but is inspired by their marking
+   functions for recording outcomes.  The individual phases are described in
+   more detail closer to the implementation.
+
+   The CFG is traversed in DFS order.  It is important that the first basic
+   block in an expression is the first one visited, but the order of
+   independent expressions does not matter.  When the function terminates,
+   every node in the dfs should have been processed and marked exactly once.
+   If there are unreachable nodes they are ignored and not instrumented.
+
+   The CFG is broken up into segments between dominators.  This isn't strictly
+   necessary, but since boolean expressions cannot cross dominators it makes
+   for a nice way to introduce limits to searches.
+
+   The coverage only considers the positions, not the symbols, in a
+   conditional, e.g. !A || (!B && A) is a 3-term conditional even though A
+   appears twice.  Subexpressions have no effect on term ordering:
+   (a && (b || (c && d)) || e) comes out as [a b c d e].
+
+   The output for gcov is a vector of pairs of unsigned integers, interpreted
+   as bit-sets, where the bit index corresponds to the index of the condition
+   in the expression.
+ */
+struct condcov*
+find_conditions (struct function *fn)
+{
+    record_loop_exits ();
+    mark_dfs_back_edges (fn);
+
+    const bool have_dom = dom_info_available_p (fn, CDI_DOMINATORS);
+    const bool have_post_dom = dom_info_available_p (fn, CDI_POST_DOMINATORS);
+    if (!have_dom)
+	calculate_dominance_info (CDI_DOMINATORS);
+    if (!have_post_dom)
+	calculate_dominance_info (CDI_POST_DOMINATORS);
+
+    const unsigned nblocks = n_basic_blocks_for_fn (fn);
+    condcov *cov = new condcov (nblocks);
+    conds_ctx& ctx = cov->ctx;
+
+    auto_vec<basic_block, 16> dfs;
+    dfs.safe_grow (nblocks);
+    const basic_block entry = ENTRY_BLOCK_PTR_FOR_FN (fn);
+    const basic_block exit = ENTRY_BLOCK_PTR_FOR_FN (fn);
+    int n = dfs_enumerate_from (entry, 0, yes, dfs.address (), nblocks, exit);
+    dfs.truncate (n);
+    make_index_map (dfs, nblocks, ctx.B1, ctx.index_map);
+
+    /* Visit all reachable nodes and collect conditions.  DFS order is
+       important so the first node of a boolean expression is visited first
+       (it will mark subsequent terms). */
+    cov->m_index.safe_push (0);
+    for (const basic_block b : dfs)
+    {
+	const vec<basic_block>& expr = collect_conditions (ctx, b);
+	if (!expr.is_empty ())
+	{
+	    cov->m_blocks.safe_splice (expr);
+	    cov->m_index.safe_push (cov->m_blocks.length ());
+	}
+    }
+    gcc_assert (ctx.all_marked (dfs));
+
+    if (!have_dom)
+	free_dominance_info (fn, CDI_DOMINATORS);
+    if (!have_post_dom)
+	free_dominance_info (fn, CDI_POST_DOMINATORS);
+
+    cov->m_masks.safe_grow_cleared (2 * cov->m_index.last());
+    const unsigned length = cov_length (cov);
+    for (unsigned i = 0; i < length; i++)
+	masking_vectors (ctx, cov_blocks (cov, i), cov_masks (cov, i));
+
+    return cov;
+}
+
+int
+instrument_decisions (array_slice<basic_block> expr, unsigned condno,
+		      tree *accu, gcov_type_unsigned *masks)
+{
+    /* Zero the local accumulators. */
+    tree zero = build_int_cst (get_gcov_type (), 0);
+    for (edge e : expr[0]->succs)
+    {
+	gsi_insert_on_edge (e, gimple_build_assign (accu[0], zero));
+	gsi_insert_on_edge (e, gimple_build_assign (accu[1], zero));
+    }
+    /* Add instructions for updating the function-local accumulators.  */
+    for (size_t i = 0; i < expr.size (); i++)
+    {
+	for (edge e : expr[i]->succs)
+	{
+	    if (!edge_conditional_p (e))
+		continue;
+
+	    /* accu |= expr[i] */
+	    const int k = condition_index (e->flags);
+	    tree rhs = build_int_cst (gcov_type_node, 1ULL << i);
+	    emit_bitwise_op (e, accu[k], accu[k], BIT_IOR_EXPR, rhs);
+
+	    if (masks[2*i + k] == 0)
+		continue;
+
+	    /* accu &= mask[i] */
+	    tree mask = build_int_cst (gcov_type_node, ~masks[2*i + k]);
+	    for (int j = 0; j < 2; j++)
+		emit_bitwise_op (e, accu[j], accu[j], BIT_AND_EXPR, mask);
+	}
+    }
+
+    const bool atomic = flag_profile_update == PROFILE_UPDATE_ATOMIC;
+    const tree atomic_ior = builtin_decl_explicit
+	(TYPE_PRECISION (gcov_type_node) > 32
+	 ? BUILT_IN_ATOMIC_FETCH_OR_8
+	 : BUILT_IN_ATOMIC_FETCH_OR_4);
+
+    /* Add instructions for flushing the local accumulators.
+
+       It is important that the flushes happen on on the outcome's incoming
+       edges, otherwise flushes could be lost to exception handling.
+
+       void fn (int a)
+       {
+	   if (a)
+	    fclose();
+	   exit();
+       }
+
+       Can yield the CFG:
+       A
+       |\
+       | B
+       |/
+       e
+
+       This typically only happen in optimized builds, but gives linker errors
+       because the counter is left as an undefined symbol. */
+
+    outcomes out = conditional_succs (expr.back ());
+    const basic_block outcome_blocks[] = { out.t, out.t, out.f, out.f, };
+    const int outcome[] = { 0, 1, 0, 1 };
+    for (int i = 0; i < 4; i++)
+    {
+	const int k = outcome[i];
+	for (edge e : outcome_blocks[i]->preds)
+	{
+	    /* The outcome may have been split and we want to check if the
+	       edge is sourced from inside the expression, so contract it to
+	       find the source conditional edge. */
+	    e = contract_edge_up (e);
+
+	    /* Only instrument edges from inside the expression. Sometimes
+	       complicated control flow (like sigsetjmp and gotos) add
+	       predecessors that don't come from the boolean expression. */
+	    if (index_of (e->src, expr) == -1)
+		continue;
+
+	    tree ref = tree_coverage_counter_ref (GCOV_COUNTER_CONDS,
+						  2*condno + k);
+	    tree tmp = make_temp_ssa_name (gcov_type_node, NULL,
+					   "__conditions_tmp");
+	    if (atomic)
+	    {
+		tree relaxed = build_int_cst (integer_type_node,
+					      MEMMODEL_RELAXED);
+		ref = unshare_expr (ref);
+		gassign *read = gimple_build_assign (tmp, accu[k]);
+		gcall *flush = gimple_build_call (atomic_ior, 3,
+						  build_addr (ref),
+						  gimple_assign_lhs (read),
+						  relaxed);
+
+		gsi_insert_on_edge (e, read);
+		gsi_insert_on_edge (e, flush);
+	    }
+	    else
+	    {
+		gassign *read = gimple_build_assign (tmp, ref);
+		tmp = gimple_assign_lhs (read);
+		gsi_insert_on_edge (e, read);
+		ref = unshare_expr (ref);
+		emit_bitwise_op (e, ref, accu[k], BIT_IOR_EXPR, tmp);
+	    }
+	}
+    }
+    return expr.size ();
+}
+
+#undef CONDITIONS_MAX_TERMS
+#undef EDGE_CONDITION
+
 /* Do initialization work for the edge profiler.  */
 
 /* Add code:
@@ -758,7 +1802,7 @@ tree_profiling (void)
 	  thunk = true;
 	  /* When generate profile, expand thunk to gimple so it can be
 	     instrumented same way as other functions.  */
-	  if (profile_arc_flag)
+	  if (profile_arc_flag || profile_condition_flag)
 	    expand_thunk (node, false, true);
 	  /* Read cgraph profile but keep function as thunk at profile-use
 	     time.  */
@@ -803,7 +1847,7 @@ tree_profiling (void)
   release_profile_file_filtering ();
 
   /* Drop pure/const flags from instrumented functions.  */
-  if (profile_arc_flag || flag_test_coverage)
+  if (profile_arc_flag || profile_condition_flag || flag_test_coverage)
     FOR_EACH_DEFINED_FUNCTION (node)
       {
 	if (!gimple_has_body_p (node->decl)
@@ -897,7 +1941,7 @@ pass_ipa_tree_profile::gate (function *)
      disabled.  */
   return (!in_lto_p && !flag_auto_profile
 	  && (flag_branch_probabilities || flag_test_coverage
-	      || profile_arc_flag));
+	      || profile_arc_flag || profile_condition_flag));
 }
 
 } // anon namespace
diff --git a/libgcc/libgcov-merge.c b/libgcc/libgcov-merge.c
index 89741f637e1..9e3e8ee5657 100644
--- a/libgcc/libgcov-merge.c
+++ b/libgcc/libgcov-merge.c
@@ -33,6 +33,11 @@ void __gcov_merge_add (gcov_type *counters  __attribute__ ((unused)),
                        unsigned n_counters __attribute__ ((unused))) {}
 #endif
 
+#ifdef L_gcov_merge_ior
+void __gcov_merge_ior (gcov_type *counters  __attribute__ ((unused)),
+		       unsigned n_counters __attribute__ ((unused))) {}
+#endif
+
 #ifdef L_gcov_merge_topn
 void __gcov_merge_topn (gcov_type *counters  __attribute__ ((unused)),
 			unsigned n_counters __attribute__ ((unused))) {}
-- 
2.34.0


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-10-12 10:16 [PATCH] Add condition coverage profiling Jørgen Kvalsvik
@ 2022-10-18  0:17 ` Hans-Peter Nilsson
  2022-10-18 10:13   ` Jørgen Kvalsvik
  2022-10-25  6:33 ` Ping " Jørgen Kvalsvik
  1 sibling, 1 reply; 39+ messages in thread
From: Hans-Peter Nilsson @ 2022-10-18  0:17 UTC (permalink / raw)
  To: Jørgen Kvalsvik; +Cc: gcc-patches

On Wed, 12 Oct 2022, Jørgen Kvalsvik via Gcc-patches wrote:
> This patch adds support in gcc+gcov for modified condition/decision
> coverage (MC/DC) with the -fprofile-conditions flag.

I'd love improvements in this area.

But this is a serious concern:

> gcov --conditions:
> 
>         3:   17:void fn (int a, int b, int c, int d) {
>         3:   18:    if ((a && (b || c)) && d)
> condition outcomes covered 3/8
> condition  0 not covered (true false)
> condition  1 not covered (true)
> condition  2 not covered (true)
> condition  3 not covered (true)
>         1:   19:        x = 1;
>         -:   20:    else
>         2:   21:        x = 2;
>         3:   22:}

Is this the suggested output from gcov?

Sorry, but this is too hard to read; I can't read this.  What 
does it mean?  What's 0 and what's 1 and which are the 8 
conditions?  (Why not 16 or more; which are redundant?)  Or to 
wit, a glance, which parts of (a && (b || c)) && d are actually 
covered?

There has got to be a better *intuitively* understandable 
presentation format than this. If you please forgive the errors 
in not matching the partal expressions like in your proposal and 
focus on the presentation format, I'd suggest something like, 
for a one-time run with a=true, b=false, c=true, d=false:

"With:
         3:   18:    if ((a && (b || c)) && d)
0:                       ^^^^^^^^^^^^^^^
1:                                          ^
2:                        ^
3:                             ^^^^^^^^
4:                              ^
5:                                   ^
condition  0 not covered (false)
condition  1 not covered (true)
condition  2 not covered (false)
condition  3 not covered (false)
condition  4 not covered (true)
condition  5 not covered (false)"
(etc)

Possibly with each partial expression repeated above its 
underscoring for readability, because of the increasing distance 
between the underscoring and referred source.

Actually, a separate indexed table like that isn't the best 
choice either.  Perhaps better quoting the source:

"condition (a && (b || c)) false not covered
condition d false not covered
condition (b || c) false not covered
condition b true not covered
condition c false not covered"

Or, just underscoring as instead of quoting the source:
"        3:   18:    if ((a && (b || c)) && d)

In condition:            ^^^^^^^^^^^^^^^
false not covered"
(etc)

It is possible I completely misunderstand your proposal, but 
there has to be something from the above to pick.  I'd hate to 
see this go down because of usability problems.  Hope this was 
constructive.

brgds, H-P

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-10-18  0:17 ` Hans-Peter Nilsson
@ 2022-10-18 10:13   ` Jørgen Kvalsvik
  0 siblings, 0 replies; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-10-18 10:13 UTC (permalink / raw)
  To: Hans-Peter Nilsson; +Cc: gcc-patches

On 18/10/2022 02:17, Hans-Peter Nilsson wrote:
> On Wed, 12 Oct 2022, Jørgen Kvalsvik via Gcc-patches wrote:
>> This patch adds support in gcc+gcov for modified condition/decision
>> coverage (MC/DC) with the -fprofile-conditions flag.
> 
> I'd love improvements in this area.
> 
> But this is a serious concern:
> 
>> gcov --conditions:
>>
>>         3:   17:void fn (int a, int b, int c, int d) {
>>         3:   18:    if ((a && (b || c)) && d)
>> condition outcomes covered 3/8
>> condition  0 not covered (true false)
>> condition  1 not covered (true)
>> condition  2 not covered (true)
>> condition  3 not covered (true)
>>         1:   19:        x = 1;
>>         -:   20:    else
>>         2:   21:        x = 2;
>>         3:   22:}
> 
> Is this the suggested output from gcov?
> 
> Sorry, but this is too hard to read; I can't read this.  What 
> does it mean?  What's 0 and what's 1 and which are the 8 
> conditions?  (Why not 16 or more; which are redundant?)  Or to 
> wit, a glance, which parts of (a && (b || c)) && d are actually 
> covered?

Hello,

Thanks for the feedback. I've modeled the output after the existing branch
coverage, but as you noticed conditions are slightly different because the
interesting output is what's _not_ taken. Like with branches, "conditions %d"
are the indices of the terms in the expression. The values in parenthesis are
the outcomes not found to be independent. Anything not listed is covered, guided
by the summary (conditions output covered n/m).

> 
> There has got to be a better *intuitively* understandable 
> presentation format than this. If you please forgive the errors 
> in not matching the partal expressions like in your proposal and 
> focus on the presentation format, I'd suggest something like, 
> for a one-time run with a=true, b=false, c=true, d=false:
> 
> "With:
>          3:   18:    if ((a && (b || c)) && d)
> 0:                       ^^^^^^^^^^^^^^^
> 1:                                          ^
> 2:                        ^
> 3:                             ^^^^^^^^
> 4:                              ^
> 5:                                   ^
> condition  0 not covered (false)
> condition  1 not covered (true)
> condition  2 not covered (false)
> condition  3 not covered (false)
> condition  4 not covered (true)
> condition  5 not covered (false)"
> (etc)
> 
> Possibly with each partial expression repeated above its 
> underscoring for readability, because of the increasing distance 
> between the underscoring and referred source.
> 
> Actually, a separate indexed table like that isn't the best 
> choice either.  Perhaps better quoting the source:
> 
> "condition (a && (b || c)) false not covered
> condition d false not covered
> condition (b || c) false not covered
> condition b true not covered
> condition c false not covered"
> 
> Or, just underscoring as instead of quoting the source:
> "        3:   18:    if ((a && (b || c)) && d)
> 
> In condition:            ^^^^^^^^^^^^^^^
> false not covered"
> (etc)
> 
> It is possible I completely misunderstand your proposal, but 
> there has to be something from the above to pick.  I'd hate to 
> see this go down because of usability problems.  Hope this was 
> constructive.
> 
> brgds, H-P

I agree, all of these are good suggestions to better outputs. The problem is
that information needed to create this output is, as far as I know, not
available when gcov runs as it is not recorded. When the instrumentation for the
condition coverage itself is determined this information *is* available (basic
blocks with locus) so it should be possible to improve both the output of
condition coverage and maybe even the branch coverage too.

Thanks,
Jørgen

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Ping [PATCH] Add condition coverage profiling
  2022-10-12 10:16 [PATCH] Add condition coverage profiling Jørgen Kvalsvik
  2022-10-18  0:17 ` Hans-Peter Nilsson
@ 2022-10-25  6:33 ` Jørgen Kvalsvik
  2022-10-27 10:34   ` Martin Liška
  2022-11-02  6:16   ` Jørgen Kvalsvik
  1 sibling, 2 replies; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-10-25  6:33 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska

On 12/10/2022 12:16, Jørgen Kvalsvik wrote:
> This patch adds support in gcc+gcov for modified condition/decision
> coverage (MC/DC) with the -fprofile-conditions flag. MC/DC is a type of
> test/code coverage and it is particularly important in the avation and
> automotive industries for safety-critical applications. MC/DC it is
> required for or recommended by:
> 
>     * DO-178C for the most critical software (Level A) in avionics
>     * IEC 61508 for SIL 4
>     * ISO 26262-6 for ASIL D
> 
> From the SQLite webpage:
> 
>     Two methods of measuring test coverage were described above:
>     "statement" and "branch" coverage. There are many other test
>     coverage metrics besides these two. Another popular metric is
>     "Modified Condition/Decision Coverage" or MC/DC. Wikipedia defines
>     MC/DC as follows:
> 
>         * Each decision tries every possible outcome.
>         * Each condition in a decision takes on every possible outcome.
>         * Each entry and exit point is invoked.
>         * Each condition in a decision is shown to independently affect
>           the outcome of the decision.
> 
>     In the C programming language where && and || are "short-circuit"
>     operators, MC/DC and branch coverage are very nearly the same thing.
>     The primary difference is in boolean vector tests. One can test for
>     any of several bits in bit-vector and still obtain 100% branch test
>     coverage even though the second element of MC/DC - the requirement
>     that each condition in a decision take on every possible outcome -
>     might not be satisfied.
> 
>     https://sqlite.org/testing.html#mcdc
> 
> Wahlen, Heimdahl, and De Silva "Efficient Test Coverage Measurement for
> MC/DC" describes an algorithm for adding instrumentation by carrying
> over information from the AST, but my algorithm analyses the the control
> flow graph to instrument for coverage. This has the benefit of being
> programming language independent and faithful to compiler decisions
> and transformations, although I have only tested it on constructs in C
> and C++, see testsuite/gcc.misc-tests and testsuite/g++.dg.
> 
> Like Wahlen et al this implementation records coverage in fixed-size
> bitsets which gcov knows how to interpret. This is very fast, but
> introduces a limit on the number of terms in a single boolean
> expression, the number of bits in a gcov_unsigned_type (which is
> typedef'd to uint64_t), so for most practical purposes this would be
> acceptable. This limitation is in the implementation and not the
> algorithm, so support for more conditions can be added by also
> introducing arbitrary-sized bitsets.
> 
> For space overhead, the instrumentation needs two accumulators
> (gcov_unsigned_type) per condition in the program which will be written
> to the gcov file. In addition, every function gets a pair of local
> accumulators, but these accmulators are reused between conditions in the
> same function.
> 
> For time overhead, there is a zeroing of the local accumulators for
> every condition and one or two bitwise operation on every edge taken in
> the an expression.
> 
> In action it looks pretty similar to the branch coverage. The -g short
> opt carries no significance, but was chosen because it was an available
> option with the upper-case free too.
> 
> gcov --conditions:
> 
>         3:   17:void fn (int a, int b, int c, int d) {
>         3:   18:    if ((a && (b || c)) && d)
> condition outcomes covered 3/8
> condition  0 not covered (true false)
> condition  1 not covered (true)
> condition  2 not covered (true)
> condition  3 not covered (true)
>         1:   19:        x = 1;
>         -:   20:    else
>         2:   21:        x = 2;
>         3:   22:}
> 
> gcov --conditions --json-format:
> 
>     "conditions": [
>         {
>             "not_covered_false": [
>                 0
>             ],
>             "count": 8,
>             "covered": 3,
>             "not_covered_true": [
>                 0,
>                 1,
>                 2,
>                 3
>             ]
>         }
>     ],
> 
> Some expressions, mostly those without else-blocks, are effectively
> "rewritten" in the CFG construction making the algorithm unable to
> distinguish them:
> 
> and.c:
> 
>     if (a && b && c)
>         x = 1;
> 
> ifs.c:
> 
>     if (a)
>         if (b)
>             if (c)
>                 x = 1;
> 
> gcc will build the same graph for both these programs, and gcov will
> report boths as 3-term expressions. It is vital that it is not
> interpreted the other way around (which is consistent with the shape of
> the graph) because otherwise the masking would be wrong for the and.c
> program which is a more severe error. While surprising, users would
> probably expect some minor rewriting of semantically-identical
> expressions.
> 
> and.c.gcov:
>     #####:    2:    if (a && b && c)
> decisions covered 6/6
>     #####:    3:        x = 1;
> 
> ifs.c.gcov:
>     #####:    2:    if (a)
>     #####:    3:        if (b)
>     #####:    4:            if (c)
>     #####:    5:                x = 1;
> condition outcomes covered 6/6
> 
> Adding else clauses alters the program (ifs.c can have 3 elses, and.c
> only 1) and coverage becomes less surprising
> 
> ifs.c.gcov:
>     #####:    2:    if (a)
> condition outcomes covered 2/2
>     #####:    4:    {
>     #####:    4:        if (b)
> condition outcomes covered 2/2
>               5:        {
>     #####:    6:            if (c)
> condition outcomes covered 2/2
>     #####:    7:                x = 1;
>     #####:    8:        }
>     #####:    9:        else
>     #####:   10:            x = 2;
>     #####:   11:    }
>     #####:   12:    else
>     #####:   13:        x = 3;
> 
> Since the algorithm works on CFGs, it cannot detect some ternary
> operator introduced conditionals. For example, int x = a ? 0 : 1 in
> gimple becomes _x = (_a == 0). From source you would expect coverage,
> but it gets neither branch nor condition coverage. For completeness, it
> could be achieved by scanning all gimple statements for such
> comparisons, and insert an extra instruction for recording the outcome.
> 
> The test suite contains a lot of small programs functions. Some of these
> were designed by hand to test for specific behaviours and graph shapes,
> and some are previously-failed test cases in other programs adapted into
> the test suite.
> 
> Alternative author email: Jørgen Kvalsvik <j@lambda.is>
> 
> gcc/ChangeLog:
> 
> 	* builtins.cc (expand_builtin_fork_or_exec): Check
> 	profile_condition_flag.
> 	* collect2.cc (main): Add -fno-profile-conditions to OBSTACK.
> 	* common.opt: Add new options -fprofile-conditions and
> 	* doc/gcov.texi: Add --conditions documentation.
> 	* doc/invoke.texi: Add -fprofile-conditions documentation.
> 	* gcc.cc: Link gcov on -fprofile-conditions.
> 	* gcov-counter.def (GCOV_COUNTER_CONDS): New.
> 	* gcov-dump.cc (tag_conditions): New.
> 	* gcov-io.h (GCOV_TAG_CONDS): New.
> 	(GCOV_TAG_CONDS_LENGTH): Likewise.
> 	(GCOV_TAG_CONDS_NUM): Likewise.
> 	* gcov.cc (class condition_info): New.
> 	(condition_info::condition_info): New.
> 	(condition_info::popcount): New.
> 	(struct coverage_info): New.
> 	(add_condition_counts): New.
> 	(output_conditions): New.
> 	(print_usage): Add -g, --conditions.
> 	(process_args): Likewise.
> 	(output_intermediate_json_line): Output conditions.
> 	(read_graph_file): Read conditions counters.
> 	(read_count_file): Read conditions counters.
> 	(file_summary): Print conditions.
> 	(accumulate_line_info): Accumulate conditions.
> 	(output_line_details): Print conditions.
> 	* ipa-inline.cc (can_early_inline_edge_p): Check
> 	profile_condition_flag.
> 	* ipa-split.cc (pass_split_functions::gate): Likewise.
> 	* passes.cc (finish_optimization_passes): Likewise.
> 	* profile.cc (find_conditions): New declaration.
> 	(cov_length): Likewise.
> 	(cov_blocks): Likewise.
> 	(cov_masks): Likewise.
> 	(cov_free): Likewise.
> 	(instrument_decisions): New.
> 	(read_thunk_profile): Control output to file.
> 	(branch_prob): Call find_conditions, instrument_decisions.
> 	(init_branch_prob): Add total_num_conds.
> 	(end_branch_prob): Likewise.
> 	* tree-profile.cc (struct conds_ctx): New.
> 	(CONDITIONS_MAX_TERMS): New.
> 	(EDGE_CONDITION): New.
> 	(cmp_index_map): New.
> 	(index_of): New.
> 	(block_conditional_p): New.
> 	(edge_conditional_p): New.
> 	(single): New.
> 	(single_edge): New.
> 	(contract_edge): New.
> 	(contract_edge_up): New.
> 	(merge_split_outcome): New.
> 	(ancestors_of): New.
> 	(struct outcomes): New.
> 	(conditional_succs): New.
> 	(condition_index): New.
> 	(masking_vectors): New.
> 	(cond_reachable_from): New.
> 	(neighborhood): New.
> 	(isolate_expression): New.
> 	(emit_bitwise_op): New.
> 	(make_index_map_visit): New.
> 	(make_index_map): New.
> 	(collect_conditions): New.
> 	(yes): New.
> 	(struct condcov): New.
> 	(cov_length): New.
> 	(cov_blocks): New.
> 	(cov_masks): New.
> 	(cov_free): New.
> 	(find_conditions): New.
> 	(instrument_decisions): New.
> 	(tree_profiling): Check profile_condition_flag.
> 	(pass_ipa_tree_profile::gate): Likewise.
> 
> libgcc/ChangeLog:
> 
> 	* libgcov-merge.c (__gcov_merge_ior): New dummy function.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* lib/gcov.exp: Add condition coverage test function.
> 	* g++.dg/gcov/gcov-18.C: New test.
> 	* gcc.misc-tests/gcov-19.c: New test.
> 	* gcc.misc-tests/gcov-20.c: New test.
> 	* gcc.misc-tests/gcov-21.c: New test.
> ---
>  gcc/builtins.cc                        |    2 +-
>  gcc/collect2.cc                        |    7 +-
>  gcc/common.opt                         |    8 +
>  gcc/doc/gcov.texi                      |   36 +
>  gcc/doc/invoke.texi                    |   19 +
>  gcc/gcc.cc                             |    4 +-
>  gcc/gcov-counter.def                   |    3 +
>  gcc/gcov-dump.cc                       |   24 +
>  gcc/gcov-io.h                          |    3 +
>  gcc/gcov.cc                            |  200 +++-
>  gcc/ipa-inline.cc                      |    2 +-
>  gcc/ipa-split.cc                       |    3 +-
>  gcc/passes.cc                          |    3 +-
>  gcc/profile.cc                         |   84 +-
>  gcc/testsuite/g++.dg/gcov/gcov-18.C    |  234 +++++
>  gcc/testsuite/gcc.misc-tests/gcov-19.c | 1250 ++++++++++++++++++++++++
>  gcc/testsuite/gcc.misc-tests/gcov-20.c |   22 +
>  gcc/testsuite/gcc.misc-tests/gcov-21.c |   16 +
>  gcc/testsuite/lib/gcov.exp             |  191 +++-
>  gcc/tree-profile.cc                    | 1050 +++++++++++++++++++-
>  libgcc/libgcov-merge.c                 |    5 +
>  21 files changed, 3138 insertions(+), 28 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-18.C
>  create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-19.c
>  create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-20.c
>  create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-21.c
> 
> diff --git a/gcc/builtins.cc b/gcc/builtins.cc
> index 5f319b28030..972ae7e2040 100644
> --- a/gcc/builtins.cc
> +++ b/gcc/builtins.cc
> @@ -5884,7 +5884,7 @@ expand_builtin_fork_or_exec (tree fn, tree exp, rtx target, int ignore)
>    tree call;
>  
>    /* If we are not profiling, just call the function.  */
> -  if (!profile_arc_flag)
> +  if (!profile_arc_flag && !profile_condition_flag)
>      return NULL_RTX;
>  
>    /* Otherwise call the wrapper.  This should be equivalent for the rest of
> diff --git a/gcc/collect2.cc b/gcc/collect2.cc
> index d81c7f28f16..0cd8bf4a3a3 100644
> --- a/gcc/collect2.cc
> +++ b/gcc/collect2.cc
> @@ -1032,9 +1032,9 @@ main (int argc, char **argv)
>        lto_mode = LTO_MODE_LTO;
>    }
>  
> -  /* -fno-profile-arcs -fno-test-coverage -fno-branch-probabilities
> -     -fno-exceptions -w -fno-whole-program */
> -  num_c_args += 6;
> +  /* -fno-profile-arcs -fno-profile-conditions -fno-test-coverage
> +     -fno-branch-probabilities -fno-exceptions -w -fno-whole-program */
> +  num_c_args += 7;
>  
>    c_argv = XCNEWVEC (char *, num_c_args);
>    c_ptr = CONST_CAST2 (const char **, char **, c_argv);
> @@ -1230,6 +1230,7 @@ main (int argc, char **argv)
>      }
>    obstack_free (&temporary_obstack, temporary_firstobj);
>    *c_ptr++ = "-fno-profile-arcs";
> +  *c_ptr++ = "-fno-profile-conditions";
>    *c_ptr++ = "-fno-test-coverage";
>    *c_ptr++ = "-fno-branch-probabilities";
>    *c_ptr++ = "-fno-exceptions";
> diff --git a/gcc/common.opt b/gcc/common.opt
> index bce3e514f65..5d0682e283a 100644
> --- a/gcc/common.opt
> +++ b/gcc/common.opt
> @@ -859,6 +859,10 @@ Wcoverage-invalid-line-number
>  Common Var(warn_coverage_invalid_linenum) Init(1) Warning
>  Warn in case a function ends earlier than it begins due to an invalid linenum macros.
>  
> +Wcoverage-too-many-conditions
> +Common Var(warn_too_many_conditions) Init(1) Warning
> +Warn when a conditional has too many terms and coverage gives up.
> +
>  Wmissing-profile
>  Common Var(warn_missing_profile) Init(1) Warning
>  Warn in case profiles in -fprofile-use do not exist.
> @@ -2318,6 +2322,10 @@ fprofile-arcs
>  Common Var(profile_arc_flag)
>  Insert arc-based program profiling code.
>  
> +fprofile-conditions
> +Common Var(profile_condition_flag)
> +Insert condition coverage profiling code.
> +
>  fprofile-dir=
>  Common Joined RejectNegative Var(profile_data_prefix)
>  Set the top-level directory for storing the profile data.
> diff --git a/gcc/doc/gcov.texi b/gcc/doc/gcov.texi
> index a1f7d26e610..7f2c0d00d94 100644
> --- a/gcc/doc/gcov.texi
> +++ b/gcc/doc/gcov.texi
> @@ -124,6 +124,7 @@ gcov [@option{-v}|@option{--version}] [@option{-h}|@option{--help}]
>       [@option{-a}|@option{--all-blocks}]
>       [@option{-b}|@option{--branch-probabilities}]
>       [@option{-c}|@option{--branch-counts}]
> +     [@option{-g}|@option{--conditions}]
>       [@option{-d}|@option{--display-progress}]
>       [@option{-f}|@option{--function-summaries}]
>       [@option{-j}|@option{--json-format}]
> @@ -169,6 +170,13 @@ be shown, unless the @option{-u} option is given.
>  Write branch frequencies as the number of branches taken, rather than
>  the percentage of branches taken.
>  
> +@item -g
> +@itemx --conditions
> +Write condition coverage to the output file, and write condition summary info
> +to the standard output.  This option allows you to see if the conditions in
> +your program at least once had an independent effect on the outcome of the
> +boolean expression (modified condition/decision coverage).
> +
>  @item -d
>  @itemx --display-progress
>  Display the progress on the standard output.
> @@ -293,6 +301,7 @@ Each @var{line} has the following form:
>  @{
>    "branches": ["$branch"],
>    "count": 2,
> +  "conditions": ["$condition"],
>    "line_number": 15,
>    "unexecuted_block": false,
>    "function_name": "foo",
> @@ -341,6 +350,33 @@ Fields of the @var{branch} element have following semantics:
>  @var{throw}: true when the branch is an exceptional branch
>  @end itemize
>  
> +Each @var{condition} element have the following semantics:
> +
> +@smallexample
> +@{
> +  "count": 4,
> +  "covered": 2,
> +  "not_covered_false": [],
> +  "not_covered_true": [0, 1],
> +@}
> +@end smallexample
> +
> +Fields of the @var{condition} element have following semantics:
> +
> +@itemize @bullet
> +@item
> +@var{count}: number of conditions in this expression
> +
> +@item
> +@var{covered}: number of covered conditions in this expression
> +
> +@item
> +@var{not_covered_true}: terms, by index, not seen as true in this expression
> +
> +@item
> +@var{not_covered_false}: terms, by index, not seen as false in this expression
> +@end itemize
> +
>  @item -H
>  @itemx --human-readable
>  Write counts in human readable format (like 24.6k).
> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
> index a9ecc4426a4..62d48df369d 100644
> --- a/gcc/doc/invoke.texi
> +++ b/gcc/doc/invoke.texi
> @@ -614,6 +614,7 @@ Objective-C and Objective-C++ Dialects}.
>  @item Program Instrumentation Options
>  @xref{Instrumentation Options,,Program Instrumentation Options}.
>  @gccoptlist{-p  -pg  -fprofile-arcs  --coverage  -ftest-coverage @gol
> +-fprofile-conditions @gol
>  -fprofile-abs-path @gol
>  -fprofile-dir=@var{path}  -fprofile-generate  -fprofile-generate=@var{path} @gol
>  -fprofile-info-section  -fprofile-info-section=@var{name} @gol
> @@ -6140,6 +6141,13 @@ error.  @option{-Wno-coverage-invalid-line-number} can be used to disable the
>  warning or @option{-Wno-error=coverage-invalid-line-number} can be used to
>  disable the error.
>  
> +@item -Wno-coverage-too-many-conditions
> +@opindex Wno-coverage-too-many-conditions
> +@opindex Wcoverage-too-many-conditions
> +Warn in case a condition have too many terms and GCC gives up coverage.
> +Coverage is given up when there are more terms in the conditional than there
> +are bits in a @code{gcov_type_unsigned}.  This warning is enabled by default.
> +
>  @item -Wno-cpp @r{(C, Objective-C, C++, Objective-C++ and Fortran only)}
>  @opindex Wno-cpp
>  @opindex Wcpp
> @@ -15839,6 +15847,13 @@ E.g. @code{gcc a.c b.c -o binary} would generate @file{binary-a.gcda} and
>  
>  @xref{Cross-profiling}.
>  
> +@item -fprofile-conditions
> +@opindex fprofile-conditions
> +Add code so that program conditions are instrumented.  During execution the
> +program records what terms in a conditional contributes to a decision.  The
> +data may be used to verify that all terms in a booleans are tested and have an
> +effect on the outcome of a condition.
> +
>  @cindex @command{gcov}
>  @item --coverage
>  @opindex coverage
> @@ -15899,6 +15914,10 @@ executed.  When an arc is the only exit or only entrance to a block, the
>  instrumentation code can be added to the block; otherwise, a new basic
>  block must be created to hold the instrumentation code.
>  
> +With @option{-fprofile-conditions}, for each conditional in your program GCC
> +creates a bitset and records the boolean values that have an independent effect
> +on the outcome of that expression.
> +
>  @need 2000
>  @item -ftest-coverage
>  @opindex ftest-coverage
> diff --git a/gcc/gcc.cc b/gcc/gcc.cc
> index afb23cd07b0..601e48377de 100644
> --- a/gcc/gcc.cc
> +++ b/gcc/gcc.cc
> @@ -1145,7 +1145,7 @@ proper position among the other output files.  */
>  	%:include(libgomp.spec)%(link_gomp)}\
>      %{fgnu-tm:%:include(libitm.spec)%(link_itm)}\
>      %(mflib) " STACK_SPLIT_SPEC "\
> -    %{fprofile-arcs|fprofile-generate*|coverage:-lgcov} " SANITIZER_SPEC " \
> +    %{fprofile-arcs|fprofile-conditions|fprofile-generate*|coverage:-lgcov} " SANITIZER_SPEC " \
>      %{!nostdlib:%{!r:%{!nodefaultlibs:%(link_ssp) %(link_gcc_c_sequence)}}}\
>      %{!nostdlib:%{!r:%{!nostartfiles:%E}}} %{T*}  \n%(post_link) }}}}}}"
>  #endif
> @@ -1262,7 +1262,7 @@ static const char *cc1_options =
>   %{!fsyntax-only:%{S:%W{o*}%{!o*:-o %w%b.s}}}\
>   %{fsyntax-only:-o %j} %{-param*}\
>   %{coverage:-fprofile-arcs -ftest-coverage}\
> - %{fprofile-arcs|fprofile-generate*|coverage:\
> + %{fprofile-arcs|fprofile-conditions|fprofile-generate*|coverage:\
>     %{!fprofile-update=single:\
>       %{pthread:-fprofile-update=prefer-atomic}}}";
>  
> diff --git a/gcc/gcov-counter.def b/gcc/gcov-counter.def
> index 6d2182bd3db..96563a59a45 100644
> --- a/gcc/gcov-counter.def
> +++ b/gcc/gcov-counter.def
> @@ -49,3 +49,6 @@ DEF_GCOV_COUNTER(GCOV_COUNTER_IOR, "ior", _ior)
>  
>  /* Time profile collecting first run of a function */
>  DEF_GCOV_COUNTER(GCOV_TIME_PROFILER, "time_profiler", _time_profile)
> +
> +/* Conditions.  The counter is interpreted as a bit-set.  */
> +DEF_GCOV_COUNTER(GCOV_COUNTER_CONDS, "conditions", _ior)
> diff --git a/gcc/gcov-dump.cc b/gcc/gcov-dump.cc
> index 03023bfb226..6dc1df6e3e1 100644
> --- a/gcc/gcov-dump.cc
> +++ b/gcc/gcov-dump.cc
> @@ -38,6 +38,7 @@ static void print_version (void);
>  static void tag_function (const char *, unsigned, int, unsigned);
>  static void tag_blocks (const char *, unsigned, int, unsigned);
>  static void tag_arcs (const char *, unsigned, int, unsigned);
> +static void tag_conditions (const char *, unsigned, int, unsigned);
>  static void tag_lines (const char *, unsigned, int, unsigned);
>  static void tag_counters (const char *, unsigned, int, unsigned);
>  static void tag_summary (const char *, unsigned, int, unsigned);
> @@ -77,6 +78,7 @@ static const tag_format_t tag_table[] =
>    {GCOV_TAG_FUNCTION, "FUNCTION", tag_function},
>    {GCOV_TAG_BLOCKS, "BLOCKS", tag_blocks},
>    {GCOV_TAG_ARCS, "ARCS", tag_arcs},
> +  {GCOV_TAG_CONDS, "CONDITIONS", tag_conditions},
>    {GCOV_TAG_LINES, "LINES", tag_lines},
>    {GCOV_TAG_OBJECT_SUMMARY, "OBJECT_SUMMARY", tag_summary},
>    {0, NULL, NULL}
> @@ -392,6 +394,28 @@ tag_arcs (const char *filename ATTRIBUTE_UNUSED,
>      }
>  }
>  
> +static void
> +tag_conditions (const char *filename ATTRIBUTE_UNUSED,
> +		unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED,
> +		unsigned depth)
> +{
> +  unsigned n_conditions = GCOV_TAG_CONDS_NUM (length);
> +
> +  printf (" %u conditionals", n_conditions);
> +  if (flag_dump_contents)
> +    {
> +      for (unsigned ix = 0; ix != n_conditions; ix++)
> +	{
> +	  const unsigned blockno = gcov_read_unsigned ();
> +	  const unsigned nterms = gcov_read_unsigned ();
> +
> +	  printf ("\n");
> +	  print_prefix (filename, depth, gcov_position ());
> +	  printf (VALUE_PADDING_PREFIX "block %u:", blockno);
> +	  printf (" %u", nterms);
> +	}
> +    }
> +}
>  static void
>  tag_lines (const char *filename ATTRIBUTE_UNUSED,
>  	   unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED,
> diff --git a/gcc/gcov-io.h b/gcc/gcov-io.h
> index e91cd736556..a9b52cc6abc 100644
> --- a/gcc/gcov-io.h
> +++ b/gcc/gcov-io.h
> @@ -261,6 +261,9 @@ typedef uint64_t gcov_type_unsigned;
>  #define GCOV_TAG_ARCS		 ((gcov_unsigned_t)0x01430000)
>  #define GCOV_TAG_ARCS_LENGTH(NUM)  (1 + (NUM) * 2 * GCOV_WORD_SIZE)
>  #define GCOV_TAG_ARCS_NUM(LENGTH)  (((LENGTH / GCOV_WORD_SIZE) - 1) / 2)
> +#define GCOV_TAG_CONDS             ((gcov_unsigned_t)0x01470000)
> +#define GCOV_TAG_CONDS_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE)
> +#define GCOV_TAG_CONDS_NUM(LENGTH) (((LENGTH) / GCOV_WORD_SIZE) / 2)
>  #define GCOV_TAG_LINES		 ((gcov_unsigned_t)0x01450000)
>  #define GCOV_TAG_COUNTER_BASE 	 ((gcov_unsigned_t)0x01a10000)
>  #define GCOV_TAG_COUNTER_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE)
> diff --git a/gcc/gcov.cc b/gcc/gcov.cc
> index 9cf1071166f..bc2932e7890 100644
> --- a/gcc/gcov.cc
> +++ b/gcc/gcov.cc
> @@ -79,6 +79,7 @@ using namespace std;
>  class function_info;
>  class block_info;
>  class source_info;
> +class condition_info;
>  
>  /* Describes an arc between two basic blocks.  */
>  
> @@ -132,6 +133,28 @@ public:
>    vector<unsigned> lines;
>  };
>  
> +class condition_info
> +{
> +public:
> +  condition_info ();
> +
> +  int popcount () const;
> +
> +  gcov_type_unsigned truev;
> +  gcov_type_unsigned falsev;
> +
> +  unsigned n_terms;
> +};
> +
> +condition_info::condition_info (): truev (0), falsev (0), n_terms (0)
> +{
> +}
> +
> +int condition_info::popcount () const
> +{
> +    return __builtin_popcountll (truev) + __builtin_popcountll (falsev);
> +}
> +
>  /* Describes a basic block. Contains lists of arcs to successor and
>     predecessor blocks.  */
>  
> @@ -165,6 +188,8 @@ public:
>    /* Block is a landing pad for longjmp or throw.  */
>    unsigned is_nonlocal_return : 1;
>  
> +  condition_info conditions;
> +
>    vector<block_location_info> locations;
>  
>    struct
> @@ -275,6 +300,8 @@ public:
>    vector<block_info> blocks;
>    unsigned blocks_executed;
>  
> +  vector<condition_info*> conditions;
> +
>    /* Raw arc coverage counts.  */
>    vector<gcov_type> counts;
>  
> @@ -351,6 +378,9 @@ struct coverage_info
>    int branches_executed;
>    int branches_taken;
>  
> +  int conditions;
> +  int conditions_covered;
> +
>    int calls;
>    int calls_executed;
>  
> @@ -550,6 +580,10 @@ static int multiple_files = 0;
>  
>  static int flag_branches = 0;
>  
> +/* Output conditions (modified condition/decision coverage) */
> +
> +static int flag_conditions = 0;
> +
>  /* Show unconditional branches too.  */
>  static int flag_unconditional = 0;
>  
> @@ -656,6 +690,7 @@ static int read_count_file (void);
>  static void solve_flow_graph (function_info *);
>  static void find_exception_blocks (function_info *);
>  static void add_branch_counts (coverage_info *, const arc_info *);
> +static void add_condition_counts (coverage_info *, const block_info *);
>  static void add_line_counts (coverage_info *, function_info *);
>  static void executed_summary (unsigned, unsigned);
>  static void function_summary (const coverage_info *);
> @@ -664,6 +699,7 @@ static const char *format_gcov (gcov_type, gcov_type, int);
>  static void accumulate_line_counts (source_info *);
>  static void output_gcov_file (const char *, source_info *);
>  static int output_branch_count (FILE *, int, const arc_info *);
> +static void output_conditions (FILE *, const block_info *);
>  static void output_lines (FILE *, const source_info *);
>  static string make_gcov_file_name (const char *, const char *);
>  static char *mangle_name (const char *);
> @@ -928,6 +964,7 @@ print_usage (int error_p)
>    fnotice (file, "  -b, --branch-probabilities      Include branch probabilities in output\n");
>    fnotice (file, "  -c, --branch-counts             Output counts of branches taken\n\
>                                      rather than percentages\n");
> +  fnotice (file, "  -g, --conditions                Include condition/decision coverage in output\n");
>    fnotice (file, "  -d, --display-progress          Display progress information\n");
>    fnotice (file, "  -D, --debug			    Display debugging dumps\n");
>    fnotice (file, "  -f, --function-summaries        Output summaries for each function\n");
> @@ -980,6 +1017,7 @@ static const struct option options[] =
>    { "all-blocks",           no_argument,       NULL, 'a' },
>    { "branch-probabilities", no_argument,       NULL, 'b' },
>    { "branch-counts",        no_argument,       NULL, 'c' },
> +  { "conditions",           no_argument,       NULL, 'g' },
>    { "json-format",	    no_argument,       NULL, 'j' },
>    { "human-readable",	    no_argument,       NULL, 'H' },
>    { "no-output",            no_argument,       NULL, 'n' },
> @@ -1008,7 +1046,7 @@ process_args (int argc, char **argv)
>  {
>    int opt;
>  
> -  const char *opts = "abcdDfhHijklmno:pqrs:tuvwx";
> +  const char *opts = "abcdDfghHijklmno:pqrs:tuvwx";
>    while ((opt = getopt_long (argc, argv, opts, options, NULL)) != -1)
>      {
>        switch (opt)
> @@ -1025,6 +1063,9 @@ process_args (int argc, char **argv)
>  	case 'f':
>  	  flag_function_summary = 1;
>  	  break;
> +	case 'g':
> +	  flag_conditions = 1;
> +	  break;
>  	case 'h':
>  	  print_usage (false);
>  	  /* print_usage will exit.  */
> @@ -1132,6 +1173,45 @@ output_intermediate_json_line (json::array *object,
>  	  }
>        }
>  
> +  json::array *conditions = new json::array ();
> +  lineo->set ("conditions", conditions);
> +  if (flag_conditions)
> +  {
> +    vector<block_info *>::const_iterator it;
> +    for (it = line->blocks.begin (); it != line->blocks.end (); it++)
> +      {
> +	const condition_info& info = (*it)->conditions;
> +	if (info.n_terms == 0)
> +	    continue;
> +
> +	const int count = 2 * info.n_terms;
> +	const int covered = info.popcount ();
> +
> +	json::object *cond = new json::object ();
> +	cond->set ("count", new json::integer_number (count));
> +	cond->set ("covered", new json::integer_number (covered));
> +
> +	json::array *mtrue = new json::array ();
> +	json::array *mfalse = new json::array ();
> +	cond->set ("not_covered_true", mtrue);
> +	cond->set ("not_covered_false", mfalse);
> +
> +	if (count != covered)
> +	  {
> +	    for (unsigned i = 0; i < info.n_terms; i++)
> +	      {
> +		gcov_type_unsigned index = 1;
> +		index <<= i;
> +		if (!(index & info.truev))
> +		    mtrue->append (new json::integer_number (i));
> +		if (!(index & info.falsev))
> +		    mfalse->append (new json::integer_number (i));
> +	      }
> +	  }
> +	conditions->append (cond);
> +      }
> +  }
> +
>    object->append (lineo);
>  }
>  
> @@ -1956,6 +2036,28 @@ read_graph_file (void)
>  		  }
>  	    }
>  	}
> +      else if (fn && tag == GCOV_TAG_CONDS)
> +	{
> +	  unsigned num_dests = GCOV_TAG_CONDS_NUM (length);
> +
> +	  if (!fn->conditions.empty ())
> +	    fnotice (stderr, "%s:already seen conditions for '%s'\n",
> +		     bbg_file_name, fn->get_name ());
> +	  else
> +	    fn->conditions.resize (num_dests);
> +
> +	  for (unsigned i = 0; i < num_dests; ++i)
> +	    {
> +	      unsigned idx = gcov_read_unsigned ();
> +
> +	      if (idx >= fn->blocks.size ())
> +		goto corrupt;
> +
> +	      condition_info *info = &fn->blocks[idx].conditions;
> +	      info->n_terms = gcov_read_unsigned ();
> +	      fn->conditions[i] = info;
> +	    }
> +	}
>        else if (fn && tag == GCOV_TAG_LINES)
>  	{
>  	  unsigned blockno = gcov_read_unsigned ();
> @@ -2086,11 +2188,26 @@ read_count_file (void)
>  	      goto cleanup;
>  	    }
>  	}
> -      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn)
> +      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_CONDS) && fn)
>  	{
> +	  length = abs (read_length);
> +	  if (length != GCOV_TAG_COUNTER_LENGTH (2 * fn->conditions.size ()))
> +	      goto mismatch;
> +
> +	  if (read_length > 0)
> +	    {
> +	      for (ix = 0; ix != fn->conditions.size (); ix++)
> +		{
> +		  fn->conditions[ix]->truev  |= gcov_read_counter ();
> +		  fn->conditions[ix]->falsev |= gcov_read_counter ();
> +		}
> +	    }
> +	}
> +      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn)
> +      {
>  	  length = abs (read_length);
>  	  if (length != GCOV_TAG_COUNTER_LENGTH (fn->counts.size ()))
> -	    goto mismatch;
> +	      goto mismatch;
>  
>  	  if (read_length > 0)
>  	    for (ix = 0; ix != fn->counts.size (); ix++)
> @@ -2430,6 +2547,13 @@ add_branch_counts (coverage_info *coverage, const arc_info *arc)
>      }
>  }
>  
> +static void
> +add_condition_counts (coverage_info *coverage, const block_info *block)
> +{
> +  coverage->conditions += 2 * block->conditions.n_terms;
> +  coverage->conditions_covered += block->conditions.popcount ();
> +}
> +
>  /* Format COUNT, if flag_human_readable_numbers is set, return it human
>     readable format.  */
>  
> @@ -2533,6 +2657,18 @@ file_summary (const coverage_info *coverage)
>  		 coverage->calls);
>        else
>  	fnotice (stdout, "No calls\n");
> +
> +    }
> +
> +  if (flag_conditions)
> +    {
> +      if (coverage->conditions)
> +	fnotice (stdout, "Decisions covered:%s of %d\n",
> +		 format_gcov (coverage->conditions_covered,
> +			      coverage->conditions, 2),
> +		 coverage->conditions);
> +      else
> +	fnotice (stdout, "No decisions\n");
>      }
>  }
>  
> @@ -2767,6 +2903,12 @@ static void accumulate_line_info (line_info *line, source_info *src,
>  	 it != line->branches.end (); it++)
>        add_branch_counts (&src->coverage, *it);
>  
> +  if (add_coverage)
> +    for (vector<block_info *>::iterator it = line->blocks.begin ();
> +	 it != line->blocks.end (); it++)
> +      add_condition_counts (&src->coverage, *it);
> +
> +
>    if (!line->blocks.empty ())
>      {
>        /* The user expects the line count to be the number of times
> @@ -2868,6 +3010,33 @@ accumulate_line_counts (source_info *src)
>        }
>  }
>  
> +static void
> +output_conditions (FILE *gcov_file, const block_info *binfo)
> +{
> +    const condition_info& info = binfo->conditions;
> +    if (info.n_terms == 0)
> +	return;
> +
> +    const int expected = 2 * info.n_terms;
> +    const int got = info.popcount ();
> +
> +    fnotice (gcov_file, "condition outcomes covered %d/%d\n", got, expected);
> +    if (expected == got)
> +	return;
> +
> +    for (unsigned i = 0; i < info.n_terms; i++)
> +    {
> +	gcov_type_unsigned index = 1;
> +	index <<= i;
> +	if ((index & info.truev & info.falsev))
> +	    continue;
> +
> +	const char *t = (index & info.truev) ? "" : "true";
> +	const char *f = (index & info.falsev) ? "" : " false";
> +	fnotice (gcov_file, "condition %2u not covered (%s%s)\n", i, t, f + !t[0]);
> +    }
> +}
> +
>  /* Output information about ARC number IX.  Returns nonzero if
>     anything is output.  */
>  
> @@ -3078,16 +3247,29 @@ output_line_details (FILE *f, const line_info *line, unsigned line_num)
>  	  if (flag_branches)
>  	    for (arc = (*it)->succ; arc; arc = arc->succ_next)
>  	      jx += output_branch_count (f, jx, arc);
> +
> +	  if (flag_conditions)
> +	      output_conditions (f, *it);
>  	}
>      }
> -  else if (flag_branches)
> +  else
>      {
> -      int ix;
> +      if (flag_branches)
> +	{
> +	  int ix;
> +
> +	  ix = 0;
> +	  for (vector<arc_info *>::const_iterator it = line->branches.begin ();
> +		  it != line->branches.end (); it++)
> +	      ix += output_branch_count (f, ix, (*it));
> +	}
>  
> -      ix = 0;
> -      for (vector<arc_info *>::const_iterator it = line->branches.begin ();
> -	   it != line->branches.end (); it++)
> -	ix += output_branch_count (f, ix, (*it));
> +      if (flag_conditions)
> +	{
> +	  for (vector<block_info *>::const_iterator it = line->blocks.begin ();
> +	       it != line->blocks.end (); it++)
> +	      output_conditions (f, *it);
> +	}
>      }
>  }
>  
> diff --git a/gcc/ipa-inline.cc b/gcc/ipa-inline.cc
> index 14969198cde..3e37305843e 100644
> --- a/gcc/ipa-inline.cc
> +++ b/gcc/ipa-inline.cc
> @@ -646,7 +646,7 @@ can_early_inline_edge_p (struct cgraph_edge *e)
>  			 "  edge not inlinable: not in SSA form\n");
>        return false;
>      }
> -  else if (profile_arc_flag
> +  else if ((profile_arc_flag || profile_condition_flag)
>  	   && ((lookup_attribute ("no_profile_instrument_function",
>  				 DECL_ATTRIBUTES (caller->decl)) == NULL_TREE)
>  	       != (lookup_attribute ("no_profile_instrument_function",
> diff --git a/gcc/ipa-split.cc b/gcc/ipa-split.cc
> index 16734617d03..07d2b17ab12 100644
> --- a/gcc/ipa-split.cc
> +++ b/gcc/ipa-split.cc
> @@ -1929,7 +1929,8 @@ pass_split_functions::gate (function *)
>    /* When doing profile feedback, we want to execute the pass after profiling
>       is read.  So disable one in early optimization.  */
>    return (flag_partial_inlining
> -	  && !profile_arc_flag && !flag_branch_probabilities);
> +      && !profile_arc_flag && !flag_branch_probabilities
> +      && !profile_condition_flag);
>  }
>  
>  } // anon namespace
> diff --git a/gcc/passes.cc b/gcc/passes.cc
> index 78a07f8691a..3457ad1c5b3 100644
> --- a/gcc/passes.cc
> +++ b/gcc/passes.cc
> @@ -352,7 +352,8 @@ finish_optimization_passes (void)
>    gcc::dump_manager *dumps = m_ctxt->get_dumps ();
>  
>    timevar_push (TV_DUMP);
> -  if (profile_arc_flag || flag_test_coverage || flag_branch_probabilities)
> +  if (profile_arc_flag || profile_condition_flag || flag_test_coverage
> +      || flag_branch_probabilities)
>      {
>        dumps->dump_start (pass_profile_1->static_pass_number, NULL);
>        end_branch_prob ();
> diff --git a/gcc/profile.cc b/gcc/profile.cc
> index 96121d60711..f7f6d1f7197 100644
> --- a/gcc/profile.cc
> +++ b/gcc/profile.cc
> @@ -66,9 +66,19 @@ along with GCC; see the file COPYING3.  If not see
>  #include "cfgloop.h"
>  #include "sreal.h"
>  #include "file-prefix-map.h"
> +#include "stringpool.h"
>  
>  #include "profile.h"
>  
> +struct condcov;
> +struct condcov *find_conditions (struct function*);
> +unsigned cov_length (const struct condcov*);
> +array_slice<basic_block> cov_blocks (struct condcov*, unsigned);
> +array_slice<gcov_type_unsigned > cov_masks (struct condcov*, unsigned);
> +void cov_free (struct condcov*);
> +int instrument_decisions (array_slice<basic_block>, unsigned, tree*,
> +			  gcov_type_unsigned*);
> +
>  /* Map from BBs/edges to gcov counters.  */
>  vec<gcov_type> bb_gcov_counts;
>  hash_map<edge,gcov_type> *edge_gcov_counts;
> @@ -100,6 +110,7 @@ static int total_num_passes;
>  static int total_num_times_called;
>  static int total_hist_br_prob[20];
>  static int total_num_branches;
> +static int total_num_conds;
>  
>  /* Forward declarations.  */
>  static void find_spanning_tree (struct edge_list *);
> @@ -1155,6 +1166,12 @@ read_thunk_profile (struct cgraph_node *node)
>     the flow graph that are needed to reconstruct the dynamic behavior of the
>     flow graph.  This data is written to the gcno file for gcov.
>  
> +   When FLAG_PROFILE_CONDITIONS is nonzero, this functions instruments the
> +   edges in the control flow graph to track what conditions are evaluated to in
> +   order to determine what conditions are covered and have an independent
> +   effect on the outcome (modified condition/decision coverage).  This data is
> +   written to the gcno file for gcov.
> +
>     When FLAG_BRANCH_PROBABILITIES is nonzero, this function reads auxiliary
>     information from the gcda file containing edge count information from
>     previous executions of the function being compiled.  In this case, the
> @@ -1173,6 +1190,7 @@ branch_prob (bool thunk)
>    struct edge_list *el;
>    histogram_values values = histogram_values ();
>    unsigned cfg_checksum, lineno_checksum;
> +  bool output_to_file;
>  
>    total_num_times_called++;
>  
> @@ -1397,10 +1415,18 @@ branch_prob (bool thunk)
>  
>    /* Write the data from which gcov can reconstruct the basic block
>       graph and function line numbers (the gcno file).  */
> +  output_to_file = false;
>    if (coverage_begin_function (lineno_checksum, cfg_checksum))
>      {
>        gcov_position_t offset;
>  
> +      /* The condition coverage needs a deeper analysis to identify expressions
> +       * of conditions, which means it is not yet ready to write to the gcno
> +       * file.  It will write its entries later, but needs to know if it do it
> +       * in the first place, which is controlled by the return value of
> +       * coverage_begin_function. */
> +      output_to_file = true;
> +
>        /* Basic block flags */
>        offset = gcov_write_tag (GCOV_TAG_BLOCKS);
>        gcov_write_unsigned (n_basic_blocks_for_fn (cfun));
> @@ -1512,29 +1538,74 @@ branch_prob (bool thunk)
>  
>    remove_fake_edges ();
>  
> +  if (profile_condition_flag || profile_arc_flag)
> +      gimple_init_gcov_profiler ();
> +
> +  if (profile_condition_flag)
> +    {
> +      struct condcov *cov = find_conditions (cfun);
> +      gcc_assert (cov);
> +      const unsigned nconds = cov_length (cov);
> +      total_num_conds += nconds;
> +
> +      if (coverage_counter_alloc (GCOV_COUNTER_CONDS, 2 * nconds))
> +	{
> +	  /* Add two extra variables to the function for the local
> +	     accumulators, which are zero'd on the entry of a new conditional.
> +	     The local accumulators are shared between decisions in order to
> +	     use less stack space. */
> +	  tree accu[2] = {
> +	    build_decl (UNKNOWN_LOCATION, VAR_DECL,
> +			get_identifier ("__accu_t"), get_gcov_type ()),
> +	    build_decl (UNKNOWN_LOCATION, VAR_DECL,
> +			get_identifier ("__accu_f"), get_gcov_type ()),
> +	  };
> +
> +	  gcov_position_t offset {};
> +	  if (output_to_file)
> +	      offset = gcov_write_tag (GCOV_TAG_CONDS);
> +
> +	  for (unsigned i = 0; i < nconds; ++i)
> +	    {
> +	      array_slice<basic_block> expr = cov_blocks (cov, i);
> +	      array_slice<gcov_type_unsigned> masks = cov_masks (cov, i);
> +	      gcc_assert (expr.is_valid ());
> +	      gcc_assert (masks.is_valid ());
> +
> +	      int terms = instrument_decisions (expr, i, accu, masks.begin ());
> +	      if (output_to_file)
> +		{
> +		  gcov_write_unsigned (expr.front ()->index);
> +		  gcov_write_unsigned (terms);
> +		}
> +	    }
> +	  if (output_to_file)
> +	      gcov_write_length (offset);
> +	}
> +      cov_free (cov);
> +    }
> +
>    /* For each edge not on the spanning tree, add counting code.  */
>    if (profile_arc_flag
>        && coverage_counter_alloc (GCOV_COUNTER_ARCS, num_instrumented))
>      {
>        unsigned n_instrumented;
>  
> -      gimple_init_gcov_profiler ();
> -
>        n_instrumented = instrument_edges (el);
>  
>        gcc_assert (n_instrumented == num_instrumented);
>  
>        if (flag_profile_values)
>  	instrument_values (values);
> -
> -      /* Commit changes done by instrumentation.  */
> -      gsi_commit_edge_inserts ();
>      }
>  
>    free_aux_for_edges ();
>  
>    values.release ();
>    free_edge_list (el);
> +  /* Commit changes done by instrumentation.  */
> +  gsi_commit_edge_inserts ();
> +
>    coverage_end_function (lineno_checksum, cfg_checksum);
>    if (flag_branch_probabilities
>        && (profile_status_for_fn (cfun) == PROFILE_READ))
> @@ -1664,6 +1735,7 @@ init_branch_prob (void)
>    total_num_passes = 0;
>    total_num_times_called = 0;
>    total_num_branches = 0;
> +  total_num_conds = 0;
>    for (i = 0; i < 20; i++)
>      total_hist_br_prob[i] = 0;
>  }
> @@ -1703,5 +1775,7 @@ end_branch_prob (void)
>  		     (total_hist_br_prob[i] + total_hist_br_prob[19-i]) * 100
>  		     / total_num_branches, 5*i, 5*i+5);
>  	}
> +      fprintf (dump_file, "Total number of conditions: %d\n",
> +	       total_num_conds);
>      }
>  }
> diff --git a/gcc/testsuite/g++.dg/gcov/gcov-18.C b/gcc/testsuite/g++.dg/gcov/gcov-18.C
> new file mode 100644
> index 00000000000..310ed5297c0
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/gcov/gcov-18.C
> @@ -0,0 +1,234 @@
> +/* { dg-options "--coverage -fprofile-conditions -std=c++11" } */
> +/* { dg-do run { target native } } */
> +
> +#include <vector>
> +#include <stdexcept>
> +
> +class nontrivial_destructor
> +{
> +public:
> +    explicit nontrivial_destructor (int v) : val (v) {}
> +    ~nontrivial_destructor () {}
> +
> +    explicit operator bool() const { return bool(val); }
> +
> +    int val;
> +};
> +
> +int identity (int x) { return x; }
> +int throws (int) { throw std::runtime_error("exception"); }
> +
> +int throw_if (int x)
> +{
> +    if (x) /* conditions(1/2) true(0) */
> +	   /* conditions(end) */
> +	throw std::runtime_error("exception");
> +    return x;
> +}
> +
> +/* used for side effects to insert nodes in conditional bodies etc. */
> +int x = 0;
> +
> +/* conditionals work in the presence of non-trivial destructors */
> +void mcdc001a (int a)
> +{
> +    nontrivial_destructor v (a);
> +
> +    if (v.val > 0) /* conditions(2/2) */
> +	x = v.val;
> +    else
> +	x = -v.val;
> +}
> +
> +/* non-trivial destructor in-loop temporary */
> +nontrivial_destructor
> +mcdc002a (int a, int b)
> +{
> +    for (int i = 0; i < a; i++) /* conditions(2/2) */
> +    {
> +	nontrivial_destructor tmp (a);
> +	if (tmp.val % b) /* conditions(2/2) */
> +	    return nontrivial_destructor (0);
> +	x += i;
> +    } /* conditions(suppress) */
> +      /* conditions(end) */
> +
> +    return nontrivial_destructor (a * b);
> +}
> +
> +/* conditional in constructor */
> +void mcdc003a (int a)
> +{
> +    class C
> +    {
> +    public:
> +	explicit C (int e) : v (e)
> +	{
> +	    if (e) /* conditions(1/2) false(0) */
> +		v = x - e;
> +	}
> +	int v;
> +    };
> +
> +    C c (a);
> +    if (c.v > 2) /* conditions(1/2) true(0) */
> +		 /* conditions(end) */
> +	x = c.v + a;
> +}
> +
> +/* conditional in destructor */
> +void mcdc004a (int a)
> +{
> +    class C
> +    {
> +    public:
> +	explicit C (int e) : v (e) {}
> +	~C ()
> +	{
> +	    if (v) /* conditions(2/2) */
> +		x = 2 * v;
> +	}
> +	int v;
> +    };
> +
> +    C c (a);
> +    x = 1; // arbitrary action between ctor+dtor
> +}
> +
> +/* conditional in try */
> +void mcdc005a (int a)
> +{
> +    try
> +    {
> +	if (a) /* conditions(1/2) true(0) */
> +	       /* conditions(end) */
> +	    x = 2 * identity (a);
> +	else
> +	    x = 1;
> +    }
> +    catch (...)
> +    {
> +	x = 0;
> +    }
> +}
> +
> +/* conditional in catch */
> +void mcdc006a (int a) {
> +    try
> +    {
> +	throws (a);
> +    }
> +    catch (std::exception&)
> +    {
> +	if (a) /* conditions(1/2) false(0) */
> +	       /* conditions(end) */
> +	    x = identity (a);
> +	else
> +	    x = 0;
> +    }
> +}
> +
> +void mcdc006b (int a)
> +{
> +    if (a) /* conditions(1/2) true(0) */
> +	   /* conditions(end) */
> +	throws (a);
> +    else
> +	x = 1;
> +}
> +
> +void mcdc006c (int a) try
> +{
> +    throws (a);
> +}
> +catch (...) {
> +    if (a) /* conditions(2/2) */
> +	x = 5;
> +}
> +
> +/* temporary with destructor as term */
> +void mcdc007a (int a, int b)
> +{
> +    x = a && nontrivial_destructor (b); /* conditions(3/4) false(1) destructor() */
> +}
> +
> +void mcdc007b (int a, int b)
> +{
> +    if (a || throw_if (b)) /* conditions(3/4) true(1) destructor() */
> +	x = -1;
> +    else
> +	x = 1;
> +}
> +
> +void mcdc007c (int a, int b)
> +{
> +    if (throw_if (a) || throw_if (b)) /* conditions(2/4) true(0 1) destructor() */
> +	x = -1;
> +    else
> +	x = 1;
> +}
> +
> +/* destructor with delete */
> +void mcdc008a (int a)
> +{
> +    class C
> +    {
> +    public:
> +        int size = 5;
> +        int* ptr = nullptr;
> +
> +        explicit C (int v) : size (v + 5), ptr (new int[size]) /* conditions(suppress) */
> +							       /* conditions(end) */
> +        {
> +	    for (int i = 0; i < size; i++) /* conditions(2/2) */
> +		ptr[i] = i + 1;
> +        }
> +        ~C()
> +        {
> +	    // delete with implicit nullptr check
> +	    delete ptr; /* conditions(1/2) false(0) */
> +			/* conditions(end) */
> +	}
> +    };
> +
> +    C c (a);
> +    if (c.ptr[a + 1]) /* conditions(1/2) false(0) */
> +	x = a;
> +}
> +
> +int
> +main (void)
> +{
> +    mcdc001a (0);
> +    mcdc001a (1);
> +
> +    mcdc002a (1, 1);
> +    mcdc002a (1, 2);
> +
> +    mcdc003a (1);
> +
> +    mcdc004a (0);
> +    mcdc004a (1);
> +
> +    mcdc005a (0);
> +
> +    mcdc006a (1);
> +
> +    mcdc006b (0);
> +
> +    mcdc006c (0);
> +    mcdc006c (1);
> +
> +    mcdc007a (0, 0);
> +    mcdc007a (1, 1);
> +
> +    mcdc007b (0, 0);
> +    mcdc007b (1, 0);
> +
> +    mcdc007c (0, 0);
> +
> +    mcdc008a (1);
> +
> +}
> +
> +/* { dg-final { run-gcov conditions { --conditions gcov-18.C } } } */
> diff --git a/gcc/testsuite/gcc.misc-tests/gcov-19.c b/gcc/testsuite/gcc.misc-tests/gcov-19.c
> new file mode 100644
> index 00000000000..1adff7c76f4
> --- /dev/null
> +++ b/gcc/testsuite/gcc.misc-tests/gcov-19.c
> @@ -0,0 +1,1250 @@
> +/* { dg-options "-fprofile-conditions -ftest-coverage" } */
> +/* { dg-do run { target native } } */
> +
> +/* some side effect to stop branches from being pruned */
> +int x = 0;
> +
> +/* || works */
> +void
> +mcdc001a (int a, int b)
> +{
> +    if (a || b) /* conditions(1/4) true(0) false(0 1) */
> +		/* conditions(end) */
> +	x = 1;
> +    else
> +	x = 2;
> +}
> +
> +void
> +mcdc001b (int a, int b)
> +{
> +    if (a || b) /* conditions(3/4) true(0) */
> +		/* conditions(end) */
> +	x = 1;
> +    else
> +	x = 2;
> +}
> +
> +void
> +mcdc001c (int a, int b)
> +{
> +    if (a || b) /* conditions(4/4) */
> +	x = 1;
> +    else
> +	x = 2;
> +}
> +
> +void
> +mcdc001d (int a, int b, int c)
> +{
> +    if (a || b || c) /* conditions(2/6) false(0 1 2) true(2) */
> +		     /* conditions(end) */
> +	x = 1;
> +}
> +
> +/* && works */
> +void
> +mcdc002a (int a, int b)
> +{
> +    if (a && b) /* conditions(1/4) true(0 1) false(0) */
> +		/* conditions(end) */
> +	x = 1;
> +    else
> +	x = 2;
> +}
> +
> +void
> +mcdc002b (int a, int b)
> +{
> +    if (a && b) /* conditions(3/4) false(0) */
> +		/* conditions(end) */
> +	x = 1;
> +    else
> +	x = 2;
> +}
> +
> +void
> +mcdc002c (int a, int b)
> +{
> +    if (a && b) /* conditions(4/4) */
> +	x = 1;
> +    else
> +	x = 2;
> +}
> +
> +void
> +mcdc002d (int a, int b, int c)
> +{
> +    if (a && b && c) /* conditions(4/6) false(0 2) */
> +		     /* conditions(end) */
> +	x = 1;
> +}
> +
> +/* negation works */
> +void
> +mcdc003a (int a, int b)
> +{
> +    if (!a || !b) /* conditions(2/4) false(0 1) */
> +		  /* conditions(end) */
> +	x = 1;
> +    else
> +	x = 2;
> +}
> +
> +/* single conditionals with and without else */
> +void
> +mcdc004a (int a)
> +{
> +    if (a) /* conditions(1/2) true(0) */
> +	   /* conditions(end) */
> +	x = 1;
> +    else
> +	x = 2;
> +}
> +
> +void
> +mcdc004b (int a)
> +{
> +    if (a) /* conditions(2/2) */
> +	x = 1;
> +    else
> +	x = 2;
> +}
> +
> +void
> +mcdc004c (int a)
> +{
> +    if (a) /* conditions(1/2) false(0) */
> +	   /* conditions(end) */
> +	x = 1;
> +}
> +
> +void
> +mcdc004d (int a, int b, int c)
> +{
> +    /* With no else this is interpreted as (a && (b || c)) */
> +    if (a)  /* conditions(3/6) true(2) false(1 2)*/
> +    {
> +	if (b || c)
> +	    x = a + b + c;
> +    }
> +}
> +
> +void
> +mcdc004e (int a, int b, int c)
> +{
> +    /* With the else, this is interpreted as 2 expressions */
> +    if (a)  /* conditions(2/2) */
> +    {
> +	if (b || c) /* conditions(1/4) true(1) false(0 1) */
> +	    x = a + b + c;
> +    }
> +    else
> +    {
> +	x = c;
> +    }
> +}
> +
> +/* mixing && and || works */
> +void
> +mcdc005a (int a, int b, int c)
> +{
> +    if ((a && b) || c) /* conditions(1/6) true(0 1) false(0 1 2) */
> +		       /* conditions(end) */
> +	x = 1;
> +    else
> +	x = 2;
> +}
> +
> +void
> +mcdc005b (int a, int b, int c, int d)
> +{
> +    /* This is where masking MC/DC gets unintuitive:
> +
> +       1 1 0 0 => covers 1 (d = 0) as && 0 masks everything to the left
> +       1 0 0 0 => covers 2 (b = 0, c = 0) as (a && 0) masks a and d is never
> +       evaluated. */
> +    if ((a && (b || c)) && d) /* conditions(3/8) true(0 1 2 3) false(0) */
> +			      /* conditions(end) */
> +	x = 1;
> +    else
> +	x = 2;
> +}
> +
> +void
> +mcdc005c (int a, int b, int c, int d)
> +{
> +    if (a || (b && c) || d) /* conditions(2/8) true(0 3) false(0 1 2 3) */
> +			    /* conditions(end) */
> +        x = a + b + c + d;
> +}
> +
> +void
> +mcdc005d (int a, int b, int c, int d)
> +{
> +    /* This test is quite significant - it has a single input
> +       (1, 0, 0, 0) and tests specifically for when a multi-term left operand
> +       is masked. d = 0 should mask a || b and for the input there are no other
> +       sources for masking a (since b = 0). */
> +    if ((a || b) && (c || d)) /* conditions(2/8) true(0 1 2 3) false(0 1) */
> +			      /* conditions(end) */
> +	x = a + b;
> +    else
> +	x = c + d;
> +}
> +
> +/* nested conditionals */
> +void
> +mcdc006a (int a, int b, int c, int d, int e)
> +{
> +    if (a) /* conditions(2/2) */
> +    {
> +	if (b && c) /* conditions(3/4) false(1) */
> +		    /* conditions(end) */
> +	    x = 1;
> +	else
> +	    x = 2;
> +    }
> +    else
> +    {
> +	if (c || d) /* conditions(2/4) true(0 1) */
> +		    /* conditions(end) */
> +	    x = 3;
> +	else
> +	    x = 4;
> +    }
> +}
> +
> +void
> +mcdc006b (int a, int b, int c)
> +{
> +    if (a) /* conditions(6/6) */
> +	if (b)
> +	    if (c)
> +		x = a + b + c;
> +}
> +
> +void
> +mcdc006c (int a, int b, int c)
> +{
> +    if (a) /* conditions(2/2) */
> +    {
> +	if (b) /*conditions(2/2) */
> +	{
> +	    if (c) /* conditions(2/2) */
> +	    {
> +		x = a + b + c;
> +	    }
> +	}
> +	else
> +	{
> +	    x = b;
> +	}
> +    }
> +    else
> +    {
> +	x = a;
> +    }
> +}
> +
> +/* else/if */
> +void
> +mcdc007a (int a, int b, int c, int d)
> +{
> +    if (a) /* conditions(2/2) */
> +    {
> +	if (b) /* conditions(1/2) true(0) */
> +	       /* conditions(end) */
> +	    x = 1;
> +	else
> +	    x = 2;
> +    }
> +    else if (c) /* conditions(2/2) */
> +    {
> +	if (d) /* conditions(1/2) true(0) */
> +	       /* conditions(end) */
> +	    x = 3;
> +	else
> +	    x = 4;
> +    }
> +}
> +
> +void
> +mcdc007b (int a, int b, int c)
> +{
> +    goto begin;
> +then:
> +    x = 1;
> +    return;
> +begin:
> +    /* Evaluates to if (a || b || c) x = 1 */
> +    if (a)	    /* conditions(5/6) true(2) */
> +		    /* conditions(end) */
> +	goto then;
> +    else if (b)
> +	goto then;
> +    else if (c)
> +	goto then;
> +}
> +
> +void
> +mcdc007c (int a, int b, int c)
> +{
> +    goto begin;
> +then1:
> +    x = 1;
> +    return;
> +then2:
> +    x = 1;
> +    return;
> +then3:
> +    x = 1;
> +    return;
> +begin:
> +    /* similar to if (a || b || c) x = 1 */
> +    if (a) /* conditions(2/2) */
> +	goto then1;
> +    else if (b) /* conditions(2/2) */
> +	goto then2;
> +    else if (c) /* conditions(1/2) true(0) */
> +		/* conditions(end) */
> +	goto then3;
> +}
> +
> +/* while loop */
> +void
> +mcdc008a (int a)
> +{
> +    while (a < 10) /* conditions(2/2) */
> +	x = a++;
> +}
> +
> +void
> +mcdc008b (int a)
> +{
> +    while (a > 10) /* conditions(1/2) true(0) */
> +		   /* conditions(end) */
> +	x = a--;
> +}
> +
> +void
> +mcdc008c (int a)
> +{
> +    // should work, even with no body
> +    while (a) /* conditions(2/2) */
> +	break;
> +}
> +
> +void
> +mcdc008d (int a, int b, int c, int d)
> +{
> +    /* multi-term loop conditional */
> +    while ((a && (b || c)) && d) /* conditions(8/8) */
> +	a = b = c = d = 0;
> +}
> +
> +void
> +mcdc009a (int a, int b)
> +{
> +    while (a > 0 && b > 0) /* conditions(3/4) false(1) */
> +			   /* conditions(end) */
> +	x = a--;
> +}
> +
> +/* for loop */
> +void
> +mcdc010a(int a, int b)
> +{
> +    for (int i = 0; i < b; i++) /* conditions(2/2) */
> +    {
> +	if (a < b) /* conditions(2/2) */
> +	    x = 1;
> +	else
> +	    x = a += 2;
> +    }
> +}
> +
> +void
> +mcdc010b ()
> +{
> +    for (int a = 0; a <= 1; ++a) /* conditions(2/2) */
> +    {
> +	x = a;
> +    }
> +}
> +
> +int always (int x) { (void) x; return 1; }
> +
> +/* no-condition infinite loops */
> +void
> +mcdc010c (int a)
> +{
> +    for (;;)
> +    {
> +	if (always(a)) /* conditions(1/2) false(0) */
> +		       /* conditions(end) */
> +	{
> +	    x = a;
> +	    break;
> +	}
> +	x += a + 1;
> +    }
> +}
> +
> +/* conditionals without control flow constructs work */
> +void
> +mcdc011a (int a, int b, int c)
> +{
> +    x = (a && b) || c; /* conditions(5/6) false(1) */
> +		       /* conditions(end) */
> +}
> +
> +/* sequential expressions are handled independently */
> +void
> +mcdc012a (int a, int b, int c)
> +{
> +    if (a || b) /* conditions(3/4) true(0) */
> +		/* conditions(end) */
> +	x = 1;
> +    else
> +	x = 2;
> +
> +    if (c) /* conditions(2/2) */
> +	x = 1;
> +}
> +
> +/*
> + * cannot ever satisfy MC/DC, even with all input combinations, because not all
> + * variables independently affect the decision
> + */
> +void
> +mcdc013a (int a, int b, int c)
> +{
> +    (void)b;
> +    /*
> +     * Specification: (a && b) || c
> +     *
> +     * But the expression was implemented wrong. This has branch coverage, but
> +     * not MC/DC
> +     */
> +    if ((a && !c) || c) /* conditions(5/6) false(1) */
> +	/* conditions(end) */
> +	x = 1;
> +    else
> +	x = 2;
> +}
> +
> +void
> +mcdc014a ()
> +{
> +    int conds[64] = { 0 };
> +    /* conditions(64/128) true(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63) */
> +    x = conds[ 0] || conds[ 1] || conds[ 2] || conds[ 3] || conds[ 4] ||
> +	conds[ 5] || conds[ 6] || conds[ 7] || conds[ 8] || conds[ 9] ||
> +	conds[10] || conds[11] || conds[12] || conds[13] || conds[14] ||
> +	conds[15] || conds[16] || conds[17] || conds[18] || conds[19] ||
> +	conds[20] || conds[21] || conds[22] || conds[23] || conds[24] ||
> +	conds[25] || conds[26] || conds[27] || conds[28] || conds[29] ||
> +	conds[30] || conds[31] || conds[32] || conds[33] || conds[34] ||
> +	conds[35] || conds[36] || conds[37] || conds[38] || conds[39] ||
> +	conds[40] || conds[41] || conds[42] || conds[43] || conds[44] ||
> +	conds[45] || conds[46] || conds[47] || conds[48] || conds[49] ||
> +	conds[50] || conds[51] || conds[52] || conds[53] || conds[54] ||
> +	conds[55] || conds[56] || conds[57] || conds[58] || conds[59] ||
> +	conds[60] || conds[61] || conds[62] || conds[63]
> +	;  /* conditions(end) */
> +}
> +
> +/* early returns */
> +void
> +mcdc015a (int a, int b)
> +{
> +    if (a) /* conditions(2/2) */
> +	return;
> +
> +    if (b) /* conditions(1/2) true(0) */
> +	   /* conditions(end) */
> +	x = 1;
> +}
> +
> +void
> +mcdc015b (int a, int b)
> +{
> +    for (int i = 5; i > a; i--) /* conditions(2/2) */
> +    {
> +	if (i == b) /* conditions(2/2) */
> +	    return;
> +	x = i;
> +    }
> +}
> +
> +void
> +mcdc015c (int a, int b)
> +{
> +    for (int i = 5; i > a; i--) /* conditions(2/2) */
> +    {
> +	if (i == b) /* conditions(2/2) */
> +	{
> +	    x = 0;
> +	    return;
> +	}
> +	else
> +	{
> +	    x = 1;
> +	    return;
> +	}
> +
> +	x = i;
> +    }
> +}
> +
> +
> +/* check nested loops */
> +void
> +mcdc016a (int a, int b)
> +{
> +    for (int i = 0; i < a; i++) /* conditions(2/2) */
> +	for (int k = 0; k < b; k++) /* conditions(2/2) */
> +	    x = i + k;
> +}
> +
> +void
> +mcdc016b (int a, int b)
> +{
> +    for (int i = 0; i < a; i++) /* conditions(2/2) */
> +    {
> +	if (a > 5) /* conditions(2/2) */
> +	    break;
> +
> +	for (int k = 0; k < b; k++) /* conditions(2/2) */
> +	    x = i + k;
> +    }
> +}
> +
> +void
> +mcdc016c (int a, int b)
> +{
> +    for (int i = 0; i < a; i++) /* conditions(2/2) */
> +    {
> +	if (a > 5) /* conditions(1/2) true(0) */
> +		   /* conditions(end) */
> +	    return;
> +
> +	for (int k = 0; k < b; k++) /* conditions(2/2) */
> +	    x = i + k;
> +    }
> +}
> +
> +void
> +mcdc016d (int a, int b)
> +{
> +    for (int i = 0; i < a; i++) /* conditions(2/2) */
> +    {
> +	for (int k = 0; k < 5; k++) /* conditions(2/2) */
> +	{
> +	    if (b > 5) /* conditions(1/2) true(0) */
> +		       /* conditions(end) */
> +		return;
> +	    x = i + k;
> +	}
> +
> +    }
> +}
> +
> +/* do-while loops */
> +void
> +mcdc017a (int a)
> +{
> +    do
> +    {
> +	a--;
> +    } while (a > 0); /* conditions(2/2) */
> +}
> +
> +void
> +noop () {}
> +
> +void
> +mcdc017b (int a, int b)
> +{
> +    do
> +    {
> +	/*
> +	 * This call is important; it can add more nodes to the body in the
> +	 * CFG, which has changes how close exits and breaks are to the loop
> +	 * conditional.
> +	 */
> +	noop ();
> +	a--;
> +	if (b) /* conditions(2/2) */
> +	    break;
> +
> +    } while (a > 0); /* conditions(2/2) */
> +}
> +
> +void
> +mcdc017c (int a, int b)
> +{
> +    int left = 0;
> +    int right = 0;
> +    int n = a + b;
> +    do
> +    {
> +	if (a) /* conditions(1/2) false(0) */
> +	       /* conditions(end) */
> +	{
> +	    left = a > left ? b : left; /* conditions(2/2) */
> +	}
> +	if (b) /* conditions(1/2) false(0) */
> +	{
> +	    right = b > right ? a : right; /* conditions(2/2) */
> +	}
> +    } while (n-- > 0); /* conditions(2/2) */
> +}
> +
> +int id  (int x) { return  x; }
> +int inv (int x) { return !x; }
> +
> +/* collection of odd cases lifted-and-adapted from real-world code */
> +int mcdc018a (int a, int b, int c, int d, int e, int f, int g, int len)
> +{
> +    int n;
> +    /* adapted from zlib/gz_read */
> +    do
> +    {
> +	n = -1;
> +	if (n > len) /* conditions(2/2) */
> +	    n = len;
> +
> +	if (b) /* conditions(2/2) */
> +	{
> +	    if (b < 5) /* conditions(2/2) */
> +		x = 1;
> +	    noop();
> +	}
> +	else if (c && d) /* conditions(3/4) false(1) */
> +	{
> +	    x = 2;
> +	    break;
> +	}
> +	else if (e || f) /* conditions(2/4) false(0 1) */
> +			 /* conditions(end) */
> +	{
> +	    if (id(g)) /* conditions(2/2) */
> +		return 0;
> +	    continue;
> +	}
> +    } while (a-- > 0); /* conditions(2/2) */
> +
> +    return 1;
> +}
> +
> +void
> +mcdc018b (int a, int b, int c)
> +{
> +    int n;
> +    while (a) /* conditions(2/2) */
> +    {
> +	/* else block does not make a difference for the problem, but ensures
> +	   loop termination. */
> +	if (b) /* conditions(2/2) */
> +	    n = c ? 0 : 0; // does not show up in CFG (embedded in the block)
> +	else
> +	    n = 0;
> +	a = n;
> +    }
> +}
> +
> +/* Adapted from zlib/compress2 */
> +void
> +mcdc018c (int a, int b)
> +{
> +    int err;
> +    do
> +    {
> +	a = inv (a);
> +	err = a;
> +    } while (err); /* conditions(1/2) true(0) */
> +		   /* conditions(end) */
> +
> +    a = id (a);
> +    if (a) /* conditions(1/2) true(0) */
> +	x *= a + 1;
> +}
> +
> +/* too many conditions, coverage gives up */
> +void
> +mcdc019a ()
> +{
> +    int conds[65] = { 0 };
> +    #pragma GCC diagnostic push
> +    #pragma GCC diagnostic ignored "-Wcoverage-too-many-conditions"
> +    x = conds[ 0] || conds[ 1] || conds[ 2] || conds[ 3] || conds[ 4] ||
> +	conds[ 5] || conds[ 6] || conds[ 7] || conds[ 8] || conds[ 9] ||
> +	conds[10] || conds[11] || conds[12] || conds[13] || conds[14] ||
> +	conds[15] || conds[16] || conds[17] || conds[18] || conds[19] ||
> +	conds[20] || conds[21] || conds[22] || conds[23] || conds[24] ||
> +	conds[25] || conds[26] || conds[27] || conds[28] || conds[29] ||
> +	conds[30] || conds[31] || conds[32] || conds[33] || conds[34] ||
> +	conds[35] || conds[36] || conds[37] || conds[38] || conds[39] ||
> +	conds[40] || conds[41] || conds[42] || conds[43] || conds[44] ||
> +	conds[45] || conds[46] || conds[47] || conds[48] || conds[49] ||
> +	conds[50] || conds[51] || conds[52] || conds[53] || conds[54] ||
> +	conds[55] || conds[56] || conds[57] || conds[58] || conds[59] ||
> +	conds[60] || conds[61] || conds[62] || conds[63] || conds[64]
> +	;
> +    #pragma GCC diagnostic pop
> +}
> +
> +/* ternary */
> +void
> +mcdc020a (int a)
> +{
> +    // special case, this can be reduced to:
> +    // _1 = argc != 0;
> +    // e = (int) _1;
> +    x = a ? 1 : 0;
> +
> +    // changing to different int makes branch
> +    x = a ? 2 : 1; /* conditions(2/2) */
> +}
> +
> +void
> +mcdc020b (int a, int b)
> +{
> +    x = (a || b) ? 1 : 0; /* conditions(3/4) true(1) */
> +}
> +
> +void
> +mcdc020c (int a, int b)
> +{
> +    x = a ? 0
> +	: b ? 1 /* conditions(2/2) */
> +	: 2;    /* conditions(1/2) false(0) */
> +		/* conditions(end) */
> +}
> +
> +/* Infinite loop (no exit-edge), this should not be called, but it should
> +   compile fine */
> +void
> +mcdc021a ()
> +{
> +    while (1)
> +	;
> +}
> +
> +/* Computed goto can give all sorts of problems, including difficult path
> +   contractions. */
> +void
> +mcdc021b ()
> +{
> +  void *op = &&dest;
> +dest:
> +  if (op) /* conditions(0/2) true(0) false(0) */
> +	  /* conditions(end) */
> +    goto * 0;
> +}
> +
> +int __sigsetjmp ();
> +
> +/* This should compile, but not called. */
> +void
> +mcdc021c ()
> +{
> +  while (x) /* conditions(0/2) true(0) false(0)*/
> +	    /* conditions(end) */
> +     __sigsetjmp ();
> +}
> +
> +/* If edges are not properly contracted the a && id (b) will be interpreted as
> +   two independent expressions. */
> +void
> +mcdc021d (int a, int b, int c, int d)
> +{
> +    if (a && id (b)) /* conditions(1/4) true(0 1) false(0) */
> +		     /* conditions(end) */
> +	x = 1;
> +    else if (c && id (d)) /* conditions(1/4) true(0 1) false(0) */
> +			  /* conditions(end) */
> +	x = 2;
> +    else
> +	x = 3;
> +}
> +
> +/* Adapted from linux arch/x86/tools/relocs.c
> +   With poor edge contracting this became an infinite loop. */
> +void
> +mcdc022a (int a, int b)
> +{
> +    for (int i = 0; i < 5; i++) /* conditions(2/2) */
> +    {
> +	x = i;
> +	for (int j = i; j < 5; j++) /* conditions(2/2) */
> +	{
> +	    if (id (id (a)) || id (b)) /* conditions(3/4) true(0) */
> +				       /* conditions(end) */
> +		continue;
> +	    b = inv(b);
> +	}
> +    }
> +}
> +
> +int
> +mcdc022b (int a)
> +{
> +    int devt;
> +    if (a) /* conditions(2/2) */
> +    {
> +	x = a * 2;
> +	if (x != a / 10 || x != a % 10) /* conditions(1/4) true(1) false(0 1) */
> +					/* conditions(end) */
> +	    return 0;
> +    } else {
> +	devt = id (a);
> +	if (devt) /* conditions(1/2) true(0) */
> +		  /* conditions(end) */
> +	    return 0;
> +    }
> +
> +    return devt;
> +}
> +
> +/* Adapted from linux arch/x86/events/intel/ds.c
> +
> +   It broken sorting so that the entry block was not the first node after
> +   sorting. */
> +void
> +mcdc022c (int a)
> +{
> +    if (!a) /* conditions(2/2) */
> +	return;
> +
> +    for (int i = 0; i < 5; i++) /* conditions(2/2) */
> +    {
> +	if (id (a + i) || inv (a - 1)) /* conditions(1/4) false(0 1) true(1) */
> +				       /* conditions(end) */
> +	    x = a + i;
> +	if (inv (a)) /* conditions(1/2) true(0) */
> +		     /* conditions(end) */
> +	    break;
> +    }
> +}
> +
> +void
> +mcdc022d (int a)
> +{
> +    int i;
> +    for (i = 0; i < id (a); i++) /* conditions(1/2) false(0) */
> +    {
> +	if (!inv (a)) /* conditions(1/2) false(0)*/
> +		      /* conditions(end) */
> +	    break;
> +    }
> +
> +    if (i < a) /* conditions(1/2) false(0) */
> +	       /* conditions(end) */
> +	x = a + 1;
> +}
> +
> +/* 023 specifically tests that masking works correctly, which gets complicated
> +   fast with a mix of operators and deep subexpressions.  These tests violates
> +   the style guide slightly to emphasize the nesting.  They all share the same
> +   implementation and only one input is given to each function to obtain clean
> +   coverage results. */
> +void
> +mcdc023a (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
> +	  int l, int m, int n)
> +{
> +    // [a m n] = 0, [b, ...] = 1
> +    // a is masked by b and the remaining terms should be short circuited
> +    if (/* conditions(1/24) true(0 2 3 4 5 6 7 8 9 10 11) false(0 1 2 3 4 5 6 7 8 9 10 11) */
> +	/* conditions(end) */
> +	   (a || b)
> +	|| (   ((c && d) || (e && (f || g) && h))
> +	    && (k || l)
> +	    && (m || n)))
> +	x = a + b;
> +    else
> +	x = b + c;
> +}
> +
> +void
> +mcdc023b (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
> +	  int l, int m, int n)
> +{
> +    // [a b d h] = 0, [c, ...] = 1
> +    // h = 0 => false but does not mask (a || b) or (c && d). d = 0 masks c.
> +    if (/* conditions(4/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 4 5 6 8 9 10 11) */
> +	/* conditions(end) */
> +	   (a || b)
> +	|| (   ((c && d) || (e && (f || g) && h))
> +	    && (k || l)
> +	    && (m || n)))
> +	x = a + b;
> +    else
> +	x = b + c;
> +}
> +
> +void
> +mcdc023c (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
> +	  int l, int m, int n)
> +{
> +    /* [m n a b] = 0, [...] = 1
> +       n,m = 0 should mask all other terms than a, b */
> +    if (/* conditions(4/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 3 4 5 6 7 8 9) */
> +	/* conditions(end) */
> +	   (a || b)
> +	|| (   ((c && d) || (e && (f || g) && h))
> +	    && (k || l)
> +	    && (m || n)))
> +	x = a + b;
> +    else
> +	x = b + c;
> +}
> +
> +void
> +mcdc023d (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
> +	  int l, int m, int n)
> +{
> +    /* [a b] = 0, [h, ...] = 1
> +       n,m = 0 should mask all other terms than a, b */
> +    if (/* conditions(4/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 3 4 5 6 7 10 11) */
> +	/* conditions(end) */
> +	   (a || b)
> +	|| (   ((c && d) || (e && (f || g) && h))
> +	    && (k || l)
> +	    && (m || n)))
> +	x = a + b;
> +    else
> +	x = b + c;
> +}
> +
> +void
> +mcdc023e (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
> +	  int l, int m, int n)
> +{
> +    /* [a b d] = 0, [c h, ...] = 1
> +       h = 1 should mask c, d, leave other terms intact.
> +       If [k l m n] were false then h itself would be masked.
> +       [a b] are masked as collateral by [m n]. */
> +    if (/* conditions(5/24) true(0 1 2 3 6 9 11) false(0 1 2 3 4 5 6 7 8 9 10 11) */
> +	/* conditions(end) */
> +	   (a || b)
> +	|| (   ((c && d) || (e && (f || g) && h))
> +	    && (k || l)
> +	    && (m || n)))
> +	x = a + b;
> +    else
> +	x = b + c;
> +}
> +
> +void
> +mcdc023f (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
> +	  int l, int m, int n)
> +{
> +    /* [a b c f g] = 0, [e, ...] = 1
> +       [f g] = 0 should mask e, leave [c d] intact. */
> +    if (/* conditions(5/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(3 4 7 8 9 10 11) */
> +	/* conditions(end) */
> +	   (a || b)
> +	|| (   ((c && d) || (e && (f || g) && h))
> +	    && (k || l)
> +	    && (m || n)))
> +	x = a + b;
> +    else
> +	x = b + c;
> +}
> +
> +void
> +mcdc023g (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
> +	  int l, int m, int n)
> +{
> +    /* [a b d f g] = 0, [e c, ...] = 1
> +       Same as 023f but with [c d] flipped so d masks c rather than c
> +       short-circuits.  This should not be lost. */
> +    if (/* conditions(5/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 4 7 8 9 10 11) */
> +	/* conditions(end) */
> +	   (a || b)
> +	|| (   ((c && d) || (e && (f || g) && h))
> +	    && (k || l)
> +	    && (m || n)))
> +	x = a + b;
> +    else
> +	x = b + c;
> +}
> +
> +void
> +mcdc024a (int a, int b)
> +{
> +    if (a && b) /* conditions(1/4) true(0 1) false(1) */
> +		/* conditions(end) */
> +    {
> +label1:
> +	x = 1;
> +    }
> +    else
> +    {
> +	x = 2;
> +    }
> +
> +    if (a || b) /* conditions(2/4) true(0 1) */
> +		/* conditions(end) */
> +    {
> +label2:
> +	x = 1;
> +    }
> +    else
> +    {
> +	x = 2;
> +    }
> +}
> +
> +void
> +mcdc024b (int a, int b)
> +{
> +
> +    if (a && b) /* conditions(1/4) true(0 1) false(1) */
> +		/* conditions(end) */
> +    {
> +	x = 1;
> +    }
> +    else
> +    {
> +label1:
> +	x = 2;
> +    }
> +
> +    if (a || b) /* conditions(2/4) true(0 1) */
> +		/* conditions(end) */
> +    {
> +	x = 1;
> +    }
> +    else
> +    {
> +label2:
> +	x = 2;
> +    }
> +}
> +
> +void
> +mcdc024c (int a, int b)
> +{
> +    if (a && b) /* conditions(1/4) true(0 1) false(1) */
> +		/* conditions(end) */
> +    {
> +label1:
> +	x = 1;
> +    }
> +    else
> +    {
> +label2:
> +	x = 2;
> +    }
> +
> +    if (a || b) /* conditions(2/4) true(0 1) */
> +		/* conditions(end) */
> +    {
> +label3:
> +	x = 1;
> +    }
> +    else
> +    {
> +label4:
> +	x = 2;
> +    }
> +}
> +
> +int main ()
> +{
> +    mcdc001a (0, 1);
> +
> +    mcdc001b (0, 1);
> +    mcdc001b (0, 0);
> +
> +    mcdc001c (0, 1);
> +    mcdc001c (0, 0);
> +    mcdc001c (1, 1);
> +
> +    mcdc001d (1, 1, 1);
> +    mcdc001d (0, 1, 0);
> +
> +    mcdc002a (1, 0);
> +
> +    mcdc002b (1, 0);
> +    mcdc002b (1, 1);
> +
> +    mcdc002c (0, 0);
> +    mcdc002c (1, 1);
> +    mcdc002c (1, 0);
> +
> +    mcdc002d (1, 1, 1);
> +    mcdc002d (1, 0, 0);
> +
> +    mcdc003a (0, 0);
> +    mcdc003a (1, 0);
> +
> +    mcdc004a (0);
> +    mcdc004b (0);
> +    mcdc004b (1);
> +    mcdc004c (1);
> +
> +    mcdc004d (0, 0, 0);
> +    mcdc004d (1, 1, 1);
> +
> +    mcdc004e (0, 0, 0);
> +    mcdc004e (1, 1, 1);
> +
> +    mcdc005a (1, 0, 1);
> +
> +    mcdc005b (1, 1, 0, 0);
> +    mcdc005b (1, 0, 0, 0);
> +
> +    mcdc005c (0, 1, 1, 0);
> +
> +    mcdc005d (1, 0, 0, 0);
> +
> +    mcdc006a (0, 0, 0, 0, 0);
> +    mcdc006a (1, 0, 0, 0, 0);
> +    mcdc006a (1, 1, 1, 0, 0);
> +
> +    mcdc006b (0, 0, 0);
> +    mcdc006b (1, 0, 0);
> +    mcdc006b (1, 1, 0);
> +    mcdc006b (1, 1, 1);
> +
> +    mcdc006c (0, 0, 0);
> +    mcdc006c (1, 0, 0);
> +    mcdc006c (1, 1, 0);
> +    mcdc006c (1, 1, 1);
> +
> +    mcdc007a (0, 0, 0, 0);
> +    mcdc007a (1, 0, 0, 0);
> +    mcdc007a (0, 0, 1, 0);
> +
> +    mcdc007b (0, 0, 0);
> +    mcdc007b (0, 1, 1);
> +    mcdc007b (1, 0, 1);
> +
> +    mcdc007c (0, 0, 0);
> +    mcdc007c (0, 1, 1);
> +    mcdc007c (1, 0, 1);
> +
> +    mcdc008a (0);
> +
> +    mcdc008b (0);
> +
> +    mcdc008c (0);
> +    mcdc008c (1);
> +
> +    mcdc008d (0, 0, 0, 0);
> +    mcdc008d (1, 0, 0, 0);
> +    mcdc008d (1, 0, 1, 0);
> +    mcdc008d (1, 0, 1, 1);
> +    mcdc008d (1, 1, 1, 1);
> +
> +    mcdc009a (0, 0);
> +    mcdc009a (1, 1);
> +
> +    mcdc010a (0, 0);
> +    mcdc010a (0, 9);
> +    mcdc010a (2, 1);
> +
> +    mcdc010b ();
> +
> +    mcdc010c (1);
> +
> +    mcdc011a (0, 0, 0);
> +    mcdc011a (1, 1, 0);
> +    mcdc011a (1, 0, 1);
> +
> +    mcdc012a (0, 0, 0);
> +    mcdc012a (0, 1, 1);
> +
> +    mcdc013a (0, 0, 0);
> +    mcdc013a (0, 0, 1);
> +    mcdc013a (0, 1, 0);
> +    mcdc013a (0, 1, 1);
> +    mcdc013a (1, 0, 0);
> +    mcdc013a (1, 0, 1);
> +    mcdc013a (1, 1, 0);
> +    mcdc013a (1, 1, 1);
> +
> +    mcdc014a ();
> +
> +    mcdc015a (0, 0);
> +    mcdc015a (1, 0);
> +
> +    mcdc015b (0, 0);
> +    mcdc015b (0, 1);
> +    mcdc015b (6, 1);
> +
> +    mcdc015c (0, 0);
> +    mcdc015c (0, 5);
> +    mcdc015c (6, 1);
> +
> +    mcdc016a (5, 5);
> +
> +    mcdc016b (5, 5);
> +    mcdc016b (6, 5);
> +
> +    mcdc016c (5, 5);
> +
> +    mcdc016d (1, 0);
> +
> +    mcdc017a (0);
> +    mcdc017a (2);
> +
> +    mcdc017b (2, 0);
> +    mcdc017b (0, 1);
> +
> +    mcdc017c (1, 1);
> +
> +    mcdc018a (0, 0, 1, 1, 0, 0, 0, 0);
> +    mcdc018a (0, 1, 0, 0, 0, 0, 1, -2);
> +    mcdc018a (0, 6, 0, 0, 0, 0, 1, -2);
> +    mcdc018a (0, 6, 0, 0, 0, 0, 1, -2);
> +    mcdc018a (0, 0, 0, 1, 0, 1, 1, 0);
> +    mcdc018a (1, 0, 0, 0, 1, 1, 0, 0);
> +
> +    mcdc018b (1, 0, 0);
> +    mcdc018b (1, 1, 0);
> +
> +    mcdc018c (1, 1);
> +
> +    mcdc019a ();
> +
> +    mcdc020a (0);
> +    mcdc020a (1);
> +
> +    mcdc020b (0, 0);
> +    mcdc020b (1, 0);
> +
> +    mcdc020c (0, 1);
> +    mcdc020c (1, 1);
> +
> +    mcdc021d (1, 0, 1, 0);
> +
> +    mcdc022a (0, 0);
> +
> +    mcdc022b (0);
> +    mcdc022b (1);
> +
> +    mcdc022c (0);
> +    mcdc022c (1);
> +
> +    mcdc022d (1);
> +
> +    mcdc023a (0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
> +    mcdc023b (0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1);
> +    mcdc023c (0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0);
> +    mcdc023d (0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1);
> +    mcdc023e (0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1);
> +    mcdc023f (0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1);
> +    mcdc023g (0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1);
> +
> +    mcdc024a (0, 0);
> +    mcdc024b (0, 0);
> +    mcdc024c (0, 0);
> +}
> +
> +/* { dg-final { run-gcov conditions { --conditions gcov-19.c } } } */
> diff --git a/gcc/testsuite/gcc.misc-tests/gcov-20.c b/gcc/testsuite/gcc.misc-tests/gcov-20.c
> new file mode 100644
> index 00000000000..847dae495db
> --- /dev/null
> +++ b/gcc/testsuite/gcc.misc-tests/gcov-20.c
> @@ -0,0 +1,22 @@
> +/* { dg-options "-fprofile-conditions -ftest-coverage -fprofile-update=atomic" } */
> +/* { dg-do run { target native } } */
> +
> +/* some side effect to stop branches from being pruned */
> +int x = 0;
> +
> +void
> +conditions_atomic001 (int a, int b)
> +{
> +    if (a || b) /* conditions(1/4) true(0) false(0 1) */
> +		/* conditions(end) */
> +	x = 1;
> +    else
> +	x = 2;
> +}
> +
> +int main ()
> +{
> +    conditions_atomic001 (0, 1);
> +}
> +
> +/* { dg-final { run-gcov conditions { --conditions gcov-20.c } } } */
> diff --git a/gcc/testsuite/gcc.misc-tests/gcov-21.c b/gcc/testsuite/gcc.misc-tests/gcov-21.c
> new file mode 100644
> index 00000000000..978be3276a2
> --- /dev/null
> +++ b/gcc/testsuite/gcc.misc-tests/gcov-21.c
> @@ -0,0 +1,16 @@
> +/* { dg-options "-fprofile-conditions" } */
> +
> +/* https://gcc.gnu.org/pipermail/gcc-patches/2022-April/592927.html */
> +char trim_filename_name;
> +int r;
> +
> +void trim_filename() {
> +    if (trim_filename_name)
> +	r = 123;
> +    while (trim_filename_name)
> +	;
> +}
> +
> +int main ()
> +{
> +}
> diff --git a/gcc/testsuite/lib/gcov.exp b/gcc/testsuite/lib/gcov.exp
> index 9d5b2cdb86b..69168d67d03 100644
> --- a/gcc/testsuite/lib/gcov.exp
> +++ b/gcc/testsuite/lib/gcov.exp
> @@ -174,6 +174,184 @@ proc verify-branches { testname testcase file } {
>      return $failed
>  }
>  
> +#
> +# verify-conditions -- check that conditions are checked as expected
> +#
> +# TESTNAME is the name of the test, including unique flags.
> +# TESTCASE is the name of the test file.
> +# FILE is the name of the gcov output file.
> +#
> +# Checks are based on comments in the source file. Condition coverage comes
> +# with with two types of output, a summary and a list of the uncovered
> +# conditions. Both must be checked to pass the test
> +#
> +# To check for conditions, add a comment the line of a conditional:
> +# /* conditions(n/m) true(0 1) false(1) */
> +#
> +# where n/m are the covered and total conditions in the expression. The true()
> +# and false() take the indices expected *not* covered.
> +#
> +# This means that all coverage statements should have been seen:
> +# /* conditions(end) */
> +#
> +# If all conditions are covered i.e. n == m, then conditions(end) can be
> +# omitted. If either true() or false() are empty they can be omitted too.
> +#
> +# C++ can insert conditionals in the CFG that are not present in source code.
> +# These must be manually suppressed since unexpected and unhandled conditions
> +# are an error (to help combat regressions). Output can be suppressed with
> +# conditions(suppress) and conditions(end). suppress should usually be on a
> +# closing brace.
> +#
> +# Some expressions, when using unnamed temporaries as operands, will have
> +# destructors in expressions. The coverage of the destructor will be reported
> +# on the same line as the expression itself, but suppress() would also swallow
> +# the expected tested-for messages. To handle these, use the destructor() [1]
> +# which will suppress everything from and including the second "conditions
> +# covered".
> +#
> +# [1] it is important that the destructor() is *on the same line* as the
> +#     conditions(m/n)
> +proc verify-conditions { testname testcase file } {
> +    set failed 0
> +    set suppress 0
> +    set destructor 0
> +    set should ""
> +    set shouldt ""
> +    set shouldf ""
> +    set shouldall ""
> +    set fd [open $file r]
> +    set n 0
> +    set keywords {"end" "suppress"}
> +    while {[gets $fd line] >= 0} {
> +	regexp "^\[^:\]+: *(\[0-9\]+):" "$line" all n
> +	set prefix "$testname line $n"
> +
> +	if {![regexp "condition" $line]} {
> +	    continue
> +	}
> +
> +	# Missing coverage for both true and false will cause a failure, but
> +	# only count it once for the report.
> +	set ok 1
> +	if [regexp {conditions *\(([0-9a-z/]+)\)} "$line" all e] {
> +	    # *Very* coarse sanity check: conditions() should either be a
> +	    # keyword or n/m, anything else means a buggy test case. end is
> +	    # optional for cases where all conditions are covered, since it
> +	    # only expects a single line of output.
> +	    if {([lsearch -exact $keywords $e] >= 0 || [regexp {\d+/\d+} "$e"]) == 0} {
> +		fail "$prefix: expected conditions (n/m), (suppress) or (end); was ($e)"
> +		incr failed
> +		continue
> +	    }
> +
> +	    # Any keyword means a new context. Set the error flag if not all
> +	    # expected output has been seen, and reset the state.
> +
> +	    if {[llength $shouldt] != 0} {
> +		fail "$prefix: expected 'not covered (true)' for terms: $shouldt"
> +		set ok 0
> +	    }
> +
> +	    if {[llength $shouldf] != 0} {
> +		fail "$prefix: expected 'not covered (false)' for terms: $shouldf"
> +		set ok 0
> +	    }
> +
> +	    if {$shouldall ne ""} {
> +		fail "$prefix: coverage summary not found; expected $shouldall"
> +		set ok 0
> +	    }
> +
> +	    set suppress 0
> +	    set destructor 0
> +	    set should ""
> +	    set shouldt ""
> +	    set shouldf ""
> +	    set shouldall ""
> +	    set newt ""
> +	    set newf ""
> +
> +	    if [regexp {destructor\(\)} "$line"] {
> +		set destructor 1
> +	    }
> +
> +	    if [regexp {(\d+)/(\d+)} "$e" all i k] {
> +		regexp {true\(([0-9 ]+)\)}  "$line" all newt
> +		regexp {false\(([0-9 ]+)\)} "$line" all newf
> +
> +		# Sanity check - if the true() and false() vectors should have
> +		# m-n elements to cover all uncovered conditions. Because of
> +		# masking it can sometimes be surprising what terms are
> +		# independent, so this makes for more robust test at the cost
> +		# of being slightly more annoying to write.
> +		set nterms [expr [llength $newt] + [llength $newf]]
> +		set nexpected [expr {$k - $i}]
> +		if {$nterms != $nexpected} {
> +		    fail "$prefix: expected $nexpected uncovered terms; got $nterms"
> +		    set ok 0
> +		}
> +		set shouldall $e
> +		set shouldt $newt
> +		set shouldf $newf
> +	    } elseif {$e == "end"} {
> +		# no-op - state has already been reset, and errors flagged
> +	    } elseif {$e == "suppress"} {
> +		set suppress 1
> +	    } else {
> +		# this should be unreachable, 
> +		fail "$prefix: unhandled control ($e), should be unreachable"
> +		set ok 0
> +	    }
> +	} elseif {$suppress == 1} {
> +	    # ignore everything in a suppress block. C++ especially can insert
> +	    # conditionals in exceptions and destructors which would otherwise
> +	    # be considered unhandled.
> +	    continue
> +	} elseif [regexp {condition +(\d+) not covered \((.*)\)} "$line" all cond condv] {
> +	    foreach v {true false} {
> +		if [regexp $v $condv] {
> +		    if {"$v" == "true"} {
> +			set should shouldt
> +		    } else {
> +			set should shouldf
> +		    }
> +
> +		    set i [lsearch [set $should] $cond]
> +		    if {$i != -1} {
> +			set $should [lreplace [set $should] $i $i]
> +		    } else {
> +			fail "$testname line $n: unexpected uncovered term $cond ($v)"
> +			set ok 0
> +		    }
> +		}
> +	    }
> +	} elseif [regexp {condition outcomes covered (\d+/\d+)} "$line" all cond] {
> +	    # the destructor-generated "conditions covered" lines will be
> +	    # written after all expression-related output. Handle these by
> +	    # turning on suppression if the destructor-suppression is
> +	    # requested.
> +	    if {$shouldall == "" && $destructor == 1} {
> +		set suppress 1
> +		continue
> +	    }
> +
> +	    if {$cond == $shouldall} {
> +		set shouldall ""
> +	    } else {
> +		fail "$testname line $n: unexpected summary $cond"
> +		set ok 0
> +	    }
> +	}
> +
> +	if {$ok != 1} {
> +	    incr failed
> +	}
> +    }
> +    close $fd
> +    return $failed
> +}
> +
>  #
>  # verify-calls -- check that call return percentages are as expected
>  #
> @@ -321,6 +499,7 @@ proc run-gcov { args } {
>      set gcov_args ""
>      set gcov_verify_calls 0
>      set gcov_verify_branches 0
> +    set gcov_verify_conditions 0
>      set gcov_verify_lines 1
>      set gcov_verify_intermediate 0
>      set gcov_remove_gcda 0
> @@ -331,10 +510,13 @@ proc run-gcov { args } {
>  	  set gcov_verify_calls 1
>  	} elseif { $a == "branches" } {
>  	  set gcov_verify_branches 1
> +	} elseif { $a == "conditions" } {
> +	  set gcov_verify_conditions 1
>  	} elseif { $a == "intermediate" } {
>  	  set gcov_verify_intermediate 1
>  	  set gcov_verify_calls 0
>  	  set gcov_verify_branches 0
> +	  set gcov_verify_conditions 0
>  	  set gcov_verify_lines 0
>  	} elseif { $a == "remove-gcda" } {
>  	  set gcov_remove_gcda 1
> @@ -404,6 +586,11 @@ proc run-gcov { args } {
>      } else {
>  	set bfailed 0
>      }
> +    if { $gcov_verify_conditions } {
> +	set cdfailed [verify-conditions $testname $testcase $testcase.gcov]
> +    } else {
> +	set cdfailed 0
> +    }
>      if { $gcov_verify_calls } {
>  	set cfailed [verify-calls $testname $testcase $testcase.gcov]
>      } else {
> @@ -418,12 +605,12 @@ proc run-gcov { args } {
>  
>      # Report whether the gcov test passed or failed.  If there were
>      # multiple failures then the message is a summary.
> -    set tfailed [expr $lfailed + $bfailed + $cfailed + $ifailed]
> +    set tfailed [expr $lfailed + $bfailed + $cdfailed + $cfailed + $ifailed]
>      if { $xfailed } {
>  	setup_xfail "*-*-*"
>      }
>      if { $tfailed > 0 } {
> -	fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cfailed in return percentages, $ifailed in intermediate format"
> +	fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cdfailed in condition/decision, $cfailed in return percentages, $ifailed in intermediate format"
>  	if { $xfailed } {
>  	    clean-gcov $testcase
>  	}
> diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
> index 2beb49241f2..0b537d64d97 100644
> --- a/gcc/tree-profile.cc
> +++ b/gcc/tree-profile.cc
> @@ -58,6 +58,8 @@ along with GCC; see the file COPYING3.  If not see
>  #include "alloc-pool.h"
>  #include "symbol-summary.h"
>  #include "symtab-thunks.h"
> +#include "cfganal.h"
> +#include "cfgloop.h"
>  
>  static GTY(()) tree gcov_type_node;
>  static GTY(()) tree tree_interval_profiler_fn;
> @@ -73,6 +75,1048 @@ static GTY(()) tree ic_tuple_var;
>  static GTY(()) tree ic_tuple_counters_field;
>  static GTY(()) tree ic_tuple_callee_field;
>  
> +namespace
> +{
> +/* Some context and reused instances between function calls.  Large embedded
> +   buffers are used to up-front request enough memory for most programs and
> +   merge them into a single allocation at the cost of using more memory in the
> +   average case.  Some numbers from linux v5.13 which is assumed to be a
> +   reasonably diverse code base: 75% of the functions in linux have less than
> +   16 nodes in the CFG and approx 2.5% have more than 64 nodes.  The functions
> +   that go beyond a few dozen nodes tend to be very large (>100) and so 64
> +   seems like a good balance.
> +
> +   This is really just a performance balance of the cost of allocation and
> +   wasted memory. */
> +struct conds_ctx
> +{
> +    /* Bitmap of the processed blocks.  Bit n set means basic_block->index has
> +       been processed either explicitly or as a part of an expression. */
> +    auto_sbitmap marks;
> +
> +    /* This is both a reusable shared allocation which is also used to return
> +       single expressions, which means it for most code should only hold a
> +       couple of elements. */
> +    auto_vec<basic_block, 32> blocks;
> +
> +    /* Map from basic_block->index to an ordering so that for a single
> +       expression (a || b && c) => index_map[a] < index_map[b] < index_map[c].
> +       The values do not have to be consecutive and can be interleaved by
> +       values from other expressions, so comparisons only make sense for blocks
> +       that belong to the same expression. */
> +    auto_vec<int, 64> index_map;
> +
> +    /* Pre-allocate bitmaps and vectors for per-function book keeping.  This is
> +       pure instance reuse and the bitmaps carry no data between function
> +       calls. */
> +    auto_vec<basic_block, 64> B1;
> +    auto_vec<basic_block, 64> B2;
> +    auto_sbitmap G1;
> +    auto_sbitmap G2;
> +    auto_sbitmap G3;
> +
> +    explicit conds_ctx (unsigned size) noexcept (true) : marks (size),
> +    G1 (size), G2 (size), G3 (size)
> +    {
> +	bitmap_clear (marks);
> +    }
> +
> +    /* Mark a node as processed so nodes are not processed twice for example in
> +       loops, gotos. */
> +    void mark (const basic_block b) noexcept (true)
> +    {
> +	gcc_assert (!bitmap_bit_p (marks, b->index));
> +	bitmap_set_bit (marks, b->index);
> +    }
> +
> +    /* Mark nodes as processed so they are not processed twice. */
> +    void mark (const vec<basic_block>& bs) noexcept (true)
> +    {
> +	for (const basic_block b : bs)
> +	    mark (b);
> +    }
> +
> +    /* Check if all nodes are marked.  A successful run should visit & mark
> +       every reachable node exactly once. */
> +    bool all_marked (const vec<basic_block>& reachable) const noexcept (true)
> +    {
> +	for (const basic_block b : reachable)
> +	    if (!bitmap_bit_p (marks, b->index))
> +		return false;
> +	return true;
> +    }
> +};
> +
> +/* Only instrument terms with fewer than number of bits in a (wide) gcov
> +   integer, which is probably 64.  The algorithm itself does not impose this
> +   limitation, but it makes for a simpler implementation.
> +
> +   * Allocating the output data structure (coverage_counter_alloc ()) can
> +     assume pairs of gcov_type_unsigned and not use a separate length field.
> +   * A pair gcov_type_unsigned can be used as accumulators.
> +   * Updating accumulators is can use the bitwise operations |=, &= and not
> +     custom operators that work for arbitrary-sized bit-sets.
> +
> +   Most real-world code should be unaffected by this, but it is possible
> +   (especially for generated code) to exceed this limit.
> + */
> +#define CONDITIONS_MAX_TERMS (sizeof (gcov_type_unsigned) * BITS_PER_UNIT)
> +#define EDGE_CONDITION (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE)
> +
> +/* Compare two basic blocks by their order in the expression i.e. for (a || b)
> +   then cmp_index_map (a, b, ...) < 0.  The result is undefined if lhs, rhs
> +   belong to different expressions. */
> +int
> +cmp_index_map (const void *lhs, const void *rhs, void *index_map)
> +{
> +    const_basic_block l = *(const basic_block*) lhs;
> +    const_basic_block r = *(const basic_block*) rhs;
> +    const vec<int>* im = (const vec<int>*) index_map;
> +    return (*im)[l->index] - (*im)[r->index];
> +}
> +
> +/* Find the index of needle in blocks; return -1 if not found. This has two
> +   uses, sometimes for the index and sometimes for set member checks.  Sets are
> +   typically very small (number of conditions, >8 is uncommon) so linear search
> +   should be very fast. */
> +int
> +index_of (const basic_block needle, array_slice<basic_block> blocks)
> +{
> +    for (size_t i = 0; i < blocks.size (); i++)
> +	if (blocks[i] == needle)
> +	    return int (i);
> +    return -1;
> +}
> +
> +/* Returns true if this is a conditional node, i.e. it has outgoing true and
> +   false edges. */
> +bool
> +block_conditional_p (const basic_block b)
> +{
> +    unsigned t = 0;
> +    unsigned f = 0;
> +    for (edge e : b->succs)
> +    {
> +	t |= (e->flags & EDGE_TRUE_VALUE);
> +	f |= (e->flags & EDGE_FALSE_VALUE);
> +    }
> +    return t && f;
> +}
> +
> +/* Check if the edge is a conditional. */
> +bool
> +edge_conditional_p (const edge e)
> +{
> +    return e->flags & EDGE_CONDITION;
> +}
> +
> +/* Special cases of the single_*_p and single_*_edge functions in basic-block.h
> +   that don't consider exception handling or other complex edges.  This helps
> +   create a view of the CFG with only normal edges - if a basic block has both
> +   an outgoing fallthrough and exceptional edge [1], it should be considered a
> +   single-successor.
> +
> +   [1] if this is not possible, these functions can be removed and replaced by
> +       their basic-block.h cousins. */
> +bool
> +single (const vec<edge, va_gc> *edges)
> +{
> +    int n = EDGE_COUNT (edges);
> +    if (n == 0)
> +	return false;
> +
> +    for (edge e : edges)
> +	if (e->flags & EDGE_COMPLEX)
> +	    n -= 1;
> +
> +    return n == 1;
> +}
> +
> +/* Get the single, non-complex edge.  Behavior is undefined edges have more
> +   than 1 non-complex edges. */
> +edge
> +single_edge (const vec<edge, va_gc> *edges)
> +{
> +    for (edge e : edges)
> +    {
> +	if (e->flags & EDGE_COMPLEX)
> +	    continue;
> +	return e;
> +    }
> +    return NULL;
> +}
> +
> +/* Sometimes, for example with function calls and C++ destructors, the CFG gets
> +   extra nodes that are essentially single-entry-single-exit in the middle of
> +   boolean expressions.  For example:
> +
> +      x || can_throw (y)
> +
> +               A
> +              /|
> +             / |
> +            B  |
> +            |  |
> +            C  |
> +           / \ |
> +          /   \|
> +         F     T
> +
> +   Without the extra node inserted by the function + exception it becomes a
> +   proper 2-term graph, not 2 single-term graphs.
> +
> +               A
> +              /|
> +             C |
> +            / \|
> +           F   T
> +
> +   contract_edge ignores the series of intermediate nodes and makes a virtual
> +   edge A -> C without having to construct a new simplified CFG explicitly.  It
> +   gets more complicated as non-conditional edges is how the body of the
> +   then/else blocks are separated from the boolean expression, so only edges
> +   that are inserted because of function calls in the expression itself must be
> +   merged.
> +
> +   Only chains of single-exit single-entry nodes that end with a condition
> +   should be contracted. */
> +edge
> +contract_edge (edge e)
> +{
> +    edge source = e;
> +    while (true)
> +    {
> +	basic_block dest = e->dest;
> +	if (!single (dest->preds))
> +	    return source;
> +	if (e->flags & EDGE_DFS_BACK)
> +	    return source;
> +	if (block_conditional_p (dest))
> +	    return e;
> +
> +	e = single_edge (dest->succs);
> +	if (!e)
> +	    return source;
> +    }
> +}
> +
> +/* This is the predecessor dual of contract_edge; it collapses the predecessor
> +   blocks between two operands in a boolean expression. */
> +edge
> +contract_edge_up (edge e)
> +{
> +    while (true)
> +    {
> +	basic_block src = e->src;
> +	if (edge_conditional_p (e))
> +	    return e;
> +	if (!single (src->preds))
> +	    return e;
> +	e = single_edge (src->preds);
> +    }
> +}
> +
> +/* "Undo" an edge split.  Sometimes the sink of a boolean expression will be
> +   split into multiple blocks to accurately track line coverage, for example
> +   when there is a goto-label at the top of the then/else block:
> +
> +    if (a && b)
> +    {
> +	l1:
> +	...
> +    }
> +    else
> +    {
> +	l2:
> +	...
> +    }
> +
> +    and the corresponding CFG where a1 and b1 are created in edge splits to the
> +    same destination (F):
> +
> +    a
> +    |\
> +    | a1
> +    b  \
> +    |\  |
> +    | b1|
> +    |  \|
> +    T   F
> +
> +    This function recognizes this shape and returns the "merges" the split
> +    outcome block by returning their common successor.  In all other cases it is
> +    the identity function.
> +*/
> +basic_block
> +merge_split_outcome (basic_block b)
> +{
> +    if (!single (b->succs))
> +	return b;
> +    if (!single (b->preds))
> +	return b;
> +
> +    const unsigned flag = single_edge (b->preds)->flags & EDGE_CONDITION;
> +    if (!flag)
> +	return b;
> +
> +    edge e = single_edge (b->succs);
> +    for (edge pred : e->dest->preds)
> +    {
> +	if (!single (pred->src->preds))
> +	    return b;
> +	if (!(single_edge (pred->src->preds)->flags & flag))
> +	    return b;
> +    }
> +    return e->dest;
> +}
> +
> +
> +/* Find the set {ancestors(p) intersect G} where ancestors is the recursive set
> +   of predecessors for p.  Limiting to the ancestors that are also in G (see
> +   cond_reachable_from) and by q is an optimization as ancestors outside G have
> +   no effect when isolating expressions.
> +
> +   dfs_enumerate_from () does not work as the filter function needs edge
> +   information and dfs_enumerate_from () only considers blocks. */
> +void
> +ancestors_of (basic_block p, basic_block q, const sbitmap G, sbitmap ancestors)
> +{
> +    if (!bitmap_bit_p (G, p->index))
> +	return;
> +
> +    bitmap_set_bit (ancestors, p->index);
> +    bitmap_set_bit (ancestors, q->index);
> +    if (p == q)
> +	return;
> +
> +    auto_vec<basic_block, 16> stack;
> +    stack.safe_push (p);
> +
> +    while (!stack.is_empty())
> +    {
> +	basic_block b = stack.pop ();
> +	if (single (b->preds))
> +	{
> +	    edge e = single_edge (b->preds);
> +	    e = contract_edge_up (e);
> +	    b = e->dest;
> +	}
> +
> +	for (edge e : b->preds)
> +	{
> +	    basic_block src = e->src;
> +	    if (bitmap_bit_p (ancestors, e->src->index))
> +		continue;
> +	    if (!bitmap_bit_p (G, e->src->index))
> +		continue;
> +	    bitmap_set_bit (ancestors, src->index);
> +	    stack.safe_push (src);
> +	}
> +    }
> +}
> +
> +/* A simple struct for storing/returning outcome block pairs.  Either both
> +   blocks are set or both are NULL. */
> +struct outcomes
> +{
> +    basic_block t = NULL;
> +    basic_block f = NULL;
> +
> +    operator bool () const noexcept (true)
> +    {
> +	return t && f;
> +    }
> +};
> +
> +/* Get the true/false successors of a basic block. If b is not a conditional
> +   block both edges are NULL. */
> +outcomes
> +conditional_succs (const basic_block b)
> +{
> +    outcomes c;
> +    for (edge e : b->succs)
> +    {
> +	if (e->flags & EDGE_TRUE_VALUE)
> +	    c.t = merge_split_outcome (e->dest);
> +	if (e->flags & EDGE_FALSE_VALUE)
> +	    c.f = merge_split_outcome (e->dest);
> +    }
> +
> +    gcc_assert ((c.t && c.f) || (!c.t && !c.f));
> +    return c;
> +}
> +
> +/* Get the index or offset of a conditional flag, 0 for true and 1 for false.
> +   These indices carry no semantics but must be consistent as they are used to
> +   index into data structures in code generation and gcov. */
> +unsigned
> +condition_index (unsigned flag)
> +{
> +    return (flag & EDGE_CONDITION) == EDGE_TRUE_VALUE ? 0 : 1;
> +}
> +
> +/* Compute the masking vector.
> +
> +   Masking and short circuiting are deeply connected - masking occurs when
> +   control flow reaches a state that is also reachable with short circuiting.
> +   In fact, masking corresponds to short circuiting in the CFG for the reversed
> +   expression.  This means we can find the limits, the last term in preceeding
> +   subexpressions, by following the edges that short circuit to the same
> +   outcome.
> +
> +   In the simplest case a || b:
> +
> +   a
> +   |\
> +   | b
> +   |/ \
> +   T   F
> +
> +   T has has multiple incoming edges and is the outcome of a short circuit,
> +   with top = a, bot = b.  The top node (a) is masked when the edge (b, T) is
> +   taken.
> +
> +   The names "top" and "bot" refer to a pair of nodes with a shared
> +   destination. The top is always the node corresponding to the left-most
> +   operand of the two it holds that index_map[top] < index_map[bot].
> +
> +   Now consider (a && b) || (c && d) and its masking vectors:
> +
> +   a
> +   |\
> +   b \
> +   |\|
> +   | c
> +   | |\
> +   | d \
> +   |/ \|
> +   T   F
> +
> +   a[0] = {}
> +   a[1] = {}
> +   b[0] = {a}
> +   b[1] = {}
> +   c[0] = {}
> +   c[1] = {}
> +   d[0] = {c}
> +   d[1] = {a,b}
> +
> +   Note that 0 and 1 are indices and not boolean values - a[0] is the index in
> +   the masking vector when a takes the true edge.
> +
> +   b[0] and d[0] are identical to the a || b example, and d[1] is the bot in
> +   the triangle [d, b] -> T.  b is the top node in the [d, b] relationship and
> +   last term in (a && b).  To find the other terms masked we use the fact that
> +   all nodes in an expression have outgoing edges to either the outcome or some
> +   other node in the expression.  The "bot" node is also the last term in a
> +   masked subexpression, so the problem becomes finding the subgraph where all
> +   paths end up in the successors to bot.
> +
> +   We find the terms by marking the outcomes (in this case c, T) and walk the
> +   predecessors starting at top (in this case b) and masking nodes when both
> +   successors are marked.
> +
> +   The masking vector is represented as two bitfields per term in the
> +   expression with the index corresponding to the term in the source
> +   expression.  a || b && c becomes the term vector [a b c] and the masking
> +   vectors [a[0] a[1] b[0] ...].  The kth bit of a masking vector is set if the
> +   the kth term is masked by taking the edge. */
> +void
> +masking_vectors (conds_ctx& ctx, array_slice<basic_block> blocks,
> +		 array_slice<gcov_type_unsigned> masks)
> +{
> +    gcc_assert (blocks.is_valid ());
> +    gcc_assert (!blocks.empty ());
> +    gcc_assert (masks.is_valid ());
> +
> +    sbitmap marks = ctx.G1;
> +    sbitmap expr = ctx.G2;
> +    vec<basic_block>& queue = ctx.B1;
> +    vec<basic_block>& body = ctx.B2;
> +    const vec<int>& index_map = ctx.index_map;
> +    bitmap_clear (expr);
> +
> +    for (const basic_block b : blocks)
> +	bitmap_set_bit (expr, b->index);
> +
> +    /* Set up for the iteration - include two outcome nodes in the traversal and
> +       ignore the leading term since it cannot mask anything.  The algorithm is
> +       not sensitive to the traversal order. */
> +    body.truncate (0);
> +    body.reserve (blocks.size () + 2);
> +    for (const basic_block b : blocks)
> +	body.quick_push (b);
> +
> +    outcomes out = conditional_succs (blocks.back ());
> +    body.quick_push (out.t);
> +    body.quick_push (out.f);
> +    body[0] = body.pop ();
> +
> +    for (const basic_block b : body)
> +    {
> +	for (edge e1 : b->preds)
> +	for (edge e2 : b->preds)
> +	{
> +	    const basic_block top = e1->src;
> +	    const basic_block bot = e2->src;
> +	    const unsigned cond = e1->flags & e2->flags & (EDGE_CONDITION);
> +
> +	    if (!cond)
> +		continue;
> +	    if (e1 == e2)
> +		continue;
> +	    if (!bitmap_bit_p (expr, top->index))
> +		continue;
> +	    if (!bitmap_bit_p (expr, bot->index))
> +		continue;
> +	    if (index_map[top->index] > index_map[bot->index])
> +		continue;
> +
> +	    outcomes out = conditional_succs (top);
> +	    gcc_assert (out);
> +	    bitmap_clear (marks);
> +	    bitmap_set_bit (marks, out.t->index);
> +	    bitmap_set_bit (marks, out.f->index);
> +	    queue.truncate (0);
> +	    queue.safe_push (top);
> +
> +	    // The edge bot -> outcome triggers the masking
> +	    const int m = 2*index_of (bot, blocks) + condition_index (cond);
> +	    while (!queue.is_empty())
> +	    {
> +		basic_block q = queue.pop ();
> +		/* q may have been processed & completed by being added to the
> +		   queue multiple times, so check that there is still work to
> +		   do before continuing. */
> +		if (bitmap_bit_p (marks, q->index))
> +		    continue;
> +
> +		outcomes succs = conditional_succs (q);
> +		if (!bitmap_bit_p (marks, succs.t->index))
> +		    continue;
> +		if (!bitmap_bit_p (marks, succs.f->index))
> +		    continue;
> +
> +		const int index = index_of (q, blocks);
> +		gcc_assert (index != -1);
> +		masks[m] |= gcov_type_unsigned (1) << index;
> +		bitmap_set_bit (marks, q->index);
> +
> +		for (edge e : q->preds)
> +		{
> +		    e = contract_edge_up (e);
> +		    if (!edge_conditional_p (e))
> +			continue;
> +		    if (e->flags & EDGE_DFS_BACK)
> +			continue;
> +		    if (bitmap_bit_p (marks, e->src->index))
> +			continue;
> +		    if (!bitmap_bit_p (expr, e->src->index))
> +			continue;
> +		    queue.safe_push (e->src);
> +		}
> +	    }
> +	}
> +    }
> +}
> +
> +/* Find the nodes reachable from p by following only (possibly contracted)
> +   condition edges dominated by p and ignore DFS back edges.  From a high level
> +   this is partitioning the CFG into subgraphs by removing all non-condition
> +   edges and selecting a single connected subgraph.  This creates a cut C = (G,
> +   G') where G is the returned explicitly by this function.
> +
> +   It is assumed that all paths from p go through q (q post-dominates p).  p
> +   must always be the first term in an expression and a condition node.
> +
> +   If |G| = 1 then this is a single term expression. If |G| > 1 then either
> +   this is a multi-term expression or the first block in the then/else block is
> +   a conditional expression as well.
> +
> +   Only nodes dominated by p is added - under optimization some blocks may be
> +   merged and multiple independent conditions may share the same outcome
> +   (making successors misidentified as a right operands), but true right-hand
> +   operands are always dominated by the first term.
> +
> +   The function outputs both a bitmap and a vector as both are useful to the
> +   caller. */
> +void
> +cond_reachable_from (basic_block p, basic_block q, sbitmap expr,
> +		     vec<basic_block>& out)
> +{
> +    out.safe_push (p);
> +    bitmap_set_bit (expr, p->index);
> +    for (unsigned pos = 0; pos < out.length (); pos++)
> +    {
> +	for (edge e : out[pos]->succs)
> +	{
> +	    basic_block dest = contract_edge (e)->dest;
> +	    if (dest == q)
> +		continue;
> +	    if (!dominated_by_p (CDI_DOMINATORS, dest, p))
> +		continue;
> +	    if (!block_conditional_p (dest))
> +		continue;
> +	    if (bitmap_bit_p (expr, dest->index))
> +		continue;
> +	    if (e->flags & EDGE_DFS_BACK)
> +		continue;
> +
> +	    bitmap_set_bit (expr, dest->index);
> +	    out.safe_push (dest);
> +	}
> +    }
> +}
> +
> +/* Find the neighborhood of the graph G = [blocks, blocks+n), the
> +   successors of nodes in G that are not also in G.  In the cut C = (G, G')
> +   these are the nodes in G' with incoming edges that cross the span. */
> +void
> +neighborhood (const vec<basic_block>& blocks, sbitmap G, vec<basic_block>& out)
> +{
> +    for (const basic_block b : blocks)
> +    {
> +	for (edge e : b->succs)
> +	{
> +	    basic_block dest = contract_edge (e)->dest;
> +	    if (bitmap_bit_p (G, dest->index))
> +		continue;
> +	    if (!out.contains (dest))
> +		out.safe_push (dest);
> +	}
> +    }
> +
> +    /* Fix the neighborhood by correcting edge splits to the outcome nodes. */
> +    for (unsigned i = 0; i != out.length (); i++)
> +    {
> +	basic_block prev = out[i];
> +	basic_block next = merge_split_outcome (prev);
> +	if (next->index != prev->index)
> +	{
> +	    bitmap_set_bit (G, prev->index);
> +	    out[i] = next;
> +	}
> +    }
> +}
> +
> +/* Find and isolate the expression starting at p.
> +
> +   Make a cut C = (G, G') following only condition edges.  G is a superset of
> +   the expression B, but the walk may include expressions from the then/else
> +   blocks if they start with conditions.  Only the subgraph B is the ancestor
> +   of *both* the then/else outcome, which means B is the intersection of the
> +   ancestors of the nodes in the neighborhood N(G). */
> +void
> +isolate_expression (conds_ctx &ctx, basic_block p, vec<basic_block>& out)
> +{
> +    sbitmap expr = ctx.G1;
> +    sbitmap reachable = ctx.G2;
> +    sbitmap ancestors = ctx.G3;
> +    bitmap_clear (expr);
> +    bitmap_clear (reachable);
> +
> +    vec<basic_block>& G = ctx.B1;
> +    vec<basic_block>& NG = ctx.B2;
> +    G.truncate (0);
> +    NG.truncate (0);
> +
> +    basic_block post = get_immediate_dominator (CDI_POST_DOMINATORS, p);
> +    cond_reachable_from (p, post, reachable, G);
> +    if (G.length () == 1)
> +    {
> +	out.safe_push (p);
> +	return;
> +    }
> +
> +    neighborhood (G, reachable, NG);
> +    bitmap_copy (expr, reachable);
> +
> +    for (const basic_block neighbor : NG)
> +    {
> +	bitmap_clear (ancestors);
> +	for (edge e : neighbor->preds)
> +	    ancestors_of (e->src, p, reachable, ancestors);
> +	bitmap_and (expr, expr, ancestors);
> +    }
> +
> +    for (const basic_block b : G)
> +	if (bitmap_bit_p (expr, b->index))
> +	    out.safe_push (b);
> +    out.sort (cmp_index_map, &ctx.index_map);
> +}
> +
> +/* Emit lhs = op1 <op> op2 on edges.  This emits non-atomic instructions and
> +   should only be used on the local accumulators. */
> +void
> +emit_bitwise_op (edge e, tree lhs, tree op1, tree_code op, tree op2)
> +{
> +    tree tmp;
> +    gassign *read;
> +    gassign *bitw;
> +    gimple *write;
> +
> +    tmp = make_temp_ssa_name (gcov_type_node, NULL, "__conditions_tmp");
> +    read = gimple_build_assign (tmp, op1);
> +    tmp = make_temp_ssa_name (gcov_type_node, NULL, "__conditions_tmp");
> +    bitw = gimple_build_assign (tmp, op, gimple_assign_lhs (read), op2);
> +    write = gimple_build_assign (lhs, gimple_assign_lhs (bitw));
> +
> +    gsi_insert_on_edge (e, read);
> +    gsi_insert_on_edge (e, bitw);
> +    gsi_insert_on_edge (e, write);
> +}
> +
> +/* Visitor for make_index_map. */
> +void
> +make_index_map_visit (basic_block b, vec<basic_block>& L, vec<int>& marks)
> +{
> +    if (marks[b->index])
> +	return;
> +
> +    for (edge e : b->succs)
> +	if (!(e->flags & EDGE_DFS_BACK))
> +	    make_index_map_visit (e->dest, L, marks);
> +
> +    marks[b->index] = 1;
> +    L.quick_push (b);
> +}
> +
> +/* Find a topological sorting of the blocks in a function so that left operands
> +   are before right operands including subexpressions.  Sorting on block index
> +   does not guarantee this property and the syntactical order of terms is very
> +   important to the condition coverage.  The sorting algorithm is from Cormen
> +   et al (2001) but with back-edges ignored and thus there is no need for
> +   temporary marks (for cycle detection).
> +
> +   It is important to select unvisited nodes in DFS order to ensure the
> +   roots/leading terms of boolean expressions are visited first (the other
> +   terms being covered by the recursive step), but the visiting order of
> +   individual boolean expressions carries no significance.
> +
> +   For the expression (a || (b && c) || d) the blocks should be [a b c d]. */
> +void
> +make_index_map (const vec<basic_block>& blocks, int max_index,
> +		vec<basic_block>& L, vec<int>& index_map)
> +{
> +    L.truncate (0);
> +    L.reserve (max_index);
> +
> +    /* Use of the output map as a temporary for tracking visited status. */
> +    index_map.truncate (0);
> +    index_map.safe_grow_cleared (max_index);
> +    for (const basic_block b : blocks)
> +	make_index_map_visit (b, L, index_map);
> +
> +    /* Insert canaries - if there are unreachable nodes (for example infinite
> +       loops) then the unreachable nodes should never be needed for comparison,
> +       and L.length () < max_index.  An index mapping should also never be
> +       recorded twice. */
> +    for (unsigned i = 0; i < index_map.length (); i++)
> +	index_map[i] = -1;
> +
> +    gcc_assert (blocks.length () == L.length ());
> +    L.reverse ();
> +    const unsigned nblocks = L.length ();
> +    for (unsigned i = 0; i < nblocks; i++)
> +    {
> +	gcc_assert (L[i]->index != -1);
> +	index_map[L[i]->index] = int (i);
> +    }
> +}
> +
> +/* Walk the CFG and collect conditionals.
> +
> +   1. Collect a candidate set G by walking from the root following all
> +      (contracted) condition edges.
> +   2. This creates a cut C = (G, G'); find the neighborhood N(G).
> +   3. For every node in N(G), follow the edges across the cut and collect all
> +      ancestors (that are also in G).
> +   4. The intersection of all these ancestor sets is the boolean expression B
> +      that starts in root.
> +
> +   Walking is not guaranteed to find nodes in the order of the expression, it
> +   might find (a || b) && c as [a c b], so the result must be sorted by the
> +   index map. */
> +const vec<basic_block>&
> +collect_conditions (conds_ctx& ctx, const basic_block block)
> +{
> +    vec<basic_block>& blocks = ctx.blocks;
> +    blocks.truncate (0);
> +
> +    if (bitmap_bit_p (ctx.marks, block->index))
> +	return blocks;
> +
> +    if (!block_conditional_p (block))
> +    {
> +	ctx.mark (block);
> +	return blocks;
> +    }
> +
> +    isolate_expression (ctx, block, blocks);
> +    ctx.mark (blocks);
> +
> +    if (blocks.length () > CONDITIONS_MAX_TERMS)
> +    {
> +	location_t loc = gimple_location (gsi_stmt (gsi_last_bb (block)));
> +	warning_at (loc, OPT_Wcoverage_too_many_conditions,
> +		    "Too many conditions (found %u); giving up coverage",
> +		    blocks.length ());
> +	blocks.truncate (0);
> +    }
> +    return blocks;
> +}
> +
> +/* Used for dfs_enumerate_from () to include all reachable nodes. */
> +bool
> +yes (const_basic_block, const void *)
> +{
> +    return true;
> +}
> +
> +}
> +
> +struct condcov {
> +    explicit condcov (unsigned nblocks) noexcept (true) : ctx (nblocks) {}
> +    auto_vec<int, 128> m_index;
> +    auto_vec<basic_block, 256> m_blocks;
> +    auto_vec<gcov_type_unsigned, 512> m_masks;
> +    conds_ctx ctx;
> +};
> +
> +unsigned
> +cov_length (const struct condcov* cov)
> +{
> +    if (cov->m_index.is_empty ())
> +	return 0;
> +    return cov->m_index.length () - 1;
> +}
> +
> +array_slice<basic_block>
> +cov_blocks (struct condcov* cov, unsigned n)
> +{
> +    if (n >= cov->m_index.length ())
> +	return array_slice<basic_block>::invalid ();
> +
> +    basic_block *begin = cov->m_blocks.begin () + cov->m_index[n];
> +    basic_block *end = cov->m_blocks.begin () + cov->m_index[n + 1];
> +    return array_slice<basic_block> (begin, end - begin);
> +}
> +
> +array_slice<gcov_type_unsigned>
> +cov_masks (struct condcov* cov, unsigned n)
> +{
> +    if (n >= cov->m_index.length ())
> +	return array_slice<gcov_type_unsigned>::invalid ();
> +
> +    gcov_type_unsigned *begin = cov->m_masks.begin () + 2*cov->m_index[n];
> +    gcov_type_unsigned *end = cov->m_masks.begin () + 2*cov->m_index[n + 1];
> +    return array_slice<gcov_type_unsigned> (begin, end - begin);
> +}
> +
> +void
> +cov_free (struct condcov* cov)
> +{
> +    delete cov;
> +}
> +
> +/* Condition coverage (MC/DC)
> +
> +   Algorithm
> +   ---------
> +   Whalen, Heimdahl, De Silva in "Efficient Test Coverage Measurement for
> +   MC/DC" describe an algorithm for modified condition/decision coverage based
> +   on AST analysis.  This algorithm analyses the control flow graph to analyze
> +   expressions and compute masking vectors, but is inspired by their marking
> +   functions for recording outcomes.  The individual phases are described in
> +   more detail closer to the implementation.
> +
> +   The CFG is traversed in DFS order.  It is important that the first basic
> +   block in an expression is the first one visited, but the order of
> +   independent expressions does not matter.  When the function terminates,
> +   every node in the dfs should have been processed and marked exactly once.
> +   If there are unreachable nodes they are ignored and not instrumented.
> +
> +   The CFG is broken up into segments between dominators.  This isn't strictly
> +   necessary, but since boolean expressions cannot cross dominators it makes
> +   for a nice way to introduce limits to searches.
> +
> +   The coverage only considers the positions, not the symbols, in a
> +   conditional, e.g. !A || (!B && A) is a 3-term conditional even though A
> +   appears twice.  Subexpressions have no effect on term ordering:
> +   (a && (b || (c && d)) || e) comes out as [a b c d e].
> +
> +   The output for gcov is a vector of pairs of unsigned integers, interpreted
> +   as bit-sets, where the bit index corresponds to the index of the condition
> +   in the expression.
> + */
> +struct condcov*
> +find_conditions (struct function *fn)
> +{
> +    record_loop_exits ();
> +    mark_dfs_back_edges (fn);
> +
> +    const bool have_dom = dom_info_available_p (fn, CDI_DOMINATORS);
> +    const bool have_post_dom = dom_info_available_p (fn, CDI_POST_DOMINATORS);
> +    if (!have_dom)
> +	calculate_dominance_info (CDI_DOMINATORS);
> +    if (!have_post_dom)
> +	calculate_dominance_info (CDI_POST_DOMINATORS);
> +
> +    const unsigned nblocks = n_basic_blocks_for_fn (fn);
> +    condcov *cov = new condcov (nblocks);
> +    conds_ctx& ctx = cov->ctx;
> +
> +    auto_vec<basic_block, 16> dfs;
> +    dfs.safe_grow (nblocks);
> +    const basic_block entry = ENTRY_BLOCK_PTR_FOR_FN (fn);
> +    const basic_block exit = ENTRY_BLOCK_PTR_FOR_FN (fn);
> +    int n = dfs_enumerate_from (entry, 0, yes, dfs.address (), nblocks, exit);
> +    dfs.truncate (n);
> +    make_index_map (dfs, nblocks, ctx.B1, ctx.index_map);
> +
> +    /* Visit all reachable nodes and collect conditions.  DFS order is
> +       important so the first node of a boolean expression is visited first
> +       (it will mark subsequent terms). */
> +    cov->m_index.safe_push (0);
> +    for (const basic_block b : dfs)
> +    {
> +	const vec<basic_block>& expr = collect_conditions (ctx, b);
> +	if (!expr.is_empty ())
> +	{
> +	    cov->m_blocks.safe_splice (expr);
> +	    cov->m_index.safe_push (cov->m_blocks.length ());
> +	}
> +    }
> +    gcc_assert (ctx.all_marked (dfs));
> +
> +    if (!have_dom)
> +	free_dominance_info (fn, CDI_DOMINATORS);
> +    if (!have_post_dom)
> +	free_dominance_info (fn, CDI_POST_DOMINATORS);
> +
> +    cov->m_masks.safe_grow_cleared (2 * cov->m_index.last());
> +    const unsigned length = cov_length (cov);
> +    for (unsigned i = 0; i < length; i++)
> +	masking_vectors (ctx, cov_blocks (cov, i), cov_masks (cov, i));
> +
> +    return cov;
> +}
> +
> +int
> +instrument_decisions (array_slice<basic_block> expr, unsigned condno,
> +		      tree *accu, gcov_type_unsigned *masks)
> +{
> +    /* Zero the local accumulators. */
> +    tree zero = build_int_cst (get_gcov_type (), 0);
> +    for (edge e : expr[0]->succs)
> +    {
> +	gsi_insert_on_edge (e, gimple_build_assign (accu[0], zero));
> +	gsi_insert_on_edge (e, gimple_build_assign (accu[1], zero));
> +    }
> +    /* Add instructions for updating the function-local accumulators.  */
> +    for (size_t i = 0; i < expr.size (); i++)
> +    {
> +	for (edge e : expr[i]->succs)
> +	{
> +	    if (!edge_conditional_p (e))
> +		continue;
> +
> +	    /* accu |= expr[i] */
> +	    const int k = condition_index (e->flags);
> +	    tree rhs = build_int_cst (gcov_type_node, 1ULL << i);
> +	    emit_bitwise_op (e, accu[k], accu[k], BIT_IOR_EXPR, rhs);
> +
> +	    if (masks[2*i + k] == 0)
> +		continue;
> +
> +	    /* accu &= mask[i] */
> +	    tree mask = build_int_cst (gcov_type_node, ~masks[2*i + k]);
> +	    for (int j = 0; j < 2; j++)
> +		emit_bitwise_op (e, accu[j], accu[j], BIT_AND_EXPR, mask);
> +	}
> +    }
> +
> +    const bool atomic = flag_profile_update == PROFILE_UPDATE_ATOMIC;
> +    const tree atomic_ior = builtin_decl_explicit
> +	(TYPE_PRECISION (gcov_type_node) > 32
> +	 ? BUILT_IN_ATOMIC_FETCH_OR_8
> +	 : BUILT_IN_ATOMIC_FETCH_OR_4);
> +
> +    /* Add instructions for flushing the local accumulators.
> +
> +       It is important that the flushes happen on on the outcome's incoming
> +       edges, otherwise flushes could be lost to exception handling.
> +
> +       void fn (int a)
> +       {
> +	   if (a)
> +	    fclose();
> +	   exit();
> +       }
> +
> +       Can yield the CFG:
> +       A
> +       |\
> +       | B
> +       |/
> +       e
> +
> +       This typically only happen in optimized builds, but gives linker errors
> +       because the counter is left as an undefined symbol. */
> +
> +    outcomes out = conditional_succs (expr.back ());
> +    const basic_block outcome_blocks[] = { out.t, out.t, out.f, out.f, };
> +    const int outcome[] = { 0, 1, 0, 1 };
> +    for (int i = 0; i < 4; i++)
> +    {
> +	const int k = outcome[i];
> +	for (edge e : outcome_blocks[i]->preds)
> +	{
> +	    /* The outcome may have been split and we want to check if the
> +	       edge is sourced from inside the expression, so contract it to
> +	       find the source conditional edge. */
> +	    e = contract_edge_up (e);
> +
> +	    /* Only instrument edges from inside the expression. Sometimes
> +	       complicated control flow (like sigsetjmp and gotos) add
> +	       predecessors that don't come from the boolean expression. */
> +	    if (index_of (e->src, expr) == -1)
> +		continue;
> +
> +	    tree ref = tree_coverage_counter_ref (GCOV_COUNTER_CONDS,
> +						  2*condno + k);
> +	    tree tmp = make_temp_ssa_name (gcov_type_node, NULL,
> +					   "__conditions_tmp");
> +	    if (atomic)
> +	    {
> +		tree relaxed = build_int_cst (integer_type_node,
> +					      MEMMODEL_RELAXED);
> +		ref = unshare_expr (ref);
> +		gassign *read = gimple_build_assign (tmp, accu[k]);
> +		gcall *flush = gimple_build_call (atomic_ior, 3,
> +						  build_addr (ref),
> +						  gimple_assign_lhs (read),
> +						  relaxed);
> +
> +		gsi_insert_on_edge (e, read);
> +		gsi_insert_on_edge (e, flush);
> +	    }
> +	    else
> +	    {
> +		gassign *read = gimple_build_assign (tmp, ref);
> +		tmp = gimple_assign_lhs (read);
> +		gsi_insert_on_edge (e, read);
> +		ref = unshare_expr (ref);
> +		emit_bitwise_op (e, ref, accu[k], BIT_IOR_EXPR, tmp);
> +	    }
> +	}
> +    }
> +    return expr.size ();
> +}
> +
> +#undef CONDITIONS_MAX_TERMS
> +#undef EDGE_CONDITION
> +
>  /* Do initialization work for the edge profiler.  */
>  
>  /* Add code:
> @@ -758,7 +1802,7 @@ tree_profiling (void)
>  	  thunk = true;
>  	  /* When generate profile, expand thunk to gimple so it can be
>  	     instrumented same way as other functions.  */
> -	  if (profile_arc_flag)
> +	  if (profile_arc_flag || profile_condition_flag)
>  	    expand_thunk (node, false, true);
>  	  /* Read cgraph profile but keep function as thunk at profile-use
>  	     time.  */
> @@ -803,7 +1847,7 @@ tree_profiling (void)
>    release_profile_file_filtering ();
>  
>    /* Drop pure/const flags from instrumented functions.  */
> -  if (profile_arc_flag || flag_test_coverage)
> +  if (profile_arc_flag || profile_condition_flag || flag_test_coverage)
>      FOR_EACH_DEFINED_FUNCTION (node)
>        {
>  	if (!gimple_has_body_p (node->decl)
> @@ -897,7 +1941,7 @@ pass_ipa_tree_profile::gate (function *)
>       disabled.  */
>    return (!in_lto_p && !flag_auto_profile
>  	  && (flag_branch_probabilities || flag_test_coverage
> -	      || profile_arc_flag));
> +	      || profile_arc_flag || profile_condition_flag));
>  }
>  
>  } // anon namespace
> diff --git a/libgcc/libgcov-merge.c b/libgcc/libgcov-merge.c
> index 89741f637e1..9e3e8ee5657 100644
> --- a/libgcc/libgcov-merge.c
> +++ b/libgcc/libgcov-merge.c
> @@ -33,6 +33,11 @@ void __gcov_merge_add (gcov_type *counters  __attribute__ ((unused)),
>                         unsigned n_counters __attribute__ ((unused))) {}
>  #endif
>  
> +#ifdef L_gcov_merge_ior
> +void __gcov_merge_ior (gcov_type *counters  __attribute__ ((unused)),
> +		       unsigned n_counters __attribute__ ((unused))) {}
> +#endif
> +
>  #ifdef L_gcov_merge_topn
>  void __gcov_merge_topn (gcov_type *counters  __attribute__ ((unused)),
>  			unsigned n_counters __attribute__ ((unused))) {}

Gentle ping. I have a tuned the summary output slightly (decisions covered ->
condition outcomes covered) already.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: Ping [PATCH] Add condition coverage profiling
  2022-10-25  6:33 ` Ping " Jørgen Kvalsvik
@ 2022-10-27 10:34   ` Martin Liška
  2022-11-02  6:16   ` Jørgen Kvalsvik
  1 sibling, 0 replies; 39+ messages in thread
From: Martin Liška @ 2022-10-27 10:34 UTC (permalink / raw)
  To: Jørgen Kvalsvik, gcc-patches

On 10/25/22 08:33, Jørgen Kvalsvik wrote:
> Gentle ping. I have a tuned the summary output slightly (decisions covered ->
> condition outcomes covered) already.

Sorry for a small delay, I'm working on it.

One general issue I noticed is you use an invalid coding style, where you use 4 spaces
for each level. Plus see the indentation for '{', '}':

diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
index 0b537d64d97..b661ed92045 100644
--- a/gcc/tree-profile.cc
+++ b/gcc/tree-profile.cc
@@ -553,11 +553,12 @@ masking_vectors (conds_ctx& ctx, array_slice<basic_block> blocks,
      body[0] = body.pop ();
  
      for (const basic_block b : body)
-    {
+      {
  	for (edge e1 : b->preds)
-	for (edge e2 : b->preds)
-	{
-	    const basic_block top = e1->src;
+	  for (edge e2 : b->preds)
+	    {
+	      const basic_block top = e1->src;
+...
  	    const basic_block bot = e2->src;
  	    const unsigned cond = e1->flags & e2->flags & (EDGE_CONDITION);
  
Link: https://gcc.gnu.org/codingconventions.html

What editor do you use?

Cheers,
Martin

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Ping [PATCH] Add condition coverage profiling
  2022-10-25  6:33 ` Ping " Jørgen Kvalsvik
  2022-10-27 10:34   ` Martin Liška
@ 2022-11-02  6:16   ` Jørgen Kvalsvik
  2022-11-09 20:08     ` [Ping 2][PATCH] " Jørgen Kvalsvik
  2022-11-10 14:19     ` Ping [PATCH] " Martin Liška
  1 sibling, 2 replies; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-11-02  6:16 UTC (permalink / raw)
  To: gcc-patches

On 25/10/2022 08:33, Jørgen Kvalsvik wrote:
> On 12/10/2022 12:16, Jørgen Kvalsvik wrote:
>> This patch adds support in gcc+gcov for modified condition/decision
>> coverage (MC/DC) with the -fprofile-conditions flag. MC/DC is a type of
>> test/code coverage and it is particularly important in the avation and
>> automotive industries for safety-critical applications. MC/DC it is
>> required for or recommended by:
>>
>>     * DO-178C for the most critical software (Level A) in avionics
>>     * IEC 61508 for SIL 4
>>     * ISO 26262-6 for ASIL D
>>
>> From the SQLite webpage:
>>
>>     Two methods of measuring test coverage were described above:
>>     "statement" and "branch" coverage. There are many other test
>>     coverage metrics besides these two. Another popular metric is
>>     "Modified Condition/Decision Coverage" or MC/DC. Wikipedia defines
>>     MC/DC as follows:
>>
>>         * Each decision tries every possible outcome.
>>         * Each condition in a decision takes on every possible outcome.
>>         * Each entry and exit point is invoked.
>>         * Each condition in a decision is shown to independently affect
>>           the outcome of the decision.
>>
>>     In the C programming language where && and || are "short-circuit"
>>     operators, MC/DC and branch coverage are very nearly the same thing.
>>     The primary difference is in boolean vector tests. One can test for
>>     any of several bits in bit-vector and still obtain 100% branch test
>>     coverage even though the second element of MC/DC - the requirement
>>     that each condition in a decision take on every possible outcome -
>>     might not be satisfied.
>>
>>     https://sqlite.org/testing.html#mcdc
>>
>> Wahlen, Heimdahl, and De Silva "Efficient Test Coverage Measurement for
>> MC/DC" describes an algorithm for adding instrumentation by carrying
>> over information from the AST, but my algorithm analyses the the control
>> flow graph to instrument for coverage. This has the benefit of being
>> programming language independent and faithful to compiler decisions
>> and transformations, although I have only tested it on constructs in C
>> and C++, see testsuite/gcc.misc-tests and testsuite/g++.dg.
>>
>> Like Wahlen et al this implementation records coverage in fixed-size
>> bitsets which gcov knows how to interpret. This is very fast, but
>> introduces a limit on the number of terms in a single boolean
>> expression, the number of bits in a gcov_unsigned_type (which is
>> typedef'd to uint64_t), so for most practical purposes this would be
>> acceptable. This limitation is in the implementation and not the
>> algorithm, so support for more conditions can be added by also
>> introducing arbitrary-sized bitsets.
>>
>> For space overhead, the instrumentation needs two accumulators
>> (gcov_unsigned_type) per condition in the program which will be written
>> to the gcov file. In addition, every function gets a pair of local
>> accumulators, but these accmulators are reused between conditions in the
>> same function.
>>
>> For time overhead, there is a zeroing of the local accumulators for
>> every condition and one or two bitwise operation on every edge taken in
>> the an expression.
>>
>> In action it looks pretty similar to the branch coverage. The -g short
>> opt carries no significance, but was chosen because it was an available
>> option with the upper-case free too.
>>
>> gcov --conditions:
>>
>>         3:   17:void fn (int a, int b, int c, int d) {
>>         3:   18:    if ((a && (b || c)) && d)
>> condition outcomes covered 3/8
>> condition  0 not covered (true false)
>> condition  1 not covered (true)
>> condition  2 not covered (true)
>> condition  3 not covered (true)
>>         1:   19:        x = 1;
>>         -:   20:    else
>>         2:   21:        x = 2;
>>         3:   22:}
>>
>> gcov --conditions --json-format:
>>
>>     "conditions": [
>>         {
>>             "not_covered_false": [
>>                 0
>>             ],
>>             "count": 8,
>>             "covered": 3,
>>             "not_covered_true": [
>>                 0,
>>                 1,
>>                 2,
>>                 3
>>             ]
>>         }
>>     ],
>>
>> Some expressions, mostly those without else-blocks, are effectively
>> "rewritten" in the CFG construction making the algorithm unable to
>> distinguish them:
>>
>> and.c:
>>
>>     if (a && b && c)
>>         x = 1;
>>
>> ifs.c:
>>
>>     if (a)
>>         if (b)
>>             if (c)
>>                 x = 1;
>>
>> gcc will build the same graph for both these programs, and gcov will
>> report boths as 3-term expressions. It is vital that it is not
>> interpreted the other way around (which is consistent with the shape of
>> the graph) because otherwise the masking would be wrong for the and.c
>> program which is a more severe error. While surprising, users would
>> probably expect some minor rewriting of semantically-identical
>> expressions.
>>
>> and.c.gcov:
>>     #####:    2:    if (a && b && c)
>> decisions covered 6/6
>>     #####:    3:        x = 1;
>>
>> ifs.c.gcov:
>>     #####:    2:    if (a)
>>     #####:    3:        if (b)
>>     #####:    4:            if (c)
>>     #####:    5:                x = 1;
>> condition outcomes covered 6/6
>>
>> Adding else clauses alters the program (ifs.c can have 3 elses, and.c
>> only 1) and coverage becomes less surprising
>>
>> ifs.c.gcov:
>>     #####:    2:    if (a)
>> condition outcomes covered 2/2
>>     #####:    4:    {
>>     #####:    4:        if (b)
>> condition outcomes covered 2/2
>>               5:        {
>>     #####:    6:            if (c)
>> condition outcomes covered 2/2
>>     #####:    7:                x = 1;
>>     #####:    8:        }
>>     #####:    9:        else
>>     #####:   10:            x = 2;
>>     #####:   11:    }
>>     #####:   12:    else
>>     #####:   13:        x = 3;
>>
>> Since the algorithm works on CFGs, it cannot detect some ternary
>> operator introduced conditionals. For example, int x = a ? 0 : 1 in
>> gimple becomes _x = (_a == 0). From source you would expect coverage,
>> but it gets neither branch nor condition coverage. For completeness, it
>> could be achieved by scanning all gimple statements for such
>> comparisons, and insert an extra instruction for recording the outcome.
>>
>> The test suite contains a lot of small programs functions. Some of these
>> were designed by hand to test for specific behaviours and graph shapes,
>> and some are previously-failed test cases in other programs adapted into
>> the test suite.
>>
>> Alternative author email: Jørgen Kvalsvik <j@lambda.is>
>>
>> gcc/ChangeLog:
>>
>> 	* builtins.cc (expand_builtin_fork_or_exec): Check
>> 	profile_condition_flag.
>> 	* collect2.cc (main): Add -fno-profile-conditions to OBSTACK.
>> 	* common.opt: Add new options -fprofile-conditions and
>> 	* doc/gcov.texi: Add --conditions documentation.
>> 	* doc/invoke.texi: Add -fprofile-conditions documentation.
>> 	* gcc.cc: Link gcov on -fprofile-conditions.
>> 	* gcov-counter.def (GCOV_COUNTER_CONDS): New.
>> 	* gcov-dump.cc (tag_conditions): New.
>> 	* gcov-io.h (GCOV_TAG_CONDS): New.
>> 	(GCOV_TAG_CONDS_LENGTH): Likewise.
>> 	(GCOV_TAG_CONDS_NUM): Likewise.
>> 	* gcov.cc (class condition_info): New.
>> 	(condition_info::condition_info): New.
>> 	(condition_info::popcount): New.
>> 	(struct coverage_info): New.
>> 	(add_condition_counts): New.
>> 	(output_conditions): New.
>> 	(print_usage): Add -g, --conditions.
>> 	(process_args): Likewise.
>> 	(output_intermediate_json_line): Output conditions.
>> 	(read_graph_file): Read conditions counters.
>> 	(read_count_file): Read conditions counters.
>> 	(file_summary): Print conditions.
>> 	(accumulate_line_info): Accumulate conditions.
>> 	(output_line_details): Print conditions.
>> 	* ipa-inline.cc (can_early_inline_edge_p): Check
>> 	profile_condition_flag.
>> 	* ipa-split.cc (pass_split_functions::gate): Likewise.
>> 	* passes.cc (finish_optimization_passes): Likewise.
>> 	* profile.cc (find_conditions): New declaration.
>> 	(cov_length): Likewise.
>> 	(cov_blocks): Likewise.
>> 	(cov_masks): Likewise.
>> 	(cov_free): Likewise.
>> 	(instrument_decisions): New.
>> 	(read_thunk_profile): Control output to file.
>> 	(branch_prob): Call find_conditions, instrument_decisions.
>> 	(init_branch_prob): Add total_num_conds.
>> 	(end_branch_prob): Likewise.
>> 	* tree-profile.cc (struct conds_ctx): New.
>> 	(CONDITIONS_MAX_TERMS): New.
>> 	(EDGE_CONDITION): New.
>> 	(cmp_index_map): New.
>> 	(index_of): New.
>> 	(block_conditional_p): New.
>> 	(edge_conditional_p): New.
>> 	(single): New.
>> 	(single_edge): New.
>> 	(contract_edge): New.
>> 	(contract_edge_up): New.
>> 	(merge_split_outcome): New.
>> 	(ancestors_of): New.
>> 	(struct outcomes): New.
>> 	(conditional_succs): New.
>> 	(condition_index): New.
>> 	(masking_vectors): New.
>> 	(cond_reachable_from): New.
>> 	(neighborhood): New.
>> 	(isolate_expression): New.
>> 	(emit_bitwise_op): New.
>> 	(make_index_map_visit): New.
>> 	(make_index_map): New.
>> 	(collect_conditions): New.
>> 	(yes): New.
>> 	(struct condcov): New.
>> 	(cov_length): New.
>> 	(cov_blocks): New.
>> 	(cov_masks): New.
>> 	(cov_free): New.
>> 	(find_conditions): New.
>> 	(instrument_decisions): New.
>> 	(tree_profiling): Check profile_condition_flag.
>> 	(pass_ipa_tree_profile::gate): Likewise.
>>
>> libgcc/ChangeLog:
>>
>> 	* libgcov-merge.c (__gcov_merge_ior): New dummy function.
>>
>> gcc/testsuite/ChangeLog:
>>
>> 	* lib/gcov.exp: Add condition coverage test function.
>> 	* g++.dg/gcov/gcov-18.C: New test.
>> 	* gcc.misc-tests/gcov-19.c: New test.
>> 	* gcc.misc-tests/gcov-20.c: New test.
>> 	* gcc.misc-tests/gcov-21.c: New test.
>> ---
>>  gcc/builtins.cc                        |    2 +-
>>  gcc/collect2.cc                        |    7 +-
>>  gcc/common.opt                         |    8 +
>>  gcc/doc/gcov.texi                      |   36 +
>>  gcc/doc/invoke.texi                    |   19 +
>>  gcc/gcc.cc                             |    4 +-
>>  gcc/gcov-counter.def                   |    3 +
>>  gcc/gcov-dump.cc                       |   24 +
>>  gcc/gcov-io.h                          |    3 +
>>  gcc/gcov.cc                            |  200 +++-
>>  gcc/ipa-inline.cc                      |    2 +-
>>  gcc/ipa-split.cc                       |    3 +-
>>  gcc/passes.cc                          |    3 +-
>>  gcc/profile.cc                         |   84 +-
>>  gcc/testsuite/g++.dg/gcov/gcov-18.C    |  234 +++++
>>  gcc/testsuite/gcc.misc-tests/gcov-19.c | 1250 ++++++++++++++++++++++++
>>  gcc/testsuite/gcc.misc-tests/gcov-20.c |   22 +
>>  gcc/testsuite/gcc.misc-tests/gcov-21.c |   16 +
>>  gcc/testsuite/lib/gcov.exp             |  191 +++-
>>  gcc/tree-profile.cc                    | 1050 +++++++++++++++++++-
>>  libgcc/libgcov-merge.c                 |    5 +
>>  21 files changed, 3138 insertions(+), 28 deletions(-)
>>  create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-18.C
>>  create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-19.c
>>  create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-20.c
>>  create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-21.c
>>
>> diff --git a/gcc/builtins.cc b/gcc/builtins.cc
>> index 5f319b28030..972ae7e2040 100644
>> --- a/gcc/builtins.cc
>> +++ b/gcc/builtins.cc
>> @@ -5884,7 +5884,7 @@ expand_builtin_fork_or_exec (tree fn, tree exp, rtx target, int ignore)
>>    tree call;
>>  
>>    /* If we are not profiling, just call the function.  */
>> -  if (!profile_arc_flag)
>> +  if (!profile_arc_flag && !profile_condition_flag)
>>      return NULL_RTX;
>>  
>>    /* Otherwise call the wrapper.  This should be equivalent for the rest of
>> diff --git a/gcc/collect2.cc b/gcc/collect2.cc
>> index d81c7f28f16..0cd8bf4a3a3 100644
>> --- a/gcc/collect2.cc
>> +++ b/gcc/collect2.cc
>> @@ -1032,9 +1032,9 @@ main (int argc, char **argv)
>>        lto_mode = LTO_MODE_LTO;
>>    }
>>  
>> -  /* -fno-profile-arcs -fno-test-coverage -fno-branch-probabilities
>> -     -fno-exceptions -w -fno-whole-program */
>> -  num_c_args += 6;
>> +  /* -fno-profile-arcs -fno-profile-conditions -fno-test-coverage
>> +     -fno-branch-probabilities -fno-exceptions -w -fno-whole-program */
>> +  num_c_args += 7;
>>  
>>    c_argv = XCNEWVEC (char *, num_c_args);
>>    c_ptr = CONST_CAST2 (const char **, char **, c_argv);
>> @@ -1230,6 +1230,7 @@ main (int argc, char **argv)
>>      }
>>    obstack_free (&temporary_obstack, temporary_firstobj);
>>    *c_ptr++ = "-fno-profile-arcs";
>> +  *c_ptr++ = "-fno-profile-conditions";
>>    *c_ptr++ = "-fno-test-coverage";
>>    *c_ptr++ = "-fno-branch-probabilities";
>>    *c_ptr++ = "-fno-exceptions";
>> diff --git a/gcc/common.opt b/gcc/common.opt
>> index bce3e514f65..5d0682e283a 100644
>> --- a/gcc/common.opt
>> +++ b/gcc/common.opt
>> @@ -859,6 +859,10 @@ Wcoverage-invalid-line-number
>>  Common Var(warn_coverage_invalid_linenum) Init(1) Warning
>>  Warn in case a function ends earlier than it begins due to an invalid linenum macros.
>>  
>> +Wcoverage-too-many-conditions
>> +Common Var(warn_too_many_conditions) Init(1) Warning
>> +Warn when a conditional has too many terms and coverage gives up.
>> +
>>  Wmissing-profile
>>  Common Var(warn_missing_profile) Init(1) Warning
>>  Warn in case profiles in -fprofile-use do not exist.
>> @@ -2318,6 +2322,10 @@ fprofile-arcs
>>  Common Var(profile_arc_flag)
>>  Insert arc-based program profiling code.
>>  
>> +fprofile-conditions
>> +Common Var(profile_condition_flag)
>> +Insert condition coverage profiling code.
>> +
>>  fprofile-dir=
>>  Common Joined RejectNegative Var(profile_data_prefix)
>>  Set the top-level directory for storing the profile data.
>> diff --git a/gcc/doc/gcov.texi b/gcc/doc/gcov.texi
>> index a1f7d26e610..7f2c0d00d94 100644
>> --- a/gcc/doc/gcov.texi
>> +++ b/gcc/doc/gcov.texi
>> @@ -124,6 +124,7 @@ gcov [@option{-v}|@option{--version}] [@option{-h}|@option{--help}]
>>       [@option{-a}|@option{--all-blocks}]
>>       [@option{-b}|@option{--branch-probabilities}]
>>       [@option{-c}|@option{--branch-counts}]
>> +     [@option{-g}|@option{--conditions}]
>>       [@option{-d}|@option{--display-progress}]
>>       [@option{-f}|@option{--function-summaries}]
>>       [@option{-j}|@option{--json-format}]
>> @@ -169,6 +170,13 @@ be shown, unless the @option{-u} option is given.
>>  Write branch frequencies as the number of branches taken, rather than
>>  the percentage of branches taken.
>>  
>> +@item -g
>> +@itemx --conditions
>> +Write condition coverage to the output file, and write condition summary info
>> +to the standard output.  This option allows you to see if the conditions in
>> +your program at least once had an independent effect on the outcome of the
>> +boolean expression (modified condition/decision coverage).
>> +
>>  @item -d
>>  @itemx --display-progress
>>  Display the progress on the standard output.
>> @@ -293,6 +301,7 @@ Each @var{line} has the following form:
>>  @{
>>    "branches": ["$branch"],
>>    "count": 2,
>> +  "conditions": ["$condition"],
>>    "line_number": 15,
>>    "unexecuted_block": false,
>>    "function_name": "foo",
>> @@ -341,6 +350,33 @@ Fields of the @var{branch} element have following semantics:
>>  @var{throw}: true when the branch is an exceptional branch
>>  @end itemize
>>  
>> +Each @var{condition} element have the following semantics:
>> +
>> +@smallexample
>> +@{
>> +  "count": 4,
>> +  "covered": 2,
>> +  "not_covered_false": [],
>> +  "not_covered_true": [0, 1],
>> +@}
>> +@end smallexample
>> +
>> +Fields of the @var{condition} element have following semantics:
>> +
>> +@itemize @bullet
>> +@item
>> +@var{count}: number of conditions in this expression
>> +
>> +@item
>> +@var{covered}: number of covered conditions in this expression
>> +
>> +@item
>> +@var{not_covered_true}: terms, by index, not seen as true in this expression
>> +
>> +@item
>> +@var{not_covered_false}: terms, by index, not seen as false in this expression
>> +@end itemize
>> +
>>  @item -H
>>  @itemx --human-readable
>>  Write counts in human readable format (like 24.6k).
>> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
>> index a9ecc4426a4..62d48df369d 100644
>> --- a/gcc/doc/invoke.texi
>> +++ b/gcc/doc/invoke.texi
>> @@ -614,6 +614,7 @@ Objective-C and Objective-C++ Dialects}.
>>  @item Program Instrumentation Options
>>  @xref{Instrumentation Options,,Program Instrumentation Options}.
>>  @gccoptlist{-p  -pg  -fprofile-arcs  --coverage  -ftest-coverage @gol
>> +-fprofile-conditions @gol
>>  -fprofile-abs-path @gol
>>  -fprofile-dir=@var{path}  -fprofile-generate  -fprofile-generate=@var{path} @gol
>>  -fprofile-info-section  -fprofile-info-section=@var{name} @gol
>> @@ -6140,6 +6141,13 @@ error.  @option{-Wno-coverage-invalid-line-number} can be used to disable the
>>  warning or @option{-Wno-error=coverage-invalid-line-number} can be used to
>>  disable the error.
>>  
>> +@item -Wno-coverage-too-many-conditions
>> +@opindex Wno-coverage-too-many-conditions
>> +@opindex Wcoverage-too-many-conditions
>> +Warn in case a condition have too many terms and GCC gives up coverage.
>> +Coverage is given up when there are more terms in the conditional than there
>> +are bits in a @code{gcov_type_unsigned}.  This warning is enabled by default.
>> +
>>  @item -Wno-cpp @r{(C, Objective-C, C++, Objective-C++ and Fortran only)}
>>  @opindex Wno-cpp
>>  @opindex Wcpp
>> @@ -15839,6 +15847,13 @@ E.g. @code{gcc a.c b.c -o binary} would generate @file{binary-a.gcda} and
>>  
>>  @xref{Cross-profiling}.
>>  
>> +@item -fprofile-conditions
>> +@opindex fprofile-conditions
>> +Add code so that program conditions are instrumented.  During execution the
>> +program records what terms in a conditional contributes to a decision.  The
>> +data may be used to verify that all terms in a booleans are tested and have an
>> +effect on the outcome of a condition.
>> +
>>  @cindex @command{gcov}
>>  @item --coverage
>>  @opindex coverage
>> @@ -15899,6 +15914,10 @@ executed.  When an arc is the only exit or only entrance to a block, the
>>  instrumentation code can be added to the block; otherwise, a new basic
>>  block must be created to hold the instrumentation code.
>>  
>> +With @option{-fprofile-conditions}, for each conditional in your program GCC
>> +creates a bitset and records the boolean values that have an independent effect
>> +on the outcome of that expression.
>> +
>>  @need 2000
>>  @item -ftest-coverage
>>  @opindex ftest-coverage
>> diff --git a/gcc/gcc.cc b/gcc/gcc.cc
>> index afb23cd07b0..601e48377de 100644
>> --- a/gcc/gcc.cc
>> +++ b/gcc/gcc.cc
>> @@ -1145,7 +1145,7 @@ proper position among the other output files.  */
>>  	%:include(libgomp.spec)%(link_gomp)}\
>>      %{fgnu-tm:%:include(libitm.spec)%(link_itm)}\
>>      %(mflib) " STACK_SPLIT_SPEC "\
>> -    %{fprofile-arcs|fprofile-generate*|coverage:-lgcov} " SANITIZER_SPEC " \
>> +    %{fprofile-arcs|fprofile-conditions|fprofile-generate*|coverage:-lgcov} " SANITIZER_SPEC " \
>>      %{!nostdlib:%{!r:%{!nodefaultlibs:%(link_ssp) %(link_gcc_c_sequence)}}}\
>>      %{!nostdlib:%{!r:%{!nostartfiles:%E}}} %{T*}  \n%(post_link) }}}}}}"
>>  #endif
>> @@ -1262,7 +1262,7 @@ static const char *cc1_options =
>>   %{!fsyntax-only:%{S:%W{o*}%{!o*:-o %w%b.s}}}\
>>   %{fsyntax-only:-o %j} %{-param*}\
>>   %{coverage:-fprofile-arcs -ftest-coverage}\
>> - %{fprofile-arcs|fprofile-generate*|coverage:\
>> + %{fprofile-arcs|fprofile-conditions|fprofile-generate*|coverage:\
>>     %{!fprofile-update=single:\
>>       %{pthread:-fprofile-update=prefer-atomic}}}";
>>  
>> diff --git a/gcc/gcov-counter.def b/gcc/gcov-counter.def
>> index 6d2182bd3db..96563a59a45 100644
>> --- a/gcc/gcov-counter.def
>> +++ b/gcc/gcov-counter.def
>> @@ -49,3 +49,6 @@ DEF_GCOV_COUNTER(GCOV_COUNTER_IOR, "ior", _ior)
>>  
>>  /* Time profile collecting first run of a function */
>>  DEF_GCOV_COUNTER(GCOV_TIME_PROFILER, "time_profiler", _time_profile)
>> +
>> +/* Conditions.  The counter is interpreted as a bit-set.  */
>> +DEF_GCOV_COUNTER(GCOV_COUNTER_CONDS, "conditions", _ior)
>> diff --git a/gcc/gcov-dump.cc b/gcc/gcov-dump.cc
>> index 03023bfb226..6dc1df6e3e1 100644
>> --- a/gcc/gcov-dump.cc
>> +++ b/gcc/gcov-dump.cc
>> @@ -38,6 +38,7 @@ static void print_version (void);
>>  static void tag_function (const char *, unsigned, int, unsigned);
>>  static void tag_blocks (const char *, unsigned, int, unsigned);
>>  static void tag_arcs (const char *, unsigned, int, unsigned);
>> +static void tag_conditions (const char *, unsigned, int, unsigned);
>>  static void tag_lines (const char *, unsigned, int, unsigned);
>>  static void tag_counters (const char *, unsigned, int, unsigned);
>>  static void tag_summary (const char *, unsigned, int, unsigned);
>> @@ -77,6 +78,7 @@ static const tag_format_t tag_table[] =
>>    {GCOV_TAG_FUNCTION, "FUNCTION", tag_function},
>>    {GCOV_TAG_BLOCKS, "BLOCKS", tag_blocks},
>>    {GCOV_TAG_ARCS, "ARCS", tag_arcs},
>> +  {GCOV_TAG_CONDS, "CONDITIONS", tag_conditions},
>>    {GCOV_TAG_LINES, "LINES", tag_lines},
>>    {GCOV_TAG_OBJECT_SUMMARY, "OBJECT_SUMMARY", tag_summary},
>>    {0, NULL, NULL}
>> @@ -392,6 +394,28 @@ tag_arcs (const char *filename ATTRIBUTE_UNUSED,
>>      }
>>  }
>>  
>> +static void
>> +tag_conditions (const char *filename ATTRIBUTE_UNUSED,
>> +		unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED,
>> +		unsigned depth)
>> +{
>> +  unsigned n_conditions = GCOV_TAG_CONDS_NUM (length);
>> +
>> +  printf (" %u conditionals", n_conditions);
>> +  if (flag_dump_contents)
>> +    {
>> +      for (unsigned ix = 0; ix != n_conditions; ix++)
>> +	{
>> +	  const unsigned blockno = gcov_read_unsigned ();
>> +	  const unsigned nterms = gcov_read_unsigned ();
>> +
>> +	  printf ("\n");
>> +	  print_prefix (filename, depth, gcov_position ());
>> +	  printf (VALUE_PADDING_PREFIX "block %u:", blockno);
>> +	  printf (" %u", nterms);
>> +	}
>> +    }
>> +}
>>  static void
>>  tag_lines (const char *filename ATTRIBUTE_UNUSED,
>>  	   unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED,
>> diff --git a/gcc/gcov-io.h b/gcc/gcov-io.h
>> index e91cd736556..a9b52cc6abc 100644
>> --- a/gcc/gcov-io.h
>> +++ b/gcc/gcov-io.h
>> @@ -261,6 +261,9 @@ typedef uint64_t gcov_type_unsigned;
>>  #define GCOV_TAG_ARCS		 ((gcov_unsigned_t)0x01430000)
>>  #define GCOV_TAG_ARCS_LENGTH(NUM)  (1 + (NUM) * 2 * GCOV_WORD_SIZE)
>>  #define GCOV_TAG_ARCS_NUM(LENGTH)  (((LENGTH / GCOV_WORD_SIZE) - 1) / 2)
>> +#define GCOV_TAG_CONDS             ((gcov_unsigned_t)0x01470000)
>> +#define GCOV_TAG_CONDS_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE)
>> +#define GCOV_TAG_CONDS_NUM(LENGTH) (((LENGTH) / GCOV_WORD_SIZE) / 2)
>>  #define GCOV_TAG_LINES		 ((gcov_unsigned_t)0x01450000)
>>  #define GCOV_TAG_COUNTER_BASE 	 ((gcov_unsigned_t)0x01a10000)
>>  #define GCOV_TAG_COUNTER_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE)
>> diff --git a/gcc/gcov.cc b/gcc/gcov.cc
>> index 9cf1071166f..bc2932e7890 100644
>> --- a/gcc/gcov.cc
>> +++ b/gcc/gcov.cc
>> @@ -79,6 +79,7 @@ using namespace std;
>>  class function_info;
>>  class block_info;
>>  class source_info;
>> +class condition_info;
>>  
>>  /* Describes an arc between two basic blocks.  */
>>  
>> @@ -132,6 +133,28 @@ public:
>>    vector<unsigned> lines;
>>  };
>>  
>> +class condition_info
>> +{
>> +public:
>> +  condition_info ();
>> +
>> +  int popcount () const;
>> +
>> +  gcov_type_unsigned truev;
>> +  gcov_type_unsigned falsev;
>> +
>> +  unsigned n_terms;
>> +};
>> +
>> +condition_info::condition_info (): truev (0), falsev (0), n_terms (0)
>> +{
>> +}
>> +
>> +int condition_info::popcount () const
>> +{
>> +    return __builtin_popcountll (truev) + __builtin_popcountll (falsev);
>> +}
>> +
>>  /* Describes a basic block. Contains lists of arcs to successor and
>>     predecessor blocks.  */
>>  
>> @@ -165,6 +188,8 @@ public:
>>    /* Block is a landing pad for longjmp or throw.  */
>>    unsigned is_nonlocal_return : 1;
>>  
>> +  condition_info conditions;
>> +
>>    vector<block_location_info> locations;
>>  
>>    struct
>> @@ -275,6 +300,8 @@ public:
>>    vector<block_info> blocks;
>>    unsigned blocks_executed;
>>  
>> +  vector<condition_info*> conditions;
>> +
>>    /* Raw arc coverage counts.  */
>>    vector<gcov_type> counts;
>>  
>> @@ -351,6 +378,9 @@ struct coverage_info
>>    int branches_executed;
>>    int branches_taken;
>>  
>> +  int conditions;
>> +  int conditions_covered;
>> +
>>    int calls;
>>    int calls_executed;
>>  
>> @@ -550,6 +580,10 @@ static int multiple_files = 0;
>>  
>>  static int flag_branches = 0;
>>  
>> +/* Output conditions (modified condition/decision coverage) */
>> +
>> +static int flag_conditions = 0;
>> +
>>  /* Show unconditional branches too.  */
>>  static int flag_unconditional = 0;
>>  
>> @@ -656,6 +690,7 @@ static int read_count_file (void);
>>  static void solve_flow_graph (function_info *);
>>  static void find_exception_blocks (function_info *);
>>  static void add_branch_counts (coverage_info *, const arc_info *);
>> +static void add_condition_counts (coverage_info *, const block_info *);
>>  static void add_line_counts (coverage_info *, function_info *);
>>  static void executed_summary (unsigned, unsigned);
>>  static void function_summary (const coverage_info *);
>> @@ -664,6 +699,7 @@ static const char *format_gcov (gcov_type, gcov_type, int);
>>  static void accumulate_line_counts (source_info *);
>>  static void output_gcov_file (const char *, source_info *);
>>  static int output_branch_count (FILE *, int, const arc_info *);
>> +static void output_conditions (FILE *, const block_info *);
>>  static void output_lines (FILE *, const source_info *);
>>  static string make_gcov_file_name (const char *, const char *);
>>  static char *mangle_name (const char *);
>> @@ -928,6 +964,7 @@ print_usage (int error_p)
>>    fnotice (file, "  -b, --branch-probabilities      Include branch probabilities in output\n");
>>    fnotice (file, "  -c, --branch-counts             Output counts of branches taken\n\
>>                                      rather than percentages\n");
>> +  fnotice (file, "  -g, --conditions                Include condition/decision coverage in output\n");
>>    fnotice (file, "  -d, --display-progress          Display progress information\n");
>>    fnotice (file, "  -D, --debug			    Display debugging dumps\n");
>>    fnotice (file, "  -f, --function-summaries        Output summaries for each function\n");
>> @@ -980,6 +1017,7 @@ static const struct option options[] =
>>    { "all-blocks",           no_argument,       NULL, 'a' },
>>    { "branch-probabilities", no_argument,       NULL, 'b' },
>>    { "branch-counts",        no_argument,       NULL, 'c' },
>> +  { "conditions",           no_argument,       NULL, 'g' },
>>    { "json-format",	    no_argument,       NULL, 'j' },
>>    { "human-readable",	    no_argument,       NULL, 'H' },
>>    { "no-output",            no_argument,       NULL, 'n' },
>> @@ -1008,7 +1046,7 @@ process_args (int argc, char **argv)
>>  {
>>    int opt;
>>  
>> -  const char *opts = "abcdDfhHijklmno:pqrs:tuvwx";
>> +  const char *opts = "abcdDfghHijklmno:pqrs:tuvwx";
>>    while ((opt = getopt_long (argc, argv, opts, options, NULL)) != -1)
>>      {
>>        switch (opt)
>> @@ -1025,6 +1063,9 @@ process_args (int argc, char **argv)
>>  	case 'f':
>>  	  flag_function_summary = 1;
>>  	  break;
>> +	case 'g':
>> +	  flag_conditions = 1;
>> +	  break;
>>  	case 'h':
>>  	  print_usage (false);
>>  	  /* print_usage will exit.  */
>> @@ -1132,6 +1173,45 @@ output_intermediate_json_line (json::array *object,
>>  	  }
>>        }
>>  
>> +  json::array *conditions = new json::array ();
>> +  lineo->set ("conditions", conditions);
>> +  if (flag_conditions)
>> +  {
>> +    vector<block_info *>::const_iterator it;
>> +    for (it = line->blocks.begin (); it != line->blocks.end (); it++)
>> +      {
>> +	const condition_info& info = (*it)->conditions;
>> +	if (info.n_terms == 0)
>> +	    continue;
>> +
>> +	const int count = 2 * info.n_terms;
>> +	const int covered = info.popcount ();
>> +
>> +	json::object *cond = new json::object ();
>> +	cond->set ("count", new json::integer_number (count));
>> +	cond->set ("covered", new json::integer_number (covered));
>> +
>> +	json::array *mtrue = new json::array ();
>> +	json::array *mfalse = new json::array ();
>> +	cond->set ("not_covered_true", mtrue);
>> +	cond->set ("not_covered_false", mfalse);
>> +
>> +	if (count != covered)
>> +	  {
>> +	    for (unsigned i = 0; i < info.n_terms; i++)
>> +	      {
>> +		gcov_type_unsigned index = 1;
>> +		index <<= i;
>> +		if (!(index & info.truev))
>> +		    mtrue->append (new json::integer_number (i));
>> +		if (!(index & info.falsev))
>> +		    mfalse->append (new json::integer_number (i));
>> +	      }
>> +	  }
>> +	conditions->append (cond);
>> +      }
>> +  }
>> +
>>    object->append (lineo);
>>  }
>>  
>> @@ -1956,6 +2036,28 @@ read_graph_file (void)
>>  		  }
>>  	    }
>>  	}
>> +      else if (fn && tag == GCOV_TAG_CONDS)
>> +	{
>> +	  unsigned num_dests = GCOV_TAG_CONDS_NUM (length);
>> +
>> +	  if (!fn->conditions.empty ())
>> +	    fnotice (stderr, "%s:already seen conditions for '%s'\n",
>> +		     bbg_file_name, fn->get_name ());
>> +	  else
>> +	    fn->conditions.resize (num_dests);
>> +
>> +	  for (unsigned i = 0; i < num_dests; ++i)
>> +	    {
>> +	      unsigned idx = gcov_read_unsigned ();
>> +
>> +	      if (idx >= fn->blocks.size ())
>> +		goto corrupt;
>> +
>> +	      condition_info *info = &fn->blocks[idx].conditions;
>> +	      info->n_terms = gcov_read_unsigned ();
>> +	      fn->conditions[i] = info;
>> +	    }
>> +	}
>>        else if (fn && tag == GCOV_TAG_LINES)
>>  	{
>>  	  unsigned blockno = gcov_read_unsigned ();
>> @@ -2086,11 +2188,26 @@ read_count_file (void)
>>  	      goto cleanup;
>>  	    }
>>  	}
>> -      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn)
>> +      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_CONDS) && fn)
>>  	{
>> +	  length = abs (read_length);
>> +	  if (length != GCOV_TAG_COUNTER_LENGTH (2 * fn->conditions.size ()))
>> +	      goto mismatch;
>> +
>> +	  if (read_length > 0)
>> +	    {
>> +	      for (ix = 0; ix != fn->conditions.size (); ix++)
>> +		{
>> +		  fn->conditions[ix]->truev  |= gcov_read_counter ();
>> +		  fn->conditions[ix]->falsev |= gcov_read_counter ();
>> +		}
>> +	    }
>> +	}
>> +      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn)
>> +      {
>>  	  length = abs (read_length);
>>  	  if (length != GCOV_TAG_COUNTER_LENGTH (fn->counts.size ()))
>> -	    goto mismatch;
>> +	      goto mismatch;
>>  
>>  	  if (read_length > 0)
>>  	    for (ix = 0; ix != fn->counts.size (); ix++)
>> @@ -2430,6 +2547,13 @@ add_branch_counts (coverage_info *coverage, const arc_info *arc)
>>      }
>>  }
>>  
>> +static void
>> +add_condition_counts (coverage_info *coverage, const block_info *block)
>> +{
>> +  coverage->conditions += 2 * block->conditions.n_terms;
>> +  coverage->conditions_covered += block->conditions.popcount ();
>> +}
>> +
>>  /* Format COUNT, if flag_human_readable_numbers is set, return it human
>>     readable format.  */
>>  
>> @@ -2533,6 +2657,18 @@ file_summary (const coverage_info *coverage)
>>  		 coverage->calls);
>>        else
>>  	fnotice (stdout, "No calls\n");
>> +
>> +    }
>> +
>> +  if (flag_conditions)
>> +    {
>> +      if (coverage->conditions)
>> +	fnotice (stdout, "Decisions covered:%s of %d\n",
>> +		 format_gcov (coverage->conditions_covered,
>> +			      coverage->conditions, 2),
>> +		 coverage->conditions);
>> +      else
>> +	fnotice (stdout, "No decisions\n");
>>      }
>>  }
>>  
>> @@ -2767,6 +2903,12 @@ static void accumulate_line_info (line_info *line, source_info *src,
>>  	 it != line->branches.end (); it++)
>>        add_branch_counts (&src->coverage, *it);
>>  
>> +  if (add_coverage)
>> +    for (vector<block_info *>::iterator it = line->blocks.begin ();
>> +	 it != line->blocks.end (); it++)
>> +      add_condition_counts (&src->coverage, *it);
>> +
>> +
>>    if (!line->blocks.empty ())
>>      {
>>        /* The user expects the line count to be the number of times
>> @@ -2868,6 +3010,33 @@ accumulate_line_counts (source_info *src)
>>        }
>>  }
>>  
>> +static void
>> +output_conditions (FILE *gcov_file, const block_info *binfo)
>> +{
>> +    const condition_info& info = binfo->conditions;
>> +    if (info.n_terms == 0)
>> +	return;
>> +
>> +    const int expected = 2 * info.n_terms;
>> +    const int got = info.popcount ();
>> +
>> +    fnotice (gcov_file, "condition outcomes covered %d/%d\n", got, expected);
>> +    if (expected == got)
>> +	return;
>> +
>> +    for (unsigned i = 0; i < info.n_terms; i++)
>> +    {
>> +	gcov_type_unsigned index = 1;
>> +	index <<= i;
>> +	if ((index & info.truev & info.falsev))
>> +	    continue;
>> +
>> +	const char *t = (index & info.truev) ? "" : "true";
>> +	const char *f = (index & info.falsev) ? "" : " false";
>> +	fnotice (gcov_file, "condition %2u not covered (%s%s)\n", i, t, f + !t[0]);
>> +    }
>> +}
>> +
>>  /* Output information about ARC number IX.  Returns nonzero if
>>     anything is output.  */
>>  
>> @@ -3078,16 +3247,29 @@ output_line_details (FILE *f, const line_info *line, unsigned line_num)
>>  	  if (flag_branches)
>>  	    for (arc = (*it)->succ; arc; arc = arc->succ_next)
>>  	      jx += output_branch_count (f, jx, arc);
>> +
>> +	  if (flag_conditions)
>> +	      output_conditions (f, *it);
>>  	}
>>      }
>> -  else if (flag_branches)
>> +  else
>>      {
>> -      int ix;
>> +      if (flag_branches)
>> +	{
>> +	  int ix;
>> +
>> +	  ix = 0;
>> +	  for (vector<arc_info *>::const_iterator it = line->branches.begin ();
>> +		  it != line->branches.end (); it++)
>> +	      ix += output_branch_count (f, ix, (*it));
>> +	}
>>  
>> -      ix = 0;
>> -      for (vector<arc_info *>::const_iterator it = line->branches.begin ();
>> -	   it != line->branches.end (); it++)
>> -	ix += output_branch_count (f, ix, (*it));
>> +      if (flag_conditions)
>> +	{
>> +	  for (vector<block_info *>::const_iterator it = line->blocks.begin ();
>> +	       it != line->blocks.end (); it++)
>> +	      output_conditions (f, *it);
>> +	}
>>      }
>>  }
>>  
>> diff --git a/gcc/ipa-inline.cc b/gcc/ipa-inline.cc
>> index 14969198cde..3e37305843e 100644
>> --- a/gcc/ipa-inline.cc
>> +++ b/gcc/ipa-inline.cc
>> @@ -646,7 +646,7 @@ can_early_inline_edge_p (struct cgraph_edge *e)
>>  			 "  edge not inlinable: not in SSA form\n");
>>        return false;
>>      }
>> -  else if (profile_arc_flag
>> +  else if ((profile_arc_flag || profile_condition_flag)
>>  	   && ((lookup_attribute ("no_profile_instrument_function",
>>  				 DECL_ATTRIBUTES (caller->decl)) == NULL_TREE)
>>  	       != (lookup_attribute ("no_profile_instrument_function",
>> diff --git a/gcc/ipa-split.cc b/gcc/ipa-split.cc
>> index 16734617d03..07d2b17ab12 100644
>> --- a/gcc/ipa-split.cc
>> +++ b/gcc/ipa-split.cc
>> @@ -1929,7 +1929,8 @@ pass_split_functions::gate (function *)
>>    /* When doing profile feedback, we want to execute the pass after profiling
>>       is read.  So disable one in early optimization.  */
>>    return (flag_partial_inlining
>> -	  && !profile_arc_flag && !flag_branch_probabilities);
>> +      && !profile_arc_flag && !flag_branch_probabilities
>> +      && !profile_condition_flag);
>>  }
>>  
>>  } // anon namespace
>> diff --git a/gcc/passes.cc b/gcc/passes.cc
>> index 78a07f8691a..3457ad1c5b3 100644
>> --- a/gcc/passes.cc
>> +++ b/gcc/passes.cc
>> @@ -352,7 +352,8 @@ finish_optimization_passes (void)
>>    gcc::dump_manager *dumps = m_ctxt->get_dumps ();
>>  
>>    timevar_push (TV_DUMP);
>> -  if (profile_arc_flag || flag_test_coverage || flag_branch_probabilities)
>> +  if (profile_arc_flag || profile_condition_flag || flag_test_coverage
>> +      || flag_branch_probabilities)
>>      {
>>        dumps->dump_start (pass_profile_1->static_pass_number, NULL);
>>        end_branch_prob ();
>> diff --git a/gcc/profile.cc b/gcc/profile.cc
>> index 96121d60711..f7f6d1f7197 100644
>> --- a/gcc/profile.cc
>> +++ b/gcc/profile.cc
>> @@ -66,9 +66,19 @@ along with GCC; see the file COPYING3.  If not see
>>  #include "cfgloop.h"
>>  #include "sreal.h"
>>  #include "file-prefix-map.h"
>> +#include "stringpool.h"
>>  
>>  #include "profile.h"
>>  
>> +struct condcov;
>> +struct condcov *find_conditions (struct function*);
>> +unsigned cov_length (const struct condcov*);
>> +array_slice<basic_block> cov_blocks (struct condcov*, unsigned);
>> +array_slice<gcov_type_unsigned > cov_masks (struct condcov*, unsigned);
>> +void cov_free (struct condcov*);
>> +int instrument_decisions (array_slice<basic_block>, unsigned, tree*,
>> +			  gcov_type_unsigned*);
>> +
>>  /* Map from BBs/edges to gcov counters.  */
>>  vec<gcov_type> bb_gcov_counts;
>>  hash_map<edge,gcov_type> *edge_gcov_counts;
>> @@ -100,6 +110,7 @@ static int total_num_passes;
>>  static int total_num_times_called;
>>  static int total_hist_br_prob[20];
>>  static int total_num_branches;
>> +static int total_num_conds;
>>  
>>  /* Forward declarations.  */
>>  static void find_spanning_tree (struct edge_list *);
>> @@ -1155,6 +1166,12 @@ read_thunk_profile (struct cgraph_node *node)
>>     the flow graph that are needed to reconstruct the dynamic behavior of the
>>     flow graph.  This data is written to the gcno file for gcov.
>>  
>> +   When FLAG_PROFILE_CONDITIONS is nonzero, this functions instruments the
>> +   edges in the control flow graph to track what conditions are evaluated to in
>> +   order to determine what conditions are covered and have an independent
>> +   effect on the outcome (modified condition/decision coverage).  This data is
>> +   written to the gcno file for gcov.
>> +
>>     When FLAG_BRANCH_PROBABILITIES is nonzero, this function reads auxiliary
>>     information from the gcda file containing edge count information from
>>     previous executions of the function being compiled.  In this case, the
>> @@ -1173,6 +1190,7 @@ branch_prob (bool thunk)
>>    struct edge_list *el;
>>    histogram_values values = histogram_values ();
>>    unsigned cfg_checksum, lineno_checksum;
>> +  bool output_to_file;
>>  
>>    total_num_times_called++;
>>  
>> @@ -1397,10 +1415,18 @@ branch_prob (bool thunk)
>>  
>>    /* Write the data from which gcov can reconstruct the basic block
>>       graph and function line numbers (the gcno file).  */
>> +  output_to_file = false;
>>    if (coverage_begin_function (lineno_checksum, cfg_checksum))
>>      {
>>        gcov_position_t offset;
>>  
>> +      /* The condition coverage needs a deeper analysis to identify expressions
>> +       * of conditions, which means it is not yet ready to write to the gcno
>> +       * file.  It will write its entries later, but needs to know if it do it
>> +       * in the first place, which is controlled by the return value of
>> +       * coverage_begin_function. */
>> +      output_to_file = true;
>> +
>>        /* Basic block flags */
>>        offset = gcov_write_tag (GCOV_TAG_BLOCKS);
>>        gcov_write_unsigned (n_basic_blocks_for_fn (cfun));
>> @@ -1512,29 +1538,74 @@ branch_prob (bool thunk)
>>  
>>    remove_fake_edges ();
>>  
>> +  if (profile_condition_flag || profile_arc_flag)
>> +      gimple_init_gcov_profiler ();
>> +
>> +  if (profile_condition_flag)
>> +    {
>> +      struct condcov *cov = find_conditions (cfun);
>> +      gcc_assert (cov);
>> +      const unsigned nconds = cov_length (cov);
>> +      total_num_conds += nconds;
>> +
>> +      if (coverage_counter_alloc (GCOV_COUNTER_CONDS, 2 * nconds))
>> +	{
>> +	  /* Add two extra variables to the function for the local
>> +	     accumulators, which are zero'd on the entry of a new conditional.
>> +	     The local accumulators are shared between decisions in order to
>> +	     use less stack space. */
>> +	  tree accu[2] = {
>> +	    build_decl (UNKNOWN_LOCATION, VAR_DECL,
>> +			get_identifier ("__accu_t"), get_gcov_type ()),
>> +	    build_decl (UNKNOWN_LOCATION, VAR_DECL,
>> +			get_identifier ("__accu_f"), get_gcov_type ()),
>> +	  };
>> +
>> +	  gcov_position_t offset {};
>> +	  if (output_to_file)
>> +	      offset = gcov_write_tag (GCOV_TAG_CONDS);
>> +
>> +	  for (unsigned i = 0; i < nconds; ++i)
>> +	    {
>> +	      array_slice<basic_block> expr = cov_blocks (cov, i);
>> +	      array_slice<gcov_type_unsigned> masks = cov_masks (cov, i);
>> +	      gcc_assert (expr.is_valid ());
>> +	      gcc_assert (masks.is_valid ());
>> +
>> +	      int terms = instrument_decisions (expr, i, accu, masks.begin ());
>> +	      if (output_to_file)
>> +		{
>> +		  gcov_write_unsigned (expr.front ()->index);
>> +		  gcov_write_unsigned (terms);
>> +		}
>> +	    }
>> +	  if (output_to_file)
>> +	      gcov_write_length (offset);
>> +	}
>> +      cov_free (cov);
>> +    }
>> +
>>    /* For each edge not on the spanning tree, add counting code.  */
>>    if (profile_arc_flag
>>        && coverage_counter_alloc (GCOV_COUNTER_ARCS, num_instrumented))
>>      {
>>        unsigned n_instrumented;
>>  
>> -      gimple_init_gcov_profiler ();
>> -
>>        n_instrumented = instrument_edges (el);
>>  
>>        gcc_assert (n_instrumented == num_instrumented);
>>  
>>        if (flag_profile_values)
>>  	instrument_values (values);
>> -
>> -      /* Commit changes done by instrumentation.  */
>> -      gsi_commit_edge_inserts ();
>>      }
>>  
>>    free_aux_for_edges ();
>>  
>>    values.release ();
>>    free_edge_list (el);
>> +  /* Commit changes done by instrumentation.  */
>> +  gsi_commit_edge_inserts ();
>> +
>>    coverage_end_function (lineno_checksum, cfg_checksum);
>>    if (flag_branch_probabilities
>>        && (profile_status_for_fn (cfun) == PROFILE_READ))
>> @@ -1664,6 +1735,7 @@ init_branch_prob (void)
>>    total_num_passes = 0;
>>    total_num_times_called = 0;
>>    total_num_branches = 0;
>> +  total_num_conds = 0;
>>    for (i = 0; i < 20; i++)
>>      total_hist_br_prob[i] = 0;
>>  }
>> @@ -1703,5 +1775,7 @@ end_branch_prob (void)
>>  		     (total_hist_br_prob[i] + total_hist_br_prob[19-i]) * 100
>>  		     / total_num_branches, 5*i, 5*i+5);
>>  	}
>> +      fprintf (dump_file, "Total number of conditions: %d\n",
>> +	       total_num_conds);
>>      }
>>  }
>> diff --git a/gcc/testsuite/g++.dg/gcov/gcov-18.C b/gcc/testsuite/g++.dg/gcov/gcov-18.C
>> new file mode 100644
>> index 00000000000..310ed5297c0
>> --- /dev/null
>> +++ b/gcc/testsuite/g++.dg/gcov/gcov-18.C
>> @@ -0,0 +1,234 @@
>> +/* { dg-options "--coverage -fprofile-conditions -std=c++11" } */
>> +/* { dg-do run { target native } } */
>> +
>> +#include <vector>
>> +#include <stdexcept>
>> +
>> +class nontrivial_destructor
>> +{
>> +public:
>> +    explicit nontrivial_destructor (int v) : val (v) {}
>> +    ~nontrivial_destructor () {}
>> +
>> +    explicit operator bool() const { return bool(val); }
>> +
>> +    int val;
>> +};
>> +
>> +int identity (int x) { return x; }
>> +int throws (int) { throw std::runtime_error("exception"); }
>> +
>> +int throw_if (int x)
>> +{
>> +    if (x) /* conditions(1/2) true(0) */
>> +	   /* conditions(end) */
>> +	throw std::runtime_error("exception");
>> +    return x;
>> +}
>> +
>> +/* used for side effects to insert nodes in conditional bodies etc. */
>> +int x = 0;
>> +
>> +/* conditionals work in the presence of non-trivial destructors */
>> +void mcdc001a (int a)
>> +{
>> +    nontrivial_destructor v (a);
>> +
>> +    if (v.val > 0) /* conditions(2/2) */
>> +	x = v.val;
>> +    else
>> +	x = -v.val;
>> +}
>> +
>> +/* non-trivial destructor in-loop temporary */
>> +nontrivial_destructor
>> +mcdc002a (int a, int b)
>> +{
>> +    for (int i = 0; i < a; i++) /* conditions(2/2) */
>> +    {
>> +	nontrivial_destructor tmp (a);
>> +	if (tmp.val % b) /* conditions(2/2) */
>> +	    return nontrivial_destructor (0);
>> +	x += i;
>> +    } /* conditions(suppress) */
>> +      /* conditions(end) */
>> +
>> +    return nontrivial_destructor (a * b);
>> +}
>> +
>> +/* conditional in constructor */
>> +void mcdc003a (int a)
>> +{
>> +    class C
>> +    {
>> +    public:
>> +	explicit C (int e) : v (e)
>> +	{
>> +	    if (e) /* conditions(1/2) false(0) */
>> +		v = x - e;
>> +	}
>> +	int v;
>> +    };
>> +
>> +    C c (a);
>> +    if (c.v > 2) /* conditions(1/2) true(0) */
>> +		 /* conditions(end) */
>> +	x = c.v + a;
>> +}
>> +
>> +/* conditional in destructor */
>> +void mcdc004a (int a)
>> +{
>> +    class C
>> +    {
>> +    public:
>> +	explicit C (int e) : v (e) {}
>> +	~C ()
>> +	{
>> +	    if (v) /* conditions(2/2) */
>> +		x = 2 * v;
>> +	}
>> +	int v;
>> +    };
>> +
>> +    C c (a);
>> +    x = 1; // arbitrary action between ctor+dtor
>> +}
>> +
>> +/* conditional in try */
>> +void mcdc005a (int a)
>> +{
>> +    try
>> +    {
>> +	if (a) /* conditions(1/2) true(0) */
>> +	       /* conditions(end) */
>> +	    x = 2 * identity (a);
>> +	else
>> +	    x = 1;
>> +    }
>> +    catch (...)
>> +    {
>> +	x = 0;
>> +    }
>> +}
>> +
>> +/* conditional in catch */
>> +void mcdc006a (int a) {
>> +    try
>> +    {
>> +	throws (a);
>> +    }
>> +    catch (std::exception&)
>> +    {
>> +	if (a) /* conditions(1/2) false(0) */
>> +	       /* conditions(end) */
>> +	    x = identity (a);
>> +	else
>> +	    x = 0;
>> +    }
>> +}
>> +
>> +void mcdc006b (int a)
>> +{
>> +    if (a) /* conditions(1/2) true(0) */
>> +	   /* conditions(end) */
>> +	throws (a);
>> +    else
>> +	x = 1;
>> +}
>> +
>> +void mcdc006c (int a) try
>> +{
>> +    throws (a);
>> +}
>> +catch (...) {
>> +    if (a) /* conditions(2/2) */
>> +	x = 5;
>> +}
>> +
>> +/* temporary with destructor as term */
>> +void mcdc007a (int a, int b)
>> +{
>> +    x = a && nontrivial_destructor (b); /* conditions(3/4) false(1) destructor() */
>> +}
>> +
>> +void mcdc007b (int a, int b)
>> +{
>> +    if (a || throw_if (b)) /* conditions(3/4) true(1) destructor() */
>> +	x = -1;
>> +    else
>> +	x = 1;
>> +}
>> +
>> +void mcdc007c (int a, int b)
>> +{
>> +    if (throw_if (a) || throw_if (b)) /* conditions(2/4) true(0 1) destructor() */
>> +	x = -1;
>> +    else
>> +	x = 1;
>> +}
>> +
>> +/* destructor with delete */
>> +void mcdc008a (int a)
>> +{
>> +    class C
>> +    {
>> +    public:
>> +        int size = 5;
>> +        int* ptr = nullptr;
>> +
>> +        explicit C (int v) : size (v + 5), ptr (new int[size]) /* conditions(suppress) */
>> +							       /* conditions(end) */
>> +        {
>> +	    for (int i = 0; i < size; i++) /* conditions(2/2) */
>> +		ptr[i] = i + 1;
>> +        }
>> +        ~C()
>> +        {
>> +	    // delete with implicit nullptr check
>> +	    delete ptr; /* conditions(1/2) false(0) */
>> +			/* conditions(end) */
>> +	}
>> +    };
>> +
>> +    C c (a);
>> +    if (c.ptr[a + 1]) /* conditions(1/2) false(0) */
>> +	x = a;
>> +}
>> +
>> +int
>> +main (void)
>> +{
>> +    mcdc001a (0);
>> +    mcdc001a (1);
>> +
>> +    mcdc002a (1, 1);
>> +    mcdc002a (1, 2);
>> +
>> +    mcdc003a (1);
>> +
>> +    mcdc004a (0);
>> +    mcdc004a (1);
>> +
>> +    mcdc005a (0);
>> +
>> +    mcdc006a (1);
>> +
>> +    mcdc006b (0);
>> +
>> +    mcdc006c (0);
>> +    mcdc006c (1);
>> +
>> +    mcdc007a (0, 0);
>> +    mcdc007a (1, 1);
>> +
>> +    mcdc007b (0, 0);
>> +    mcdc007b (1, 0);
>> +
>> +    mcdc007c (0, 0);
>> +
>> +    mcdc008a (1);
>> +
>> +}
>> +
>> +/* { dg-final { run-gcov conditions { --conditions gcov-18.C } } } */
>> diff --git a/gcc/testsuite/gcc.misc-tests/gcov-19.c b/gcc/testsuite/gcc.misc-tests/gcov-19.c
>> new file mode 100644
>> index 00000000000..1adff7c76f4
>> --- /dev/null
>> +++ b/gcc/testsuite/gcc.misc-tests/gcov-19.c
>> @@ -0,0 +1,1250 @@
>> +/* { dg-options "-fprofile-conditions -ftest-coverage" } */
>> +/* { dg-do run { target native } } */
>> +
>> +/* some side effect to stop branches from being pruned */
>> +int x = 0;
>> +
>> +/* || works */
>> +void
>> +mcdc001a (int a, int b)
>> +{
>> +    if (a || b) /* conditions(1/4) true(0) false(0 1) */
>> +		/* conditions(end) */
>> +	x = 1;
>> +    else
>> +	x = 2;
>> +}
>> +
>> +void
>> +mcdc001b (int a, int b)
>> +{
>> +    if (a || b) /* conditions(3/4) true(0) */
>> +		/* conditions(end) */
>> +	x = 1;
>> +    else
>> +	x = 2;
>> +}
>> +
>> +void
>> +mcdc001c (int a, int b)
>> +{
>> +    if (a || b) /* conditions(4/4) */
>> +	x = 1;
>> +    else
>> +	x = 2;
>> +}
>> +
>> +void
>> +mcdc001d (int a, int b, int c)
>> +{
>> +    if (a || b || c) /* conditions(2/6) false(0 1 2) true(2) */
>> +		     /* conditions(end) */
>> +	x = 1;
>> +}
>> +
>> +/* && works */
>> +void
>> +mcdc002a (int a, int b)
>> +{
>> +    if (a && b) /* conditions(1/4) true(0 1) false(0) */
>> +		/* conditions(end) */
>> +	x = 1;
>> +    else
>> +	x = 2;
>> +}
>> +
>> +void
>> +mcdc002b (int a, int b)
>> +{
>> +    if (a && b) /* conditions(3/4) false(0) */
>> +		/* conditions(end) */
>> +	x = 1;
>> +    else
>> +	x = 2;
>> +}
>> +
>> +void
>> +mcdc002c (int a, int b)
>> +{
>> +    if (a && b) /* conditions(4/4) */
>> +	x = 1;
>> +    else
>> +	x = 2;
>> +}
>> +
>> +void
>> +mcdc002d (int a, int b, int c)
>> +{
>> +    if (a && b && c) /* conditions(4/6) false(0 2) */
>> +		     /* conditions(end) */
>> +	x = 1;
>> +}
>> +
>> +/* negation works */
>> +void
>> +mcdc003a (int a, int b)
>> +{
>> +    if (!a || !b) /* conditions(2/4) false(0 1) */
>> +		  /* conditions(end) */
>> +	x = 1;
>> +    else
>> +	x = 2;
>> +}
>> +
>> +/* single conditionals with and without else */
>> +void
>> +mcdc004a (int a)
>> +{
>> +    if (a) /* conditions(1/2) true(0) */
>> +	   /* conditions(end) */
>> +	x = 1;
>> +    else
>> +	x = 2;
>> +}
>> +
>> +void
>> +mcdc004b (int a)
>> +{
>> +    if (a) /* conditions(2/2) */
>> +	x = 1;
>> +    else
>> +	x = 2;
>> +}
>> +
>> +void
>> +mcdc004c (int a)
>> +{
>> +    if (a) /* conditions(1/2) false(0) */
>> +	   /* conditions(end) */
>> +	x = 1;
>> +}
>> +
>> +void
>> +mcdc004d (int a, int b, int c)
>> +{
>> +    /* With no else this is interpreted as (a && (b || c)) */
>> +    if (a)  /* conditions(3/6) true(2) false(1 2)*/
>> +    {
>> +	if (b || c)
>> +	    x = a + b + c;
>> +    }
>> +}
>> +
>> +void
>> +mcdc004e (int a, int b, int c)
>> +{
>> +    /* With the else, this is interpreted as 2 expressions */
>> +    if (a)  /* conditions(2/2) */
>> +    {
>> +	if (b || c) /* conditions(1/4) true(1) false(0 1) */
>> +	    x = a + b + c;
>> +    }
>> +    else
>> +    {
>> +	x = c;
>> +    }
>> +}
>> +
>> +/* mixing && and || works */
>> +void
>> +mcdc005a (int a, int b, int c)
>> +{
>> +    if ((a && b) || c) /* conditions(1/6) true(0 1) false(0 1 2) */
>> +		       /* conditions(end) */
>> +	x = 1;
>> +    else
>> +	x = 2;
>> +}
>> +
>> +void
>> +mcdc005b (int a, int b, int c, int d)
>> +{
>> +    /* This is where masking MC/DC gets unintuitive:
>> +
>> +       1 1 0 0 => covers 1 (d = 0) as && 0 masks everything to the left
>> +       1 0 0 0 => covers 2 (b = 0, c = 0) as (a && 0) masks a and d is never
>> +       evaluated. */
>> +    if ((a && (b || c)) && d) /* conditions(3/8) true(0 1 2 3) false(0) */
>> +			      /* conditions(end) */
>> +	x = 1;
>> +    else
>> +	x = 2;
>> +}
>> +
>> +void
>> +mcdc005c (int a, int b, int c, int d)
>> +{
>> +    if (a || (b && c) || d) /* conditions(2/8) true(0 3) false(0 1 2 3) */
>> +			    /* conditions(end) */
>> +        x = a + b + c + d;
>> +}
>> +
>> +void
>> +mcdc005d (int a, int b, int c, int d)
>> +{
>> +    /* This test is quite significant - it has a single input
>> +       (1, 0, 0, 0) and tests specifically for when a multi-term left operand
>> +       is masked. d = 0 should mask a || b and for the input there are no other
>> +       sources for masking a (since b = 0). */
>> +    if ((a || b) && (c || d)) /* conditions(2/8) true(0 1 2 3) false(0 1) */
>> +			      /* conditions(end) */
>> +	x = a + b;
>> +    else
>> +	x = c + d;
>> +}
>> +
>> +/* nested conditionals */
>> +void
>> +mcdc006a (int a, int b, int c, int d, int e)
>> +{
>> +    if (a) /* conditions(2/2) */
>> +    {
>> +	if (b && c) /* conditions(3/4) false(1) */
>> +		    /* conditions(end) */
>> +	    x = 1;
>> +	else
>> +	    x = 2;
>> +    }
>> +    else
>> +    {
>> +	if (c || d) /* conditions(2/4) true(0 1) */
>> +		    /* conditions(end) */
>> +	    x = 3;
>> +	else
>> +	    x = 4;
>> +    }
>> +}
>> +
>> +void
>> +mcdc006b (int a, int b, int c)
>> +{
>> +    if (a) /* conditions(6/6) */
>> +	if (b)
>> +	    if (c)
>> +		x = a + b + c;
>> +}
>> +
>> +void
>> +mcdc006c (int a, int b, int c)
>> +{
>> +    if (a) /* conditions(2/2) */
>> +    {
>> +	if (b) /*conditions(2/2) */
>> +	{
>> +	    if (c) /* conditions(2/2) */
>> +	    {
>> +		x = a + b + c;
>> +	    }
>> +	}
>> +	else
>> +	{
>> +	    x = b;
>> +	}
>> +    }
>> +    else
>> +    {
>> +	x = a;
>> +    }
>> +}
>> +
>> +/* else/if */
>> +void
>> +mcdc007a (int a, int b, int c, int d)
>> +{
>> +    if (a) /* conditions(2/2) */
>> +    {
>> +	if (b) /* conditions(1/2) true(0) */
>> +	       /* conditions(end) */
>> +	    x = 1;
>> +	else
>> +	    x = 2;
>> +    }
>> +    else if (c) /* conditions(2/2) */
>> +    {
>> +	if (d) /* conditions(1/2) true(0) */
>> +	       /* conditions(end) */
>> +	    x = 3;
>> +	else
>> +	    x = 4;
>> +    }
>> +}
>> +
>> +void
>> +mcdc007b (int a, int b, int c)
>> +{
>> +    goto begin;
>> +then:
>> +    x = 1;
>> +    return;
>> +begin:
>> +    /* Evaluates to if (a || b || c) x = 1 */
>> +    if (a)	    /* conditions(5/6) true(2) */
>> +		    /* conditions(end) */
>> +	goto then;
>> +    else if (b)
>> +	goto then;
>> +    else if (c)
>> +	goto then;
>> +}
>> +
>> +void
>> +mcdc007c (int a, int b, int c)
>> +{
>> +    goto begin;
>> +then1:
>> +    x = 1;
>> +    return;
>> +then2:
>> +    x = 1;
>> +    return;
>> +then3:
>> +    x = 1;
>> +    return;
>> +begin:
>> +    /* similar to if (a || b || c) x = 1 */
>> +    if (a) /* conditions(2/2) */
>> +	goto then1;
>> +    else if (b) /* conditions(2/2) */
>> +	goto then2;
>> +    else if (c) /* conditions(1/2) true(0) */
>> +		/* conditions(end) */
>> +	goto then3;
>> +}
>> +
>> +/* while loop */
>> +void
>> +mcdc008a (int a)
>> +{
>> +    while (a < 10) /* conditions(2/2) */
>> +	x = a++;
>> +}
>> +
>> +void
>> +mcdc008b (int a)
>> +{
>> +    while (a > 10) /* conditions(1/2) true(0) */
>> +		   /* conditions(end) */
>> +	x = a--;
>> +}
>> +
>> +void
>> +mcdc008c (int a)
>> +{
>> +    // should work, even with no body
>> +    while (a) /* conditions(2/2) */
>> +	break;
>> +}
>> +
>> +void
>> +mcdc008d (int a, int b, int c, int d)
>> +{
>> +    /* multi-term loop conditional */
>> +    while ((a && (b || c)) && d) /* conditions(8/8) */
>> +	a = b = c = d = 0;
>> +}
>> +
>> +void
>> +mcdc009a (int a, int b)
>> +{
>> +    while (a > 0 && b > 0) /* conditions(3/4) false(1) */
>> +			   /* conditions(end) */
>> +	x = a--;
>> +}
>> +
>> +/* for loop */
>> +void
>> +mcdc010a(int a, int b)
>> +{
>> +    for (int i = 0; i < b; i++) /* conditions(2/2) */
>> +    {
>> +	if (a < b) /* conditions(2/2) */
>> +	    x = 1;
>> +	else
>> +	    x = a += 2;
>> +    }
>> +}
>> +
>> +void
>> +mcdc010b ()
>> +{
>> +    for (int a = 0; a <= 1; ++a) /* conditions(2/2) */
>> +    {
>> +	x = a;
>> +    }
>> +}
>> +
>> +int always (int x) { (void) x; return 1; }
>> +
>> +/* no-condition infinite loops */
>> +void
>> +mcdc010c (int a)
>> +{
>> +    for (;;)
>> +    {
>> +	if (always(a)) /* conditions(1/2) false(0) */
>> +		       /* conditions(end) */
>> +	{
>> +	    x = a;
>> +	    break;
>> +	}
>> +	x += a + 1;
>> +    }
>> +}
>> +
>> +/* conditionals without control flow constructs work */
>> +void
>> +mcdc011a (int a, int b, int c)
>> +{
>> +    x = (a && b) || c; /* conditions(5/6) false(1) */
>> +		       /* conditions(end) */
>> +}
>> +
>> +/* sequential expressions are handled independently */
>> +void
>> +mcdc012a (int a, int b, int c)
>> +{
>> +    if (a || b) /* conditions(3/4) true(0) */
>> +		/* conditions(end) */
>> +	x = 1;
>> +    else
>> +	x = 2;
>> +
>> +    if (c) /* conditions(2/2) */
>> +	x = 1;
>> +}
>> +
>> +/*
>> + * cannot ever satisfy MC/DC, even with all input combinations, because not all
>> + * variables independently affect the decision
>> + */
>> +void
>> +mcdc013a (int a, int b, int c)
>> +{
>> +    (void)b;
>> +    /*
>> +     * Specification: (a && b) || c
>> +     *
>> +     * But the expression was implemented wrong. This has branch coverage, but
>> +     * not MC/DC
>> +     */
>> +    if ((a && !c) || c) /* conditions(5/6) false(1) */
>> +	/* conditions(end) */
>> +	x = 1;
>> +    else
>> +	x = 2;
>> +}
>> +
>> +void
>> +mcdc014a ()
>> +{
>> +    int conds[64] = { 0 };
>> +    /* conditions(64/128) true(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63) */
>> +    x = conds[ 0] || conds[ 1] || conds[ 2] || conds[ 3] || conds[ 4] ||
>> +	conds[ 5] || conds[ 6] || conds[ 7] || conds[ 8] || conds[ 9] ||
>> +	conds[10] || conds[11] || conds[12] || conds[13] || conds[14] ||
>> +	conds[15] || conds[16] || conds[17] || conds[18] || conds[19] ||
>> +	conds[20] || conds[21] || conds[22] || conds[23] || conds[24] ||
>> +	conds[25] || conds[26] || conds[27] || conds[28] || conds[29] ||
>> +	conds[30] || conds[31] || conds[32] || conds[33] || conds[34] ||
>> +	conds[35] || conds[36] || conds[37] || conds[38] || conds[39] ||
>> +	conds[40] || conds[41] || conds[42] || conds[43] || conds[44] ||
>> +	conds[45] || conds[46] || conds[47] || conds[48] || conds[49] ||
>> +	conds[50] || conds[51] || conds[52] || conds[53] || conds[54] ||
>> +	conds[55] || conds[56] || conds[57] || conds[58] || conds[59] ||
>> +	conds[60] || conds[61] || conds[62] || conds[63]
>> +	;  /* conditions(end) */
>> +}
>> +
>> +/* early returns */
>> +void
>> +mcdc015a (int a, int b)
>> +{
>> +    if (a) /* conditions(2/2) */
>> +	return;
>> +
>> +    if (b) /* conditions(1/2) true(0) */
>> +	   /* conditions(end) */
>> +	x = 1;
>> +}
>> +
>> +void
>> +mcdc015b (int a, int b)
>> +{
>> +    for (int i = 5; i > a; i--) /* conditions(2/2) */
>> +    {
>> +	if (i == b) /* conditions(2/2) */
>> +	    return;
>> +	x = i;
>> +    }
>> +}
>> +
>> +void
>> +mcdc015c (int a, int b)
>> +{
>> +    for (int i = 5; i > a; i--) /* conditions(2/2) */
>> +    {
>> +	if (i == b) /* conditions(2/2) */
>> +	{
>> +	    x = 0;
>> +	    return;
>> +	}
>> +	else
>> +	{
>> +	    x = 1;
>> +	    return;
>> +	}
>> +
>> +	x = i;
>> +    }
>> +}
>> +
>> +
>> +/* check nested loops */
>> +void
>> +mcdc016a (int a, int b)
>> +{
>> +    for (int i = 0; i < a; i++) /* conditions(2/2) */
>> +	for (int k = 0; k < b; k++) /* conditions(2/2) */
>> +	    x = i + k;
>> +}
>> +
>> +void
>> +mcdc016b (int a, int b)
>> +{
>> +    for (int i = 0; i < a; i++) /* conditions(2/2) */
>> +    {
>> +	if (a > 5) /* conditions(2/2) */
>> +	    break;
>> +
>> +	for (int k = 0; k < b; k++) /* conditions(2/2) */
>> +	    x = i + k;
>> +    }
>> +}
>> +
>> +void
>> +mcdc016c (int a, int b)
>> +{
>> +    for (int i = 0; i < a; i++) /* conditions(2/2) */
>> +    {
>> +	if (a > 5) /* conditions(1/2) true(0) */
>> +		   /* conditions(end) */
>> +	    return;
>> +
>> +	for (int k = 0; k < b; k++) /* conditions(2/2) */
>> +	    x = i + k;
>> +    }
>> +}
>> +
>> +void
>> +mcdc016d (int a, int b)
>> +{
>> +    for (int i = 0; i < a; i++) /* conditions(2/2) */
>> +    {
>> +	for (int k = 0; k < 5; k++) /* conditions(2/2) */
>> +	{
>> +	    if (b > 5) /* conditions(1/2) true(0) */
>> +		       /* conditions(end) */
>> +		return;
>> +	    x = i + k;
>> +	}
>> +
>> +    }
>> +}
>> +
>> +/* do-while loops */
>> +void
>> +mcdc017a (int a)
>> +{
>> +    do
>> +    {
>> +	a--;
>> +    } while (a > 0); /* conditions(2/2) */
>> +}
>> +
>> +void
>> +noop () {}
>> +
>> +void
>> +mcdc017b (int a, int b)
>> +{
>> +    do
>> +    {
>> +	/*
>> +	 * This call is important; it can add more nodes to the body in the
>> +	 * CFG, which has changes how close exits and breaks are to the loop
>> +	 * conditional.
>> +	 */
>> +	noop ();
>> +	a--;
>> +	if (b) /* conditions(2/2) */
>> +	    break;
>> +
>> +    } while (a > 0); /* conditions(2/2) */
>> +}
>> +
>> +void
>> +mcdc017c (int a, int b)
>> +{
>> +    int left = 0;
>> +    int right = 0;
>> +    int n = a + b;
>> +    do
>> +    {
>> +	if (a) /* conditions(1/2) false(0) */
>> +	       /* conditions(end) */
>> +	{
>> +	    left = a > left ? b : left; /* conditions(2/2) */
>> +	}
>> +	if (b) /* conditions(1/2) false(0) */
>> +	{
>> +	    right = b > right ? a : right; /* conditions(2/2) */
>> +	}
>> +    } while (n-- > 0); /* conditions(2/2) */
>> +}
>> +
>> +int id  (int x) { return  x; }
>> +int inv (int x) { return !x; }
>> +
>> +/* collection of odd cases lifted-and-adapted from real-world code */
>> +int mcdc018a (int a, int b, int c, int d, int e, int f, int g, int len)
>> +{
>> +    int n;
>> +    /* adapted from zlib/gz_read */
>> +    do
>> +    {
>> +	n = -1;
>> +	if (n > len) /* conditions(2/2) */
>> +	    n = len;
>> +
>> +	if (b) /* conditions(2/2) */
>> +	{
>> +	    if (b < 5) /* conditions(2/2) */
>> +		x = 1;
>> +	    noop();
>> +	}
>> +	else if (c && d) /* conditions(3/4) false(1) */
>> +	{
>> +	    x = 2;
>> +	    break;
>> +	}
>> +	else if (e || f) /* conditions(2/4) false(0 1) */
>> +			 /* conditions(end) */
>> +	{
>> +	    if (id(g)) /* conditions(2/2) */
>> +		return 0;
>> +	    continue;
>> +	}
>> +    } while (a-- > 0); /* conditions(2/2) */
>> +
>> +    return 1;
>> +}
>> +
>> +void
>> +mcdc018b (int a, int b, int c)
>> +{
>> +    int n;
>> +    while (a) /* conditions(2/2) */
>> +    {
>> +	/* else block does not make a difference for the problem, but ensures
>> +	   loop termination. */
>> +	if (b) /* conditions(2/2) */
>> +	    n = c ? 0 : 0; // does not show up in CFG (embedded in the block)
>> +	else
>> +	    n = 0;
>> +	a = n;
>> +    }
>> +}
>> +
>> +/* Adapted from zlib/compress2 */
>> +void
>> +mcdc018c (int a, int b)
>> +{
>> +    int err;
>> +    do
>> +    {
>> +	a = inv (a);
>> +	err = a;
>> +    } while (err); /* conditions(1/2) true(0) */
>> +		   /* conditions(end) */
>> +
>> +    a = id (a);
>> +    if (a) /* conditions(1/2) true(0) */
>> +	x *= a + 1;
>> +}
>> +
>> +/* too many conditions, coverage gives up */
>> +void
>> +mcdc019a ()
>> +{
>> +    int conds[65] = { 0 };
>> +    #pragma GCC diagnostic push
>> +    #pragma GCC diagnostic ignored "-Wcoverage-too-many-conditions"
>> +    x = conds[ 0] || conds[ 1] || conds[ 2] || conds[ 3] || conds[ 4] ||
>> +	conds[ 5] || conds[ 6] || conds[ 7] || conds[ 8] || conds[ 9] ||
>> +	conds[10] || conds[11] || conds[12] || conds[13] || conds[14] ||
>> +	conds[15] || conds[16] || conds[17] || conds[18] || conds[19] ||
>> +	conds[20] || conds[21] || conds[22] || conds[23] || conds[24] ||
>> +	conds[25] || conds[26] || conds[27] || conds[28] || conds[29] ||
>> +	conds[30] || conds[31] || conds[32] || conds[33] || conds[34] ||
>> +	conds[35] || conds[36] || conds[37] || conds[38] || conds[39] ||
>> +	conds[40] || conds[41] || conds[42] || conds[43] || conds[44] ||
>> +	conds[45] || conds[46] || conds[47] || conds[48] || conds[49] ||
>> +	conds[50] || conds[51] || conds[52] || conds[53] || conds[54] ||
>> +	conds[55] || conds[56] || conds[57] || conds[58] || conds[59] ||
>> +	conds[60] || conds[61] || conds[62] || conds[63] || conds[64]
>> +	;
>> +    #pragma GCC diagnostic pop
>> +}
>> +
>> +/* ternary */
>> +void
>> +mcdc020a (int a)
>> +{
>> +    // special case, this can be reduced to:
>> +    // _1 = argc != 0;
>> +    // e = (int) _1;
>> +    x = a ? 1 : 0;
>> +
>> +    // changing to different int makes branch
>> +    x = a ? 2 : 1; /* conditions(2/2) */
>> +}
>> +
>> +void
>> +mcdc020b (int a, int b)
>> +{
>> +    x = (a || b) ? 1 : 0; /* conditions(3/4) true(1) */
>> +}
>> +
>> +void
>> +mcdc020c (int a, int b)
>> +{
>> +    x = a ? 0
>> +	: b ? 1 /* conditions(2/2) */
>> +	: 2;    /* conditions(1/2) false(0) */
>> +		/* conditions(end) */
>> +}
>> +
>> +/* Infinite loop (no exit-edge), this should not be called, but it should
>> +   compile fine */
>> +void
>> +mcdc021a ()
>> +{
>> +    while (1)
>> +	;
>> +}
>> +
>> +/* Computed goto can give all sorts of problems, including difficult path
>> +   contractions. */
>> +void
>> +mcdc021b ()
>> +{
>> +  void *op = &&dest;
>> +dest:
>> +  if (op) /* conditions(0/2) true(0) false(0) */
>> +	  /* conditions(end) */
>> +    goto * 0;
>> +}
>> +
>> +int __sigsetjmp ();
>> +
>> +/* This should compile, but not called. */
>> +void
>> +mcdc021c ()
>> +{
>> +  while (x) /* conditions(0/2) true(0) false(0)*/
>> +	    /* conditions(end) */
>> +     __sigsetjmp ();
>> +}
>> +
>> +/* If edges are not properly contracted the a && id (b) will be interpreted as
>> +   two independent expressions. */
>> +void
>> +mcdc021d (int a, int b, int c, int d)
>> +{
>> +    if (a && id (b)) /* conditions(1/4) true(0 1) false(0) */
>> +		     /* conditions(end) */
>> +	x = 1;
>> +    else if (c && id (d)) /* conditions(1/4) true(0 1) false(0) */
>> +			  /* conditions(end) */
>> +	x = 2;
>> +    else
>> +	x = 3;
>> +}
>> +
>> +/* Adapted from linux arch/x86/tools/relocs.c
>> +   With poor edge contracting this became an infinite loop. */
>> +void
>> +mcdc022a (int a, int b)
>> +{
>> +    for (int i = 0; i < 5; i++) /* conditions(2/2) */
>> +    {
>> +	x = i;
>> +	for (int j = i; j < 5; j++) /* conditions(2/2) */
>> +	{
>> +	    if (id (id (a)) || id (b)) /* conditions(3/4) true(0) */
>> +				       /* conditions(end) */
>> +		continue;
>> +	    b = inv(b);
>> +	}
>> +    }
>> +}
>> +
>> +int
>> +mcdc022b (int a)
>> +{
>> +    int devt;
>> +    if (a) /* conditions(2/2) */
>> +    {
>> +	x = a * 2;
>> +	if (x != a / 10 || x != a % 10) /* conditions(1/4) true(1) false(0 1) */
>> +					/* conditions(end) */
>> +	    return 0;
>> +    } else {
>> +	devt = id (a);
>> +	if (devt) /* conditions(1/2) true(0) */
>> +		  /* conditions(end) */
>> +	    return 0;
>> +    }
>> +
>> +    return devt;
>> +}
>> +
>> +/* Adapted from linux arch/x86/events/intel/ds.c
>> +
>> +   It broken sorting so that the entry block was not the first node after
>> +   sorting. */
>> +void
>> +mcdc022c (int a)
>> +{
>> +    if (!a) /* conditions(2/2) */
>> +	return;
>> +
>> +    for (int i = 0; i < 5; i++) /* conditions(2/2) */
>> +    {
>> +	if (id (a + i) || inv (a - 1)) /* conditions(1/4) false(0 1) true(1) */
>> +				       /* conditions(end) */
>> +	    x = a + i;
>> +	if (inv (a)) /* conditions(1/2) true(0) */
>> +		     /* conditions(end) */
>> +	    break;
>> +    }
>> +}
>> +
>> +void
>> +mcdc022d (int a)
>> +{
>> +    int i;
>> +    for (i = 0; i < id (a); i++) /* conditions(1/2) false(0) */
>> +    {
>> +	if (!inv (a)) /* conditions(1/2) false(0)*/
>> +		      /* conditions(end) */
>> +	    break;
>> +    }
>> +
>> +    if (i < a) /* conditions(1/2) false(0) */
>> +	       /* conditions(end) */
>> +	x = a + 1;
>> +}
>> +
>> +/* 023 specifically tests that masking works correctly, which gets complicated
>> +   fast with a mix of operators and deep subexpressions.  These tests violates
>> +   the style guide slightly to emphasize the nesting.  They all share the same
>> +   implementation and only one input is given to each function to obtain clean
>> +   coverage results. */
>> +void
>> +mcdc023a (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
>> +	  int l, int m, int n)
>> +{
>> +    // [a m n] = 0, [b, ...] = 1
>> +    // a is masked by b and the remaining terms should be short circuited
>> +    if (/* conditions(1/24) true(0 2 3 4 5 6 7 8 9 10 11) false(0 1 2 3 4 5 6 7 8 9 10 11) */
>> +	/* conditions(end) */
>> +	   (a || b)
>> +	|| (   ((c && d) || (e && (f || g) && h))
>> +	    && (k || l)
>> +	    && (m || n)))
>> +	x = a + b;
>> +    else
>> +	x = b + c;
>> +}
>> +
>> +void
>> +mcdc023b (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
>> +	  int l, int m, int n)
>> +{
>> +    // [a b d h] = 0, [c, ...] = 1
>> +    // h = 0 => false but does not mask (a || b) or (c && d). d = 0 masks c.
>> +    if (/* conditions(4/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 4 5 6 8 9 10 11) */
>> +	/* conditions(end) */
>> +	   (a || b)
>> +	|| (   ((c && d) || (e && (f || g) && h))
>> +	    && (k || l)
>> +	    && (m || n)))
>> +	x = a + b;
>> +    else
>> +	x = b + c;
>> +}
>> +
>> +void
>> +mcdc023c (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
>> +	  int l, int m, int n)
>> +{
>> +    /* [m n a b] = 0, [...] = 1
>> +       n,m = 0 should mask all other terms than a, b */
>> +    if (/* conditions(4/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 3 4 5 6 7 8 9) */
>> +	/* conditions(end) */
>> +	   (a || b)
>> +	|| (   ((c && d) || (e && (f || g) && h))
>> +	    && (k || l)
>> +	    && (m || n)))
>> +	x = a + b;
>> +    else
>> +	x = b + c;
>> +}
>> +
>> +void
>> +mcdc023d (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
>> +	  int l, int m, int n)
>> +{
>> +    /* [a b] = 0, [h, ...] = 1
>> +       n,m = 0 should mask all other terms than a, b */
>> +    if (/* conditions(4/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 3 4 5 6 7 10 11) */
>> +	/* conditions(end) */
>> +	   (a || b)
>> +	|| (   ((c && d) || (e && (f || g) && h))
>> +	    && (k || l)
>> +	    && (m || n)))
>> +	x = a + b;
>> +    else
>> +	x = b + c;
>> +}
>> +
>> +void
>> +mcdc023e (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
>> +	  int l, int m, int n)
>> +{
>> +    /* [a b d] = 0, [c h, ...] = 1
>> +       h = 1 should mask c, d, leave other terms intact.
>> +       If [k l m n] were false then h itself would be masked.
>> +       [a b] are masked as collateral by [m n]. */
>> +    if (/* conditions(5/24) true(0 1 2 3 6 9 11) false(0 1 2 3 4 5 6 7 8 9 10 11) */
>> +	/* conditions(end) */
>> +	   (a || b)
>> +	|| (   ((c && d) || (e && (f || g) && h))
>> +	    && (k || l)
>> +	    && (m || n)))
>> +	x = a + b;
>> +    else
>> +	x = b + c;
>> +}
>> +
>> +void
>> +mcdc023f (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
>> +	  int l, int m, int n)
>> +{
>> +    /* [a b c f g] = 0, [e, ...] = 1
>> +       [f g] = 0 should mask e, leave [c d] intact. */
>> +    if (/* conditions(5/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(3 4 7 8 9 10 11) */
>> +	/* conditions(end) */
>> +	   (a || b)
>> +	|| (   ((c && d) || (e && (f || g) && h))
>> +	    && (k || l)
>> +	    && (m || n)))
>> +	x = a + b;
>> +    else
>> +	x = b + c;
>> +}
>> +
>> +void
>> +mcdc023g (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
>> +	  int l, int m, int n)
>> +{
>> +    /* [a b d f g] = 0, [e c, ...] = 1
>> +       Same as 023f but with [c d] flipped so d masks c rather than c
>> +       short-circuits.  This should not be lost. */
>> +    if (/* conditions(5/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 4 7 8 9 10 11) */
>> +	/* conditions(end) */
>> +	   (a || b)
>> +	|| (   ((c && d) || (e && (f || g) && h))
>> +	    && (k || l)
>> +	    && (m || n)))
>> +	x = a + b;
>> +    else
>> +	x = b + c;
>> +}
>> +
>> +void
>> +mcdc024a (int a, int b)
>> +{
>> +    if (a && b) /* conditions(1/4) true(0 1) false(1) */
>> +		/* conditions(end) */
>> +    {
>> +label1:
>> +	x = 1;
>> +    }
>> +    else
>> +    {
>> +	x = 2;
>> +    }
>> +
>> +    if (a || b) /* conditions(2/4) true(0 1) */
>> +		/* conditions(end) */
>> +    {
>> +label2:
>> +	x = 1;
>> +    }
>> +    else
>> +    {
>> +	x = 2;
>> +    }
>> +}
>> +
>> +void
>> +mcdc024b (int a, int b)
>> +{
>> +
>> +    if (a && b) /* conditions(1/4) true(0 1) false(1) */
>> +		/* conditions(end) */
>> +    {
>> +	x = 1;
>> +    }
>> +    else
>> +    {
>> +label1:
>> +	x = 2;
>> +    }
>> +
>> +    if (a || b) /* conditions(2/4) true(0 1) */
>> +		/* conditions(end) */
>> +    {
>> +	x = 1;
>> +    }
>> +    else
>> +    {
>> +label2:
>> +	x = 2;
>> +    }
>> +}
>> +
>> +void
>> +mcdc024c (int a, int b)
>> +{
>> +    if (a && b) /* conditions(1/4) true(0 1) false(1) */
>> +		/* conditions(end) */
>> +    {
>> +label1:
>> +	x = 1;
>> +    }
>> +    else
>> +    {
>> +label2:
>> +	x = 2;
>> +    }
>> +
>> +    if (a || b) /* conditions(2/4) true(0 1) */
>> +		/* conditions(end) */
>> +    {
>> +label3:
>> +	x = 1;
>> +    }
>> +    else
>> +    {
>> +label4:
>> +	x = 2;
>> +    }
>> +}
>> +
>> +int main ()
>> +{
>> +    mcdc001a (0, 1);
>> +
>> +    mcdc001b (0, 1);
>> +    mcdc001b (0, 0);
>> +
>> +    mcdc001c (0, 1);
>> +    mcdc001c (0, 0);
>> +    mcdc001c (1, 1);
>> +
>> +    mcdc001d (1, 1, 1);
>> +    mcdc001d (0, 1, 0);
>> +
>> +    mcdc002a (1, 0);
>> +
>> +    mcdc002b (1, 0);
>> +    mcdc002b (1, 1);
>> +
>> +    mcdc002c (0, 0);
>> +    mcdc002c (1, 1);
>> +    mcdc002c (1, 0);
>> +
>> +    mcdc002d (1, 1, 1);
>> +    mcdc002d (1, 0, 0);
>> +
>> +    mcdc003a (0, 0);
>> +    mcdc003a (1, 0);
>> +
>> +    mcdc004a (0);
>> +    mcdc004b (0);
>> +    mcdc004b (1);
>> +    mcdc004c (1);
>> +
>> +    mcdc004d (0, 0, 0);
>> +    mcdc004d (1, 1, 1);
>> +
>> +    mcdc004e (0, 0, 0);
>> +    mcdc004e (1, 1, 1);
>> +
>> +    mcdc005a (1, 0, 1);
>> +
>> +    mcdc005b (1, 1, 0, 0);
>> +    mcdc005b (1, 0, 0, 0);
>> +
>> +    mcdc005c (0, 1, 1, 0);
>> +
>> +    mcdc005d (1, 0, 0, 0);
>> +
>> +    mcdc006a (0, 0, 0, 0, 0);
>> +    mcdc006a (1, 0, 0, 0, 0);
>> +    mcdc006a (1, 1, 1, 0, 0);
>> +
>> +    mcdc006b (0, 0, 0);
>> +    mcdc006b (1, 0, 0);
>> +    mcdc006b (1, 1, 0);
>> +    mcdc006b (1, 1, 1);
>> +
>> +    mcdc006c (0, 0, 0);
>> +    mcdc006c (1, 0, 0);
>> +    mcdc006c (1, 1, 0);
>> +    mcdc006c (1, 1, 1);
>> +
>> +    mcdc007a (0, 0, 0, 0);
>> +    mcdc007a (1, 0, 0, 0);
>> +    mcdc007a (0, 0, 1, 0);
>> +
>> +    mcdc007b (0, 0, 0);
>> +    mcdc007b (0, 1, 1);
>> +    mcdc007b (1, 0, 1);
>> +
>> +    mcdc007c (0, 0, 0);
>> +    mcdc007c (0, 1, 1);
>> +    mcdc007c (1, 0, 1);
>> +
>> +    mcdc008a (0);
>> +
>> +    mcdc008b (0);
>> +
>> +    mcdc008c (0);
>> +    mcdc008c (1);
>> +
>> +    mcdc008d (0, 0, 0, 0);
>> +    mcdc008d (1, 0, 0, 0);
>> +    mcdc008d (1, 0, 1, 0);
>> +    mcdc008d (1, 0, 1, 1);
>> +    mcdc008d (1, 1, 1, 1);
>> +
>> +    mcdc009a (0, 0);
>> +    mcdc009a (1, 1);
>> +
>> +    mcdc010a (0, 0);
>> +    mcdc010a (0, 9);
>> +    mcdc010a (2, 1);
>> +
>> +    mcdc010b ();
>> +
>> +    mcdc010c (1);
>> +
>> +    mcdc011a (0, 0, 0);
>> +    mcdc011a (1, 1, 0);
>> +    mcdc011a (1, 0, 1);
>> +
>> +    mcdc012a (0, 0, 0);
>> +    mcdc012a (0, 1, 1);
>> +
>> +    mcdc013a (0, 0, 0);
>> +    mcdc013a (0, 0, 1);
>> +    mcdc013a (0, 1, 0);
>> +    mcdc013a (0, 1, 1);
>> +    mcdc013a (1, 0, 0);
>> +    mcdc013a (1, 0, 1);
>> +    mcdc013a (1, 1, 0);
>> +    mcdc013a (1, 1, 1);
>> +
>> +    mcdc014a ();
>> +
>> +    mcdc015a (0, 0);
>> +    mcdc015a (1, 0);
>> +
>> +    mcdc015b (0, 0);
>> +    mcdc015b (0, 1);
>> +    mcdc015b (6, 1);
>> +
>> +    mcdc015c (0, 0);
>> +    mcdc015c (0, 5);
>> +    mcdc015c (6, 1);
>> +
>> +    mcdc016a (5, 5);
>> +
>> +    mcdc016b (5, 5);
>> +    mcdc016b (6, 5);
>> +
>> +    mcdc016c (5, 5);
>> +
>> +    mcdc016d (1, 0);
>> +
>> +    mcdc017a (0);
>> +    mcdc017a (2);
>> +
>> +    mcdc017b (2, 0);
>> +    mcdc017b (0, 1);
>> +
>> +    mcdc017c (1, 1);
>> +
>> +    mcdc018a (0, 0, 1, 1, 0, 0, 0, 0);
>> +    mcdc018a (0, 1, 0, 0, 0, 0, 1, -2);
>> +    mcdc018a (0, 6, 0, 0, 0, 0, 1, -2);
>> +    mcdc018a (0, 6, 0, 0, 0, 0, 1, -2);
>> +    mcdc018a (0, 0, 0, 1, 0, 1, 1, 0);
>> +    mcdc018a (1, 0, 0, 0, 1, 1, 0, 0);
>> +
>> +    mcdc018b (1, 0, 0);
>> +    mcdc018b (1, 1, 0);
>> +
>> +    mcdc018c (1, 1);
>> +
>> +    mcdc019a ();
>> +
>> +    mcdc020a (0);
>> +    mcdc020a (1);
>> +
>> +    mcdc020b (0, 0);
>> +    mcdc020b (1, 0);
>> +
>> +    mcdc020c (0, 1);
>> +    mcdc020c (1, 1);
>> +
>> +    mcdc021d (1, 0, 1, 0);
>> +
>> +    mcdc022a (0, 0);
>> +
>> +    mcdc022b (0);
>> +    mcdc022b (1);
>> +
>> +    mcdc022c (0);
>> +    mcdc022c (1);
>> +
>> +    mcdc022d (1);
>> +
>> +    mcdc023a (0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
>> +    mcdc023b (0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1);
>> +    mcdc023c (0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0);
>> +    mcdc023d (0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1);
>> +    mcdc023e (0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1);
>> +    mcdc023f (0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1);
>> +    mcdc023g (0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1);
>> +
>> +    mcdc024a (0, 0);
>> +    mcdc024b (0, 0);
>> +    mcdc024c (0, 0);
>> +}
>> +
>> +/* { dg-final { run-gcov conditions { --conditions gcov-19.c } } } */
>> diff --git a/gcc/testsuite/gcc.misc-tests/gcov-20.c b/gcc/testsuite/gcc.misc-tests/gcov-20.c
>> new file mode 100644
>> index 00000000000..847dae495db
>> --- /dev/null
>> +++ b/gcc/testsuite/gcc.misc-tests/gcov-20.c
>> @@ -0,0 +1,22 @@
>> +/* { dg-options "-fprofile-conditions -ftest-coverage -fprofile-update=atomic" } */
>> +/* { dg-do run { target native } } */
>> +
>> +/* some side effect to stop branches from being pruned */
>> +int x = 0;
>> +
>> +void
>> +conditions_atomic001 (int a, int b)
>> +{
>> +    if (a || b) /* conditions(1/4) true(0) false(0 1) */
>> +		/* conditions(end) */
>> +	x = 1;
>> +    else
>> +	x = 2;
>> +}
>> +
>> +int main ()
>> +{
>> +    conditions_atomic001 (0, 1);
>> +}
>> +
>> +/* { dg-final { run-gcov conditions { --conditions gcov-20.c } } } */
>> diff --git a/gcc/testsuite/gcc.misc-tests/gcov-21.c b/gcc/testsuite/gcc.misc-tests/gcov-21.c
>> new file mode 100644
>> index 00000000000..978be3276a2
>> --- /dev/null
>> +++ b/gcc/testsuite/gcc.misc-tests/gcov-21.c
>> @@ -0,0 +1,16 @@
>> +/* { dg-options "-fprofile-conditions" } */
>> +
>> +/* https://gcc.gnu.org/pipermail/gcc-patches/2022-April/592927.html */
>> +char trim_filename_name;
>> +int r;
>> +
>> +void trim_filename() {
>> +    if (trim_filename_name)
>> +	r = 123;
>> +    while (trim_filename_name)
>> +	;
>> +}
>> +
>> +int main ()
>> +{
>> +}
>> diff --git a/gcc/testsuite/lib/gcov.exp b/gcc/testsuite/lib/gcov.exp
>> index 9d5b2cdb86b..69168d67d03 100644
>> --- a/gcc/testsuite/lib/gcov.exp
>> +++ b/gcc/testsuite/lib/gcov.exp
>> @@ -174,6 +174,184 @@ proc verify-branches { testname testcase file } {
>>      return $failed
>>  }
>>  
>> +#
>> +# verify-conditions -- check that conditions are checked as expected
>> +#
>> +# TESTNAME is the name of the test, including unique flags.
>> +# TESTCASE is the name of the test file.
>> +# FILE is the name of the gcov output file.
>> +#
>> +# Checks are based on comments in the source file. Condition coverage comes
>> +# with with two types of output, a summary and a list of the uncovered
>> +# conditions. Both must be checked to pass the test
>> +#
>> +# To check for conditions, add a comment the line of a conditional:
>> +# /* conditions(n/m) true(0 1) false(1) */
>> +#
>> +# where n/m are the covered and total conditions in the expression. The true()
>> +# and false() take the indices expected *not* covered.
>> +#
>> +# This means that all coverage statements should have been seen:
>> +# /* conditions(end) */
>> +#
>> +# If all conditions are covered i.e. n == m, then conditions(end) can be
>> +# omitted. If either true() or false() are empty they can be omitted too.
>> +#
>> +# C++ can insert conditionals in the CFG that are not present in source code.
>> +# These must be manually suppressed since unexpected and unhandled conditions
>> +# are an error (to help combat regressions). Output can be suppressed with
>> +# conditions(suppress) and conditions(end). suppress should usually be on a
>> +# closing brace.
>> +#
>> +# Some expressions, when using unnamed temporaries as operands, will have
>> +# destructors in expressions. The coverage of the destructor will be reported
>> +# on the same line as the expression itself, but suppress() would also swallow
>> +# the expected tested-for messages. To handle these, use the destructor() [1]
>> +# which will suppress everything from and including the second "conditions
>> +# covered".
>> +#
>> +# [1] it is important that the destructor() is *on the same line* as the
>> +#     conditions(m/n)
>> +proc verify-conditions { testname testcase file } {
>> +    set failed 0
>> +    set suppress 0
>> +    set destructor 0
>> +    set should ""
>> +    set shouldt ""
>> +    set shouldf ""
>> +    set shouldall ""
>> +    set fd [open $file r]
>> +    set n 0
>> +    set keywords {"end" "suppress"}
>> +    while {[gets $fd line] >= 0} {
>> +	regexp "^\[^:\]+: *(\[0-9\]+):" "$line" all n
>> +	set prefix "$testname line $n"
>> +
>> +	if {![regexp "condition" $line]} {
>> +	    continue
>> +	}
>> +
>> +	# Missing coverage for both true and false will cause a failure, but
>> +	# only count it once for the report.
>> +	set ok 1
>> +	if [regexp {conditions *\(([0-9a-z/]+)\)} "$line" all e] {
>> +	    # *Very* coarse sanity check: conditions() should either be a
>> +	    # keyword or n/m, anything else means a buggy test case. end is
>> +	    # optional for cases where all conditions are covered, since it
>> +	    # only expects a single line of output.
>> +	    if {([lsearch -exact $keywords $e] >= 0 || [regexp {\d+/\d+} "$e"]) == 0} {
>> +		fail "$prefix: expected conditions (n/m), (suppress) or (end); was ($e)"
>> +		incr failed
>> +		continue
>> +	    }
>> +
>> +	    # Any keyword means a new context. Set the error flag if not all
>> +	    # expected output has been seen, and reset the state.
>> +
>> +	    if {[llength $shouldt] != 0} {
>> +		fail "$prefix: expected 'not covered (true)' for terms: $shouldt"
>> +		set ok 0
>> +	    }
>> +
>> +	    if {[llength $shouldf] != 0} {
>> +		fail "$prefix: expected 'not covered (false)' for terms: $shouldf"
>> +		set ok 0
>> +	    }
>> +
>> +	    if {$shouldall ne ""} {
>> +		fail "$prefix: coverage summary not found; expected $shouldall"
>> +		set ok 0
>> +	    }
>> +
>> +	    set suppress 0
>> +	    set destructor 0
>> +	    set should ""
>> +	    set shouldt ""
>> +	    set shouldf ""
>> +	    set shouldall ""
>> +	    set newt ""
>> +	    set newf ""
>> +
>> +	    if [regexp {destructor\(\)} "$line"] {
>> +		set destructor 1
>> +	    }
>> +
>> +	    if [regexp {(\d+)/(\d+)} "$e" all i k] {
>> +		regexp {true\(([0-9 ]+)\)}  "$line" all newt
>> +		regexp {false\(([0-9 ]+)\)} "$line" all newf
>> +
>> +		# Sanity check - if the true() and false() vectors should have
>> +		# m-n elements to cover all uncovered conditions. Because of
>> +		# masking it can sometimes be surprising what terms are
>> +		# independent, so this makes for more robust test at the cost
>> +		# of being slightly more annoying to write.
>> +		set nterms [expr [llength $newt] + [llength $newf]]
>> +		set nexpected [expr {$k - $i}]
>> +		if {$nterms != $nexpected} {
>> +		    fail "$prefix: expected $nexpected uncovered terms; got $nterms"
>> +		    set ok 0
>> +		}
>> +		set shouldall $e
>> +		set shouldt $newt
>> +		set shouldf $newf
>> +	    } elseif {$e == "end"} {
>> +		# no-op - state has already been reset, and errors flagged
>> +	    } elseif {$e == "suppress"} {
>> +		set suppress 1
>> +	    } else {
>> +		# this should be unreachable, 
>> +		fail "$prefix: unhandled control ($e), should be unreachable"
>> +		set ok 0
>> +	    }
>> +	} elseif {$suppress == 1} {
>> +	    # ignore everything in a suppress block. C++ especially can insert
>> +	    # conditionals in exceptions and destructors which would otherwise
>> +	    # be considered unhandled.
>> +	    continue
>> +	} elseif [regexp {condition +(\d+) not covered \((.*)\)} "$line" all cond condv] {
>> +	    foreach v {true false} {
>> +		if [regexp $v $condv] {
>> +		    if {"$v" == "true"} {
>> +			set should shouldt
>> +		    } else {
>> +			set should shouldf
>> +		    }
>> +
>> +		    set i [lsearch [set $should] $cond]
>> +		    if {$i != -1} {
>> +			set $should [lreplace [set $should] $i $i]
>> +		    } else {
>> +			fail "$testname line $n: unexpected uncovered term $cond ($v)"
>> +			set ok 0
>> +		    }
>> +		}
>> +	    }
>> +	} elseif [regexp {condition outcomes covered (\d+/\d+)} "$line" all cond] {
>> +	    # the destructor-generated "conditions covered" lines will be
>> +	    # written after all expression-related output. Handle these by
>> +	    # turning on suppression if the destructor-suppression is
>> +	    # requested.
>> +	    if {$shouldall == "" && $destructor == 1} {
>> +		set suppress 1
>> +		continue
>> +	    }
>> +
>> +	    if {$cond == $shouldall} {
>> +		set shouldall ""
>> +	    } else {
>> +		fail "$testname line $n: unexpected summary $cond"
>> +		set ok 0
>> +	    }
>> +	}
>> +
>> +	if {$ok != 1} {
>> +	    incr failed
>> +	}
>> +    }
>> +    close $fd
>> +    return $failed
>> +}
>> +
>>  #
>>  # verify-calls -- check that call return percentages are as expected
>>  #
>> @@ -321,6 +499,7 @@ proc run-gcov { args } {
>>      set gcov_args ""
>>      set gcov_verify_calls 0
>>      set gcov_verify_branches 0
>> +    set gcov_verify_conditions 0
>>      set gcov_verify_lines 1
>>      set gcov_verify_intermediate 0
>>      set gcov_remove_gcda 0
>> @@ -331,10 +510,13 @@ proc run-gcov { args } {
>>  	  set gcov_verify_calls 1
>>  	} elseif { $a == "branches" } {
>>  	  set gcov_verify_branches 1
>> +	} elseif { $a == "conditions" } {
>> +	  set gcov_verify_conditions 1
>>  	} elseif { $a == "intermediate" } {
>>  	  set gcov_verify_intermediate 1
>>  	  set gcov_verify_calls 0
>>  	  set gcov_verify_branches 0
>> +	  set gcov_verify_conditions 0
>>  	  set gcov_verify_lines 0
>>  	} elseif { $a == "remove-gcda" } {
>>  	  set gcov_remove_gcda 1
>> @@ -404,6 +586,11 @@ proc run-gcov { args } {
>>      } else {
>>  	set bfailed 0
>>      }
>> +    if { $gcov_verify_conditions } {
>> +	set cdfailed [verify-conditions $testname $testcase $testcase.gcov]
>> +    } else {
>> +	set cdfailed 0
>> +    }
>>      if { $gcov_verify_calls } {
>>  	set cfailed [verify-calls $testname $testcase $testcase.gcov]
>>      } else {
>> @@ -418,12 +605,12 @@ proc run-gcov { args } {
>>  
>>      # Report whether the gcov test passed or failed.  If there were
>>      # multiple failures then the message is a summary.
>> -    set tfailed [expr $lfailed + $bfailed + $cfailed + $ifailed]
>> +    set tfailed [expr $lfailed + $bfailed + $cdfailed + $cfailed + $ifailed]
>>      if { $xfailed } {
>>  	setup_xfail "*-*-*"
>>      }
>>      if { $tfailed > 0 } {
>> -	fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cfailed in return percentages, $ifailed in intermediate format"
>> +	fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cdfailed in condition/decision, $cfailed in return percentages, $ifailed in intermediate format"
>>  	if { $xfailed } {
>>  	    clean-gcov $testcase
>>  	}
>> diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
>> index 2beb49241f2..0b537d64d97 100644
>> --- a/gcc/tree-profile.cc
>> +++ b/gcc/tree-profile.cc
>> @@ -58,6 +58,8 @@ along with GCC; see the file COPYING3.  If not see
>>  #include "alloc-pool.h"
>>  #include "symbol-summary.h"
>>  #include "symtab-thunks.h"
>> +#include "cfganal.h"
>> +#include "cfgloop.h"
>>  
>>  static GTY(()) tree gcov_type_node;
>>  static GTY(()) tree tree_interval_profiler_fn;
>> @@ -73,6 +75,1048 @@ static GTY(()) tree ic_tuple_var;
>>  static GTY(()) tree ic_tuple_counters_field;
>>  static GTY(()) tree ic_tuple_callee_field;
>>  
>> +namespace
>> +{
>> +/* Some context and reused instances between function calls.  Large embedded
>> +   buffers are used to up-front request enough memory for most programs and
>> +   merge them into a single allocation at the cost of using more memory in the
>> +   average case.  Some numbers from linux v5.13 which is assumed to be a
>> +   reasonably diverse code base: 75% of the functions in linux have less than
>> +   16 nodes in the CFG and approx 2.5% have more than 64 nodes.  The functions
>> +   that go beyond a few dozen nodes tend to be very large (>100) and so 64
>> +   seems like a good balance.
>> +
>> +   This is really just a performance balance of the cost of allocation and
>> +   wasted memory. */
>> +struct conds_ctx
>> +{
>> +    /* Bitmap of the processed blocks.  Bit n set means basic_block->index has
>> +       been processed either explicitly or as a part of an expression. */
>> +    auto_sbitmap marks;
>> +
>> +    /* This is both a reusable shared allocation which is also used to return
>> +       single expressions, which means it for most code should only hold a
>> +       couple of elements. */
>> +    auto_vec<basic_block, 32> blocks;
>> +
>> +    /* Map from basic_block->index to an ordering so that for a single
>> +       expression (a || b && c) => index_map[a] < index_map[b] < index_map[c].
>> +       The values do not have to be consecutive and can be interleaved by
>> +       values from other expressions, so comparisons only make sense for blocks
>> +       that belong to the same expression. */
>> +    auto_vec<int, 64> index_map;
>> +
>> +    /* Pre-allocate bitmaps and vectors for per-function book keeping.  This is
>> +       pure instance reuse and the bitmaps carry no data between function
>> +       calls. */
>> +    auto_vec<basic_block, 64> B1;
>> +    auto_vec<basic_block, 64> B2;
>> +    auto_sbitmap G1;
>> +    auto_sbitmap G2;
>> +    auto_sbitmap G3;
>> +
>> +    explicit conds_ctx (unsigned size) noexcept (true) : marks (size),
>> +    G1 (size), G2 (size), G3 (size)
>> +    {
>> +	bitmap_clear (marks);
>> +    }
>> +
>> +    /* Mark a node as processed so nodes are not processed twice for example in
>> +       loops, gotos. */
>> +    void mark (const basic_block b) noexcept (true)
>> +    {
>> +	gcc_assert (!bitmap_bit_p (marks, b->index));
>> +	bitmap_set_bit (marks, b->index);
>> +    }
>> +
>> +    /* Mark nodes as processed so they are not processed twice. */
>> +    void mark (const vec<basic_block>& bs) noexcept (true)
>> +    {
>> +	for (const basic_block b : bs)
>> +	    mark (b);
>> +    }
>> +
>> +    /* Check if all nodes are marked.  A successful run should visit & mark
>> +       every reachable node exactly once. */
>> +    bool all_marked (const vec<basic_block>& reachable) const noexcept (true)
>> +    {
>> +	for (const basic_block b : reachable)
>> +	    if (!bitmap_bit_p (marks, b->index))
>> +		return false;
>> +	return true;
>> +    }
>> +};
>> +
>> +/* Only instrument terms with fewer than number of bits in a (wide) gcov
>> +   integer, which is probably 64.  The algorithm itself does not impose this
>> +   limitation, but it makes for a simpler implementation.
>> +
>> +   * Allocating the output data structure (coverage_counter_alloc ()) can
>> +     assume pairs of gcov_type_unsigned and not use a separate length field.
>> +   * A pair gcov_type_unsigned can be used as accumulators.
>> +   * Updating accumulators is can use the bitwise operations |=, &= and not
>> +     custom operators that work for arbitrary-sized bit-sets.
>> +
>> +   Most real-world code should be unaffected by this, but it is possible
>> +   (especially for generated code) to exceed this limit.
>> + */
>> +#define CONDITIONS_MAX_TERMS (sizeof (gcov_type_unsigned) * BITS_PER_UNIT)
>> +#define EDGE_CONDITION (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE)
>> +
>> +/* Compare two basic blocks by their order in the expression i.e. for (a || b)
>> +   then cmp_index_map (a, b, ...) < 0.  The result is undefined if lhs, rhs
>> +   belong to different expressions. */
>> +int
>> +cmp_index_map (const void *lhs, const void *rhs, void *index_map)
>> +{
>> +    const_basic_block l = *(const basic_block*) lhs;
>> +    const_basic_block r = *(const basic_block*) rhs;
>> +    const vec<int>* im = (const vec<int>*) index_map;
>> +    return (*im)[l->index] - (*im)[r->index];
>> +}
>> +
>> +/* Find the index of needle in blocks; return -1 if not found. This has two
>> +   uses, sometimes for the index and sometimes for set member checks.  Sets are
>> +   typically very small (number of conditions, >8 is uncommon) so linear search
>> +   should be very fast. */
>> +int
>> +index_of (const basic_block needle, array_slice<basic_block> blocks)
>> +{
>> +    for (size_t i = 0; i < blocks.size (); i++)
>> +	if (blocks[i] == needle)
>> +	    return int (i);
>> +    return -1;
>> +}
>> +
>> +/* Returns true if this is a conditional node, i.e. it has outgoing true and
>> +   false edges. */
>> +bool
>> +block_conditional_p (const basic_block b)
>> +{
>> +    unsigned t = 0;
>> +    unsigned f = 0;
>> +    for (edge e : b->succs)
>> +    {
>> +	t |= (e->flags & EDGE_TRUE_VALUE);
>> +	f |= (e->flags & EDGE_FALSE_VALUE);
>> +    }
>> +    return t && f;
>> +}
>> +
>> +/* Check if the edge is a conditional. */
>> +bool
>> +edge_conditional_p (const edge e)
>> +{
>> +    return e->flags & EDGE_CONDITION;
>> +}
>> +
>> +/* Special cases of the single_*_p and single_*_edge functions in basic-block.h
>> +   that don't consider exception handling or other complex edges.  This helps
>> +   create a view of the CFG with only normal edges - if a basic block has both
>> +   an outgoing fallthrough and exceptional edge [1], it should be considered a
>> +   single-successor.
>> +
>> +   [1] if this is not possible, these functions can be removed and replaced by
>> +       their basic-block.h cousins. */
>> +bool
>> +single (const vec<edge, va_gc> *edges)
>> +{
>> +    int n = EDGE_COUNT (edges);
>> +    if (n == 0)
>> +	return false;
>> +
>> +    for (edge e : edges)
>> +	if (e->flags & EDGE_COMPLEX)
>> +	    n -= 1;
>> +
>> +    return n == 1;
>> +}
>> +
>> +/* Get the single, non-complex edge.  Behavior is undefined edges have more
>> +   than 1 non-complex edges. */
>> +edge
>> +single_edge (const vec<edge, va_gc> *edges)
>> +{
>> +    for (edge e : edges)
>> +    {
>> +	if (e->flags & EDGE_COMPLEX)
>> +	    continue;
>> +	return e;
>> +    }
>> +    return NULL;
>> +}
>> +
>> +/* Sometimes, for example with function calls and C++ destructors, the CFG gets
>> +   extra nodes that are essentially single-entry-single-exit in the middle of
>> +   boolean expressions.  For example:
>> +
>> +      x || can_throw (y)
>> +
>> +               A
>> +              /|
>> +             / |
>> +            B  |
>> +            |  |
>> +            C  |
>> +           / \ |
>> +          /   \|
>> +         F     T
>> +
>> +   Without the extra node inserted by the function + exception it becomes a
>> +   proper 2-term graph, not 2 single-term graphs.
>> +
>> +               A
>> +              /|
>> +             C |
>> +            / \|
>> +           F   T
>> +
>> +   contract_edge ignores the series of intermediate nodes and makes a virtual
>> +   edge A -> C without having to construct a new simplified CFG explicitly.  It
>> +   gets more complicated as non-conditional edges is how the body of the
>> +   then/else blocks are separated from the boolean expression, so only edges
>> +   that are inserted because of function calls in the expression itself must be
>> +   merged.
>> +
>> +   Only chains of single-exit single-entry nodes that end with a condition
>> +   should be contracted. */
>> +edge
>> +contract_edge (edge e)
>> +{
>> +    edge source = e;
>> +    while (true)
>> +    {
>> +	basic_block dest = e->dest;
>> +	if (!single (dest->preds))
>> +	    return source;
>> +	if (e->flags & EDGE_DFS_BACK)
>> +	    return source;
>> +	if (block_conditional_p (dest))
>> +	    return e;
>> +
>> +	e = single_edge (dest->succs);
>> +	if (!e)
>> +	    return source;
>> +    }
>> +}
>> +
>> +/* This is the predecessor dual of contract_edge; it collapses the predecessor
>> +   blocks between two operands in a boolean expression. */
>> +edge
>> +contract_edge_up (edge e)
>> +{
>> +    while (true)
>> +    {
>> +	basic_block src = e->src;
>> +	if (edge_conditional_p (e))
>> +	    return e;
>> +	if (!single (src->preds))
>> +	    return e;
>> +	e = single_edge (src->preds);
>> +    }
>> +}
>> +
>> +/* "Undo" an edge split.  Sometimes the sink of a boolean expression will be
>> +   split into multiple blocks to accurately track line coverage, for example
>> +   when there is a goto-label at the top of the then/else block:
>> +
>> +    if (a && b)
>> +    {
>> +	l1:
>> +	...
>> +    }
>> +    else
>> +    {
>> +	l2:
>> +	...
>> +    }
>> +
>> +    and the corresponding CFG where a1 and b1 are created in edge splits to the
>> +    same destination (F):
>> +
>> +    a
>> +    |\
>> +    | a1
>> +    b  \
>> +    |\  |
>> +    | b1|
>> +    |  \|
>> +    T   F
>> +
>> +    This function recognizes this shape and returns the "merges" the split
>> +    outcome block by returning their common successor.  In all other cases it is
>> +    the identity function.
>> +*/
>> +basic_block
>> +merge_split_outcome (basic_block b)
>> +{
>> +    if (!single (b->succs))
>> +	return b;
>> +    if (!single (b->preds))
>> +	return b;
>> +
>> +    const unsigned flag = single_edge (b->preds)->flags & EDGE_CONDITION;
>> +    if (!flag)
>> +	return b;
>> +
>> +    edge e = single_edge (b->succs);
>> +    for (edge pred : e->dest->preds)
>> +    {
>> +	if (!single (pred->src->preds))
>> +	    return b;
>> +	if (!(single_edge (pred->src->preds)->flags & flag))
>> +	    return b;
>> +    }
>> +    return e->dest;
>> +}
>> +
>> +
>> +/* Find the set {ancestors(p) intersect G} where ancestors is the recursive set
>> +   of predecessors for p.  Limiting to the ancestors that are also in G (see
>> +   cond_reachable_from) and by q is an optimization as ancestors outside G have
>> +   no effect when isolating expressions.
>> +
>> +   dfs_enumerate_from () does not work as the filter function needs edge
>> +   information and dfs_enumerate_from () only considers blocks. */
>> +void
>> +ancestors_of (basic_block p, basic_block q, const sbitmap G, sbitmap ancestors)
>> +{
>> +    if (!bitmap_bit_p (G, p->index))
>> +	return;
>> +
>> +    bitmap_set_bit (ancestors, p->index);
>> +    bitmap_set_bit (ancestors, q->index);
>> +    if (p == q)
>> +	return;
>> +
>> +    auto_vec<basic_block, 16> stack;
>> +    stack.safe_push (p);
>> +
>> +    while (!stack.is_empty())
>> +    {
>> +	basic_block b = stack.pop ();
>> +	if (single (b->preds))
>> +	{
>> +	    edge e = single_edge (b->preds);
>> +	    e = contract_edge_up (e);
>> +	    b = e->dest;
>> +	}
>> +
>> +	for (edge e : b->preds)
>> +	{
>> +	    basic_block src = e->src;
>> +	    if (bitmap_bit_p (ancestors, e->src->index))
>> +		continue;
>> +	    if (!bitmap_bit_p (G, e->src->index))
>> +		continue;
>> +	    bitmap_set_bit (ancestors, src->index);
>> +	    stack.safe_push (src);
>> +	}
>> +    }
>> +}
>> +
>> +/* A simple struct for storing/returning outcome block pairs.  Either both
>> +   blocks are set or both are NULL. */
>> +struct outcomes
>> +{
>> +    basic_block t = NULL;
>> +    basic_block f = NULL;
>> +
>> +    operator bool () const noexcept (true)
>> +    {
>> +	return t && f;
>> +    }
>> +};
>> +
>> +/* Get the true/false successors of a basic block. If b is not a conditional
>> +   block both edges are NULL. */
>> +outcomes
>> +conditional_succs (const basic_block b)
>> +{
>> +    outcomes c;
>> +    for (edge e : b->succs)
>> +    {
>> +	if (e->flags & EDGE_TRUE_VALUE)
>> +	    c.t = merge_split_outcome (e->dest);
>> +	if (e->flags & EDGE_FALSE_VALUE)
>> +	    c.f = merge_split_outcome (e->dest);
>> +    }
>> +
>> +    gcc_assert ((c.t && c.f) || (!c.t && !c.f));
>> +    return c;
>> +}
>> +
>> +/* Get the index or offset of a conditional flag, 0 for true and 1 for false.
>> +   These indices carry no semantics but must be consistent as they are used to
>> +   index into data structures in code generation and gcov. */
>> +unsigned
>> +condition_index (unsigned flag)
>> +{
>> +    return (flag & EDGE_CONDITION) == EDGE_TRUE_VALUE ? 0 : 1;
>> +}
>> +
>> +/* Compute the masking vector.
>> +
>> +   Masking and short circuiting are deeply connected - masking occurs when
>> +   control flow reaches a state that is also reachable with short circuiting.
>> +   In fact, masking corresponds to short circuiting in the CFG for the reversed
>> +   expression.  This means we can find the limits, the last term in preceeding
>> +   subexpressions, by following the edges that short circuit to the same
>> +   outcome.
>> +
>> +   In the simplest case a || b:
>> +
>> +   a
>> +   |\
>> +   | b
>> +   |/ \
>> +   T   F
>> +
>> +   T has has multiple incoming edges and is the outcome of a short circuit,
>> +   with top = a, bot = b.  The top node (a) is masked when the edge (b, T) is
>> +   taken.
>> +
>> +   The names "top" and "bot" refer to a pair of nodes with a shared
>> +   destination. The top is always the node corresponding to the left-most
>> +   operand of the two it holds that index_map[top] < index_map[bot].
>> +
>> +   Now consider (a && b) || (c && d) and its masking vectors:
>> +
>> +   a
>> +   |\
>> +   b \
>> +   |\|
>> +   | c
>> +   | |\
>> +   | d \
>> +   |/ \|
>> +   T   F
>> +
>> +   a[0] = {}
>> +   a[1] = {}
>> +   b[0] = {a}
>> +   b[1] = {}
>> +   c[0] = {}
>> +   c[1] = {}
>> +   d[0] = {c}
>> +   d[1] = {a,b}
>> +
>> +   Note that 0 and 1 are indices and not boolean values - a[0] is the index in
>> +   the masking vector when a takes the true edge.
>> +
>> +   b[0] and d[0] are identical to the a || b example, and d[1] is the bot in
>> +   the triangle [d, b] -> T.  b is the top node in the [d, b] relationship and
>> +   last term in (a && b).  To find the other terms masked we use the fact that
>> +   all nodes in an expression have outgoing edges to either the outcome or some
>> +   other node in the expression.  The "bot" node is also the last term in a
>> +   masked subexpression, so the problem becomes finding the subgraph where all
>> +   paths end up in the successors to bot.
>> +
>> +   We find the terms by marking the outcomes (in this case c, T) and walk the
>> +   predecessors starting at top (in this case b) and masking nodes when both
>> +   successors are marked.
>> +
>> +   The masking vector is represented as two bitfields per term in the
>> +   expression with the index corresponding to the term in the source
>> +   expression.  a || b && c becomes the term vector [a b c] and the masking
>> +   vectors [a[0] a[1] b[0] ...].  The kth bit of a masking vector is set if the
>> +   the kth term is masked by taking the edge. */
>> +void
>> +masking_vectors (conds_ctx& ctx, array_slice<basic_block> blocks,
>> +		 array_slice<gcov_type_unsigned> masks)
>> +{
>> +    gcc_assert (blocks.is_valid ());
>> +    gcc_assert (!blocks.empty ());
>> +    gcc_assert (masks.is_valid ());
>> +
>> +    sbitmap marks = ctx.G1;
>> +    sbitmap expr = ctx.G2;
>> +    vec<basic_block>& queue = ctx.B1;
>> +    vec<basic_block>& body = ctx.B2;
>> +    const vec<int>& index_map = ctx.index_map;
>> +    bitmap_clear (expr);
>> +
>> +    for (const basic_block b : blocks)
>> +	bitmap_set_bit (expr, b->index);
>> +
>> +    /* Set up for the iteration - include two outcome nodes in the traversal and
>> +       ignore the leading term since it cannot mask anything.  The algorithm is
>> +       not sensitive to the traversal order. */
>> +    body.truncate (0);
>> +    body.reserve (blocks.size () + 2);
>> +    for (const basic_block b : blocks)
>> +	body.quick_push (b);
>> +
>> +    outcomes out = conditional_succs (blocks.back ());
>> +    body.quick_push (out.t);
>> +    body.quick_push (out.f);
>> +    body[0] = body.pop ();
>> +
>> +    for (const basic_block b : body)
>> +    {
>> +	for (edge e1 : b->preds)
>> +	for (edge e2 : b->preds)
>> +	{
>> +	    const basic_block top = e1->src;
>> +	    const basic_block bot = e2->src;
>> +	    const unsigned cond = e1->flags & e2->flags & (EDGE_CONDITION);
>> +
>> +	    if (!cond)
>> +		continue;
>> +	    if (e1 == e2)
>> +		continue;
>> +	    if (!bitmap_bit_p (expr, top->index))
>> +		continue;
>> +	    if (!bitmap_bit_p (expr, bot->index))
>> +		continue;
>> +	    if (index_map[top->index] > index_map[bot->index])
>> +		continue;
>> +
>> +	    outcomes out = conditional_succs (top);
>> +	    gcc_assert (out);
>> +	    bitmap_clear (marks);
>> +	    bitmap_set_bit (marks, out.t->index);
>> +	    bitmap_set_bit (marks, out.f->index);
>> +	    queue.truncate (0);
>> +	    queue.safe_push (top);
>> +
>> +	    // The edge bot -> outcome triggers the masking
>> +	    const int m = 2*index_of (bot, blocks) + condition_index (cond);
>> +	    while (!queue.is_empty())
>> +	    {
>> +		basic_block q = queue.pop ();
>> +		/* q may have been processed & completed by being added to the
>> +		   queue multiple times, so check that there is still work to
>> +		   do before continuing. */
>> +		if (bitmap_bit_p (marks, q->index))
>> +		    continue;
>> +
>> +		outcomes succs = conditional_succs (q);
>> +		if (!bitmap_bit_p (marks, succs.t->index))
>> +		    continue;
>> +		if (!bitmap_bit_p (marks, succs.f->index))
>> +		    continue;
>> +
>> +		const int index = index_of (q, blocks);
>> +		gcc_assert (index != -1);
>> +		masks[m] |= gcov_type_unsigned (1) << index;
>> +		bitmap_set_bit (marks, q->index);
>> +
>> +		for (edge e : q->preds)
>> +		{
>> +		    e = contract_edge_up (e);
>> +		    if (!edge_conditional_p (e))
>> +			continue;
>> +		    if (e->flags & EDGE_DFS_BACK)
>> +			continue;
>> +		    if (bitmap_bit_p (marks, e->src->index))
>> +			continue;
>> +		    if (!bitmap_bit_p (expr, e->src->index))
>> +			continue;
>> +		    queue.safe_push (e->src);
>> +		}
>> +	    }
>> +	}
>> +    }
>> +}
>> +
>> +/* Find the nodes reachable from p by following only (possibly contracted)
>> +   condition edges dominated by p and ignore DFS back edges.  From a high level
>> +   this is partitioning the CFG into subgraphs by removing all non-condition
>> +   edges and selecting a single connected subgraph.  This creates a cut C = (G,
>> +   G') where G is the returned explicitly by this function.
>> +
>> +   It is assumed that all paths from p go through q (q post-dominates p).  p
>> +   must always be the first term in an expression and a condition node.
>> +
>> +   If |G| = 1 then this is a single term expression. If |G| > 1 then either
>> +   this is a multi-term expression or the first block in the then/else block is
>> +   a conditional expression as well.
>> +
>> +   Only nodes dominated by p is added - under optimization some blocks may be
>> +   merged and multiple independent conditions may share the same outcome
>> +   (making successors misidentified as a right operands), but true right-hand
>> +   operands are always dominated by the first term.
>> +
>> +   The function outputs both a bitmap and a vector as both are useful to the
>> +   caller. */
>> +void
>> +cond_reachable_from (basic_block p, basic_block q, sbitmap expr,
>> +		     vec<basic_block>& out)
>> +{
>> +    out.safe_push (p);
>> +    bitmap_set_bit (expr, p->index);
>> +    for (unsigned pos = 0; pos < out.length (); pos++)
>> +    {
>> +	for (edge e : out[pos]->succs)
>> +	{
>> +	    basic_block dest = contract_edge (e)->dest;
>> +	    if (dest == q)
>> +		continue;
>> +	    if (!dominated_by_p (CDI_DOMINATORS, dest, p))
>> +		continue;
>> +	    if (!block_conditional_p (dest))
>> +		continue;
>> +	    if (bitmap_bit_p (expr, dest->index))
>> +		continue;
>> +	    if (e->flags & EDGE_DFS_BACK)
>> +		continue;
>> +
>> +	    bitmap_set_bit (expr, dest->index);
>> +	    out.safe_push (dest);
>> +	}
>> +    }
>> +}
>> +
>> +/* Find the neighborhood of the graph G = [blocks, blocks+n), the
>> +   successors of nodes in G that are not also in G.  In the cut C = (G, G')
>> +   these are the nodes in G' with incoming edges that cross the span. */
>> +void
>> +neighborhood (const vec<basic_block>& blocks, sbitmap G, vec<basic_block>& out)
>> +{
>> +    for (const basic_block b : blocks)
>> +    {
>> +	for (edge e : b->succs)
>> +	{
>> +	    basic_block dest = contract_edge (e)->dest;
>> +	    if (bitmap_bit_p (G, dest->index))
>> +		continue;
>> +	    if (!out.contains (dest))
>> +		out.safe_push (dest);
>> +	}
>> +    }
>> +
>> +    /* Fix the neighborhood by correcting edge splits to the outcome nodes. */
>> +    for (unsigned i = 0; i != out.length (); i++)
>> +    {
>> +	basic_block prev = out[i];
>> +	basic_block next = merge_split_outcome (prev);
>> +	if (next->index != prev->index)
>> +	{
>> +	    bitmap_set_bit (G, prev->index);
>> +	    out[i] = next;
>> +	}
>> +    }
>> +}
>> +
>> +/* Find and isolate the expression starting at p.
>> +
>> +   Make a cut C = (G, G') following only condition edges.  G is a superset of
>> +   the expression B, but the walk may include expressions from the then/else
>> +   blocks if they start with conditions.  Only the subgraph B is the ancestor
>> +   of *both* the then/else outcome, which means B is the intersection of the
>> +   ancestors of the nodes in the neighborhood N(G). */
>> +void
>> +isolate_expression (conds_ctx &ctx, basic_block p, vec<basic_block>& out)
>> +{
>> +    sbitmap expr = ctx.G1;
>> +    sbitmap reachable = ctx.G2;
>> +    sbitmap ancestors = ctx.G3;
>> +    bitmap_clear (expr);
>> +    bitmap_clear (reachable);
>> +
>> +    vec<basic_block>& G = ctx.B1;
>> +    vec<basic_block>& NG = ctx.B2;
>> +    G.truncate (0);
>> +    NG.truncate (0);
>> +
>> +    basic_block post = get_immediate_dominator (CDI_POST_DOMINATORS, p);
>> +    cond_reachable_from (p, post, reachable, G);
>> +    if (G.length () == 1)
>> +    {
>> +	out.safe_push (p);
>> +	return;
>> +    }
>> +
>> +    neighborhood (G, reachable, NG);
>> +    bitmap_copy (expr, reachable);
>> +
>> +    for (const basic_block neighbor : NG)
>> +    {
>> +	bitmap_clear (ancestors);
>> +	for (edge e : neighbor->preds)
>> +	    ancestors_of (e->src, p, reachable, ancestors);
>> +	bitmap_and (expr, expr, ancestors);
>> +    }
>> +
>> +    for (const basic_block b : G)
>> +	if (bitmap_bit_p (expr, b->index))
>> +	    out.safe_push (b);
>> +    out.sort (cmp_index_map, &ctx.index_map);
>> +}
>> +
>> +/* Emit lhs = op1 <op> op2 on edges.  This emits non-atomic instructions and
>> +   should only be used on the local accumulators. */
>> +void
>> +emit_bitwise_op (edge e, tree lhs, tree op1, tree_code op, tree op2)
>> +{
>> +    tree tmp;
>> +    gassign *read;
>> +    gassign *bitw;
>> +    gimple *write;
>> +
>> +    tmp = make_temp_ssa_name (gcov_type_node, NULL, "__conditions_tmp");
>> +    read = gimple_build_assign (tmp, op1);
>> +    tmp = make_temp_ssa_name (gcov_type_node, NULL, "__conditions_tmp");
>> +    bitw = gimple_build_assign (tmp, op, gimple_assign_lhs (read), op2);
>> +    write = gimple_build_assign (lhs, gimple_assign_lhs (bitw));
>> +
>> +    gsi_insert_on_edge (e, read);
>> +    gsi_insert_on_edge (e, bitw);
>> +    gsi_insert_on_edge (e, write);
>> +}
>> +
>> +/* Visitor for make_index_map. */
>> +void
>> +make_index_map_visit (basic_block b, vec<basic_block>& L, vec<int>& marks)
>> +{
>> +    if (marks[b->index])
>> +	return;
>> +
>> +    for (edge e : b->succs)
>> +	if (!(e->flags & EDGE_DFS_BACK))
>> +	    make_index_map_visit (e->dest, L, marks);
>> +
>> +    marks[b->index] = 1;
>> +    L.quick_push (b);
>> +}
>> +
>> +/* Find a topological sorting of the blocks in a function so that left operands
>> +   are before right operands including subexpressions.  Sorting on block index
>> +   does not guarantee this property and the syntactical order of terms is very
>> +   important to the condition coverage.  The sorting algorithm is from Cormen
>> +   et al (2001) but with back-edges ignored and thus there is no need for
>> +   temporary marks (for cycle detection).
>> +
>> +   It is important to select unvisited nodes in DFS order to ensure the
>> +   roots/leading terms of boolean expressions are visited first (the other
>> +   terms being covered by the recursive step), but the visiting order of
>> +   individual boolean expressions carries no significance.
>> +
>> +   For the expression (a || (b && c) || d) the blocks should be [a b c d]. */
>> +void
>> +make_index_map (const vec<basic_block>& blocks, int max_index,
>> +		vec<basic_block>& L, vec<int>& index_map)
>> +{
>> +    L.truncate (0);
>> +    L.reserve (max_index);
>> +
>> +    /* Use of the output map as a temporary for tracking visited status. */
>> +    index_map.truncate (0);
>> +    index_map.safe_grow_cleared (max_index);
>> +    for (const basic_block b : blocks)
>> +	make_index_map_visit (b, L, index_map);
>> +
>> +    /* Insert canaries - if there are unreachable nodes (for example infinite
>> +       loops) then the unreachable nodes should never be needed for comparison,
>> +       and L.length () < max_index.  An index mapping should also never be
>> +       recorded twice. */
>> +    for (unsigned i = 0; i < index_map.length (); i++)
>> +	index_map[i] = -1;
>> +
>> +    gcc_assert (blocks.length () == L.length ());
>> +    L.reverse ();
>> +    const unsigned nblocks = L.length ();
>> +    for (unsigned i = 0; i < nblocks; i++)
>> +    {
>> +	gcc_assert (L[i]->index != -1);
>> +	index_map[L[i]->index] = int (i);
>> +    }
>> +}
>> +
>> +/* Walk the CFG and collect conditionals.
>> +
>> +   1. Collect a candidate set G by walking from the root following all
>> +      (contracted) condition edges.
>> +   2. This creates a cut C = (G, G'); find the neighborhood N(G).
>> +   3. For every node in N(G), follow the edges across the cut and collect all
>> +      ancestors (that are also in G).
>> +   4. The intersection of all these ancestor sets is the boolean expression B
>> +      that starts in root.
>> +
>> +   Walking is not guaranteed to find nodes in the order of the expression, it
>> +   might find (a || b) && c as [a c b], so the result must be sorted by the
>> +   index map. */
>> +const vec<basic_block>&
>> +collect_conditions (conds_ctx& ctx, const basic_block block)
>> +{
>> +    vec<basic_block>& blocks = ctx.blocks;
>> +    blocks.truncate (0);
>> +
>> +    if (bitmap_bit_p (ctx.marks, block->index))
>> +	return blocks;
>> +
>> +    if (!block_conditional_p (block))
>> +    {
>> +	ctx.mark (block);
>> +	return blocks;
>> +    }
>> +
>> +    isolate_expression (ctx, block, blocks);
>> +    ctx.mark (blocks);
>> +
>> +    if (blocks.length () > CONDITIONS_MAX_TERMS)
>> +    {
>> +	location_t loc = gimple_location (gsi_stmt (gsi_last_bb (block)));
>> +	warning_at (loc, OPT_Wcoverage_too_many_conditions,
>> +		    "Too many conditions (found %u); giving up coverage",
>> +		    blocks.length ());
>> +	blocks.truncate (0);
>> +    }
>> +    return blocks;
>> +}
>> +
>> +/* Used for dfs_enumerate_from () to include all reachable nodes. */
>> +bool
>> +yes (const_basic_block, const void *)
>> +{
>> +    return true;
>> +}
>> +
>> +}
>> +
>> +struct condcov {
>> +    explicit condcov (unsigned nblocks) noexcept (true) : ctx (nblocks) {}
>> +    auto_vec<int, 128> m_index;
>> +    auto_vec<basic_block, 256> m_blocks;
>> +    auto_vec<gcov_type_unsigned, 512> m_masks;
>> +    conds_ctx ctx;
>> +};
>> +
>> +unsigned
>> +cov_length (const struct condcov* cov)
>> +{
>> +    if (cov->m_index.is_empty ())
>> +	return 0;
>> +    return cov->m_index.length () - 1;
>> +}
>> +
>> +array_slice<basic_block>
>> +cov_blocks (struct condcov* cov, unsigned n)
>> +{
>> +    if (n >= cov->m_index.length ())
>> +	return array_slice<basic_block>::invalid ();
>> +
>> +    basic_block *begin = cov->m_blocks.begin () + cov->m_index[n];
>> +    basic_block *end = cov->m_blocks.begin () + cov->m_index[n + 1];
>> +    return array_slice<basic_block> (begin, end - begin);
>> +}
>> +
>> +array_slice<gcov_type_unsigned>
>> +cov_masks (struct condcov* cov, unsigned n)
>> +{
>> +    if (n >= cov->m_index.length ())
>> +	return array_slice<gcov_type_unsigned>::invalid ();
>> +
>> +    gcov_type_unsigned *begin = cov->m_masks.begin () + 2*cov->m_index[n];
>> +    gcov_type_unsigned *end = cov->m_masks.begin () + 2*cov->m_index[n + 1];
>> +    return array_slice<gcov_type_unsigned> (begin, end - begin);
>> +}
>> +
>> +void
>> +cov_free (struct condcov* cov)
>> +{
>> +    delete cov;
>> +}
>> +
>> +/* Condition coverage (MC/DC)
>> +
>> +   Algorithm
>> +   ---------
>> +   Whalen, Heimdahl, De Silva in "Efficient Test Coverage Measurement for
>> +   MC/DC" describe an algorithm for modified condition/decision coverage based
>> +   on AST analysis.  This algorithm analyses the control flow graph to analyze
>> +   expressions and compute masking vectors, but is inspired by their marking
>> +   functions for recording outcomes.  The individual phases are described in
>> +   more detail closer to the implementation.
>> +
>> +   The CFG is traversed in DFS order.  It is important that the first basic
>> +   block in an expression is the first one visited, but the order of
>> +   independent expressions does not matter.  When the function terminates,
>> +   every node in the dfs should have been processed and marked exactly once.
>> +   If there are unreachable nodes they are ignored and not instrumented.
>> +
>> +   The CFG is broken up into segments between dominators.  This isn't strictly
>> +   necessary, but since boolean expressions cannot cross dominators it makes
>> +   for a nice way to introduce limits to searches.
>> +
>> +   The coverage only considers the positions, not the symbols, in a
>> +   conditional, e.g. !A || (!B && A) is a 3-term conditional even though A
>> +   appears twice.  Subexpressions have no effect on term ordering:
>> +   (a && (b || (c && d)) || e) comes out as [a b c d e].
>> +
>> +   The output for gcov is a vector of pairs of unsigned integers, interpreted
>> +   as bit-sets, where the bit index corresponds to the index of the condition
>> +   in the expression.
>> + */
>> +struct condcov*
>> +find_conditions (struct function *fn)
>> +{
>> +    record_loop_exits ();
>> +    mark_dfs_back_edges (fn);
>> +
>> +    const bool have_dom = dom_info_available_p (fn, CDI_DOMINATORS);
>> +    const bool have_post_dom = dom_info_available_p (fn, CDI_POST_DOMINATORS);
>> +    if (!have_dom)
>> +	calculate_dominance_info (CDI_DOMINATORS);
>> +    if (!have_post_dom)
>> +	calculate_dominance_info (CDI_POST_DOMINATORS);
>> +
>> +    const unsigned nblocks = n_basic_blocks_for_fn (fn);
>> +    condcov *cov = new condcov (nblocks);
>> +    conds_ctx& ctx = cov->ctx;
>> +
>> +    auto_vec<basic_block, 16> dfs;
>> +    dfs.safe_grow (nblocks);
>> +    const basic_block entry = ENTRY_BLOCK_PTR_FOR_FN (fn);
>> +    const basic_block exit = ENTRY_BLOCK_PTR_FOR_FN (fn);
>> +    int n = dfs_enumerate_from (entry, 0, yes, dfs.address (), nblocks, exit);
>> +    dfs.truncate (n);
>> +    make_index_map (dfs, nblocks, ctx.B1, ctx.index_map);
>> +
>> +    /* Visit all reachable nodes and collect conditions.  DFS order is
>> +       important so the first node of a boolean expression is visited first
>> +       (it will mark subsequent terms). */
>> +    cov->m_index.safe_push (0);
>> +    for (const basic_block b : dfs)
>> +    {
>> +	const vec<basic_block>& expr = collect_conditions (ctx, b);
>> +	if (!expr.is_empty ())
>> +	{
>> +	    cov->m_blocks.safe_splice (expr);
>> +	    cov->m_index.safe_push (cov->m_blocks.length ());
>> +	}
>> +    }
>> +    gcc_assert (ctx.all_marked (dfs));
>> +
>> +    if (!have_dom)
>> +	free_dominance_info (fn, CDI_DOMINATORS);
>> +    if (!have_post_dom)
>> +	free_dominance_info (fn, CDI_POST_DOMINATORS);
>> +
>> +    cov->m_masks.safe_grow_cleared (2 * cov->m_index.last());
>> +    const unsigned length = cov_length (cov);
>> +    for (unsigned i = 0; i < length; i++)
>> +	masking_vectors (ctx, cov_blocks (cov, i), cov_masks (cov, i));
>> +
>> +    return cov;
>> +}
>> +
>> +int
>> +instrument_decisions (array_slice<basic_block> expr, unsigned condno,
>> +		      tree *accu, gcov_type_unsigned *masks)
>> +{
>> +    /* Zero the local accumulators. */
>> +    tree zero = build_int_cst (get_gcov_type (), 0);
>> +    for (edge e : expr[0]->succs)
>> +    {
>> +	gsi_insert_on_edge (e, gimple_build_assign (accu[0], zero));
>> +	gsi_insert_on_edge (e, gimple_build_assign (accu[1], zero));
>> +    }
>> +    /* Add instructions for updating the function-local accumulators.  */
>> +    for (size_t i = 0; i < expr.size (); i++)
>> +    {
>> +	for (edge e : expr[i]->succs)
>> +	{
>> +	    if (!edge_conditional_p (e))
>> +		continue;
>> +
>> +	    /* accu |= expr[i] */
>> +	    const int k = condition_index (e->flags);
>> +	    tree rhs = build_int_cst (gcov_type_node, 1ULL << i);
>> +	    emit_bitwise_op (e, accu[k], accu[k], BIT_IOR_EXPR, rhs);
>> +
>> +	    if (masks[2*i + k] == 0)
>> +		continue;
>> +
>> +	    /* accu &= mask[i] */
>> +	    tree mask = build_int_cst (gcov_type_node, ~masks[2*i + k]);
>> +	    for (int j = 0; j < 2; j++)
>> +		emit_bitwise_op (e, accu[j], accu[j], BIT_AND_EXPR, mask);
>> +	}
>> +    }
>> +
>> +    const bool atomic = flag_profile_update == PROFILE_UPDATE_ATOMIC;
>> +    const tree atomic_ior = builtin_decl_explicit
>> +	(TYPE_PRECISION (gcov_type_node) > 32
>> +	 ? BUILT_IN_ATOMIC_FETCH_OR_8
>> +	 : BUILT_IN_ATOMIC_FETCH_OR_4);
>> +
>> +    /* Add instructions for flushing the local accumulators.
>> +
>> +       It is important that the flushes happen on on the outcome's incoming
>> +       edges, otherwise flushes could be lost to exception handling.
>> +
>> +       void fn (int a)
>> +       {
>> +	   if (a)
>> +	    fclose();
>> +	   exit();
>> +       }
>> +
>> +       Can yield the CFG:
>> +       A
>> +       |\
>> +       | B
>> +       |/
>> +       e
>> +
>> +       This typically only happen in optimized builds, but gives linker errors
>> +       because the counter is left as an undefined symbol. */
>> +
>> +    outcomes out = conditional_succs (expr.back ());
>> +    const basic_block outcome_blocks[] = { out.t, out.t, out.f, out.f, };
>> +    const int outcome[] = { 0, 1, 0, 1 };
>> +    for (int i = 0; i < 4; i++)
>> +    {
>> +	const int k = outcome[i];
>> +	for (edge e : outcome_blocks[i]->preds)
>> +	{
>> +	    /* The outcome may have been split and we want to check if the
>> +	       edge is sourced from inside the expression, so contract it to
>> +	       find the source conditional edge. */
>> +	    e = contract_edge_up (e);
>> +
>> +	    /* Only instrument edges from inside the expression. Sometimes
>> +	       complicated control flow (like sigsetjmp and gotos) add
>> +	       predecessors that don't come from the boolean expression. */
>> +	    if (index_of (e->src, expr) == -1)
>> +		continue;
>> +
>> +	    tree ref = tree_coverage_counter_ref (GCOV_COUNTER_CONDS,
>> +						  2*condno + k);
>> +	    tree tmp = make_temp_ssa_name (gcov_type_node, NULL,
>> +					   "__conditions_tmp");
>> +	    if (atomic)
>> +	    {
>> +		tree relaxed = build_int_cst (integer_type_node,
>> +					      MEMMODEL_RELAXED);
>> +		ref = unshare_expr (ref);
>> +		gassign *read = gimple_build_assign (tmp, accu[k]);
>> +		gcall *flush = gimple_build_call (atomic_ior, 3,
>> +						  build_addr (ref),
>> +						  gimple_assign_lhs (read),
>> +						  relaxed);
>> +
>> +		gsi_insert_on_edge (e, read);
>> +		gsi_insert_on_edge (e, flush);
>> +	    }
>> +	    else
>> +	    {
>> +		gassign *read = gimple_build_assign (tmp, ref);
>> +		tmp = gimple_assign_lhs (read);
>> +		gsi_insert_on_edge (e, read);
>> +		ref = unshare_expr (ref);
>> +		emit_bitwise_op (e, ref, accu[k], BIT_IOR_EXPR, tmp);
>> +	    }
>> +	}
>> +    }
>> +    return expr.size ();
>> +}
>> +
>> +#undef CONDITIONS_MAX_TERMS
>> +#undef EDGE_CONDITION
>> +
>>  /* Do initialization work for the edge profiler.  */
>>  
>>  /* Add code:
>> @@ -758,7 +1802,7 @@ tree_profiling (void)
>>  	  thunk = true;
>>  	  /* When generate profile, expand thunk to gimple so it can be
>>  	     instrumented same way as other functions.  */
>> -	  if (profile_arc_flag)
>> +	  if (profile_arc_flag || profile_condition_flag)
>>  	    expand_thunk (node, false, true);
>>  	  /* Read cgraph profile but keep function as thunk at profile-use
>>  	     time.  */
>> @@ -803,7 +1847,7 @@ tree_profiling (void)
>>    release_profile_file_filtering ();
>>  
>>    /* Drop pure/const flags from instrumented functions.  */
>> -  if (profile_arc_flag || flag_test_coverage)
>> +  if (profile_arc_flag || profile_condition_flag || flag_test_coverage)
>>      FOR_EACH_DEFINED_FUNCTION (node)
>>        {
>>  	if (!gimple_has_body_p (node->decl)
>> @@ -897,7 +1941,7 @@ pass_ipa_tree_profile::gate (function *)
>>       disabled.  */
>>    return (!in_lto_p && !flag_auto_profile
>>  	  && (flag_branch_probabilities || flag_test_coverage
>> -	      || profile_arc_flag));
>> +	      || profile_arc_flag || profile_condition_flag));
>>  }
>>  
>>  } // anon namespace
>> diff --git a/libgcc/libgcov-merge.c b/libgcc/libgcov-merge.c
>> index 89741f637e1..9e3e8ee5657 100644
>> --- a/libgcc/libgcov-merge.c
>> +++ b/libgcc/libgcov-merge.c
>> @@ -33,6 +33,11 @@ void __gcov_merge_add (gcov_type *counters  __attribute__ ((unused)),
>>                         unsigned n_counters __attribute__ ((unused))) {}
>>  #endif
>>  
>> +#ifdef L_gcov_merge_ior
>> +void __gcov_merge_ior (gcov_type *counters  __attribute__ ((unused)),
>> +		       unsigned n_counters __attribute__ ((unused))) {}
>> +#endif
>> +
>>  #ifdef L_gcov_merge_topn
>>  void __gcov_merge_topn (gcov_type *counters  __attribute__ ((unused)),
>>  			unsigned n_counters __attribute__ ((unused))) {}
> 
> Gentle ping. I have a tuned the summary output slightly (decisions covered ->
> condition outcomes covered) already.

Ping. I would like to see this become a part of gcc 13, will we be able to
commit before the window closes?

Thanks,
Jørgen

^ permalink raw reply	[flat|nested] 39+ messages in thread

* [Ping 2][PATCH] Add condition coverage profiling
  2022-11-02  6:16   ` Jørgen Kvalsvik
@ 2022-11-09 20:08     ` Jørgen Kvalsvik
  2022-11-10 14:19     ` Ping [PATCH] " Martin Liška
  1 sibling, 0 replies; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-11-09 20:08 UTC (permalink / raw)
  To: gcc-patches

On 02/11/2022 07:16, Jørgen Kvalsvik wrote:
> On 25/10/2022 08:33, Jørgen Kvalsvik wrote:
>> On 12/10/2022 12:16, Jørgen Kvalsvik wrote:
>>> This patch adds support in gcc+gcov for modified condition/decision
>>> coverage (MC/DC) with the -fprofile-conditions flag. MC/DC is a type of
>>> test/code coverage and it is particularly important in the avation and
>>> automotive industries for safety-critical applications. MC/DC it is
>>> required for or recommended by:
>>>
>>>     * DO-178C for the most critical software (Level A) in avionics
>>>     * IEC 61508 for SIL 4
>>>     * ISO 26262-6 for ASIL D
>>>
>>> From the SQLite webpage:
>>>
>>>     Two methods of measuring test coverage were described above:
>>>     "statement" and "branch" coverage. There are many other test
>>>     coverage metrics besides these two. Another popular metric is
>>>     "Modified Condition/Decision Coverage" or MC/DC. Wikipedia defines
>>>     MC/DC as follows:
>>>
>>>         * Each decision tries every possible outcome.
>>>         * Each condition in a decision takes on every possible outcome.
>>>         * Each entry and exit point is invoked.
>>>         * Each condition in a decision is shown to independently affect
>>>           the outcome of the decision.
>>>
>>>     In the C programming language where && and || are "short-circuit"
>>>     operators, MC/DC and branch coverage are very nearly the same thing.
>>>     The primary difference is in boolean vector tests. One can test for
>>>     any of several bits in bit-vector and still obtain 100% branch test
>>>     coverage even though the second element of MC/DC - the requirement
>>>     that each condition in a decision take on every possible outcome -
>>>     might not be satisfied.
>>>
>>>     https://sqlite.org/testing.html#mcdc
>>>
>>> Wahlen, Heimdahl, and De Silva "Efficient Test Coverage Measurement for
>>> MC/DC" describes an algorithm for adding instrumentation by carrying
>>> over information from the AST, but my algorithm analyses the the control
>>> flow graph to instrument for coverage. This has the benefit of being
>>> programming language independent and faithful to compiler decisions
>>> and transformations, although I have only tested it on constructs in C
>>> and C++, see testsuite/gcc.misc-tests and testsuite/g++.dg.
>>>
>>> Like Wahlen et al this implementation records coverage in fixed-size
>>> bitsets which gcov knows how to interpret. This is very fast, but
>>> introduces a limit on the number of terms in a single boolean
>>> expression, the number of bits in a gcov_unsigned_type (which is
>>> typedef'd to uint64_t), so for most practical purposes this would be
>>> acceptable. This limitation is in the implementation and not the
>>> algorithm, so support for more conditions can be added by also
>>> introducing arbitrary-sized bitsets.
>>>
>>> For space overhead, the instrumentation needs two accumulators
>>> (gcov_unsigned_type) per condition in the program which will be written
>>> to the gcov file. In addition, every function gets a pair of local
>>> accumulators, but these accmulators are reused between conditions in the
>>> same function.
>>>
>>> For time overhead, there is a zeroing of the local accumulators for
>>> every condition and one or two bitwise operation on every edge taken in
>>> the an expression.
>>>
>>> In action it looks pretty similar to the branch coverage. The -g short
>>> opt carries no significance, but was chosen because it was an available
>>> option with the upper-case free too.
>>>
>>> gcov --conditions:
>>>
>>>         3:   17:void fn (int a, int b, int c, int d) {
>>>         3:   18:    if ((a && (b || c)) && d)
>>> condition outcomes covered 3/8
>>> condition  0 not covered (true false)
>>> condition  1 not covered (true)
>>> condition  2 not covered (true)
>>> condition  3 not covered (true)
>>>         1:   19:        x = 1;
>>>         -:   20:    else
>>>         2:   21:        x = 2;
>>>         3:   22:}
>>>
>>> gcov --conditions --json-format:
>>>
>>>     "conditions": [
>>>         {
>>>             "not_covered_false": [
>>>                 0
>>>             ],
>>>             "count": 8,
>>>             "covered": 3,
>>>             "not_covered_true": [
>>>                 0,
>>>                 1,
>>>                 2,
>>>                 3
>>>             ]
>>>         }
>>>     ],
>>>
>>> Some expressions, mostly those without else-blocks, are effectively
>>> "rewritten" in the CFG construction making the algorithm unable to
>>> distinguish them:
>>>
>>> and.c:
>>>
>>>     if (a && b && c)
>>>         x = 1;
>>>
>>> ifs.c:
>>>
>>>     if (a)
>>>         if (b)
>>>             if (c)
>>>                 x = 1;
>>>
>>> gcc will build the same graph for both these programs, and gcov will
>>> report boths as 3-term expressions. It is vital that it is not
>>> interpreted the other way around (which is consistent with the shape of
>>> the graph) because otherwise the masking would be wrong for the and.c
>>> program which is a more severe error. While surprising, users would
>>> probably expect some minor rewriting of semantically-identical
>>> expressions.
>>>
>>> and.c.gcov:
>>>     #####:    2:    if (a && b && c)
>>> decisions covered 6/6
>>>     #####:    3:        x = 1;
>>>
>>> ifs.c.gcov:
>>>     #####:    2:    if (a)
>>>     #####:    3:        if (b)
>>>     #####:    4:            if (c)
>>>     #####:    5:                x = 1;
>>> condition outcomes covered 6/6
>>>
>>> Adding else clauses alters the program (ifs.c can have 3 elses, and.c
>>> only 1) and coverage becomes less surprising
>>>
>>> ifs.c.gcov:
>>>     #####:    2:    if (a)
>>> condition outcomes covered 2/2
>>>     #####:    4:    {
>>>     #####:    4:        if (b)
>>> condition outcomes covered 2/2
>>>               5:        {
>>>     #####:    6:            if (c)
>>> condition outcomes covered 2/2
>>>     #####:    7:                x = 1;
>>>     #####:    8:        }
>>>     #####:    9:        else
>>>     #####:   10:            x = 2;
>>>     #####:   11:    }
>>>     #####:   12:    else
>>>     #####:   13:        x = 3;
>>>
>>> Since the algorithm works on CFGs, it cannot detect some ternary
>>> operator introduced conditionals. For example, int x = a ? 0 : 1 in
>>> gimple becomes _x = (_a == 0). From source you would expect coverage,
>>> but it gets neither branch nor condition coverage. For completeness, it
>>> could be achieved by scanning all gimple statements for such
>>> comparisons, and insert an extra instruction for recording the outcome.
>>>
>>> The test suite contains a lot of small programs functions. Some of these
>>> were designed by hand to test for specific behaviours and graph shapes,
>>> and some are previously-failed test cases in other programs adapted into
>>> the test suite.
>>>
>>> Alternative author email: Jørgen Kvalsvik <j@lambda.is>
>>>
>>> gcc/ChangeLog:
>>>
>>> 	* builtins.cc (expand_builtin_fork_or_exec): Check
>>> 	profile_condition_flag.
>>> 	* collect2.cc (main): Add -fno-profile-conditions to OBSTACK.
>>> 	* common.opt: Add new options -fprofile-conditions and
>>> 	* doc/gcov.texi: Add --conditions documentation.
>>> 	* doc/invoke.texi: Add -fprofile-conditions documentation.
>>> 	* gcc.cc: Link gcov on -fprofile-conditions.
>>> 	* gcov-counter.def (GCOV_COUNTER_CONDS): New.
>>> 	* gcov-dump.cc (tag_conditions): New.
>>> 	* gcov-io.h (GCOV_TAG_CONDS): New.
>>> 	(GCOV_TAG_CONDS_LENGTH): Likewise.
>>> 	(GCOV_TAG_CONDS_NUM): Likewise.
>>> 	* gcov.cc (class condition_info): New.
>>> 	(condition_info::condition_info): New.
>>> 	(condition_info::popcount): New.
>>> 	(struct coverage_info): New.
>>> 	(add_condition_counts): New.
>>> 	(output_conditions): New.
>>> 	(print_usage): Add -g, --conditions.
>>> 	(process_args): Likewise.
>>> 	(output_intermediate_json_line): Output conditions.
>>> 	(read_graph_file): Read conditions counters.
>>> 	(read_count_file): Read conditions counters.
>>> 	(file_summary): Print conditions.
>>> 	(accumulate_line_info): Accumulate conditions.
>>> 	(output_line_details): Print conditions.
>>> 	* ipa-inline.cc (can_early_inline_edge_p): Check
>>> 	profile_condition_flag.
>>> 	* ipa-split.cc (pass_split_functions::gate): Likewise.
>>> 	* passes.cc (finish_optimization_passes): Likewise.
>>> 	* profile.cc (find_conditions): New declaration.
>>> 	(cov_length): Likewise.
>>> 	(cov_blocks): Likewise.
>>> 	(cov_masks): Likewise.
>>> 	(cov_free): Likewise.
>>> 	(instrument_decisions): New.
>>> 	(read_thunk_profile): Control output to file.
>>> 	(branch_prob): Call find_conditions, instrument_decisions.
>>> 	(init_branch_prob): Add total_num_conds.
>>> 	(end_branch_prob): Likewise.
>>> 	* tree-profile.cc (struct conds_ctx): New.
>>> 	(CONDITIONS_MAX_TERMS): New.
>>> 	(EDGE_CONDITION): New.
>>> 	(cmp_index_map): New.
>>> 	(index_of): New.
>>> 	(block_conditional_p): New.
>>> 	(edge_conditional_p): New.
>>> 	(single): New.
>>> 	(single_edge): New.
>>> 	(contract_edge): New.
>>> 	(contract_edge_up): New.
>>> 	(merge_split_outcome): New.
>>> 	(ancestors_of): New.
>>> 	(struct outcomes): New.
>>> 	(conditional_succs): New.
>>> 	(condition_index): New.
>>> 	(masking_vectors): New.
>>> 	(cond_reachable_from): New.
>>> 	(neighborhood): New.
>>> 	(isolate_expression): New.
>>> 	(emit_bitwise_op): New.
>>> 	(make_index_map_visit): New.
>>> 	(make_index_map): New.
>>> 	(collect_conditions): New.
>>> 	(yes): New.
>>> 	(struct condcov): New.
>>> 	(cov_length): New.
>>> 	(cov_blocks): New.
>>> 	(cov_masks): New.
>>> 	(cov_free): New.
>>> 	(find_conditions): New.
>>> 	(instrument_decisions): New.
>>> 	(tree_profiling): Check profile_condition_flag.
>>> 	(pass_ipa_tree_profile::gate): Likewise.
>>>
>>> libgcc/ChangeLog:
>>>
>>> 	* libgcov-merge.c (__gcov_merge_ior): New dummy function.
>>>
>>> gcc/testsuite/ChangeLog:
>>>
>>> 	* lib/gcov.exp: Add condition coverage test function.
>>> 	* g++.dg/gcov/gcov-18.C: New test.
>>> 	* gcc.misc-tests/gcov-19.c: New test.
>>> 	* gcc.misc-tests/gcov-20.c: New test.
>>> 	* gcc.misc-tests/gcov-21.c: New test.
>>> ---
>>>  gcc/builtins.cc                        |    2 +-
>>>  gcc/collect2.cc                        |    7 +-
>>>  gcc/common.opt                         |    8 +
>>>  gcc/doc/gcov.texi                      |   36 +
>>>  gcc/doc/invoke.texi                    |   19 +
>>>  gcc/gcc.cc                             |    4 +-
>>>  gcc/gcov-counter.def                   |    3 +
>>>  gcc/gcov-dump.cc                       |   24 +
>>>  gcc/gcov-io.h                          |    3 +
>>>  gcc/gcov.cc                            |  200 +++-
>>>  gcc/ipa-inline.cc                      |    2 +-
>>>  gcc/ipa-split.cc                       |    3 +-
>>>  gcc/passes.cc                          |    3 +-
>>>  gcc/profile.cc                         |   84 +-
>>>  gcc/testsuite/g++.dg/gcov/gcov-18.C    |  234 +++++
>>>  gcc/testsuite/gcc.misc-tests/gcov-19.c | 1250 ++++++++++++++++++++++++
>>>  gcc/testsuite/gcc.misc-tests/gcov-20.c |   22 +
>>>  gcc/testsuite/gcc.misc-tests/gcov-21.c |   16 +
>>>  gcc/testsuite/lib/gcov.exp             |  191 +++-
>>>  gcc/tree-profile.cc                    | 1050 +++++++++++++++++++-
>>>  libgcc/libgcov-merge.c                 |    5 +
>>>  21 files changed, 3138 insertions(+), 28 deletions(-)
>>>  create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-18.C
>>>  create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-19.c
>>>  create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-20.c
>>>  create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-21.c
>>>
>>> diff --git a/gcc/builtins.cc b/gcc/builtins.cc
>>> index 5f319b28030..972ae7e2040 100644
>>> --- a/gcc/builtins.cc
>>> +++ b/gcc/builtins.cc
>>> @@ -5884,7 +5884,7 @@ expand_builtin_fork_or_exec (tree fn, tree exp, rtx target, int ignore)
>>>    tree call;
>>>  
>>>    /* If we are not profiling, just call the function.  */
>>> -  if (!profile_arc_flag)
>>> +  if (!profile_arc_flag && !profile_condition_flag)
>>>      return NULL_RTX;
>>>  
>>>    /* Otherwise call the wrapper.  This should be equivalent for the rest of
>>> diff --git a/gcc/collect2.cc b/gcc/collect2.cc
>>> index d81c7f28f16..0cd8bf4a3a3 100644
>>> --- a/gcc/collect2.cc
>>> +++ b/gcc/collect2.cc
>>> @@ -1032,9 +1032,9 @@ main (int argc, char **argv)
>>>        lto_mode = LTO_MODE_LTO;
>>>    }
>>>  
>>> -  /* -fno-profile-arcs -fno-test-coverage -fno-branch-probabilities
>>> -     -fno-exceptions -w -fno-whole-program */
>>> -  num_c_args += 6;
>>> +  /* -fno-profile-arcs -fno-profile-conditions -fno-test-coverage
>>> +     -fno-branch-probabilities -fno-exceptions -w -fno-whole-program */
>>> +  num_c_args += 7;
>>>  
>>>    c_argv = XCNEWVEC (char *, num_c_args);
>>>    c_ptr = CONST_CAST2 (const char **, char **, c_argv);
>>> @@ -1230,6 +1230,7 @@ main (int argc, char **argv)
>>>      }
>>>    obstack_free (&temporary_obstack, temporary_firstobj);
>>>    *c_ptr++ = "-fno-profile-arcs";
>>> +  *c_ptr++ = "-fno-profile-conditions";
>>>    *c_ptr++ = "-fno-test-coverage";
>>>    *c_ptr++ = "-fno-branch-probabilities";
>>>    *c_ptr++ = "-fno-exceptions";
>>> diff --git a/gcc/common.opt b/gcc/common.opt
>>> index bce3e514f65..5d0682e283a 100644
>>> --- a/gcc/common.opt
>>> +++ b/gcc/common.opt
>>> @@ -859,6 +859,10 @@ Wcoverage-invalid-line-number
>>>  Common Var(warn_coverage_invalid_linenum) Init(1) Warning
>>>  Warn in case a function ends earlier than it begins due to an invalid linenum macros.
>>>  
>>> +Wcoverage-too-many-conditions
>>> +Common Var(warn_too_many_conditions) Init(1) Warning
>>> +Warn when a conditional has too many terms and coverage gives up.
>>> +
>>>  Wmissing-profile
>>>  Common Var(warn_missing_profile) Init(1) Warning
>>>  Warn in case profiles in -fprofile-use do not exist.
>>> @@ -2318,6 +2322,10 @@ fprofile-arcs
>>>  Common Var(profile_arc_flag)
>>>  Insert arc-based program profiling code.
>>>  
>>> +fprofile-conditions
>>> +Common Var(profile_condition_flag)
>>> +Insert condition coverage profiling code.
>>> +
>>>  fprofile-dir=
>>>  Common Joined RejectNegative Var(profile_data_prefix)
>>>  Set the top-level directory for storing the profile data.
>>> diff --git a/gcc/doc/gcov.texi b/gcc/doc/gcov.texi
>>> index a1f7d26e610..7f2c0d00d94 100644
>>> --- a/gcc/doc/gcov.texi
>>> +++ b/gcc/doc/gcov.texi
>>> @@ -124,6 +124,7 @@ gcov [@option{-v}|@option{--version}] [@option{-h}|@option{--help}]
>>>       [@option{-a}|@option{--all-blocks}]
>>>       [@option{-b}|@option{--branch-probabilities}]
>>>       [@option{-c}|@option{--branch-counts}]
>>> +     [@option{-g}|@option{--conditions}]
>>>       [@option{-d}|@option{--display-progress}]
>>>       [@option{-f}|@option{--function-summaries}]
>>>       [@option{-j}|@option{--json-format}]
>>> @@ -169,6 +170,13 @@ be shown, unless the @option{-u} option is given.
>>>  Write branch frequencies as the number of branches taken, rather than
>>>  the percentage of branches taken.
>>>  
>>> +@item -g
>>> +@itemx --conditions
>>> +Write condition coverage to the output file, and write condition summary info
>>> +to the standard output.  This option allows you to see if the conditions in
>>> +your program at least once had an independent effect on the outcome of the
>>> +boolean expression (modified condition/decision coverage).
>>> +
>>>  @item -d
>>>  @itemx --display-progress
>>>  Display the progress on the standard output.
>>> @@ -293,6 +301,7 @@ Each @var{line} has the following form:
>>>  @{
>>>    "branches": ["$branch"],
>>>    "count": 2,
>>> +  "conditions": ["$condition"],
>>>    "line_number": 15,
>>>    "unexecuted_block": false,
>>>    "function_name": "foo",
>>> @@ -341,6 +350,33 @@ Fields of the @var{branch} element have following semantics:
>>>  @var{throw}: true when the branch is an exceptional branch
>>>  @end itemize
>>>  
>>> +Each @var{condition} element have the following semantics:
>>> +
>>> +@smallexample
>>> +@{
>>> +  "count": 4,
>>> +  "covered": 2,
>>> +  "not_covered_false": [],
>>> +  "not_covered_true": [0, 1],
>>> +@}
>>> +@end smallexample
>>> +
>>> +Fields of the @var{condition} element have following semantics:
>>> +
>>> +@itemize @bullet
>>> +@item
>>> +@var{count}: number of conditions in this expression
>>> +
>>> +@item
>>> +@var{covered}: number of covered conditions in this expression
>>> +
>>> +@item
>>> +@var{not_covered_true}: terms, by index, not seen as true in this expression
>>> +
>>> +@item
>>> +@var{not_covered_false}: terms, by index, not seen as false in this expression
>>> +@end itemize
>>> +
>>>  @item -H
>>>  @itemx --human-readable
>>>  Write counts in human readable format (like 24.6k).
>>> diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
>>> index a9ecc4426a4..62d48df369d 100644
>>> --- a/gcc/doc/invoke.texi
>>> +++ b/gcc/doc/invoke.texi
>>> @@ -614,6 +614,7 @@ Objective-C and Objective-C++ Dialects}.
>>>  @item Program Instrumentation Options
>>>  @xref{Instrumentation Options,,Program Instrumentation Options}.
>>>  @gccoptlist{-p  -pg  -fprofile-arcs  --coverage  -ftest-coverage @gol
>>> +-fprofile-conditions @gol
>>>  -fprofile-abs-path @gol
>>>  -fprofile-dir=@var{path}  -fprofile-generate  -fprofile-generate=@var{path} @gol
>>>  -fprofile-info-section  -fprofile-info-section=@var{name} @gol
>>> @@ -6140,6 +6141,13 @@ error.  @option{-Wno-coverage-invalid-line-number} can be used to disable the
>>>  warning or @option{-Wno-error=coverage-invalid-line-number} can be used to
>>>  disable the error.
>>>  
>>> +@item -Wno-coverage-too-many-conditions
>>> +@opindex Wno-coverage-too-many-conditions
>>> +@opindex Wcoverage-too-many-conditions
>>> +Warn in case a condition have too many terms and GCC gives up coverage.
>>> +Coverage is given up when there are more terms in the conditional than there
>>> +are bits in a @code{gcov_type_unsigned}.  This warning is enabled by default.
>>> +
>>>  @item -Wno-cpp @r{(C, Objective-C, C++, Objective-C++ and Fortran only)}
>>>  @opindex Wno-cpp
>>>  @opindex Wcpp
>>> @@ -15839,6 +15847,13 @@ E.g. @code{gcc a.c b.c -o binary} would generate @file{binary-a.gcda} and
>>>  
>>>  @xref{Cross-profiling}.
>>>  
>>> +@item -fprofile-conditions
>>> +@opindex fprofile-conditions
>>> +Add code so that program conditions are instrumented.  During execution the
>>> +program records what terms in a conditional contributes to a decision.  The
>>> +data may be used to verify that all terms in a booleans are tested and have an
>>> +effect on the outcome of a condition.
>>> +
>>>  @cindex @command{gcov}
>>>  @item --coverage
>>>  @opindex coverage
>>> @@ -15899,6 +15914,10 @@ executed.  When an arc is the only exit or only entrance to a block, the
>>>  instrumentation code can be added to the block; otherwise, a new basic
>>>  block must be created to hold the instrumentation code.
>>>  
>>> +With @option{-fprofile-conditions}, for each conditional in your program GCC
>>> +creates a bitset and records the boolean values that have an independent effect
>>> +on the outcome of that expression.
>>> +
>>>  @need 2000
>>>  @item -ftest-coverage
>>>  @opindex ftest-coverage
>>> diff --git a/gcc/gcc.cc b/gcc/gcc.cc
>>> index afb23cd07b0..601e48377de 100644
>>> --- a/gcc/gcc.cc
>>> +++ b/gcc/gcc.cc
>>> @@ -1145,7 +1145,7 @@ proper position among the other output files.  */
>>>  	%:include(libgomp.spec)%(link_gomp)}\
>>>      %{fgnu-tm:%:include(libitm.spec)%(link_itm)}\
>>>      %(mflib) " STACK_SPLIT_SPEC "\
>>> -    %{fprofile-arcs|fprofile-generate*|coverage:-lgcov} " SANITIZER_SPEC " \
>>> +    %{fprofile-arcs|fprofile-conditions|fprofile-generate*|coverage:-lgcov} " SANITIZER_SPEC " \
>>>      %{!nostdlib:%{!r:%{!nodefaultlibs:%(link_ssp) %(link_gcc_c_sequence)}}}\
>>>      %{!nostdlib:%{!r:%{!nostartfiles:%E}}} %{T*}  \n%(post_link) }}}}}}"
>>>  #endif
>>> @@ -1262,7 +1262,7 @@ static const char *cc1_options =
>>>   %{!fsyntax-only:%{S:%W{o*}%{!o*:-o %w%b.s}}}\
>>>   %{fsyntax-only:-o %j} %{-param*}\
>>>   %{coverage:-fprofile-arcs -ftest-coverage}\
>>> - %{fprofile-arcs|fprofile-generate*|coverage:\
>>> + %{fprofile-arcs|fprofile-conditions|fprofile-generate*|coverage:\
>>>     %{!fprofile-update=single:\
>>>       %{pthread:-fprofile-update=prefer-atomic}}}";
>>>  
>>> diff --git a/gcc/gcov-counter.def b/gcc/gcov-counter.def
>>> index 6d2182bd3db..96563a59a45 100644
>>> --- a/gcc/gcov-counter.def
>>> +++ b/gcc/gcov-counter.def
>>> @@ -49,3 +49,6 @@ DEF_GCOV_COUNTER(GCOV_COUNTER_IOR, "ior", _ior)
>>>  
>>>  /* Time profile collecting first run of a function */
>>>  DEF_GCOV_COUNTER(GCOV_TIME_PROFILER, "time_profiler", _time_profile)
>>> +
>>> +/* Conditions.  The counter is interpreted as a bit-set.  */
>>> +DEF_GCOV_COUNTER(GCOV_COUNTER_CONDS, "conditions", _ior)
>>> diff --git a/gcc/gcov-dump.cc b/gcc/gcov-dump.cc
>>> index 03023bfb226..6dc1df6e3e1 100644
>>> --- a/gcc/gcov-dump.cc
>>> +++ b/gcc/gcov-dump.cc
>>> @@ -38,6 +38,7 @@ static void print_version (void);
>>>  static void tag_function (const char *, unsigned, int, unsigned);
>>>  static void tag_blocks (const char *, unsigned, int, unsigned);
>>>  static void tag_arcs (const char *, unsigned, int, unsigned);
>>> +static void tag_conditions (const char *, unsigned, int, unsigned);
>>>  static void tag_lines (const char *, unsigned, int, unsigned);
>>>  static void tag_counters (const char *, unsigned, int, unsigned);
>>>  static void tag_summary (const char *, unsigned, int, unsigned);
>>> @@ -77,6 +78,7 @@ static const tag_format_t tag_table[] =
>>>    {GCOV_TAG_FUNCTION, "FUNCTION", tag_function},
>>>    {GCOV_TAG_BLOCKS, "BLOCKS", tag_blocks},
>>>    {GCOV_TAG_ARCS, "ARCS", tag_arcs},
>>> +  {GCOV_TAG_CONDS, "CONDITIONS", tag_conditions},
>>>    {GCOV_TAG_LINES, "LINES", tag_lines},
>>>    {GCOV_TAG_OBJECT_SUMMARY, "OBJECT_SUMMARY", tag_summary},
>>>    {0, NULL, NULL}
>>> @@ -392,6 +394,28 @@ tag_arcs (const char *filename ATTRIBUTE_UNUSED,
>>>      }
>>>  }
>>>  
>>> +static void
>>> +tag_conditions (const char *filename ATTRIBUTE_UNUSED,
>>> +		unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED,
>>> +		unsigned depth)
>>> +{
>>> +  unsigned n_conditions = GCOV_TAG_CONDS_NUM (length);
>>> +
>>> +  printf (" %u conditionals", n_conditions);
>>> +  if (flag_dump_contents)
>>> +    {
>>> +      for (unsigned ix = 0; ix != n_conditions; ix++)
>>> +	{
>>> +	  const unsigned blockno = gcov_read_unsigned ();
>>> +	  const unsigned nterms = gcov_read_unsigned ();
>>> +
>>> +	  printf ("\n");
>>> +	  print_prefix (filename, depth, gcov_position ());
>>> +	  printf (VALUE_PADDING_PREFIX "block %u:", blockno);
>>> +	  printf (" %u", nterms);
>>> +	}
>>> +    }
>>> +}
>>>  static void
>>>  tag_lines (const char *filename ATTRIBUTE_UNUSED,
>>>  	   unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED,
>>> diff --git a/gcc/gcov-io.h b/gcc/gcov-io.h
>>> index e91cd736556..a9b52cc6abc 100644
>>> --- a/gcc/gcov-io.h
>>> +++ b/gcc/gcov-io.h
>>> @@ -261,6 +261,9 @@ typedef uint64_t gcov_type_unsigned;
>>>  #define GCOV_TAG_ARCS		 ((gcov_unsigned_t)0x01430000)
>>>  #define GCOV_TAG_ARCS_LENGTH(NUM)  (1 + (NUM) * 2 * GCOV_WORD_SIZE)
>>>  #define GCOV_TAG_ARCS_NUM(LENGTH)  (((LENGTH / GCOV_WORD_SIZE) - 1) / 2)
>>> +#define GCOV_TAG_CONDS             ((gcov_unsigned_t)0x01470000)
>>> +#define GCOV_TAG_CONDS_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE)
>>> +#define GCOV_TAG_CONDS_NUM(LENGTH) (((LENGTH) / GCOV_WORD_SIZE) / 2)
>>>  #define GCOV_TAG_LINES		 ((gcov_unsigned_t)0x01450000)
>>>  #define GCOV_TAG_COUNTER_BASE 	 ((gcov_unsigned_t)0x01a10000)
>>>  #define GCOV_TAG_COUNTER_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE)
>>> diff --git a/gcc/gcov.cc b/gcc/gcov.cc
>>> index 9cf1071166f..bc2932e7890 100644
>>> --- a/gcc/gcov.cc
>>> +++ b/gcc/gcov.cc
>>> @@ -79,6 +79,7 @@ using namespace std;
>>>  class function_info;
>>>  class block_info;
>>>  class source_info;
>>> +class condition_info;
>>>  
>>>  /* Describes an arc between two basic blocks.  */
>>>  
>>> @@ -132,6 +133,28 @@ public:
>>>    vector<unsigned> lines;
>>>  };
>>>  
>>> +class condition_info
>>> +{
>>> +public:
>>> +  condition_info ();
>>> +
>>> +  int popcount () const;
>>> +
>>> +  gcov_type_unsigned truev;
>>> +  gcov_type_unsigned falsev;
>>> +
>>> +  unsigned n_terms;
>>> +};
>>> +
>>> +condition_info::condition_info (): truev (0), falsev (0), n_terms (0)
>>> +{
>>> +}
>>> +
>>> +int condition_info::popcount () const
>>> +{
>>> +    return __builtin_popcountll (truev) + __builtin_popcountll (falsev);
>>> +}
>>> +
>>>  /* Describes a basic block. Contains lists of arcs to successor and
>>>     predecessor blocks.  */
>>>  
>>> @@ -165,6 +188,8 @@ public:
>>>    /* Block is a landing pad for longjmp or throw.  */
>>>    unsigned is_nonlocal_return : 1;
>>>  
>>> +  condition_info conditions;
>>> +
>>>    vector<block_location_info> locations;
>>>  
>>>    struct
>>> @@ -275,6 +300,8 @@ public:
>>>    vector<block_info> blocks;
>>>    unsigned blocks_executed;
>>>  
>>> +  vector<condition_info*> conditions;
>>> +
>>>    /* Raw arc coverage counts.  */
>>>    vector<gcov_type> counts;
>>>  
>>> @@ -351,6 +378,9 @@ struct coverage_info
>>>    int branches_executed;
>>>    int branches_taken;
>>>  
>>> +  int conditions;
>>> +  int conditions_covered;
>>> +
>>>    int calls;
>>>    int calls_executed;
>>>  
>>> @@ -550,6 +580,10 @@ static int multiple_files = 0;
>>>  
>>>  static int flag_branches = 0;
>>>  
>>> +/* Output conditions (modified condition/decision coverage) */
>>> +
>>> +static int flag_conditions = 0;
>>> +
>>>  /* Show unconditional branches too.  */
>>>  static int flag_unconditional = 0;
>>>  
>>> @@ -656,6 +690,7 @@ static int read_count_file (void);
>>>  static void solve_flow_graph (function_info *);
>>>  static void find_exception_blocks (function_info *);
>>>  static void add_branch_counts (coverage_info *, const arc_info *);
>>> +static void add_condition_counts (coverage_info *, const block_info *);
>>>  static void add_line_counts (coverage_info *, function_info *);
>>>  static void executed_summary (unsigned, unsigned);
>>>  static void function_summary (const coverage_info *);
>>> @@ -664,6 +699,7 @@ static const char *format_gcov (gcov_type, gcov_type, int);
>>>  static void accumulate_line_counts (source_info *);
>>>  static void output_gcov_file (const char *, source_info *);
>>>  static int output_branch_count (FILE *, int, const arc_info *);
>>> +static void output_conditions (FILE *, const block_info *);
>>>  static void output_lines (FILE *, const source_info *);
>>>  static string make_gcov_file_name (const char *, const char *);
>>>  static char *mangle_name (const char *);
>>> @@ -928,6 +964,7 @@ print_usage (int error_p)
>>>    fnotice (file, "  -b, --branch-probabilities      Include branch probabilities in output\n");
>>>    fnotice (file, "  -c, --branch-counts             Output counts of branches taken\n\
>>>                                      rather than percentages\n");
>>> +  fnotice (file, "  -g, --conditions                Include condition/decision coverage in output\n");
>>>    fnotice (file, "  -d, --display-progress          Display progress information\n");
>>>    fnotice (file, "  -D, --debug			    Display debugging dumps\n");
>>>    fnotice (file, "  -f, --function-summaries        Output summaries for each function\n");
>>> @@ -980,6 +1017,7 @@ static const struct option options[] =
>>>    { "all-blocks",           no_argument,       NULL, 'a' },
>>>    { "branch-probabilities", no_argument,       NULL, 'b' },
>>>    { "branch-counts",        no_argument,       NULL, 'c' },
>>> +  { "conditions",           no_argument,       NULL, 'g' },
>>>    { "json-format",	    no_argument,       NULL, 'j' },
>>>    { "human-readable",	    no_argument,       NULL, 'H' },
>>>    { "no-output",            no_argument,       NULL, 'n' },
>>> @@ -1008,7 +1046,7 @@ process_args (int argc, char **argv)
>>>  {
>>>    int opt;
>>>  
>>> -  const char *opts = "abcdDfhHijklmno:pqrs:tuvwx";
>>> +  const char *opts = "abcdDfghHijklmno:pqrs:tuvwx";
>>>    while ((opt = getopt_long (argc, argv, opts, options, NULL)) != -1)
>>>      {
>>>        switch (opt)
>>> @@ -1025,6 +1063,9 @@ process_args (int argc, char **argv)
>>>  	case 'f':
>>>  	  flag_function_summary = 1;
>>>  	  break;
>>> +	case 'g':
>>> +	  flag_conditions = 1;
>>> +	  break;
>>>  	case 'h':
>>>  	  print_usage (false);
>>>  	  /* print_usage will exit.  */
>>> @@ -1132,6 +1173,45 @@ output_intermediate_json_line (json::array *object,
>>>  	  }
>>>        }
>>>  
>>> +  json::array *conditions = new json::array ();
>>> +  lineo->set ("conditions", conditions);
>>> +  if (flag_conditions)
>>> +  {
>>> +    vector<block_info *>::const_iterator it;
>>> +    for (it = line->blocks.begin (); it != line->blocks.end (); it++)
>>> +      {
>>> +	const condition_info& info = (*it)->conditions;
>>> +	if (info.n_terms == 0)
>>> +	    continue;
>>> +
>>> +	const int count = 2 * info.n_terms;
>>> +	const int covered = info.popcount ();
>>> +
>>> +	json::object *cond = new json::object ();
>>> +	cond->set ("count", new json::integer_number (count));
>>> +	cond->set ("covered", new json::integer_number (covered));
>>> +
>>> +	json::array *mtrue = new json::array ();
>>> +	json::array *mfalse = new json::array ();
>>> +	cond->set ("not_covered_true", mtrue);
>>> +	cond->set ("not_covered_false", mfalse);
>>> +
>>> +	if (count != covered)
>>> +	  {
>>> +	    for (unsigned i = 0; i < info.n_terms; i++)
>>> +	      {
>>> +		gcov_type_unsigned index = 1;
>>> +		index <<= i;
>>> +		if (!(index & info.truev))
>>> +		    mtrue->append (new json::integer_number (i));
>>> +		if (!(index & info.falsev))
>>> +		    mfalse->append (new json::integer_number (i));
>>> +	      }
>>> +	  }
>>> +	conditions->append (cond);
>>> +      }
>>> +  }
>>> +
>>>    object->append (lineo);
>>>  }
>>>  
>>> @@ -1956,6 +2036,28 @@ read_graph_file (void)
>>>  		  }
>>>  	    }
>>>  	}
>>> +      else if (fn && tag == GCOV_TAG_CONDS)
>>> +	{
>>> +	  unsigned num_dests = GCOV_TAG_CONDS_NUM (length);
>>> +
>>> +	  if (!fn->conditions.empty ())
>>> +	    fnotice (stderr, "%s:already seen conditions for '%s'\n",
>>> +		     bbg_file_name, fn->get_name ());
>>> +	  else
>>> +	    fn->conditions.resize (num_dests);
>>> +
>>> +	  for (unsigned i = 0; i < num_dests; ++i)
>>> +	    {
>>> +	      unsigned idx = gcov_read_unsigned ();
>>> +
>>> +	      if (idx >= fn->blocks.size ())
>>> +		goto corrupt;
>>> +
>>> +	      condition_info *info = &fn->blocks[idx].conditions;
>>> +	      info->n_terms = gcov_read_unsigned ();
>>> +	      fn->conditions[i] = info;
>>> +	    }
>>> +	}
>>>        else if (fn && tag == GCOV_TAG_LINES)
>>>  	{
>>>  	  unsigned blockno = gcov_read_unsigned ();
>>> @@ -2086,11 +2188,26 @@ read_count_file (void)
>>>  	      goto cleanup;
>>>  	    }
>>>  	}
>>> -      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn)
>>> +      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_CONDS) && fn)
>>>  	{
>>> +	  length = abs (read_length);
>>> +	  if (length != GCOV_TAG_COUNTER_LENGTH (2 * fn->conditions.size ()))
>>> +	      goto mismatch;
>>> +
>>> +	  if (read_length > 0)
>>> +	    {
>>> +	      for (ix = 0; ix != fn->conditions.size (); ix++)
>>> +		{
>>> +		  fn->conditions[ix]->truev  |= gcov_read_counter ();
>>> +		  fn->conditions[ix]->falsev |= gcov_read_counter ();
>>> +		}
>>> +	    }
>>> +	}
>>> +      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn)
>>> +      {
>>>  	  length = abs (read_length);
>>>  	  if (length != GCOV_TAG_COUNTER_LENGTH (fn->counts.size ()))
>>> -	    goto mismatch;
>>> +	      goto mismatch;
>>>  
>>>  	  if (read_length > 0)
>>>  	    for (ix = 0; ix != fn->counts.size (); ix++)
>>> @@ -2430,6 +2547,13 @@ add_branch_counts (coverage_info *coverage, const arc_info *arc)
>>>      }
>>>  }
>>>  
>>> +static void
>>> +add_condition_counts (coverage_info *coverage, const block_info *block)
>>> +{
>>> +  coverage->conditions += 2 * block->conditions.n_terms;
>>> +  coverage->conditions_covered += block->conditions.popcount ();
>>> +}
>>> +
>>>  /* Format COUNT, if flag_human_readable_numbers is set, return it human
>>>     readable format.  */
>>>  
>>> @@ -2533,6 +2657,18 @@ file_summary (const coverage_info *coverage)
>>>  		 coverage->calls);
>>>        else
>>>  	fnotice (stdout, "No calls\n");
>>> +
>>> +    }
>>> +
>>> +  if (flag_conditions)
>>> +    {
>>> +      if (coverage->conditions)
>>> +	fnotice (stdout, "Decisions covered:%s of %d\n",
>>> +		 format_gcov (coverage->conditions_covered,
>>> +			      coverage->conditions, 2),
>>> +		 coverage->conditions);
>>> +      else
>>> +	fnotice (stdout, "No decisions\n");
>>>      }
>>>  }
>>>  
>>> @@ -2767,6 +2903,12 @@ static void accumulate_line_info (line_info *line, source_info *src,
>>>  	 it != line->branches.end (); it++)
>>>        add_branch_counts (&src->coverage, *it);
>>>  
>>> +  if (add_coverage)
>>> +    for (vector<block_info *>::iterator it = line->blocks.begin ();
>>> +	 it != line->blocks.end (); it++)
>>> +      add_condition_counts (&src->coverage, *it);
>>> +
>>> +
>>>    if (!line->blocks.empty ())
>>>      {
>>>        /* The user expects the line count to be the number of times
>>> @@ -2868,6 +3010,33 @@ accumulate_line_counts (source_info *src)
>>>        }
>>>  }
>>>  
>>> +static void
>>> +output_conditions (FILE *gcov_file, const block_info *binfo)
>>> +{
>>> +    const condition_info& info = binfo->conditions;
>>> +    if (info.n_terms == 0)
>>> +	return;
>>> +
>>> +    const int expected = 2 * info.n_terms;
>>> +    const int got = info.popcount ();
>>> +
>>> +    fnotice (gcov_file, "condition outcomes covered %d/%d\n", got, expected);
>>> +    if (expected == got)
>>> +	return;
>>> +
>>> +    for (unsigned i = 0; i < info.n_terms; i++)
>>> +    {
>>> +	gcov_type_unsigned index = 1;
>>> +	index <<= i;
>>> +	if ((index & info.truev & info.falsev))
>>> +	    continue;
>>> +
>>> +	const char *t = (index & info.truev) ? "" : "true";
>>> +	const char *f = (index & info.falsev) ? "" : " false";
>>> +	fnotice (gcov_file, "condition %2u not covered (%s%s)\n", i, t, f + !t[0]);
>>> +    }
>>> +}
>>> +
>>>  /* Output information about ARC number IX.  Returns nonzero if
>>>     anything is output.  */
>>>  
>>> @@ -3078,16 +3247,29 @@ output_line_details (FILE *f, const line_info *line, unsigned line_num)
>>>  	  if (flag_branches)
>>>  	    for (arc = (*it)->succ; arc; arc = arc->succ_next)
>>>  	      jx += output_branch_count (f, jx, arc);
>>> +
>>> +	  if (flag_conditions)
>>> +	      output_conditions (f, *it);
>>>  	}
>>>      }
>>> -  else if (flag_branches)
>>> +  else
>>>      {
>>> -      int ix;
>>> +      if (flag_branches)
>>> +	{
>>> +	  int ix;
>>> +
>>> +	  ix = 0;
>>> +	  for (vector<arc_info *>::const_iterator it = line->branches.begin ();
>>> +		  it != line->branches.end (); it++)
>>> +	      ix += output_branch_count (f, ix, (*it));
>>> +	}
>>>  
>>> -      ix = 0;
>>> -      for (vector<arc_info *>::const_iterator it = line->branches.begin ();
>>> -	   it != line->branches.end (); it++)
>>> -	ix += output_branch_count (f, ix, (*it));
>>> +      if (flag_conditions)
>>> +	{
>>> +	  for (vector<block_info *>::const_iterator it = line->blocks.begin ();
>>> +	       it != line->blocks.end (); it++)
>>> +	      output_conditions (f, *it);
>>> +	}
>>>      }
>>>  }
>>>  
>>> diff --git a/gcc/ipa-inline.cc b/gcc/ipa-inline.cc
>>> index 14969198cde..3e37305843e 100644
>>> --- a/gcc/ipa-inline.cc
>>> +++ b/gcc/ipa-inline.cc
>>> @@ -646,7 +646,7 @@ can_early_inline_edge_p (struct cgraph_edge *e)
>>>  			 "  edge not inlinable: not in SSA form\n");
>>>        return false;
>>>      }
>>> -  else if (profile_arc_flag
>>> +  else if ((profile_arc_flag || profile_condition_flag)
>>>  	   && ((lookup_attribute ("no_profile_instrument_function",
>>>  				 DECL_ATTRIBUTES (caller->decl)) == NULL_TREE)
>>>  	       != (lookup_attribute ("no_profile_instrument_function",
>>> diff --git a/gcc/ipa-split.cc b/gcc/ipa-split.cc
>>> index 16734617d03..07d2b17ab12 100644
>>> --- a/gcc/ipa-split.cc
>>> +++ b/gcc/ipa-split.cc
>>> @@ -1929,7 +1929,8 @@ pass_split_functions::gate (function *)
>>>    /* When doing profile feedback, we want to execute the pass after profiling
>>>       is read.  So disable one in early optimization.  */
>>>    return (flag_partial_inlining
>>> -	  && !profile_arc_flag && !flag_branch_probabilities);
>>> +      && !profile_arc_flag && !flag_branch_probabilities
>>> +      && !profile_condition_flag);
>>>  }
>>>  
>>>  } // anon namespace
>>> diff --git a/gcc/passes.cc b/gcc/passes.cc
>>> index 78a07f8691a..3457ad1c5b3 100644
>>> --- a/gcc/passes.cc
>>> +++ b/gcc/passes.cc
>>> @@ -352,7 +352,8 @@ finish_optimization_passes (void)
>>>    gcc::dump_manager *dumps = m_ctxt->get_dumps ();
>>>  
>>>    timevar_push (TV_DUMP);
>>> -  if (profile_arc_flag || flag_test_coverage || flag_branch_probabilities)
>>> +  if (profile_arc_flag || profile_condition_flag || flag_test_coverage
>>> +      || flag_branch_probabilities)
>>>      {
>>>        dumps->dump_start (pass_profile_1->static_pass_number, NULL);
>>>        end_branch_prob ();
>>> diff --git a/gcc/profile.cc b/gcc/profile.cc
>>> index 96121d60711..f7f6d1f7197 100644
>>> --- a/gcc/profile.cc
>>> +++ b/gcc/profile.cc
>>> @@ -66,9 +66,19 @@ along with GCC; see the file COPYING3.  If not see
>>>  #include "cfgloop.h"
>>>  #include "sreal.h"
>>>  #include "file-prefix-map.h"
>>> +#include "stringpool.h"
>>>  
>>>  #include "profile.h"
>>>  
>>> +struct condcov;
>>> +struct condcov *find_conditions (struct function*);
>>> +unsigned cov_length (const struct condcov*);
>>> +array_slice<basic_block> cov_blocks (struct condcov*, unsigned);
>>> +array_slice<gcov_type_unsigned > cov_masks (struct condcov*, unsigned);
>>> +void cov_free (struct condcov*);
>>> +int instrument_decisions (array_slice<basic_block>, unsigned, tree*,
>>> +			  gcov_type_unsigned*);
>>> +
>>>  /* Map from BBs/edges to gcov counters.  */
>>>  vec<gcov_type> bb_gcov_counts;
>>>  hash_map<edge,gcov_type> *edge_gcov_counts;
>>> @@ -100,6 +110,7 @@ static int total_num_passes;
>>>  static int total_num_times_called;
>>>  static int total_hist_br_prob[20];
>>>  static int total_num_branches;
>>> +static int total_num_conds;
>>>  
>>>  /* Forward declarations.  */
>>>  static void find_spanning_tree (struct edge_list *);
>>> @@ -1155,6 +1166,12 @@ read_thunk_profile (struct cgraph_node *node)
>>>     the flow graph that are needed to reconstruct the dynamic behavior of the
>>>     flow graph.  This data is written to the gcno file for gcov.
>>>  
>>> +   When FLAG_PROFILE_CONDITIONS is nonzero, this functions instruments the
>>> +   edges in the control flow graph to track what conditions are evaluated to in
>>> +   order to determine what conditions are covered and have an independent
>>> +   effect on the outcome (modified condition/decision coverage).  This data is
>>> +   written to the gcno file for gcov.
>>> +
>>>     When FLAG_BRANCH_PROBABILITIES is nonzero, this function reads auxiliary
>>>     information from the gcda file containing edge count information from
>>>     previous executions of the function being compiled.  In this case, the
>>> @@ -1173,6 +1190,7 @@ branch_prob (bool thunk)
>>>    struct edge_list *el;
>>>    histogram_values values = histogram_values ();
>>>    unsigned cfg_checksum, lineno_checksum;
>>> +  bool output_to_file;
>>>  
>>>    total_num_times_called++;
>>>  
>>> @@ -1397,10 +1415,18 @@ branch_prob (bool thunk)
>>>  
>>>    /* Write the data from which gcov can reconstruct the basic block
>>>       graph and function line numbers (the gcno file).  */
>>> +  output_to_file = false;
>>>    if (coverage_begin_function (lineno_checksum, cfg_checksum))
>>>      {
>>>        gcov_position_t offset;
>>>  
>>> +      /* The condition coverage needs a deeper analysis to identify expressions
>>> +       * of conditions, which means it is not yet ready to write to the gcno
>>> +       * file.  It will write its entries later, but needs to know if it do it
>>> +       * in the first place, which is controlled by the return value of
>>> +       * coverage_begin_function. */
>>> +      output_to_file = true;
>>> +
>>>        /* Basic block flags */
>>>        offset = gcov_write_tag (GCOV_TAG_BLOCKS);
>>>        gcov_write_unsigned (n_basic_blocks_for_fn (cfun));
>>> @@ -1512,29 +1538,74 @@ branch_prob (bool thunk)
>>>  
>>>    remove_fake_edges ();
>>>  
>>> +  if (profile_condition_flag || profile_arc_flag)
>>> +      gimple_init_gcov_profiler ();
>>> +
>>> +  if (profile_condition_flag)
>>> +    {
>>> +      struct condcov *cov = find_conditions (cfun);
>>> +      gcc_assert (cov);
>>> +      const unsigned nconds = cov_length (cov);
>>> +      total_num_conds += nconds;
>>> +
>>> +      if (coverage_counter_alloc (GCOV_COUNTER_CONDS, 2 * nconds))
>>> +	{
>>> +	  /* Add two extra variables to the function for the local
>>> +	     accumulators, which are zero'd on the entry of a new conditional.
>>> +	     The local accumulators are shared between decisions in order to
>>> +	     use less stack space. */
>>> +	  tree accu[2] = {
>>> +	    build_decl (UNKNOWN_LOCATION, VAR_DECL,
>>> +			get_identifier ("__accu_t"), get_gcov_type ()),
>>> +	    build_decl (UNKNOWN_LOCATION, VAR_DECL,
>>> +			get_identifier ("__accu_f"), get_gcov_type ()),
>>> +	  };
>>> +
>>> +	  gcov_position_t offset {};
>>> +	  if (output_to_file)
>>> +	      offset = gcov_write_tag (GCOV_TAG_CONDS);
>>> +
>>> +	  for (unsigned i = 0; i < nconds; ++i)
>>> +	    {
>>> +	      array_slice<basic_block> expr = cov_blocks (cov, i);
>>> +	      array_slice<gcov_type_unsigned> masks = cov_masks (cov, i);
>>> +	      gcc_assert (expr.is_valid ());
>>> +	      gcc_assert (masks.is_valid ());
>>> +
>>> +	      int terms = instrument_decisions (expr, i, accu, masks.begin ());
>>> +	      if (output_to_file)
>>> +		{
>>> +		  gcov_write_unsigned (expr.front ()->index);
>>> +		  gcov_write_unsigned (terms);
>>> +		}
>>> +	    }
>>> +	  if (output_to_file)
>>> +	      gcov_write_length (offset);
>>> +	}
>>> +      cov_free (cov);
>>> +    }
>>> +
>>>    /* For each edge not on the spanning tree, add counting code.  */
>>>    if (profile_arc_flag
>>>        && coverage_counter_alloc (GCOV_COUNTER_ARCS, num_instrumented))
>>>      {
>>>        unsigned n_instrumented;
>>>  
>>> -      gimple_init_gcov_profiler ();
>>> -
>>>        n_instrumented = instrument_edges (el);
>>>  
>>>        gcc_assert (n_instrumented == num_instrumented);
>>>  
>>>        if (flag_profile_values)
>>>  	instrument_values (values);
>>> -
>>> -      /* Commit changes done by instrumentation.  */
>>> -      gsi_commit_edge_inserts ();
>>>      }
>>>  
>>>    free_aux_for_edges ();
>>>  
>>>    values.release ();
>>>    free_edge_list (el);
>>> +  /* Commit changes done by instrumentation.  */
>>> +  gsi_commit_edge_inserts ();
>>> +
>>>    coverage_end_function (lineno_checksum, cfg_checksum);
>>>    if (flag_branch_probabilities
>>>        && (profile_status_for_fn (cfun) == PROFILE_READ))
>>> @@ -1664,6 +1735,7 @@ init_branch_prob (void)
>>>    total_num_passes = 0;
>>>    total_num_times_called = 0;
>>>    total_num_branches = 0;
>>> +  total_num_conds = 0;
>>>    for (i = 0; i < 20; i++)
>>>      total_hist_br_prob[i] = 0;
>>>  }
>>> @@ -1703,5 +1775,7 @@ end_branch_prob (void)
>>>  		     (total_hist_br_prob[i] + total_hist_br_prob[19-i]) * 100
>>>  		     / total_num_branches, 5*i, 5*i+5);
>>>  	}
>>> +      fprintf (dump_file, "Total number of conditions: %d\n",
>>> +	       total_num_conds);
>>>      }
>>>  }
>>> diff --git a/gcc/testsuite/g++.dg/gcov/gcov-18.C b/gcc/testsuite/g++.dg/gcov/gcov-18.C
>>> new file mode 100644
>>> index 00000000000..310ed5297c0
>>> --- /dev/null
>>> +++ b/gcc/testsuite/g++.dg/gcov/gcov-18.C
>>> @@ -0,0 +1,234 @@
>>> +/* { dg-options "--coverage -fprofile-conditions -std=c++11" } */
>>> +/* { dg-do run { target native } } */
>>> +
>>> +#include <vector>
>>> +#include <stdexcept>
>>> +
>>> +class nontrivial_destructor
>>> +{
>>> +public:
>>> +    explicit nontrivial_destructor (int v) : val (v) {}
>>> +    ~nontrivial_destructor () {}
>>> +
>>> +    explicit operator bool() const { return bool(val); }
>>> +
>>> +    int val;
>>> +};
>>> +
>>> +int identity (int x) { return x; }
>>> +int throws (int) { throw std::runtime_error("exception"); }
>>> +
>>> +int throw_if (int x)
>>> +{
>>> +    if (x) /* conditions(1/2) true(0) */
>>> +	   /* conditions(end) */
>>> +	throw std::runtime_error("exception");
>>> +    return x;
>>> +}
>>> +
>>> +/* used for side effects to insert nodes in conditional bodies etc. */
>>> +int x = 0;
>>> +
>>> +/* conditionals work in the presence of non-trivial destructors */
>>> +void mcdc001a (int a)
>>> +{
>>> +    nontrivial_destructor v (a);
>>> +
>>> +    if (v.val > 0) /* conditions(2/2) */
>>> +	x = v.val;
>>> +    else
>>> +	x = -v.val;
>>> +}
>>> +
>>> +/* non-trivial destructor in-loop temporary */
>>> +nontrivial_destructor
>>> +mcdc002a (int a, int b)
>>> +{
>>> +    for (int i = 0; i < a; i++) /* conditions(2/2) */
>>> +    {
>>> +	nontrivial_destructor tmp (a);
>>> +	if (tmp.val % b) /* conditions(2/2) */
>>> +	    return nontrivial_destructor (0);
>>> +	x += i;
>>> +    } /* conditions(suppress) */
>>> +      /* conditions(end) */
>>> +
>>> +    return nontrivial_destructor (a * b);
>>> +}
>>> +
>>> +/* conditional in constructor */
>>> +void mcdc003a (int a)
>>> +{
>>> +    class C
>>> +    {
>>> +    public:
>>> +	explicit C (int e) : v (e)
>>> +	{
>>> +	    if (e) /* conditions(1/2) false(0) */
>>> +		v = x - e;
>>> +	}
>>> +	int v;
>>> +    };
>>> +
>>> +    C c (a);
>>> +    if (c.v > 2) /* conditions(1/2) true(0) */
>>> +		 /* conditions(end) */
>>> +	x = c.v + a;
>>> +}
>>> +
>>> +/* conditional in destructor */
>>> +void mcdc004a (int a)
>>> +{
>>> +    class C
>>> +    {
>>> +    public:
>>> +	explicit C (int e) : v (e) {}
>>> +	~C ()
>>> +	{
>>> +	    if (v) /* conditions(2/2) */
>>> +		x = 2 * v;
>>> +	}
>>> +	int v;
>>> +    };
>>> +
>>> +    C c (a);
>>> +    x = 1; // arbitrary action between ctor+dtor
>>> +}
>>> +
>>> +/* conditional in try */
>>> +void mcdc005a (int a)
>>> +{
>>> +    try
>>> +    {
>>> +	if (a) /* conditions(1/2) true(0) */
>>> +	       /* conditions(end) */
>>> +	    x = 2 * identity (a);
>>> +	else
>>> +	    x = 1;
>>> +    }
>>> +    catch (...)
>>> +    {
>>> +	x = 0;
>>> +    }
>>> +}
>>> +
>>> +/* conditional in catch */
>>> +void mcdc006a (int a) {
>>> +    try
>>> +    {
>>> +	throws (a);
>>> +    }
>>> +    catch (std::exception&)
>>> +    {
>>> +	if (a) /* conditions(1/2) false(0) */
>>> +	       /* conditions(end) */
>>> +	    x = identity (a);
>>> +	else
>>> +	    x = 0;
>>> +    }
>>> +}
>>> +
>>> +void mcdc006b (int a)
>>> +{
>>> +    if (a) /* conditions(1/2) true(0) */
>>> +	   /* conditions(end) */
>>> +	throws (a);
>>> +    else
>>> +	x = 1;
>>> +}
>>> +
>>> +void mcdc006c (int a) try
>>> +{
>>> +    throws (a);
>>> +}
>>> +catch (...) {
>>> +    if (a) /* conditions(2/2) */
>>> +	x = 5;
>>> +}
>>> +
>>> +/* temporary with destructor as term */
>>> +void mcdc007a (int a, int b)
>>> +{
>>> +    x = a && nontrivial_destructor (b); /* conditions(3/4) false(1) destructor() */
>>> +}
>>> +
>>> +void mcdc007b (int a, int b)
>>> +{
>>> +    if (a || throw_if (b)) /* conditions(3/4) true(1) destructor() */
>>> +	x = -1;
>>> +    else
>>> +	x = 1;
>>> +}
>>> +
>>> +void mcdc007c (int a, int b)
>>> +{
>>> +    if (throw_if (a) || throw_if (b)) /* conditions(2/4) true(0 1) destructor() */
>>> +	x = -1;
>>> +    else
>>> +	x = 1;
>>> +}
>>> +
>>> +/* destructor with delete */
>>> +void mcdc008a (int a)
>>> +{
>>> +    class C
>>> +    {
>>> +    public:
>>> +        int size = 5;
>>> +        int* ptr = nullptr;
>>> +
>>> +        explicit C (int v) : size (v + 5), ptr (new int[size]) /* conditions(suppress) */
>>> +							       /* conditions(end) */
>>> +        {
>>> +	    for (int i = 0; i < size; i++) /* conditions(2/2) */
>>> +		ptr[i] = i + 1;
>>> +        }
>>> +        ~C()
>>> +        {
>>> +	    // delete with implicit nullptr check
>>> +	    delete ptr; /* conditions(1/2) false(0) */
>>> +			/* conditions(end) */
>>> +	}
>>> +    };
>>> +
>>> +    C c (a);
>>> +    if (c.ptr[a + 1]) /* conditions(1/2) false(0) */
>>> +	x = a;
>>> +}
>>> +
>>> +int
>>> +main (void)
>>> +{
>>> +    mcdc001a (0);
>>> +    mcdc001a (1);
>>> +
>>> +    mcdc002a (1, 1);
>>> +    mcdc002a (1, 2);
>>> +
>>> +    mcdc003a (1);
>>> +
>>> +    mcdc004a (0);
>>> +    mcdc004a (1);
>>> +
>>> +    mcdc005a (0);
>>> +
>>> +    mcdc006a (1);
>>> +
>>> +    mcdc006b (0);
>>> +
>>> +    mcdc006c (0);
>>> +    mcdc006c (1);
>>> +
>>> +    mcdc007a (0, 0);
>>> +    mcdc007a (1, 1);
>>> +
>>> +    mcdc007b (0, 0);
>>> +    mcdc007b (1, 0);
>>> +
>>> +    mcdc007c (0, 0);
>>> +
>>> +    mcdc008a (1);
>>> +
>>> +}
>>> +
>>> +/* { dg-final { run-gcov conditions { --conditions gcov-18.C } } } */
>>> diff --git a/gcc/testsuite/gcc.misc-tests/gcov-19.c b/gcc/testsuite/gcc.misc-tests/gcov-19.c
>>> new file mode 100644
>>> index 00000000000..1adff7c76f4
>>> --- /dev/null
>>> +++ b/gcc/testsuite/gcc.misc-tests/gcov-19.c
>>> @@ -0,0 +1,1250 @@
>>> +/* { dg-options "-fprofile-conditions -ftest-coverage" } */
>>> +/* { dg-do run { target native } } */
>>> +
>>> +/* some side effect to stop branches from being pruned */
>>> +int x = 0;
>>> +
>>> +/* || works */
>>> +void
>>> +mcdc001a (int a, int b)
>>> +{
>>> +    if (a || b) /* conditions(1/4) true(0) false(0 1) */
>>> +		/* conditions(end) */
>>> +	x = 1;
>>> +    else
>>> +	x = 2;
>>> +}
>>> +
>>> +void
>>> +mcdc001b (int a, int b)
>>> +{
>>> +    if (a || b) /* conditions(3/4) true(0) */
>>> +		/* conditions(end) */
>>> +	x = 1;
>>> +    else
>>> +	x = 2;
>>> +}
>>> +
>>> +void
>>> +mcdc001c (int a, int b)
>>> +{
>>> +    if (a || b) /* conditions(4/4) */
>>> +	x = 1;
>>> +    else
>>> +	x = 2;
>>> +}
>>> +
>>> +void
>>> +mcdc001d (int a, int b, int c)
>>> +{
>>> +    if (a || b || c) /* conditions(2/6) false(0 1 2) true(2) */
>>> +		     /* conditions(end) */
>>> +	x = 1;
>>> +}
>>> +
>>> +/* && works */
>>> +void
>>> +mcdc002a (int a, int b)
>>> +{
>>> +    if (a && b) /* conditions(1/4) true(0 1) false(0) */
>>> +		/* conditions(end) */
>>> +	x = 1;
>>> +    else
>>> +	x = 2;
>>> +}
>>> +
>>> +void
>>> +mcdc002b (int a, int b)
>>> +{
>>> +    if (a && b) /* conditions(3/4) false(0) */
>>> +		/* conditions(end) */
>>> +	x = 1;
>>> +    else
>>> +	x = 2;
>>> +}
>>> +
>>> +void
>>> +mcdc002c (int a, int b)
>>> +{
>>> +    if (a && b) /* conditions(4/4) */
>>> +	x = 1;
>>> +    else
>>> +	x = 2;
>>> +}
>>> +
>>> +void
>>> +mcdc002d (int a, int b, int c)
>>> +{
>>> +    if (a && b && c) /* conditions(4/6) false(0 2) */
>>> +		     /* conditions(end) */
>>> +	x = 1;
>>> +}
>>> +
>>> +/* negation works */
>>> +void
>>> +mcdc003a (int a, int b)
>>> +{
>>> +    if (!a || !b) /* conditions(2/4) false(0 1) */
>>> +		  /* conditions(end) */
>>> +	x = 1;
>>> +    else
>>> +	x = 2;
>>> +}
>>> +
>>> +/* single conditionals with and without else */
>>> +void
>>> +mcdc004a (int a)
>>> +{
>>> +    if (a) /* conditions(1/2) true(0) */
>>> +	   /* conditions(end) */
>>> +	x = 1;
>>> +    else
>>> +	x = 2;
>>> +}
>>> +
>>> +void
>>> +mcdc004b (int a)
>>> +{
>>> +    if (a) /* conditions(2/2) */
>>> +	x = 1;
>>> +    else
>>> +	x = 2;
>>> +}
>>> +
>>> +void
>>> +mcdc004c (int a)
>>> +{
>>> +    if (a) /* conditions(1/2) false(0) */
>>> +	   /* conditions(end) */
>>> +	x = 1;
>>> +}
>>> +
>>> +void
>>> +mcdc004d (int a, int b, int c)
>>> +{
>>> +    /* With no else this is interpreted as (a && (b || c)) */
>>> +    if (a)  /* conditions(3/6) true(2) false(1 2)*/
>>> +    {
>>> +	if (b || c)
>>> +	    x = a + b + c;
>>> +    }
>>> +}
>>> +
>>> +void
>>> +mcdc004e (int a, int b, int c)
>>> +{
>>> +    /* With the else, this is interpreted as 2 expressions */
>>> +    if (a)  /* conditions(2/2) */
>>> +    {
>>> +	if (b || c) /* conditions(1/4) true(1) false(0 1) */
>>> +	    x = a + b + c;
>>> +    }
>>> +    else
>>> +    {
>>> +	x = c;
>>> +    }
>>> +}
>>> +
>>> +/* mixing && and || works */
>>> +void
>>> +mcdc005a (int a, int b, int c)
>>> +{
>>> +    if ((a && b) || c) /* conditions(1/6) true(0 1) false(0 1 2) */
>>> +		       /* conditions(end) */
>>> +	x = 1;
>>> +    else
>>> +	x = 2;
>>> +}
>>> +
>>> +void
>>> +mcdc005b (int a, int b, int c, int d)
>>> +{
>>> +    /* This is where masking MC/DC gets unintuitive:
>>> +
>>> +       1 1 0 0 => covers 1 (d = 0) as && 0 masks everything to the left
>>> +       1 0 0 0 => covers 2 (b = 0, c = 0) as (a && 0) masks a and d is never
>>> +       evaluated. */
>>> +    if ((a && (b || c)) && d) /* conditions(3/8) true(0 1 2 3) false(0) */
>>> +			      /* conditions(end) */
>>> +	x = 1;
>>> +    else
>>> +	x = 2;
>>> +}
>>> +
>>> +void
>>> +mcdc005c (int a, int b, int c, int d)
>>> +{
>>> +    if (a || (b && c) || d) /* conditions(2/8) true(0 3) false(0 1 2 3) */
>>> +			    /* conditions(end) */
>>> +        x = a + b + c + d;
>>> +}
>>> +
>>> +void
>>> +mcdc005d (int a, int b, int c, int d)
>>> +{
>>> +    /* This test is quite significant - it has a single input
>>> +       (1, 0, 0, 0) and tests specifically for when a multi-term left operand
>>> +       is masked. d = 0 should mask a || b and for the input there are no other
>>> +       sources for masking a (since b = 0). */
>>> +    if ((a || b) && (c || d)) /* conditions(2/8) true(0 1 2 3) false(0 1) */
>>> +			      /* conditions(end) */
>>> +	x = a + b;
>>> +    else
>>> +	x = c + d;
>>> +}
>>> +
>>> +/* nested conditionals */
>>> +void
>>> +mcdc006a (int a, int b, int c, int d, int e)
>>> +{
>>> +    if (a) /* conditions(2/2) */
>>> +    {
>>> +	if (b && c) /* conditions(3/4) false(1) */
>>> +		    /* conditions(end) */
>>> +	    x = 1;
>>> +	else
>>> +	    x = 2;
>>> +    }
>>> +    else
>>> +    {
>>> +	if (c || d) /* conditions(2/4) true(0 1) */
>>> +		    /* conditions(end) */
>>> +	    x = 3;
>>> +	else
>>> +	    x = 4;
>>> +    }
>>> +}
>>> +
>>> +void
>>> +mcdc006b (int a, int b, int c)
>>> +{
>>> +    if (a) /* conditions(6/6) */
>>> +	if (b)
>>> +	    if (c)
>>> +		x = a + b + c;
>>> +}
>>> +
>>> +void
>>> +mcdc006c (int a, int b, int c)
>>> +{
>>> +    if (a) /* conditions(2/2) */
>>> +    {
>>> +	if (b) /*conditions(2/2) */
>>> +	{
>>> +	    if (c) /* conditions(2/2) */
>>> +	    {
>>> +		x = a + b + c;
>>> +	    }
>>> +	}
>>> +	else
>>> +	{
>>> +	    x = b;
>>> +	}
>>> +    }
>>> +    else
>>> +    {
>>> +	x = a;
>>> +    }
>>> +}
>>> +
>>> +/* else/if */
>>> +void
>>> +mcdc007a (int a, int b, int c, int d)
>>> +{
>>> +    if (a) /* conditions(2/2) */
>>> +    {
>>> +	if (b) /* conditions(1/2) true(0) */
>>> +	       /* conditions(end) */
>>> +	    x = 1;
>>> +	else
>>> +	    x = 2;
>>> +    }
>>> +    else if (c) /* conditions(2/2) */
>>> +    {
>>> +	if (d) /* conditions(1/2) true(0) */
>>> +	       /* conditions(end) */
>>> +	    x = 3;
>>> +	else
>>> +	    x = 4;
>>> +    }
>>> +}
>>> +
>>> +void
>>> +mcdc007b (int a, int b, int c)
>>> +{
>>> +    goto begin;
>>> +then:
>>> +    x = 1;
>>> +    return;
>>> +begin:
>>> +    /* Evaluates to if (a || b || c) x = 1 */
>>> +    if (a)	    /* conditions(5/6) true(2) */
>>> +		    /* conditions(end) */
>>> +	goto then;
>>> +    else if (b)
>>> +	goto then;
>>> +    else if (c)
>>> +	goto then;
>>> +}
>>> +
>>> +void
>>> +mcdc007c (int a, int b, int c)
>>> +{
>>> +    goto begin;
>>> +then1:
>>> +    x = 1;
>>> +    return;
>>> +then2:
>>> +    x = 1;
>>> +    return;
>>> +then3:
>>> +    x = 1;
>>> +    return;
>>> +begin:
>>> +    /* similar to if (a || b || c) x = 1 */
>>> +    if (a) /* conditions(2/2) */
>>> +	goto then1;
>>> +    else if (b) /* conditions(2/2) */
>>> +	goto then2;
>>> +    else if (c) /* conditions(1/2) true(0) */
>>> +		/* conditions(end) */
>>> +	goto then3;
>>> +}
>>> +
>>> +/* while loop */
>>> +void
>>> +mcdc008a (int a)
>>> +{
>>> +    while (a < 10) /* conditions(2/2) */
>>> +	x = a++;
>>> +}
>>> +
>>> +void
>>> +mcdc008b (int a)
>>> +{
>>> +    while (a > 10) /* conditions(1/2) true(0) */
>>> +		   /* conditions(end) */
>>> +	x = a--;
>>> +}
>>> +
>>> +void
>>> +mcdc008c (int a)
>>> +{
>>> +    // should work, even with no body
>>> +    while (a) /* conditions(2/2) */
>>> +	break;
>>> +}
>>> +
>>> +void
>>> +mcdc008d (int a, int b, int c, int d)
>>> +{
>>> +    /* multi-term loop conditional */
>>> +    while ((a && (b || c)) && d) /* conditions(8/8) */
>>> +	a = b = c = d = 0;
>>> +}
>>> +
>>> +void
>>> +mcdc009a (int a, int b)
>>> +{
>>> +    while (a > 0 && b > 0) /* conditions(3/4) false(1) */
>>> +			   /* conditions(end) */
>>> +	x = a--;
>>> +}
>>> +
>>> +/* for loop */
>>> +void
>>> +mcdc010a(int a, int b)
>>> +{
>>> +    for (int i = 0; i < b; i++) /* conditions(2/2) */
>>> +    {
>>> +	if (a < b) /* conditions(2/2) */
>>> +	    x = 1;
>>> +	else
>>> +	    x = a += 2;
>>> +    }
>>> +}
>>> +
>>> +void
>>> +mcdc010b ()
>>> +{
>>> +    for (int a = 0; a <= 1; ++a) /* conditions(2/2) */
>>> +    {
>>> +	x = a;
>>> +    }
>>> +}
>>> +
>>> +int always (int x) { (void) x; return 1; }
>>> +
>>> +/* no-condition infinite loops */
>>> +void
>>> +mcdc010c (int a)
>>> +{
>>> +    for (;;)
>>> +    {
>>> +	if (always(a)) /* conditions(1/2) false(0) */
>>> +		       /* conditions(end) */
>>> +	{
>>> +	    x = a;
>>> +	    break;
>>> +	}
>>> +	x += a + 1;
>>> +    }
>>> +}
>>> +
>>> +/* conditionals without control flow constructs work */
>>> +void
>>> +mcdc011a (int a, int b, int c)
>>> +{
>>> +    x = (a && b) || c; /* conditions(5/6) false(1) */
>>> +		       /* conditions(end) */
>>> +}
>>> +
>>> +/* sequential expressions are handled independently */
>>> +void
>>> +mcdc012a (int a, int b, int c)
>>> +{
>>> +    if (a || b) /* conditions(3/4) true(0) */
>>> +		/* conditions(end) */
>>> +	x = 1;
>>> +    else
>>> +	x = 2;
>>> +
>>> +    if (c) /* conditions(2/2) */
>>> +	x = 1;
>>> +}
>>> +
>>> +/*
>>> + * cannot ever satisfy MC/DC, even with all input combinations, because not all
>>> + * variables independently affect the decision
>>> + */
>>> +void
>>> +mcdc013a (int a, int b, int c)
>>> +{
>>> +    (void)b;
>>> +    /*
>>> +     * Specification: (a && b) || c
>>> +     *
>>> +     * But the expression was implemented wrong. This has branch coverage, but
>>> +     * not MC/DC
>>> +     */
>>> +    if ((a && !c) || c) /* conditions(5/6) false(1) */
>>> +	/* conditions(end) */
>>> +	x = 1;
>>> +    else
>>> +	x = 2;
>>> +}
>>> +
>>> +void
>>> +mcdc014a ()
>>> +{
>>> +    int conds[64] = { 0 };
>>> +    /* conditions(64/128) true(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63) */
>>> +    x = conds[ 0] || conds[ 1] || conds[ 2] || conds[ 3] || conds[ 4] ||
>>> +	conds[ 5] || conds[ 6] || conds[ 7] || conds[ 8] || conds[ 9] ||
>>> +	conds[10] || conds[11] || conds[12] || conds[13] || conds[14] ||
>>> +	conds[15] || conds[16] || conds[17] || conds[18] || conds[19] ||
>>> +	conds[20] || conds[21] || conds[22] || conds[23] || conds[24] ||
>>> +	conds[25] || conds[26] || conds[27] || conds[28] || conds[29] ||
>>> +	conds[30] || conds[31] || conds[32] || conds[33] || conds[34] ||
>>> +	conds[35] || conds[36] || conds[37] || conds[38] || conds[39] ||
>>> +	conds[40] || conds[41] || conds[42] || conds[43] || conds[44] ||
>>> +	conds[45] || conds[46] || conds[47] || conds[48] || conds[49] ||
>>> +	conds[50] || conds[51] || conds[52] || conds[53] || conds[54] ||
>>> +	conds[55] || conds[56] || conds[57] || conds[58] || conds[59] ||
>>> +	conds[60] || conds[61] || conds[62] || conds[63]
>>> +	;  /* conditions(end) */
>>> +}
>>> +
>>> +/* early returns */
>>> +void
>>> +mcdc015a (int a, int b)
>>> +{
>>> +    if (a) /* conditions(2/2) */
>>> +	return;
>>> +
>>> +    if (b) /* conditions(1/2) true(0) */
>>> +	   /* conditions(end) */
>>> +	x = 1;
>>> +}
>>> +
>>> +void
>>> +mcdc015b (int a, int b)
>>> +{
>>> +    for (int i = 5; i > a; i--) /* conditions(2/2) */
>>> +    {
>>> +	if (i == b) /* conditions(2/2) */
>>> +	    return;
>>> +	x = i;
>>> +    }
>>> +}
>>> +
>>> +void
>>> +mcdc015c (int a, int b)
>>> +{
>>> +    for (int i = 5; i > a; i--) /* conditions(2/2) */
>>> +    {
>>> +	if (i == b) /* conditions(2/2) */
>>> +	{
>>> +	    x = 0;
>>> +	    return;
>>> +	}
>>> +	else
>>> +	{
>>> +	    x = 1;
>>> +	    return;
>>> +	}
>>> +
>>> +	x = i;
>>> +    }
>>> +}
>>> +
>>> +
>>> +/* check nested loops */
>>> +void
>>> +mcdc016a (int a, int b)
>>> +{
>>> +    for (int i = 0; i < a; i++) /* conditions(2/2) */
>>> +	for (int k = 0; k < b; k++) /* conditions(2/2) */
>>> +	    x = i + k;
>>> +}
>>> +
>>> +void
>>> +mcdc016b (int a, int b)
>>> +{
>>> +    for (int i = 0; i < a; i++) /* conditions(2/2) */
>>> +    {
>>> +	if (a > 5) /* conditions(2/2) */
>>> +	    break;
>>> +
>>> +	for (int k = 0; k < b; k++) /* conditions(2/2) */
>>> +	    x = i + k;
>>> +    }
>>> +}
>>> +
>>> +void
>>> +mcdc016c (int a, int b)
>>> +{
>>> +    for (int i = 0; i < a; i++) /* conditions(2/2) */
>>> +    {
>>> +	if (a > 5) /* conditions(1/2) true(0) */
>>> +		   /* conditions(end) */
>>> +	    return;
>>> +
>>> +	for (int k = 0; k < b; k++) /* conditions(2/2) */
>>> +	    x = i + k;
>>> +    }
>>> +}
>>> +
>>> +void
>>> +mcdc016d (int a, int b)
>>> +{
>>> +    for (int i = 0; i < a; i++) /* conditions(2/2) */
>>> +    {
>>> +	for (int k = 0; k < 5; k++) /* conditions(2/2) */
>>> +	{
>>> +	    if (b > 5) /* conditions(1/2) true(0) */
>>> +		       /* conditions(end) */
>>> +		return;
>>> +	    x = i + k;
>>> +	}
>>> +
>>> +    }
>>> +}
>>> +
>>> +/* do-while loops */
>>> +void
>>> +mcdc017a (int a)
>>> +{
>>> +    do
>>> +    {
>>> +	a--;
>>> +    } while (a > 0); /* conditions(2/2) */
>>> +}
>>> +
>>> +void
>>> +noop () {}
>>> +
>>> +void
>>> +mcdc017b (int a, int b)
>>> +{
>>> +    do
>>> +    {
>>> +	/*
>>> +	 * This call is important; it can add more nodes to the body in the
>>> +	 * CFG, which has changes how close exits and breaks are to the loop
>>> +	 * conditional.
>>> +	 */
>>> +	noop ();
>>> +	a--;
>>> +	if (b) /* conditions(2/2) */
>>> +	    break;
>>> +
>>> +    } while (a > 0); /* conditions(2/2) */
>>> +}
>>> +
>>> +void
>>> +mcdc017c (int a, int b)
>>> +{
>>> +    int left = 0;
>>> +    int right = 0;
>>> +    int n = a + b;
>>> +    do
>>> +    {
>>> +	if (a) /* conditions(1/2) false(0) */
>>> +	       /* conditions(end) */
>>> +	{
>>> +	    left = a > left ? b : left; /* conditions(2/2) */
>>> +	}
>>> +	if (b) /* conditions(1/2) false(0) */
>>> +	{
>>> +	    right = b > right ? a : right; /* conditions(2/2) */
>>> +	}
>>> +    } while (n-- > 0); /* conditions(2/2) */
>>> +}
>>> +
>>> +int id  (int x) { return  x; }
>>> +int inv (int x) { return !x; }
>>> +
>>> +/* collection of odd cases lifted-and-adapted from real-world code */
>>> +int mcdc018a (int a, int b, int c, int d, int e, int f, int g, int len)
>>> +{
>>> +    int n;
>>> +    /* adapted from zlib/gz_read */
>>> +    do
>>> +    {
>>> +	n = -1;
>>> +	if (n > len) /* conditions(2/2) */
>>> +	    n = len;
>>> +
>>> +	if (b) /* conditions(2/2) */
>>> +	{
>>> +	    if (b < 5) /* conditions(2/2) */
>>> +		x = 1;
>>> +	    noop();
>>> +	}
>>> +	else if (c && d) /* conditions(3/4) false(1) */
>>> +	{
>>> +	    x = 2;
>>> +	    break;
>>> +	}
>>> +	else if (e || f) /* conditions(2/4) false(0 1) */
>>> +			 /* conditions(end) */
>>> +	{
>>> +	    if (id(g)) /* conditions(2/2) */
>>> +		return 0;
>>> +	    continue;
>>> +	}
>>> +    } while (a-- > 0); /* conditions(2/2) */
>>> +
>>> +    return 1;
>>> +}
>>> +
>>> +void
>>> +mcdc018b (int a, int b, int c)
>>> +{
>>> +    int n;
>>> +    while (a) /* conditions(2/2) */
>>> +    {
>>> +	/* else block does not make a difference for the problem, but ensures
>>> +	   loop termination. */
>>> +	if (b) /* conditions(2/2) */
>>> +	    n = c ? 0 : 0; // does not show up in CFG (embedded in the block)
>>> +	else
>>> +	    n = 0;
>>> +	a = n;
>>> +    }
>>> +}
>>> +
>>> +/* Adapted from zlib/compress2 */
>>> +void
>>> +mcdc018c (int a, int b)
>>> +{
>>> +    int err;
>>> +    do
>>> +    {
>>> +	a = inv (a);
>>> +	err = a;
>>> +    } while (err); /* conditions(1/2) true(0) */
>>> +		   /* conditions(end) */
>>> +
>>> +    a = id (a);
>>> +    if (a) /* conditions(1/2) true(0) */
>>> +	x *= a + 1;
>>> +}
>>> +
>>> +/* too many conditions, coverage gives up */
>>> +void
>>> +mcdc019a ()
>>> +{
>>> +    int conds[65] = { 0 };
>>> +    #pragma GCC diagnostic push
>>> +    #pragma GCC diagnostic ignored "-Wcoverage-too-many-conditions"
>>> +    x = conds[ 0] || conds[ 1] || conds[ 2] || conds[ 3] || conds[ 4] ||
>>> +	conds[ 5] || conds[ 6] || conds[ 7] || conds[ 8] || conds[ 9] ||
>>> +	conds[10] || conds[11] || conds[12] || conds[13] || conds[14] ||
>>> +	conds[15] || conds[16] || conds[17] || conds[18] || conds[19] ||
>>> +	conds[20] || conds[21] || conds[22] || conds[23] || conds[24] ||
>>> +	conds[25] || conds[26] || conds[27] || conds[28] || conds[29] ||
>>> +	conds[30] || conds[31] || conds[32] || conds[33] || conds[34] ||
>>> +	conds[35] || conds[36] || conds[37] || conds[38] || conds[39] ||
>>> +	conds[40] || conds[41] || conds[42] || conds[43] || conds[44] ||
>>> +	conds[45] || conds[46] || conds[47] || conds[48] || conds[49] ||
>>> +	conds[50] || conds[51] || conds[52] || conds[53] || conds[54] ||
>>> +	conds[55] || conds[56] || conds[57] || conds[58] || conds[59] ||
>>> +	conds[60] || conds[61] || conds[62] || conds[63] || conds[64]
>>> +	;
>>> +    #pragma GCC diagnostic pop
>>> +}
>>> +
>>> +/* ternary */
>>> +void
>>> +mcdc020a (int a)
>>> +{
>>> +    // special case, this can be reduced to:
>>> +    // _1 = argc != 0;
>>> +    // e = (int) _1;
>>> +    x = a ? 1 : 0;
>>> +
>>> +    // changing to different int makes branch
>>> +    x = a ? 2 : 1; /* conditions(2/2) */
>>> +}
>>> +
>>> +void
>>> +mcdc020b (int a, int b)
>>> +{
>>> +    x = (a || b) ? 1 : 0; /* conditions(3/4) true(1) */
>>> +}
>>> +
>>> +void
>>> +mcdc020c (int a, int b)
>>> +{
>>> +    x = a ? 0
>>> +	: b ? 1 /* conditions(2/2) */
>>> +	: 2;    /* conditions(1/2) false(0) */
>>> +		/* conditions(end) */
>>> +}
>>> +
>>> +/* Infinite loop (no exit-edge), this should not be called, but it should
>>> +   compile fine */
>>> +void
>>> +mcdc021a ()
>>> +{
>>> +    while (1)
>>> +	;
>>> +}
>>> +
>>> +/* Computed goto can give all sorts of problems, including difficult path
>>> +   contractions. */
>>> +void
>>> +mcdc021b ()
>>> +{
>>> +  void *op = &&dest;
>>> +dest:
>>> +  if (op) /* conditions(0/2) true(0) false(0) */
>>> +	  /* conditions(end) */
>>> +    goto * 0;
>>> +}
>>> +
>>> +int __sigsetjmp ();
>>> +
>>> +/* This should compile, but not called. */
>>> +void
>>> +mcdc021c ()
>>> +{
>>> +  while (x) /* conditions(0/2) true(0) false(0)*/
>>> +	    /* conditions(end) */
>>> +     __sigsetjmp ();
>>> +}
>>> +
>>> +/* If edges are not properly contracted the a && id (b) will be interpreted as
>>> +   two independent expressions. */
>>> +void
>>> +mcdc021d (int a, int b, int c, int d)
>>> +{
>>> +    if (a && id (b)) /* conditions(1/4) true(0 1) false(0) */
>>> +		     /* conditions(end) */
>>> +	x = 1;
>>> +    else if (c && id (d)) /* conditions(1/4) true(0 1) false(0) */
>>> +			  /* conditions(end) */
>>> +	x = 2;
>>> +    else
>>> +	x = 3;
>>> +}
>>> +
>>> +/* Adapted from linux arch/x86/tools/relocs.c
>>> +   With poor edge contracting this became an infinite loop. */
>>> +void
>>> +mcdc022a (int a, int b)
>>> +{
>>> +    for (int i = 0; i < 5; i++) /* conditions(2/2) */
>>> +    {
>>> +	x = i;
>>> +	for (int j = i; j < 5; j++) /* conditions(2/2) */
>>> +	{
>>> +	    if (id (id (a)) || id (b)) /* conditions(3/4) true(0) */
>>> +				       /* conditions(end) */
>>> +		continue;
>>> +	    b = inv(b);
>>> +	}
>>> +    }
>>> +}
>>> +
>>> +int
>>> +mcdc022b (int a)
>>> +{
>>> +    int devt;
>>> +    if (a) /* conditions(2/2) */
>>> +    {
>>> +	x = a * 2;
>>> +	if (x != a / 10 || x != a % 10) /* conditions(1/4) true(1) false(0 1) */
>>> +					/* conditions(end) */
>>> +	    return 0;
>>> +    } else {
>>> +	devt = id (a);
>>> +	if (devt) /* conditions(1/2) true(0) */
>>> +		  /* conditions(end) */
>>> +	    return 0;
>>> +    }
>>> +
>>> +    return devt;
>>> +}
>>> +
>>> +/* Adapted from linux arch/x86/events/intel/ds.c
>>> +
>>> +   It broken sorting so that the entry block was not the first node after
>>> +   sorting. */
>>> +void
>>> +mcdc022c (int a)
>>> +{
>>> +    if (!a) /* conditions(2/2) */
>>> +	return;
>>> +
>>> +    for (int i = 0; i < 5; i++) /* conditions(2/2) */
>>> +    {
>>> +	if (id (a + i) || inv (a - 1)) /* conditions(1/4) false(0 1) true(1) */
>>> +				       /* conditions(end) */
>>> +	    x = a + i;
>>> +	if (inv (a)) /* conditions(1/2) true(0) */
>>> +		     /* conditions(end) */
>>> +	    break;
>>> +    }
>>> +}
>>> +
>>> +void
>>> +mcdc022d (int a)
>>> +{
>>> +    int i;
>>> +    for (i = 0; i < id (a); i++) /* conditions(1/2) false(0) */
>>> +    {
>>> +	if (!inv (a)) /* conditions(1/2) false(0)*/
>>> +		      /* conditions(end) */
>>> +	    break;
>>> +    }
>>> +
>>> +    if (i < a) /* conditions(1/2) false(0) */
>>> +	       /* conditions(end) */
>>> +	x = a + 1;
>>> +}
>>> +
>>> +/* 023 specifically tests that masking works correctly, which gets complicated
>>> +   fast with a mix of operators and deep subexpressions.  These tests violates
>>> +   the style guide slightly to emphasize the nesting.  They all share the same
>>> +   implementation and only one input is given to each function to obtain clean
>>> +   coverage results. */
>>> +void
>>> +mcdc023a (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
>>> +	  int l, int m, int n)
>>> +{
>>> +    // [a m n] = 0, [b, ...] = 1
>>> +    // a is masked by b and the remaining terms should be short circuited
>>> +    if (/* conditions(1/24) true(0 2 3 4 5 6 7 8 9 10 11) false(0 1 2 3 4 5 6 7 8 9 10 11) */
>>> +	/* conditions(end) */
>>> +	   (a || b)
>>> +	|| (   ((c && d) || (e && (f || g) && h))
>>> +	    && (k || l)
>>> +	    && (m || n)))
>>> +	x = a + b;
>>> +    else
>>> +	x = b + c;
>>> +}
>>> +
>>> +void
>>> +mcdc023b (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
>>> +	  int l, int m, int n)
>>> +{
>>> +    // [a b d h] = 0, [c, ...] = 1
>>> +    // h = 0 => false but does not mask (a || b) or (c && d). d = 0 masks c.
>>> +    if (/* conditions(4/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 4 5 6 8 9 10 11) */
>>> +	/* conditions(end) */
>>> +	   (a || b)
>>> +	|| (   ((c && d) || (e && (f || g) && h))
>>> +	    && (k || l)
>>> +	    && (m || n)))
>>> +	x = a + b;
>>> +    else
>>> +	x = b + c;
>>> +}
>>> +
>>> +void
>>> +mcdc023c (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
>>> +	  int l, int m, int n)
>>> +{
>>> +    /* [m n a b] = 0, [...] = 1
>>> +       n,m = 0 should mask all other terms than a, b */
>>> +    if (/* conditions(4/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 3 4 5 6 7 8 9) */
>>> +	/* conditions(end) */
>>> +	   (a || b)
>>> +	|| (   ((c && d) || (e && (f || g) && h))
>>> +	    && (k || l)
>>> +	    && (m || n)))
>>> +	x = a + b;
>>> +    else
>>> +	x = b + c;
>>> +}
>>> +
>>> +void
>>> +mcdc023d (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
>>> +	  int l, int m, int n)
>>> +{
>>> +    /* [a b] = 0, [h, ...] = 1
>>> +       n,m = 0 should mask all other terms than a, b */
>>> +    if (/* conditions(4/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 3 4 5 6 7 10 11) */
>>> +	/* conditions(end) */
>>> +	   (a || b)
>>> +	|| (   ((c && d) || (e && (f || g) && h))
>>> +	    && (k || l)
>>> +	    && (m || n)))
>>> +	x = a + b;
>>> +    else
>>> +	x = b + c;
>>> +}
>>> +
>>> +void
>>> +mcdc023e (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
>>> +	  int l, int m, int n)
>>> +{
>>> +    /* [a b d] = 0, [c h, ...] = 1
>>> +       h = 1 should mask c, d, leave other terms intact.
>>> +       If [k l m n] were false then h itself would be masked.
>>> +       [a b] are masked as collateral by [m n]. */
>>> +    if (/* conditions(5/24) true(0 1 2 3 6 9 11) false(0 1 2 3 4 5 6 7 8 9 10 11) */
>>> +	/* conditions(end) */
>>> +	   (a || b)
>>> +	|| (   ((c && d) || (e && (f || g) && h))
>>> +	    && (k || l)
>>> +	    && (m || n)))
>>> +	x = a + b;
>>> +    else
>>> +	x = b + c;
>>> +}
>>> +
>>> +void
>>> +mcdc023f (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
>>> +	  int l, int m, int n)
>>> +{
>>> +    /* [a b c f g] = 0, [e, ...] = 1
>>> +       [f g] = 0 should mask e, leave [c d] intact. */
>>> +    if (/* conditions(5/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(3 4 7 8 9 10 11) */
>>> +	/* conditions(end) */
>>> +	   (a || b)
>>> +	|| (   ((c && d) || (e && (f || g) && h))
>>> +	    && (k || l)
>>> +	    && (m || n)))
>>> +	x = a + b;
>>> +    else
>>> +	x = b + c;
>>> +}
>>> +
>>> +void
>>> +mcdc023g (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
>>> +	  int l, int m, int n)
>>> +{
>>> +    /* [a b d f g] = 0, [e c, ...] = 1
>>> +       Same as 023f but with [c d] flipped so d masks c rather than c
>>> +       short-circuits.  This should not be lost. */
>>> +    if (/* conditions(5/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 4 7 8 9 10 11) */
>>> +	/* conditions(end) */
>>> +	   (a || b)
>>> +	|| (   ((c && d) || (e && (f || g) && h))
>>> +	    && (k || l)
>>> +	    && (m || n)))
>>> +	x = a + b;
>>> +    else
>>> +	x = b + c;
>>> +}
>>> +
>>> +void
>>> +mcdc024a (int a, int b)
>>> +{
>>> +    if (a && b) /* conditions(1/4) true(0 1) false(1) */
>>> +		/* conditions(end) */
>>> +    {
>>> +label1:
>>> +	x = 1;
>>> +    }
>>> +    else
>>> +    {
>>> +	x = 2;
>>> +    }
>>> +
>>> +    if (a || b) /* conditions(2/4) true(0 1) */
>>> +		/* conditions(end) */
>>> +    {
>>> +label2:
>>> +	x = 1;
>>> +    }
>>> +    else
>>> +    {
>>> +	x = 2;
>>> +    }
>>> +}
>>> +
>>> +void
>>> +mcdc024b (int a, int b)
>>> +{
>>> +
>>> +    if (a && b) /* conditions(1/4) true(0 1) false(1) */
>>> +		/* conditions(end) */
>>> +    {
>>> +	x = 1;
>>> +    }
>>> +    else
>>> +    {
>>> +label1:
>>> +	x = 2;
>>> +    }
>>> +
>>> +    if (a || b) /* conditions(2/4) true(0 1) */
>>> +		/* conditions(end) */
>>> +    {
>>> +	x = 1;
>>> +    }
>>> +    else
>>> +    {
>>> +label2:
>>> +	x = 2;
>>> +    }
>>> +}
>>> +
>>> +void
>>> +mcdc024c (int a, int b)
>>> +{
>>> +    if (a && b) /* conditions(1/4) true(0 1) false(1) */
>>> +		/* conditions(end) */
>>> +    {
>>> +label1:
>>> +	x = 1;
>>> +    }
>>> +    else
>>> +    {
>>> +label2:
>>> +	x = 2;
>>> +    }
>>> +
>>> +    if (a || b) /* conditions(2/4) true(0 1) */
>>> +		/* conditions(end) */
>>> +    {
>>> +label3:
>>> +	x = 1;
>>> +    }
>>> +    else
>>> +    {
>>> +label4:
>>> +	x = 2;
>>> +    }
>>> +}
>>> +
>>> +int main ()
>>> +{
>>> +    mcdc001a (0, 1);
>>> +
>>> +    mcdc001b (0, 1);
>>> +    mcdc001b (0, 0);
>>> +
>>> +    mcdc001c (0, 1);
>>> +    mcdc001c (0, 0);
>>> +    mcdc001c (1, 1);
>>> +
>>> +    mcdc001d (1, 1, 1);
>>> +    mcdc001d (0, 1, 0);
>>> +
>>> +    mcdc002a (1, 0);
>>> +
>>> +    mcdc002b (1, 0);
>>> +    mcdc002b (1, 1);
>>> +
>>> +    mcdc002c (0, 0);
>>> +    mcdc002c (1, 1);
>>> +    mcdc002c (1, 0);
>>> +
>>> +    mcdc002d (1, 1, 1);
>>> +    mcdc002d (1, 0, 0);
>>> +
>>> +    mcdc003a (0, 0);
>>> +    mcdc003a (1, 0);
>>> +
>>> +    mcdc004a (0);
>>> +    mcdc004b (0);
>>> +    mcdc004b (1);
>>> +    mcdc004c (1);
>>> +
>>> +    mcdc004d (0, 0, 0);
>>> +    mcdc004d (1, 1, 1);
>>> +
>>> +    mcdc004e (0, 0, 0);
>>> +    mcdc004e (1, 1, 1);
>>> +
>>> +    mcdc005a (1, 0, 1);
>>> +
>>> +    mcdc005b (1, 1, 0, 0);
>>> +    mcdc005b (1, 0, 0, 0);
>>> +
>>> +    mcdc005c (0, 1, 1, 0);
>>> +
>>> +    mcdc005d (1, 0, 0, 0);
>>> +
>>> +    mcdc006a (0, 0, 0, 0, 0);
>>> +    mcdc006a (1, 0, 0, 0, 0);
>>> +    mcdc006a (1, 1, 1, 0, 0);
>>> +
>>> +    mcdc006b (0, 0, 0);
>>> +    mcdc006b (1, 0, 0);
>>> +    mcdc006b (1, 1, 0);
>>> +    mcdc006b (1, 1, 1);
>>> +
>>> +    mcdc006c (0, 0, 0);
>>> +    mcdc006c (1, 0, 0);
>>> +    mcdc006c (1, 1, 0);
>>> +    mcdc006c (1, 1, 1);
>>> +
>>> +    mcdc007a (0, 0, 0, 0);
>>> +    mcdc007a (1, 0, 0, 0);
>>> +    mcdc007a (0, 0, 1, 0);
>>> +
>>> +    mcdc007b (0, 0, 0);
>>> +    mcdc007b (0, 1, 1);
>>> +    mcdc007b (1, 0, 1);
>>> +
>>> +    mcdc007c (0, 0, 0);
>>> +    mcdc007c (0, 1, 1);
>>> +    mcdc007c (1, 0, 1);
>>> +
>>> +    mcdc008a (0);
>>> +
>>> +    mcdc008b (0);
>>> +
>>> +    mcdc008c (0);
>>> +    mcdc008c (1);
>>> +
>>> +    mcdc008d (0, 0, 0, 0);
>>> +    mcdc008d (1, 0, 0, 0);
>>> +    mcdc008d (1, 0, 1, 0);
>>> +    mcdc008d (1, 0, 1, 1);
>>> +    mcdc008d (1, 1, 1, 1);
>>> +
>>> +    mcdc009a (0, 0);
>>> +    mcdc009a (1, 1);
>>> +
>>> +    mcdc010a (0, 0);
>>> +    mcdc010a (0, 9);
>>> +    mcdc010a (2, 1);
>>> +
>>> +    mcdc010b ();
>>> +
>>> +    mcdc010c (1);
>>> +
>>> +    mcdc011a (0, 0, 0);
>>> +    mcdc011a (1, 1, 0);
>>> +    mcdc011a (1, 0, 1);
>>> +
>>> +    mcdc012a (0, 0, 0);
>>> +    mcdc012a (0, 1, 1);
>>> +
>>> +    mcdc013a (0, 0, 0);
>>> +    mcdc013a (0, 0, 1);
>>> +    mcdc013a (0, 1, 0);
>>> +    mcdc013a (0, 1, 1);
>>> +    mcdc013a (1, 0, 0);
>>> +    mcdc013a (1, 0, 1);
>>> +    mcdc013a (1, 1, 0);
>>> +    mcdc013a (1, 1, 1);
>>> +
>>> +    mcdc014a ();
>>> +
>>> +    mcdc015a (0, 0);
>>> +    mcdc015a (1, 0);
>>> +
>>> +    mcdc015b (0, 0);
>>> +    mcdc015b (0, 1);
>>> +    mcdc015b (6, 1);
>>> +
>>> +    mcdc015c (0, 0);
>>> +    mcdc015c (0, 5);
>>> +    mcdc015c (6, 1);
>>> +
>>> +    mcdc016a (5, 5);
>>> +
>>> +    mcdc016b (5, 5);
>>> +    mcdc016b (6, 5);
>>> +
>>> +    mcdc016c (5, 5);
>>> +
>>> +    mcdc016d (1, 0);
>>> +
>>> +    mcdc017a (0);
>>> +    mcdc017a (2);
>>> +
>>> +    mcdc017b (2, 0);
>>> +    mcdc017b (0, 1);
>>> +
>>> +    mcdc017c (1, 1);
>>> +
>>> +    mcdc018a (0, 0, 1, 1, 0, 0, 0, 0);
>>> +    mcdc018a (0, 1, 0, 0, 0, 0, 1, -2);
>>> +    mcdc018a (0, 6, 0, 0, 0, 0, 1, -2);
>>> +    mcdc018a (0, 6, 0, 0, 0, 0, 1, -2);
>>> +    mcdc018a (0, 0, 0, 1, 0, 1, 1, 0);
>>> +    mcdc018a (1, 0, 0, 0, 1, 1, 0, 0);
>>> +
>>> +    mcdc018b (1, 0, 0);
>>> +    mcdc018b (1, 1, 0);
>>> +
>>> +    mcdc018c (1, 1);
>>> +
>>> +    mcdc019a ();
>>> +
>>> +    mcdc020a (0);
>>> +    mcdc020a (1);
>>> +
>>> +    mcdc020b (0, 0);
>>> +    mcdc020b (1, 0);
>>> +
>>> +    mcdc020c (0, 1);
>>> +    mcdc020c (1, 1);
>>> +
>>> +    mcdc021d (1, 0, 1, 0);
>>> +
>>> +    mcdc022a (0, 0);
>>> +
>>> +    mcdc022b (0);
>>> +    mcdc022b (1);
>>> +
>>> +    mcdc022c (0);
>>> +    mcdc022c (1);
>>> +
>>> +    mcdc022d (1);
>>> +
>>> +    mcdc023a (0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
>>> +    mcdc023b (0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1);
>>> +    mcdc023c (0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0);
>>> +    mcdc023d (0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1);
>>> +    mcdc023e (0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1);
>>> +    mcdc023f (0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1);
>>> +    mcdc023g (0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1);
>>> +
>>> +    mcdc024a (0, 0);
>>> +    mcdc024b (0, 0);
>>> +    mcdc024c (0, 0);
>>> +}
>>> +
>>> +/* { dg-final { run-gcov conditions { --conditions gcov-19.c } } } */
>>> diff --git a/gcc/testsuite/gcc.misc-tests/gcov-20.c b/gcc/testsuite/gcc.misc-tests/gcov-20.c
>>> new file mode 100644
>>> index 00000000000..847dae495db
>>> --- /dev/null
>>> +++ b/gcc/testsuite/gcc.misc-tests/gcov-20.c
>>> @@ -0,0 +1,22 @@
>>> +/* { dg-options "-fprofile-conditions -ftest-coverage -fprofile-update=atomic" } */
>>> +/* { dg-do run { target native } } */
>>> +
>>> +/* some side effect to stop branches from being pruned */
>>> +int x = 0;
>>> +
>>> +void
>>> +conditions_atomic001 (int a, int b)
>>> +{
>>> +    if (a || b) /* conditions(1/4) true(0) false(0 1) */
>>> +		/* conditions(end) */
>>> +	x = 1;
>>> +    else
>>> +	x = 2;
>>> +}
>>> +
>>> +int main ()
>>> +{
>>> +    conditions_atomic001 (0, 1);
>>> +}
>>> +
>>> +/* { dg-final { run-gcov conditions { --conditions gcov-20.c } } } */
>>> diff --git a/gcc/testsuite/gcc.misc-tests/gcov-21.c b/gcc/testsuite/gcc.misc-tests/gcov-21.c
>>> new file mode 100644
>>> index 00000000000..978be3276a2
>>> --- /dev/null
>>> +++ b/gcc/testsuite/gcc.misc-tests/gcov-21.c
>>> @@ -0,0 +1,16 @@
>>> +/* { dg-options "-fprofile-conditions" } */
>>> +
>>> +/* https://gcc.gnu.org/pipermail/gcc-patches/2022-April/592927.html */
>>> +char trim_filename_name;
>>> +int r;
>>> +
>>> +void trim_filename() {
>>> +    if (trim_filename_name)
>>> +	r = 123;
>>> +    while (trim_filename_name)
>>> +	;
>>> +}
>>> +
>>> +int main ()
>>> +{
>>> +}
>>> diff --git a/gcc/testsuite/lib/gcov.exp b/gcc/testsuite/lib/gcov.exp
>>> index 9d5b2cdb86b..69168d67d03 100644
>>> --- a/gcc/testsuite/lib/gcov.exp
>>> +++ b/gcc/testsuite/lib/gcov.exp
>>> @@ -174,6 +174,184 @@ proc verify-branches { testname testcase file } {
>>>      return $failed
>>>  }
>>>  
>>> +#
>>> +# verify-conditions -- check that conditions are checked as expected
>>> +#
>>> +# TESTNAME is the name of the test, including unique flags.
>>> +# TESTCASE is the name of the test file.
>>> +# FILE is the name of the gcov output file.
>>> +#
>>> +# Checks are based on comments in the source file. Condition coverage comes
>>> +# with with two types of output, a summary and a list of the uncovered
>>> +# conditions. Both must be checked to pass the test
>>> +#
>>> +# To check for conditions, add a comment the line of a conditional:
>>> +# /* conditions(n/m) true(0 1) false(1) */
>>> +#
>>> +# where n/m are the covered and total conditions in the expression. The true()
>>> +# and false() take the indices expected *not* covered.
>>> +#
>>> +# This means that all coverage statements should have been seen:
>>> +# /* conditions(end) */
>>> +#
>>> +# If all conditions are covered i.e. n == m, then conditions(end) can be
>>> +# omitted. If either true() or false() are empty they can be omitted too.
>>> +#
>>> +# C++ can insert conditionals in the CFG that are not present in source code.
>>> +# These must be manually suppressed since unexpected and unhandled conditions
>>> +# are an error (to help combat regressions). Output can be suppressed with
>>> +# conditions(suppress) and conditions(end). suppress should usually be on a
>>> +# closing brace.
>>> +#
>>> +# Some expressions, when using unnamed temporaries as operands, will have
>>> +# destructors in expressions. The coverage of the destructor will be reported
>>> +# on the same line as the expression itself, but suppress() would also swallow
>>> +# the expected tested-for messages. To handle these, use the destructor() [1]
>>> +# which will suppress everything from and including the second "conditions
>>> +# covered".
>>> +#
>>> +# [1] it is important that the destructor() is *on the same line* as the
>>> +#     conditions(m/n)
>>> +proc verify-conditions { testname testcase file } {
>>> +    set failed 0
>>> +    set suppress 0
>>> +    set destructor 0
>>> +    set should ""
>>> +    set shouldt ""
>>> +    set shouldf ""
>>> +    set shouldall ""
>>> +    set fd [open $file r]
>>> +    set n 0
>>> +    set keywords {"end" "suppress"}
>>> +    while {[gets $fd line] >= 0} {
>>> +	regexp "^\[^:\]+: *(\[0-9\]+):" "$line" all n
>>> +	set prefix "$testname line $n"
>>> +
>>> +	if {![regexp "condition" $line]} {
>>> +	    continue
>>> +	}
>>> +
>>> +	# Missing coverage for both true and false will cause a failure, but
>>> +	# only count it once for the report.
>>> +	set ok 1
>>> +	if [regexp {conditions *\(([0-9a-z/]+)\)} "$line" all e] {
>>> +	    # *Very* coarse sanity check: conditions() should either be a
>>> +	    # keyword or n/m, anything else means a buggy test case. end is
>>> +	    # optional for cases where all conditions are covered, since it
>>> +	    # only expects a single line of output.
>>> +	    if {([lsearch -exact $keywords $e] >= 0 || [regexp {\d+/\d+} "$e"]) == 0} {
>>> +		fail "$prefix: expected conditions (n/m), (suppress) or (end); was ($e)"
>>> +		incr failed
>>> +		continue
>>> +	    }
>>> +
>>> +	    # Any keyword means a new context. Set the error flag if not all
>>> +	    # expected output has been seen, and reset the state.
>>> +
>>> +	    if {[llength $shouldt] != 0} {
>>> +		fail "$prefix: expected 'not covered (true)' for terms: $shouldt"
>>> +		set ok 0
>>> +	    }
>>> +
>>> +	    if {[llength $shouldf] != 0} {
>>> +		fail "$prefix: expected 'not covered (false)' for terms: $shouldf"
>>> +		set ok 0
>>> +	    }
>>> +
>>> +	    if {$shouldall ne ""} {
>>> +		fail "$prefix: coverage summary not found; expected $shouldall"
>>> +		set ok 0
>>> +	    }
>>> +
>>> +	    set suppress 0
>>> +	    set destructor 0
>>> +	    set should ""
>>> +	    set shouldt ""
>>> +	    set shouldf ""
>>> +	    set shouldall ""
>>> +	    set newt ""
>>> +	    set newf ""
>>> +
>>> +	    if [regexp {destructor\(\)} "$line"] {
>>> +		set destructor 1
>>> +	    }
>>> +
>>> +	    if [regexp {(\d+)/(\d+)} "$e" all i k] {
>>> +		regexp {true\(([0-9 ]+)\)}  "$line" all newt
>>> +		regexp {false\(([0-9 ]+)\)} "$line" all newf
>>> +
>>> +		# Sanity check - if the true() and false() vectors should have
>>> +		# m-n elements to cover all uncovered conditions. Because of
>>> +		# masking it can sometimes be surprising what terms are
>>> +		# independent, so this makes for more robust test at the cost
>>> +		# of being slightly more annoying to write.
>>> +		set nterms [expr [llength $newt] + [llength $newf]]
>>> +		set nexpected [expr {$k - $i}]
>>> +		if {$nterms != $nexpected} {
>>> +		    fail "$prefix: expected $nexpected uncovered terms; got $nterms"
>>> +		    set ok 0
>>> +		}
>>> +		set shouldall $e
>>> +		set shouldt $newt
>>> +		set shouldf $newf
>>> +	    } elseif {$e == "end"} {
>>> +		# no-op - state has already been reset, and errors flagged
>>> +	    } elseif {$e == "suppress"} {
>>> +		set suppress 1
>>> +	    } else {
>>> +		# this should be unreachable, 
>>> +		fail "$prefix: unhandled control ($e), should be unreachable"
>>> +		set ok 0
>>> +	    }
>>> +	} elseif {$suppress == 1} {
>>> +	    # ignore everything in a suppress block. C++ especially can insert
>>> +	    # conditionals in exceptions and destructors which would otherwise
>>> +	    # be considered unhandled.
>>> +	    continue
>>> +	} elseif [regexp {condition +(\d+) not covered \((.*)\)} "$line" all cond condv] {
>>> +	    foreach v {true false} {
>>> +		if [regexp $v $condv] {
>>> +		    if {"$v" == "true"} {
>>> +			set should shouldt
>>> +		    } else {
>>> +			set should shouldf
>>> +		    }
>>> +
>>> +		    set i [lsearch [set $should] $cond]
>>> +		    if {$i != -1} {
>>> +			set $should [lreplace [set $should] $i $i]
>>> +		    } else {
>>> +			fail "$testname line $n: unexpected uncovered term $cond ($v)"
>>> +			set ok 0
>>> +		    }
>>> +		}
>>> +	    }
>>> +	} elseif [regexp {condition outcomes covered (\d+/\d+)} "$line" all cond] {
>>> +	    # the destructor-generated "conditions covered" lines will be
>>> +	    # written after all expression-related output. Handle these by
>>> +	    # turning on suppression if the destructor-suppression is
>>> +	    # requested.
>>> +	    if {$shouldall == "" && $destructor == 1} {
>>> +		set suppress 1
>>> +		continue
>>> +	    }
>>> +
>>> +	    if {$cond == $shouldall} {
>>> +		set shouldall ""
>>> +	    } else {
>>> +		fail "$testname line $n: unexpected summary $cond"
>>> +		set ok 0
>>> +	    }
>>> +	}
>>> +
>>> +	if {$ok != 1} {
>>> +	    incr failed
>>> +	}
>>> +    }
>>> +    close $fd
>>> +    return $failed
>>> +}
>>> +
>>>  #
>>>  # verify-calls -- check that call return percentages are as expected
>>>  #
>>> @@ -321,6 +499,7 @@ proc run-gcov { args } {
>>>      set gcov_args ""
>>>      set gcov_verify_calls 0
>>>      set gcov_verify_branches 0
>>> +    set gcov_verify_conditions 0
>>>      set gcov_verify_lines 1
>>>      set gcov_verify_intermediate 0
>>>      set gcov_remove_gcda 0
>>> @@ -331,10 +510,13 @@ proc run-gcov { args } {
>>>  	  set gcov_verify_calls 1
>>>  	} elseif { $a == "branches" } {
>>>  	  set gcov_verify_branches 1
>>> +	} elseif { $a == "conditions" } {
>>> +	  set gcov_verify_conditions 1
>>>  	} elseif { $a == "intermediate" } {
>>>  	  set gcov_verify_intermediate 1
>>>  	  set gcov_verify_calls 0
>>>  	  set gcov_verify_branches 0
>>> +	  set gcov_verify_conditions 0
>>>  	  set gcov_verify_lines 0
>>>  	} elseif { $a == "remove-gcda" } {
>>>  	  set gcov_remove_gcda 1
>>> @@ -404,6 +586,11 @@ proc run-gcov { args } {
>>>      } else {
>>>  	set bfailed 0
>>>      }
>>> +    if { $gcov_verify_conditions } {
>>> +	set cdfailed [verify-conditions $testname $testcase $testcase.gcov]
>>> +    } else {
>>> +	set cdfailed 0
>>> +    }
>>>      if { $gcov_verify_calls } {
>>>  	set cfailed [verify-calls $testname $testcase $testcase.gcov]
>>>      } else {
>>> @@ -418,12 +605,12 @@ proc run-gcov { args } {
>>>  
>>>      # Report whether the gcov test passed or failed.  If there were
>>>      # multiple failures then the message is a summary.
>>> -    set tfailed [expr $lfailed + $bfailed + $cfailed + $ifailed]
>>> +    set tfailed [expr $lfailed + $bfailed + $cdfailed + $cfailed + $ifailed]
>>>      if { $xfailed } {
>>>  	setup_xfail "*-*-*"
>>>      }
>>>      if { $tfailed > 0 } {
>>> -	fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cfailed in return percentages, $ifailed in intermediate format"
>>> +	fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cdfailed in condition/decision, $cfailed in return percentages, $ifailed in intermediate format"
>>>  	if { $xfailed } {
>>>  	    clean-gcov $testcase
>>>  	}
>>> diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
>>> index 2beb49241f2..0b537d64d97 100644
>>> --- a/gcc/tree-profile.cc
>>> +++ b/gcc/tree-profile.cc
>>> @@ -58,6 +58,8 @@ along with GCC; see the file COPYING3.  If not see
>>>  #include "alloc-pool.h"
>>>  #include "symbol-summary.h"
>>>  #include "symtab-thunks.h"
>>> +#include "cfganal.h"
>>> +#include "cfgloop.h"
>>>  
>>>  static GTY(()) tree gcov_type_node;
>>>  static GTY(()) tree tree_interval_profiler_fn;
>>> @@ -73,6 +75,1048 @@ static GTY(()) tree ic_tuple_var;
>>>  static GTY(()) tree ic_tuple_counters_field;
>>>  static GTY(()) tree ic_tuple_callee_field;
>>>  
>>> +namespace
>>> +{
>>> +/* Some context and reused instances between function calls.  Large embedded
>>> +   buffers are used to up-front request enough memory for most programs and
>>> +   merge them into a single allocation at the cost of using more memory in the
>>> +   average case.  Some numbers from linux v5.13 which is assumed to be a
>>> +   reasonably diverse code base: 75% of the functions in linux have less than
>>> +   16 nodes in the CFG and approx 2.5% have more than 64 nodes.  The functions
>>> +   that go beyond a few dozen nodes tend to be very large (>100) and so 64
>>> +   seems like a good balance.
>>> +
>>> +   This is really just a performance balance of the cost of allocation and
>>> +   wasted memory. */
>>> +struct conds_ctx
>>> +{
>>> +    /* Bitmap of the processed blocks.  Bit n set means basic_block->index has
>>> +       been processed either explicitly or as a part of an expression. */
>>> +    auto_sbitmap marks;
>>> +
>>> +    /* This is both a reusable shared allocation which is also used to return
>>> +       single expressions, which means it for most code should only hold a
>>> +       couple of elements. */
>>> +    auto_vec<basic_block, 32> blocks;
>>> +
>>> +    /* Map from basic_block->index to an ordering so that for a single
>>> +       expression (a || b && c) => index_map[a] < index_map[b] < index_map[c].
>>> +       The values do not have to be consecutive and can be interleaved by
>>> +       values from other expressions, so comparisons only make sense for blocks
>>> +       that belong to the same expression. */
>>> +    auto_vec<int, 64> index_map;
>>> +
>>> +    /* Pre-allocate bitmaps and vectors for per-function book keeping.  This is
>>> +       pure instance reuse and the bitmaps carry no data between function
>>> +       calls. */
>>> +    auto_vec<basic_block, 64> B1;
>>> +    auto_vec<basic_block, 64> B2;
>>> +    auto_sbitmap G1;
>>> +    auto_sbitmap G2;
>>> +    auto_sbitmap G3;
>>> +
>>> +    explicit conds_ctx (unsigned size) noexcept (true) : marks (size),
>>> +    G1 (size), G2 (size), G3 (size)
>>> +    {
>>> +	bitmap_clear (marks);
>>> +    }
>>> +
>>> +    /* Mark a node as processed so nodes are not processed twice for example in
>>> +       loops, gotos. */
>>> +    void mark (const basic_block b) noexcept (true)
>>> +    {
>>> +	gcc_assert (!bitmap_bit_p (marks, b->index));
>>> +	bitmap_set_bit (marks, b->index);
>>> +    }
>>> +
>>> +    /* Mark nodes as processed so they are not processed twice. */
>>> +    void mark (const vec<basic_block>& bs) noexcept (true)
>>> +    {
>>> +	for (const basic_block b : bs)
>>> +	    mark (b);
>>> +    }
>>> +
>>> +    /* Check if all nodes are marked.  A successful run should visit & mark
>>> +       every reachable node exactly once. */
>>> +    bool all_marked (const vec<basic_block>& reachable) const noexcept (true)
>>> +    {
>>> +	for (const basic_block b : reachable)
>>> +	    if (!bitmap_bit_p (marks, b->index))
>>> +		return false;
>>> +	return true;
>>> +    }
>>> +};
>>> +
>>> +/* Only instrument terms with fewer than number of bits in a (wide) gcov
>>> +   integer, which is probably 64.  The algorithm itself does not impose this
>>> +   limitation, but it makes for a simpler implementation.
>>> +
>>> +   * Allocating the output data structure (coverage_counter_alloc ()) can
>>> +     assume pairs of gcov_type_unsigned and not use a separate length field.
>>> +   * A pair gcov_type_unsigned can be used as accumulators.
>>> +   * Updating accumulators is can use the bitwise operations |=, &= and not
>>> +     custom operators that work for arbitrary-sized bit-sets.
>>> +
>>> +   Most real-world code should be unaffected by this, but it is possible
>>> +   (especially for generated code) to exceed this limit.
>>> + */
>>> +#define CONDITIONS_MAX_TERMS (sizeof (gcov_type_unsigned) * BITS_PER_UNIT)
>>> +#define EDGE_CONDITION (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE)
>>> +
>>> +/* Compare two basic blocks by their order in the expression i.e. for (a || b)
>>> +   then cmp_index_map (a, b, ...) < 0.  The result is undefined if lhs, rhs
>>> +   belong to different expressions. */
>>> +int
>>> +cmp_index_map (const void *lhs, const void *rhs, void *index_map)
>>> +{
>>> +    const_basic_block l = *(const basic_block*) lhs;
>>> +    const_basic_block r = *(const basic_block*) rhs;
>>> +    const vec<int>* im = (const vec<int>*) index_map;
>>> +    return (*im)[l->index] - (*im)[r->index];
>>> +}
>>> +
>>> +/* Find the index of needle in blocks; return -1 if not found. This has two
>>> +   uses, sometimes for the index and sometimes for set member checks.  Sets are
>>> +   typically very small (number of conditions, >8 is uncommon) so linear search
>>> +   should be very fast. */
>>> +int
>>> +index_of (const basic_block needle, array_slice<basic_block> blocks)
>>> +{
>>> +    for (size_t i = 0; i < blocks.size (); i++)
>>> +	if (blocks[i] == needle)
>>> +	    return int (i);
>>> +    return -1;
>>> +}
>>> +
>>> +/* Returns true if this is a conditional node, i.e. it has outgoing true and
>>> +   false edges. */
>>> +bool
>>> +block_conditional_p (const basic_block b)
>>> +{
>>> +    unsigned t = 0;
>>> +    unsigned f = 0;
>>> +    for (edge e : b->succs)
>>> +    {
>>> +	t |= (e->flags & EDGE_TRUE_VALUE);
>>> +	f |= (e->flags & EDGE_FALSE_VALUE);
>>> +    }
>>> +    return t && f;
>>> +}
>>> +
>>> +/* Check if the edge is a conditional. */
>>> +bool
>>> +edge_conditional_p (const edge e)
>>> +{
>>> +    return e->flags & EDGE_CONDITION;
>>> +}
>>> +
>>> +/* Special cases of the single_*_p and single_*_edge functions in basic-block.h
>>> +   that don't consider exception handling or other complex edges.  This helps
>>> +   create a view of the CFG with only normal edges - if a basic block has both
>>> +   an outgoing fallthrough and exceptional edge [1], it should be considered a
>>> +   single-successor.
>>> +
>>> +   [1] if this is not possible, these functions can be removed and replaced by
>>> +       their basic-block.h cousins. */
>>> +bool
>>> +single (const vec<edge, va_gc> *edges)
>>> +{
>>> +    int n = EDGE_COUNT (edges);
>>> +    if (n == 0)
>>> +	return false;
>>> +
>>> +    for (edge e : edges)
>>> +	if (e->flags & EDGE_COMPLEX)
>>> +	    n -= 1;
>>> +
>>> +    return n == 1;
>>> +}
>>> +
>>> +/* Get the single, non-complex edge.  Behavior is undefined edges have more
>>> +   than 1 non-complex edges. */
>>> +edge
>>> +single_edge (const vec<edge, va_gc> *edges)
>>> +{
>>> +    for (edge e : edges)
>>> +    {
>>> +	if (e->flags & EDGE_COMPLEX)
>>> +	    continue;
>>> +	return e;
>>> +    }
>>> +    return NULL;
>>> +}
>>> +
>>> +/* Sometimes, for example with function calls and C++ destructors, the CFG gets
>>> +   extra nodes that are essentially single-entry-single-exit in the middle of
>>> +   boolean expressions.  For example:
>>> +
>>> +      x || can_throw (y)
>>> +
>>> +               A
>>> +              /|
>>> +             / |
>>> +            B  |
>>> +            |  |
>>> +            C  |
>>> +           / \ |
>>> +          /   \|
>>> +         F     T
>>> +
>>> +   Without the extra node inserted by the function + exception it becomes a
>>> +   proper 2-term graph, not 2 single-term graphs.
>>> +
>>> +               A
>>> +              /|
>>> +             C |
>>> +            / \|
>>> +           F   T
>>> +
>>> +   contract_edge ignores the series of intermediate nodes and makes a virtual
>>> +   edge A -> C without having to construct a new simplified CFG explicitly.  It
>>> +   gets more complicated as non-conditional edges is how the body of the
>>> +   then/else blocks are separated from the boolean expression, so only edges
>>> +   that are inserted because of function calls in the expression itself must be
>>> +   merged.
>>> +
>>> +   Only chains of single-exit single-entry nodes that end with a condition
>>> +   should be contracted. */
>>> +edge
>>> +contract_edge (edge e)
>>> +{
>>> +    edge source = e;
>>> +    while (true)
>>> +    {
>>> +	basic_block dest = e->dest;
>>> +	if (!single (dest->preds))
>>> +	    return source;
>>> +	if (e->flags & EDGE_DFS_BACK)
>>> +	    return source;
>>> +	if (block_conditional_p (dest))
>>> +	    return e;
>>> +
>>> +	e = single_edge (dest->succs);
>>> +	if (!e)
>>> +	    return source;
>>> +    }
>>> +}
>>> +
>>> +/* This is the predecessor dual of contract_edge; it collapses the predecessor
>>> +   blocks between two operands in a boolean expression. */
>>> +edge
>>> +contract_edge_up (edge e)
>>> +{
>>> +    while (true)
>>> +    {
>>> +	basic_block src = e->src;
>>> +	if (edge_conditional_p (e))
>>> +	    return e;
>>> +	if (!single (src->preds))
>>> +	    return e;
>>> +	e = single_edge (src->preds);
>>> +    }
>>> +}
>>> +
>>> +/* "Undo" an edge split.  Sometimes the sink of a boolean expression will be
>>> +   split into multiple blocks to accurately track line coverage, for example
>>> +   when there is a goto-label at the top of the then/else block:
>>> +
>>> +    if (a && b)
>>> +    {
>>> +	l1:
>>> +	...
>>> +    }
>>> +    else
>>> +    {
>>> +	l2:
>>> +	...
>>> +    }
>>> +
>>> +    and the corresponding CFG where a1 and b1 are created in edge splits to the
>>> +    same destination (F):
>>> +
>>> +    a
>>> +    |\
>>> +    | a1
>>> +    b  \
>>> +    |\  |
>>> +    | b1|
>>> +    |  \|
>>> +    T   F
>>> +
>>> +    This function recognizes this shape and returns the "merges" the split
>>> +    outcome block by returning their common successor.  In all other cases it is
>>> +    the identity function.
>>> +*/
>>> +basic_block
>>> +merge_split_outcome (basic_block b)
>>> +{
>>> +    if (!single (b->succs))
>>> +	return b;
>>> +    if (!single (b->preds))
>>> +	return b;
>>> +
>>> +    const unsigned flag = single_edge (b->preds)->flags & EDGE_CONDITION;
>>> +    if (!flag)
>>> +	return b;
>>> +
>>> +    edge e = single_edge (b->succs);
>>> +    for (edge pred : e->dest->preds)
>>> +    {
>>> +	if (!single (pred->src->preds))
>>> +	    return b;
>>> +	if (!(single_edge (pred->src->preds)->flags & flag))
>>> +	    return b;
>>> +    }
>>> +    return e->dest;
>>> +}
>>> +
>>> +
>>> +/* Find the set {ancestors(p) intersect G} where ancestors is the recursive set
>>> +   of predecessors for p.  Limiting to the ancestors that are also in G (see
>>> +   cond_reachable_from) and by q is an optimization as ancestors outside G have
>>> +   no effect when isolating expressions.
>>> +
>>> +   dfs_enumerate_from () does not work as the filter function needs edge
>>> +   information and dfs_enumerate_from () only considers blocks. */
>>> +void
>>> +ancestors_of (basic_block p, basic_block q, const sbitmap G, sbitmap ancestors)
>>> +{
>>> +    if (!bitmap_bit_p (G, p->index))
>>> +	return;
>>> +
>>> +    bitmap_set_bit (ancestors, p->index);
>>> +    bitmap_set_bit (ancestors, q->index);
>>> +    if (p == q)
>>> +	return;
>>> +
>>> +    auto_vec<basic_block, 16> stack;
>>> +    stack.safe_push (p);
>>> +
>>> +    while (!stack.is_empty())
>>> +    {
>>> +	basic_block b = stack.pop ();
>>> +	if (single (b->preds))
>>> +	{
>>> +	    edge e = single_edge (b->preds);
>>> +	    e = contract_edge_up (e);
>>> +	    b = e->dest;
>>> +	}
>>> +
>>> +	for (edge e : b->preds)
>>> +	{
>>> +	    basic_block src = e->src;
>>> +	    if (bitmap_bit_p (ancestors, e->src->index))
>>> +		continue;
>>> +	    if (!bitmap_bit_p (G, e->src->index))
>>> +		continue;
>>> +	    bitmap_set_bit (ancestors, src->index);
>>> +	    stack.safe_push (src);
>>> +	}
>>> +    }
>>> +}
>>> +
>>> +/* A simple struct for storing/returning outcome block pairs.  Either both
>>> +   blocks are set or both are NULL. */
>>> +struct outcomes
>>> +{
>>> +    basic_block t = NULL;
>>> +    basic_block f = NULL;
>>> +
>>> +    operator bool () const noexcept (true)
>>> +    {
>>> +	return t && f;
>>> +    }
>>> +};
>>> +
>>> +/* Get the true/false successors of a basic block. If b is not a conditional
>>> +   block both edges are NULL. */
>>> +outcomes
>>> +conditional_succs (const basic_block b)
>>> +{
>>> +    outcomes c;
>>> +    for (edge e : b->succs)
>>> +    {
>>> +	if (e->flags & EDGE_TRUE_VALUE)
>>> +	    c.t = merge_split_outcome (e->dest);
>>> +	if (e->flags & EDGE_FALSE_VALUE)
>>> +	    c.f = merge_split_outcome (e->dest);
>>> +    }
>>> +
>>> +    gcc_assert ((c.t && c.f) || (!c.t && !c.f));
>>> +    return c;
>>> +}
>>> +
>>> +/* Get the index or offset of a conditional flag, 0 for true and 1 for false.
>>> +   These indices carry no semantics but must be consistent as they are used to
>>> +   index into data structures in code generation and gcov. */
>>> +unsigned
>>> +condition_index (unsigned flag)
>>> +{
>>> +    return (flag & EDGE_CONDITION) == EDGE_TRUE_VALUE ? 0 : 1;
>>> +}
>>> +
>>> +/* Compute the masking vector.
>>> +
>>> +   Masking and short circuiting are deeply connected - masking occurs when
>>> +   control flow reaches a state that is also reachable with short circuiting.
>>> +   In fact, masking corresponds to short circuiting in the CFG for the reversed
>>> +   expression.  This means we can find the limits, the last term in preceeding
>>> +   subexpressions, by following the edges that short circuit to the same
>>> +   outcome.
>>> +
>>> +   In the simplest case a || b:
>>> +
>>> +   a
>>> +   |\
>>> +   | b
>>> +   |/ \
>>> +   T   F
>>> +
>>> +   T has has multiple incoming edges and is the outcome of a short circuit,
>>> +   with top = a, bot = b.  The top node (a) is masked when the edge (b, T) is
>>> +   taken.
>>> +
>>> +   The names "top" and "bot" refer to a pair of nodes with a shared
>>> +   destination. The top is always the node corresponding to the left-most
>>> +   operand of the two it holds that index_map[top] < index_map[bot].
>>> +
>>> +   Now consider (a && b) || (c && d) and its masking vectors:
>>> +
>>> +   a
>>> +   |\
>>> +   b \
>>> +   |\|
>>> +   | c
>>> +   | |\
>>> +   | d \
>>> +   |/ \|
>>> +   T   F
>>> +
>>> +   a[0] = {}
>>> +   a[1] = {}
>>> +   b[0] = {a}
>>> +   b[1] = {}
>>> +   c[0] = {}
>>> +   c[1] = {}
>>> +   d[0] = {c}
>>> +   d[1] = {a,b}
>>> +
>>> +   Note that 0 and 1 are indices and not boolean values - a[0] is the index in
>>> +   the masking vector when a takes the true edge.
>>> +
>>> +   b[0] and d[0] are identical to the a || b example, and d[1] is the bot in
>>> +   the triangle [d, b] -> T.  b is the top node in the [d, b] relationship and
>>> +   last term in (a && b).  To find the other terms masked we use the fact that
>>> +   all nodes in an expression have outgoing edges to either the outcome or some
>>> +   other node in the expression.  The "bot" node is also the last term in a
>>> +   masked subexpression, so the problem becomes finding the subgraph where all
>>> +   paths end up in the successors to bot.
>>> +
>>> +   We find the terms by marking the outcomes (in this case c, T) and walk the
>>> +   predecessors starting at top (in this case b) and masking nodes when both
>>> +   successors are marked.
>>> +
>>> +   The masking vector is represented as two bitfields per term in the
>>> +   expression with the index corresponding to the term in the source
>>> +   expression.  a || b && c becomes the term vector [a b c] and the masking
>>> +   vectors [a[0] a[1] b[0] ...].  The kth bit of a masking vector is set if the
>>> +   the kth term is masked by taking the edge. */
>>> +void
>>> +masking_vectors (conds_ctx& ctx, array_slice<basic_block> blocks,
>>> +		 array_slice<gcov_type_unsigned> masks)
>>> +{
>>> +    gcc_assert (blocks.is_valid ());
>>> +    gcc_assert (!blocks.empty ());
>>> +    gcc_assert (masks.is_valid ());
>>> +
>>> +    sbitmap marks = ctx.G1;
>>> +    sbitmap expr = ctx.G2;
>>> +    vec<basic_block>& queue = ctx.B1;
>>> +    vec<basic_block>& body = ctx.B2;
>>> +    const vec<int>& index_map = ctx.index_map;
>>> +    bitmap_clear (expr);
>>> +
>>> +    for (const basic_block b : blocks)
>>> +	bitmap_set_bit (expr, b->index);
>>> +
>>> +    /* Set up for the iteration - include two outcome nodes in the traversal and
>>> +       ignore the leading term since it cannot mask anything.  The algorithm is
>>> +       not sensitive to the traversal order. */
>>> +    body.truncate (0);
>>> +    body.reserve (blocks.size () + 2);
>>> +    for (const basic_block b : blocks)
>>> +	body.quick_push (b);
>>> +
>>> +    outcomes out = conditional_succs (blocks.back ());
>>> +    body.quick_push (out.t);
>>> +    body.quick_push (out.f);
>>> +    body[0] = body.pop ();
>>> +
>>> +    for (const basic_block b : body)
>>> +    {
>>> +	for (edge e1 : b->preds)
>>> +	for (edge e2 : b->preds)
>>> +	{
>>> +	    const basic_block top = e1->src;
>>> +	    const basic_block bot = e2->src;
>>> +	    const unsigned cond = e1->flags & e2->flags & (EDGE_CONDITION);
>>> +
>>> +	    if (!cond)
>>> +		continue;
>>> +	    if (e1 == e2)
>>> +		continue;
>>> +	    if (!bitmap_bit_p (expr, top->index))
>>> +		continue;
>>> +	    if (!bitmap_bit_p (expr, bot->index))
>>> +		continue;
>>> +	    if (index_map[top->index] > index_map[bot->index])
>>> +		continue;
>>> +
>>> +	    outcomes out = conditional_succs (top);
>>> +	    gcc_assert (out);
>>> +	    bitmap_clear (marks);
>>> +	    bitmap_set_bit (marks, out.t->index);
>>> +	    bitmap_set_bit (marks, out.f->index);
>>> +	    queue.truncate (0);
>>> +	    queue.safe_push (top);
>>> +
>>> +	    // The edge bot -> outcome triggers the masking
>>> +	    const int m = 2*index_of (bot, blocks) + condition_index (cond);
>>> +	    while (!queue.is_empty())
>>> +	    {
>>> +		basic_block q = queue.pop ();
>>> +		/* q may have been processed & completed by being added to the
>>> +		   queue multiple times, so check that there is still work to
>>> +		   do before continuing. */
>>> +		if (bitmap_bit_p (marks, q->index))
>>> +		    continue;
>>> +
>>> +		outcomes succs = conditional_succs (q);
>>> +		if (!bitmap_bit_p (marks, succs.t->index))
>>> +		    continue;
>>> +		if (!bitmap_bit_p (marks, succs.f->index))
>>> +		    continue;
>>> +
>>> +		const int index = index_of (q, blocks);
>>> +		gcc_assert (index != -1);
>>> +		masks[m] |= gcov_type_unsigned (1) << index;
>>> +		bitmap_set_bit (marks, q->index);
>>> +
>>> +		for (edge e : q->preds)
>>> +		{
>>> +		    e = contract_edge_up (e);
>>> +		    if (!edge_conditional_p (e))
>>> +			continue;
>>> +		    if (e->flags & EDGE_DFS_BACK)
>>> +			continue;
>>> +		    if (bitmap_bit_p (marks, e->src->index))
>>> +			continue;
>>> +		    if (!bitmap_bit_p (expr, e->src->index))
>>> +			continue;
>>> +		    queue.safe_push (e->src);
>>> +		}
>>> +	    }
>>> +	}
>>> +    }
>>> +}
>>> +
>>> +/* Find the nodes reachable from p by following only (possibly contracted)
>>> +   condition edges dominated by p and ignore DFS back edges.  From a high level
>>> +   this is partitioning the CFG into subgraphs by removing all non-condition
>>> +   edges and selecting a single connected subgraph.  This creates a cut C = (G,
>>> +   G') where G is the returned explicitly by this function.
>>> +
>>> +   It is assumed that all paths from p go through q (q post-dominates p).  p
>>> +   must always be the first term in an expression and a condition node.
>>> +
>>> +   If |G| = 1 then this is a single term expression. If |G| > 1 then either
>>> +   this is a multi-term expression or the first block in the then/else block is
>>> +   a conditional expression as well.
>>> +
>>> +   Only nodes dominated by p is added - under optimization some blocks may be
>>> +   merged and multiple independent conditions may share the same outcome
>>> +   (making successors misidentified as a right operands), but true right-hand
>>> +   operands are always dominated by the first term.
>>> +
>>> +   The function outputs both a bitmap and a vector as both are useful to the
>>> +   caller. */
>>> +void
>>> +cond_reachable_from (basic_block p, basic_block q, sbitmap expr,
>>> +		     vec<basic_block>& out)
>>> +{
>>> +    out.safe_push (p);
>>> +    bitmap_set_bit (expr, p->index);
>>> +    for (unsigned pos = 0; pos < out.length (); pos++)
>>> +    {
>>> +	for (edge e : out[pos]->succs)
>>> +	{
>>> +	    basic_block dest = contract_edge (e)->dest;
>>> +	    if (dest == q)
>>> +		continue;
>>> +	    if (!dominated_by_p (CDI_DOMINATORS, dest, p))
>>> +		continue;
>>> +	    if (!block_conditional_p (dest))
>>> +		continue;
>>> +	    if (bitmap_bit_p (expr, dest->index))
>>> +		continue;
>>> +	    if (e->flags & EDGE_DFS_BACK)
>>> +		continue;
>>> +
>>> +	    bitmap_set_bit (expr, dest->index);
>>> +	    out.safe_push (dest);
>>> +	}
>>> +    }
>>> +}
>>> +
>>> +/* Find the neighborhood of the graph G = [blocks, blocks+n), the
>>> +   successors of nodes in G that are not also in G.  In the cut C = (G, G')
>>> +   these are the nodes in G' with incoming edges that cross the span. */
>>> +void
>>> +neighborhood (const vec<basic_block>& blocks, sbitmap G, vec<basic_block>& out)
>>> +{
>>> +    for (const basic_block b : blocks)
>>> +    {
>>> +	for (edge e : b->succs)
>>> +	{
>>> +	    basic_block dest = contract_edge (e)->dest;
>>> +	    if (bitmap_bit_p (G, dest->index))
>>> +		continue;
>>> +	    if (!out.contains (dest))
>>> +		out.safe_push (dest);
>>> +	}
>>> +    }
>>> +
>>> +    /* Fix the neighborhood by correcting edge splits to the outcome nodes. */
>>> +    for (unsigned i = 0; i != out.length (); i++)
>>> +    {
>>> +	basic_block prev = out[i];
>>> +	basic_block next = merge_split_outcome (prev);
>>> +	if (next->index != prev->index)
>>> +	{
>>> +	    bitmap_set_bit (G, prev->index);
>>> +	    out[i] = next;
>>> +	}
>>> +    }
>>> +}
>>> +
>>> +/* Find and isolate the expression starting at p.
>>> +
>>> +   Make a cut C = (G, G') following only condition edges.  G is a superset of
>>> +   the expression B, but the walk may include expressions from the then/else
>>> +   blocks if they start with conditions.  Only the subgraph B is the ancestor
>>> +   of *both* the then/else outcome, which means B is the intersection of the
>>> +   ancestors of the nodes in the neighborhood N(G). */
>>> +void
>>> +isolate_expression (conds_ctx &ctx, basic_block p, vec<basic_block>& out)
>>> +{
>>> +    sbitmap expr = ctx.G1;
>>> +    sbitmap reachable = ctx.G2;
>>> +    sbitmap ancestors = ctx.G3;
>>> +    bitmap_clear (expr);
>>> +    bitmap_clear (reachable);
>>> +
>>> +    vec<basic_block>& G = ctx.B1;
>>> +    vec<basic_block>& NG = ctx.B2;
>>> +    G.truncate (0);
>>> +    NG.truncate (0);
>>> +
>>> +    basic_block post = get_immediate_dominator (CDI_POST_DOMINATORS, p);
>>> +    cond_reachable_from (p, post, reachable, G);
>>> +    if (G.length () == 1)
>>> +    {
>>> +	out.safe_push (p);
>>> +	return;
>>> +    }
>>> +
>>> +    neighborhood (G, reachable, NG);
>>> +    bitmap_copy (expr, reachable);
>>> +
>>> +    for (const basic_block neighbor : NG)
>>> +    {
>>> +	bitmap_clear (ancestors);
>>> +	for (edge e : neighbor->preds)
>>> +	    ancestors_of (e->src, p, reachable, ancestors);
>>> +	bitmap_and (expr, expr, ancestors);
>>> +    }
>>> +
>>> +    for (const basic_block b : G)
>>> +	if (bitmap_bit_p (expr, b->index))
>>> +	    out.safe_push (b);
>>> +    out.sort (cmp_index_map, &ctx.index_map);
>>> +}
>>> +
>>> +/* Emit lhs = op1 <op> op2 on edges.  This emits non-atomic instructions and
>>> +   should only be used on the local accumulators. */
>>> +void
>>> +emit_bitwise_op (edge e, tree lhs, tree op1, tree_code op, tree op2)
>>> +{
>>> +    tree tmp;
>>> +    gassign *read;
>>> +    gassign *bitw;
>>> +    gimple *write;
>>> +
>>> +    tmp = make_temp_ssa_name (gcov_type_node, NULL, "__conditions_tmp");
>>> +    read = gimple_build_assign (tmp, op1);
>>> +    tmp = make_temp_ssa_name (gcov_type_node, NULL, "__conditions_tmp");
>>> +    bitw = gimple_build_assign (tmp, op, gimple_assign_lhs (read), op2);
>>> +    write = gimple_build_assign (lhs, gimple_assign_lhs (bitw));
>>> +
>>> +    gsi_insert_on_edge (e, read);
>>> +    gsi_insert_on_edge (e, bitw);
>>> +    gsi_insert_on_edge (e, write);
>>> +}
>>> +
>>> +/* Visitor for make_index_map. */
>>> +void
>>> +make_index_map_visit (basic_block b, vec<basic_block>& L, vec<int>& marks)
>>> +{
>>> +    if (marks[b->index])
>>> +	return;
>>> +
>>> +    for (edge e : b->succs)
>>> +	if (!(e->flags & EDGE_DFS_BACK))
>>> +	    make_index_map_visit (e->dest, L, marks);
>>> +
>>> +    marks[b->index] = 1;
>>> +    L.quick_push (b);
>>> +}
>>> +
>>> +/* Find a topological sorting of the blocks in a function so that left operands
>>> +   are before right operands including subexpressions.  Sorting on block index
>>> +   does not guarantee this property and the syntactical order of terms is very
>>> +   important to the condition coverage.  The sorting algorithm is from Cormen
>>> +   et al (2001) but with back-edges ignored and thus there is no need for
>>> +   temporary marks (for cycle detection).
>>> +
>>> +   It is important to select unvisited nodes in DFS order to ensure the
>>> +   roots/leading terms of boolean expressions are visited first (the other
>>> +   terms being covered by the recursive step), but the visiting order of
>>> +   individual boolean expressions carries no significance.
>>> +
>>> +   For the expression (a || (b && c) || d) the blocks should be [a b c d]. */
>>> +void
>>> +make_index_map (const vec<basic_block>& blocks, int max_index,
>>> +		vec<basic_block>& L, vec<int>& index_map)
>>> +{
>>> +    L.truncate (0);
>>> +    L.reserve (max_index);
>>> +
>>> +    /* Use of the output map as a temporary for tracking visited status. */
>>> +    index_map.truncate (0);
>>> +    index_map.safe_grow_cleared (max_index);
>>> +    for (const basic_block b : blocks)
>>> +	make_index_map_visit (b, L, index_map);
>>> +
>>> +    /* Insert canaries - if there are unreachable nodes (for example infinite
>>> +       loops) then the unreachable nodes should never be needed for comparison,
>>> +       and L.length () < max_index.  An index mapping should also never be
>>> +       recorded twice. */
>>> +    for (unsigned i = 0; i < index_map.length (); i++)
>>> +	index_map[i] = -1;
>>> +
>>> +    gcc_assert (blocks.length () == L.length ());
>>> +    L.reverse ();
>>> +    const unsigned nblocks = L.length ();
>>> +    for (unsigned i = 0; i < nblocks; i++)
>>> +    {
>>> +	gcc_assert (L[i]->index != -1);
>>> +	index_map[L[i]->index] = int (i);
>>> +    }
>>> +}
>>> +
>>> +/* Walk the CFG and collect conditionals.
>>> +
>>> +   1. Collect a candidate set G by walking from the root following all
>>> +      (contracted) condition edges.
>>> +   2. This creates a cut C = (G, G'); find the neighborhood N(G).
>>> +   3. For every node in N(G), follow the edges across the cut and collect all
>>> +      ancestors (that are also in G).
>>> +   4. The intersection of all these ancestor sets is the boolean expression B
>>> +      that starts in root.
>>> +
>>> +   Walking is not guaranteed to find nodes in the order of the expression, it
>>> +   might find (a || b) && c as [a c b], so the result must be sorted by the
>>> +   index map. */
>>> +const vec<basic_block>&
>>> +collect_conditions (conds_ctx& ctx, const basic_block block)
>>> +{
>>> +    vec<basic_block>& blocks = ctx.blocks;
>>> +    blocks.truncate (0);
>>> +
>>> +    if (bitmap_bit_p (ctx.marks, block->index))
>>> +	return blocks;
>>> +
>>> +    if (!block_conditional_p (block))
>>> +    {
>>> +	ctx.mark (block);
>>> +	return blocks;
>>> +    }
>>> +
>>> +    isolate_expression (ctx, block, blocks);
>>> +    ctx.mark (blocks);
>>> +
>>> +    if (blocks.length () > CONDITIONS_MAX_TERMS)
>>> +    {
>>> +	location_t loc = gimple_location (gsi_stmt (gsi_last_bb (block)));
>>> +	warning_at (loc, OPT_Wcoverage_too_many_conditions,
>>> +		    "Too many conditions (found %u); giving up coverage",
>>> +		    blocks.length ());
>>> +	blocks.truncate (0);
>>> +    }
>>> +    return blocks;
>>> +}
>>> +
>>> +/* Used for dfs_enumerate_from () to include all reachable nodes. */
>>> +bool
>>> +yes (const_basic_block, const void *)
>>> +{
>>> +    return true;
>>> +}
>>> +
>>> +}
>>> +
>>> +struct condcov {
>>> +    explicit condcov (unsigned nblocks) noexcept (true) : ctx (nblocks) {}
>>> +    auto_vec<int, 128> m_index;
>>> +    auto_vec<basic_block, 256> m_blocks;
>>> +    auto_vec<gcov_type_unsigned, 512> m_masks;
>>> +    conds_ctx ctx;
>>> +};
>>> +
>>> +unsigned
>>> +cov_length (const struct condcov* cov)
>>> +{
>>> +    if (cov->m_index.is_empty ())
>>> +	return 0;
>>> +    return cov->m_index.length () - 1;
>>> +}
>>> +
>>> +array_slice<basic_block>
>>> +cov_blocks (struct condcov* cov, unsigned n)
>>> +{
>>> +    if (n >= cov->m_index.length ())
>>> +	return array_slice<basic_block>::invalid ();
>>> +
>>> +    basic_block *begin = cov->m_blocks.begin () + cov->m_index[n];
>>> +    basic_block *end = cov->m_blocks.begin () + cov->m_index[n + 1];
>>> +    return array_slice<basic_block> (begin, end - begin);
>>> +}
>>> +
>>> +array_slice<gcov_type_unsigned>
>>> +cov_masks (struct condcov* cov, unsigned n)
>>> +{
>>> +    if (n >= cov->m_index.length ())
>>> +	return array_slice<gcov_type_unsigned>::invalid ();
>>> +
>>> +    gcov_type_unsigned *begin = cov->m_masks.begin () + 2*cov->m_index[n];
>>> +    gcov_type_unsigned *end = cov->m_masks.begin () + 2*cov->m_index[n + 1];
>>> +    return array_slice<gcov_type_unsigned> (begin, end - begin);
>>> +}
>>> +
>>> +void
>>> +cov_free (struct condcov* cov)
>>> +{
>>> +    delete cov;
>>> +}
>>> +
>>> +/* Condition coverage (MC/DC)
>>> +
>>> +   Algorithm
>>> +   ---------
>>> +   Whalen, Heimdahl, De Silva in "Efficient Test Coverage Measurement for
>>> +   MC/DC" describe an algorithm for modified condition/decision coverage based
>>> +   on AST analysis.  This algorithm analyses the control flow graph to analyze
>>> +   expressions and compute masking vectors, but is inspired by their marking
>>> +   functions for recording outcomes.  The individual phases are described in
>>> +   more detail closer to the implementation.
>>> +
>>> +   The CFG is traversed in DFS order.  It is important that the first basic
>>> +   block in an expression is the first one visited, but the order of
>>> +   independent expressions does not matter.  When the function terminates,
>>> +   every node in the dfs should have been processed and marked exactly once.
>>> +   If there are unreachable nodes they are ignored and not instrumented.
>>> +
>>> +   The CFG is broken up into segments between dominators.  This isn't strictly
>>> +   necessary, but since boolean expressions cannot cross dominators it makes
>>> +   for a nice way to introduce limits to searches.
>>> +
>>> +   The coverage only considers the positions, not the symbols, in a
>>> +   conditional, e.g. !A || (!B && A) is a 3-term conditional even though A
>>> +   appears twice.  Subexpressions have no effect on term ordering:
>>> +   (a && (b || (c && d)) || e) comes out as [a b c d e].
>>> +
>>> +   The output for gcov is a vector of pairs of unsigned integers, interpreted
>>> +   as bit-sets, where the bit index corresponds to the index of the condition
>>> +   in the expression.
>>> + */
>>> +struct condcov*
>>> +find_conditions (struct function *fn)
>>> +{
>>> +    record_loop_exits ();
>>> +    mark_dfs_back_edges (fn);
>>> +
>>> +    const bool have_dom = dom_info_available_p (fn, CDI_DOMINATORS);
>>> +    const bool have_post_dom = dom_info_available_p (fn, CDI_POST_DOMINATORS);
>>> +    if (!have_dom)
>>> +	calculate_dominance_info (CDI_DOMINATORS);
>>> +    if (!have_post_dom)
>>> +	calculate_dominance_info (CDI_POST_DOMINATORS);
>>> +
>>> +    const unsigned nblocks = n_basic_blocks_for_fn (fn);
>>> +    condcov *cov = new condcov (nblocks);
>>> +    conds_ctx& ctx = cov->ctx;
>>> +
>>> +    auto_vec<basic_block, 16> dfs;
>>> +    dfs.safe_grow (nblocks);
>>> +    const basic_block entry = ENTRY_BLOCK_PTR_FOR_FN (fn);
>>> +    const basic_block exit = ENTRY_BLOCK_PTR_FOR_FN (fn);
>>> +    int n = dfs_enumerate_from (entry, 0, yes, dfs.address (), nblocks, exit);
>>> +    dfs.truncate (n);
>>> +    make_index_map (dfs, nblocks, ctx.B1, ctx.index_map);
>>> +
>>> +    /* Visit all reachable nodes and collect conditions.  DFS order is
>>> +       important so the first node of a boolean expression is visited first
>>> +       (it will mark subsequent terms). */
>>> +    cov->m_index.safe_push (0);
>>> +    for (const basic_block b : dfs)
>>> +    {
>>> +	const vec<basic_block>& expr = collect_conditions (ctx, b);
>>> +	if (!expr.is_empty ())
>>> +	{
>>> +	    cov->m_blocks.safe_splice (expr);
>>> +	    cov->m_index.safe_push (cov->m_blocks.length ());
>>> +	}
>>> +    }
>>> +    gcc_assert (ctx.all_marked (dfs));
>>> +
>>> +    if (!have_dom)
>>> +	free_dominance_info (fn, CDI_DOMINATORS);
>>> +    if (!have_post_dom)
>>> +	free_dominance_info (fn, CDI_POST_DOMINATORS);
>>> +
>>> +    cov->m_masks.safe_grow_cleared (2 * cov->m_index.last());
>>> +    const unsigned length = cov_length (cov);
>>> +    for (unsigned i = 0; i < length; i++)
>>> +	masking_vectors (ctx, cov_blocks (cov, i), cov_masks (cov, i));
>>> +
>>> +    return cov;
>>> +}
>>> +
>>> +int
>>> +instrument_decisions (array_slice<basic_block> expr, unsigned condno,
>>> +		      tree *accu, gcov_type_unsigned *masks)
>>> +{
>>> +    /* Zero the local accumulators. */
>>> +    tree zero = build_int_cst (get_gcov_type (), 0);
>>> +    for (edge e : expr[0]->succs)
>>> +    {
>>> +	gsi_insert_on_edge (e, gimple_build_assign (accu[0], zero));
>>> +	gsi_insert_on_edge (e, gimple_build_assign (accu[1], zero));
>>> +    }
>>> +    /* Add instructions for updating the function-local accumulators.  */
>>> +    for (size_t i = 0; i < expr.size (); i++)
>>> +    {
>>> +	for (edge e : expr[i]->succs)
>>> +	{
>>> +	    if (!edge_conditional_p (e))
>>> +		continue;
>>> +
>>> +	    /* accu |= expr[i] */
>>> +	    const int k = condition_index (e->flags);
>>> +	    tree rhs = build_int_cst (gcov_type_node, 1ULL << i);
>>> +	    emit_bitwise_op (e, accu[k], accu[k], BIT_IOR_EXPR, rhs);
>>> +
>>> +	    if (masks[2*i + k] == 0)
>>> +		continue;
>>> +
>>> +	    /* accu &= mask[i] */
>>> +	    tree mask = build_int_cst (gcov_type_node, ~masks[2*i + k]);
>>> +	    for (int j = 0; j < 2; j++)
>>> +		emit_bitwise_op (e, accu[j], accu[j], BIT_AND_EXPR, mask);
>>> +	}
>>> +    }
>>> +
>>> +    const bool atomic = flag_profile_update == PROFILE_UPDATE_ATOMIC;
>>> +    const tree atomic_ior = builtin_decl_explicit
>>> +	(TYPE_PRECISION (gcov_type_node) > 32
>>> +	 ? BUILT_IN_ATOMIC_FETCH_OR_8
>>> +	 : BUILT_IN_ATOMIC_FETCH_OR_4);
>>> +
>>> +    /* Add instructions for flushing the local accumulators.
>>> +
>>> +       It is important that the flushes happen on on the outcome's incoming
>>> +       edges, otherwise flushes could be lost to exception handling.
>>> +
>>> +       void fn (int a)
>>> +       {
>>> +	   if (a)
>>> +	    fclose();
>>> +	   exit();
>>> +       }
>>> +
>>> +       Can yield the CFG:
>>> +       A
>>> +       |\
>>> +       | B
>>> +       |/
>>> +       e
>>> +
>>> +       This typically only happen in optimized builds, but gives linker errors
>>> +       because the counter is left as an undefined symbol. */
>>> +
>>> +    outcomes out = conditional_succs (expr.back ());
>>> +    const basic_block outcome_blocks[] = { out.t, out.t, out.f, out.f, };
>>> +    const int outcome[] = { 0, 1, 0, 1 };
>>> +    for (int i = 0; i < 4; i++)
>>> +    {
>>> +	const int k = outcome[i];
>>> +	for (edge e : outcome_blocks[i]->preds)
>>> +	{
>>> +	    /* The outcome may have been split and we want to check if the
>>> +	       edge is sourced from inside the expression, so contract it to
>>> +	       find the source conditional edge. */
>>> +	    e = contract_edge_up (e);
>>> +
>>> +	    /* Only instrument edges from inside the expression. Sometimes
>>> +	       complicated control flow (like sigsetjmp and gotos) add
>>> +	       predecessors that don't come from the boolean expression. */
>>> +	    if (index_of (e->src, expr) == -1)
>>> +		continue;
>>> +
>>> +	    tree ref = tree_coverage_counter_ref (GCOV_COUNTER_CONDS,
>>> +						  2*condno + k);
>>> +	    tree tmp = make_temp_ssa_name (gcov_type_node, NULL,
>>> +					   "__conditions_tmp");
>>> +	    if (atomic)
>>> +	    {
>>> +		tree relaxed = build_int_cst (integer_type_node,
>>> +					      MEMMODEL_RELAXED);
>>> +		ref = unshare_expr (ref);
>>> +		gassign *read = gimple_build_assign (tmp, accu[k]);
>>> +		gcall *flush = gimple_build_call (atomic_ior, 3,
>>> +						  build_addr (ref),
>>> +						  gimple_assign_lhs (read),
>>> +						  relaxed);
>>> +
>>> +		gsi_insert_on_edge (e, read);
>>> +		gsi_insert_on_edge (e, flush);
>>> +	    }
>>> +	    else
>>> +	    {
>>> +		gassign *read = gimple_build_assign (tmp, ref);
>>> +		tmp = gimple_assign_lhs (read);
>>> +		gsi_insert_on_edge (e, read);
>>> +		ref = unshare_expr (ref);
>>> +		emit_bitwise_op (e, ref, accu[k], BIT_IOR_EXPR, tmp);
>>> +	    }
>>> +	}
>>> +    }
>>> +    return expr.size ();
>>> +}
>>> +
>>> +#undef CONDITIONS_MAX_TERMS
>>> +#undef EDGE_CONDITION
>>> +
>>>  /* Do initialization work for the edge profiler.  */
>>>  
>>>  /* Add code:
>>> @@ -758,7 +1802,7 @@ tree_profiling (void)
>>>  	  thunk = true;
>>>  	  /* When generate profile, expand thunk to gimple so it can be
>>>  	     instrumented same way as other functions.  */
>>> -	  if (profile_arc_flag)
>>> +	  if (profile_arc_flag || profile_condition_flag)
>>>  	    expand_thunk (node, false, true);
>>>  	  /* Read cgraph profile but keep function as thunk at profile-use
>>>  	     time.  */
>>> @@ -803,7 +1847,7 @@ tree_profiling (void)
>>>    release_profile_file_filtering ();
>>>  
>>>    /* Drop pure/const flags from instrumented functions.  */
>>> -  if (profile_arc_flag || flag_test_coverage)
>>> +  if (profile_arc_flag || profile_condition_flag || flag_test_coverage)
>>>      FOR_EACH_DEFINED_FUNCTION (node)
>>>        {
>>>  	if (!gimple_has_body_p (node->decl)
>>> @@ -897,7 +1941,7 @@ pass_ipa_tree_profile::gate (function *)
>>>       disabled.  */
>>>    return (!in_lto_p && !flag_auto_profile
>>>  	  && (flag_branch_probabilities || flag_test_coverage
>>> -	      || profile_arc_flag));
>>> +	      || profile_arc_flag || profile_condition_flag));
>>>  }
>>>  
>>>  } // anon namespace
>>> diff --git a/libgcc/libgcov-merge.c b/libgcc/libgcov-merge.c
>>> index 89741f637e1..9e3e8ee5657 100644
>>> --- a/libgcc/libgcov-merge.c
>>> +++ b/libgcc/libgcov-merge.c
>>> @@ -33,6 +33,11 @@ void __gcov_merge_add (gcov_type *counters  __attribute__ ((unused)),
>>>                         unsigned n_counters __attribute__ ((unused))) {}
>>>  #endif
>>>  
>>> +#ifdef L_gcov_merge_ior
>>> +void __gcov_merge_ior (gcov_type *counters  __attribute__ ((unused)),
>>> +		       unsigned n_counters __attribute__ ((unused))) {}
>>> +#endif
>>> +
>>>  #ifdef L_gcov_merge_topn
>>>  void __gcov_merge_topn (gcov_type *counters  __attribute__ ((unused)),
>>>  			unsigned n_counters __attribute__ ((unused))) {}
>>
>> Gentle ping. I have a tuned the summary output slightly (decisions covered ->
>> condition outcomes covered) already.
> 
> Ping. I would like to see this become a part of gcc 13, will we be able to
> commit before the window closes?
> 
> Thanks,
> Jørgen



^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: Ping [PATCH] Add condition coverage profiling
  2022-11-02  6:16   ` Jørgen Kvalsvik
  2022-11-09 20:08     ` [Ping 2][PATCH] " Jørgen Kvalsvik
@ 2022-11-10 14:19     ` Martin Liška
  1 sibling, 0 replies; 39+ messages in thread
From: Martin Liška @ 2022-11-10 14:19 UTC (permalink / raw)
  To: Jørgen Kvalsvik, gcc-patches

On 11/2/22 07:16, Jørgen Kvalsvik via Gcc-patches wrote:
> Ping. I would like to see this become a part of gcc 13, will we be able to
> commit before the window closes?

Hello.

I'm sorry but I was interrupted by the Sphinx conversion task. Anyway, please update
the coding style and I can return to patch review in the upcoming weeks.

About the window closure: we still have (basically till the end of year) as stage1 closing
period means that all patches should be *under review* until this deadline. And that's true
for your patch.

Cheers,
Martin

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-08-04  7:43         ` Sebastian Huber
@ 2022-08-04  9:13           ` Jørgen Kvalsvik
  0 siblings, 0 replies; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-08-04  9:13 UTC (permalink / raw)
  To: Sebastian Huber, gcc-patches

On 04/08/2022 09:43, Sebastian Huber wrote:
> On 02/08/2022 09:58, Jørgen Kvalsvik wrote:
>> Based on this established terminology I can think of a few good candidates:
>>
>> condition outcomes covered n/m
>> outcomes covered n/m
>>
>> What do you think?
> 
> Both are fine, but I would prefer "condition outcomes covered n/m".
> 

I'll update the patch. Maybe we should review the other outputs too? Something like:

condition N missing outcome (true false)

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-08-02  7:58       ` Jørgen Kvalsvik
@ 2022-08-04  7:43         ` Sebastian Huber
  2022-08-04  9:13           ` Jørgen Kvalsvik
  0 siblings, 1 reply; 39+ messages in thread
From: Sebastian Huber @ 2022-08-04  7:43 UTC (permalink / raw)
  To: Jørgen Kvalsvik, gcc-patches

On 02/08/2022 09:58, Jørgen Kvalsvik wrote:
> Based on this established terminology I can think of a few good candidates:
> 
> condition outcomes covered n/m
> outcomes covered n/m
> 
> What do you think?

Both are fine, but I would prefer "condition outcomes covered n/m".

-- 
embedded brains GmbH
Herr Sebastian HUBER
Dornierstr. 4
82178 Puchheim
Germany
email: sebastian.huber@embedded-brains.de
phone: +49-89-18 94 741 - 16
fax:   +49-89-18 94 741 - 08

Registergericht: Amtsgericht München
Registernummer: HRB 157899
Vertretungsberechtigte Geschäftsführer: Peter Rasmussen, Thomas Dörfler
Unsere Datenschutzerklärung finden Sie hier:
https://embedded-brains.de/datenschutzerklaerung/

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-07-15 13:47     ` Jørgen Kvalsvik
@ 2022-08-02  7:58       ` Jørgen Kvalsvik
  2022-08-04  7:43         ` Sebastian Huber
  0 siblings, 1 reply; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-08-02  7:58 UTC (permalink / raw)
  To: Sebastian Huber, gcc-patches

On 15/07/2022 15:47, Jørgen Kvalsvik wrote:
> On 15/07/2022 15:31, Sebastian Huber wrote:
>> On 15.07.22 13:47, Jørgen Kvalsvik via Gcc-patches wrote:
>>> 2. New vocabulary for the output - decisions for, well, the decisions. It also
>>> writes at most one line per condition:
>>>
>>>     decisions covered 1/4
>>>     condition  0 not covered (true false)
>>>     condition  1 not covered (true)
>>
>> Do we really have multiple decisions? I think we have only one decision composed
>> of conditions and zero or more boolean operators. We have variants of condition
>> outcomes.
>>
> 
> Maybe, I'm not sure - you could argue that since a fixture of boolean
> "dominates" the outcome (either by short circuiting subsequent terms or masking
> preceding terms) then that fixture is a decision which leads to one of two
> outcomes.  The other parameters may change but they wouldn't change the
> decision. You have 2^N inputs but only N+1 decisions. Personally the "variants"
> phrasing doesn't feel right to me.
> 
> That being said I'm open to making this whatever the maintainers feel is
> appropriate.

Coming back to this, this is the terms + definitions gracefully borrowed from
wiki [1]:

Condition
    A condition is a leaf-level Boolean expression (it cannot be broken down
into simpler Boolean expressions).

Decision
    A Boolean expression composed of conditions and zero or more Boolean
operators. A decision without a Boolean operator is a condition.

Condition coverage
    Every condition in a decision in the program has taken all possible outcomes
at least once.

Decision coverage
    Every point of entry and exit in the program has been invoked at least once,
and every decision in the program has taken all possible outcomes at least once.



I agree that decisions n/m, based on these definitions, is not the best output
unless it is specifically overloaded to mean "the family of bit vectors that
cover some conditions". Not great.

Based on this established terminology I can think of a few good candidates:

condition outcomes covered n/m
outcomes covered n/m

What do you think?

[1] https://en.wikipedia.org/wiki/Modified_condition/decision_coverage

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-07-15 13:31   ` Sebastian Huber
@ 2022-07-15 13:47     ` Jørgen Kvalsvik
  2022-08-02  7:58       ` Jørgen Kvalsvik
  0 siblings, 1 reply; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-07-15 13:47 UTC (permalink / raw)
  To: Sebastian Huber, gcc-patches

On 15/07/2022 15:31, Sebastian Huber wrote:
> On 15.07.22 13:47, Jørgen Kvalsvik via Gcc-patches wrote:
>> 2. New vocabulary for the output - decisions for, well, the decisions. It also
>> writes at most one line per condition:
>>
>>     decisions covered 1/4
>>     condition  0 not covered (true false)
>>     condition  1 not covered (true)
> 
> Do we really have multiple decisions? I think we have only one decision composed
> of conditions and zero or more boolean operators. We have variants of condition
> outcomes.
> 

Maybe, I'm not sure - you could argue that since a fixture of boolean
"dominates" the outcome (either by short circuiting subsequent terms or masking
preceding terms) then that fixture is a decision which leads to one of two
outcomes.  The other parameters may change but they wouldn't change the
decision. You have 2^N inputs but only N+1 decisions. Personally the "variants"
phrasing doesn't feel right to me.

That being said I'm open to making this whatever the maintainers feel is
appropriate.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-07-15 11:47 ` Jørgen Kvalsvik
@ 2022-07-15 13:31   ` Sebastian Huber
  2022-07-15 13:47     ` Jørgen Kvalsvik
  0 siblings, 1 reply; 39+ messages in thread
From: Sebastian Huber @ 2022-07-15 13:31 UTC (permalink / raw)
  To: Jørgen Kvalsvik, gcc-patches

On 15.07.22 13:47, Jørgen Kvalsvik via Gcc-patches wrote:
> 2. New vocabulary for the output - decisions for, well, the decisions. It also
> writes at most one line per condition:
> 
> 	decisions covered 1/4
> 	condition  0 not covered (true false)
> 	condition  1 not covered (true)

Do we really have multiple decisions? I think we have only one decision 
composed of conditions and zero or more boolean operators. We have 
variants of condition outcomes.

-- 
embedded brains GmbH
Herr Sebastian HUBER
Dornierstr. 4
82178 Puchheim
Germany
email: sebastian.huber@embedded-brains.de
phone: +49-89-18 94 741 - 16
fax:   +49-89-18 94 741 - 08

Registergericht: Amtsgericht München
Registernummer: HRB 157899
Vertretungsberechtigte Geschäftsführer: Peter Rasmussen, Thomas Dörfler
Unsere Datenschutzerklärung finden Sie hier:
https://embedded-brains.de/datenschutzerklaerung/

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-07-15 11:39 Jørgen Kvalsvik
@ 2022-07-15 11:47 ` Jørgen Kvalsvik
  2022-07-15 13:31   ` Sebastian Huber
  0 siblings, 1 reply; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-07-15 11:47 UTC (permalink / raw)
  To: gcc-patches

On 15/07/2022 13:39, Jørgen Kvalsvik wrote:
> This patch adds support in gcc+gcov for modified condition/decision
> coverage (MC/DC) with the -fprofile-conditions flag.

Hello,

I have updated this patch based on the feedback from Sebastian and Martin.

1. The description of the masking_vector function has been updated.
2. New vocabulary for the output - decisions for, well, the decisions. It also
writes at most one line per condition:

	decisions covered 1/4
	condition  0 not covered (true false)
	condition  1 not covered (true)
3. I applied Sebastian's patch and so it should work free standing.

Thanks,
Jørgen


^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH] Add condition coverage profiling
@ 2022-07-15 11:39 Jørgen Kvalsvik
  2022-07-15 11:47 ` Jørgen Kvalsvik
  0 siblings, 1 reply; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-07-15 11:39 UTC (permalink / raw)
  To: gcc-patches

This patch adds support in gcc+gcov for modified condition/decision
coverage (MC/DC) with the -fprofile-conditions flag. MC/DC is a type of
test/code coverage and it is particularly important in the avation and
automotive industries for safety-critical applications. MC/DC it is
required for or recommended by:

    * DO-178C for the most critical software (Level A) in avionics
    * IEC 61508 for SIL 4
    * ISO 26262-6 for ASIL D

From the SQLite webpage:

    Two methods of measuring test coverage were described above:
    "statement" and "branch" coverage. There are many other test
    coverage metrics besides these two. Another popular metric is
    "Modified Condition/Decision Coverage" or MC/DC. Wikipedia defines
    MC/DC as follows:

        * Each decision tries every possible outcome.
        * Each condition in a decision takes on every possible outcome.
        * Each entry and exit point is invoked.
        * Each condition in a decision is shown to independently affect
          the outcome of the decision.

    In the C programming language where && and || are "short-circuit"
    operators, MC/DC and branch coverage are very nearly the same thing.
    The primary difference is in boolean vector tests. One can test for
    any of several bits in bit-vector and still obtain 100% branch test
    coverage even though the second element of MC/DC - the requirement
    that each condition in a decision take on every possible outcome -
    might not be satisfied.

    https://sqlite.org/testing.html#mcdc

Wahlen, Heimdahl, and De Silva "Efficient Test Coverage Measurement for
MC/DC" describes an algorithm for adding instrumentation by carrying
over information from the AST, but my algorithm analyses the the control
flow graph to instrument for coverage. This has the benefit of being
programming language independent and faithful to compiler decisions
and transformations, although I have only tested it on constructs in C
and C++, see testsuite/gcc.misc-tests and testsuite/g++.dg.

Like Wahlen et al this implementation records coverage in fixed-size
bitsets which gcov knows how to interpret. This is very fast, but
introduces a limit on the number of terms in a single boolean
expression, the number of bits in a gcov_unsigned_type (which is
typedef'd to uint64_t), so for most practical purposes this would be
acceptable. This limitation is in the implementation and not the
algorithm, so support for more conditions can be added by also
introducing arbitrary-sized bitsets.

For space overhead, the instrumentation needs two accumulators
(gcov_unsigned_type) per condition in the program which will be written
to the gcov file. In addition, every function gets a pair of local
accumulators, but these accmulators are reused between conditions in the
same function.

For time overhead, there is a zeroing of the local accumulators for
every condition and one or two bitwise operation on every edge taken in
the an expression.

In action it looks pretty similar to the branch coverage. The -g short
opt carries no significance, but was chosen because it was an available
option with the upper-case free too.

gcov --conditions:

        3:   17:void fn (int a, int b, int c, int d) {
        3:   18:    if ((a && (b || c)) && d)
decisions covered 3/8
condition  0 not covered (true false)
condition  1 not covered (true)
condition  2 not covered (true)
condition  3 not covered (true)
        1:   19:        x = 1;
        -:   20:    else
        2:   21:        x = 2;
        3:   22:}

gcov --conditions --json-format:

    "conditions": [
        {
            "not_covered_false": [
                0
            ],
            "count": 8,
            "covered": 3,
            "not_covered_true": [
                0,
                1,
                2,
                3
            ]
        }
    ],

Some expressions, mostly those without else-blocks, are effectively
"rewritten" in the CFG construction making the algorithm unable to
distinguish them:

and.c:

    if (a && b && c)
        x = 1;

ifs.c:

    if (a)
        if (b)
            if (c)
                x = 1;

gcc will build the same graph for both these programs, and gcov will
report boths as 3-term expressions. It is vital that it is not
interpreted the other way around (which is consistent with the shape of
the graph) because otherwise the masking would be wrong for the and.c
program which is a more severe error. While surprising, users would
probably expect some minor rewriting of semantically-identical
expressions.

and.c.gcov:
    #####:    2:    if (a && b && c)
decisions covered 6/6
    #####:    3:        x = 1;

ifs.c.gcov:
    #####:    2:    if (a)
    #####:    3:        if (b)
    #####:    4:            if (c)
    #####:    5:                x = 1;
decisions covered 6/6

Adding else clauses alters the program (ifs.c can have 3 elses, and.c
only 1) and coverage becomes less surprising

ifs.c.gcov:
    #####:    2:    if (a)
decisions covered 2/2
    #####:    4:    {
    #####:    4:        if (b)
decisions covered 2/2
              5:        {
    #####:    6:            if (c)
decisions covered 2/2
    #####:    7:                x = 1;
    #####:    8:        }
    #####:    9:        else
    #####:   10:            x = 2;
    #####:   11:    }
    #####:   12:    else
    #####:   13:        x = 3;

Since the algorithm works on CFGs, it cannot detect some ternary
operator introduced conditionals. For example, int x = a ? 0 : 1 in
gimple becomes _x = (_a == 0). From source you would expect coverage,
but it gets neither branch nor condition coverage. For completeness, it
could be achieved by scanning all gimple statements for such
comparisons, and insert an extra instruction for recording the outcome.

The test suite contains a lot of small programs functions. Some of these
were designed by hand to test for specific behaviours and graph shapes,
and some are previously-failed test cases in other programs adapted into
the test suite.

Alternative author email: Jørgen Kvalsvik <j@lambda.is>

gcc/ChangeLog:

	* builtins.cc (expand_builtin_fork_or_exec): Check
	profile_condition_flag.
        * collect2.cc (main): Add -fno-profile-conditions to OBSTACK.
	* common.opt: Add new options -fprofile-conditions and
	* doc/gcov.texi: Add --conditions documentation.
	* doc/invoke.texi: Add -fprofile-conditions documentation.
	* gcc.cc: Link gcov on -fprofile-conditions.
	* gcov-counter.def (GCOV_COUNTER_CONDS): New.
	* gcov-dump.cc (tag_conditions): New.
	* gcov-io.h (GCOV_TAG_CONDS): New.
	(GCOV_TAG_CONDS_LENGTH): Likewise.
	(GCOV_TAG_CONDS_NUM): Likewise.
	* gcov.cc (class condition_info): New.
	(condition_info::condition_info): New.
	(condition_info::popcount): New.
	(struct coverage_info): New.
	(add_condition_counts): New.
	(output_conditions): New.
	(print_usage): Add -g, --conditions.
	(process_args): Likewise.
	(output_intermediate_json_line): Output conditions.
	(read_graph_file): Read conditions counters.
	(read_count_file): Read conditions counters.
	(file_summary): Print conditions.
	(accumulate_line_info): Accumulate conditions.
	(output_line_details): Print conditions.
	* ipa-inline.cc (can_early_inline_edge_p): Check
	profile_condition_flag.
	* ipa-split.cc (pass_split_functions::gate): Likewise.
	* passes.cc (finish_optimization_passes): Likewise.
	* profile.cc (find_conditions): New declaration.
	(cov_length): Likewise.
	(cov_blocks): Likewise.
	(cov_masks): Likewise.
	(cov_free): Likewise.
	(instrument_decisions): New.
	(read_thunk_profile): Control output to file.
	(branch_prob): Call find_conditions, instrument_decisions.
	(init_branch_prob): Add total_num_conds.
	(end_branch_prob): Likewise.
	* tree-profile.cc (struct conds_ctx): New.
	(CONDITIONS_MAX_TERMS): New.
	(EDGE_CONDITION): New.
	(cmp_index_map): New.
	(index_of): New.
	(block_conditional_p): New.
	(edge_conditional_p): New.
	(single): New.
	(single_edge): New.
	(contract_edge): New.
	(contract_edge_up): New.
	(ancestors_of): New.
	(struct outcomes): New.
	(conditional_succs): New.
	(condition_index): New.
	(masking_vectors): New.
	(cond_reachable_from): New.
	(neighborhood): New.
	(isolate_expression): New.
	(emit_bitwise_op): New.
	(make_index_map_visit): New.
	(make_index_map): New.
	(collect_conditions): New.
	(yes): New.
	(struct condcov): New.
	(cov_length): New.
	(cov_blocks): New.
	(cov_masks): New.
	(cov_free): New.
	(find_conditions): New.
	(instrument_decisions): New.
	(tree_profiling): Check profile_condition_flag.
	(pass_ipa_tree_profile::gate): Likewise.

gcc/testsuite/ChangeLog:

	* lib/gcov.exp: Add condition coverage test function.
	* g++.dg/gcov/gcov-18.C: New test.
	* gcc.misc-tests/gcov-19.c: New test.
	* gcc.misc-tests/gcov-20.c: New test.
	* gcc.misc-tests/gcov-21.c: New test.
---
 gcc/builtins.cc                        |    2 +-
 gcc/collect2.cc                        |    5 +-
 gcc/common.opt                         |    8 +
 gcc/doc/gcov.texi                      |   36 +
 gcc/doc/invoke.texi                    |   19 +
 gcc/gcc.cc                             |    4 +-
 gcc/gcov-counter.def                   |    3 +
 gcc/gcov-dump.cc                       |   24 +
 gcc/gcov-io.h                          |    3 +
 gcc/gcov.cc                            |  200 ++++-
 gcc/ipa-inline.cc                      |    2 +-
 gcc/ipa-split.cc                       |    3 +-
 gcc/passes.cc                          |    3 +-
 gcc/profile.cc                         |   84 +-
 gcc/testsuite/g++.dg/gcov/gcov-18.C    |  234 +++++
 gcc/testsuite/gcc.misc-tests/gcov-19.c | 1137 ++++++++++++++++++++++++
 gcc/testsuite/gcc.misc-tests/gcov-20.c |   22 +
 gcc/testsuite/gcc.misc-tests/gcov-21.c |   16 +
 gcc/testsuite/lib/gcov.exp             |  191 +++-
 gcc/tree-profile.cc                    |  978 +++++++++++++++++++-
 libgcc/libgcov-merge.c                 |    5 +
 21 files changed, 2952 insertions(+), 27 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-18.C
 create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-19.c
 create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-20.c
 create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-21.c

diff --git a/gcc/builtins.cc b/gcc/builtins.cc
index 35b9197945f..0cf32a9c443 100644
--- a/gcc/builtins.cc
+++ b/gcc/builtins.cc
@@ -5584,7 +5584,7 @@ expand_builtin_fork_or_exec (tree fn, tree exp, rtx target, int ignore)
   tree call;
 
   /* If we are not profiling, just call the function.  */
-  if (!profile_arc_flag)
+  if (!profile_arc_flag && !profile_condition_flag)
     return NULL_RTX;
 
   /* Otherwise call the wrapper.  This should be equivalent for the rest of
diff --git a/gcc/collect2.cc b/gcc/collect2.cc
index d81c7f28f16..7b0968d535d 100644
--- a/gcc/collect2.cc
+++ b/gcc/collect2.cc
@@ -1032,9 +1032,9 @@ main (int argc, char **argv)
       lto_mode = LTO_MODE_LTO;
   }
 
-  /* -fno-profile-arcs -fno-test-coverage -fno-branch-probabilities
+  /* -fno-profile-arcs -fno-profile-conditions -fno-test-coverage -fno-branch-probabilities
      -fno-exceptions -w -fno-whole-program */
-  num_c_args += 6;
+  num_c_args += 7;
 
   c_argv = XCNEWVEC (char *, num_c_args);
   c_ptr = CONST_CAST2 (const char **, char **, c_argv);
@@ -1230,6 +1230,7 @@ main (int argc, char **argv)
     }
   obstack_free (&temporary_obstack, temporary_firstobj);
   *c_ptr++ = "-fno-profile-arcs";
+  *c_ptr++ = "-fno-profile-conditions";
   *c_ptr++ = "-fno-test-coverage";
   *c_ptr++ = "-fno-branch-probabilities";
   *c_ptr++ = "-fno-exceptions";
diff --git a/gcc/common.opt b/gcc/common.opt
index e7a51e882ba..4da06767bc3 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -865,6 +865,10 @@ Wcoverage-invalid-line-number
 Common Var(warn_coverage_invalid_linenum) Init(1) Warning
 Warn in case a function ends earlier than it begins due to an invalid linenum macros.
 
+Wcoverage-too-many-conditions
+Common Var(warn_too_many_conditions) Init(1) Warning
+Warn when a conditional has too many terms and coverage gives up.
+
 Wmissing-profile
 Common Var(warn_missing_profile) Init(1) Warning
 Warn in case profiles in -fprofile-use do not exist.
@@ -2321,6 +2325,10 @@ fprofile-arcs
 Common Var(profile_arc_flag)
 Insert arc-based program profiling code.
 
+fprofile-conditions
+Common Var(profile_condition_flag)
+Insert condition coverage profiling code.
+
 fprofile-dir=
 Common Joined RejectNegative Var(profile_data_prefix)
 Set the top-level directory for storing the profile data.
diff --git a/gcc/doc/gcov.texi b/gcc/doc/gcov.texi
index a1f7d26e610..7f2c0d00d94 100644
--- a/gcc/doc/gcov.texi
+++ b/gcc/doc/gcov.texi
@@ -124,6 +124,7 @@ gcov [@option{-v}|@option{--version}] [@option{-h}|@option{--help}]
      [@option{-a}|@option{--all-blocks}]
      [@option{-b}|@option{--branch-probabilities}]
      [@option{-c}|@option{--branch-counts}]
+     [@option{-g}|@option{--conditions}]
      [@option{-d}|@option{--display-progress}]
      [@option{-f}|@option{--function-summaries}]
      [@option{-j}|@option{--json-format}]
@@ -169,6 +170,13 @@ be shown, unless the @option{-u} option is given.
 Write branch frequencies as the number of branches taken, rather than
 the percentage of branches taken.
 
+@item -g
+@itemx --conditions
+Write condition coverage to the output file, and write condition summary info
+to the standard output.  This option allows you to see if the conditions in
+your program at least once had an independent effect on the outcome of the
+boolean expression (modified condition/decision coverage).
+
 @item -d
 @itemx --display-progress
 Display the progress on the standard output.
@@ -293,6 +301,7 @@ Each @var{line} has the following form:
 @{
   "branches": ["$branch"],
   "count": 2,
+  "conditions": ["$condition"],
   "line_number": 15,
   "unexecuted_block": false,
   "function_name": "foo",
@@ -341,6 +350,33 @@ Fields of the @var{branch} element have following semantics:
 @var{throw}: true when the branch is an exceptional branch
 @end itemize
 
+Each @var{condition} element have the following semantics:
+
+@smallexample
+@{
+  "count": 4,
+  "covered": 2,
+  "not_covered_false": [],
+  "not_covered_true": [0, 1],
+@}
+@end smallexample
+
+Fields of the @var{condition} element have following semantics:
+
+@itemize @bullet
+@item
+@var{count}: number of conditions in this expression
+
+@item
+@var{covered}: number of covered conditions in this expression
+
+@item
+@var{not_covered_true}: terms, by index, not seen as true in this expression
+
+@item
+@var{not_covered_false}: terms, by index, not seen as false in this expression
+@end itemize
+
 @item -H
 @itemx --human-readable
 Write counts in human readable format (like 24.6k).
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index d5ff1018372..28cc9fa2316 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -602,6 +602,7 @@ Objective-C and Objective-C++ Dialects}.
 @item Program Instrumentation Options
 @xref{Instrumentation Options,,Program Instrumentation Options}.
 @gccoptlist{-p  -pg  -fprofile-arcs  --coverage  -ftest-coverage @gol
+-fprofile-conditions @gol
 -fprofile-abs-path @gol
 -fprofile-dir=@var{path}  -fprofile-generate  -fprofile-generate=@var{path} @gol
 -fprofile-info-section  -fprofile-info-section=@var{name} @gol
@@ -6088,6 +6089,13 @@ error.  @option{-Wno-coverage-invalid-line-number} can be used to disable the
 warning or @option{-Wno-error=coverage-invalid-line-number} can be used to
 disable the error.
 
+@item -Wno-coverage-too-many-conditions
+@opindex Wno-coverage-too-many-conditions
+@opindex Wcoverage-too-many-conditions
+Warn in case a condition have too many terms and GCC gives up coverage.
+Coverage is given up when there are more terms in the conditional than there
+are bits in a @code{gcov_type_unsigned}.  This warning is enabled by default.
+
 @item -Wno-cpp @r{(C, Objective-C, C++, Objective-C++ and Fortran only)}
 @opindex Wno-cpp
 @opindex Wcpp
@@ -15549,6 +15557,13 @@ E.g. @code{gcc a.c b.c -o binary} would generate @file{binary-a.gcda} and
 
 @xref{Cross-profiling}.
 
+@item -fprofile-conditions
+@opindex fprofile-conditions
+Add code so that program conditions are instrumented.  During execution the
+program records what terms in a conditional contributes to a decision.  The
+data may be used to verify that all terms in a booleans are tested and have an
+effect on the outcome of a condition.
+
 @cindex @command{gcov}
 @item --coverage
 @opindex coverage
@@ -15609,6 +15624,10 @@ executed.  When an arc is the only exit or only entrance to a block, the
 instrumentation code can be added to the block; otherwise, a new basic
 block must be created to hold the instrumentation code.
 
+With @option{-fprofile-conditions}, for each conditional in your program GCC
+creates a bitset and records the boolean values that have an independent effect
+on the outcome of that expression.
+
 @need 2000
 @item -ftest-coverage
 @opindex ftest-coverage
diff --git a/gcc/gcc.cc b/gcc/gcc.cc
index 5cbb38560b2..3c0c40bf388 100644
--- a/gcc/gcc.cc
+++ b/gcc/gcc.cc
@@ -1165,7 +1165,7 @@ proper position among the other output files.  */
 	%:include(libgomp.spec)%(link_gomp)}\
     %{fgnu-tm:%:include(libitm.spec)%(link_itm)}\
     %(mflib) " STACK_SPLIT_SPEC "\
-    %{fprofile-arcs|fprofile-generate*|coverage:-lgcov} " SANITIZER_SPEC " \
+    %{fprofile-arcs|fprofile-conditions|fprofile-generate*|coverage:-lgcov} " SANITIZER_SPEC " \
     %{!nostdlib:%{!r:%{!nodefaultlibs:%(link_ssp) %(link_gcc_c_sequence)}}}\
     %{!nostdlib:%{!r:%{!nostartfiles:%E}}} %{T*}  \n%(post_link) }}}}}}"
 #endif
@@ -1282,7 +1282,7 @@ static const char *cc1_options =
  %{!fsyntax-only:%{S:%W{o*}%{!o*:-o %w%b.s}}}\
  %{fsyntax-only:-o %j} %{-param*}\
  %{coverage:-fprofile-arcs -ftest-coverage}\
- %{fprofile-arcs|fprofile-generate*|coverage:\
+ %{fprofile-arcs|fprofile-conditions|fprofile-generate*|coverage:\
    %{!fprofile-update=single:\
      %{pthread:-fprofile-update=prefer-atomic}}}";
 
diff --git a/gcc/gcov-counter.def b/gcc/gcov-counter.def
index 6d2182bd3db..96563a59a45 100644
--- a/gcc/gcov-counter.def
+++ b/gcc/gcov-counter.def
@@ -49,3 +49,6 @@ DEF_GCOV_COUNTER(GCOV_COUNTER_IOR, "ior", _ior)
 
 /* Time profile collecting first run of a function */
 DEF_GCOV_COUNTER(GCOV_TIME_PROFILER, "time_profiler", _time_profile)
+
+/* Conditions.  The counter is interpreted as a bit-set.  */
+DEF_GCOV_COUNTER(GCOV_COUNTER_CONDS, "conditions", _ior)
diff --git a/gcc/gcov-dump.cc b/gcc/gcov-dump.cc
index 0804c794e9e..12830e5984e 100644
--- a/gcc/gcov-dump.cc
+++ b/gcc/gcov-dump.cc
@@ -35,6 +35,7 @@ static void print_version (void);
 static void tag_function (const char *, unsigned, int, unsigned);
 static void tag_blocks (const char *, unsigned, int, unsigned);
 static void tag_arcs (const char *, unsigned, int, unsigned);
+static void tag_conditions (const char *, unsigned, int, unsigned);
 static void tag_lines (const char *, unsigned, int, unsigned);
 static void tag_counters (const char *, unsigned, int, unsigned);
 static void tag_summary (const char *, unsigned, int, unsigned);
@@ -71,6 +72,7 @@ static const tag_format_t tag_table[] =
   {GCOV_TAG_FUNCTION, "FUNCTION", tag_function},
   {GCOV_TAG_BLOCKS, "BLOCKS", tag_blocks},
   {GCOV_TAG_ARCS, "ARCS", tag_arcs},
+  {GCOV_TAG_CONDS, "CONDITIONS", tag_conditions},
   {GCOV_TAG_LINES, "LINES", tag_lines},
   {GCOV_TAG_OBJECT_SUMMARY, "OBJECT_SUMMARY", tag_summary},
   {0, NULL, NULL}
@@ -381,6 +383,28 @@ tag_arcs (const char *filename ATTRIBUTE_UNUSED,
     }
 }
 
+static void
+tag_conditions (const char *filename ATTRIBUTE_UNUSED,
+		unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED,
+		unsigned depth)
+{
+  unsigned n_conditions = GCOV_TAG_CONDS_NUM (length);
+
+  printf (" %u conditionals", n_conditions);
+  if (flag_dump_contents)
+    {
+      for (unsigned ix = 0; ix != n_conditions; ix++)
+	{
+	  const unsigned blockno = gcov_read_unsigned ();
+	  const unsigned nterms = gcov_read_unsigned ();
+
+	  printf ("\n");
+	  print_prefix (filename, depth, gcov_position ());
+	  printf (VALUE_PADDING_PREFIX "block %u:", blockno);
+	  printf (" %u", nterms);
+	}
+    }
+}
 static void
 tag_lines (const char *filename ATTRIBUTE_UNUSED,
 	   unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED,
diff --git a/gcc/gcov-io.h b/gcc/gcov-io.h
index 30947634d73..eaf3f366f5d 100644
--- a/gcc/gcov-io.h
+++ b/gcc/gcov-io.h
@@ -261,6 +261,9 @@ typedef uint64_t gcov_type_unsigned;
 #define GCOV_TAG_ARCS		 ((gcov_unsigned_t)0x01430000)
 #define GCOV_TAG_ARCS_LENGTH(NUM)  (1 + (NUM) * 2 * GCOV_WORD_SIZE)
 #define GCOV_TAG_ARCS_NUM(LENGTH)  (((LENGTH / GCOV_WORD_SIZE) - 1) / 2)
+#define GCOV_TAG_CONDS             ((gcov_unsigned_t)0x01470000)
+#define GCOV_TAG_CONDS_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE)
+#define GCOV_TAG_CONDS_NUM(LENGTH) (((LENGTH) / GCOV_WORD_SIZE) / 2)
 #define GCOV_TAG_LINES		 ((gcov_unsigned_t)0x01450000)
 #define GCOV_TAG_COUNTER_BASE 	 ((gcov_unsigned_t)0x01a10000)
 #define GCOV_TAG_COUNTER_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE)
diff --git a/gcc/gcov.cc b/gcc/gcov.cc
index 27be5ff0911..9fa26db814c 100644
--- a/gcc/gcov.cc
+++ b/gcc/gcov.cc
@@ -79,6 +79,7 @@ using namespace std;
 class function_info;
 class block_info;
 class source_info;
+class condition_info;
 
 /* Describes an arc between two basic blocks.  */
 
@@ -132,6 +133,28 @@ public:
   vector<unsigned> lines;
 };
 
+class condition_info
+{
+public:
+  condition_info ();
+
+  int popcount () const;
+
+  gcov_type_unsigned truev;
+  gcov_type_unsigned falsev;
+
+  unsigned n_terms;
+};
+
+condition_info::condition_info (): truev (0), falsev (0), n_terms (0)
+{
+}
+
+int condition_info::popcount () const
+{
+    return __builtin_popcountll (truev) + __builtin_popcountll (falsev);
+}
+
 /* Describes a basic block. Contains lists of arcs to successor and
    predecessor blocks.  */
 
@@ -165,6 +188,8 @@ public:
   /* Block is a landing pad for longjmp or throw.  */
   unsigned is_nonlocal_return : 1;
 
+  condition_info conditions;
+
   vector<block_location_info> locations;
 
   struct
@@ -275,6 +300,8 @@ public:
   vector<block_info> blocks;
   unsigned blocks_executed;
 
+  vector<condition_info*> conditions;
+
   /* Raw arc coverage counts.  */
   vector<gcov_type> counts;
 
@@ -351,6 +378,9 @@ struct coverage_info
   int branches_executed;
   int branches_taken;
 
+  int conditions;
+  int conditions_covered;
+
   int calls;
   int calls_executed;
 
@@ -550,6 +580,10 @@ static int multiple_files = 0;
 
 static int flag_branches = 0;
 
+/* Output conditions (modified condition/decision coverage) */
+
+static int flag_conditions = 0;
+
 /* Show unconditional branches too.  */
 static int flag_unconditional = 0;
 
@@ -656,6 +690,7 @@ static int read_count_file (void);
 static void solve_flow_graph (function_info *);
 static void find_exception_blocks (function_info *);
 static void add_branch_counts (coverage_info *, const arc_info *);
+static void add_condition_counts (coverage_info *, const block_info *);
 static void add_line_counts (coverage_info *, function_info *);
 static void executed_summary (unsigned, unsigned);
 static void function_summary (const coverage_info *);
@@ -664,6 +699,7 @@ static const char *format_gcov (gcov_type, gcov_type, int);
 static void accumulate_line_counts (source_info *);
 static void output_gcov_file (const char *, source_info *);
 static int output_branch_count (FILE *, int, const arc_info *);
+static void output_conditions (FILE *, const block_info *);
 static void output_lines (FILE *, const source_info *);
 static string make_gcov_file_name (const char *, const char *);
 static char *mangle_name (const char *);
@@ -928,6 +964,7 @@ print_usage (int error_p)
   fnotice (file, "  -b, --branch-probabilities      Include branch probabilities in output\n");
   fnotice (file, "  -c, --branch-counts             Output counts of branches taken\n\
                                     rather than percentages\n");
+  fnotice (file, "  -g, --conditions                Include condition/decision coverage in output\n");
   fnotice (file, "  -d, --display-progress          Display progress information\n");
   fnotice (file, "  -D, --debug			    Display debugging dumps\n");
   fnotice (file, "  -f, --function-summaries        Output summaries for each function\n");
@@ -980,6 +1017,7 @@ static const struct option options[] =
   { "all-blocks",           no_argument,       NULL, 'a' },
   { "branch-probabilities", no_argument,       NULL, 'b' },
   { "branch-counts",        no_argument,       NULL, 'c' },
+  { "conditions",           no_argument,       NULL, 'g' },
   { "json-format",	    no_argument,       NULL, 'j' },
   { "human-readable",	    no_argument,       NULL, 'H' },
   { "no-output",            no_argument,       NULL, 'n' },
@@ -1008,7 +1046,7 @@ process_args (int argc, char **argv)
 {
   int opt;
 
-  const char *opts = "abcdDfhHijklmno:pqrs:tuvwx";
+  const char *opts = "abcdDfghHijklmno:pqrs:tuvwx";
   while ((opt = getopt_long (argc, argv, opts, options, NULL)) != -1)
     {
       switch (opt)
@@ -1025,6 +1063,9 @@ process_args (int argc, char **argv)
 	case 'f':
 	  flag_function_summary = 1;
 	  break;
+	case 'g':
+	  flag_conditions = 1;
+	  break;
 	case 'h':
 	  print_usage (false);
 	  /* print_usage will exit.  */
@@ -1132,6 +1173,45 @@ output_intermediate_json_line (json::array *object,
 	  }
       }
 
+  json::array *conditions = new json::array ();
+  lineo->set ("conditions", conditions);
+  if (flag_conditions)
+  {
+    vector<block_info *>::const_iterator it;
+    for (it = line->blocks.begin (); it != line->blocks.end (); it++)
+      {
+	const condition_info& info = (*it)->conditions;
+	if (info.n_terms == 0)
+	    continue;
+
+	const int count = 2 * info.n_terms;
+	const int covered = info.popcount ();
+
+	json::object *cond = new json::object ();
+	cond->set ("count", new json::integer_number (count));
+	cond->set ("covered", new json::integer_number (covered));
+
+	json::array *mtrue = new json::array ();
+	json::array *mfalse = new json::array ();
+	cond->set ("not_covered_true", mtrue);
+	cond->set ("not_covered_false", mfalse);
+
+	if (count != covered)
+	  {
+	    for (unsigned i = 0; i < info.n_terms; i++)
+	      {
+		gcov_type_unsigned index = 1;
+		index <<= i;
+		if (!(index & info.truev))
+		    mtrue->append (new json::integer_number (i));
+		if (!(index & info.falsev))
+		    mfalse->append (new json::integer_number (i));
+	      }
+	  }
+	conditions->append (cond);
+      }
+  }
+
   object->append (lineo);
 }
 
@@ -1956,6 +2036,28 @@ read_graph_file (void)
 		  }
 	    }
 	}
+      else if (fn && tag == GCOV_TAG_CONDS)
+	{
+	  unsigned num_dests = GCOV_TAG_CONDS_NUM (length);
+
+	  if (!fn->conditions.empty ())
+	    fnotice (stderr, "%s:already seen conditions for '%s'\n",
+		     bbg_file_name, fn->get_name ());
+	  else
+	    fn->conditions.resize (num_dests);
+
+	  for (unsigned i = 0; i < num_dests; ++i)
+	    {
+	      unsigned idx = gcov_read_unsigned ();
+
+	      if (idx >= fn->blocks.size ())
+		goto corrupt;
+
+	      condition_info *info = &fn->blocks[idx].conditions;
+	      info->n_terms = gcov_read_unsigned ();
+	      fn->conditions[i] = info;
+	    }
+	}
       else if (fn && tag == GCOV_TAG_LINES)
 	{
 	  unsigned blockno = gcov_read_unsigned ();
@@ -2086,11 +2188,26 @@ read_count_file (void)
 	      goto cleanup;
 	    }
 	}
-      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn)
+      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_CONDS) && fn)
 	{
+	  length = abs (read_length);
+	    if (length != GCOV_TAG_COUNTER_LENGTH (2 * fn->conditions.size ()))
+		goto mismatch;
+
+	    if (read_length > 0)
+	      {
+		for (ix = 0; ix != fn->conditions.size (); ix++)
+		{
+		    fn->conditions[ix]->truev  |= gcov_read_counter ();
+		    fn->conditions[ix]->falsev |= gcov_read_counter ();
+		}
+	      }
+	}
+      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn)
+      {
 	  length = abs (read_length);
 	  if (length != GCOV_TAG_COUNTER_LENGTH (fn->counts.size ()))
-	    goto mismatch;
+	      goto mismatch;
 
 	  if (read_length > 0)
 	    for (ix = 0; ix != fn->counts.size (); ix++)
@@ -2430,6 +2547,13 @@ add_branch_counts (coverage_info *coverage, const arc_info *arc)
     }
 }
 
+static void
+add_condition_counts (coverage_info *coverage, const block_info *block)
+{
+  coverage->conditions += 2 * block->conditions.n_terms;
+  coverage->conditions_covered += block->conditions.popcount ();
+}
+
 /* Format COUNT, if flag_human_readable_numbers is set, return it human
    readable format.  */
 
@@ -2533,6 +2657,18 @@ file_summary (const coverage_info *coverage)
 		 coverage->calls);
       else
 	fnotice (stdout, "No calls\n");
+
+    }
+
+  if (flag_conditions)
+    {
+      if (coverage->conditions)
+	fnotice (stdout, "Decisions covered:%s of %d\n",
+		 format_gcov (coverage->conditions_covered,
+			      coverage->conditions, 2),
+		 coverage->conditions);
+      else
+	fnotice (stdout, "No decisions\n");
     }
 }
 
@@ -2760,6 +2896,12 @@ static void accumulate_line_info (line_info *line, source_info *src,
 	 it != line->branches.end (); it++)
       add_branch_counts (&src->coverage, *it);
 
+  if (add_coverage)
+    for (vector<block_info *>::iterator it = line->blocks.begin ();
+	 it != line->blocks.end (); it++)
+      add_condition_counts (&src->coverage, *it);
+
+
   if (!line->blocks.empty ())
     {
       /* The user expects the line count to be the number of times
@@ -2861,6 +3003,33 @@ accumulate_line_counts (source_info *src)
       }
 }
 
+static void
+output_conditions (FILE *gcov_file, const block_info *binfo)
+{
+    const condition_info& info = binfo->conditions;
+    if (info.n_terms == 0)
+	return;
+
+    const int expected = 2 * info.n_terms;
+    const int got = info.popcount ();
+
+    fnotice (gcov_file, "decisions covered %d/%d\n", got, expected);
+    if (expected == got)
+	return;
+
+    for (unsigned i = 0; i < info.n_terms; i++)
+    {
+	gcov_type_unsigned index = 1;
+	index <<= i;
+	if ((index & info.truev & info.falsev))
+	    continue;
+
+	const char *t = (index & info.truev) ? "" : "true";
+	const char *f = (index & info.falsev) ? "" : " false";
+	fnotice (gcov_file, "condition %2u not covered (%s%s)\n", i, t, f + !t[0]);
+    }
+}
+
 /* Output information about ARC number IX.  Returns nonzero if
    anything is output.  */
 
@@ -3071,16 +3240,29 @@ output_line_details (FILE *f, const line_info *line, unsigned line_num)
 	  if (flag_branches)
 	    for (arc = (*it)->succ; arc; arc = arc->succ_next)
 	      jx += output_branch_count (f, jx, arc);
+
+	  if (flag_conditions)
+	      output_conditions (f, *it);
 	}
     }
-  else if (flag_branches)
+  else
     {
-      int ix;
+      if (flag_branches)
+	{
+	  int ix;
+
+	  ix = 0;
+	  for (vector<arc_info *>::const_iterator it = line->branches.begin ();
+		  it != line->branches.end (); it++)
+	      ix += output_branch_count (f, ix, (*it));
+	}
 
-      ix = 0;
-      for (vector<arc_info *>::const_iterator it = line->branches.begin ();
-	   it != line->branches.end (); it++)
-	ix += output_branch_count (f, ix, (*it));
+      if (flag_conditions)
+	{
+	  for (vector<block_info *>::const_iterator it = line->blocks.begin ();
+	       it != line->blocks.end (); it++)
+	      output_conditions (f, *it);
+	}
     }
 }
 
diff --git a/gcc/ipa-inline.cc b/gcc/ipa-inline.cc
index 14969198cde..3e37305843e 100644
--- a/gcc/ipa-inline.cc
+++ b/gcc/ipa-inline.cc
@@ -646,7 +646,7 @@ can_early_inline_edge_p (struct cgraph_edge *e)
 			 "  edge not inlinable: not in SSA form\n");
       return false;
     }
-  else if (profile_arc_flag
+  else if ((profile_arc_flag || profile_condition_flag)
 	   && ((lookup_attribute ("no_profile_instrument_function",
 				 DECL_ATTRIBUTES (caller->decl)) == NULL_TREE)
 	       != (lookup_attribute ("no_profile_instrument_function",
diff --git a/gcc/ipa-split.cc b/gcc/ipa-split.cc
index 16734617d03..07d2b17ab12 100644
--- a/gcc/ipa-split.cc
+++ b/gcc/ipa-split.cc
@@ -1929,7 +1929,8 @@ pass_split_functions::gate (function *)
   /* When doing profile feedback, we want to execute the pass after profiling
      is read.  So disable one in early optimization.  */
   return (flag_partial_inlining
-	  && !profile_arc_flag && !flag_branch_probabilities);
+      && !profile_arc_flag && !flag_branch_probabilities
+      && !profile_condition_flag);
 }
 
 } // anon namespace
diff --git a/gcc/passes.cc b/gcc/passes.cc
index 78a07f8691a..3457ad1c5b3 100644
--- a/gcc/passes.cc
+++ b/gcc/passes.cc
@@ -352,7 +352,8 @@ finish_optimization_passes (void)
   gcc::dump_manager *dumps = m_ctxt->get_dumps ();
 
   timevar_push (TV_DUMP);
-  if (profile_arc_flag || flag_test_coverage || flag_branch_probabilities)
+  if (profile_arc_flag || profile_condition_flag || flag_test_coverage
+      || flag_branch_probabilities)
     {
       dumps->dump_start (pass_profile_1->static_pass_number, NULL);
       end_branch_prob ();
diff --git a/gcc/profile.cc b/gcc/profile.cc
index 08af512cbca..a4751279571 100644
--- a/gcc/profile.cc
+++ b/gcc/profile.cc
@@ -66,9 +66,19 @@ along with GCC; see the file COPYING3.  If not see
 #include "cfgloop.h"
 #include "sreal.h"
 #include "file-prefix-map.h"
+#include "stringpool.h"
 
 #include "profile.h"
 
+struct condcov;
+struct condcov *find_conditions (struct function*);
+unsigned cov_length (const struct condcov*);
+array_slice<basic_block> cov_blocks (struct condcov*, unsigned);
+array_slice<gcov_type_unsigned > cov_masks (struct condcov*, unsigned);
+void cov_free (struct condcov*);
+int instrument_decisions (array_slice<basic_block>, unsigned, tree*,
+			  gcov_type_unsigned*);
+
 /* Map from BBs/edges to gcov counters.  */
 vec<gcov_type> bb_gcov_counts;
 hash_map<edge,gcov_type> *edge_gcov_counts;
@@ -100,6 +110,7 @@ static int total_num_passes;
 static int total_num_times_called;
 static int total_hist_br_prob[20];
 static int total_num_branches;
+static int total_num_conds;
 
 /* Forward declarations.  */
 static void find_spanning_tree (struct edge_list *);
@@ -1154,6 +1165,12 @@ read_thunk_profile (struct cgraph_node *node)
    the flow graph that are needed to reconstruct the dynamic behavior of the
    flow graph.  This data is written to the gcno file for gcov.
 
+   When FLAG_PROFILE_CONDITIONS is nonzero, this functions instruments the
+   edges in the control flow graph to track what conditions are evaluated to in
+   order to determine what conditions are covered and have an independent
+   effect on the outcome (modified condition/decision coverage).  This data is
+   written to the gcno file for gcov.
+
    When FLAG_BRANCH_PROBABILITIES is nonzero, this function reads auxiliary
    information from the gcda file containing edge count information from
    previous executions of the function being compiled.  In this case, the
@@ -1172,6 +1189,7 @@ branch_prob (bool thunk)
   struct edge_list *el;
   histogram_values values = histogram_values ();
   unsigned cfg_checksum, lineno_checksum;
+  bool output_to_file;
 
   total_num_times_called++;
 
@@ -1396,10 +1414,18 @@ branch_prob (bool thunk)
 
   /* Write the data from which gcov can reconstruct the basic block
      graph and function line numbers (the gcno file).  */
+  output_to_file = false;
   if (coverage_begin_function (lineno_checksum, cfg_checksum))
     {
       gcov_position_t offset;
 
+      /* The condition coverage needs a deeper analysis to identify expressions
+       * of conditions, which means it is not yet ready to write to the gcno
+       * file.  It will write its entries later, but needs to know if it do it
+       * in the first place, which is controlled by the return value of
+       * coverage_begin_function. */
+      output_to_file = true;
+
       /* Basic block flags */
       offset = gcov_write_tag (GCOV_TAG_BLOCKS);
       gcov_write_unsigned (n_basic_blocks_for_fn (cfun));
@@ -1511,29 +1537,74 @@ branch_prob (bool thunk)
 
   remove_fake_edges ();
 
+  if (profile_condition_flag || profile_arc_flag)
+      gimple_init_gcov_profiler ();
+
+  if (profile_condition_flag)
+    {
+      struct condcov *cov = find_conditions (cfun);
+      gcc_assert (cov);
+      const unsigned nconds = cov_length (cov);
+      total_num_conds += nconds;
+
+      if (coverage_counter_alloc (GCOV_COUNTER_CONDS, 2 * nconds))
+	{
+	  /* Add two extra variables to the function for the local
+	     accumulators, which are zero'd on the entry of a new conditional.
+	     The local accumulators are shared between decisions in order to
+	     use less stack space. */
+	  tree accu[2] = {
+	    build_decl (UNKNOWN_LOCATION, VAR_DECL,
+			get_identifier ("__accu_t"), get_gcov_type ()),
+	    build_decl (UNKNOWN_LOCATION, VAR_DECL,
+			get_identifier ("__accu_f"), get_gcov_type ()),
+	  };
+
+	  gcov_position_t offset {};
+	  if (output_to_file)
+	      offset = gcov_write_tag (GCOV_TAG_CONDS);
+
+	  for (unsigned i = 0; i < nconds; ++i)
+	    {
+	      array_slice<basic_block> expr = cov_blocks (cov, i);
+	      array_slice<gcov_type_unsigned> masks = cov_masks (cov, i);
+	      gcc_assert (expr.is_valid ());
+	      gcc_assert (masks.is_valid ());
+
+	      int terms = instrument_decisions (expr, i, accu, masks.begin ());
+	      if (output_to_file)
+		{
+		  gcov_write_unsigned (expr.front ()->index);
+		  gcov_write_unsigned (terms);
+		}
+	    }
+	  if (output_to_file)
+	      gcov_write_length (offset);
+	}
+      cov_free (cov);
+    }
+
   /* For each edge not on the spanning tree, add counting code.  */
   if (profile_arc_flag
       && coverage_counter_alloc (GCOV_COUNTER_ARCS, num_instrumented))
     {
       unsigned n_instrumented;
 
-      gimple_init_gcov_profiler ();
-
       n_instrumented = instrument_edges (el);
 
       gcc_assert (n_instrumented == num_instrumented);
 
       if (flag_profile_values)
 	instrument_values (values);
-
-      /* Commit changes done by instrumentation.  */
-      gsi_commit_edge_inserts ();
     }
 
   free_aux_for_edges ();
 
   values.release ();
   free_edge_list (el);
+  /* Commit changes done by instrumentation.  */
+  gsi_commit_edge_inserts ();
+
   coverage_end_function (lineno_checksum, cfg_checksum);
   if (flag_branch_probabilities
       && (profile_status_for_fn (cfun) == PROFILE_READ))
@@ -1663,6 +1734,7 @@ init_branch_prob (void)
   total_num_passes = 0;
   total_num_times_called = 0;
   total_num_branches = 0;
+  total_num_conds = 0;
   for (i = 0; i < 20; i++)
     total_hist_br_prob[i] = 0;
 }
@@ -1702,5 +1774,7 @@ end_branch_prob (void)
 		     (total_hist_br_prob[i] + total_hist_br_prob[19-i]) * 100
 		     / total_num_branches, 5*i, 5*i+5);
 	}
+      fprintf (dump_file, "Total number of conditions: %d\n",
+	       total_num_conds);
     }
 }
diff --git a/gcc/testsuite/g++.dg/gcov/gcov-18.C b/gcc/testsuite/g++.dg/gcov/gcov-18.C
new file mode 100644
index 00000000000..310ed5297c0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gcov/gcov-18.C
@@ -0,0 +1,234 @@
+/* { dg-options "--coverage -fprofile-conditions -std=c++11" } */
+/* { dg-do run { target native } } */
+
+#include <vector>
+#include <stdexcept>
+
+class nontrivial_destructor
+{
+public:
+    explicit nontrivial_destructor (int v) : val (v) {}
+    ~nontrivial_destructor () {}
+
+    explicit operator bool() const { return bool(val); }
+
+    int val;
+};
+
+int identity (int x) { return x; }
+int throws (int) { throw std::runtime_error("exception"); }
+
+int throw_if (int x)
+{
+    if (x) /* conditions(1/2) true(0) */
+	   /* conditions(end) */
+	throw std::runtime_error("exception");
+    return x;
+}
+
+/* used for side effects to insert nodes in conditional bodies etc. */
+int x = 0;
+
+/* conditionals work in the presence of non-trivial destructors */
+void mcdc001a (int a)
+{
+    nontrivial_destructor v (a);
+
+    if (v.val > 0) /* conditions(2/2) */
+	x = v.val;
+    else
+	x = -v.val;
+}
+
+/* non-trivial destructor in-loop temporary */
+nontrivial_destructor
+mcdc002a (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+	nontrivial_destructor tmp (a);
+	if (tmp.val % b) /* conditions(2/2) */
+	    return nontrivial_destructor (0);
+	x += i;
+    } /* conditions(suppress) */
+      /* conditions(end) */
+
+    return nontrivial_destructor (a * b);
+}
+
+/* conditional in constructor */
+void mcdc003a (int a)
+{
+    class C
+    {
+    public:
+	explicit C (int e) : v (e)
+	{
+	    if (e) /* conditions(1/2) false(0) */
+		v = x - e;
+	}
+	int v;
+    };
+
+    C c (a);
+    if (c.v > 2) /* conditions(1/2) true(0) */
+		 /* conditions(end) */
+	x = c.v + a;
+}
+
+/* conditional in destructor */
+void mcdc004a (int a)
+{
+    class C
+    {
+    public:
+	explicit C (int e) : v (e) {}
+	~C ()
+	{
+	    if (v) /* conditions(2/2) */
+		x = 2 * v;
+	}
+	int v;
+    };
+
+    C c (a);
+    x = 1; // arbitrary action between ctor+dtor
+}
+
+/* conditional in try */
+void mcdc005a (int a)
+{
+    try
+    {
+	if (a) /* conditions(1/2) true(0) */
+	       /* conditions(end) */
+	    x = 2 * identity (a);
+	else
+	    x = 1;
+    }
+    catch (...)
+    {
+	x = 0;
+    }
+}
+
+/* conditional in catch */
+void mcdc006a (int a) {
+    try
+    {
+	throws (a);
+    }
+    catch (std::exception&)
+    {
+	if (a) /* conditions(1/2) false(0) */
+	       /* conditions(end) */
+	    x = identity (a);
+	else
+	    x = 0;
+    }
+}
+
+void mcdc006b (int a)
+{
+    if (a) /* conditions(1/2) true(0) */
+	   /* conditions(end) */
+	throws (a);
+    else
+	x = 1;
+}
+
+void mcdc006c (int a) try
+{
+    throws (a);
+}
+catch (...) {
+    if (a) /* conditions(2/2) */
+	x = 5;
+}
+
+/* temporary with destructor as term */
+void mcdc007a (int a, int b)
+{
+    x = a && nontrivial_destructor (b); /* conditions(3/4) false(1) destructor() */
+}
+
+void mcdc007b (int a, int b)
+{
+    if (a || throw_if (b)) /* conditions(3/4) true(1) destructor() */
+	x = -1;
+    else
+	x = 1;
+}
+
+void mcdc007c (int a, int b)
+{
+    if (throw_if (a) || throw_if (b)) /* conditions(2/4) true(0 1) destructor() */
+	x = -1;
+    else
+	x = 1;
+}
+
+/* destructor with delete */
+void mcdc008a (int a)
+{
+    class C
+    {
+    public:
+        int size = 5;
+        int* ptr = nullptr;
+
+        explicit C (int v) : size (v + 5), ptr (new int[size]) /* conditions(suppress) */
+							       /* conditions(end) */
+        {
+	    for (int i = 0; i < size; i++) /* conditions(2/2) */
+		ptr[i] = i + 1;
+        }
+        ~C()
+        {
+	    // delete with implicit nullptr check
+	    delete ptr; /* conditions(1/2) false(0) */
+			/* conditions(end) */
+	}
+    };
+
+    C c (a);
+    if (c.ptr[a + 1]) /* conditions(1/2) false(0) */
+	x = a;
+}
+
+int
+main (void)
+{
+    mcdc001a (0);
+    mcdc001a (1);
+
+    mcdc002a (1, 1);
+    mcdc002a (1, 2);
+
+    mcdc003a (1);
+
+    mcdc004a (0);
+    mcdc004a (1);
+
+    mcdc005a (0);
+
+    mcdc006a (1);
+
+    mcdc006b (0);
+
+    mcdc006c (0);
+    mcdc006c (1);
+
+    mcdc007a (0, 0);
+    mcdc007a (1, 1);
+
+    mcdc007b (0, 0);
+    mcdc007b (1, 0);
+
+    mcdc007c (0, 0);
+
+    mcdc008a (1);
+
+}
+
+/* { dg-final { run-gcov conditions { --conditions gcov-18.C } } } */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-19.c b/gcc/testsuite/gcc.misc-tests/gcov-19.c
new file mode 100644
index 00000000000..fb3be7dac76
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-19.c
@@ -0,0 +1,1137 @@
+/* { dg-options "-fprofile-conditions -ftest-coverage" } */
+/* { dg-do run { target native } } */
+
+/* some side effect to stop branches from being pruned */
+int x = 0;
+
+/* || works */
+void
+mcdc001a (int a, int b)
+{
+    if (a || b) /* conditions(1/4) true(0) false(0 1) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc001b (int a, int b)
+{
+    if (a || b) /* conditions(3/4) true(0) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc001c (int a, int b)
+{
+    if (a || b) /* conditions(4/4) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc001d (int a, int b, int c)
+{
+    if (a || b || c) /* conditions(2/6) false(0 1 2) true(2) */
+		     /* conditions(end) */
+	x = 1;
+}
+
+/* && works */
+void
+mcdc002a (int a, int b)
+{
+    if (a && b) /* conditions(1/4) true(0 1) false(0) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc002b (int a, int b)
+{
+    if (a && b) /* conditions(3/4) false(0) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc002c (int a, int b)
+{
+    if (a && b) /* conditions(4/4) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc002d (int a, int b, int c)
+{
+    if (a && b && c) /* conditions(4/6) false(0 2) */
+		     /* conditions(end) */
+	x = 1;
+}
+
+/* negation works */
+void
+mcdc003a (int a, int b)
+{
+    if (!a || !b) /* conditions(2/4) false(0 1) */
+		  /* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+/* single conditionals with and without else */
+void
+mcdc004a (int a)
+{
+    if (a) /* conditions(1/2) true(0) */
+	   /* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc004b (int a)
+{
+    if (a) /* conditions(2/2) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc004c (int a)
+{
+    if (a) /* conditions(1/2) false(0) */
+	   /* conditions(end) */
+	x = 1;
+}
+
+void
+mcdc004d (int a, int b, int c)
+{
+    /* With no else this is interpreted as (a && (b || c)) */
+    if (a)  /* conditions(3/6) true(2) false(1 2)*/
+    {
+	if (b || c)
+	    x = a + b + c;
+    }
+}
+
+void
+mcdc004e (int a, int b, int c)
+{
+    /* With the else, this is interpreted as 2 expressions */
+    if (a)  /* conditions(2/2) */
+    {
+	if (b || c) /* conditions(1/4) true(1) false(0 1) */
+	    x = a + b + c;
+    }
+    else
+    {
+	x = c;
+    }
+}
+
+/* mixing && and || works */
+void
+mcdc005a (int a, int b, int c)
+{
+    if ((a && b) || c) /* conditions(1/6) true(0 1) false(0 1 2) */
+		       /* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc005b (int a, int b, int c, int d)
+{
+    /* This is where masking MC/DC gets unintuitive:
+
+       1 1 0 0 => covers 1 (d = 0) as && 0 masks everything to the left
+       1 0 0 0 => covers 2 (b = 0, c = 0) as (a && 0) masks a and d is never
+       evaluated. */
+    if ((a && (b || c)) && d) /* conditions(3/8) true(0 1 2 3) false(0) */
+			      /* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc005c (int a, int b, int c, int d)
+{
+    if (a || (b && c) || d) /* conditions(2/8) true(0 3) false(0 1 2 3) */
+			    /* conditions(end) */
+        x = a + b + c + d;
+}
+
+void
+mcdc005d (int a, int b, int c, int d)
+{
+    /* This test is quite significant - it has a single input
+       (1, 0, 0, 0) and tests specifically for when a multi-term left operand
+       is masked. d = 0 should mask a || b and for the input there are no other
+       sources for masking a (since b = 0). */
+    if ((a || b) && (c || d)) /* conditions(2/8) true(0 1 2 3) false(0 1) */
+			      /* conditions(end) */
+	x = a + b;
+    else
+	x = c + d;
+}
+
+/* nested conditionals */
+void
+mcdc006a (int a, int b, int c, int d, int e)
+{
+    if (a) /* conditions(2/2) */
+    {
+	if (b && c) /* conditions(3/4) false(1) */
+		    /* conditions(end) */
+	    x = 1;
+	else
+	    x = 2;
+    }
+    else
+    {
+	if (c || d) /* conditions(2/4) true(0 1) */
+		    /* conditions(end) */
+	    x = 3;
+	else
+	    x = 4;
+    }
+}
+
+void
+mcdc006b (int a, int b, int c)
+{
+    if (a) /* conditions(6/6) */
+	if (b)
+	    if (c)
+		x = a + b + c;
+}
+
+void
+mcdc006c (int a, int b, int c)
+{
+    if (a) /* conditions(2/2) */
+    {
+	if (b) /*conditions(2/2) */
+	{
+	    if (c) /* conditions(2/2) */
+	    {
+		x = a + b + c;
+	    }
+	}
+	else
+	{
+	    x = b;
+	}
+    }
+    else
+    {
+	x = a;
+    }
+}
+
+/* else/if */
+void
+mcdc007a (int a, int b, int c, int d)
+{
+    if (a) /* conditions(2/2) */
+    {
+	if (b) /* conditions(1/2) true(0) */
+	       /* conditions(end) */
+	    x = 1;
+	else
+	    x = 2;
+    }
+    else if (c) /* conditions(2/2) */
+    {
+	if (d) /* conditions(1/2) true(0) */
+	       /* conditions(end) */
+	    x = 3;
+	else
+	    x = 4;
+    }
+}
+
+void
+mcdc007b (int a, int b, int c)
+{
+    goto begin;
+then:
+    x = 1;
+    return;
+begin:
+    /* similar to if (a || b || c) x = 1 */
+    if (a) /* conditions(2/2) */
+	goto then;
+    else if (b) /* conditions(2/2) */
+	goto then;
+    else if (c) /* conditions(1/2) true(0) */
+		/* conditions(end) */
+	goto then;
+}
+
+/* while loop */
+void
+mcdc008a (int a)
+{
+    while (a < 10) /* conditions(2/2) */
+	x = a++;
+}
+
+void
+mcdc008b (int a)
+{
+    while (a > 10) /* conditions(1/2) true(0) */
+		   /* conditions(end) */
+	x = a--;
+}
+
+void
+mcdc008c (int a)
+{
+    // should work, even with no body
+    while (a) /* conditions(2/2) */
+	break;
+}
+
+void
+mcdc008d (int a, int b, int c, int d)
+{
+    /* multi-term loop conditional */
+    while ((a && (b || c)) && d) /* conditions(8/8) */
+	a = b = c = d = 0;
+}
+
+void
+mcdc009a (int a, int b)
+{
+    while (a > 0 && b > 0) /* conditions(3/4) false(1) */
+			   /* conditions(end) */
+	x = a--;
+}
+
+/* for loop */
+void
+mcdc010a(int a, int b)
+{
+    for (int i = 0; i < b; i++) /* conditions(2/2) */
+    {
+	if (a < b) /* conditions(2/2) */
+	    x = 1;
+	else
+	    x = a += 2;
+    }
+}
+
+void
+mcdc010b ()
+{
+    for (int a = 0; a <= 1; ++a) /* conditions(2/2) */
+    {
+	x = a;
+    }
+}
+
+int always (int x) { (void) x; return 1; }
+
+/* no-condition infinite loops */
+void
+mcdc010c (int a)
+{
+    for (;;)
+    {
+	if (always(a)) /* conditions(1/2) false(0) */
+		       /* conditions(end) */
+	{
+	    x = a;
+	    break;
+	}
+	x += a + 1;
+    }
+}
+
+/* conditionals without control flow constructs work */
+void
+mcdc011a (int a, int b, int c)
+{
+    x = (a && b) || c; /* conditions(5/6) false(1) */
+		       /* conditions(end) */
+}
+
+/* sequential expressions are handled independently */
+void
+mcdc012a (int a, int b, int c)
+{
+    if (a || b) /* conditions(3/4) true(0) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+
+    if (c) /* conditions(2/2) */
+	x = 1;
+}
+
+/*
+ * cannot ever satisfy MC/DC, even with all input combinations, because not all
+ * variables independently affect the decision
+ */
+void
+mcdc013a (int a, int b, int c)
+{
+    (void)b;
+    /*
+     * Specification: (a && b) || c
+     *
+     * But the expression was implemented wrong. This has branch coverage, but
+     * not MC/DC
+     */
+    if ((a && !c) || c) /* conditions(5/6) false(1) */
+	/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc014a ()
+{
+    int conds[64] = { 0 };
+    /* conditions(64/128) true(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63) */
+    x = conds[ 0] || conds[ 1] || conds[ 2] || conds[ 3] || conds[ 4] ||
+	conds[ 5] || conds[ 6] || conds[ 7] || conds[ 8] || conds[ 9] ||
+	conds[10] || conds[11] || conds[12] || conds[13] || conds[14] ||
+	conds[15] || conds[16] || conds[17] || conds[18] || conds[19] ||
+	conds[20] || conds[21] || conds[22] || conds[23] || conds[24] ||
+	conds[25] || conds[26] || conds[27] || conds[28] || conds[29] ||
+	conds[30] || conds[31] || conds[32] || conds[33] || conds[34] ||
+	conds[35] || conds[36] || conds[37] || conds[38] || conds[39] ||
+	conds[40] || conds[41] || conds[42] || conds[43] || conds[44] ||
+	conds[45] || conds[46] || conds[47] || conds[48] || conds[49] ||
+	conds[50] || conds[51] || conds[52] || conds[53] || conds[54] ||
+	conds[55] || conds[56] || conds[57] || conds[58] || conds[59] ||
+	conds[60] || conds[61] || conds[62] || conds[63]
+	;  /* conditions(end) */
+}
+
+/* early returns */
+void
+mcdc015a (int a, int b)
+{
+    if (a) /* conditions(2/2) */
+	return;
+
+    if (b) /* conditions(1/2) true(0) */
+	   /* conditions(end) */
+	x = 1;
+}
+
+void
+mcdc015b (int a, int b)
+{
+    for (int i = 5; i > a; i--) /* conditions(2/2) */
+    {
+	if (i == b) /* conditions(2/2) */
+	    return;
+	x = i;
+    }
+}
+
+void
+mcdc015c (int a, int b)
+{
+    for (int i = 5; i > a; i--) /* conditions(2/2) */
+    {
+	if (i == b) /* conditions(2/2) */
+	{
+	    x = 0;
+	    return;
+	}
+	else
+	{
+	    x = 1;
+	    return;
+	}
+
+	x = i;
+    }
+}
+
+
+/* check nested loops */
+void
+mcdc016a (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+	for (int k = 0; k < b; k++) /* conditions(2/2) */
+	    x = i + k;
+}
+
+void
+mcdc016b (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+	if (a > 5) /* conditions(2/2) */
+	    break;
+
+	for (int k = 0; k < b; k++) /* conditions(2/2) */
+	    x = i + k;
+    }
+}
+
+void
+mcdc016c (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+	if (a > 5) /* conditions(1/2) true(0) */
+		   /* conditions(end) */
+	    return;
+
+	for (int k = 0; k < b; k++) /* conditions(2/2) */
+	    x = i + k;
+    }
+}
+
+void
+mcdc016d (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+	for (int k = 0; k < 5; k++) /* conditions(2/2) */
+	{
+	    if (b > 5) /* conditions(1/2) true(0) */
+		       /* conditions(end) */
+		return;
+	    x = i + k;
+	}
+
+    }
+}
+
+/* do-while loops */
+void
+mcdc017a (int a)
+{
+    do
+    {
+	a--;
+    } while (a > 0); /* conditions(2/2) */
+}
+
+void
+noop () {}
+
+void
+mcdc017b (int a, int b)
+{
+    do
+    {
+	/*
+	 * This call is important; it can add more nodes to the body in the
+	 * CFG, which has changes how close exits and breaks are to the loop
+	 * conditional.
+	 */
+	noop ();
+	a--;
+	if (b) /* conditions(2/2) */
+	    break;
+
+    } while (a > 0); /* conditions(2/2) */
+}
+
+void
+mcdc017c (int a, int b)
+{
+    int left = 0;
+    int right = 0;
+    int n = a + b;
+    do
+    {
+	if (a) /* conditions(1/2) false(0) */
+	       /* conditions(end) */
+	{
+	    left = a > left ? b : left; /* conditions(2/2) */
+	}
+	if (b) /* conditions(1/2) false(0) */
+	{
+	    right = b > right ? a : right; /* conditions(2/2) */
+	}
+    } while (n-- > 0); /* conditions(2/2) */
+}
+
+int id  (int x) { return  x; }
+int inv (int x) { return !x; }
+
+/* collection of odd cases lifted-and-adapted from real-world code */
+int mcdc018a (int a, int b, int c, int d, int e, int f, int g, int len)
+{
+    int n;
+    /* adapted from zlib/gz_read */
+    do
+    {
+	n = -1;
+	if (n > len) /* conditions(2/2) */
+	    n = len;
+
+	if (b) /* conditions(2/2) */
+	{
+	    if (b < 5) /* conditions(2/2) */
+		x = 1;
+	    noop();
+	}
+	else if (c && d) /* conditions(3/4) false(1) */
+	{
+	    x = 2;
+	    break;
+	}
+	else if (e || f) /* conditions(2/4) false(0 1) */
+			 /* conditions(end) */
+	{
+	    if (id(g)) /* conditions(2/2) */
+		return 0;
+	    continue;
+	}
+    } while (a-- > 0); /* conditions(2/2) */
+
+    return 1;
+}
+
+void
+mcdc018b (int a, int b, int c)
+{
+    int n;
+    while (a) /* conditions(2/2) */
+    {
+	/* else block does not make a difference for the problem, but ensures
+	   loop termination. */
+	if (b) /* conditions(2/2) */
+	    n = c ? 0 : 0; // does not show up in CFG (embedded in the block)
+	else
+	    n = 0;
+	a = n;
+    }
+}
+
+/* Adapted from zlib/compress2 */
+void
+mcdc018c (int a, int b)
+{
+    int err;
+    do
+    {
+	a = inv (a);
+	err = a;
+    } while (err); /* conditions(1/2) true(0) */
+		   /* conditions(end) */
+
+    a = id (a);
+    if (a) /* conditions(1/2) true(0) */
+	x *= a + 1;
+}
+
+/* too many conditions, coverage gives up */
+void
+mcdc019a ()
+{
+    int conds[65] = { 0 };
+    #pragma GCC diagnostic push
+    #pragma GCC diagnostic ignored "-Wcoverage-too-many-conditions"
+    x = conds[ 0] || conds[ 1] || conds[ 2] || conds[ 3] || conds[ 4] ||
+	conds[ 5] || conds[ 6] || conds[ 7] || conds[ 8] || conds[ 9] ||
+	conds[10] || conds[11] || conds[12] || conds[13] || conds[14] ||
+	conds[15] || conds[16] || conds[17] || conds[18] || conds[19] ||
+	conds[20] || conds[21] || conds[22] || conds[23] || conds[24] ||
+	conds[25] || conds[26] || conds[27] || conds[28] || conds[29] ||
+	conds[30] || conds[31] || conds[32] || conds[33] || conds[34] ||
+	conds[35] || conds[36] || conds[37] || conds[38] || conds[39] ||
+	conds[40] || conds[41] || conds[42] || conds[43] || conds[44] ||
+	conds[45] || conds[46] || conds[47] || conds[48] || conds[49] ||
+	conds[50] || conds[51] || conds[52] || conds[53] || conds[54] ||
+	conds[55] || conds[56] || conds[57] || conds[58] || conds[59] ||
+	conds[60] || conds[61] || conds[62] || conds[63] || conds[64]
+	;
+    #pragma GCC diagnostic pop
+}
+
+/* ternary */
+void
+mcdc020a (int a)
+{
+    // special case, this can be reduced to:
+    // _1 = argc != 0;
+    // e = (int) _1;
+    x = a ? 1 : 0;
+
+    // changing to different int makes branch
+    x = a ? 2 : 1; /* conditions(2/2) */
+}
+
+void
+mcdc020b (int a, int b)
+{
+    x = (a || b) ? 1 : 0; /* conditions(3/4) true(1) */
+}
+
+void
+mcdc020c (int a, int b)
+{
+    x = a ? 0
+	: b ? 1 /* conditions(2/2) */
+	: 2;    /* conditions(1/2) false(0) */
+		/* conditions(end) */
+}
+
+/* Infinite loop (no exit-edge), this should not be called, but it should
+   compile fine */
+void
+mcdc021a ()
+{
+    while (1)
+	;
+}
+
+/* Computed goto can give all sorts of problems, including difficult path
+   contractions. */
+void
+mcdc021b ()
+{
+  void *op = &&dest;
+dest:
+  if (op) /* conditions(0/2) true(0) false(0) */
+	  /* conditions(end) */
+    goto * 0;
+}
+
+int __sigsetjmp ();
+
+/* This should compile, but not called. */
+void
+mcdc021c ()
+{
+  while (x) /* conditions(0/2) true(0) false(0)*/
+	    /* conditions(end) */
+     __sigsetjmp ();
+}
+
+/* If edges are not properly contracted the a && id (b) will be interpreted as
+   two independent expressions. */
+void
+mcdc021d (int a, int b, int c, int d)
+{
+    if (a && id (b)) /* conditions(1/4) true(0 1) false(0) */
+		     /* conditions(end) */
+	x = 1;
+    else if (c && id (d)) /* conditions(1/4) true(0 1) false(0) */
+			  /* conditions(end) */
+	x = 2;
+    else
+	x = 3;
+}
+
+/* Adapted from linux arch/x86/tools/relocs.c
+   With poor edge contracting this became an infinite loop. */
+void
+mcdc022a (int a, int b)
+{
+    for (int i = 0; i < 5; i++) /* conditions(2/2) */
+    {
+	x = i;
+	for (int j = i; j < 5; j++) /* conditions(2/2) */
+	{
+	    if (id (id (a)) || id (b)) /* conditions(3/4) true(0) */
+				       /* conditions(end) */
+		continue;
+	    b = inv(b);
+	}
+    }
+}
+
+int
+mcdc022b (int a)
+{
+    int devt;
+    if (a) /* conditions(2/2) */
+    {
+	x = a * 2;
+	if (x != a / 10 || x != a % 10) /* conditions(1/4) true(1) false(0 1) */
+					/* conditions(end) */
+	    return 0;
+    } else {
+	devt = id (a);
+	if (devt) /* conditions(1/2) true(0) */
+		  /* conditions(end) */
+	    return 0;
+    }
+
+    return devt;
+}
+
+/* Adapted from linux arch/x86/events/intel/ds.c
+
+   It broken sorting so that the entry block was not the first node after
+   sorting. */
+void
+mcdc022c (int a)
+{
+    if (!a) /* conditions(2/2) */
+	return;
+
+    for (int i = 0; i < 5; i++) /* conditions(2/2) */
+    {
+	if (id (a + i) || inv (a - 1)) /* conditions(1/4) false(0 1) true(1) */
+				       /* conditions(end) */
+	    x = a + i;
+	if (inv (a)) /* conditions(1/2) true(0) */
+		     /* conditions(end) */
+	    break;
+    }
+}
+
+void
+mcdc022d (int a)
+{
+    int i;
+    for (i = 0; i < id (a); i++) /* conditions(1/2) false(0) */
+    {
+	if (!inv (a)) /* conditions(1/2) false(0)*/
+		      /* conditions(end) */
+	    break;
+    }
+
+    if (i < a) /* conditions(1/2) false(0) */
+	       /* conditions(end) */
+	x = a + 1;
+}
+
+/* 023 specifically tests that masking works correctly, which gets complicated
+   fast with a mix of operators and deep subexpressions.  These tests violates
+   the style guide slightly to emphasize the nesting.  They all share the same
+   implementation and only one input is given to each function to obtain clean
+   coverage results. */
+void
+mcdc023a (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
+	  int l, int m, int n)
+{
+    // [a m n] = 0, [b, ...] = 1
+    // a is masked by b and the remaining terms should be short circuited
+    if (/* conditions(1/24) true(0 2 3 4 5 6 7 8 9 10 11) false(0 1 2 3 4 5 6 7 8 9 10 11) */
+	/* conditions(end) */
+	   (a || b)
+	|| (   ((c && d) || (e && (f || g) && h))
+	    && (k || l)
+	    && (m || n)))
+	x = a + b;
+    else
+	x = b + c;
+}
+
+void
+mcdc023b (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
+	  int l, int m, int n)
+{
+    // [a b d h] = 0, [c, ...] = 1
+    // h = 0 => false but does not mask (a || b) or (c && d). d = 0 masks c.
+    if (/* conditions(4/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 4 5 6 8 9 10 11) */
+	/* conditions(end) */
+	   (a || b)
+	|| (   ((c && d) || (e && (f || g) && h))
+	    && (k || l)
+	    && (m || n)))
+	x = a + b;
+    else
+	x = b + c;
+}
+
+void
+mcdc023c (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
+	  int l, int m, int n)
+{
+    /* [m n a b] = 0, [...] = 1
+       n,m = 0 should mask all other terms than a, b */
+    if (/* conditions(4/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 3 4 5 6 7 8 9) */
+	/* conditions(end) */
+	   (a || b)
+	|| (   ((c && d) || (e && (f || g) && h))
+	    && (k || l)
+	    && (m || n)))
+	x = a + b;
+    else
+	x = b + c;
+}
+
+void
+mcdc023d (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
+	  int l, int m, int n)
+{
+    /* [a b] = 0, [h, ...] = 1
+       n,m = 0 should mask all other terms than a, b */
+    if (/* conditions(4/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 3 4 5 6 7 10 11) */
+	/* conditions(end) */
+	   (a || b)
+	|| (   ((c && d) || (e && (f || g) && h))
+	    && (k || l)
+	    && (m || n)))
+	x = a + b;
+    else
+	x = b + c;
+}
+
+void
+mcdc023e (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
+	  int l, int m, int n)
+{
+    /* [a b d] = 0, [c h, ...] = 1
+       h = 1 should mask c, d, leave other terms intact.
+       If [k l m n] were false then h itself would be masked.
+       [a b] are masked as collateral by [m n]. */
+    if (/* conditions(5/24) true(0 1 2 3 6 9 11) false(0 1 2 3 4 5 6 7 8 9 10 11) */
+	/* conditions(end) */
+	   (a || b)
+	|| (   ((c && d) || (e && (f || g) && h))
+	    && (k || l)
+	    && (m || n)))
+	x = a + b;
+    else
+	x = b + c;
+}
+
+void
+mcdc023f (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
+	  int l, int m, int n)
+{
+    /* [a b c f g] = 0, [e, ...] = 1
+       [f g] = 0 should mask e, leave [c d] intact. */
+    if (/* conditions(5/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(3 4 7 8 9 10 11) */
+	/* conditions(end) */
+	   (a || b)
+	|| (   ((c && d) || (e && (f || g) && h))
+	    && (k || l)
+	    && (m || n)))
+	x = a + b;
+    else
+	x = b + c;
+}
+
+void
+mcdc023g (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
+	  int l, int m, int n)
+{
+    /* [a b d f g] = 0, [e c, ...] = 1
+       Same as 023f but with [c d] flipped so d masks c rather than c
+       short-circuits.  This should not be lost. */
+    if (/* conditions(5/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 4 7 8 9 10 11) */
+	/* conditions(end) */
+	   (a || b)
+	|| (   ((c && d) || (e && (f || g) && h))
+	    && (k || l)
+	    && (m || n)))
+	x = a + b;
+    else
+	x = b + c;
+}
+
+int main ()
+{
+    mcdc001a (0, 1);
+
+    mcdc001b (0, 1);
+    mcdc001b (0, 0);
+
+    mcdc001c (0, 1);
+    mcdc001c (0, 0);
+    mcdc001c (1, 1);
+
+    mcdc001d (1, 1, 1);
+    mcdc001d (0, 1, 0);
+
+    mcdc002a (1, 0);
+
+    mcdc002b (1, 0);
+    mcdc002b (1, 1);
+
+    mcdc002c (0, 0);
+    mcdc002c (1, 1);
+    mcdc002c (1, 0);
+
+    mcdc002d (1, 1, 1);
+    mcdc002d (1, 0, 0);
+
+    mcdc003a (0, 0);
+    mcdc003a (1, 0);
+
+    mcdc004a (0);
+    mcdc004b (0);
+    mcdc004b (1);
+    mcdc004c (1);
+
+    mcdc004d (0, 0, 0);
+    mcdc004d (1, 1, 1);
+
+    mcdc004e (0, 0, 0);
+    mcdc004e (1, 1, 1);
+
+    mcdc005a (1, 0, 1);
+
+    mcdc005b (1, 1, 0, 0);
+    mcdc005b (1, 0, 0, 0);
+
+    mcdc005c (0, 1, 1, 0);
+
+    mcdc005d (1, 0, 0, 0);
+
+    mcdc006a (0, 0, 0, 0, 0);
+    mcdc006a (1, 0, 0, 0, 0);
+    mcdc006a (1, 1, 1, 0, 0);
+
+    mcdc006b (0, 0, 0);
+    mcdc006b (1, 0, 0);
+    mcdc006b (1, 1, 0);
+    mcdc006b (1, 1, 1);
+
+    mcdc006c (0, 0, 0);
+    mcdc006c (1, 0, 0);
+    mcdc006c (1, 1, 0);
+    mcdc006c (1, 1, 1);
+
+    mcdc007a (0, 0, 0, 0);
+    mcdc007a (1, 0, 0, 0);
+    mcdc007a (0, 0, 1, 0);
+
+    mcdc007b (0, 0, 0);
+    mcdc007b (0, 1, 1);
+    mcdc007b (1, 0, 1);
+
+    mcdc008a (0);
+
+    mcdc008b (0);
+
+    mcdc008c (0);
+    mcdc008c (1);
+
+    mcdc008d (0, 0, 0, 0);
+    mcdc008d (1, 0, 0, 0);
+    mcdc008d (1, 0, 1, 0);
+    mcdc008d (1, 0, 1, 1);
+    mcdc008d (1, 1, 1, 1);
+
+    mcdc009a (0, 0);
+    mcdc009a (1, 1);
+
+    mcdc010a (0, 0);
+    mcdc010a (0, 9);
+    mcdc010a (2, 1);
+
+    mcdc010b ();
+
+    mcdc010c (1);
+
+    mcdc011a (0, 0, 0);
+    mcdc011a (1, 1, 0);
+    mcdc011a (1, 0, 1);
+
+    mcdc012a (0, 0, 0);
+    mcdc012a (0, 1, 1);
+
+    mcdc013a (0, 0, 0);
+    mcdc013a (0, 0, 1);
+    mcdc013a (0, 1, 0);
+    mcdc013a (0, 1, 1);
+    mcdc013a (1, 0, 0);
+    mcdc013a (1, 0, 1);
+    mcdc013a (1, 1, 0);
+    mcdc013a (1, 1, 1);
+
+    mcdc014a ();
+
+    mcdc015a (0, 0);
+    mcdc015a (1, 0);
+
+    mcdc015b (0, 0);
+    mcdc015b (0, 1);
+    mcdc015b (6, 1);
+
+    mcdc015c (0, 0);
+    mcdc015c (0, 5);
+    mcdc015c (6, 1);
+
+    mcdc016a (5, 5);
+
+    mcdc016b (5, 5);
+    mcdc016b (6, 5);
+
+    mcdc016c (5, 5);
+
+    mcdc016d (1, 0);
+
+    mcdc017a (0);
+    mcdc017a (2);
+
+    mcdc017b (2, 0);
+    mcdc017b (0, 1);
+
+    mcdc017c (1, 1);
+
+    mcdc018a (0, 0, 1, 1, 0, 0, 0, 0);
+    mcdc018a (0, 1, 0, 0, 0, 0, 1, -2);
+    mcdc018a (0, 6, 0, 0, 0, 0, 1, -2);
+    mcdc018a (0, 6, 0, 0, 0, 0, 1, -2);
+    mcdc018a (0, 0, 0, 1, 0, 1, 1, 0);
+    mcdc018a (1, 0, 0, 0, 1, 1, 0, 0);
+
+    mcdc018b (1, 0, 0);
+    mcdc018b (1, 1, 0);
+
+    mcdc018c (1, 1);
+
+    mcdc019a ();
+
+    mcdc020a (0);
+    mcdc020a (1);
+
+    mcdc020b (0, 0);
+    mcdc020b (1, 0);
+
+    mcdc020c (0, 1);
+    mcdc020c (1, 1);
+
+    mcdc021d (1, 0, 1, 0);
+
+    mcdc022a (0, 0);
+
+    mcdc022b (0);
+    mcdc022b (1);
+
+    mcdc022c (0);
+    mcdc022c (1);
+
+    mcdc022d (1);
+
+    mcdc023a (0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+    mcdc023b (0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1);
+    mcdc023c (0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0);
+    mcdc023d (0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1);
+    mcdc023e (0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+    mcdc023f (0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1);
+    mcdc023g (0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1);
+}
+
+/* { dg-final { run-gcov conditions { --conditions gcov-19.c } } } */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-20.c b/gcc/testsuite/gcc.misc-tests/gcov-20.c
new file mode 100644
index 00000000000..847dae495db
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-20.c
@@ -0,0 +1,22 @@
+/* { dg-options "-fprofile-conditions -ftest-coverage -fprofile-update=atomic" } */
+/* { dg-do run { target native } } */
+
+/* some side effect to stop branches from being pruned */
+int x = 0;
+
+void
+conditions_atomic001 (int a, int b)
+{
+    if (a || b) /* conditions(1/4) true(0) false(0 1) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+int main ()
+{
+    conditions_atomic001 (0, 1);
+}
+
+/* { dg-final { run-gcov conditions { --conditions gcov-20.c } } } */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-21.c b/gcc/testsuite/gcc.misc-tests/gcov-21.c
new file mode 100644
index 00000000000..978be3276a2
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-21.c
@@ -0,0 +1,16 @@
+/* { dg-options "-fprofile-conditions" } */
+
+/* https://gcc.gnu.org/pipermail/gcc-patches/2022-April/592927.html */
+char trim_filename_name;
+int r;
+
+void trim_filename() {
+    if (trim_filename_name)
+	r = 123;
+    while (trim_filename_name)
+	;
+}
+
+int main ()
+{
+}
diff --git a/gcc/testsuite/lib/gcov.exp b/gcc/testsuite/lib/gcov.exp
index 9d5b2cdb86b..f1c32ed0138 100644
--- a/gcc/testsuite/lib/gcov.exp
+++ b/gcc/testsuite/lib/gcov.exp
@@ -174,6 +174,184 @@ proc verify-branches { testname testcase file } {
     return $failed
 }
 
+#
+# verify-conditions -- check that conditions are checked as expected
+#
+# TESTNAME is the name of the test, including unique flags.
+# TESTCASE is the name of the test file.
+# FILE is the name of the gcov output file.
+#
+# Checks are based on comments in the source file. Condition coverage comes
+# with with two types of output, a summary and a list of the uncovered
+# conditions. Both must be checked to pass the test
+#
+# To check for conditions, add a comment the line of a conditional:
+# /* conditions(n/m) true(0 1) false(1) */
+#
+# where n/m are the covered and total conditions in the expression. The true()
+# and false() take the indices expected *not* covered.
+#
+# This means that all coverage statements should have been seen:
+# /* conditions(end) */
+#
+# If all conditions are covered i.e. n == m, then conditions(end) can be
+# omitted. If either true() or false() are empty they can be omitted too.
+#
+# C++ can insert conditionals in the CFG that are not present in source code.
+# These must be manually suppressed since unexpected and unhandled conditions
+# are an error (to help combat regressions). Output can be suppressed with
+# conditions(suppress) and conditions(end). suppress should usually be on a
+# closing brace.
+#
+# Some expressions, when using unnamed temporaries as operands, will have
+# destructors in expressions. The coverage of the destructor will be reported
+# on the same line as the expression itself, but suppress() would also swallow
+# the expected tested-for messages. To handle these, use the destructor() [1]
+# which will suppress everything from and including the second "conditions
+# covered".
+#
+# [1] it is important that the destructor() is *on the same line* as the
+#     conditions(m/n)
+proc verify-conditions { testname testcase file } {
+    set failed 0
+    set suppress 0
+    set destructor 0
+    set should ""
+    set shouldt ""
+    set shouldf ""
+    set shouldall ""
+    set fd [open $file r]
+    set n 0
+    set keywords {"end" "suppress"}
+    while {[gets $fd line] >= 0} {
+        regexp "^\[^:\]+: *(\[0-9\]+):" "$line" all n
+        set prefix "$testname line $n"
+
+        if {![regexp "condition" $line] && ![regexp "decision" $line]} {
+            continue
+        }
+
+        # Missing coverage for both true and false will cause a failure, but
+        # only count it once for the report.
+        set ok 1
+        if [regexp {conditions *\(([0-9a-z/]+)\)} "$line" all e] {
+            # *Very* coarse sanity check: conditions() should either be a
+            # keyword or n/m, anything else means a buggy test case. end is
+            # optional for cases where all conditions are covered, since it
+            # only expects a single line of output.
+            if {([lsearch -exact $keywords $e] >= 0 || [regexp {\d+/\d+} "$e"]) == 0} {
+                fail "$prefix: expected conditions (n/m), (suppress) or (end); was ($e)"
+                incr failed
+                continue
+            }
+
+            # Any keyword means a new context. Set the error flag if not all
+            # expected output has been seen, and reset the state.
+
+            if {[llength $shouldt] != 0} {
+                fail "$prefix: expected 'not covered (true)' for terms: $shouldt"
+                set ok 0
+            }
+
+            if {[llength $shouldf] != 0} {
+                fail "$prefix: expected 'not covered (false)' for terms: $shouldf"
+                set ok 0
+            }
+
+            if {$shouldall ne ""} {
+                fail "$prefix: coverage summary not found; expected $shouldall"
+                set ok 0
+            }
+
+            set suppress 0
+            set destructor 0
+            set should ""
+            set shouldt ""
+            set shouldf ""
+            set shouldall ""
+            set newt ""
+            set newf ""
+
+            if [regexp {destructor\(\)} "$line"] {
+                set destructor 1
+            }
+
+            if [regexp {(\d+)/(\d+)} "$e" all i k] {
+                regexp {true\(([0-9 ]+)\)}  "$line" all newt
+                regexp {false\(([0-9 ]+)\)} "$line" all newf
+
+                # Sanity check - if the true() and false() vectors should have
+                # m-n elements to cover all uncovered conditions. Because of
+                # masking it can sometimes be surprising what terms are
+                # independent, so this makes for more robust test at the cost
+                # of being slightly more annoying to write.
+                set nterms [expr [llength $newt] + [llength $newf]]
+                set nexpected [expr {$k - $i}]
+                if {$nterms != $nexpected} {
+                    fail "$prefix: expected $nexpected uncovered terms; got $nterms"
+                    set ok 0
+                }
+                set shouldall $e
+                set shouldt $newt
+                set shouldf $newf
+            } elseif {$e == "end"} {
+                # no-op - state has already been reset, and errors flagged
+            } elseif {$e == "suppress"} {
+                set suppress 1
+            } else {
+                # this should be unreachable, 
+                fail "$prefix: unhandled control ($e), should be unreachable"
+                set ok 0
+            }
+        } elseif {$suppress == 1} {
+            # ignore everything in a suppress block. C++ especially can insert
+            # conditionals in exceptions and destructors which would otherwise
+            # be considered unhandled.
+            continue
+        } elseif [regexp {condition +(\d+) not covered \((.*)\)} "$line" all cond condv] {
+            foreach v {true false} {
+                if [regexp $v $condv] {
+                    if {"$v" == "true"} {
+                        set should shouldt
+                    } else {
+                        set should shouldf
+                    }
+
+                    set i [lsearch [set $should] $cond]
+                    if {$i != -1} {
+                        set $should [lreplace [set $should] $i $i]
+                    } else {
+                        fail "$testname line $n: unexpected uncovered term $cond ($v)"
+                        set ok 0
+                    }
+                }
+            }
+        } elseif [regexp {decisions covered (\d+/\d+)} "$line" all cond] {
+            # the destructor-generated "conditions covered" lines will be
+            # written after all expression-related output. Handle these by
+            # turning on suppression if the destructor-suppression is
+            # requested.
+            if {$shouldall == "" && $destructor == 1} {
+                set suppress 1
+                continue
+            }
+
+            if {$cond == $shouldall} {
+                set shouldall ""
+            } else {
+                fail "$testname line $n: unexpected summary $cond"
+                set ok 0
+            }
+        }
+
+        if {$ok != 1} {
+            incr failed
+        }
+    }
+    close $fd
+    return $failed
+}
+
 #
 # verify-calls -- check that call return percentages are as expected
 #
@@ -321,6 +499,7 @@ proc run-gcov { args } {
     set gcov_args ""
     set gcov_verify_calls 0
     set gcov_verify_branches 0
+    set gcov_verify_conditions 0
     set gcov_verify_lines 1
     set gcov_verify_intermediate 0
     set gcov_remove_gcda 0
@@ -331,10 +510,13 @@ proc run-gcov { args } {
 	  set gcov_verify_calls 1
 	} elseif { $a == "branches" } {
 	  set gcov_verify_branches 1
+	} elseif { $a == "conditions" } {
+	  set gcov_verify_conditions 1
 	} elseif { $a == "intermediate" } {
 	  set gcov_verify_intermediate 1
 	  set gcov_verify_calls 0
 	  set gcov_verify_branches 0
+	  set gcov_verify_conditions 0
 	  set gcov_verify_lines 0
 	} elseif { $a == "remove-gcda" } {
 	  set gcov_remove_gcda 1
@@ -404,6 +586,11 @@ proc run-gcov { args } {
     } else {
 	set bfailed 0
     }
+    if { $gcov_verify_conditions } {
+	set cdfailed [verify-conditions $testname $testcase $testcase.gcov]
+    } else {
+	set cdfailed 0
+    }
     if { $gcov_verify_calls } {
 	set cfailed [verify-calls $testname $testcase $testcase.gcov]
     } else {
@@ -418,12 +605,12 @@ proc run-gcov { args } {
 
     # Report whether the gcov test passed or failed.  If there were
     # multiple failures then the message is a summary.
-    set tfailed [expr $lfailed + $bfailed + $cfailed + $ifailed]
+    set tfailed [expr $lfailed + $bfailed + $cdfailed + $cfailed + $ifailed]
     if { $xfailed } {
 	setup_xfail "*-*-*"
     }
     if { $tfailed > 0 } {
-	fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cfailed in return percentages, $ifailed in intermediate format"
+	fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cdfailed in condition/decision, $cfailed in return percentages, $ifailed in intermediate format"
 	if { $xfailed } {
 	    clean-gcov $testcase
 	}
diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
index 2beb49241f2..f2c6617ad44 100644
--- a/gcc/tree-profile.cc
+++ b/gcc/tree-profile.cc
@@ -58,6 +58,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "alloc-pool.h"
 #include "symbol-summary.h"
 #include "symtab-thunks.h"
+#include "cfganal.h"
+#include "cfgloop.h"
 
 static GTY(()) tree gcov_type_node;
 static GTY(()) tree tree_interval_profiler_fn;
@@ -73,6 +75,976 @@ static GTY(()) tree ic_tuple_var;
 static GTY(()) tree ic_tuple_counters_field;
 static GTY(()) tree ic_tuple_callee_field;
 
+namespace
+{
+/* Some context and reused instances between function calls.  Large embedded
+   buffers are used to up-front request enough memory for most programs and
+   merge them into a single allocation at the cost of using more memory in the
+   average case.  Some numbers from linux v5.13 which is assumed to be a
+   reasonably diverse code base: 75% of the functions in linux have less than
+   16 nodes in the CFG and approx 2.5% have more than 64 nodes.  The functions
+   that go beyond a few dozen nodes tend to be very large (>100) and so 64
+   seems like a good balance.
+
+   This is really just a performance balance of the cost of allocation and
+   wasted memory. */
+struct conds_ctx
+{
+    /* Bitmap of the processed blocks.  Bit n set means basic_block->index has
+       been processed either explicitly or as a part of an expression. */
+    auto_sbitmap marks;
+
+    /* This is both a reusable shared allocation which is also used to return
+       single expressions, which means it for most code should only hold a
+       couple of elements. */
+    auto_vec<basic_block, 32> blocks;
+
+    /* Map from basic_block->index to an ordering so that for a single
+       expression (a || b && c) => index_map[a] < index_map[b] < index_map[c].
+       The values do not have to be consecutive and can be interleaved by
+       values from other expressions, so comparisons only make sense for blocks
+       that belong to the same expression. */
+    auto_vec<int, 64> index_map;
+
+    /* Pre-allocate bitmaps and vectors for per-function book keeping.  This is
+       pure instance reuse and the bitmaps carry no data between function
+       calls. */
+    auto_vec<basic_block, 64> B1;
+    auto_vec<basic_block, 64> B2;
+    auto_sbitmap G1;
+    auto_sbitmap G2;
+    auto_sbitmap G3;
+
+    explicit conds_ctx (unsigned size) noexcept (true) : marks (size),
+    G1 (size), G2 (size), G3 (size)
+    {
+	bitmap_clear (marks);
+    }
+
+    /* Mark a node as processed so nodes are not processed twice for example in
+       loops, gotos. */
+    void mark (const basic_block b) noexcept (true)
+    {
+	gcc_assert (!bitmap_bit_p (marks, b->index));
+	bitmap_set_bit (marks, b->index);
+    }
+
+    /* Mark nodes as processed so they are not processed twice. */
+    void mark (const vec<basic_block>& bs) noexcept (true)
+    {
+	for (const basic_block b : bs)
+	    mark (b);
+    }
+
+    /* Check if all nodes are marked.  A successful run should visit & mark
+       every reachable node exactly once. */
+    bool all_marked (const vec<basic_block>& reachable) const noexcept (true)
+    {
+	for (const basic_block b : reachable)
+	    if (!bitmap_bit_p (marks, b->index))
+		return false;
+	return true;
+    }
+};
+
+/* Only instrument terms with fewer than number of bits in a (wide) gcov
+   integer, which is probably 64.  The algorithm itself does not impose this
+   limitation, but it makes for a simpler implementation.
+
+   * Allocating the output data structure (coverage_counter_alloc ()) can
+     assume pairs of gcov_type_unsigned and not use a separate length field.
+   * A pair gcov_type_unsigned can be used as accumulators.
+   * Updating accumulators is can use the bitwise operations |=, &= and not
+     custom operators that work for arbitrary-sized bit-sets.
+
+   Most real-world code should be unaffected by this, but it is possible
+   (especially for generated code) to exceed this limit.
+ */
+#define CONDITIONS_MAX_TERMS (sizeof (gcov_type_unsigned) * BITS_PER_UNIT)
+#define EDGE_CONDITION (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE)
+
+/* Compare two basic blocks by their order in the expression i.e. for (a || b)
+   then cmp_index_map (a, b, ...) < 0.  The result is undefined if lhs, rhs
+   belong to different expressions. */
+int
+cmp_index_map (const void *lhs, const void *rhs, void *index_map)
+{
+    const_basic_block l = *(const basic_block*) lhs;
+    const_basic_block r = *(const basic_block*) rhs;
+    const vec<int>* im = (const vec<int>*) index_map;
+    return (*im)[l->index] - (*im)[r->index];
+}
+
+/* Find the index of needle in blocks; return -1 if not found. This has two
+   uses, sometimes for the index and sometimes for set member checks.  Sets are
+   typically very small (number of conditions, >8 is uncommon) so linear search
+   should be very fast. */
+int
+index_of (const basic_block needle, array_slice<basic_block> blocks)
+{
+    const int size = int (blocks.size ());
+    for (int i = 0; i < size; i++)
+	if (blocks[i] == needle)
+	    return i;
+    return -1;
+}
+
+/* Returns true if this is a conditional node, i.e. it has outgoing true and
+   false edges. */
+bool
+block_conditional_p (const basic_block b)
+{
+    unsigned t = 0;
+    unsigned f = 0;
+    for (edge e : b->succs)
+    {
+	t |= (e->flags & EDGE_TRUE_VALUE);
+	f |= (e->flags & EDGE_FALSE_VALUE);
+    }
+    return t && f;
+}
+
+/* Check if the edge is a conditional. */
+bool
+edge_conditional_p (const edge e)
+{
+    return e->flags & EDGE_CONDITION;
+}
+
+/* Special cases of the single_*_p and single_*_edge functions in basic-block.h
+   that don't consider exception handling or other complex edges.  This helps
+   create a view of the CFG with only normal edges - if a basic block has both
+   an outgoing fallthrough and exceptional edge [1], it should be considered a
+   single-successor.
+
+   [1] if this is not possible, these functions can be removed and replaced by
+       their basic-block.h cousins. */
+bool
+single (const vec<edge, va_gc> *edges)
+{
+    int n = EDGE_COUNT (edges);
+    if (n == 0)
+	return false;
+
+    for (edge e : edges)
+	if (e->flags & EDGE_COMPLEX)
+	    n -= 1;
+
+    return n == 1;
+}
+
+/* Get the single, non-complex edge.  Behavior is undefined edges have more
+   than 1 non-complex edges. */
+edge
+single_edge (const vec<edge, va_gc> *edges)
+{
+    for (edge e : edges)
+    {
+	if (e->flags & EDGE_COMPLEX)
+	    continue;
+	return e;
+    }
+    return NULL;
+}
+
+/* Sometimes, for example with function calls and C++ destructors, the CFG gets
+   extra nodes that are essentially single-entry-single-exit in the middle of
+   boolean expressions.  For example:
+
+      x || can_throw (y)
+
+               A
+              /|
+             / |
+            B  |
+            |  |
+            C  |
+           / \ |
+          /   \|
+         F     T
+
+   Without the extra node inserted by the function + exception it becomes a
+   proper 2-term graph, not 2 single-term graphs.
+
+               A
+              /|
+             C |
+            / \|
+           F   T
+
+   contract_edge ignores the series of intermediate nodes and makes a virtual
+   edge A -> C without having to construct a new simplified CFG explicitly.  It
+   gets more complicated as non-conditional edges is how the body of the
+   then/else blocks are separated from the boolean expression, so only edges
+   that are inserted because of function calls in the expression itself must be
+   merged.
+
+   Only chains of single-exit single-entry nodes that end with a condition
+   should be contracted. */
+edge
+contract_edge (edge e)
+{
+    edge source = e;
+    while (true)
+    {
+	basic_block dest = e->dest;
+	if (!single (dest->preds))
+	    return source;
+	if (e->flags & EDGE_DFS_BACK)
+	    return source;
+	if (block_conditional_p (dest))
+	    return e;
+
+	e = single_edge (dest->succs);
+	if (!e)
+	    return source;
+    }
+}
+
+/* This is the predecessor dual of contract_edge; it collapses the predecessor
+   blocks between two operands in a boolean expression. */
+edge
+contract_edge_up (edge e)
+{
+    while (true)
+    {
+	basic_block src = e->src;
+	if (edge_conditional_p (e))
+	    return e;
+	if (!single (src->preds))
+	    return e;
+	e = single_edge (src->preds);
+    }
+}
+
+/* Find the set {ancestors(p) intersect G} where ancestors is the recursive set
+   of predecessors for p.  Limiting to the ancestors that are also in G (see
+   cond_reachable_from) and by q is an optimization as ancestors outside G have
+   no effect when isolating expressions.
+
+   dfs_enumerate_from () does not work as the filter function needs edge
+   information and dfs_enumerate_from () only considers blocks. */
+void
+ancestors_of (basic_block p, basic_block q, const sbitmap G, sbitmap ancestors)
+{
+    if (!bitmap_bit_p (G, p->index))
+	return;
+
+    bitmap_set_bit (ancestors, p->index);
+    bitmap_set_bit (ancestors, q->index);
+    if (p == q)
+	return;
+
+    auto_vec<basic_block, 16> stack;
+    stack.safe_push (p);
+
+    while (!stack.is_empty())
+    {
+	basic_block b = stack.pop ();
+	if (single (b->preds))
+	{
+	    edge e = single_edge (b->preds);
+	    e = contract_edge_up (e);
+	    b = e->dest;
+	}
+
+	for (edge e : b->preds)
+	{
+	    basic_block src = e->src;
+	    if (bitmap_bit_p (ancestors, e->src->index))
+		continue;
+	    if (!bitmap_bit_p (G, e->src->index))
+		continue;
+	    bitmap_set_bit (ancestors, src->index);
+	    stack.safe_push (src);
+	}
+    }
+}
+
+/* A simple struct for storing/returning outcome block pairs.  Either both
+   blocks are set or both are NULL. */
+struct outcomes
+{
+    basic_block t = NULL;
+    basic_block f = NULL;
+
+    operator bool () const noexcept (true)
+    {
+	return t && f;
+    }
+};
+
+/* Get the true/false successors of a basic block. If b is not a conditional
+   block both edges are NULL. */
+outcomes
+conditional_succs (const basic_block b)
+{
+    outcomes c;
+    for (edge e : b->succs)
+    {
+	if (e->flags & EDGE_TRUE_VALUE)
+	    c.t = (e)->dest;
+	if (e->flags & EDGE_FALSE_VALUE)
+	    c.f = (e)->dest;
+    }
+
+    gcc_assert ((c.t && c.f) || (!c.t && !c.f));
+    return c;
+}
+
+/* Get the index or offset of a conditional flag, 0 for true and 1 for false.
+   These indices carry no semantics but must be consistent as they are used to
+   index into data structures in code generation and gcov. */
+unsigned
+condition_index (unsigned flag)
+{
+    return (flag & EDGE_CONDITION) == EDGE_TRUE_VALUE ? 0 : 1;
+}
+
+/* Compute the masking vector.
+
+   Masking and short circuiting are deeply connected - masking occurs when
+   control flow reaches a state that is also reachable with short circuiting.
+   In fact, masking corresponds to short circuiting in the CFG for the reversed
+   expression.  This means we can find the limits, the last term in preceeding
+   subexpressions, by following the edges that short circuit to the same
+   outcome.
+
+   In the simplest case a || b:
+
+   a
+   |\
+   | b
+   |/ \
+   T   F
+
+   T has has multiple incoming edges and is the outcome of a short circuit,
+   with top = a, bot = b.  The top node (a)is masked when the edge (b, T) is
+   taken.
+
+   The names "top" and "bot" refer to a pair of nodes with a shared
+   destination. The top is always the node corresponding to the left-most
+   operand of the two it holds that index_map[top] < index_map[bot].
+
+   Now consider (a && b) || (c && d) and its masking vectors:
+
+   a
+   |\
+   b \
+   |\|
+   | c
+   | |\
+   | d \
+   |/ \|
+   T   F
+
+   a[0] = {}
+   a[1] = {}
+   b[0] = {a}
+   b[1] = {}
+   c[0] = {}
+   c[1] = {}
+   d[0] = {c}
+   d[1] = {a,b}
+
+   Note that 0 and 1 are indices and not boolean values - a[0] is the index in
+   the masking vector when a takes the true edge.
+
+   b[0] and d[0] are identical to the a || b example, and d[1] is the bot in
+   the triangle [d, b] -> T.  b is the top node in the [d, b] relationship and
+   last term in (a && b).  To find the other terms masked we use the fact that
+   all nodes in an expression have outgoing edges to either the outcome or some
+   other node in the expression.  We know one outcome is T and that b is the
+   last term, which means the other outcome is b's other successor (in this
+   case c).  We find the terms by marking the outcomes (in this case c, T) and
+   walk the predecessors starting at top (in this case b) and masking nodes
+   when both successors are marked.
+
+   The masking vector is represented as two bitfields per term in the
+   expression with the index corresponding to the term in the source
+   expression.  a || b && c becomes the term vector [a b c] and the masking
+   vectors [a[0] a[1] b[0] ...].  The kth bit of a masking vector is set if the
+   the kth term is masked by taking the edge. */
+void
+masking_vectors (conds_ctx& ctx, array_slice<basic_block> blocks,
+		 array_slice<gcov_type_unsigned> masks)
+{
+    gcc_assert (blocks.is_valid ());
+    gcc_assert (!blocks.empty ());
+    gcc_assert (masks.is_valid ());
+
+    sbitmap marks = ctx.G1;
+    sbitmap expr = ctx.G2;
+    vec<basic_block>& queue = ctx.B1;
+    vec<basic_block>& body = ctx.B2;
+    const vec<int>& index_map = ctx.index_map;
+    bitmap_clear (expr);
+
+    for (const basic_block b : blocks)
+	bitmap_set_bit (expr, b->index);
+
+    /* Set up for the iteration - include two outcome nodes in the traversal and
+       ignore the leading term since it cannot mask anything.  The algorithm is
+       not sensitive to the traversal order. */
+    body.truncate (0);
+    body.reserve (blocks.size () + 2);
+    for (const basic_block b : blocks)
+	body.quick_push (b);
+
+    outcomes out = conditional_succs (blocks.back ());
+    body.quick_push (out.t);
+    body.quick_push (out.f);
+    body[0] = body.pop ();
+
+    for (const basic_block b : body)
+    {
+	for (edge e1 : b->preds)
+	for (edge e2 : b->preds)
+	{
+	    const basic_block top = e1->src;
+	    const basic_block bot = e2->src;
+	    const unsigned cond = e1->flags & e2->flags & (EDGE_CONDITION);
+
+	    if (!cond)
+		continue;
+	    if (e1 == e2)
+		continue;
+	    if (!bitmap_bit_p (expr, top->index))
+		continue;
+	    if (!bitmap_bit_p (expr, bot->index))
+		continue;
+	    if (index_map[top->index] > index_map[bot->index])
+		continue;
+
+	    outcomes out = conditional_succs (top);
+	    gcc_assert (out);
+	    bitmap_clear (marks);
+	    bitmap_set_bit (marks, out.t->index);
+	    bitmap_set_bit (marks, out.f->index);
+	    queue.truncate (0);
+	    queue.safe_push (top);
+
+	    // The edge bot -> outcome triggers the masking
+	    const int m = 2*index_of (bot, blocks) + condition_index (cond);
+	    while (!queue.is_empty())
+	    {
+		basic_block q = queue.pop ();
+		/* q may have been processed & completed by being added to the
+		   queue multiple times, so check that there is still work to
+		   do before continuing. */
+		if (bitmap_bit_p (marks, q->index))
+		    continue;
+
+		outcomes succs = conditional_succs (q);
+		if (!bitmap_bit_p (marks, succs.t->index))
+		    continue;
+		if (!bitmap_bit_p (marks, succs.f->index))
+		    continue;
+
+		const int index = index_of (q, blocks);
+		gcc_assert (index != -1);
+		masks[m] |= gcov_type_unsigned (1) << index;
+		bitmap_set_bit (marks, q->index);
+
+		for (edge e : q->preds)
+		{
+		    e = contract_edge_up (e);
+		    if (!edge_conditional_p (e))
+			continue;
+		    if (e->flags & EDGE_DFS_BACK)
+			continue;
+		    if (bitmap_bit_p (marks, e->src->index))
+			continue;
+		    if (!bitmap_bit_p (expr, e->src->index))
+			continue;
+		    queue.safe_push (e->src);
+		}
+	    }
+	}
+    }
+}
+
+/* Find the nodes reachable from p by following only (possibly contracted)
+   condition edges dominated by p and ignore DFS back edges.  From a high level
+   this is partitioning the CFG into subgraphs by removing all non-condition
+   edges and selecting a single connected subgraph.  This creates a cut C = (G,
+   G') where G is the returned explicitly by this function.
+
+   It is assumed that all paths from p go through q (q post-dominates p).  p
+   must always be the first term in an expression and a condition node.
+
+   If |G| = 1 then this is a single term expression. If |G| > 1 then either
+   this is a multi-term expression or the first block in the then/else block is
+   a conditional expression as well.
+
+   Only nodes dominated by p is added - under optimization some blocks may be
+   merged and multiple independent conditions may share the same outcome
+   (making successors misidentified as a right operands), but true right-hand
+   operands are always dominated by the first term.
+
+   The function outputs both a bitmap and a vector as both are useful to the
+   caller. */
+void
+cond_reachable_from (basic_block p, basic_block q, sbitmap expr,
+		     vec<basic_block>& out)
+{
+    out.safe_push (p);
+    bitmap_set_bit (expr, p->index);
+    for (unsigned pos = 0; pos < out.length (); pos++)
+    {
+	for (edge e : out[pos]->succs)
+	{
+	    basic_block dest = contract_edge (e)->dest;
+	    if (dest == q)
+		continue;
+	    if (!dominated_by_p (CDI_DOMINATORS, dest, p))
+		continue;
+	    if (!block_conditional_p (dest))
+		continue;
+	    if (bitmap_bit_p (expr, dest->index))
+		continue;
+	    if (e->flags & EDGE_DFS_BACK)
+		continue;
+
+	    bitmap_set_bit (expr, dest->index);
+	    out.safe_push (dest);
+	}
+    }
+}
+
+/* Find the neighborhood of the graph G = [blocks, blocks+n), the
+   successors of nodes in G that are not also in G.  In the cut C = (G, G')
+   these are the nodes in G' with incoming edges that cross the span. */
+void
+neighborhood (const vec<basic_block>& blocks, const sbitmap G,
+	      vec<basic_block>& out)
+{
+    for (const basic_block b : blocks)
+    {
+	for (edge e : b->succs)
+	{
+	    basic_block dest = contract_edge (e)->dest;
+	    if (bitmap_bit_p (G, dest->index))
+		continue;
+	    if (!out.contains (dest))
+		out.safe_push (dest);
+	}
+    }
+}
+
+/* Find and isolate the expression starting at p.
+
+   Make a cut C = (G, G') following only condition edges.  G is a superset of
+   the expression B, but the walk may include expressions from the then/else
+   blocks if they start with conditions.  Only the subgraph B is the ancestor
+   of *both* the then/else outcome, which means B is the intersection of the
+   ancestors of the nodes in the neighborhood N(G). */
+void
+isolate_expression (conds_ctx &ctx, basic_block p, vec<basic_block>& out)
+{
+    sbitmap expr = ctx.G1;
+    sbitmap reachable = ctx.G2;
+    sbitmap ancestors = ctx.G3;
+    bitmap_clear (expr);
+    bitmap_clear (reachable);
+
+    vec<basic_block>& G = ctx.B1;
+    vec<basic_block>& NG = ctx.B2;
+    G.truncate (0);
+    NG.truncate (0);
+
+    basic_block post = get_immediate_dominator (CDI_POST_DOMINATORS, p);
+    cond_reachable_from (p, post, reachable, G);
+    if (G.length () == 1)
+    {
+	out.safe_push (p);
+	return;
+    }
+
+    neighborhood (G, reachable, NG);
+    bitmap_copy (expr, reachable);
+
+    for (const basic_block neighbor : NG)
+    {
+	bitmap_clear (ancestors);
+	for (edge e : neighbor->preds)
+	    ancestors_of (e->src, p, reachable, ancestors);
+	bitmap_and (expr, expr, ancestors);
+    }
+
+    for (const basic_block b : G)
+	if (bitmap_bit_p (expr, b->index))
+	    out.safe_push (b);
+    out.sort (cmp_index_map, &ctx.index_map);
+}
+
+/* Emit lhs = op1 <op> op2 on edges.  This emits non-atomic instructions and
+   should only be used on the local accumulators. */
+void
+emit_bitwise_op (edge e, tree lhs, tree op1, tree_code op, tree op2)
+{
+    tree tmp;
+    gassign *read;
+    gassign *bitw;
+    gimple *write;
+
+    tmp = make_temp_ssa_name (gcov_type_node, NULL, "__conditions_tmp");
+    read = gimple_build_assign (tmp, op1);
+    tmp = make_temp_ssa_name (gcov_type_node, NULL, "__conditions_tmp");
+    bitw = gimple_build_assign (tmp, op, gimple_assign_lhs (read), op2);
+    write = gimple_build_assign (lhs, gimple_assign_lhs (bitw));
+
+    gsi_insert_on_edge (e, read);
+    gsi_insert_on_edge (e, bitw);
+    gsi_insert_on_edge (e, write);
+}
+
+/* Visitor for make_index_map. */
+void
+make_index_map_visit (basic_block b, vec<basic_block>& L, vec<int>& marks)
+{
+    if (marks[b->index])
+	return;
+
+    for (edge e : b->succs)
+	if (!(e->flags & EDGE_DFS_BACK))
+	    make_index_map_visit (e->dest, L, marks);
+
+    marks[b->index] = 1;
+    L.quick_push (b);
+}
+
+/* Find a topological sorting of the blocks in a function so that left operands
+   are before right operands including subexpressions.  Sorting on block index
+   does not guarantee this property and the syntactical order of terms is very
+   important to the condition coverage.  The sorting algorithm is from Cormen
+   et al (2001) but with back-edges ignored and thus there is no need for
+   temporary marks (for cycle detection).
+
+   It is important to select unvisited nodes in DFS order to ensure the
+   roots/leading terms of boolean expressions are visited first (the other
+   terms being covered by the recursive step), but the visiting order of
+   individual boolean expressions carries no significance.
+
+   For the expression (a || (b && c) || d) the blocks should be [a b c d]. */
+void
+make_index_map (const vec<basic_block>& blocks, int max_index,
+		vec<basic_block>& L, vec<int>& index_map)
+{
+    L.truncate (0);
+    L.reserve (max_index);
+
+    /* Use of the output map as a temporary for tracking visited status. */
+    index_map.truncate (0);
+    index_map.safe_grow_cleared (max_index);
+    for (const basic_block b : blocks)
+	make_index_map_visit (b, L, index_map);
+
+    /* Insert canaries - if there are unreachable nodes (for example infinite
+       loops) then the unreachable nodes should never be needed for comparison,
+       and L.length () < max_index.  An index mapping should also never be
+       recorded twice. */
+    for (unsigned i = 0; i < index_map.length (); i++)
+	index_map[i] = -1;
+
+    gcc_assert (blocks.length () == L.length ());
+    L.reverse ();
+    const int nblocks = L.length ();
+    for (int i = 0; i < nblocks; i++)
+    {
+	gcc_assert (L[i]->index != -1);
+	index_map[L[i]->index] = i;
+    }
+}
+
+/* Walk the CFG and collect conditionals.
+
+   1. Collect a candidate set G by walking from the root following all
+      (contracted) condition edges.
+   2. This creates a cut C = (G, G'); find the neighborhood N(G).
+   3. For every node in N(G), follow the edges across the cut and collect all
+      ancestors (that are also in G).
+   4. The intersection of all these ancestor sets is the boolean expression B
+      that starts in root.
+
+   Walking is not guaranteed to find nodes in the order of the expression, it
+   might find (a || b) && c as [a c b], so the result must be sorted by the
+   index map. */
+const vec<basic_block>&
+collect_conditions (conds_ctx& ctx, const basic_block block)
+{
+    vec<basic_block>& blocks = ctx.blocks;
+    blocks.truncate (0);
+
+    if (bitmap_bit_p (ctx.marks, block->index))
+	return blocks;
+
+    if (!block_conditional_p (block))
+    {
+	ctx.mark (block);
+	return blocks;
+    }
+
+    isolate_expression (ctx, block, blocks);
+    ctx.mark (blocks);
+
+    if (blocks.length () > CONDITIONS_MAX_TERMS)
+    {
+	location_t loc = gimple_location (gsi_stmt (gsi_last_bb (block)));
+	warning_at (loc, OPT_Wcoverage_too_many_conditions,
+		    "Too many conditions (found %u); giving up coverage",
+		    blocks.length ());
+	blocks.truncate (0);
+    }
+    return blocks;
+}
+
+/* Used for dfs_enumerate_from () to include all reachable nodes. */
+bool
+yes (const_basic_block, const void *)
+{
+    return true;
+}
+
+}
+
+struct condcov {
+    explicit condcov (unsigned nblocks) noexcept (true) : ctx (nblocks) {}
+    auto_vec<int, 128> m_index;
+    auto_vec<basic_block, 256> m_blocks;
+    auto_vec<gcov_type_unsigned, 512> m_masks;
+    conds_ctx ctx;
+};
+
+unsigned
+cov_length (const struct condcov* cov)
+{
+    if (cov->m_index.is_empty ())
+	return 0;
+    return cov->m_index.length () - 1;
+}
+
+array_slice<basic_block>
+cov_blocks (struct condcov* cov, unsigned n)
+{
+    if (n >= cov->m_index.length ())
+	return array_slice<basic_block>::invalid ();
+
+    basic_block *begin = cov->m_blocks.begin () + cov->m_index[n];
+    basic_block *end = cov->m_blocks.begin () + cov->m_index[n + 1];
+    return array_slice<basic_block> (begin, end - begin);
+}
+
+array_slice<gcov_type_unsigned>
+cov_masks (struct condcov* cov, unsigned n)
+{
+    if (n >= cov->m_index.length ())
+	return array_slice<gcov_type_unsigned>::invalid ();
+
+    gcov_type_unsigned *begin = cov->m_masks.begin () + 2*cov->m_index[n];
+    gcov_type_unsigned *end = cov->m_masks.begin () + 2*cov->m_index[n + 1];
+    return array_slice<gcov_type_unsigned> (begin, end - begin);
+}
+
+void
+cov_free (struct condcov* cov)
+{
+    delete cov;
+}
+
+/* Condition coverage (MC/DC)
+
+   Algorithm
+   ---------
+   Whalen, Heimdahl, De Silva in "Efficient Test Coverage Measurement for
+   MC/DC" describe an algorithm for modified condition/decision coverage based
+   on AST analysis.  This algorithm analyses the control flow graph to analyze
+   expressions and compute masking vectors, but is inspired by their marking
+   functions for recording outcomes.  The individual phases are described in
+   more detail closer to the implementation.
+
+   The CFG is traversed in DFS order.  It is important that the first basic
+   block in an expression is the first one visited, but the order of
+   independent expressions does not matter.  When the function terminates,
+   every node in the dfs should have been processed and marked exactly once.
+   If there are unreachable nodes they are ignored and not instrumented.
+
+   The CFG is broken up into segments between dominators.  This isn't strictly
+   necessary, but since boolean expressions cannot cross dominators it makes
+   for a nice way to introduce limits to searches.
+
+   The coverage only considers the positions, not the symbols, in a
+   conditional, e.g. !A || (!B && A) is a 3-term conditional even though A
+   appears twice.  Subexpressions have no effect on term ordering:
+   (a && (b || (c && d)) || e) comes out as [a b c d e].
+
+   The output for gcov is a vector of pairs of unsigned integers, interpreted
+   as bit-sets, where the bit index corresponds to the index of the condition
+   in the expression.
+ */
+struct condcov*
+find_conditions (struct function *fn)
+{
+    record_loop_exits ();
+    mark_dfs_back_edges (fn);
+
+    const bool have_dom = dom_info_available_p (fn, CDI_DOMINATORS);
+    const bool have_post_dom = dom_info_available_p (fn, CDI_POST_DOMINATORS);
+    if (!have_dom)
+	calculate_dominance_info (CDI_DOMINATORS);
+    if (!have_post_dom)
+	calculate_dominance_info (CDI_POST_DOMINATORS);
+
+    const unsigned nblocks = n_basic_blocks_for_fn (fn);
+    condcov *cov = new condcov (nblocks);
+    conds_ctx& ctx = cov->ctx;
+
+    auto_vec<basic_block, 16> dfs;
+    dfs.safe_grow (nblocks);
+    const basic_block entry = ENTRY_BLOCK_PTR_FOR_FN (fn);
+    const basic_block exit = ENTRY_BLOCK_PTR_FOR_FN (fn);
+    int n = dfs_enumerate_from (entry, 0, yes, dfs.address (), nblocks, exit);
+    dfs.truncate (n);
+    make_index_map (dfs, nblocks, ctx.B1, ctx.index_map);
+
+    /* Visit all reachable nodes and collect conditions.  DFS order is
+       important so the first node of a boolean expression is visited first
+       (it will mark subsequent terms). */
+    cov->m_index.safe_push (0);
+    for (const basic_block b : dfs)
+    {
+	const vec<basic_block>& expr = collect_conditions (ctx, b);
+	if (!expr.is_empty ())
+	{
+	    cov->m_blocks.safe_splice (expr);
+	    cov->m_index.safe_push (cov->m_blocks.length ());
+	}
+    }
+    gcc_assert (ctx.all_marked (dfs));
+
+    if (!have_dom)
+	free_dominance_info (fn, CDI_DOMINATORS);
+    if (!have_post_dom)
+	free_dominance_info (fn, CDI_POST_DOMINATORS);
+
+    cov->m_masks.safe_grow_cleared (2 * cov->m_index.last());
+    const unsigned length = cov_length (cov);
+    for (unsigned i = 0; i < length; i++)
+	masking_vectors (ctx, cov_blocks (cov, i), cov_masks (cov, i));
+
+    return cov;
+}
+
+int
+instrument_decisions (array_slice<basic_block> expr, unsigned condno,
+		      tree *accu, gcov_type_unsigned *masks)
+{
+    /* Zero the local accumulators. */
+    tree zero = build_int_cst (get_gcov_type (), 0);
+    for (edge e : expr[0]->succs)
+    {
+	gsi_insert_on_edge (e, gimple_build_assign (accu[0], zero));
+	gsi_insert_on_edge (e, gimple_build_assign (accu[1], zero));
+    }
+    /* Add instructions for updating the function-local accumulators.  */
+    for (size_t i = 0; i < expr.size (); i++)
+    {
+	for (edge e : expr[i]->succs)
+	{
+	    if (!edge_conditional_p (e))
+		continue;
+
+	    /* accu |= expr[i] */
+	    const int k = condition_index (e->flags);
+	    tree rhs = build_int_cst (gcov_type_node, 1ULL << i);
+	    emit_bitwise_op (e, accu[k], accu[k], BIT_IOR_EXPR, rhs);
+
+	    if (masks[2*i + k] == 0)
+		continue;
+
+	    /* accu &= mask[i] */
+	    tree mask = build_int_cst (gcov_type_node, ~masks[2*i + k]);
+	    for (int j = 0; j < 2; j++)
+		emit_bitwise_op (e, accu[j], accu[j], BIT_AND_EXPR, mask);
+	}
+    }
+
+    const bool atomic = flag_profile_update == PROFILE_UPDATE_ATOMIC;
+    const tree atomic_ior = builtin_decl_explicit
+	(TYPE_PRECISION (gcov_type_node) > 32
+	 ? BUILT_IN_ATOMIC_FETCH_OR_8
+	 : BUILT_IN_ATOMIC_FETCH_OR_4);
+
+    /* Add instructions for flushing the local accumulators.
+
+       It is important that the flushes happen on on the outcome's incoming
+       edges, otherwise flushes could be lost to exception handling.
+
+       void fn (int a)
+       {
+	   if (a)
+	    fclose();
+	   exit();
+       }
+
+       Can yield the CFG:
+       A
+       |\
+       | B
+       |/
+       e
+
+       This typically only happen in optimized builds, but gives linker errors
+       because the counter is left as an undefined symbol. */
+
+    outcomes out = conditional_succs (expr.back ());
+    const basic_block outcome_blocks[] = { out.t, out.t, out.f, out.f, };
+    const int outcome[] = { 0, 1, 0, 1 };
+    for (int i = 0; i < 4; i++)
+    {
+	const int k = outcome[i];
+	for (edge e : outcome_blocks[i]->preds)
+	{
+	    /* Only instrument edges from inside the expression. Sometimes
+	       complicated control flow (like sigsetjmp and gotos) add
+	       predecessors that don't come from the boolean expression. */
+	    if (index_of (e->src, expr) == -1)
+		continue;
+
+	    tree ref = tree_coverage_counter_ref (GCOV_COUNTER_CONDS,
+						  2*condno + k);
+	    tree tmp = make_temp_ssa_name (gcov_type_node, NULL,
+					   "__conditions_tmp");
+	    if (atomic)
+	    {
+		tree relaxed = build_int_cst (integer_type_node,
+					      MEMMODEL_RELAXED);
+		ref = unshare_expr (ref);
+		gassign *read = gimple_build_assign (tmp, accu[k]);
+		gcall *flush = gimple_build_call (atomic_ior, 3,
+						  build_addr (ref),
+						  gimple_assign_lhs (read),
+						  relaxed);
+
+		gsi_insert_on_edge (e, read);
+		gsi_insert_on_edge (e, flush);
+	    }
+	    else
+	    {
+		gassign *read = gimple_build_assign (tmp, ref);
+		tmp = gimple_assign_lhs (read);
+		gsi_insert_on_edge (e, read);
+		ref = unshare_expr (ref);
+		emit_bitwise_op (e, ref, accu[k], BIT_IOR_EXPR, tmp);
+	    }
+	}
+    }
+    return expr.size ();
+}
+
+#undef CONDITIONS_MAX_TERMS
+#undef EDGE_CONDITION
+
 /* Do initialization work for the edge profiler.  */
 
 /* Add code:
@@ -758,7 +1730,7 @@ tree_profiling (void)
 	  thunk = true;
 	  /* When generate profile, expand thunk to gimple so it can be
 	     instrumented same way as other functions.  */
-	  if (profile_arc_flag)
+	  if (profile_arc_flag || profile_condition_flag)
 	    expand_thunk (node, false, true);
 	  /* Read cgraph profile but keep function as thunk at profile-use
 	     time.  */
@@ -803,7 +1775,7 @@ tree_profiling (void)
   release_profile_file_filtering ();
 
   /* Drop pure/const flags from instrumented functions.  */
-  if (profile_arc_flag || flag_test_coverage)
+  if (profile_arc_flag || profile_condition_flag || flag_test_coverage)
     FOR_EACH_DEFINED_FUNCTION (node)
       {
 	if (!gimple_has_body_p (node->decl)
@@ -897,7 +1869,7 @@ pass_ipa_tree_profile::gate (function *)
      disabled.  */
   return (!in_lto_p && !flag_auto_profile
 	  && (flag_branch_probabilities || flag_test_coverage
-	      || profile_arc_flag));
+	      || profile_arc_flag || profile_condition_flag));
 }
 
 } // anon namespace
diff --git a/libgcc/libgcov-merge.c b/libgcc/libgcov-merge.c
index 89741f637e1..9e3e8ee5657 100644
--- a/libgcc/libgcov-merge.c
+++ b/libgcc/libgcov-merge.c
@@ -33,6 +33,11 @@ void __gcov_merge_add (gcov_type *counters  __attribute__ ((unused)),
                        unsigned n_counters __attribute__ ((unused))) {}
 #endif
 
+#ifdef L_gcov_merge_ior
+void __gcov_merge_ior (gcov_type *counters  __attribute__ ((unused)),
+		       unsigned n_counters __attribute__ ((unused))) {}
+#endif
+
 #ifdef L_gcov_merge_topn
 void __gcov_merge_topn (gcov_type *counters  __attribute__ ((unused)),
 			unsigned n_counters __attribute__ ((unused))) {}
-- 
2.34.0


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-07-12 14:05 ` Sebastian Huber
@ 2022-07-13  2:04   ` Jørgen Kvalsvik
  0 siblings, 0 replies; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-07-13  2:04 UTC (permalink / raw)
  To: Sebastian Huber, gcc-patches

On 12/07/2022 16:05, Sebastian Huber wrote:
> Hello Jørgen,
> 
> thanks for the updated patch. I used it for a test suite run and the results
> look quite good.
> 
> Could you please add this hunk to your patch set:
> 
> diff --git a/libgcc/libgcov-merge.c b/libgcc/libgcov-merge.c
> index 89741f637e1..9e3e8ee5657 100644
> --- a/libgcc/libgcov-merge.c
> +++ b/libgcc/libgcov-merge.c
> @@ -33,6 +33,11 @@ void __gcov_merge_add (gcov_type *counters __attribute__
> ((unused)),
>                         unsigned n_counters __attribute__ ((unused))) {}
>  #endif
> 
> +#ifdef L_gcov_merge_ior
> +void __gcov_merge_ior (gcov_type *counters  __attribute__ ((unused)),
> +                      unsigned n_counters __attribute__ ((unused))) {}
> +#endif
> +
>  #ifdef L_gcov_merge_topn
>  void __gcov_merge_topn (gcov_type *counters  __attribute__ ((unused)),
>                         unsigned n_counters __attribute__ ((unused))) {}
> 
> It is necessary to use gcov in freestanding environments (inhibit_libc is defined).
> 
> The condition profiling found one spot for which we have insufficient condition
> coverage:
> 
> function _Leap_year called 227 returned 100% blocks executed 100%
>       227:   54:static bool _Leap_year(
>         -:   55:  uint32_t year
>         -:   56:)
>         -:   57:{
>       227:   58:  return (((year % 4) == 0) && ((year % 100) != 0)) || ((year %
> 400) == 0);
> branch  0 taken 19% (fallthrough)
> branch  1 taken 81%
> branch  2 taken 16% (fallthrough)
> branch  3 taken 84%
> branch  4 taken 4% (fallthrough)
> branch  5 taken 96%
> conditions covered 5/6
> condition  1 not covered (false)
>         -:   59:}
> 
> This is because we don't test with the year 2100 for example. This value would
> result in:
> 
> year % 4 == 0: true
> year % 100 != 0: false
> year % 400 == 0: false
> 
> It was not immediately clear to me what the
> 
> "conditions covered 5/6
> condition  1 not covered (false)"
> 
> is supposed to tell me. I guess a reasonable interpretation is: condition 1
> (which is "(year % 100) != 0" should be false and determine the outcome of the
> decision.
> 
> What could be a bit confusing is that we have "conditions covered 5/6", however,
> there are only three conditions (0: (year % 4) == 0, 1: (year % 100) != 0, 2:
> (year % 400) == 0). Maybe it would be more clear if the report says "condition
> variants covered 5/6" or something like this.
> 

Hello,

Thanks for the feedback. I'll apply the patch, no problem.

As for output I was honestly never really too happy with the output, and hoped
something would leap out during development (it didn't). I modeled most of it
after what the branch coverage output, and I'll give it a bit of thinking to see
if I can make it more intuitive at least.

Thanks,
Jørgen

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-07-11 10:02 Jørgen Kvalsvik
@ 2022-07-12 14:05 ` Sebastian Huber
  2022-07-13  2:04   ` Jørgen Kvalsvik
  0 siblings, 1 reply; 39+ messages in thread
From: Sebastian Huber @ 2022-07-12 14:05 UTC (permalink / raw)
  To: Jørgen Kvalsvik, gcc-patches

Hello Jørgen,

thanks for the updated patch. I used it for a test suite run and the 
results look quite good.

Could you please add this hunk to your patch set:

diff --git a/libgcc/libgcov-merge.c b/libgcc/libgcov-merge.c
index 89741f637e1..9e3e8ee5657 100644
--- a/libgcc/libgcov-merge.c
+++ b/libgcc/libgcov-merge.c
@@ -33,6 +33,11 @@ void __gcov_merge_add (gcov_type *counters 
__attribute__ ((unused)),
                         unsigned n_counters __attribute__ ((unused))) {}
  #endif

+#ifdef L_gcov_merge_ior
+void __gcov_merge_ior (gcov_type *counters  __attribute__ ((unused)),
+                      unsigned n_counters __attribute__ ((unused))) {}
+#endif
+
  #ifdef L_gcov_merge_topn
  void __gcov_merge_topn (gcov_type *counters  __attribute__ ((unused)),
                         unsigned n_counters __attribute__ ((unused))) {}

It is necessary to use gcov in freestanding environments (inhibit_libc 
is defined).

The condition profiling found one spot for which we have insufficient 
condition coverage:

function _Leap_year called 227 returned 100% blocks executed 100%
       227:   54:static bool _Leap_year(
         -:   55:  uint32_t year
         -:   56:)
         -:   57:{
       227:   58:  return (((year % 4) == 0) && ((year % 100) != 0)) || 
((year % 400) == 0);
branch  0 taken 19% (fallthrough)
branch  1 taken 81%
branch  2 taken 16% (fallthrough)
branch  3 taken 84%
branch  4 taken 4% (fallthrough)
branch  5 taken 96%
conditions covered 5/6
condition  1 not covered (false)
         -:   59:}

This is because we don't test with the year 2100 for example. This value 
would result in:

year % 4 == 0: true
year % 100 != 0: false
year % 400 == 0: false

It was not immediately clear to me what the

"conditions covered 5/6
condition  1 not covered (false)"

is supposed to tell me. I guess a reasonable interpretation is: 
condition 1 (which is "(year % 100) != 0" should be false and determine 
the outcome of the decision.

What could be a bit confusing is that we have "conditions covered 5/6", 
however, there are only three conditions (0: (year % 4) == 0, 1: (year % 
100) != 0, 2: (year % 400) == 0). Maybe it would be more clear if the 
report says "condition variants covered 5/6" or something like this.

-- 
embedded brains GmbH
Herr Sebastian HUBER
Dornierstr. 4
82178 Puchheim
Germany
email: sebastian.huber@embedded-brains.de
phone: +49-89-18 94 741 - 16
fax:   +49-89-18 94 741 - 08

Registergericht: Amtsgericht München
Registernummer: HRB 157899
Vertretungsberechtigte Geschäftsführer: Peter Rasmussen, Thomas Dörfler
Unsere Datenschutzerklärung finden Sie hier:
https://embedded-brains.de/datenschutzerklaerung/

^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH] Add condition coverage profiling
@ 2022-07-11 10:02 Jørgen Kvalsvik
  2022-07-12 14:05 ` Sebastian Huber
  0 siblings, 1 reply; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-07-11 10:02 UTC (permalink / raw)
  To: gcc-patches

This patch adds support in gcc+gcov for modified condition/decision
coverage (MC/DC) with the -fprofile-conditions flag. MC/DC is a type of
test/code coverage and it is particularly important in the avation and
automotive industries for safety-critical applications. MC/DC it is
required for or recommended by:

    * DO-178C for the most critical software (Level A) in avionics
    * IEC 61508 for SIL 4
    * ISO 26262-6 for ASIL D

From the SQLite webpage:

    Two methods of measuring test coverage were described above:
    "statement" and "branch" coverage. There are many other test
    coverage metrics besides these two. Another popular metric is
    "Modified Condition/Decision Coverage" or MC/DC. Wikipedia defines
    MC/DC as follows:

        * Each decision tries every possible outcome.
        * Each condition in a decision takes on every possible outcome.
        * Each entry and exit point is invoked.
        * Each condition in a decision is shown to independently affect
          the outcome of the decision.

    In the C programming language where && and || are "short-circuit"
    operators, MC/DC and branch coverage are very nearly the same thing.
    The primary difference is in boolean vector tests. One can test for
    any of several bits in bit-vector and still obtain 100% branch test
    coverage even though the second element of MC/DC - the requirement
    that each condition in a decision take on every possible outcome -
    might not be satisfied.

    https://sqlite.org/testing.html#mcdc

Wahlen, Heimdahl, and De Silva "Efficient Test Coverage Measurement for
MC/DC" describes an algorithm for adding instrumentation by carrying
over information from the AST, but my algorithm analyses the the control
flow graph to instrument for coverage. This has the benefit of being
programming language independent and faithful to compiler decisions
and transformations, although I have only tested it on constructs in C
and C++, see testsuite/gcc.misc-tests and testsuite/g++.dg.

Like Wahlen et al this implementation records coverage in fixed-size
bitsets which gcov knows how to interpret. This is very fast, but
introduces a limit on the number of terms in a single boolean
expression, the number of bits in a gcov_unsigned_type (which is
typedef'd to uint64_t), so for most practical purposes this would be
acceptable. This limitation is in the implementation and not the
algorithm, so support for more conditions can be added by also
introducing arbitrary-sized bitsets.

For space overhead, the instrumentation needs two accumulators
(gcov_unsigned_type) per condition in the program which will be written
to the gcov file. In addition, every function gets a pair of local
accumulators, but these accmulators are reused between conditions in the
same function.

For time overhead, there is a zeroing of the local accumulators for
every condition and one or two bitwise operation on every edge taken in
the an expression.

In action it looks pretty similar to the branch coverage. The -g short
opt carries no significance, but was chosen because it was an available
option with the upper-case free too.

gcov --conditions:

        3:   17:void fn (int a, int b, int c, int d) {
        3:   18:    if ((a && (b || c)) && d)
conditions covered 3/8
condition  0 not covered (true)
condition  0 not covered (false)
condition  1 not covered (true)
condition  2 not covered (true)
condition  3 not covered (true)
        1:   19:        x = 1;
        -:   20:    else
        2:   21:        x = 2;
        3:   22:}

gcov --conditions --json-format:

    "conditions": [
        {
            "not_covered_false": [
                0
            ],
            "count": 8,
            "covered": 3,
            "not_covered_true": [
                0,
                1,
                2,
                3
            ]
        }
    ],

Some expressions, mostly those without else-blocks, are effectively
"rewritten" in the CFG construction making the algorithm unable to
distinguish them:

and.c:

    if (a && b && c)
        x = 1;

ifs.c:

    if (a)
        if (b)
            if (c)
                x = 1;

gcc will build the same graph for both these programs, and gcov will
report boths as 3-term expressions. It is vital that it is not
interpreted the other way around (which is consistent with the shape of
the graph) because otherwise the masking would be wrong for the and.c
program which is a more severe error. While surprising, users would
probably expect some minor rewriting of semantically-identical
expressions.

and.c.gcov:
    #####:    2:    if (a && b && c)
conditions covered 6/6
    #####:    3:        x = 1;

ifs.c.gcov:
    #####:    2:    if (a)
    #####:    3:        if (b)
    #####:    4:            if (c)
    #####:    5:                x = 1;
conditions covered 6/6

Adding else clauses alters the program (ifs.c can have 3 elses, and.c
only 1) and coverage becomes less surprising

ifs.c.gcov:
    #####:    2:    if (a)
conditions covered 2/2
    #####:    4:    {
    #####:    4:        if (b)
conditions covered 2/2
              5:        {
    #####:    6:            if (c)
conditions covered 2/2
    #####:    7:                x = 1;
    #####:    8:        }
    #####:    9:        else
    #####:   10:            x = 2;
    #####:   11:    }
    #####:   12:    else
    #####:   13:        x = 3;

Since the algorithm works on CFGs, it cannot detect some ternary
operator introduced conditionals. For example, int x = a ? 0 : 1 in
gimple becomes _x = (_a == 0). From source you would expect coverage,
but it gets neither branch nor condition coverage. For completeness, it
could be achieved by scanning all gimple statements for such
comparisons, and insert an extra instruction for recording the outcome.

The test suite contains a lot of small programs functions. Some of these
were designed by hand to test for specific behaviours and graph shapes,
and some are previously-failed test cases in other programs adapted into
the test suite.

Alternative author email: Jørgen Kvalsvik <j@lambda.is>

gcc/ChangeLog:

	* builtins.cc (expand_builtin_fork_or_exec): Check
	profile_condition_flag.
        * collect2.cc (main): Add -fno-profile-conditions to OBSTACK.
	* common.opt: Add new options -fprofile-conditions and
	* doc/gcov.texi: Add --conditions documentation.
	* doc/invoke.texi: Add -fprofile-conditions documentation.
	* gcc.cc: Link gcov on -fprofile-conditions.
	* gcov-counter.def (GCOV_COUNTER_CONDS): New.
	* gcov-dump.cc (tag_conditions): New.
	* gcov-io.h (GCOV_TAG_CONDS): New.
	(GCOV_TAG_CONDS_LENGTH): Likewise.
	(GCOV_TAG_CONDS_NUM): Likewise.
	* gcov.cc (class condition_info): New.
	(condition_info::condition_info): New.
	(condition_info::popcount): New.
	(struct coverage_info): New.
	(add_condition_counts): New.
	(output_conditions): New.
	(print_usage): Add -g, --conditions.
	(process_args): Likewise.
	(output_intermediate_json_line): Output conditions.
	(read_graph_file): Read conditions counters.
	(read_count_file): Read conditions counters.
	(file_summary): Print conditions.
	(accumulate_line_info): Accumulate conditions.
	(output_line_details): Print conditions.
	* ipa-inline.cc (can_early_inline_edge_p): Check
	profile_condition_flag.
	* ipa-split.cc (pass_split_functions::gate): Likewise.
	* passes.cc (finish_optimization_passes): Likewise.
	* profile.cc (find_conditions): New declaration.
	(cov_length): Likewise.
	(cov_blocks): Likewise.
	(cov_masks): Likewise.
	(cov_free): Likewise.
	(instrument_decisions): New.
	(read_thunk_profile): Control output to file.
	(branch_prob): Call find_conditions, instrument_decisions.
	(init_branch_prob): Add total_num_conds.
	(end_branch_prob): Likewise.
	* tree-profile.cc (struct conds_ctx): New.
	(CONDITIONS_MAX_TERMS): New.
	(EDGE_CONDITION): New.
	(cmp_index_map): New.
	(index_of): New.
	(block_conditional_p): New.
	(edge_conditional_p): New.
	(single): New.
	(single_edge): New.
	(contract_edge): New.
	(contract_edge_up): New.
	(ancestors_of): New.
	(struct outcomes): New.
	(conditional_succs): New.
	(condition_index): New.
	(masking_vectors): New.
	(cond_reachable_from): New.
	(neighborhood): New.
	(isolate_expression): New.
	(emit_bitwise_op): New.
	(make_index_map_visit): New.
	(make_index_map): New.
	(collect_conditions): New.
	(yes): New.
	(struct condcov): New.
	(cov_length): New.
	(cov_blocks): New.
	(cov_masks): New.
	(cov_free): New.
	(find_conditions): New.
	(instrument_decisions): New.
	(tree_profiling): Check profile_condition_flag.
	(pass_ipa_tree_profile::gate): Likewise.

gcc/testsuite/ChangeLog:

	* lib/gcov.exp: Add condition coverage test function.
	* g++.dg/gcov/gcov-18.C: New test.
	* gcc.misc-tests/gcov-19.c: New test.
	* gcc.misc-tests/gcov-20.c: New test.
	* gcc.misc-tests/gcov-21.c: New test.
---
 gcc/builtins.cc                        |    2 +-
 gcc/collect2.cc                        |    5 +-
 gcc/common.opt                         |    8 +
 gcc/doc/gcov.texi                      |   36 +
 gcc/doc/invoke.texi                    |   19 +
 gcc/gcc.cc                             |    4 +-
 gcc/gcov-counter.def                   |    3 +
 gcc/gcov-dump.cc                       |   24 +
 gcc/gcov-io.h                          |    3 +
 gcc/gcov.cc                            |  198 ++++-
 gcc/ipa-inline.cc                      |    2 +-
 gcc/ipa-split.cc                       |    3 +-
 gcc/passes.cc                          |    3 +-
 gcc/profile.cc                         |   84 +-
 gcc/testsuite/g++.dg/gcov/gcov-18.C    |  234 +++++
 gcc/testsuite/gcc.misc-tests/gcov-19.c | 1137 ++++++++++++++++++++++++
 gcc/testsuite/gcc.misc-tests/gcov-20.c |   22 +
 gcc/testsuite/gcc.misc-tests/gcov-21.c |   16 +
 gcc/testsuite/lib/gcov.exp             |  187 +++-
 gcc/tree-profile.cc                    |  974 +++++++++++++++++++-
 20 files changed, 2937 insertions(+), 27 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-18.C
 create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-19.c
 create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-20.c
 create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-21.c

diff --git a/gcc/builtins.cc b/gcc/builtins.cc
index 35b9197945f..0cf32a9c443 100644
--- a/gcc/builtins.cc
+++ b/gcc/builtins.cc
@@ -5584,7 +5584,7 @@ expand_builtin_fork_or_exec (tree fn, tree exp, rtx target, int ignore)
   tree call;
 
   /* If we are not profiling, just call the function.  */
-  if (!profile_arc_flag)
+  if (!profile_arc_flag && !profile_condition_flag)
     return NULL_RTX;
 
   /* Otherwise call the wrapper.  This should be equivalent for the rest of
diff --git a/gcc/collect2.cc b/gcc/collect2.cc
index d81c7f28f16..7b0968d535d 100644
--- a/gcc/collect2.cc
+++ b/gcc/collect2.cc
@@ -1032,9 +1032,9 @@ main (int argc, char **argv)
       lto_mode = LTO_MODE_LTO;
   }
 
-  /* -fno-profile-arcs -fno-test-coverage -fno-branch-probabilities
+  /* -fno-profile-arcs -fno-profile-conditions -fno-test-coverage -fno-branch-probabilities
      -fno-exceptions -w -fno-whole-program */
-  num_c_args += 6;
+  num_c_args += 7;
 
   c_argv = XCNEWVEC (char *, num_c_args);
   c_ptr = CONST_CAST2 (const char **, char **, c_argv);
@@ -1230,6 +1230,7 @@ main (int argc, char **argv)
     }
   obstack_free (&temporary_obstack, temporary_firstobj);
   *c_ptr++ = "-fno-profile-arcs";
+  *c_ptr++ = "-fno-profile-conditions";
   *c_ptr++ = "-fno-test-coverage";
   *c_ptr++ = "-fno-branch-probabilities";
   *c_ptr++ = "-fno-exceptions";
diff --git a/gcc/common.opt b/gcc/common.opt
index e7a51e882ba..4da06767bc3 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -865,6 +865,10 @@ Wcoverage-invalid-line-number
 Common Var(warn_coverage_invalid_linenum) Init(1) Warning
 Warn in case a function ends earlier than it begins due to an invalid linenum macros.
 
+Wcoverage-too-many-conditions
+Common Var(warn_too_many_conditions) Init(1) Warning
+Warn when a conditional has too many terms and coverage gives up.
+
 Wmissing-profile
 Common Var(warn_missing_profile) Init(1) Warning
 Warn in case profiles in -fprofile-use do not exist.
@@ -2321,6 +2325,10 @@ fprofile-arcs
 Common Var(profile_arc_flag)
 Insert arc-based program profiling code.
 
+fprofile-conditions
+Common Var(profile_condition_flag)
+Insert condition coverage profiling code.
+
 fprofile-dir=
 Common Joined RejectNegative Var(profile_data_prefix)
 Set the top-level directory for storing the profile data.
diff --git a/gcc/doc/gcov.texi b/gcc/doc/gcov.texi
index a1f7d26e610..7f2c0d00d94 100644
--- a/gcc/doc/gcov.texi
+++ b/gcc/doc/gcov.texi
@@ -124,6 +124,7 @@ gcov [@option{-v}|@option{--version}] [@option{-h}|@option{--help}]
      [@option{-a}|@option{--all-blocks}]
      [@option{-b}|@option{--branch-probabilities}]
      [@option{-c}|@option{--branch-counts}]
+     [@option{-g}|@option{--conditions}]
      [@option{-d}|@option{--display-progress}]
      [@option{-f}|@option{--function-summaries}]
      [@option{-j}|@option{--json-format}]
@@ -169,6 +170,13 @@ be shown, unless the @option{-u} option is given.
 Write branch frequencies as the number of branches taken, rather than
 the percentage of branches taken.
 
+@item -g
+@itemx --conditions
+Write condition coverage to the output file, and write condition summary info
+to the standard output.  This option allows you to see if the conditions in
+your program at least once had an independent effect on the outcome of the
+boolean expression (modified condition/decision coverage).
+
 @item -d
 @itemx --display-progress
 Display the progress on the standard output.
@@ -293,6 +301,7 @@ Each @var{line} has the following form:
 @{
   "branches": ["$branch"],
   "count": 2,
+  "conditions": ["$condition"],
   "line_number": 15,
   "unexecuted_block": false,
   "function_name": "foo",
@@ -341,6 +350,33 @@ Fields of the @var{branch} element have following semantics:
 @var{throw}: true when the branch is an exceptional branch
 @end itemize
 
+Each @var{condition} element have the following semantics:
+
+@smallexample
+@{
+  "count": 4,
+  "covered": 2,
+  "not_covered_false": [],
+  "not_covered_true": [0, 1],
+@}
+@end smallexample
+
+Fields of the @var{condition} element have following semantics:
+
+@itemize @bullet
+@item
+@var{count}: number of conditions in this expression
+
+@item
+@var{covered}: number of covered conditions in this expression
+
+@item
+@var{not_covered_true}: terms, by index, not seen as true in this expression
+
+@item
+@var{not_covered_false}: terms, by index, not seen as false in this expression
+@end itemize
+
 @item -H
 @itemx --human-readable
 Write counts in human readable format (like 24.6k).
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index d5ff1018372..28cc9fa2316 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -602,6 +602,7 @@ Objective-C and Objective-C++ Dialects}.
 @item Program Instrumentation Options
 @xref{Instrumentation Options,,Program Instrumentation Options}.
 @gccoptlist{-p  -pg  -fprofile-arcs  --coverage  -ftest-coverage @gol
+-fprofile-conditions @gol
 -fprofile-abs-path @gol
 -fprofile-dir=@var{path}  -fprofile-generate  -fprofile-generate=@var{path} @gol
 -fprofile-info-section  -fprofile-info-section=@var{name} @gol
@@ -6088,6 +6089,13 @@ error.  @option{-Wno-coverage-invalid-line-number} can be used to disable the
 warning or @option{-Wno-error=coverage-invalid-line-number} can be used to
 disable the error.
 
+@item -Wno-coverage-too-many-conditions
+@opindex Wno-coverage-too-many-conditions
+@opindex Wcoverage-too-many-conditions
+Warn in case a condition have too many terms and GCC gives up coverage.
+Coverage is given up when there are more terms in the conditional than there
+are bits in a @code{gcov_type_unsigned}.  This warning is enabled by default.
+
 @item -Wno-cpp @r{(C, Objective-C, C++, Objective-C++ and Fortran only)}
 @opindex Wno-cpp
 @opindex Wcpp
@@ -15549,6 +15557,13 @@ E.g. @code{gcc a.c b.c -o binary} would generate @file{binary-a.gcda} and
 
 @xref{Cross-profiling}.
 
+@item -fprofile-conditions
+@opindex fprofile-conditions
+Add code so that program conditions are instrumented.  During execution the
+program records what terms in a conditional contributes to a decision.  The
+data may be used to verify that all terms in a booleans are tested and have an
+effect on the outcome of a condition.
+
 @cindex @command{gcov}
 @item --coverage
 @opindex coverage
@@ -15609,6 +15624,10 @@ executed.  When an arc is the only exit or only entrance to a block, the
 instrumentation code can be added to the block; otherwise, a new basic
 block must be created to hold the instrumentation code.
 
+With @option{-fprofile-conditions}, for each conditional in your program GCC
+creates a bitset and records the boolean values that have an independent effect
+on the outcome of that expression.
+
 @need 2000
 @item -ftest-coverage
 @opindex ftest-coverage
diff --git a/gcc/gcc.cc b/gcc/gcc.cc
index 5cbb38560b2..3c0c40bf388 100644
--- a/gcc/gcc.cc
+++ b/gcc/gcc.cc
@@ -1165,7 +1165,7 @@ proper position among the other output files.  */
 	%:include(libgomp.spec)%(link_gomp)}\
     %{fgnu-tm:%:include(libitm.spec)%(link_itm)}\
     %(mflib) " STACK_SPLIT_SPEC "\
-    %{fprofile-arcs|fprofile-generate*|coverage:-lgcov} " SANITIZER_SPEC " \
+    %{fprofile-arcs|fprofile-conditions|fprofile-generate*|coverage:-lgcov} " SANITIZER_SPEC " \
     %{!nostdlib:%{!r:%{!nodefaultlibs:%(link_ssp) %(link_gcc_c_sequence)}}}\
     %{!nostdlib:%{!r:%{!nostartfiles:%E}}} %{T*}  \n%(post_link) }}}}}}"
 #endif
@@ -1282,7 +1282,7 @@ static const char *cc1_options =
  %{!fsyntax-only:%{S:%W{o*}%{!o*:-o %w%b.s}}}\
  %{fsyntax-only:-o %j} %{-param*}\
  %{coverage:-fprofile-arcs -ftest-coverage}\
- %{fprofile-arcs|fprofile-generate*|coverage:\
+ %{fprofile-arcs|fprofile-conditions|fprofile-generate*|coverage:\
    %{!fprofile-update=single:\
      %{pthread:-fprofile-update=prefer-atomic}}}";
 
diff --git a/gcc/gcov-counter.def b/gcc/gcov-counter.def
index 6d2182bd3db..96563a59a45 100644
--- a/gcc/gcov-counter.def
+++ b/gcc/gcov-counter.def
@@ -49,3 +49,6 @@ DEF_GCOV_COUNTER(GCOV_COUNTER_IOR, "ior", _ior)
 
 /* Time profile collecting first run of a function */
 DEF_GCOV_COUNTER(GCOV_TIME_PROFILER, "time_profiler", _time_profile)
+
+/* Conditions.  The counter is interpreted as a bit-set.  */
+DEF_GCOV_COUNTER(GCOV_COUNTER_CONDS, "conditions", _ior)
diff --git a/gcc/gcov-dump.cc b/gcc/gcov-dump.cc
index 0804c794e9e..12830e5984e 100644
--- a/gcc/gcov-dump.cc
+++ b/gcc/gcov-dump.cc
@@ -35,6 +35,7 @@ static void print_version (void);
 static void tag_function (const char *, unsigned, int, unsigned);
 static void tag_blocks (const char *, unsigned, int, unsigned);
 static void tag_arcs (const char *, unsigned, int, unsigned);
+static void tag_conditions (const char *, unsigned, int, unsigned);
 static void tag_lines (const char *, unsigned, int, unsigned);
 static void tag_counters (const char *, unsigned, int, unsigned);
 static void tag_summary (const char *, unsigned, int, unsigned);
@@ -71,6 +72,7 @@ static const tag_format_t tag_table[] =
   {GCOV_TAG_FUNCTION, "FUNCTION", tag_function},
   {GCOV_TAG_BLOCKS, "BLOCKS", tag_blocks},
   {GCOV_TAG_ARCS, "ARCS", tag_arcs},
+  {GCOV_TAG_CONDS, "CONDITIONS", tag_conditions},
   {GCOV_TAG_LINES, "LINES", tag_lines},
   {GCOV_TAG_OBJECT_SUMMARY, "OBJECT_SUMMARY", tag_summary},
   {0, NULL, NULL}
@@ -381,6 +383,28 @@ tag_arcs (const char *filename ATTRIBUTE_UNUSED,
     }
 }
 
+static void
+tag_conditions (const char *filename ATTRIBUTE_UNUSED,
+		unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED,
+		unsigned depth)
+{
+  unsigned n_conditions = GCOV_TAG_CONDS_NUM (length);
+
+  printf (" %u conditionals", n_conditions);
+  if (flag_dump_contents)
+    {
+      for (unsigned ix = 0; ix != n_conditions; ix++)
+	{
+	  const unsigned blockno = gcov_read_unsigned ();
+	  const unsigned nterms = gcov_read_unsigned ();
+
+	  printf ("\n");
+	  print_prefix (filename, depth, gcov_position ());
+	  printf (VALUE_PADDING_PREFIX "block %u:", blockno);
+	  printf (" %u", nterms);
+	}
+    }
+}
 static void
 tag_lines (const char *filename ATTRIBUTE_UNUSED,
 	   unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED,
diff --git a/gcc/gcov-io.h b/gcc/gcov-io.h
index 30947634d73..eaf3f366f5d 100644
--- a/gcc/gcov-io.h
+++ b/gcc/gcov-io.h
@@ -261,6 +261,9 @@ typedef uint64_t gcov_type_unsigned;
 #define GCOV_TAG_ARCS		 ((gcov_unsigned_t)0x01430000)
 #define GCOV_TAG_ARCS_LENGTH(NUM)  (1 + (NUM) * 2 * GCOV_WORD_SIZE)
 #define GCOV_TAG_ARCS_NUM(LENGTH)  (((LENGTH / GCOV_WORD_SIZE) - 1) / 2)
+#define GCOV_TAG_CONDS             ((gcov_unsigned_t)0x01470000)
+#define GCOV_TAG_CONDS_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE)
+#define GCOV_TAG_CONDS_NUM(LENGTH) (((LENGTH) / GCOV_WORD_SIZE) / 2)
 #define GCOV_TAG_LINES		 ((gcov_unsigned_t)0x01450000)
 #define GCOV_TAG_COUNTER_BASE 	 ((gcov_unsigned_t)0x01a10000)
 #define GCOV_TAG_COUNTER_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE)
diff --git a/gcc/gcov.cc b/gcc/gcov.cc
index 27be5ff0911..680815f2f11 100644
--- a/gcc/gcov.cc
+++ b/gcc/gcov.cc
@@ -79,6 +79,7 @@ using namespace std;
 class function_info;
 class block_info;
 class source_info;
+class condition_info;
 
 /* Describes an arc between two basic blocks.  */
 
@@ -132,6 +133,28 @@ public:
   vector<unsigned> lines;
 };
 
+class condition_info
+{
+public:
+  condition_info ();
+
+  int popcount () const;
+
+  gcov_type_unsigned truev;
+  gcov_type_unsigned falsev;
+
+  unsigned n_terms;
+};
+
+condition_info::condition_info (): truev (0), falsev (0), n_terms (0)
+{
+}
+
+int condition_info::popcount () const
+{
+    return __builtin_popcountll (truev) + __builtin_popcountll (falsev);
+}
+
 /* Describes a basic block. Contains lists of arcs to successor and
    predecessor blocks.  */
 
@@ -165,6 +188,8 @@ public:
   /* Block is a landing pad for longjmp or throw.  */
   unsigned is_nonlocal_return : 1;
 
+  condition_info conditions;
+
   vector<block_location_info> locations;
 
   struct
@@ -275,6 +300,8 @@ public:
   vector<block_info> blocks;
   unsigned blocks_executed;
 
+  vector<condition_info*> conditions;
+
   /* Raw arc coverage counts.  */
   vector<gcov_type> counts;
 
@@ -351,6 +378,9 @@ struct coverage_info
   int branches_executed;
   int branches_taken;
 
+  int conditions;
+  int conditions_covered;
+
   int calls;
   int calls_executed;
 
@@ -550,6 +580,10 @@ static int multiple_files = 0;
 
 static int flag_branches = 0;
 
+/* Output conditions (modified condition/decision coverage) */
+
+static int flag_conditions = 0;
+
 /* Show unconditional branches too.  */
 static int flag_unconditional = 0;
 
@@ -656,6 +690,7 @@ static int read_count_file (void);
 static void solve_flow_graph (function_info *);
 static void find_exception_blocks (function_info *);
 static void add_branch_counts (coverage_info *, const arc_info *);
+static void add_condition_counts (coverage_info *, const block_info *);
 static void add_line_counts (coverage_info *, function_info *);
 static void executed_summary (unsigned, unsigned);
 static void function_summary (const coverage_info *);
@@ -664,6 +699,7 @@ static const char *format_gcov (gcov_type, gcov_type, int);
 static void accumulate_line_counts (source_info *);
 static void output_gcov_file (const char *, source_info *);
 static int output_branch_count (FILE *, int, const arc_info *);
+static void output_conditions (FILE *, const block_info *);
 static void output_lines (FILE *, const source_info *);
 static string make_gcov_file_name (const char *, const char *);
 static char *mangle_name (const char *);
@@ -928,6 +964,7 @@ print_usage (int error_p)
   fnotice (file, "  -b, --branch-probabilities      Include branch probabilities in output\n");
   fnotice (file, "  -c, --branch-counts             Output counts of branches taken\n\
                                     rather than percentages\n");
+  fnotice (file, "  -g, --conditions                Include condition coverage in output (MC/DC)\n");
   fnotice (file, "  -d, --display-progress          Display progress information\n");
   fnotice (file, "  -D, --debug			    Display debugging dumps\n");
   fnotice (file, "  -f, --function-summaries        Output summaries for each function\n");
@@ -980,6 +1017,7 @@ static const struct option options[] =
   { "all-blocks",           no_argument,       NULL, 'a' },
   { "branch-probabilities", no_argument,       NULL, 'b' },
   { "branch-counts",        no_argument,       NULL, 'c' },
+  { "conditions",           no_argument,       NULL, 'g' },
   { "json-format",	    no_argument,       NULL, 'j' },
   { "human-readable",	    no_argument,       NULL, 'H' },
   { "no-output",            no_argument,       NULL, 'n' },
@@ -1008,7 +1046,7 @@ process_args (int argc, char **argv)
 {
   int opt;
 
-  const char *opts = "abcdDfhHijklmno:pqrs:tuvwx";
+  const char *opts = "abcdDfghHijklmno:pqrs:tuvwx";
   while ((opt = getopt_long (argc, argv, opts, options, NULL)) != -1)
     {
       switch (opt)
@@ -1025,6 +1063,9 @@ process_args (int argc, char **argv)
 	case 'f':
 	  flag_function_summary = 1;
 	  break;
+	case 'g':
+	  flag_conditions = 1;
+	  break;
 	case 'h':
 	  print_usage (false);
 	  /* print_usage will exit.  */
@@ -1132,6 +1173,45 @@ output_intermediate_json_line (json::array *object,
 	  }
       }
 
+  json::array *conditions = new json::array ();
+  lineo->set ("conditions", conditions);
+  if (flag_conditions)
+  {
+    vector<block_info *>::const_iterator it;
+    for (it = line->blocks.begin (); it != line->blocks.end (); it++)
+      {
+	const condition_info& info = (*it)->conditions;
+	if (info.n_terms == 0)
+	    continue;
+
+	const int count = 2 * info.n_terms;
+	const int covered = info.popcount ();
+
+	json::object *cond = new json::object ();
+	cond->set ("count", new json::integer_number (count));
+	cond->set ("covered", new json::integer_number (covered));
+
+	json::array *mtrue = new json::array ();
+	json::array *mfalse = new json::array ();
+	cond->set ("not_covered_true", mtrue);
+	cond->set ("not_covered_false", mfalse);
+
+	if (count != covered)
+	  {
+	    for (unsigned i = 0; i < info.n_terms; i++)
+	      {
+		gcov_type_unsigned index = 1;
+		index <<= i;
+		if (!(index & info.truev))
+		    mtrue->append (new json::integer_number (i));
+		if (!(index & info.falsev))
+		    mfalse->append (new json::integer_number (i));
+	      }
+	  }
+	conditions->append (cond);
+      }
+  }
+
   object->append (lineo);
 }
 
@@ -1956,6 +2036,28 @@ read_graph_file (void)
 		  }
 	    }
 	}
+      else if (fn && tag == GCOV_TAG_CONDS)
+	{
+	  unsigned num_dests = GCOV_TAG_CONDS_NUM (length);
+
+	  if (!fn->conditions.empty ())
+	    fnotice (stderr, "%s:already seen conditions for '%s'\n",
+		     bbg_file_name, fn->get_name ());
+	  else
+	    fn->conditions.resize (num_dests);
+
+	  for (unsigned i = 0; i < num_dests; ++i)
+	    {
+	      unsigned idx = gcov_read_unsigned ();
+
+	      if (idx >= fn->blocks.size ())
+		goto corrupt;
+
+	      condition_info *info = &fn->blocks[idx].conditions;
+	      info->n_terms = gcov_read_unsigned ();
+	      fn->conditions[i] = info;
+	    }
+	}
       else if (fn && tag == GCOV_TAG_LINES)
 	{
 	  unsigned blockno = gcov_read_unsigned ();
@@ -2086,11 +2188,26 @@ read_count_file (void)
 	      goto cleanup;
 	    }
 	}
-      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn)
+      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_CONDS) && fn)
 	{
+	  length = abs (read_length);
+	    if (length != GCOV_TAG_COUNTER_LENGTH (2 * fn->conditions.size ()))
+		goto mismatch;
+
+	    if (read_length > 0)
+	      {
+		for (ix = 0; ix != fn->conditions.size (); ix++)
+		{
+		    fn->conditions[ix]->truev  |= gcov_read_counter ();
+		    fn->conditions[ix]->falsev |= gcov_read_counter ();
+		}
+	      }
+	}
+      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn)
+      {
 	  length = abs (read_length);
 	  if (length != GCOV_TAG_COUNTER_LENGTH (fn->counts.size ()))
-	    goto mismatch;
+	      goto mismatch;
 
 	  if (read_length > 0)
 	    for (ix = 0; ix != fn->counts.size (); ix++)
@@ -2430,6 +2547,13 @@ add_branch_counts (coverage_info *coverage, const arc_info *arc)
     }
 }
 
+static void
+add_condition_counts (coverage_info *coverage, const block_info *block)
+{
+  coverage->conditions += 2 * block->conditions.n_terms;
+  coverage->conditions_covered += block->conditions.popcount ();
+}
+
 /* Format COUNT, if flag_human_readable_numbers is set, return it human
    readable format.  */
 
@@ -2533,6 +2657,18 @@ file_summary (const coverage_info *coverage)
 		 coverage->calls);
       else
 	fnotice (stdout, "No calls\n");
+
+    }
+
+  if (flag_conditions)
+    {
+      if (coverage->conditions)
+	fnotice (stdout, "Conditions covered:%s of %d\n",
+		 format_gcov (coverage->conditions_covered,
+			      coverage->conditions, 2),
+		 coverage->conditions);
+      else
+	fnotice (stdout, "No conditions\n");
     }
 }
 
@@ -2760,6 +2896,12 @@ static void accumulate_line_info (line_info *line, source_info *src,
 	 it != line->branches.end (); it++)
       add_branch_counts (&src->coverage, *it);
 
+  if (add_coverage)
+    for (vector<block_info *>::iterator it = line->blocks.begin ();
+	 it != line->blocks.end (); it++)
+      add_condition_counts (&src->coverage, *it);
+
+
   if (!line->blocks.empty ())
     {
       /* The user expects the line count to be the number of times
@@ -2861,6 +3003,31 @@ accumulate_line_counts (source_info *src)
       }
 }
 
+static void
+output_conditions (FILE *gcov_file, const block_info *binfo)
+{
+    const condition_info& info = binfo->conditions;
+    if (info.n_terms == 0)
+	return;
+
+    const int expected = 2 * info.n_terms;
+    const int got = info.popcount ();
+
+    fnotice (gcov_file, "conditions covered %d/%d\n", got, expected);
+    if (expected == got)
+	return;
+
+    for (unsigned i = 0; i < info.n_terms; i++)
+    {
+	gcov_type_unsigned index = 1;
+	index <<= i;
+	if (!(index & info.truev))
+	    fnotice (gcov_file, "condition %2u not covered (true)\n", i);
+	if (!(index & info.falsev))
+	    fnotice (gcov_file, "condition %2u not covered (false)\n", i);
+    }
+}
+
 /* Output information about ARC number IX.  Returns nonzero if
    anything is output.  */
 
@@ -3071,16 +3238,29 @@ output_line_details (FILE *f, const line_info *line, unsigned line_num)
 	  if (flag_branches)
 	    for (arc = (*it)->succ; arc; arc = arc->succ_next)
 	      jx += output_branch_count (f, jx, arc);
+
+	  if (flag_conditions)
+	      output_conditions (f, *it);
 	}
     }
-  else if (flag_branches)
+  else
     {
-      int ix;
+      if (flag_branches)
+	{
+	  int ix;
+
+	  ix = 0;
+	  for (vector<arc_info *>::const_iterator it = line->branches.begin ();
+		  it != line->branches.end (); it++)
+	      ix += output_branch_count (f, ix, (*it));
+	}
 
-      ix = 0;
-      for (vector<arc_info *>::const_iterator it = line->branches.begin ();
-	   it != line->branches.end (); it++)
-	ix += output_branch_count (f, ix, (*it));
+      if (flag_conditions)
+	{
+	  for (vector<block_info *>::const_iterator it = line->blocks.begin ();
+	       it != line->blocks.end (); it++)
+	      output_conditions (f, *it);
+	}
     }
 }
 
diff --git a/gcc/ipa-inline.cc b/gcc/ipa-inline.cc
index 14969198cde..3e37305843e 100644
--- a/gcc/ipa-inline.cc
+++ b/gcc/ipa-inline.cc
@@ -646,7 +646,7 @@ can_early_inline_edge_p (struct cgraph_edge *e)
 			 "  edge not inlinable: not in SSA form\n");
       return false;
     }
-  else if (profile_arc_flag
+  else if ((profile_arc_flag || profile_condition_flag)
 	   && ((lookup_attribute ("no_profile_instrument_function",
 				 DECL_ATTRIBUTES (caller->decl)) == NULL_TREE)
 	       != (lookup_attribute ("no_profile_instrument_function",
diff --git a/gcc/ipa-split.cc b/gcc/ipa-split.cc
index 16734617d03..07d2b17ab12 100644
--- a/gcc/ipa-split.cc
+++ b/gcc/ipa-split.cc
@@ -1929,7 +1929,8 @@ pass_split_functions::gate (function *)
   /* When doing profile feedback, we want to execute the pass after profiling
      is read.  So disable one in early optimization.  */
   return (flag_partial_inlining
-	  && !profile_arc_flag && !flag_branch_probabilities);
+      && !profile_arc_flag && !flag_branch_probabilities
+      && !profile_condition_flag);
 }
 
 } // anon namespace
diff --git a/gcc/passes.cc b/gcc/passes.cc
index 78a07f8691a..3457ad1c5b3 100644
--- a/gcc/passes.cc
+++ b/gcc/passes.cc
@@ -352,7 +352,8 @@ finish_optimization_passes (void)
   gcc::dump_manager *dumps = m_ctxt->get_dumps ();
 
   timevar_push (TV_DUMP);
-  if (profile_arc_flag || flag_test_coverage || flag_branch_probabilities)
+  if (profile_arc_flag || profile_condition_flag || flag_test_coverage
+      || flag_branch_probabilities)
     {
       dumps->dump_start (pass_profile_1->static_pass_number, NULL);
       end_branch_prob ();
diff --git a/gcc/profile.cc b/gcc/profile.cc
index 08af512cbca..a4751279571 100644
--- a/gcc/profile.cc
+++ b/gcc/profile.cc
@@ -66,9 +66,19 @@ along with GCC; see the file COPYING3.  If not see
 #include "cfgloop.h"
 #include "sreal.h"
 #include "file-prefix-map.h"
+#include "stringpool.h"
 
 #include "profile.h"
 
+struct condcov;
+struct condcov *find_conditions (struct function*);
+unsigned cov_length (const struct condcov*);
+array_slice<basic_block> cov_blocks (struct condcov*, unsigned);
+array_slice<gcov_type_unsigned > cov_masks (struct condcov*, unsigned);
+void cov_free (struct condcov*);
+int instrument_decisions (array_slice<basic_block>, unsigned, tree*,
+			  gcov_type_unsigned*);
+
 /* Map from BBs/edges to gcov counters.  */
 vec<gcov_type> bb_gcov_counts;
 hash_map<edge,gcov_type> *edge_gcov_counts;
@@ -100,6 +110,7 @@ static int total_num_passes;
 static int total_num_times_called;
 static int total_hist_br_prob[20];
 static int total_num_branches;
+static int total_num_conds;
 
 /* Forward declarations.  */
 static void find_spanning_tree (struct edge_list *);
@@ -1154,6 +1165,12 @@ read_thunk_profile (struct cgraph_node *node)
    the flow graph that are needed to reconstruct the dynamic behavior of the
    flow graph.  This data is written to the gcno file for gcov.
 
+   When FLAG_PROFILE_CONDITIONS is nonzero, this functions instruments the
+   edges in the control flow graph to track what conditions are evaluated to in
+   order to determine what conditions are covered and have an independent
+   effect on the outcome (modified condition/decision coverage).  This data is
+   written to the gcno file for gcov.
+
    When FLAG_BRANCH_PROBABILITIES is nonzero, this function reads auxiliary
    information from the gcda file containing edge count information from
    previous executions of the function being compiled.  In this case, the
@@ -1172,6 +1189,7 @@ branch_prob (bool thunk)
   struct edge_list *el;
   histogram_values values = histogram_values ();
   unsigned cfg_checksum, lineno_checksum;
+  bool output_to_file;
 
   total_num_times_called++;
 
@@ -1396,10 +1414,18 @@ branch_prob (bool thunk)
 
   /* Write the data from which gcov can reconstruct the basic block
      graph and function line numbers (the gcno file).  */
+  output_to_file = false;
   if (coverage_begin_function (lineno_checksum, cfg_checksum))
     {
       gcov_position_t offset;
 
+      /* The condition coverage needs a deeper analysis to identify expressions
+       * of conditions, which means it is not yet ready to write to the gcno
+       * file.  It will write its entries later, but needs to know if it do it
+       * in the first place, which is controlled by the return value of
+       * coverage_begin_function. */
+      output_to_file = true;
+
       /* Basic block flags */
       offset = gcov_write_tag (GCOV_TAG_BLOCKS);
       gcov_write_unsigned (n_basic_blocks_for_fn (cfun));
@@ -1511,29 +1537,74 @@ branch_prob (bool thunk)
 
   remove_fake_edges ();
 
+  if (profile_condition_flag || profile_arc_flag)
+      gimple_init_gcov_profiler ();
+
+  if (profile_condition_flag)
+    {
+      struct condcov *cov = find_conditions (cfun);
+      gcc_assert (cov);
+      const unsigned nconds = cov_length (cov);
+      total_num_conds += nconds;
+
+      if (coverage_counter_alloc (GCOV_COUNTER_CONDS, 2 * nconds))
+	{
+	  /* Add two extra variables to the function for the local
+	     accumulators, which are zero'd on the entry of a new conditional.
+	     The local accumulators are shared between decisions in order to
+	     use less stack space. */
+	  tree accu[2] = {
+	    build_decl (UNKNOWN_LOCATION, VAR_DECL,
+			get_identifier ("__accu_t"), get_gcov_type ()),
+	    build_decl (UNKNOWN_LOCATION, VAR_DECL,
+			get_identifier ("__accu_f"), get_gcov_type ()),
+	  };
+
+	  gcov_position_t offset {};
+	  if (output_to_file)
+	      offset = gcov_write_tag (GCOV_TAG_CONDS);
+
+	  for (unsigned i = 0; i < nconds; ++i)
+	    {
+	      array_slice<basic_block> expr = cov_blocks (cov, i);
+	      array_slice<gcov_type_unsigned> masks = cov_masks (cov, i);
+	      gcc_assert (expr.is_valid ());
+	      gcc_assert (masks.is_valid ());
+
+	      int terms = instrument_decisions (expr, i, accu, masks.begin ());
+	      if (output_to_file)
+		{
+		  gcov_write_unsigned (expr.front ()->index);
+		  gcov_write_unsigned (terms);
+		}
+	    }
+	  if (output_to_file)
+	      gcov_write_length (offset);
+	}
+      cov_free (cov);
+    }
+
   /* For each edge not on the spanning tree, add counting code.  */
   if (profile_arc_flag
       && coverage_counter_alloc (GCOV_COUNTER_ARCS, num_instrumented))
     {
       unsigned n_instrumented;
 
-      gimple_init_gcov_profiler ();
-
       n_instrumented = instrument_edges (el);
 
       gcc_assert (n_instrumented == num_instrumented);
 
       if (flag_profile_values)
 	instrument_values (values);
-
-      /* Commit changes done by instrumentation.  */
-      gsi_commit_edge_inserts ();
     }
 
   free_aux_for_edges ();
 
   values.release ();
   free_edge_list (el);
+  /* Commit changes done by instrumentation.  */
+  gsi_commit_edge_inserts ();
+
   coverage_end_function (lineno_checksum, cfg_checksum);
   if (flag_branch_probabilities
       && (profile_status_for_fn (cfun) == PROFILE_READ))
@@ -1663,6 +1734,7 @@ init_branch_prob (void)
   total_num_passes = 0;
   total_num_times_called = 0;
   total_num_branches = 0;
+  total_num_conds = 0;
   for (i = 0; i < 20; i++)
     total_hist_br_prob[i] = 0;
 }
@@ -1702,5 +1774,7 @@ end_branch_prob (void)
 		     (total_hist_br_prob[i] + total_hist_br_prob[19-i]) * 100
 		     / total_num_branches, 5*i, 5*i+5);
 	}
+      fprintf (dump_file, "Total number of conditions: %d\n",
+	       total_num_conds);
     }
 }
diff --git a/gcc/testsuite/g++.dg/gcov/gcov-18.C b/gcc/testsuite/g++.dg/gcov/gcov-18.C
new file mode 100644
index 00000000000..310ed5297c0
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gcov/gcov-18.C
@@ -0,0 +1,234 @@
+/* { dg-options "--coverage -fprofile-conditions -std=c++11" } */
+/* { dg-do run { target native } } */
+
+#include <vector>
+#include <stdexcept>
+
+class nontrivial_destructor
+{
+public:
+    explicit nontrivial_destructor (int v) : val (v) {}
+    ~nontrivial_destructor () {}
+
+    explicit operator bool() const { return bool(val); }
+
+    int val;
+};
+
+int identity (int x) { return x; }
+int throws (int) { throw std::runtime_error("exception"); }
+
+int throw_if (int x)
+{
+    if (x) /* conditions(1/2) true(0) */
+	   /* conditions(end) */
+	throw std::runtime_error("exception");
+    return x;
+}
+
+/* used for side effects to insert nodes in conditional bodies etc. */
+int x = 0;
+
+/* conditionals work in the presence of non-trivial destructors */
+void mcdc001a (int a)
+{
+    nontrivial_destructor v (a);
+
+    if (v.val > 0) /* conditions(2/2) */
+	x = v.val;
+    else
+	x = -v.val;
+}
+
+/* non-trivial destructor in-loop temporary */
+nontrivial_destructor
+mcdc002a (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+	nontrivial_destructor tmp (a);
+	if (tmp.val % b) /* conditions(2/2) */
+	    return nontrivial_destructor (0);
+	x += i;
+    } /* conditions(suppress) */
+      /* conditions(end) */
+
+    return nontrivial_destructor (a * b);
+}
+
+/* conditional in constructor */
+void mcdc003a (int a)
+{
+    class C
+    {
+    public:
+	explicit C (int e) : v (e)
+	{
+	    if (e) /* conditions(1/2) false(0) */
+		v = x - e;
+	}
+	int v;
+    };
+
+    C c (a);
+    if (c.v > 2) /* conditions(1/2) true(0) */
+		 /* conditions(end) */
+	x = c.v + a;
+}
+
+/* conditional in destructor */
+void mcdc004a (int a)
+{
+    class C
+    {
+    public:
+	explicit C (int e) : v (e) {}
+	~C ()
+	{
+	    if (v) /* conditions(2/2) */
+		x = 2 * v;
+	}
+	int v;
+    };
+
+    C c (a);
+    x = 1; // arbitrary action between ctor+dtor
+}
+
+/* conditional in try */
+void mcdc005a (int a)
+{
+    try
+    {
+	if (a) /* conditions(1/2) true(0) */
+	       /* conditions(end) */
+	    x = 2 * identity (a);
+	else
+	    x = 1;
+    }
+    catch (...)
+    {
+	x = 0;
+    }
+}
+
+/* conditional in catch */
+void mcdc006a (int a) {
+    try
+    {
+	throws (a);
+    }
+    catch (std::exception&)
+    {
+	if (a) /* conditions(1/2) false(0) */
+	       /* conditions(end) */
+	    x = identity (a);
+	else
+	    x = 0;
+    }
+}
+
+void mcdc006b (int a)
+{
+    if (a) /* conditions(1/2) true(0) */
+	   /* conditions(end) */
+	throws (a);
+    else
+	x = 1;
+}
+
+void mcdc006c (int a) try
+{
+    throws (a);
+}
+catch (...) {
+    if (a) /* conditions(2/2) */
+	x = 5;
+}
+
+/* temporary with destructor as term */
+void mcdc007a (int a, int b)
+{
+    x = a && nontrivial_destructor (b); /* conditions(3/4) false(1) destructor() */
+}
+
+void mcdc007b (int a, int b)
+{
+    if (a || throw_if (b)) /* conditions(3/4) true(1) destructor() */
+	x = -1;
+    else
+	x = 1;
+}
+
+void mcdc007c (int a, int b)
+{
+    if (throw_if (a) || throw_if (b)) /* conditions(2/4) true(0 1) destructor() */
+	x = -1;
+    else
+	x = 1;
+}
+
+/* destructor with delete */
+void mcdc008a (int a)
+{
+    class C
+    {
+    public:
+        int size = 5;
+        int* ptr = nullptr;
+
+        explicit C (int v) : size (v + 5), ptr (new int[size]) /* conditions(suppress) */
+							       /* conditions(end) */
+        {
+	    for (int i = 0; i < size; i++) /* conditions(2/2) */
+		ptr[i] = i + 1;
+        }
+        ~C()
+        {
+	    // delete with implicit nullptr check
+	    delete ptr; /* conditions(1/2) false(0) */
+			/* conditions(end) */
+	}
+    };
+
+    C c (a);
+    if (c.ptr[a + 1]) /* conditions(1/2) false(0) */
+	x = a;
+}
+
+int
+main (void)
+{
+    mcdc001a (0);
+    mcdc001a (1);
+
+    mcdc002a (1, 1);
+    mcdc002a (1, 2);
+
+    mcdc003a (1);
+
+    mcdc004a (0);
+    mcdc004a (1);
+
+    mcdc005a (0);
+
+    mcdc006a (1);
+
+    mcdc006b (0);
+
+    mcdc006c (0);
+    mcdc006c (1);
+
+    mcdc007a (0, 0);
+    mcdc007a (1, 1);
+
+    mcdc007b (0, 0);
+    mcdc007b (1, 0);
+
+    mcdc007c (0, 0);
+
+    mcdc008a (1);
+
+}
+
+/* { dg-final { run-gcov conditions { --conditions gcov-18.C } } } */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-19.c b/gcc/testsuite/gcc.misc-tests/gcov-19.c
new file mode 100644
index 00000000000..fb3be7dac76
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-19.c
@@ -0,0 +1,1137 @@
+/* { dg-options "-fprofile-conditions -ftest-coverage" } */
+/* { dg-do run { target native } } */
+
+/* some side effect to stop branches from being pruned */
+int x = 0;
+
+/* || works */
+void
+mcdc001a (int a, int b)
+{
+    if (a || b) /* conditions(1/4) true(0) false(0 1) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc001b (int a, int b)
+{
+    if (a || b) /* conditions(3/4) true(0) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc001c (int a, int b)
+{
+    if (a || b) /* conditions(4/4) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc001d (int a, int b, int c)
+{
+    if (a || b || c) /* conditions(2/6) false(0 1 2) true(2) */
+		     /* conditions(end) */
+	x = 1;
+}
+
+/* && works */
+void
+mcdc002a (int a, int b)
+{
+    if (a && b) /* conditions(1/4) true(0 1) false(0) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc002b (int a, int b)
+{
+    if (a && b) /* conditions(3/4) false(0) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc002c (int a, int b)
+{
+    if (a && b) /* conditions(4/4) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc002d (int a, int b, int c)
+{
+    if (a && b && c) /* conditions(4/6) false(0 2) */
+		     /* conditions(end) */
+	x = 1;
+}
+
+/* negation works */
+void
+mcdc003a (int a, int b)
+{
+    if (!a || !b) /* conditions(2/4) false(0 1) */
+		  /* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+/* single conditionals with and without else */
+void
+mcdc004a (int a)
+{
+    if (a) /* conditions(1/2) true(0) */
+	   /* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc004b (int a)
+{
+    if (a) /* conditions(2/2) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc004c (int a)
+{
+    if (a) /* conditions(1/2) false(0) */
+	   /* conditions(end) */
+	x = 1;
+}
+
+void
+mcdc004d (int a, int b, int c)
+{
+    /* With no else this is interpreted as (a && (b || c)) */
+    if (a)  /* conditions(3/6) true(2) false(1 2)*/
+    {
+	if (b || c)
+	    x = a + b + c;
+    }
+}
+
+void
+mcdc004e (int a, int b, int c)
+{
+    /* With the else, this is interpreted as 2 expressions */
+    if (a)  /* conditions(2/2) */
+    {
+	if (b || c) /* conditions(1/4) true(1) false(0 1) */
+	    x = a + b + c;
+    }
+    else
+    {
+	x = c;
+    }
+}
+
+/* mixing && and || works */
+void
+mcdc005a (int a, int b, int c)
+{
+    if ((a && b) || c) /* conditions(1/6) true(0 1) false(0 1 2) */
+		       /* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc005b (int a, int b, int c, int d)
+{
+    /* This is where masking MC/DC gets unintuitive:
+
+       1 1 0 0 => covers 1 (d = 0) as && 0 masks everything to the left
+       1 0 0 0 => covers 2 (b = 0, c = 0) as (a && 0) masks a and d is never
+       evaluated. */
+    if ((a && (b || c)) && d) /* conditions(3/8) true(0 1 2 3) false(0) */
+			      /* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc005c (int a, int b, int c, int d)
+{
+    if (a || (b && c) || d) /* conditions(2/8) true(0 3) false(0 1 2 3) */
+			    /* conditions(end) */
+        x = a + b + c + d;
+}
+
+void
+mcdc005d (int a, int b, int c, int d)
+{
+    /* This test is quite significant - it has a single input
+       (1, 0, 0, 0) and tests specifically for when a multi-term left operand
+       is masked. d = 0 should mask a || b and for the input there are no other
+       sources for masking a (since b = 0). */
+    if ((a || b) && (c || d)) /* conditions(2/8) true(0 1 2 3) false(0 1) */
+			      /* conditions(end) */
+	x = a + b;
+    else
+	x = c + d;
+}
+
+/* nested conditionals */
+void
+mcdc006a (int a, int b, int c, int d, int e)
+{
+    if (a) /* conditions(2/2) */
+    {
+	if (b && c) /* conditions(3/4) false(1) */
+		    /* conditions(end) */
+	    x = 1;
+	else
+	    x = 2;
+    }
+    else
+    {
+	if (c || d) /* conditions(2/4) true(0 1) */
+		    /* conditions(end) */
+	    x = 3;
+	else
+	    x = 4;
+    }
+}
+
+void
+mcdc006b (int a, int b, int c)
+{
+    if (a) /* conditions(6/6) */
+	if (b)
+	    if (c)
+		x = a + b + c;
+}
+
+void
+mcdc006c (int a, int b, int c)
+{
+    if (a) /* conditions(2/2) */
+    {
+	if (b) /*conditions(2/2) */
+	{
+	    if (c) /* conditions(2/2) */
+	    {
+		x = a + b + c;
+	    }
+	}
+	else
+	{
+	    x = b;
+	}
+    }
+    else
+    {
+	x = a;
+    }
+}
+
+/* else/if */
+void
+mcdc007a (int a, int b, int c, int d)
+{
+    if (a) /* conditions(2/2) */
+    {
+	if (b) /* conditions(1/2) true(0) */
+	       /* conditions(end) */
+	    x = 1;
+	else
+	    x = 2;
+    }
+    else if (c) /* conditions(2/2) */
+    {
+	if (d) /* conditions(1/2) true(0) */
+	       /* conditions(end) */
+	    x = 3;
+	else
+	    x = 4;
+    }
+}
+
+void
+mcdc007b (int a, int b, int c)
+{
+    goto begin;
+then:
+    x = 1;
+    return;
+begin:
+    /* similar to if (a || b || c) x = 1 */
+    if (a) /* conditions(2/2) */
+	goto then;
+    else if (b) /* conditions(2/2) */
+	goto then;
+    else if (c) /* conditions(1/2) true(0) */
+		/* conditions(end) */
+	goto then;
+}
+
+/* while loop */
+void
+mcdc008a (int a)
+{
+    while (a < 10) /* conditions(2/2) */
+	x = a++;
+}
+
+void
+mcdc008b (int a)
+{
+    while (a > 10) /* conditions(1/2) true(0) */
+		   /* conditions(end) */
+	x = a--;
+}
+
+void
+mcdc008c (int a)
+{
+    // should work, even with no body
+    while (a) /* conditions(2/2) */
+	break;
+}
+
+void
+mcdc008d (int a, int b, int c, int d)
+{
+    /* multi-term loop conditional */
+    while ((a && (b || c)) && d) /* conditions(8/8) */
+	a = b = c = d = 0;
+}
+
+void
+mcdc009a (int a, int b)
+{
+    while (a > 0 && b > 0) /* conditions(3/4) false(1) */
+			   /* conditions(end) */
+	x = a--;
+}
+
+/* for loop */
+void
+mcdc010a(int a, int b)
+{
+    for (int i = 0; i < b; i++) /* conditions(2/2) */
+    {
+	if (a < b) /* conditions(2/2) */
+	    x = 1;
+	else
+	    x = a += 2;
+    }
+}
+
+void
+mcdc010b ()
+{
+    for (int a = 0; a <= 1; ++a) /* conditions(2/2) */
+    {
+	x = a;
+    }
+}
+
+int always (int x) { (void) x; return 1; }
+
+/* no-condition infinite loops */
+void
+mcdc010c (int a)
+{
+    for (;;)
+    {
+	if (always(a)) /* conditions(1/2) false(0) */
+		       /* conditions(end) */
+	{
+	    x = a;
+	    break;
+	}
+	x += a + 1;
+    }
+}
+
+/* conditionals without control flow constructs work */
+void
+mcdc011a (int a, int b, int c)
+{
+    x = (a && b) || c; /* conditions(5/6) false(1) */
+		       /* conditions(end) */
+}
+
+/* sequential expressions are handled independently */
+void
+mcdc012a (int a, int b, int c)
+{
+    if (a || b) /* conditions(3/4) true(0) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+
+    if (c) /* conditions(2/2) */
+	x = 1;
+}
+
+/*
+ * cannot ever satisfy MC/DC, even with all input combinations, because not all
+ * variables independently affect the decision
+ */
+void
+mcdc013a (int a, int b, int c)
+{
+    (void)b;
+    /*
+     * Specification: (a && b) || c
+     *
+     * But the expression was implemented wrong. This has branch coverage, but
+     * not MC/DC
+     */
+    if ((a && !c) || c) /* conditions(5/6) false(1) */
+	/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void
+mcdc014a ()
+{
+    int conds[64] = { 0 };
+    /* conditions(64/128) true(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63) */
+    x = conds[ 0] || conds[ 1] || conds[ 2] || conds[ 3] || conds[ 4] ||
+	conds[ 5] || conds[ 6] || conds[ 7] || conds[ 8] || conds[ 9] ||
+	conds[10] || conds[11] || conds[12] || conds[13] || conds[14] ||
+	conds[15] || conds[16] || conds[17] || conds[18] || conds[19] ||
+	conds[20] || conds[21] || conds[22] || conds[23] || conds[24] ||
+	conds[25] || conds[26] || conds[27] || conds[28] || conds[29] ||
+	conds[30] || conds[31] || conds[32] || conds[33] || conds[34] ||
+	conds[35] || conds[36] || conds[37] || conds[38] || conds[39] ||
+	conds[40] || conds[41] || conds[42] || conds[43] || conds[44] ||
+	conds[45] || conds[46] || conds[47] || conds[48] || conds[49] ||
+	conds[50] || conds[51] || conds[52] || conds[53] || conds[54] ||
+	conds[55] || conds[56] || conds[57] || conds[58] || conds[59] ||
+	conds[60] || conds[61] || conds[62] || conds[63]
+	;  /* conditions(end) */
+}
+
+/* early returns */
+void
+mcdc015a (int a, int b)
+{
+    if (a) /* conditions(2/2) */
+	return;
+
+    if (b) /* conditions(1/2) true(0) */
+	   /* conditions(end) */
+	x = 1;
+}
+
+void
+mcdc015b (int a, int b)
+{
+    for (int i = 5; i > a; i--) /* conditions(2/2) */
+    {
+	if (i == b) /* conditions(2/2) */
+	    return;
+	x = i;
+    }
+}
+
+void
+mcdc015c (int a, int b)
+{
+    for (int i = 5; i > a; i--) /* conditions(2/2) */
+    {
+	if (i == b) /* conditions(2/2) */
+	{
+	    x = 0;
+	    return;
+	}
+	else
+	{
+	    x = 1;
+	    return;
+	}
+
+	x = i;
+    }
+}
+
+
+/* check nested loops */
+void
+mcdc016a (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+	for (int k = 0; k < b; k++) /* conditions(2/2) */
+	    x = i + k;
+}
+
+void
+mcdc016b (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+	if (a > 5) /* conditions(2/2) */
+	    break;
+
+	for (int k = 0; k < b; k++) /* conditions(2/2) */
+	    x = i + k;
+    }
+}
+
+void
+mcdc016c (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+	if (a > 5) /* conditions(1/2) true(0) */
+		   /* conditions(end) */
+	    return;
+
+	for (int k = 0; k < b; k++) /* conditions(2/2) */
+	    x = i + k;
+    }
+}
+
+void
+mcdc016d (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+	for (int k = 0; k < 5; k++) /* conditions(2/2) */
+	{
+	    if (b > 5) /* conditions(1/2) true(0) */
+		       /* conditions(end) */
+		return;
+	    x = i + k;
+	}
+
+    }
+}
+
+/* do-while loops */
+void
+mcdc017a (int a)
+{
+    do
+    {
+	a--;
+    } while (a > 0); /* conditions(2/2) */
+}
+
+void
+noop () {}
+
+void
+mcdc017b (int a, int b)
+{
+    do
+    {
+	/*
+	 * This call is important; it can add more nodes to the body in the
+	 * CFG, which has changes how close exits and breaks are to the loop
+	 * conditional.
+	 */
+	noop ();
+	a--;
+	if (b) /* conditions(2/2) */
+	    break;
+
+    } while (a > 0); /* conditions(2/2) */
+}
+
+void
+mcdc017c (int a, int b)
+{
+    int left = 0;
+    int right = 0;
+    int n = a + b;
+    do
+    {
+	if (a) /* conditions(1/2) false(0) */
+	       /* conditions(end) */
+	{
+	    left = a > left ? b : left; /* conditions(2/2) */
+	}
+	if (b) /* conditions(1/2) false(0) */
+	{
+	    right = b > right ? a : right; /* conditions(2/2) */
+	}
+    } while (n-- > 0); /* conditions(2/2) */
+}
+
+int id  (int x) { return  x; }
+int inv (int x) { return !x; }
+
+/* collection of odd cases lifted-and-adapted from real-world code */
+int mcdc018a (int a, int b, int c, int d, int e, int f, int g, int len)
+{
+    int n;
+    /* adapted from zlib/gz_read */
+    do
+    {
+	n = -1;
+	if (n > len) /* conditions(2/2) */
+	    n = len;
+
+	if (b) /* conditions(2/2) */
+	{
+	    if (b < 5) /* conditions(2/2) */
+		x = 1;
+	    noop();
+	}
+	else if (c && d) /* conditions(3/4) false(1) */
+	{
+	    x = 2;
+	    break;
+	}
+	else if (e || f) /* conditions(2/4) false(0 1) */
+			 /* conditions(end) */
+	{
+	    if (id(g)) /* conditions(2/2) */
+		return 0;
+	    continue;
+	}
+    } while (a-- > 0); /* conditions(2/2) */
+
+    return 1;
+}
+
+void
+mcdc018b (int a, int b, int c)
+{
+    int n;
+    while (a) /* conditions(2/2) */
+    {
+	/* else block does not make a difference for the problem, but ensures
+	   loop termination. */
+	if (b) /* conditions(2/2) */
+	    n = c ? 0 : 0; // does not show up in CFG (embedded in the block)
+	else
+	    n = 0;
+	a = n;
+    }
+}
+
+/* Adapted from zlib/compress2 */
+void
+mcdc018c (int a, int b)
+{
+    int err;
+    do
+    {
+	a = inv (a);
+	err = a;
+    } while (err); /* conditions(1/2) true(0) */
+		   /* conditions(end) */
+
+    a = id (a);
+    if (a) /* conditions(1/2) true(0) */
+	x *= a + 1;
+}
+
+/* too many conditions, coverage gives up */
+void
+mcdc019a ()
+{
+    int conds[65] = { 0 };
+    #pragma GCC diagnostic push
+    #pragma GCC diagnostic ignored "-Wcoverage-too-many-conditions"
+    x = conds[ 0] || conds[ 1] || conds[ 2] || conds[ 3] || conds[ 4] ||
+	conds[ 5] || conds[ 6] || conds[ 7] || conds[ 8] || conds[ 9] ||
+	conds[10] || conds[11] || conds[12] || conds[13] || conds[14] ||
+	conds[15] || conds[16] || conds[17] || conds[18] || conds[19] ||
+	conds[20] || conds[21] || conds[22] || conds[23] || conds[24] ||
+	conds[25] || conds[26] || conds[27] || conds[28] || conds[29] ||
+	conds[30] || conds[31] || conds[32] || conds[33] || conds[34] ||
+	conds[35] || conds[36] || conds[37] || conds[38] || conds[39] ||
+	conds[40] || conds[41] || conds[42] || conds[43] || conds[44] ||
+	conds[45] || conds[46] || conds[47] || conds[48] || conds[49] ||
+	conds[50] || conds[51] || conds[52] || conds[53] || conds[54] ||
+	conds[55] || conds[56] || conds[57] || conds[58] || conds[59] ||
+	conds[60] || conds[61] || conds[62] || conds[63] || conds[64]
+	;
+    #pragma GCC diagnostic pop
+}
+
+/* ternary */
+void
+mcdc020a (int a)
+{
+    // special case, this can be reduced to:
+    // _1 = argc != 0;
+    // e = (int) _1;
+    x = a ? 1 : 0;
+
+    // changing to different int makes branch
+    x = a ? 2 : 1; /* conditions(2/2) */
+}
+
+void
+mcdc020b (int a, int b)
+{
+    x = (a || b) ? 1 : 0; /* conditions(3/4) true(1) */
+}
+
+void
+mcdc020c (int a, int b)
+{
+    x = a ? 0
+	: b ? 1 /* conditions(2/2) */
+	: 2;    /* conditions(1/2) false(0) */
+		/* conditions(end) */
+}
+
+/* Infinite loop (no exit-edge), this should not be called, but it should
+   compile fine */
+void
+mcdc021a ()
+{
+    while (1)
+	;
+}
+
+/* Computed goto can give all sorts of problems, including difficult path
+   contractions. */
+void
+mcdc021b ()
+{
+  void *op = &&dest;
+dest:
+  if (op) /* conditions(0/2) true(0) false(0) */
+	  /* conditions(end) */
+    goto * 0;
+}
+
+int __sigsetjmp ();
+
+/* This should compile, but not called. */
+void
+mcdc021c ()
+{
+  while (x) /* conditions(0/2) true(0) false(0)*/
+	    /* conditions(end) */
+     __sigsetjmp ();
+}
+
+/* If edges are not properly contracted the a && id (b) will be interpreted as
+   two independent expressions. */
+void
+mcdc021d (int a, int b, int c, int d)
+{
+    if (a && id (b)) /* conditions(1/4) true(0 1) false(0) */
+		     /* conditions(end) */
+	x = 1;
+    else if (c && id (d)) /* conditions(1/4) true(0 1) false(0) */
+			  /* conditions(end) */
+	x = 2;
+    else
+	x = 3;
+}
+
+/* Adapted from linux arch/x86/tools/relocs.c
+   With poor edge contracting this became an infinite loop. */
+void
+mcdc022a (int a, int b)
+{
+    for (int i = 0; i < 5; i++) /* conditions(2/2) */
+    {
+	x = i;
+	for (int j = i; j < 5; j++) /* conditions(2/2) */
+	{
+	    if (id (id (a)) || id (b)) /* conditions(3/4) true(0) */
+				       /* conditions(end) */
+		continue;
+	    b = inv(b);
+	}
+    }
+}
+
+int
+mcdc022b (int a)
+{
+    int devt;
+    if (a) /* conditions(2/2) */
+    {
+	x = a * 2;
+	if (x != a / 10 || x != a % 10) /* conditions(1/4) true(1) false(0 1) */
+					/* conditions(end) */
+	    return 0;
+    } else {
+	devt = id (a);
+	if (devt) /* conditions(1/2) true(0) */
+		  /* conditions(end) */
+	    return 0;
+    }
+
+    return devt;
+}
+
+/* Adapted from linux arch/x86/events/intel/ds.c
+
+   It broken sorting so that the entry block was not the first node after
+   sorting. */
+void
+mcdc022c (int a)
+{
+    if (!a) /* conditions(2/2) */
+	return;
+
+    for (int i = 0; i < 5; i++) /* conditions(2/2) */
+    {
+	if (id (a + i) || inv (a - 1)) /* conditions(1/4) false(0 1) true(1) */
+				       /* conditions(end) */
+	    x = a + i;
+	if (inv (a)) /* conditions(1/2) true(0) */
+		     /* conditions(end) */
+	    break;
+    }
+}
+
+void
+mcdc022d (int a)
+{
+    int i;
+    for (i = 0; i < id (a); i++) /* conditions(1/2) false(0) */
+    {
+	if (!inv (a)) /* conditions(1/2) false(0)*/
+		      /* conditions(end) */
+	    break;
+    }
+
+    if (i < a) /* conditions(1/2) false(0) */
+	       /* conditions(end) */
+	x = a + 1;
+}
+
+/* 023 specifically tests that masking works correctly, which gets complicated
+   fast with a mix of operators and deep subexpressions.  These tests violates
+   the style guide slightly to emphasize the nesting.  They all share the same
+   implementation and only one input is given to each function to obtain clean
+   coverage results. */
+void
+mcdc023a (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
+	  int l, int m, int n)
+{
+    // [a m n] = 0, [b, ...] = 1
+    // a is masked by b and the remaining terms should be short circuited
+    if (/* conditions(1/24) true(0 2 3 4 5 6 7 8 9 10 11) false(0 1 2 3 4 5 6 7 8 9 10 11) */
+	/* conditions(end) */
+	   (a || b)
+	|| (   ((c && d) || (e && (f || g) && h))
+	    && (k || l)
+	    && (m || n)))
+	x = a + b;
+    else
+	x = b + c;
+}
+
+void
+mcdc023b (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
+	  int l, int m, int n)
+{
+    // [a b d h] = 0, [c, ...] = 1
+    // h = 0 => false but does not mask (a || b) or (c && d). d = 0 masks c.
+    if (/* conditions(4/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 4 5 6 8 9 10 11) */
+	/* conditions(end) */
+	   (a || b)
+	|| (   ((c && d) || (e && (f || g) && h))
+	    && (k || l)
+	    && (m || n)))
+	x = a + b;
+    else
+	x = b + c;
+}
+
+void
+mcdc023c (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
+	  int l, int m, int n)
+{
+    /* [m n a b] = 0, [...] = 1
+       n,m = 0 should mask all other terms than a, b */
+    if (/* conditions(4/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 3 4 5 6 7 8 9) */
+	/* conditions(end) */
+	   (a || b)
+	|| (   ((c && d) || (e && (f || g) && h))
+	    && (k || l)
+	    && (m || n)))
+	x = a + b;
+    else
+	x = b + c;
+}
+
+void
+mcdc023d (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
+	  int l, int m, int n)
+{
+    /* [a b] = 0, [h, ...] = 1
+       n,m = 0 should mask all other terms than a, b */
+    if (/* conditions(4/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 3 4 5 6 7 10 11) */
+	/* conditions(end) */
+	   (a || b)
+	|| (   ((c && d) || (e && (f || g) && h))
+	    && (k || l)
+	    && (m || n)))
+	x = a + b;
+    else
+	x = b + c;
+}
+
+void
+mcdc023e (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
+	  int l, int m, int n)
+{
+    /* [a b d] = 0, [c h, ...] = 1
+       h = 1 should mask c, d, leave other terms intact.
+       If [k l m n] were false then h itself would be masked.
+       [a b] are masked as collateral by [m n]. */
+    if (/* conditions(5/24) true(0 1 2 3 6 9 11) false(0 1 2 3 4 5 6 7 8 9 10 11) */
+	/* conditions(end) */
+	   (a || b)
+	|| (   ((c && d) || (e && (f || g) && h))
+	    && (k || l)
+	    && (m || n)))
+	x = a + b;
+    else
+	x = b + c;
+}
+
+void
+mcdc023f (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
+	  int l, int m, int n)
+{
+    /* [a b c f g] = 0, [e, ...] = 1
+       [f g] = 0 should mask e, leave [c d] intact. */
+    if (/* conditions(5/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(3 4 7 8 9 10 11) */
+	/* conditions(end) */
+	   (a || b)
+	|| (   ((c && d) || (e && (f || g) && h))
+	    && (k || l)
+	    && (m || n)))
+	x = a + b;
+    else
+	x = b + c;
+}
+
+void
+mcdc023g (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
+	  int l, int m, int n)
+{
+    /* [a b d f g] = 0, [e c, ...] = 1
+       Same as 023f but with [c d] flipped so d masks c rather than c
+       short-circuits.  This should not be lost. */
+    if (/* conditions(5/24) true(0 1 2 3 4 5 6 7 8 9 10 11) false(2 4 7 8 9 10 11) */
+	/* conditions(end) */
+	   (a || b)
+	|| (   ((c && d) || (e && (f || g) && h))
+	    && (k || l)
+	    && (m || n)))
+	x = a + b;
+    else
+	x = b + c;
+}
+
+int main ()
+{
+    mcdc001a (0, 1);
+
+    mcdc001b (0, 1);
+    mcdc001b (0, 0);
+
+    mcdc001c (0, 1);
+    mcdc001c (0, 0);
+    mcdc001c (1, 1);
+
+    mcdc001d (1, 1, 1);
+    mcdc001d (0, 1, 0);
+
+    mcdc002a (1, 0);
+
+    mcdc002b (1, 0);
+    mcdc002b (1, 1);
+
+    mcdc002c (0, 0);
+    mcdc002c (1, 1);
+    mcdc002c (1, 0);
+
+    mcdc002d (1, 1, 1);
+    mcdc002d (1, 0, 0);
+
+    mcdc003a (0, 0);
+    mcdc003a (1, 0);
+
+    mcdc004a (0);
+    mcdc004b (0);
+    mcdc004b (1);
+    mcdc004c (1);
+
+    mcdc004d (0, 0, 0);
+    mcdc004d (1, 1, 1);
+
+    mcdc004e (0, 0, 0);
+    mcdc004e (1, 1, 1);
+
+    mcdc005a (1, 0, 1);
+
+    mcdc005b (1, 1, 0, 0);
+    mcdc005b (1, 0, 0, 0);
+
+    mcdc005c (0, 1, 1, 0);
+
+    mcdc005d (1, 0, 0, 0);
+
+    mcdc006a (0, 0, 0, 0, 0);
+    mcdc006a (1, 0, 0, 0, 0);
+    mcdc006a (1, 1, 1, 0, 0);
+
+    mcdc006b (0, 0, 0);
+    mcdc006b (1, 0, 0);
+    mcdc006b (1, 1, 0);
+    mcdc006b (1, 1, 1);
+
+    mcdc006c (0, 0, 0);
+    mcdc006c (1, 0, 0);
+    mcdc006c (1, 1, 0);
+    mcdc006c (1, 1, 1);
+
+    mcdc007a (0, 0, 0, 0);
+    mcdc007a (1, 0, 0, 0);
+    mcdc007a (0, 0, 1, 0);
+
+    mcdc007b (0, 0, 0);
+    mcdc007b (0, 1, 1);
+    mcdc007b (1, 0, 1);
+
+    mcdc008a (0);
+
+    mcdc008b (0);
+
+    mcdc008c (0);
+    mcdc008c (1);
+
+    mcdc008d (0, 0, 0, 0);
+    mcdc008d (1, 0, 0, 0);
+    mcdc008d (1, 0, 1, 0);
+    mcdc008d (1, 0, 1, 1);
+    mcdc008d (1, 1, 1, 1);
+
+    mcdc009a (0, 0);
+    mcdc009a (1, 1);
+
+    mcdc010a (0, 0);
+    mcdc010a (0, 9);
+    mcdc010a (2, 1);
+
+    mcdc010b ();
+
+    mcdc010c (1);
+
+    mcdc011a (0, 0, 0);
+    mcdc011a (1, 1, 0);
+    mcdc011a (1, 0, 1);
+
+    mcdc012a (0, 0, 0);
+    mcdc012a (0, 1, 1);
+
+    mcdc013a (0, 0, 0);
+    mcdc013a (0, 0, 1);
+    mcdc013a (0, 1, 0);
+    mcdc013a (0, 1, 1);
+    mcdc013a (1, 0, 0);
+    mcdc013a (1, 0, 1);
+    mcdc013a (1, 1, 0);
+    mcdc013a (1, 1, 1);
+
+    mcdc014a ();
+
+    mcdc015a (0, 0);
+    mcdc015a (1, 0);
+
+    mcdc015b (0, 0);
+    mcdc015b (0, 1);
+    mcdc015b (6, 1);
+
+    mcdc015c (0, 0);
+    mcdc015c (0, 5);
+    mcdc015c (6, 1);
+
+    mcdc016a (5, 5);
+
+    mcdc016b (5, 5);
+    mcdc016b (6, 5);
+
+    mcdc016c (5, 5);
+
+    mcdc016d (1, 0);
+
+    mcdc017a (0);
+    mcdc017a (2);
+
+    mcdc017b (2, 0);
+    mcdc017b (0, 1);
+
+    mcdc017c (1, 1);
+
+    mcdc018a (0, 0, 1, 1, 0, 0, 0, 0);
+    mcdc018a (0, 1, 0, 0, 0, 0, 1, -2);
+    mcdc018a (0, 6, 0, 0, 0, 0, 1, -2);
+    mcdc018a (0, 6, 0, 0, 0, 0, 1, -2);
+    mcdc018a (0, 0, 0, 1, 0, 1, 1, 0);
+    mcdc018a (1, 0, 0, 0, 1, 1, 0, 0);
+
+    mcdc018b (1, 0, 0);
+    mcdc018b (1, 1, 0);
+
+    mcdc018c (1, 1);
+
+    mcdc019a ();
+
+    mcdc020a (0);
+    mcdc020a (1);
+
+    mcdc020b (0, 0);
+    mcdc020b (1, 0);
+
+    mcdc020c (0, 1);
+    mcdc020c (1, 1);
+
+    mcdc021d (1, 0, 1, 0);
+
+    mcdc022a (0, 0);
+
+    mcdc022b (0);
+    mcdc022b (1);
+
+    mcdc022c (0);
+    mcdc022c (1);
+
+    mcdc022d (1);
+
+    mcdc023a (0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+    mcdc023b (0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1);
+    mcdc023c (0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0);
+    mcdc023d (0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1);
+    mcdc023e (0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1);
+    mcdc023f (0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1);
+    mcdc023g (0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1);
+}
+
+/* { dg-final { run-gcov conditions { --conditions gcov-19.c } } } */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-20.c b/gcc/testsuite/gcc.misc-tests/gcov-20.c
new file mode 100644
index 00000000000..847dae495db
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-20.c
@@ -0,0 +1,22 @@
+/* { dg-options "-fprofile-conditions -ftest-coverage -fprofile-update=atomic" } */
+/* { dg-do run { target native } } */
+
+/* some side effect to stop branches from being pruned */
+int x = 0;
+
+void
+conditions_atomic001 (int a, int b)
+{
+    if (a || b) /* conditions(1/4) true(0) false(0 1) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+int main ()
+{
+    conditions_atomic001 (0, 1);
+}
+
+/* { dg-final { run-gcov conditions { --conditions gcov-20.c } } } */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-21.c b/gcc/testsuite/gcc.misc-tests/gcov-21.c
new file mode 100644
index 00000000000..978be3276a2
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-21.c
@@ -0,0 +1,16 @@
+/* { dg-options "-fprofile-conditions" } */
+
+/* https://gcc.gnu.org/pipermail/gcc-patches/2022-April/592927.html */
+char trim_filename_name;
+int r;
+
+void trim_filename() {
+    if (trim_filename_name)
+	r = 123;
+    while (trim_filename_name)
+	;
+}
+
+int main ()
+{
+}
diff --git a/gcc/testsuite/lib/gcov.exp b/gcc/testsuite/lib/gcov.exp
index 9d5b2cdb86b..88f28d26bb6 100644
--- a/gcc/testsuite/lib/gcov.exp
+++ b/gcc/testsuite/lib/gcov.exp
@@ -174,6 +174,180 @@ proc verify-branches { testname testcase file } {
     return $failed
 }
 
+#
+# verify-conditions -- check that conditions are checked as expected
+#
+# TESTNAME is the name of the test, including unique flags.
+# TESTCASE is the name of the test file.
+# FILE is the name of the gcov output file.
+#
+# Checks are based on comments in the source file. Condition coverage comes
+# with with two types of output, a summary and a list of the uncovered
+# conditions. Both must be checked to pass the test
+#
+# To check for conditions, add a comment the line of a conditional:
+# /* conditions(n/m) true(0 1) false(1) */
+#
+# where n/m are the covered and total conditions in the expression. The true()
+# and false() take the indices expected *not* covered.
+#
+# This means that all coverage statements should have been seen:
+# /* conditions(end) */
+#
+# If all conditions are covered i.e. n == m, then conditions(end) can be
+# omitted. If either true() or false() are empty they can be omitted too.
+#
+# C++ can insert conditionals in the CFG that are not present in source code.
+# These must be manually suppressed since unexpected and unhandled conditions
+# are an error (to help combat regressions). Output can be suppressed with
+# conditions(suppress) and conditions(end). suppress should usually be on a
+# closing brace.
+#
+# Some expressions, when using unnamed temporaries as operands, will have
+# destructors in expressions. The coverage of the destructor will be reported
+# on the same line as the expression itself, but suppress() would also swallow
+# the expected tested-for messages. To handle these, use the destructor() [1]
+# which will suppress everything from and including the second "conditions
+# covered".
+#
+# [1] it is important that the destructor() is *on the same line* as the
+#     conditions(m/n)
+proc verify-conditions { testname testcase file } {
+    set failed 0
+    set suppress 0
+    set destructor 0
+    set shouldt ""
+    set shouldf ""
+    set shouldall ""
+    set fd [open $file r]
+    set n 0
+    set keywords {"end" "suppress"}
+    while {[gets $fd line] >= 0} {
+        regexp "^\[^:\]+: *(\[0-9\]+):" "$line" all n
+        set prefix "$testname line $n"
+
+        if {![regexp "condition" $line]} {
+            continue
+        }
+
+        # Missing coverage for both true and false will cause a failure, but
+        # only count it once for the report.
+        set ok 1
+        if [regexp {conditions *\(([0-9a-z/]+)\)} "$line" all e] {
+            # *Very* coarse sanity check: conditions() should either be a
+            # keyword or n/m, anything else means a buggy test case. end is
+            # optional for cases where all conditions are covered, since it
+            # only expects a single line of output.
+            if {([lsearch -exact $keywords $e] >= 0 || [regexp {\d+/\d+} "$e"]) == 0} {
+                fail "$prefix: expected conditions (n/m), (suppress) or (end); was ($e)"
+                incr failed
+                continue
+            }
+
+            # Any keyword means a new context. Set the error flag if not all
+            # expected output has been seen, and reset the state.
+
+            if {[llength $shouldt] != 0} {
+                fail "$prefix: expected uncovered (true) for terms $shouldt"
+                set ok 0
+            }
+
+            if {[llength $shouldf] != 0} {
+                fail "$prefix: expected uncovered (false) for terms $shouldf"
+                set ok 0
+            }
+
+            if {$shouldall ne ""} {
+                fail "$prefix: coverage summary not found; expected $shouldall"
+                set ok 0
+            }
+
+            set suppress 0
+            set destructor 0
+            set shouldt ""
+            set shouldf ""
+            set shouldall ""
+            set newt ""
+            set newf ""
+
+            if [regexp {destructor\(\)} "$line"] {
+                set destructor 1
+            }
+
+            if [regexp {(\d+)/(\d+)} "$e" all i k] {
+                regexp {true\(([0-9 ]+)\)}  "$line" all newt
+                regexp {false\(([0-9 ]+)\)} "$line" all newf
+
+                # Sanity check - if the true() and false() vectors should have
+                # m-n elements to cover all uncovered conditions. Because of
+                # masking it can sometimes be surprising what terms are
+                # independent, so this makes for more robust test at the cost
+                # of being slightly more annoying to write.
+                set nterms [expr [llength $newt] + [llength $newf]]
+                set nexpected [expr {$k - $i}]
+                if {$nterms != $nexpected} {
+                    fail "$prefix: expected $nexpected uncovered terms; got $nterms"
+                    set ok 0
+                }
+                set shouldall $e
+                set shouldt $newt
+                set shouldf $newf
+            } elseif {$e == "end"} {
+                # no-op - state has already been reset, and errors flagged
+            } elseif {$e == "suppress"} {
+                set suppress 1
+            } else {
+                # this should be unreachable, 
+                fail "$prefix: unhandled control ($e), should be unreachable"
+                set ok 0
+            }
+        } elseif {$suppress == 1} {
+            # ignore everything in a suppress block. C++ especially can insert
+            # conditionals in exceptions and destructors which would otherwise
+            # be considered unhandled.
+            continue
+        } elseif [regexp {condition +(\d+) not covered \(true\)} "$line" all cond] {
+            set i [lsearch $shouldt $cond]
+            if {$i != -1} {
+                set shouldt [lreplace $shouldt $i $i]
+            } else {
+                fail "$testname line $n: unexpected uncovered term $cond (true)"
+                set ok 0
+            }
+        } elseif [regexp {condition +(\d+) not covered \(false\)} "$line" all cond] {
+            set i [lsearch $shouldf $cond]
+            if {$i != -1} {
+                set shouldf [lreplace $shouldf $i $i]
+            } else {
+                fail "$testname line $n: unexpected uncovered term $cond (false)"
+                set ok 0
+            }
+        } elseif [regexp {conditions covered (\d+/\d+)} "$line" all cond] {
+            # the destructor-generated "conditions covered" lines will be
+            # written after all expression-related output. Handle these by
+            # turning on suppression if the destructor-suppression is
+            # requested.
+            if {$shouldall == "" && $destructor == 1} {
+                set suppress 1
+                continue
+            }
+
+            if {$cond == $shouldall} {
+                set shouldall ""
+            } else {
+                fail "$testname line $n: unexpected summary $cond"
+                set ok 0
+            }
+        }
+
+        if {$ok != 1} {
+            incr failed
+        }
+    }
+    close $fd
+    return $failed
+}
+
 #
 # verify-calls -- check that call return percentages are as expected
 #
@@ -321,6 +495,7 @@ proc run-gcov { args } {
     set gcov_args ""
     set gcov_verify_calls 0
     set gcov_verify_branches 0
+    set gcov_verify_conditions 0
     set gcov_verify_lines 1
     set gcov_verify_intermediate 0
     set gcov_remove_gcda 0
@@ -331,10 +506,13 @@ proc run-gcov { args } {
 	  set gcov_verify_calls 1
 	} elseif { $a == "branches" } {
 	  set gcov_verify_branches 1
+	} elseif { $a == "conditions" } {
+	  set gcov_verify_conditions 1
 	} elseif { $a == "intermediate" } {
 	  set gcov_verify_intermediate 1
 	  set gcov_verify_calls 0
 	  set gcov_verify_branches 0
+	  set gcov_verify_conditions 0
 	  set gcov_verify_lines 0
 	} elseif { $a == "remove-gcda" } {
 	  set gcov_remove_gcda 1
@@ -404,6 +582,11 @@ proc run-gcov { args } {
     } else {
 	set bfailed 0
     }
+    if { $gcov_verify_conditions } {
+	set cdfailed [verify-conditions $testname $testcase $testcase.gcov]
+    } else {
+	set cdfailed 0
+    }
     if { $gcov_verify_calls } {
 	set cfailed [verify-calls $testname $testcase $testcase.gcov]
     } else {
@@ -418,12 +601,12 @@ proc run-gcov { args } {
 
     # Report whether the gcov test passed or failed.  If there were
     # multiple failures then the message is a summary.
-    set tfailed [expr $lfailed + $bfailed + $cfailed + $ifailed]
+    set tfailed [expr $lfailed + $bfailed + $cdfailed + $cfailed + $ifailed]
     if { $xfailed } {
 	setup_xfail "*-*-*"
     }
     if { $tfailed > 0 } {
-	fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cfailed in return percentages, $ifailed in intermediate format"
+	fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cdfailed in condition/decision, $cfailed in return percentages, $ifailed in intermediate format"
 	if { $xfailed } {
 	    clean-gcov $testcase
 	}
diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
index 2beb49241f2..5e5ac1e731b 100644
--- a/gcc/tree-profile.cc
+++ b/gcc/tree-profile.cc
@@ -58,6 +58,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "alloc-pool.h"
 #include "symbol-summary.h"
 #include "symtab-thunks.h"
+#include "cfganal.h"
+#include "cfgloop.h"
 
 static GTY(()) tree gcov_type_node;
 static GTY(()) tree tree_interval_profiler_fn;
@@ -73,6 +75,972 @@ static GTY(()) tree ic_tuple_var;
 static GTY(()) tree ic_tuple_counters_field;
 static GTY(()) tree ic_tuple_callee_field;
 
+namespace
+{
+/* Some context and reused instances between function calls.  Large embedded
+   buffers are used to up-front request enough memory for most programs and
+   merge them into a single allocation at the cost of using more memory in the
+   average case.  Some numbers from linux v5.13 which is assumed to be a
+   reasonably diverse code base: 75% of the functions in linux have less than
+   16 nodes in the CFG and approx 2.5% have more than 64 nodes.  The functions
+   that go beyond a few dozen nodes tend to be very large (>100) and so 64
+   seems like a good balance.
+
+   This is really just a performance balance of the cost of allocation and
+   wasted memory. */
+struct conds_ctx
+{
+    /* Bitmap of the processed blocks.  Bit n set means basic_block->index has
+       been processed either explicitly or as a part of an expression. */
+    auto_sbitmap marks;
+
+    /* This is both a reusable shared allocation which is also used to return
+       single expressions, which means it for most code should only hold a
+       couple of elements. */
+    auto_vec<basic_block, 32> blocks;
+
+    /* Map from basic_block->index to an ordering so that for a single
+       expression (a || b && c) => index_map[a] < index_map[b] < index_map[c].
+       The values do not have to be consecutive and can be interleaved by
+       values from other expressions, so comparisons only make sense for blocks
+       that belong to the same expression. */
+    auto_vec<int, 64> index_map;
+
+    /* Pre-allocate bitmaps and vectors for per-function book keeping.  This is
+       pure instance reuse and the bitmaps carry no data between function
+       calls. */
+    auto_vec<basic_block, 64> B1;
+    auto_vec<basic_block, 64> B2;
+    auto_sbitmap G1;
+    auto_sbitmap G2;
+    auto_sbitmap G3;
+
+    explicit conds_ctx (unsigned size) noexcept (true) : marks (size),
+    G1 (size), G2 (size), G3 (size)
+    {
+	bitmap_clear (marks);
+    }
+
+    /* Mark a node as processed so nodes are not processed twice for example in
+       loops, gotos. */
+    void mark (const basic_block b) noexcept (true)
+    {
+	gcc_assert (!bitmap_bit_p (marks, b->index));
+	bitmap_set_bit (marks, b->index);
+    }
+
+    /* Mark nodes as processed so they are not processed twice. */
+    void mark (const vec<basic_block>& bs) noexcept (true)
+    {
+	for (const basic_block b : bs)
+	    mark (b);
+    }
+
+    /* Check if all nodes are marked.  A successful run should visit & mark
+       every reachable node exactly once. */
+    bool all_marked (const vec<basic_block>& reachable) const noexcept (true)
+    {
+	for (const basic_block b : reachable)
+	    if (!bitmap_bit_p (marks, b->index))
+		return false;
+	return true;
+    }
+};
+
+/* Only instrument terms with fewer than number of bits in a (wide) gcov
+   integer, which is probably 64.  The algorithm itself does not impose this
+   limitation, but it makes for a simpler implementation.
+
+   * Allocating the output data structure (coverage_counter_alloc ()) can
+     assume pairs of gcov_type_unsigned and not use a separate length field.
+   * A pair gcov_type_unsigned can be used as accumulators.
+   * Updating accumulators is can use the bitwise operations |=, &= and not
+     custom operators that work for arbitrary-sized bit-sets.
+
+   Most real-world code should be unaffected by this, but it is possible
+   (especially for generated code) to exceed this limit.
+ */
+#define CONDITIONS_MAX_TERMS (sizeof (gcov_type_unsigned) * BITS_PER_UNIT)
+#define EDGE_CONDITION (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE)
+
+/* Compare two basic blocks by their order in the expression i.e. for (a || b)
+   then cmp_index_map (a, b, ...) < 0.  The result is undefined if lhs, rhs
+   belong to different expressions. */
+int
+cmp_index_map (const void *lhs, const void *rhs, void *index_map)
+{
+    const_basic_block l = *(const basic_block*) lhs;
+    const_basic_block r = *(const basic_block*) rhs;
+    const vec<int>* im = (const vec<int>*) index_map;
+    return (*im)[l->index] - (*im)[r->index];
+}
+
+/* Find the index of needle in blocks; return -1 if not found. This has two
+   uses, sometimes for the index and sometimes for set member checks.  Sets are
+   typically very small (number of conditions, >8 is uncommon) so linear search
+   should be very fast. */
+int
+index_of (const basic_block needle, array_slice<basic_block> blocks)
+{
+    const int size = int (blocks.size ());
+    for (int i = 0; i < size; i++)
+	if (blocks[i] == needle)
+	    return i;
+    return -1;
+}
+
+/* Returns true if this is a conditional node, i.e. it has outgoing true and
+   false edges. */
+bool
+block_conditional_p (const basic_block b)
+{
+    unsigned t = 0;
+    unsigned f = 0;
+    for (edge e : b->succs)
+    {
+	t |= (e->flags & EDGE_TRUE_VALUE);
+	f |= (e->flags & EDGE_FALSE_VALUE);
+    }
+    return t && f;
+}
+
+/* Check if the edge is a conditional. */
+bool
+edge_conditional_p (const edge e)
+{
+    return e->flags & EDGE_CONDITION;
+}
+
+/* Special cases of the single_*_p and single_*_edge functions in basic-block.h
+   that don't consider exception handling or other complex edges.  This helps
+   create a view of the CFG with only normal edges - if a basic block has both
+   an outgoing fallthrough and exceptional edge [1], it should be considered a
+   single-successor.
+
+   [1] if this is not possible, these functions can be removed and replaced by
+       their basic-block.h cousins. */
+bool
+single (const vec<edge, va_gc> *edges)
+{
+    int n = EDGE_COUNT (edges);
+    if (n == 0)
+	return false;
+
+    for (edge e : edges)
+	if (e->flags & EDGE_COMPLEX)
+	    n -= 1;
+
+    return n == 1;
+}
+
+/* Get the single, non-complex edge.  Behavior is undefined edges have more
+   than 1 non-complex edges. */
+edge
+single_edge (const vec<edge, va_gc> *edges)
+{
+    for (edge e : edges)
+    {
+	if (e->flags & EDGE_COMPLEX)
+	    continue;
+	return e;
+    }
+    return NULL;
+}
+
+/* Sometimes, for example with function calls and C++ destructors, the CFG gets
+   extra nodes that are essentially single-entry-single-exit in the middle of
+   boolean expressions.  For example:
+
+      x || can_throw (y)
+
+               A
+              /|
+             / |
+            B  |
+            |  |
+            C  |
+           / \ |
+          /   \|
+         F     T
+
+   Without the extra node inserted by the function + exception it becomes a
+   proper 2-term graph, not 2 single-term graphs.
+
+               A
+              /|
+             C |
+            / \|
+           F   T
+
+   contract_edge ignores the series of intermediate nodes and makes a virtual
+   edge A -> C without having to construct a new simplified CFG explicitly.  It
+   gets more complicated as non-conditional edges is how the body of the
+   then/else blocks are separated from the boolean expression, so only edges
+   that are inserted because of function calls in the expression itself must be
+   merged.
+
+   Only chains of single-exit single-entry nodes that end with a condition
+   should be contracted. */
+edge
+contract_edge (edge e)
+{
+    edge source = e;
+    while (true)
+    {
+	basic_block dest = e->dest;
+	if (!single (dest->preds))
+	    return source;
+	if (e->flags & EDGE_DFS_BACK)
+	    return source;
+	if (block_conditional_p (dest))
+	    return e;
+
+	e = single_edge (dest->succs);
+	if (!e)
+	    return source;
+    }
+}
+
+/* This is the predecessor dual of contract_edge; it collapses the predecessor
+   blocks between two operands in a boolean expression. */
+edge
+contract_edge_up (edge e)
+{
+    while (true)
+    {
+	basic_block src = e->src;
+	if (edge_conditional_p (e))
+	    return e;
+	if (!single (src->preds))
+	    return e;
+	e = single_edge (src->preds);
+    }
+}
+
+/* Find the set {ancestors(p) intersect G} where ancestors is the recursive set
+   of predecessors for p.  Limiting to the ancestors that are also in G (see
+   cond_reachable_from) and by q is an optimization as ancestors outside G have
+   no effect when isolating expressions.
+
+   dfs_enumerate_from () does not work as the filter function needs edge
+   information and dfs_enumerate_from () only considers blocks. */
+void
+ancestors_of (basic_block p, basic_block q, const sbitmap G, sbitmap ancestors)
+{
+    if (!bitmap_bit_p (G, p->index))
+	return;
+
+    bitmap_set_bit (ancestors, p->index);
+    bitmap_set_bit (ancestors, q->index);
+    if (p == q)
+	return;
+
+    auto_vec<basic_block, 16> stack;
+    stack.safe_push (p);
+
+    while (!stack.is_empty())
+    {
+	basic_block b = stack.pop ();
+	if (single (b->preds))
+	{
+	    edge e = single_edge (b->preds);
+	    e = contract_edge_up (e);
+	    b = e->dest;
+	}
+
+	for (edge e : b->preds)
+	{
+	    basic_block src = e->src;
+	    if (bitmap_bit_p (ancestors, e->src->index))
+		continue;
+	    if (!bitmap_bit_p (G, e->src->index))
+		continue;
+	    bitmap_set_bit (ancestors, src->index);
+	    stack.safe_push (src);
+	}
+    }
+}
+
+/* A simple struct for storing/returning outcome block pairs.  Either both
+   blocks are set or both are NULL. */
+struct outcomes
+{
+    basic_block t = NULL;
+    basic_block f = NULL;
+
+    operator bool () const noexcept (true)
+    {
+	return t && f;
+    }
+};
+
+/* Get the true/false successors of a basic block. If b is not a conditional
+   block both edges are NULL. */
+outcomes
+conditional_succs (const basic_block b)
+{
+    outcomes c;
+    for (edge e : b->succs)
+    {
+	if (e->flags & EDGE_TRUE_VALUE)
+	    c.t = (e)->dest;
+	if (e->flags & EDGE_FALSE_VALUE)
+	    c.f = (e)->dest;
+    }
+
+    gcc_assert ((c.t && c.f) || (!c.t && !c.f));
+    return c;
+}
+
+/* Get the index or offset of a conditional flag, 0 for true and 1 for false.
+   These indices carry no semantics but must be consistent as they are used to
+   index into data structures in code generation and gcov. */
+unsigned
+condition_index (unsigned flag)
+{
+    return (flag & EDGE_CONDITION) == EDGE_TRUE_VALUE ? 0 : 1;
+}
+
+/* Compute the masking vector.
+
+   Masking and short circuiting are deeply connected - masking occurs when
+   control flow reaches a state that is also reachable with short circuiting.
+   In fact, masking appears as short circuiting in the reversed expression.
+   This means we can find the limits, the last term in preceeding
+   subexpressions, by following the edges that short circuit to the same
+   outcome.
+
+   In the simplest case a || b:
+
+   a
+   |\
+   | b
+   |/ \
+   T   F
+
+   T has has multiple incoming edges and is the outcome of a short circuit,
+   with top = a, bot = b.  lim = a.succs[0] = b, which has 1 ancestor a and so
+   the masking vector is b[1] = {a}.
+
+   The names "top" and "bot" refers to a pair of nodes with a shared
+   destination. The top is always the node corresponding to the left-most
+   operand of the two, it holds that index_map[top] < index_map[bot], and the
+   top is the right-most term in its subexpression.
+
+   Now consider (a && b) || (c && d) and its masking vectors:
+
+   a
+   |\
+   b \
+   |\|
+   | c
+   | |\
+   | d \
+   |/ \|
+   T   F
+
+   a[0] = {}
+   a[1] = {}
+   b[0] = {a}
+   b[1] = {}
+   c[0] = {}
+   c[1] = {}
+   d[0] = {c}
+   d[1] = {a,b}
+
+   b[0] and d[0] are identical to the a || b example, and d[1] is the bot in
+   the triangle [d, b] -> T.  b is the top node and last term in (a && b).  To
+   find the other individual terms masked we use the fact that all nodes in a
+   subexpression have outgoing edges to either the outcome or some other node
+   in the expression.  We know one outcome is T and that b is the last term,
+   which means the other outcome is b's other successor (in this case c).  From
+   this we find the terms by walking the predecessors starting at top (in this
+   case b) and adding nodes when both successors are present in the output.
+
+   The masking vector is represented as two bitfields per term in the
+   expression.  The expression a || b && c becomes the term vector [a b c] and
+   the bitsets are masks = [a.true a.false b.true ...].  The kth bit is set if
+   the the kth term is masked by the node-outcome. */
+void
+masking_vectors (conds_ctx& ctx, array_slice<basic_block> blocks,
+		 array_slice<gcov_type_unsigned> masks)
+{
+    gcc_assert (blocks.is_valid ());
+    gcc_assert (!blocks.empty ());
+    gcc_assert (masks.is_valid ());
+
+    sbitmap marks = ctx.G1;
+    sbitmap expr = ctx.G2;
+    vec<basic_block>& queue = ctx.B1;
+    vec<basic_block>& body = ctx.B2;
+    const vec<int>& index_map = ctx.index_map;
+    bitmap_clear (expr);
+
+    for (const basic_block b : blocks)
+	bitmap_set_bit (expr, b->index);
+
+    /* Set up for the iteration - include two outcome nodes in the traversal and
+       ignore the leading term since it cannot mask anything.  The algorithm is
+       not sensitive to the traversal order. */
+    body.truncate (0);
+    body.reserve (blocks.size () + 2);
+    for (const basic_block b : blocks)
+	body.quick_push (b);
+
+    outcomes out = conditional_succs (blocks.back ());
+    body.quick_push (out.t);
+    body.quick_push (out.f);
+    body[0] = body.pop ();
+
+    for (const basic_block b : body)
+    {
+	for (edge e1 : b->preds)
+	for (edge e2 : b->preds)
+	{
+	    const basic_block top = e1->src;
+	    const basic_block bot = e2->src;
+	    const unsigned cond = e1->flags & e2->flags & (EDGE_CONDITION);
+
+	    if (!cond)
+		continue;
+	    if (e1 == e2)
+		continue;
+	    if (!bitmap_bit_p (expr, top->index))
+		continue;
+	    if (!bitmap_bit_p (expr, bot->index))
+		continue;
+	    if (index_map[top->index] > index_map[bot->index])
+		continue;
+
+	    outcomes out = conditional_succs (top);
+	    gcc_assert (out);
+	    bitmap_clear (marks);
+	    bitmap_set_bit (marks, out.t->index);
+	    bitmap_set_bit (marks, out.f->index);
+	    queue.truncate (0);
+	    queue.safe_push (top);
+
+	    // The edge bot -> outcome triggers the masking
+	    const int m = 2*index_of (bot, blocks) + condition_index (cond);
+	    while (!queue.is_empty())
+	    {
+		basic_block q = queue.pop ();
+		/* q may have been processed & completed by being added to the
+		   queue multiple times, so check that there is still work to
+		   do before continuing. */
+		if (bitmap_bit_p (marks, q->index))
+		    continue;
+
+		outcomes succs = conditional_succs (q);
+		if (!bitmap_bit_p (marks, succs.t->index))
+		    continue;
+		if (!bitmap_bit_p (marks, succs.f->index))
+		    continue;
+
+		const int index = index_of (q, blocks);
+		gcc_assert (index != -1);
+		masks[m] |= gcov_type_unsigned (1) << index;
+		bitmap_set_bit (marks, q->index);
+
+		for (edge e : q->preds)
+		{
+		    e = contract_edge_up (e);
+		    if (!edge_conditional_p (e))
+			continue;
+		    if (e->flags & EDGE_DFS_BACK)
+			continue;
+		    if (bitmap_bit_p (marks, e->src->index))
+			continue;
+		    if (!bitmap_bit_p (expr, e->src->index))
+			continue;
+		    queue.safe_push (e->src);
+		}
+	    }
+	}
+    }
+}
+
+/* Find the nodes reachable from p by following only (possibly contracted)
+   condition edges dominated by p and ignore DFS back edges.  From a high level
+   this is partitioning the CFG into subgraphs by removing all non-condition
+   edges and selecting a single connected subgraph.  This creates a cut C = (G,
+   G') where G is the returned explicitly by this function.
+
+   It is assumed that all paths from p go through q (q post-dominates p).  p
+   must always be the first term in an expression and a condition node.
+
+   If |G| = 1 then this is a single term expression. If |G| > 1 then either
+   this is a multi-term expression or the first block in the then/else block is
+   a conditional expression as well.
+
+   Only nodes dominated by p is added - under optimization some blocks may be
+   merged and multiple independent conditions may share the same outcome
+   (making successors misidentified as a right operands), but true right-hand
+   operands are always dominated by the first term.
+
+   The function outputs both a bitmap and a vector as both are useful to the
+   caller. */
+void
+cond_reachable_from (basic_block p, basic_block q, sbitmap expr,
+		     vec<basic_block>& out)
+{
+    out.safe_push (p);
+    bitmap_set_bit (expr, p->index);
+    for (unsigned pos = 0; pos < out.length (); pos++)
+    {
+	for (edge e : out[pos]->succs)
+	{
+	    basic_block dest = contract_edge (e)->dest;
+	    if (dest == q)
+		continue;
+	    if (!dominated_by_p (CDI_DOMINATORS, dest, p))
+		continue;
+	    if (!block_conditional_p (dest))
+		continue;
+	    if (bitmap_bit_p (expr, dest->index))
+		continue;
+	    if (e->flags & EDGE_DFS_BACK)
+		continue;
+
+	    bitmap_set_bit (expr, dest->index);
+	    out.safe_push (dest);
+	}
+    }
+}
+
+/* Find the neighborhood of the graph G = [blocks, blocks+n), the
+   successors of nodes in G that are not also in G.  In the cut C = (G, G')
+   these are the nodes in G' with incoming edges that cross the span. */
+void
+neighborhood (const vec<basic_block>& blocks, const sbitmap G,
+	      vec<basic_block>& out)
+{
+    for (const basic_block b : blocks)
+    {
+	for (edge e : b->succs)
+	{
+	    basic_block dest = contract_edge (e)->dest;
+	    if (bitmap_bit_p (G, dest->index))
+		continue;
+	    if (!out.contains (dest))
+		out.safe_push (dest);
+	}
+    }
+}
+
+/* Find and isolate the expression starting at p.
+
+   Make a cut C = (G, G') following only condition edges.  G is a superset of
+   the expression B, but the walk may include expressions from the then/else
+   blocks if they start with conditions.  Only the subgraph B is the ancestor
+   of *both* the then/else outcome, which means B is the intersection of the
+   ancestors of the nodes in the neighborhood N(G). */
+void
+isolate_expression (conds_ctx &ctx, basic_block p, vec<basic_block>& out)
+{
+    sbitmap expr = ctx.G1;
+    sbitmap reachable = ctx.G2;
+    sbitmap ancestors = ctx.G3;
+    bitmap_clear (expr);
+    bitmap_clear (reachable);
+
+    vec<basic_block>& G = ctx.B1;
+    vec<basic_block>& NG = ctx.B2;
+    G.truncate (0);
+    NG.truncate (0);
+
+    basic_block post = get_immediate_dominator (CDI_POST_DOMINATORS, p);
+    cond_reachable_from (p, post, reachable, G);
+    if (G.length () == 1)
+    {
+	out.safe_push (p);
+	return;
+    }
+
+    neighborhood (G, reachable, NG);
+    bitmap_copy (expr, reachable);
+
+    for (const basic_block neighbor : NG)
+    {
+	bitmap_clear (ancestors);
+	for (edge e : neighbor->preds)
+	    ancestors_of (e->src, p, reachable, ancestors);
+	bitmap_and (expr, expr, ancestors);
+    }
+
+    for (const basic_block b : G)
+	if (bitmap_bit_p (expr, b->index))
+	    out.safe_push (b);
+    out.sort (cmp_index_map, &ctx.index_map);
+}
+
+/* Emit lhs = op1 <op> op2 on edges.  This emits non-atomic instructions and
+   should only be used on the local accumulators. */
+void
+emit_bitwise_op (edge e, tree lhs, tree op1, tree_code op, tree op2)
+{
+    tree tmp;
+    gassign *read;
+    gassign *bitw;
+    gimple *write;
+
+    tmp = make_temp_ssa_name (gcov_type_node, NULL, "__conditions_tmp");
+    read = gimple_build_assign (tmp, op1);
+    tmp = make_temp_ssa_name (gcov_type_node, NULL, "__conditions_tmp");
+    bitw = gimple_build_assign (tmp, op, gimple_assign_lhs (read), op2);
+    write = gimple_build_assign (lhs, gimple_assign_lhs (bitw));
+
+    gsi_insert_on_edge (e, read);
+    gsi_insert_on_edge (e, bitw);
+    gsi_insert_on_edge (e, write);
+}
+
+/* Visitor for make_index_map. */
+void
+make_index_map_visit (basic_block b, vec<basic_block>& L, vec<int>& marks)
+{
+    if (marks[b->index])
+	return;
+
+    for (edge e : b->succs)
+	if (!(e->flags & EDGE_DFS_BACK))
+	    make_index_map_visit (e->dest, L, marks);
+
+    marks[b->index] = 1;
+    L.quick_push (b);
+}
+
+/* Find a topological sorting of the blocks in a function so that left operands
+   are before right operands including subexpressions.  Sorting on block index
+   does not guarantee this property and the syntactical order of terms is very
+   important to the condition coverage.  The sorting algorithm is from Cormen
+   et al (2001) but with back-edges ignored and thus there is no need for
+   temporary marks (for cycle detection).
+
+   It is important to select unvisited nodes in DFS order to ensure the
+   roots/leading terms of boolean expressions are visited first (the other
+   terms being covered by the recursive step), but the visiting order of
+   individual boolean expressions carries no significance.
+
+   For the expression (a || (b && c) || d) the blocks should be [a b c d]. */
+void
+make_index_map (const vec<basic_block>& blocks, int max_index,
+		vec<basic_block>& L, vec<int>& index_map)
+{
+    L.truncate (0);
+    L.reserve (max_index);
+
+    /* Use of the output map as a temporary for tracking visited status. */
+    index_map.truncate (0);
+    index_map.safe_grow_cleared (max_index);
+    for (const basic_block b : blocks)
+	make_index_map_visit (b, L, index_map);
+
+    /* Insert canaries - if there are unreachable nodes (for example infinite
+       loops) then the unreachable nodes should never be needed for comparison,
+       and L.length () < max_index.  An index mapping should also never be
+       recorded twice. */
+    for (unsigned i = 0; i < index_map.length (); i++)
+	index_map[i] = -1;
+
+    gcc_assert (blocks.length () == L.length ());
+    L.reverse ();
+    const int nblocks = L.length ();
+    for (int i = 0; i < nblocks; i++)
+    {
+	gcc_assert (L[i]->index != -1);
+	index_map[L[i]->index] = i;
+    }
+}
+
+/* Walk the CFG and collect conditionals.
+
+   1. Collect a candidate set G by walking from the root following all
+      (contracted) condition edges.
+   2. This creates a cut C = (G, G'); find the neighborhood N(G).
+   3. For every node in N(G), follow the edges across the cut and collect all
+      ancestors (that are also in G).
+   4. The intersection of all these ancestor sets is the boolean expression B
+      that starts in root.
+
+   Walking is not guaranteed to find nodes in the order of the expression, it
+   might find (a || b) && c as [a c b], so the result must be sorted by the
+   index map. */
+const vec<basic_block>&
+collect_conditions (conds_ctx& ctx, const basic_block block)
+{
+    vec<basic_block>& blocks = ctx.blocks;
+    blocks.truncate (0);
+
+    if (bitmap_bit_p (ctx.marks, block->index))
+	return blocks;
+
+    if (!block_conditional_p (block))
+    {
+	ctx.mark (block);
+	return blocks;
+    }
+
+    isolate_expression (ctx, block, blocks);
+    ctx.mark (blocks);
+
+    if (blocks.length () > CONDITIONS_MAX_TERMS)
+    {
+	location_t loc = gimple_location (gsi_stmt (gsi_last_bb (block)));
+	warning_at (loc, OPT_Wcoverage_too_many_conditions,
+		    "Too many conditions (found %u); giving up coverage",
+		    blocks.length ());
+	blocks.truncate (0);
+    }
+    return blocks;
+}
+
+/* Used for dfs_enumerate_from () to include all reachable nodes. */
+bool
+yes (const_basic_block, const void *)
+{
+    return true;
+}
+
+}
+
+struct condcov {
+    explicit condcov (unsigned nblocks) noexcept (true) : ctx (nblocks) {}
+    auto_vec<int, 128> m_index;
+    auto_vec<basic_block, 256> m_blocks;
+    auto_vec<gcov_type_unsigned, 512> m_masks;
+    conds_ctx ctx;
+};
+
+unsigned
+cov_length (const struct condcov* cov)
+{
+    if (cov->m_index.is_empty ())
+	return 0;
+    return cov->m_index.length () - 1;
+}
+
+array_slice<basic_block>
+cov_blocks (struct condcov* cov, unsigned n)
+{
+    if (n >= cov->m_index.length ())
+	return array_slice<basic_block>::invalid ();
+
+    basic_block *begin = cov->m_blocks.begin () + cov->m_index[n];
+    basic_block *end = cov->m_blocks.begin () + cov->m_index[n + 1];
+    return array_slice<basic_block> (begin, end - begin);
+}
+
+array_slice<gcov_type_unsigned>
+cov_masks (struct condcov* cov, unsigned n)
+{
+    if (n >= cov->m_index.length ())
+	return array_slice<gcov_type_unsigned>::invalid ();
+
+    gcov_type_unsigned *begin = cov->m_masks.begin () + 2*cov->m_index[n];
+    gcov_type_unsigned *end = cov->m_masks.begin () + 2*cov->m_index[n + 1];
+    return array_slice<gcov_type_unsigned> (begin, end - begin);
+}
+
+void
+cov_free (struct condcov* cov)
+{
+    delete cov;
+}
+
+/* Condition coverage (MC/DC)
+
+   Algorithm
+   ---------
+   Whalen, Heimdahl, De Silva in "Efficient Test Coverage Measurement for
+   MC/DC" describe an algorithm for modified condition/decision coverage based
+   on AST analysis.  This algorithm analyses the control flow graph to analyze
+   expressions and compute masking vectors, but is inspired by their marking
+   functions for recording outcomes.  The individual phases are described in
+   more detail closer to the implementation.
+
+   The CFG is traversed in DFS order.  It is important that the first basic
+   block in an expression is the first one visited, but the order of
+   independent expressions does not matter.  When the function terminates,
+   every node in the dfs should have been processed and marked exactly once.
+   If there are unreachable nodes they are ignored and not instrumented.
+
+   The CFG is broken up into segments between dominators.  This isn't strictly
+   necessary, but since boolean expressions cannot cross dominators it makes
+   for a nice way to introduce limits to searches.
+
+   The coverage only considers the positions, not the symbols, in a
+   conditional, e.g. !A || (!B && A) is a 3-term conditional even though A
+   appears twice.  Subexpressions have no effect on term ordering:
+   (a && (b || (c && d)) || e) comes out as [a b c d e].
+
+   The output for gcov is a vector of pairs of unsigned integers, interpreted
+   as bit-sets, where the bit index corresponds to the index of the condition
+   in the expression.
+ */
+struct condcov*
+find_conditions (struct function *fn)
+{
+    record_loop_exits ();
+    mark_dfs_back_edges (fn);
+
+    const bool have_dom = dom_info_available_p (fn, CDI_DOMINATORS);
+    const bool have_post_dom = dom_info_available_p (fn, CDI_POST_DOMINATORS);
+    if (!have_dom)
+	calculate_dominance_info (CDI_DOMINATORS);
+    if (!have_post_dom)
+	calculate_dominance_info (CDI_POST_DOMINATORS);
+
+    const unsigned nblocks = n_basic_blocks_for_fn (fn);
+    condcov *cov = new condcov (nblocks);
+    conds_ctx& ctx = cov->ctx;
+
+    auto_vec<basic_block, 16> dfs;
+    dfs.safe_grow (nblocks);
+    const basic_block entry = ENTRY_BLOCK_PTR_FOR_FN (fn);
+    const basic_block exit = ENTRY_BLOCK_PTR_FOR_FN (fn);
+    int n = dfs_enumerate_from (entry, 0, yes, dfs.address (), nblocks, exit);
+    dfs.truncate (n);
+    make_index_map (dfs, nblocks, ctx.B1, ctx.index_map);
+
+    /* Visit all reachable nodes and collect conditions.  DFS order is
+       important so the first node of a boolean expression is visited first
+       (it will mark subsequent terms). */
+    cov->m_index.safe_push (0);
+    for (const basic_block b : dfs)
+    {
+	const vec<basic_block>& expr = collect_conditions (ctx, b);
+	if (!expr.is_empty ())
+	{
+	    cov->m_blocks.safe_splice (expr);
+	    cov->m_index.safe_push (cov->m_blocks.length ());
+	}
+    }
+    gcc_assert (ctx.all_marked (dfs));
+
+    if (!have_dom)
+	free_dominance_info (fn, CDI_DOMINATORS);
+    if (!have_post_dom)
+	free_dominance_info (fn, CDI_POST_DOMINATORS);
+
+    cov->m_masks.safe_grow_cleared (2 * cov->m_index.last());
+    const unsigned length = cov_length (cov);
+    for (unsigned i = 0; i < length; i++)
+	masking_vectors (ctx, cov_blocks (cov, i), cov_masks (cov, i));
+
+    return cov;
+}
+
+int
+instrument_decisions (array_slice<basic_block> expr, unsigned condno,
+		      tree *accu, gcov_type_unsigned *masks)
+{
+    /* Zero the local accumulators. */
+    tree zero = build_int_cst (get_gcov_type (), 0);
+    for (edge e : expr[0]->succs)
+    {
+	gsi_insert_on_edge (e, gimple_build_assign (accu[0], zero));
+	gsi_insert_on_edge (e, gimple_build_assign (accu[1], zero));
+    }
+    /* Add instructions for updating the function-local accumulators.  */
+    for (size_t i = 0; i < expr.size (); i++)
+    {
+	for (edge e : expr[i]->succs)
+	{
+	    if (!edge_conditional_p (e))
+		continue;
+
+	    /* accu |= expr[i] */
+	    const int k = condition_index (e->flags);
+	    tree rhs = build_int_cst (gcov_type_node, 1ULL << i);
+	    emit_bitwise_op (e, accu[k], accu[k], BIT_IOR_EXPR, rhs);
+
+	    if (masks[2*i + k] == 0)
+		continue;
+
+	    /* accu &= mask[i] */
+	    tree mask = build_int_cst (gcov_type_node, ~masks[2*i + k]);
+	    for (int j = 0; j < 2; j++)
+		emit_bitwise_op (e, accu[j], accu[j], BIT_AND_EXPR, mask);
+	}
+    }
+
+    const bool atomic = flag_profile_update == PROFILE_UPDATE_ATOMIC;
+    const tree atomic_ior = builtin_decl_explicit
+	(TYPE_PRECISION (gcov_type_node) > 32
+	 ? BUILT_IN_ATOMIC_FETCH_OR_8
+	 : BUILT_IN_ATOMIC_FETCH_OR_4);
+
+    /* Add instructions for flushing the local accumulators.
+
+       It is important that the flushes happen on on the outcome's incoming
+       edges, otherwise flushes could be lost to exception handling.
+
+       void fn (int a)
+       {
+	   if (a)
+	    fclose();
+	   exit();
+       }
+
+       Can yield the CFG:
+       A
+       |\
+       | B
+       |/
+       e
+
+       This typically only happen in optimized builds, but gives linker errors
+       because the counter is left as an undefined symbol. */
+
+    outcomes out = conditional_succs (expr.back ());
+    const basic_block outcome_blocks[] = { out.t, out.t, out.f, out.f, };
+    const int outcome[] = { 0, 1, 0, 1 };
+    for (int i = 0; i < 4; i++)
+    {
+	const int k = outcome[i];
+	for (edge e : outcome_blocks[i]->preds)
+	{
+	    /* Only instrument edges from inside the expression. Sometimes
+	       complicated control flow (like sigsetjmp and gotos) add
+	       predecessors that don't come from the boolean expression. */
+	    if (index_of (e->src, expr) == -1)
+		continue;
+
+	    tree ref = tree_coverage_counter_ref (GCOV_COUNTER_CONDS,
+						  2*condno + k);
+	    tree tmp = make_temp_ssa_name (gcov_type_node, NULL,
+					   "__conditions_tmp");
+	    if (atomic)
+	    {
+		tree relaxed = build_int_cst (integer_type_node,
+					      MEMMODEL_RELAXED);
+		ref = unshare_expr (ref);
+		gassign *read = gimple_build_assign (tmp, accu[k]);
+		gcall *flush = gimple_build_call (atomic_ior, 3,
+						  build_addr (ref),
+						  gimple_assign_lhs (read),
+						  relaxed);
+
+		gsi_insert_on_edge (e, read);
+		gsi_insert_on_edge (e, flush);
+	    }
+	    else
+	    {
+		gassign *read = gimple_build_assign (tmp, ref);
+		tmp = gimple_assign_lhs (read);
+		gsi_insert_on_edge (e, read);
+		ref = unshare_expr (ref);
+		emit_bitwise_op (e, ref, accu[k], BIT_IOR_EXPR, tmp);
+	    }
+	}
+    }
+    return expr.size ();
+}
+
+#undef CONDITIONS_MAX_TERMS
+#undef EDGE_CONDITION
+
 /* Do initialization work for the edge profiler.  */
 
 /* Add code:
@@ -758,7 +1726,7 @@ tree_profiling (void)
 	  thunk = true;
 	  /* When generate profile, expand thunk to gimple so it can be
 	     instrumented same way as other functions.  */
-	  if (profile_arc_flag)
+	  if (profile_arc_flag || profile_condition_flag)
 	    expand_thunk (node, false, true);
 	  /* Read cgraph profile but keep function as thunk at profile-use
 	     time.  */
@@ -803,7 +1771,7 @@ tree_profiling (void)
   release_profile_file_filtering ();
 
   /* Drop pure/const flags from instrumented functions.  */
-  if (profile_arc_flag || flag_test_coverage)
+  if (profile_arc_flag || profile_condition_flag || flag_test_coverage)
     FOR_EACH_DEFINED_FUNCTION (node)
       {
 	if (!gimple_has_body_p (node->decl)
@@ -897,7 +1865,7 @@ pass_ipa_tree_profile::gate (function *)
      disabled.  */
   return (!in_lto_p && !flag_auto_profile
 	  && (flag_branch_probabilities || flag_test_coverage
-	      || profile_arc_flag));
+	      || profile_arc_flag || profile_condition_flag));
 }
 
 } // anon namespace
-- 
2.34.0


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-07-08 13:45 ` Sebastian Huber
@ 2022-07-11  7:26   ` Jørgen Kvalsvik
  0 siblings, 0 replies; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-07-11  7:26 UTC (permalink / raw)
  To: Sebastian Huber, gcc-patches

On 08/07/2022 15:45, Sebastian Huber wrote:
> Hello Jørgen,
> 
> some time passed. It would be nice if you could give a status update. I am quite
> interested in your work.
> 
> Best regards,
>     Sebastian
> 

I'm sorry for the late updates. There have have been a lot of issues (with the
patch) to sort out and some other immediate priorities getting in the way. I
will resubmit the updated patch now.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-03-21 11:55 Jørgen Kvalsvik
  2022-03-24 16:08 ` Martin Liška
  2022-04-04  8:14 ` Sebastian Huber
@ 2022-07-08 13:45 ` Sebastian Huber
  2022-07-11  7:26   ` Jørgen Kvalsvik
  2 siblings, 1 reply; 39+ messages in thread
From: Sebastian Huber @ 2022-07-08 13:45 UTC (permalink / raw)
  To: Jørgen Kvalsvik, gcc-patches

Hello Jørgen,

some time passed. It would be nice if you could give a status update. I 
am quite interested in your work.

Best regards,
     Sebastian

-- 
embedded brains GmbH
Herr Sebastian HUBER
Dornierstr. 4
82178 Puchheim
Germany
email: sebastian.huber@embedded-brains.de
phone: +49-89-18 94 741 - 16
fax:   +49-89-18 94 741 - 08

Registergericht: Amtsgericht München
Registernummer: HRB 157899
Vertretungsberechtigte Geschäftsführer: Peter Rasmussen, Thomas Dörfler
Unsere Datenschutzerklärung finden Sie hier:
https://embedded-brains.de/datenschutzerklaerung/

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-04-22  5:37         ` Sebastian Huber
@ 2022-04-22 10:13           ` Jørgen Kvalsvik
  0 siblings, 0 replies; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-04-22 10:13 UTC (permalink / raw)
  To: Sebastian Huber, gcc-patches

On 22/04/2022 07:37, Sebastian Huber wrote:
> 
> 
> On 17/04/2022 13:27, Jørgen Kvalsvik wrote:
>>> In theory, would it be possible to print the state of the truth table with the
>>> information available in the gcda and gcno files? For example:
>>>
>>> Truth table for: a && (b || c)) && d
>>>
>>> 0 | 1 | 2 | 3 || covered
>>> --+---+---+---++--------
>>> 0 | X | X | X || Y
>>> 0 | X | X | X || Y
>>> 0 | X | X | X || Y
>>> 0 | X | X | X || Y
>>> 0 | X | X | X || Y
>>> 0 | X | X | X || Y
>>> 0 | X | X | X || Y
>>> 0 | X | X | X || Y
>>> 1 | 0 | 0 | X || N
>>> 1 | 0 | 0 | X || N
>>> 1 | 0 | 1 | 0 || N
>>> 1 | 0 | 1 | 1 || N
>>> 1 | 1 | X | 0 || Y
>>> 1 | 1 | X | 0 || Y
>>> 1 | 1 | X | 1 || Y
>>> 1 | 1 | X | 1 || Y
>> Maybe? We would at least need to store the masking tables too, which right now
>> are implicitly stored as in the instrumentation. It's not too bad, but it
>> probably means the two functions should return some richer structure, which in
>> turn means a little bit of redesign. Computing the truth table itself shouldn't
>> be difficult.
> 
> Using the tool in the context of safety-critical application would normally
> require also a tool qualification. For GCC, this is a bit unrealistic. It would
> help if the tool output can be verified. Being able to inspect the masking
> tables could help a reviewer to check what the tool did for a sample set of inputs.
> 

It would be useful for the developer too. Recording the masking vectors isn't
hard (they have to be computed after all), maybe it as opt-in behind a flag?

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-04-17 11:27       ` Jørgen Kvalsvik
@ 2022-04-22  5:37         ` Sebastian Huber
  2022-04-22 10:13           ` Jørgen Kvalsvik
  0 siblings, 1 reply; 39+ messages in thread
From: Sebastian Huber @ 2022-04-22  5:37 UTC (permalink / raw)
  To: Jørgen Kvalsvik, gcc-patches



On 17/04/2022 13:27, Jørgen Kvalsvik wrote:
>> In theory, would it be possible to print the state of the truth table with the
>> information available in the gcda and gcno files? For example:
>>
>> Truth table for: a && (b || c)) && d
>>
>> 0 | 1 | 2 | 3 || covered
>> --+---+---+---++--------
>> 0 | X | X | X || Y
>> 0 | X | X | X || Y
>> 0 | X | X | X || Y
>> 0 | X | X | X || Y
>> 0 | X | X | X || Y
>> 0 | X | X | X || Y
>> 0 | X | X | X || Y
>> 0 | X | X | X || Y
>> 1 | 0 | 0 | X || N
>> 1 | 0 | 0 | X || N
>> 1 | 0 | 1 | 0 || N
>> 1 | 0 | 1 | 1 || N
>> 1 | 1 | X | 0 || Y
>> 1 | 1 | X | 0 || Y
>> 1 | 1 | X | 1 || Y
>> 1 | 1 | X | 1 || Y
> Maybe? We would at least need to store the masking tables too, which right now
> are implicitly stored as in the instrumentation. It's not too bad, but it
> probably means the two functions should return some richer structure, which in
> turn means a little bit of redesign. Computing the truth table itself shouldn't
> be difficult.

Using the tool in the context of safety-critical application would 
normally require also a tool qualification. For GCC, this is a bit 
unrealistic. It would help if the tool output can be verified. Being 
able to inspect the masking tables could help a reviewer to check what 
the tool did for a sample set of inputs.

-- 
embedded brains GmbH
Herr Sebastian HUBER
Dornierstr. 4
82178 Puchheim
Germany
email: sebastian.huber@embedded-brains.de
phone: +49-89-18 94 741 - 16
fax:   +49-89-18 94 741 - 08

Registergericht: Amtsgericht München
Registernummer: HRB 157899
Vertretungsberechtigte Geschäftsführer: Peter Rasmussen, Thomas Dörfler
Unsere Datenschutzerklärung finden Sie hier:
https://embedded-brains.de/datenschutzerklaerung/

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-04-07 12:04         ` Martin Liška
@ 2022-04-19 14:22           ` Jørgen Kvalsvik
  0 siblings, 0 replies; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-04-19 14:22 UTC (permalink / raw)
  To: Martin Liška, gcc-patches

On 07/04/2022 14:04, Martin Liška wrote:
> On 3/28/22 16:40, Jørgen Kvalsvik via Gcc-patches wrote:
>> ... And with another tiny change that fixes Martin's while (1); case.
> 
> Hello.
> 
> Back to this ;) Thank you for the updated version of the patch. I have a couple
> of comments/requests:

Sorry for the late response, this email was eaten by spam filtering for some reason.

> 1) I noticed the following cannot be linked:
> 
> $ cat errors.C
> char trim_filename_name;
> int r;
> 
> void trim_filename() {
>   if (trim_filename_name)
>     r = 123;
>   while (trim_filename_name)
>     ;
> }
> 
> int
> main() {}
> 
> $ g++ errors.C -fprofile-conditions -O2
> mold: error: undefined symbol: /tmp/ccmZANB5.o: __gcov8._Z13trim_filenamev
> collect2: error: ld returned 1 exit status

I was able to reproduce this, and I think I have it fixed (that is, I cannot
reproduce it anymore). I looks like the counters were allocated, but never used
in the absence of an output file. I changed it so that the writes are guarded
behind an output check and the counter instrumentation is unconditional which
makes it go away.

The coverage data dependencies are more interleaved (at least currently) than
all the other counters, which makes the flow awkward. I am not convinced a
proper fix is actually worth it, or if anything it would be an separate next step.


> Btw. how much have you tested the patch set so far? Have you tried building
> something bigger
> with -fprofile-conditions enabled?
> 

My own test suite plus zlib, and it looks like linux is compiling fine. I
haven't rigorously studied the _output_ in these projects yet, and it is very
time consuming without a benchmark.

> 2) As noticed by Sebastian, please support the new tag in gcov-dump:
> 
> $ gcov-dump -l a.gcno
> ...
> a.gcno:    01450000:  28:LINES
> a.gcno:                  block 7:`a.c':11
> a.gcno:    01470000:   8:UNKNOWN
> 

Will do.

> 3) Then I have some top-level formatting comments:
> 
> a) please re-run check_GNU_style.py, I still see:
> === ERROR type #1: blocks of 8 spaces should be replaced with tabs (35 error(s))
> ===
> ...
> 
> b) write comments for each newly added function (and separate it by one empty
> line from
> the function definition)
> 
> c) do not create visual alignment, we don't use it:
> 
> +       cond->set ("count",   new json::integer_number (count));
> +       cond->set ("covered", new json::integer_number (covered));
> 
> and similar examples
> 
> d) finish multiple comments after a dot on the same line:
> 
> +    /* Number of expressions found - this value is the number of entries in the
> +       gcov output and the parameter to gcov_counter_alloc ().
> +       */
> 
> should be ... gcov_counter_alloc ().  */
> 
> e) for long lines with a function call like:
> 
> +           const int n = find_conditions_masked_by
> +               (block, expr, flags + k, path, CONDITIONS_MAX_TERMS);
> 
> do rather
> const int n
>   = find_conditions_masked_by (block, expr,
>                                next_arg, ...);
> 
> f) for function params:
> 
> +int
> +collect_reachable_conditionals (
> +    basic_block pre,
> +    basic_block post,
> +    basic_block *out,
> +    int maxsize,
> +    sbitmap expr)
> 
> use rather:
> 
> int
> collect_reachable_conditionals (basic_block pre,
>                                 second_arg,...
> 

Consider it done for the next revision.

> In the next round, I'm going to take a look at the CFG algorithm that identifies
> and instruments the sub-expressions.

Thank you.


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-04-06  7:35     ` Sebastian Huber
@ 2022-04-17 11:27       ` Jørgen Kvalsvik
  2022-04-22  5:37         ` Sebastian Huber
  0 siblings, 1 reply; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-04-17 11:27 UTC (permalink / raw)
  To: Sebastian Huber, gcc-patches

On 06/04/2022 09:35, Sebastian Huber wrote:
> Ok, for the default output this is good. The output can be explained in the
> documentation. I will try to help here.

Splendid, thanks! I would be perfectly happy with better and/or more intuitive
messages too, but I figured it shouldn't delay the rest of the algorithm.

> In theory, would it be possible to print the state of the truth table with the
> information available in the gcda and gcno files? For example:
> 
> Truth table for: a && (b || c)) && d
> 
> 0 | 1 | 2 | 3 || covered
> --+---+---+---++--------
> 0 | X | X | X || Y
> 0 | X | X | X || Y
> 0 | X | X | X || Y
> 0 | X | X | X || Y
> 0 | X | X | X || Y
> 0 | X | X | X || Y
> 0 | X | X | X || Y
> 0 | X | X | X || Y
> 1 | 0 | 0 | X || N
> 1 | 0 | 0 | X || N
> 1 | 0 | 1 | 0 || N
> 1 | 0 | 1 | 1 || N
> 1 | 1 | X | 0 || Y
> 1 | 1 | X | 0 || Y
> 1 | 1 | X | 1 || Y
> 1 | 1 | X | 1 || Y

Maybe? We would at least need to store the masking tables too, which right now
are implicitly stored as in the instrumentation. It's not too bad, but it
probably means the two functions should return some richer structure, which in
turn means a little bit of redesign. Computing the truth table itself shouldn't
be difficult.

> Would it be possible to map the condition index to a source code snippet? For
> example condition 1 to "b"?

Not sure - I didn't find an obvious way from location, but gcc is already able
to point to bad names in complex expressions so I'm sure it is doable. If it can
warn about possibly-uninitialized there's no reason it shouldn't be able to
figure this out. It has the basic block for the condition.

-

I think I have figured out the correct approach for computing masking vectors,
which means I should be able to submit the updated patch next week. I have a
little bit more testing to do before that, including trying out the nested-for
error Sebastian found.

I also went back on a decision:

	if (a) if (b) if (c) { ... }; // no else

should be treated as if (a && b && c) {}. It is to be expected that a compiler
makes some minor transformations, and it is more correct that masking works for
(a && b && c) than for the (equivalent) nested-if being different expressions.
The same kind of "reinterpretation" happens with branch coverage, so there is
some precedence. This also means updating a few tests and figuring out how to
handle the conditions inserted by destructors. Do they have some special basic
block flag or property?

--
Thanks,
Jørgen

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-04-08  7:33             ` Jørgen Kvalsvik
@ 2022-04-08  8:50               ` Sebastian Huber
  0 siblings, 0 replies; 39+ messages in thread
From: Sebastian Huber @ 2022-04-08  8:50 UTC (permalink / raw)
  To: Jørgen Kvalsvik, gcc-patches

On 08/04/2022 09:33, Jørgen Kvalsvik wrote:
> On 08/04/2022 09:28, Jørgen Kvalsvik wrote:
>> On 07/04/2022 18:53, Sebastian Huber wrote:
>>> Hello Jørgen,
>>>
>>> there could be an issue with conditions in for loops:
>>>
>>>          3:  189:  for (int a = 0; a <= 1; ++a) {
>>> branch  0 taken 2
>>> branch  1 taken 1 (fallthrough)
>>> conditions covered 0/2
>>> condition  0 not covered (true)
>>> condition  0 not covered (false)
>>>
>> Hello,
>>
>> Thanks, I'll have a look at it. There should be a for loop in the test suite,
>> but I'll add this exact example and see if I can reproduce it. This should
>> obviously be 100% covered.
> Sebastian,
> 
> I added this exact example to the test suite, but it reports 2/2 for me (linux
> amd64). Do you have a very different system, and what flags did you use?

Thanks for having a look at this. My setup is a bit more complicated. I 
use an arm cross compiler and run an application on a simulator (Qemu). 
The *.gcda files are created by gcov-tool. It is probably an issue with 
my setup, so maybe we should ignore this issue at the moment.

When I move the code to a separate translation unit:

#include <stdio.h>

static __attribute__((__noinline__)) int f(int a, int b, int c, int d)
{
   if (a || (b && c) || d) {
     return 1;
   } else {
     return 2;
   }
}

void test(void)
{
   for (int a = 0; a <= 1; ++a) {
     for (int b = 0; b <= 1; ++b) {
       for (int c = 0; c <= 1; ++c) {
         for (int d = 0; d <= 1; ++d) {
           printf("%i\n", f(a, b, c, d));
         }
       }
     }
   }
}

I get the right report:

         -:    0:Source:conds.c
         -:    0:Graph:b-xilinx_zynq_a9_qemu/conds.gcno
         -:    0:Data:b-xilinx_zynq_a9_qemu/conds.gcda
         -:    0:Runs:0
         -:    1:#include <stdio.h>
         -:    2:
function f called 16 returned 100% blocks executed 100%
        16:    3:static __attribute__((__noinline__)) int f(int a, int 
b, int c, int d)
         -:    4:{
        16:    5:  if (a || (b && c) || d) {
branch  0 taken 8 (fallthrough)
branch  1 taken 8
branch  2 taken 4 (fallthrough)
branch  3 taken 4
branch  4 taken 2 (fallthrough)
branch  5 taken 2
branch  6 taken 3 (fallthrough)
branch  7 taken 3
conditions covered 8/8
        13:    6:    return 1;
         -:    7:  } else {
         3:    8:    return 2;
         -:    9:  }
         -:   10:}
         -:   11:
function test called 1 returned 100% blocks executed 100%
         1:   12:void test(void)
         -:   13:{
         3:   14:  for (int a = 0; a <= 1; ++a) {
branch  0 taken 2
branch  1 taken 1 (fallthrough)
conditions covered 2/2
         6:   15:    for (int b = 0; b <= 1; ++b) {
branch  0 taken 4
branch  1 taken 2 (fallthrough)
conditions covered 2/2
        12:   16:      for (int c = 0; c <= 1; ++c) {
branch  0 taken 8
branch  1 taken 4 (fallthrough)
conditions covered 2/2
        24:   17:        for (int d = 0; d <= 1; ++d) {
branch  0 taken 16
branch  1 taken 8 (fallthrough)
conditions covered 2/2
        16:   18:          printf("%i\n", f(a, b, c, d));
call    0 returned 16
call    1 returned 16
         -:   19:        }
         -:   20:      }
         -:   21:    }
         -:   22:  }
         1:   23:}

For exactly the same code in another translation unit with more other 
code I get this report:

function f called 16 returned 100% blocks executed 100%
        16:  172:static __attribute__((__noinline__)) int f(int a, int 
b, int c, int d)
         -:  173:{
        16:  174:  if (a || (b && c) || d) {
branch  0 taken 8 (fallthrough)
branch  1 taken 8
branch  2 taken 4 (fallthrough)
branch  3 taken 4
branch  4 taken 2 (fallthrough)
branch  5 taken 2
branch  6 taken 3 (fallthrough)
branch  7 taken 3
conditions covered 8/8
        13:  175:    return 1;
         -:  176:  } else {
         3:  177:    return 2;
         -:  178:  }
         -:  179:}
         -:  180:
function Init called 1 returned 0% blocks executed 93%
         1:  181:static void Init( rtems_task_argument arg )
         -:  182:{
         -:  183:  struct ethernetif ethif;
         -:  184:  struct netif *netif;
         -:  185:
         -:  186:  (void) arg;
         1:  187:  netif = &ethif.netif;
         -:  188:
         3:  189:  for (int a = 0; a <= 1; ++a) {
branch  0 taken 2
branch  1 taken 1 (fallthrough)
conditions covered 0/2
condition  0 not covered (true)
condition  0 not covered (false)
         6:  190:    for (int b = 0; b <= 1; ++b) {
branch  0 taken 4
branch  1 taken 2 (fallthrough)
conditions covered 0/2
condition  0 not covered (true)
condition  0 not covered (false)
        12:  191:      for (int c = 0; c <= 1; ++c) {
branch  0 taken 8
branch  1 taken 4 (fallthrough)
conditions covered 0/2
condition  0 not covered (true)
condition  0 not covered (false)
        24:  192:        for (int d = 0; d <= 1; ++d) {
branch  0 taken 16
branch  1 taken 8 (fallthrough)
conditions covered 0/2
condition  0 not covered (true)
condition  0 not covered (false)
        16:  193:          printf("%i\n", f(a, b, c, d));
call    0 returned 16
call    1 returned 16
         -:  194:        }
         -:  195:      }
         -:  196:    }
         -:  197:  }
         1:  198:  test();
call    0 returned 1

-- 
embedded brains GmbH
Herr Sebastian HUBER
Dornierstr. 4
82178 Puchheim
Germany
email: sebastian.huber@embedded-brains.de
phone: +49-89-18 94 741 - 16
fax:   +49-89-18 94 741 - 08

Registergericht: Amtsgericht München
Registernummer: HRB 157899
Vertretungsberechtigte Geschäftsführer: Peter Rasmussen, Thomas Dörfler
Unsere Datenschutzerklärung finden Sie hier:
https://embedded-brains.de/datenschutzerklaerung/

^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH] Add condition coverage profiling
  2022-04-08  7:28           ` Jørgen Kvalsvik
@ 2022-04-08  7:33             ` Jørgen Kvalsvik
  2022-04-08  8:50               ` Sebastian Huber
  0 siblings, 1 reply; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-04-08  7:33 UTC (permalink / raw)
  To: Sebastian Huber, gcc-patches

On 08/04/2022 09:28, Jørgen Kvalsvik wrote:
> On 07/04/2022 18:53, Sebastian Huber wrote:
>> Hello Jørgen,
>>
>> there could be an issue with conditions in for loops:
>>
>>         3:  189:  for (int a = 0; a <= 1; ++a) {
>> branch  0 taken 2
>> branch  1 taken 1 (fallthrough)
>> conditions covered 0/2
>> condition  0 not covered (true)
>> condition  0 not covered (false)
>>
> 
> Hello,
> 
> Thanks, I'll have a look at it. There should be a for loop in the test suite,
> but I'll add this exact example and see if I can reproduce it. This should
> obviously be 100% covered.

Sebastian,

I added this exact example to the test suite, but it reports 2/2 for me (linux
amd64). Do you have a very different system, and what flags did you use?

Cheers,
Jørgen

^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH] Add condition coverage profiling
  2022-04-07 16:53         ` Sebastian Huber
@ 2022-04-08  7:28           ` Jørgen Kvalsvik
  2022-04-08  7:33             ` Jørgen Kvalsvik
  0 siblings, 1 reply; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-04-08  7:28 UTC (permalink / raw)
  To: Sebastian Huber, gcc-patches

On 07/04/2022 18:53, Sebastian Huber wrote:
> Hello Jørgen,
> 
> there could be an issue with conditions in for loops:
> 
>         3:  189:  for (int a = 0; a <= 1; ++a) {
> branch  0 taken 2
> branch  1 taken 1 (fallthrough)
> conditions covered 0/2
> condition  0 not covered (true)
> condition  0 not covered (false)
> 

Hello,

Thanks, I'll have a look at it. There should be a for loop in the test suite,
but I'll add this exact example and see if I can reproduce it. This should
obviously be 100% covered.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-03-28 14:40       ` Jørgen Kvalsvik
  2022-04-07 12:04         ` Martin Liška
@ 2022-04-07 16:53         ` Sebastian Huber
  2022-04-08  7:28           ` Jørgen Kvalsvik
  1 sibling, 1 reply; 39+ messages in thread
From: Sebastian Huber @ 2022-04-07 16:53 UTC (permalink / raw)
  To: Jørgen Kvalsvik, gcc-patches

Hello Jørgen,

there could be an issue with conditions in for loops:

         3:  189:  for (int a = 0; a <= 1; ++a) {
branch  0 taken 2
branch  1 taken 1 (fallthrough)
conditions covered 0/2
condition  0 not covered (true)
condition  0 not covered (false)

-- 
embedded brains GmbH
Herr Sebastian HUBER
Dornierstr. 4
82178 Puchheim
Germany
email: sebastian.huber@embedded-brains.de
phone: +49-89-18 94 741 - 16
fax:   +49-89-18 94 741 - 08

Registergericht: Amtsgericht München
Registernummer: HRB 157899
Vertretungsberechtigte Geschäftsführer: Peter Rasmussen, Thomas Dörfler
Unsere Datenschutzerklärung finden Sie hier:
https://embedded-brains.de/datenschutzerklaerung/

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-03-28 14:40       ` Jørgen Kvalsvik
@ 2022-04-07 12:04         ` Martin Liška
  2022-04-19 14:22           ` Jørgen Kvalsvik
  2022-04-07 16:53         ` Sebastian Huber
  1 sibling, 1 reply; 39+ messages in thread
From: Martin Liška @ 2022-04-07 12:04 UTC (permalink / raw)
  To: Jørgen Kvalsvik, gcc-patches

On 3/28/22 16:40, Jørgen Kvalsvik via Gcc-patches wrote:
> ... And with another tiny change that fixes Martin's while (1); case.

Hello.

Back to this ;) Thank you for the updated version of the patch. I have a couple of comments/requests:

1) I noticed the following cannot be linked:

$ cat errors.C
char trim_filename_name;
int r;

void trim_filename() {
   if (trim_filename_name)
     r = 123;
   while (trim_filename_name)
     ;
}

int
main() {}

$ g++ errors.C -fprofile-conditions -O2
mold: error: undefined symbol: /tmp/ccmZANB5.o: __gcov8._Z13trim_filenamev
collect2: error: ld returned 1 exit status

Btw. how much have you tested the patch set so far? Have you tried building something bigger
with -fprofile-conditions enabled?

2) As noticed by Sebastian, please support the new tag in gcov-dump:

$ gcov-dump -l a.gcno
...
a.gcno:    01450000:  28:LINES
a.gcno:                  block 7:`a.c':11
a.gcno:    01470000:   8:UNKNOWN

3) Then I have some top-level formatting comments:

a) please re-run check_GNU_style.py, I still see:
=== ERROR type #1: blocks of 8 spaces should be replaced with tabs (35 error(s)) ===
...

b) write comments for each newly added function (and separate it by one empty line from
the function definition)

c) do not create visual alignment, we don't use it:

+       cond->set ("count",   new json::integer_number (count));
+       cond->set ("covered", new json::integer_number (covered));

and similar examples

d) finish multiple comments after a dot on the same line:

+    /* Number of expressions found - this value is the number of entries in the
+       gcov output and the parameter to gcov_counter_alloc ().
+       */

should be ... gcov_counter_alloc ().  */

e) for long lines with a function call like:

+           const int n = find_conditions_masked_by
+               (block, expr, flags + k, path, CONDITIONS_MAX_TERMS);

do rather
const int n
   = find_conditions_masked_by (block, expr,
                                next_arg, ...);

f) for function params:

+int
+collect_reachable_conditionals (
+    basic_block pre,
+    basic_block post,
+    basic_block *out,
+    int maxsize,
+    sbitmap expr)

use rather:

int
collect_reachable_conditionals (basic_block pre,
                                 second_arg,...

In the next round, I'm going to take a look at the CFG algorithm that identifies
and instruments the sub-expressions.

Thanks.

Cheers,
Martin


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-04-05 20:07   ` Jørgen Kvalsvik
@ 2022-04-06  7:35     ` Sebastian Huber
  2022-04-17 11:27       ` Jørgen Kvalsvik
  0 siblings, 1 reply; 39+ messages in thread
From: Sebastian Huber @ 2022-04-06  7:35 UTC (permalink / raw)
  To: Jørgen Kvalsvik, gcc-patches

On 05/04/2022 22:07, Jørgen Kvalsvik wrote:
>>> In action it looks pretty similar to the branch coverage. The -g short
>>> opt carries no significance, but was chosen because it was an available
>>> option with the upper-case free too.
>>>
>>> gcov --conditions:
>>>
>>>           3:   17:void fn (int a, int b, int c, int d) {
>>>           3:   18:    if ((a && (b || c)) && d)
>>> conditions covered 5/8
>>> condition  1 not covered (false)
>>> condition  2 not covered (true)
>>> condition  2 not covered (false)
>>>           1:   19:        x = 1;
>>>           -:   20:    else
>>>           2:   21:        x = 2;
>>>           3:   22:}
>> I have some trouble to understand the output. Would 8/8 mean that we have 100%
>> MC/DC coverage? What does "not covered (false)" or "not covered (true)" mean?
>>
> Yes, 8/8 would mean that the expression is 100% covered (all conditions take on
> both values and have independent effect on the outcome). 

This is really great.

> "not covered" is a
> report of missing coverage, that is "condition  1 not covered (false)" means
> that bit N (N = 1, b in this case) has not taken on false yet, and to achieve
> 100% coverage you need a test case where b = false.
> 
> The wording is arbitrary, and I tried to strike a balance between density,
> clarity, grepability and noise. I am open to other suggestions that would
> improve this.

Ok, for the default output this is good. The output can be explained in 
the documentation. I will try to help here.

In theory, would it be possible to print the state of the truth table 
with the information available in the gcda and gcno files? For example:

Truth table for: a && (b || c)) && d

0 | 1 | 2 | 3 || covered
--+---+---+---++--------
0 | X | X | X || Y
0 | X | X | X || Y
0 | X | X | X || Y
0 | X | X | X || Y
0 | X | X | X || Y
0 | X | X | X || Y
0 | X | X | X || Y
0 | X | X | X || Y
1 | 0 | 0 | X || N
1 | 0 | 0 | X || N
1 | 0 | 1 | 0 || N
1 | 0 | 1 | 1 || N
1 | 1 | X | 0 || Y
1 | 1 | X | 0 || Y
1 | 1 | X | 1 || Y
1 | 1 | X | 1 || Y

Would it be possible to map the condition index to a source code 
snippet? For example condition 1 to "b"?

> 
> Unrelated to this, in typing up some notes on this I found a few minor and one
> quite significant (really, the masking algorithm is broken) error in the
> algorithm, which I am working on correcting. I will propose the new patch with
> these fixes too once I have finished writing and testing it.

Thanks a lot for this work.

-- 
embedded brains GmbH
Herr Sebastian HUBER
Dornierstr. 4
82178 Puchheim
Germany
email: sebastian.huber@embedded-brains.de
phone: +49-89-18 94 741 - 16
fax:   +49-89-18 94 741 - 08

Registergericht: Amtsgericht München
Registernummer: HRB 157899
Vertretungsberechtigte Geschäftsführer: Peter Rasmussen, Thomas Dörfler
Unsere Datenschutzerklärung finden Sie hier:
https://embedded-brains.de/datenschutzerklaerung/

^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH] Add condition coverage profiling
  2022-04-04  8:14 ` Sebastian Huber
  2022-04-05  7:04   ` Sebastian Huber
@ 2022-04-05 20:07   ` Jørgen Kvalsvik
  2022-04-06  7:35     ` Sebastian Huber
  1 sibling, 1 reply; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-04-05 20:07 UTC (permalink / raw)
  To: Sebastian Huber, gcc-patches

On 04/04/2022 10:14, Sebastian Huber wrote:
> Hello Jørgen,
> 
> having support for MC/DC coverage in GCC would be really nice. I tried out your
> latest patch on an arm cross-compiler with Newlib (inhibit_libc is defined).
> Could you please add the following fix to your patch:
> 
> diff --git a/libgcc/libgcov-merge.c b/libgcc/libgcov-merge.c
> index 89741f637e1..9e3e8ee5657 100644
> --- a/libgcc/libgcov-merge.c
> +++ b/libgcc/libgcov-merge.c
> @@ -33,6 +33,11 @@ void __gcov_merge_add (gcov_type *counters __attribute__
> ((unused)),
>                         unsigned n_counters __attribute__ ((unused))) {}
>  #endif
> 
> +#ifdef L_gcov_merge_ior
> +void __gcov_merge_ior (gcov_type *counters  __attribute__ ((unused)),
> +                      unsigned n_counters __attribute__ ((unused))) {}
> +#endif
> +
>  #ifdef L_gcov_merge_topn
>  void __gcov_merge_topn (gcov_type *counters  __attribute__ ((unused)),
>                         unsigned n_counters __attribute__ ((unused))) {}
> 
> It seems that support for the new GCOV_TAG_CONDS is missing in gcov-tool and
> gcov-dump, see "tag_table" in gcc/gcov-dump.c and libgcc/libgcov-util.c.
> 
> On 21/03/2022 12:55, Jørgen Kvalsvik via Gcc-patches wrote:
> [...]
>> Like Wahlen et al this implementation uses bitsets to store conditions,
>> which gcov later interprets. This is very fast, but introduces an max
>> limit for the number of terms in a single boolean expression. This limit
>> is the number of bits in a gcov_unsigned_type (which is typedef'd to
>> uint64_t), so for most practical purposes this would be acceptable.
>> limitation can be relaxed with a more sophisticated way of storing and
>> updating bitsets (for example length-encoding).
> 
> For multi-threaded applications using -fprofile-update=atomic is quite
> important. Unfortunately, not all 32-bit targets support 64-bit atomic
> operations in hardware. There is a target hook to select the size of gcov_type.
> Maybe a dedicated 64-bit type should be used for the bitfield using two 32-bit
> atomic OR if necessary.

I was not very clear here - I select operations (and limits) based on the width
of gcov_unsigned_type, which (judging from other snippets in tree-profile.cc)
can be 32 or 64-bit depending on platform. I assume gcov_type is 32 bit on the
platforms you allude to, in which case it should still work fine but fail on
expressions that would work on 64-bit systems.

If I am wrong here I suppose we should consider what you propose (concatenating
bitfields would also be a strategy for supporting >64 conditions), but in that
case I think the other counters are wrong.

I would appreciate it if someone would take a closer look at the code touching
the gcov type to see if I got it right, which also includes the atomic code.
Should something need fixing I will be happy to do it.

> 
>>
>> In action it looks pretty similar to the branch coverage. The -g short
>> opt carries no significance, but was chosen because it was an available
>> option with the upper-case free too.
>>
>> gcov --conditions:
>>
>>          3:   17:void fn (int a, int b, int c, int d) {
>>          3:   18:    if ((a && (b || c)) && d)
>> conditions covered 5/8
>> condition  1 not covered (false)
>> condition  2 not covered (true)
>> condition  2 not covered (false)
>>          1:   19:        x = 1;
>>          -:   20:    else
>>          2:   21:        x = 2;
>>          3:   22:}
> 
> I have some trouble to understand the output. Would 8/8 mean that we have 100%
> MC/DC coverage? What does "not covered (false)" or "not covered (true)" mean?
> 

Yes, 8/8 would mean that the expression is 100% covered (all conditions take on
both values and have independent effect on the outcome). "not covered" is a
report of missing coverage, that is "condition  1 not covered (false)" means
that bit N (N = 1, b in this case) has not taken on false yet, and to achieve
100% coverage you need a test case where b = false.

The wording is arbitrary, and I tried to strike a balance between density,
clarity, grepability and noise. I am open to other suggestions that would
improve this.

Unrelated to this, in typing up some notes on this I found a few minor and one
quite significant (really, the masking algorithm is broken) error in the
algorithm, which I am working on correcting. I will propose the new patch with
these fixes too once I have finished writing and testing it.

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-04-04  8:14 ` Sebastian Huber
@ 2022-04-05  7:04   ` Sebastian Huber
  2022-04-05 20:07   ` Jørgen Kvalsvik
  1 sibling, 0 replies; 39+ messages in thread
From: Sebastian Huber @ 2022-04-05  7:04 UTC (permalink / raw)
  To: Jørgen Kvalsvik, gcc-patches

Hello Jørgen,

On 04/04/2022 10:14, Sebastian Huber wrote:
> It seems that support for the new GCOV_TAG_CONDS is missing in gcov-tool 
> and gcov-dump, see "tag_table" in gcc/gcov-dump.c and 
> libgcc/libgcov-util.c.

it seems that for gcov-tool no changes are necessary. You added the 
condition bit fields as a new counter to gcov-counter.def and they are 
already properly merged with the help of __gcov_merge_ior().

I guess for gcov-dump it could be useful to add support for 
GCOV_TAG_CONDS used in the notes files.

-- 
embedded brains GmbH
Herr Sebastian HUBER
Dornierstr. 4
82178 Puchheim
Germany
email: sebastian.huber@embedded-brains.de
phone: +49-89-18 94 741 - 16
fax:   +49-89-18 94 741 - 08

Registergericht: Amtsgericht München
Registernummer: HRB 157899
Vertretungsberechtigte Geschäftsführer: Peter Rasmussen, Thomas Dörfler
Unsere Datenschutzerklärung finden Sie hier:
https://embedded-brains.de/datenschutzerklaerung/

^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-03-21 11:55 Jørgen Kvalsvik
  2022-03-24 16:08 ` Martin Liška
@ 2022-04-04  8:14 ` Sebastian Huber
  2022-04-05  7:04   ` Sebastian Huber
  2022-04-05 20:07   ` Jørgen Kvalsvik
  2022-07-08 13:45 ` Sebastian Huber
  2 siblings, 2 replies; 39+ messages in thread
From: Sebastian Huber @ 2022-04-04  8:14 UTC (permalink / raw)
  To: Jørgen Kvalsvik, gcc-patches

Hello Jørgen,

having support for MC/DC coverage in GCC would be really nice. I tried 
out your latest patch on an arm cross-compiler with Newlib (inhibit_libc 
is defined). Could you please add the following fix to your patch:

diff --git a/libgcc/libgcov-merge.c b/libgcc/libgcov-merge.c
index 89741f637e1..9e3e8ee5657 100644
--- a/libgcc/libgcov-merge.c
+++ b/libgcc/libgcov-merge.c
@@ -33,6 +33,11 @@ void __gcov_merge_add (gcov_type *counters 
__attribute__ ((unused)),
                         unsigned n_counters __attribute__ ((unused))) {}
  #endif

+#ifdef L_gcov_merge_ior
+void __gcov_merge_ior (gcov_type *counters  __attribute__ ((unused)),
+                      unsigned n_counters __attribute__ ((unused))) {}
+#endif
+
  #ifdef L_gcov_merge_topn
  void __gcov_merge_topn (gcov_type *counters  __attribute__ ((unused)),
                         unsigned n_counters __attribute__ ((unused))) {}

It seems that support for the new GCOV_TAG_CONDS is missing in gcov-tool 
and gcov-dump, see "tag_table" in gcc/gcov-dump.c and libgcc/libgcov-util.c.

On 21/03/2022 12:55, Jørgen Kvalsvik via Gcc-patches wrote:
[...]
> Like Wahlen et al this implementation uses bitsets to store conditions,
> which gcov later interprets. This is very fast, but introduces an max
> limit for the number of terms in a single boolean expression. This limit
> is the number of bits in a gcov_unsigned_type (which is typedef'd to
> uint64_t), so for most practical purposes this would be acceptable.
> limitation can be relaxed with a more sophisticated way of storing and
> updating bitsets (for example length-encoding).

For multi-threaded applications using -fprofile-update=atomic is quite 
important. Unfortunately, not all 32-bit targets support 64-bit atomic 
operations in hardware. There is a target hook to select the size of 
gcov_type. Maybe a dedicated 64-bit type should be used for the bitfield 
using two 32-bit atomic OR if necessary.

> 
> In action it looks pretty similar to the branch coverage. The -g short
> opt carries no significance, but was chosen because it was an available
> option with the upper-case free too.
> 
> gcov --conditions:
> 
>          3:   17:void fn (int a, int b, int c, int d) {
>          3:   18:    if ((a && (b || c)) && d)
> conditions covered 5/8
> condition  1 not covered (false)
> condition  2 not covered (true)
> condition  2 not covered (false)
>          1:   19:        x = 1;
>          -:   20:    else
>          2:   21:        x = 2;
>          3:   22:}

I have some trouble to understand the output. Would 8/8 mean that we 
have 100% MC/DC coverage? What does "not covered (false)" or "not 
covered (true)" mean?

-- 
embedded brains GmbH
Herr Sebastian HUBER
Dornierstr. 4
82178 Puchheim
Germany
email: sebastian.huber@embedded-brains.de
phone: +49-89-18 94 741 - 16
fax:   +49-89-18 94 741 - 08

Registergericht: Amtsgericht München
Registernummer: HRB 157899
Vertretungsberechtigte Geschäftsführer: Peter Rasmussen, Thomas Dörfler
Unsere Datenschutzerklärung finden Sie hier:
https://embedded-brains.de/datenschutzerklaerung/

^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH] Add condition coverage profiling
  2022-03-28 13:52     ` Jørgen Kvalsvik
@ 2022-03-28 14:40       ` Jørgen Kvalsvik
  2022-04-07 12:04         ` Martin Liška
  2022-04-07 16:53         ` Sebastian Huber
  0 siblings, 2 replies; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-03-28 14:40 UTC (permalink / raw)
  To: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 28698 bytes --]

On 28/03/2022 15:52, Jørgen Kvalsvik wrote:
> Hello,
> 
> I have made some changes based on the review feedback, please have a look. In
> summary:
> 
> * Support for -profile=atomic
> * gcc -fprofile-conditions does not ICE without -fprofile-arcs
> * Indentation and formatting complies with contrib/GNU_style_check
> * Some small adjustment to documentation, phrasing
> 
> As mentioned in the previous message, I think it should only be necessary to do
> atomic fetch/store on the global accumulators, not the locals (as they are
> specific to the activation record). If is wrong then I have no problems
> extending the atomics to the function-local accumulators too, please let me
> know. I added a simple test case for -profile=update, gcov-20.c.
> 
> I've added checks for profile_condition_flag everywhere the profile_arc_flag is
> checked, as they share the same code paths and infrastructure. I also made it so
> that -fprofile-conditions is sufficient for linking gcov.
> 
> gcc will no longer crash on -fprofile-conditions -fprofile-generate by
> explicitly checking coverage_begin_*. I'm not too happy with how it turned out,
> but the condition coverage needs a lot more analysis before it can write out its
> gcov structure, which doesn't fit with the flow of branch_prob ().
> 
> Thanks,
> Jørgen
> 
> On 25/03/2022 20:44, Jørgen Kvalsvik wrote:
>> Hello, and thanks for the review!
>>
>>> 1) Do I correctly understand that __conditions_accu_true/false track
>>> every short-circuit sub-expression of a condition and record
>>> if a given sub-expr is seen to be true or false?
>>
>> Sort-of. It is not really aware of sub-expressions at all, but tracks every
>> boolean condition/term in the full expression, mapped to a bitvector. I usually
>> find it easier to understand visually:
>>
>> if (a || (b && c) || d)
>>
>> terms | a b c d
>> ------|--------
>> true  | 0 0 0 0
>> false | 0 0 0 0
>>
>> Whenever a = true, true becomes 1000 and false remains 0000. a = true would
>> short-circuit, (implemented as a normal edge to the "exit" node of the
>> expression) leaving bcd unevaluated. Coverage is then determined as the number
>> of unset bits in this vector. The accumulators are zero'd on every function
>> entry, and |= into the global "counter" on function exit.
>>
>>> 2) As mentioned these are bit sets, can you please explain why you (&
>>> -value) from these sets?
>>
>> Yes, excellent question. This should answer 3) too, and is the most surprsing
>> thing when unfamiliar with MC/DC.
>>
>> For modified condition/decision coverage we need to prove that a condition's
>> value independently affects the outcome/decision. In other words, would
>> flipping a condition change the outcome?
>>
>> Let's take your if (a || b) check, which says that 1/4 conditions are covered
>> on (0, 1). If a = 1, the expression is always true, and b is never evaluated.
>> In fact, the value of b does not change the outcome at all, so intuitively only
>> 1/4 of the conditions are covered.
>>
>> On (0, 1) b gets evaluated, so in a way we know that a = 0. However, the value
>> of a will no longer affect the outcome because (* || 1) is always true. In a
>> way, you can think of it as reverse short-circuiting, the same thing would
>> happen on (* && 0). This is what Wahlen, Heimdahl, and De Silva calls masking.
>> What I do is figure out when masking must happen by analyzing the CFG and zero
>> out the bits that are masked (i.e. no longer affect the outcome). This is in
>> practice what happens to the bits when regular short circuiting happen.
>>
>> So while (0, 1) "sees" the condition a = false, it cannot prove that it
>> mattered based on your inputs. In general, you need N+1 test cases to cover N
>> conditionals with MC/DC. As a consequence, the only way of covering a = false
>> is (0, 0), which alone would be 2/4 cases covered.
>>
>> I hope this made it clearer, if not I can write a more elaborate explanation
>> with more examples.
>>
>>> 4) I noticed various CFG patterns in tree-profile.cc which are handled. Can
>>> you please explain how the algorithm works even without knowing the original
>>> paper?
>>
>> My paper is not written yet, but I will describe the high-level algorithm (+
>> extracts from source comments) at the end of this email, as it is a bit long.
>>
>>> 5) I noticed the following ICE:
>> Yes, this is an unfortunate flaw - I piggyback on the setup done by branch
>> coverage, which means I silently assume --coverage is used. I was unsure what
>> to do (and meant to ask, sorry!) as it is a larger decision - should
>> condition-coverage also imply branch coverage? Should condition coverage work
>> on its own? Other options?
>>
>> I'm happy to implement either strategy - please advise on what you would
>> prefer.
>>
>>
>>> 6) Please follow the GNU coding style, most notable we replace 8 spaces with 
>>> a tab. And we break line before {, (, ... Remove noexcept specifiers for 
>>> functions and I think most of the newly added functions can be static. And 
>>> each newly added function should have a comment.
>>
>> Sure thing, I'll add some tabs. All the helper functions are already static (in
>> an unnamed namespace), but I can remove the namespace and/or add static.
>>
>>> 7) Please consider supporting of -profile-update=atomic where you'll need to
>>> utilize an atomic builts (see how other instrumentation looks like with
>>> it).
>> Will do! I always intended to support the atomics, not having them there is an
>> oversight. To confirm, I will only need to use atomics for the global counters,
>> right? The per-call accumulators are local to each activation record and should
>> not need the atomics, or am I missing something?
>>
>> ALGORITHM
>>
>> Phase 1: expression isolation from CFG analysis
>>
>> Break the CFG into smaller pieces ("sections") by splitting it on dominators.
>> No expression can cross a dominator, so becomes a natural way of terminating
>> expression searches.
>>
>> For each section, do a BFS from the root node and collect all reachable nodes
>> following edges that point to "conditional" nodes, i.e. nodes with outgoing
>> true&false edges. Series of single-parent single-exit nodes are contracted.
>> This gives a good estimate for an expression, but might include conditions in
>> the if/else blocks.
>>
>> The algorithm then collects the immediate neighborhood, i.e. all nodes adjacent
>> to the collected set, dropping everything in the set itself. Then, a new BFS is
>> performed, but now "upwards" (i.e. following predecessors), skipping any
>> neighbor not dominated by the root (this eliminates loops and other expressions
>> with the same "exit" block, see BFS-up). The intersection of all the paths
>> found with this BFS is the isolated expression.
>>
>> If the root is not a conditional, it is skipped and it's successor
>> becomes the new root. If an expression is found it has a pair of "expression
>> sucessors", and the algorithm recurses into both, evaluating this sub-section
>> in isolation, i.e.
>>
>> if (a || b) {
>> root:
>>     if (c && d) {
>>         ...
>>     }
>>     ...
>> sink:
>> }
>>
>> The sections between the root and sink labels are evaulated as if the outer
>> expression did not exist.
>>
>> From the comments:
>>
>>     1. Collect all nodes reachable from the root node through (contracted) paths
>>        of true/false edges.
>>     2. Collect the neighbors of the reachable node (set).
>>     3. From every node in the neighborhood, walk the up the CFG and mark every
>>        reachable node. Only the nodes reachable from *every* node in the
>>        neighborhood are a part of the first expression.
>>     4. Record the expression plus the two successors of the last (highest-index)
>>        node in the expression, i.e. the last term.
>>     5. Repeat using the two successors as new root nodes.
>>
>>     It is not guaranteed to find nodes in the order of the expression, i.e. it
>>     might find (a || b) && c as [a c b], so the output is sorted by
>>     basic_block->index.
>>
>>     Steps 2 and 3 are necessary to distinguish chained conditionals from
>>     multi-term conditionals, e.g. to separate
>>
>>         if (a)
>>         {
>>             if (b)
>>                 work ();
>>         }
>>         if (a && b)
>>             work ();
>>
>>
>> On BFS-up:
>>
>>     Either block of a conditional could have more decisions and loops, so
>>     isolate the first decision by set-intersecting all paths from the
>>     post-dominator to the entry block.
>>
>>     The function returns the number of blocks from n that make up the leading
>>     expression in prefix order (i.e. the order expected by the instrumenting
>>     code).  When this function returns 0 there are no decisions between pre and
>>     post, and this segment of the CFG can safely be skipped.
>>
>>     The post nodes can have predecessors that do not belong to this subgraph,
>>     which are skipped.  This is expected, for example when there is a
>>     conditional in the else-block of a larger expression:
>>
>>     if (A)
>>     {
>>        if (B) {}
>>     } else {
>>        if (C) {} else {}
>>     }
>>
>>              A
>>           t / \ f
>>            /   \
>>           B     C
>>          /\    / \
>>         /  \  T   F
>>        T    \  \ /
>>         \   |   o
>>          \  |  /
>>           \ | /
>>            \|/
>>             E
>>
>>     Processing [B, E) which looks like:
>>
>>        B
>>       /|
>>      / |
>>     T  |
>>      \ /
>>       E ----> o // predecessor outside [B, e)
>>
>>
>> Phase 2: Masking vector and instrumentation
>>
>> The input to this phase is the basic blocks that make up every expression, plus
>> its two exit nodes, i.e. the 3-term expression (a || b || c) yields the input
>> [a b c T F] to phase 2.
>>
>> For every expression, allocate two local accumulators (bit-vectors), one for
>> the true-vector, one for the false-vector. These are zero'd on function entry
>> and flushed on function exit.
>>
>> For each "conditional" block (a b c), unconditionally set the corresponding bit
>> when an edge in the CFG is taken. Rough pseudo-gimple illustration:
>>
>> if (a) {
>>     accu_true |= 1 << 0
>>     goto done;
>> } else {
>>     accu_false |= 1 << 0
>>     if (b) {
>>         accu_true |= 1 << 1;
>>         goto done;
>>     } else {
>>         accu_false |= 1 << 1;
>>         ...
>>     }
>> }
>>
>> global_true[expr_n]  |= accu_true
>> global_false[expr_n] |= accu_false
>>
>> This alone would more-or-less be branch coverage, but would not prove that
>> terms would independently decide the outcome. To find masking, the expression
>> is walked in search of triangles. A || B as a decision diagram becomes
>>
>>    A
>>   t|\f
>>    | \
>>    |  B
>>    |t/ \f
>>    |/   \
>>    T     F
>>
>> and we search for triangles like ATB, that is nodes with 2 predecessors with
>> the same boolean value, where the two predecessors are connected with the
>> opposite value. This marks a candidate for masking, and the terms masked are
>> determined by chasing predecessors starting at the top of the triangle for as
>> long as the chain of predecessors have the same truth value as the "exit"
>> edges. This set is often empty.
>>
>> The edge that triggers masking is then instrumented with a accu &= mask which
>> wipes out anything that does not independently affect the outcome. Note that
>> nothing is ever wiped from the global accumulator, only the local. The (a || b)
>> example had a false vector of [1, 0] at some point, but it was wiped by the
>> masking operation (b = 1 is the only masking value for a || b).
>>
>> From the comments:
>>
>>     Scan the blocks that make up an expression and look for conditions that
>>     would mask other conditions.  For a deeper discussion on masking, see
>>     Whalen, Heimdahl, De Silva "Efficient Test Coverage Measurement for MC/DC".
>>     Masking is best illustrated with an example:
>>
>>     A || B.  If B is true then A will does not independently affect the
>>     decision.  In a way, this is "reverse" short circuiting, and always work on
>>     the right-hand side of expressions.  * || true is always true and * &&
>>     false is always false - the left-hand-side does not affect the outcome, and
>>     their values should not contribute to modified condition/decision coverage.
>>
>>     A || B interpreted as a decision diagram becomes:
>>
>>      A
>>     t|\f
>>      | \
>>      |  B
>>      |t/ \f
>>      |/   \
>>      T     F
>>
>>     The algorithm looks for triangles like ATB.  Masking right-hand sides happen
>>     when a block has a pair of incoming edges of the same boolean value, and
>>     there is an edge connecting the two predecessors with the *opposite* boolean
>>     value.  The masking block is B, and the masked block is A.  In this
>>     particular example:
>>
>>     Masking can affect "outside" its own subexpression; in A || (B && C) if C is
>>     false, B is masked.  However, if (B && C) is true, A gets masked.  B && C
>>     can be determined from evaluating C since !B would short-circuit, so a true
>>     C would mask A.
>>
>>      A
>>     t|\f
>>      | \
>>      |  \
>>      |   \
>>      |    B
>>      |  t/ \f
>>      |  C   |
>>      |t/ \f |
>>      |/   \ |
>>      T     F
>>
>>     Notice how BFC forms a triangle.  Expressions masked by an edge are
>>     determined by:
>>     * Go to the predecessor with truth value b (if it exists).
>>     * Follow the path of ancestors with taking only !b edges, recording every
>>       node from here on.
>>
>>     For the case where C can mask A, the path goes C [true]-> B -> [false] A, so
>>     C [true] masks A.
>>
>>     The mask is output as a bit-set stored in a gcov_type_unsigned.  The
>>     bit-sets are output in pairs, one for each decision (the outcome of the
>>     boolean expression, or which arc to take in the CFG).
>>
>> Phase 3: reading the report
>>
>> This is pretty standard gcov stuff, except the "counters" are interpreted as
>> bitsets, and the number of terms in the expressions is stored. Coverage then
>> becomes popcount / expected.
>>
>> Final remarks:
>>
>> Granted, this is a bit dense, but I think the overall approach is easy to grasp
>> when supported by a lot of example graphs and stepping. I will try to type up a
>> bunch with the paper and publish, but for now all I have is the code (which I
>> hope for the most part is easy to follow). I'll update this with style updates
>> and atomics support, but I would prefer a maintainer to make the call if this
>> condition coverage should imply branch coverage, or if it should work without it.
>>
>> Thanks,
>> Jørgen
>>
>> On 24/03/2022 17:08, Martin Liška via Gcc-patches wrote:
>>> On 3/21/22 12:55, Jørgen Kvalsvik via Gcc-patches wrote:
>>>
>>> Hello.
>>>
>>> Thanks for very interesting extension of the existing GCOV profiling.
>>>
>>> I briefly read the patch and investigated what happens and I've got various
>>> questions/comments about it:
>>>
>>> 1) Do I correctly understand that __conditions_accu_true/false track
>>> every short-circuit sub-expression of a condition and record
>>> if a given sub-expr is seen to be true or false?
>>>
>>> 2) As mentioned these are bit sets, can you please explain why you (& -value)
>>> from these sets?
>>>
>>> 3) I noticed a false report for a simple test-case:
>>>
>>> $ cat cond.c
>>> int x;
>>>
>>> void foo (int a, int b)
>>> {
>>>      if (a || b)
>>>        x = 1;
>>>      else
>>>        x = 2;
>>> }
>>>
>>> int main()
>>> {
>>>    foo (0, 1);
>>>    return 0;
>>> }
>>>
>>> $ rm *gcda ; gcc --coverage cond.c -fprofile-conditions && ./a.out && gcov -g -t a-cond.c
>>>          -:    0:Source:cond.c
>>>          -:    0:Graph:a-cond.gcno
>>>          -:    0:Data:a-cond.gcda
>>>          -:    0:Runs:1
>>>          -:    1:int x;
>>>          -:    2:
>>>          1:    3:void foo (int a, int b)
>>>          -:    4:{
>>>          1:    5:    if (a || b)
>>> conditions covered 1/4
>>> condition  0 not covered (true)
>>> condition  0 not covered (false) <-- this was executed if I'm correct
>>> condition  1 not covered (false)
>>>          1:    6:      x = 1;
>>>          -:    7:    else
>>>      #####:    8:      x = 2;
>>>          1:    9:}
>>>          -:   10:
>>>          1:   11:int main()
>>>          -:   12:{
>>>          1:   13:  foo (0, 1);
>>>          1:   14:  return 0;
>>>          -:   15:}
>>>
>>> 4) I noticed various CFG patterns in tree-profile.cc which are handled. Can you please
>>> explain how the algorithm works even without knowing the original paper?
>>>
>>> 5) I noticed the following ICE:
>>>
>>> $ gcc cond.c -fprofile-conditions -fprofile-generate
>>> during IPA pass: profile
>>> cond.c: In function ?foo?:
>>> cond.c:15:1: internal compiler error: Segmentation fault
>>>     15 | }
>>>        | ^
>>> 0xf4d64a crash_signal
>>> 	/home/marxin/Programming/gcc/gcc/toplev.cc:322
>>> 0x7ffff78b93cf ???
>>> 	/usr/src/debug/glibc-2.35-2.1.x86_64/signal/../sysdeps/unix/sysv/linux/x86_64/libc_sigaction.c:0
>>> 0x7ffff78f29ed __GI__IO_ftell
>>> 	/usr/src/debug/glibc-2.35-2.1.x86_64/libio/ioftell.c:37
>>> 0xa9dfda gcov_position
>>> 	/home/marxin/Programming/gcc/gcc/gcov-io.cc:48
>>> 0xa9dfda gcov_write_tag(unsigned int)
>>> 	/home/marxin/Programming/gcc/gcc/gcov-io.cc:321
>>> 0xe9734a branch_prob(bool)
>>> 	/home/marxin/Programming/gcc/gcc/profile.cc:1542
>>> 0x1032e08 tree_profiling
>>> 	/home/marxin/Programming/gcc/gcc/tree-profile.cc:1620
>>> 0x1032e08 execute
>>> 	/home/marxin/Programming/gcc/gcc/tree-profile.cc:1726
>>> Please submit a full bug report, with preprocessed source (by using -freport-bug).
>>> Please include the complete backtrace with any bug report.
>>> See <https://gcc.gnu.org/bugs/> for instructions.
>>>
>>> 6) Please follow the GNU coding style, most notable we replace 8 spaces with a tab.
>>> And we break line before {, (, ... Remove noexcept specifiers for functions and I think
>>> most of the newly added functions can be static. And each newly added function should
>>> have a comment.
>>>
>>> 7) Please consider supporting of -profile-update=atomic where you'll need to utilize
>>> an atomic builts (see how other instrumentation looks like with it).
>>>
>>> That's the very first round of the review. Hope it's helpful.
>>>
>>> Cheers,
>>> Martin
>>>
>>>> This patch adds support in gcc+gcov for modified condition/decision
>>>> coverage (MC/DC) with the -fprofile-conditions flag. MC/DC is a type of
>>>> test/code coverage, and it is particularly important in the avation and
>>>> automotive industries for safety-critical applications. In particular,
>>>> it is required or recommended by:
>>>>
>>>>      * DO-178C for the most critical software (Level A) in avionics
>>>>      * IEC 61508 for SIL 4
>>>>      * ISO 26262-6 for ASIL D
>>>>
>>>>  From the SQLite webpage:
>>>>
>>>>      Two methods of measuring test coverage were described above:
>>>>      "statement" and "branch" coverage. There are many other test
>>>>      coverage metrics besides these two. Another popular metric is
>>>>      "Modified Condition/Decision Coverage" or MC/DC. Wikipedia defines
>>>>      MC/DC as follows:
>>>>
>>>>          * Each decision tries every possible outcome.
>>>>          * Each condition in a decision takes on every possible outcome.
>>>>          * Each entry and exit point is invoked.
>>>>          * Each condition in a decision is shown to independently affect
>>>>            the outcome of the decision.
>>>>
>>>>      In the C programming language where && and || are "short-circuit"
>>>>      operators, MC/DC and branch coverage are very nearly the same thing.
>>>>      The primary difference is in boolean vector tests. One can test for
>>>>      any of several bits in bit-vector and still obtain 100% branch test
>>>>      coverage even though the second element of MC/DC - the requirement
>>>>      that each condition in a decision take on every possible outcome -
>>>>      might not be satisfied.
>>>>
>>>>      https://sqlite.org/testing.html#mcdc
>>>>
>>>> Wahlen, Heimdahl, and De Silva "Efficient Test Coverage Measurement for
>>>> MC/DC" describes an algorithm for adding instrumentation by carrying
>>>> over information from the AST, but my algorithm does analysis on the
>>>> control flow graph. This should make it work for any language gcc
>>>> supports, although I have only tested it on constructs in C and C++, see
>>>> testsuite/gcc.misc-tests and testsuite/g++.dg.
>>>>
>>>> Like Wahlen et al this implementation uses bitsets to store conditions,
>>>> which gcov later interprets. This is very fast, but introduces an max
>>>> limit for the number of terms in a single boolean expression. This limit
>>>> is the number of bits in a gcov_unsigned_type (which is typedef'd to
>>>> uint64_t), so for most practical purposes this would be acceptable.
>>>> limitation can be relaxed with a more sophisticated way of storing and
>>>> updating bitsets (for example length-encoding).
>>>>
>>>> In action it looks pretty similar to the branch coverage. The -g short
>>>> opt carries no significance, but was chosen because it was an available
>>>> option with the upper-case free too.
>>>>
>>>> gcov --conditions:
>>>>
>>>>          3:   17:void fn (int a, int b, int c, int d) {
>>>>          3:   18:    if ((a && (b || c)) && d)
>>>> conditions covered 5/8
>>>> condition  1 not covered (false)
>>>> condition  2 not covered (true)
>>>> condition  2 not covered (false)
>>>>          1:   19:        x = 1;
>>>>          -:   20:    else
>>>>          2:   21:        x = 2;
>>>>          3:   22:}
>>>>
>>>> gcov --conditions --json-format:
>>>>
>>>>      "conditions": [
>>>>          {
>>>>              "not_covered_false": [
>>>>                  1,
>>>>                  2
>>>>              ],
>>>>              "count": 8,
>>>>              "covered": 5,
>>>>              "not_covered_true": [
>>>>                  2
>>>>              ]
>>>>          }
>>>>      ],
>>>>
>>>> C++ destructors will add extra conditionals that are not explicitly
>>>> present in source code. These are present in the CFG, which means the
>>>> condition coverage will show them.
>>>>
>>>> gcov --conditions
>>>>
>>>>          -:    5:struct A {
>>>>          1:    6:    explicit A (int x) : v (x) {}
>>>>          1:    7:    operator bool () const { return bool(v); }
>>>>          1:    8:    ~A() {}
>>>>          -:    9:
>>>>          -:   10:    int v;
>>>>          -:   11:};
>>>>          -:   12:
>>>>          2:   13:void fn (int a, int b) {
>>>>         2*:   14:    x = a && A(b);
>>>> conditions covered 2/4
>>>> condition  0 not covered (true)
>>>> condition  1 not covered (true)
>>>> conditions covered 2/2
>>>>
>>>> They are also reported by the branch coverage.
>>>>
>>>> gcov -bc
>>>>
>>>>         2*:   14:    x = a && A(b);
>>>> branch  0 taken 1 (fallthrough)
>>>> branch  1 taken 1
>>>> call    2 returned 1
>>>> call    3 returned 1
>>>> branch  4 taken 0 (fallthrough)
>>>> branch  5 taken 1
>>>> branch  6 taken 1 (fallthrough)
>>>> branch  7 taken 1
>>>>
>>>> The algorithm struggles in a particular case where gcc would generate
>>>> identical CFGs for different expressions:
>>>>
>>>> and.c:
>>>>
>>>>      if (a && b && c)
>>>>          x = 1;
>>>>
>>>> ifs.c:
>>>>
>>>>      if (a)
>>>>          if (b)
>>>>              if (c)
>>>>                  x = 1;
>>>>
>>>>      if (a && b && c)
>>>>          x = 1;
>>>>
>>>> and.c.gcov:
>>>>
>>>>              #####:    2:    if (a && b && c)
>>>> conditions covered 2/2
>>>> conditions covered 2/2
>>>> conditions covered 2/2
>>>>      #####:    3:        x = 1;
>>>>
>>>> ifs.c.gcov:
>>>>      #####:    2:    if (a)
>>>> conditions covered 2/2
>>>>      #####:    3:   2    if (b)
>>>> conditions covered 2/2
>>>>      #####:    4:   2        if (c)
>>>> conditions covered 2/2
>>>>      #####:    5:                x = 1;
>>>>
>>>> These programs are semantically equivalent, and are interpreted as 3
>>>> separate conditional expressions. It does not matter w.r.t. coverage,
>>>> but is not ideal. Adding an else to and.c fixes the issue:
>>>>
>>>>      #####:    2:    if (a && b && c)
>>>> conditions covered 6/6
>>>>      #####:    3:        x = 1;
>>>>      #####:    4:    else x = x;
>>>>
>>>> In the (a && b && c) case the else block must be shared between all
>>>> three conditionals, and for if (a) if (b) if (c) there would be three
>>>> *separate* else blocks.
>>>>
>>>> Since the algorithm works on CFGs, it cannot detect conditionals (from
>>>> ternaries) that don't get an entry in the cfg. For example,
>>>> int x = a ? 0 : 1 in gimple becomes _x = (_a == 0). From source you
>>>> would expect coverage, but it gets neither branch nor condition
>>>> coverage. For completeness, it could be achieved by scanning all gimple
>>>> statements for comparisons, and insert an extra instruction for
>>>> recording the outcome.
>>>>
>>>> The test suite consists of all different CFG kinds and control flow
>>>> structures I could find, but there is certainly room for many more.
>>>>
>>>> Alternative email: Jørgen Kvalsvik <j@lambda.is>
>>>>
>>>> --
>>>>
>>>> Some final remarks (not a part of the commit message), this is written in a C++ style that I felt meshed well with what was already there, but if the maintainers would prefer a different approach then I'm happy to to make adjustments. I plan to write a more thorough description of the algorithm, as well as adding the same feature to clang as it is very useful for us at Woven Planet.
>>>>
>>>> I tried to pick flag names and options that were comfortable and descriptive, but if you have better names or different ideas for flags then I am happy to change them. The --coverage flag was not changed to imply -fprofile-conditions, but it is possibly something to consider.
>>>>
>>>> gcc/ChangeLog:
>>>>
>>>> 	* common.opt: Add new options -fprofile-conditions and
>>>> 	-Wcoverage-too-many-conditions.
>>>> 	* doc/gcov.texi: Add --conditions documentation.
>>>> 	* doc/invoke.texi: Add -fprofile-conditions documentation.
>>>> 	* gcov-counter.def (GCOV_COUNTER_CONDS): New.
>>>> 	* gcov-io.h (GCOV_TAG_CONDS): New.
>>>> 	(GCOV_TAG_CONDS_LENGTH): Likewise.
>>>> 	(GCOV_TAG_CONDS_NUM): Likewise.
>>>> 	* gcov.cc (class condition_info): New.
>>>> 	(condition_info::condition_info): New.
>>>> 	(condition_info::popcount): New.
>>>> 	(struct coverage_info): New.
>>>> 	(add_condition_counts): New.
>>>> 	(output_conditions): New.
>>>> 	(print_usage): Add -g, --conditions.
>>>> 	(process_args): Likewise.
>>>> 	(output_intermediate_json_line): Output conditions.
>>>> 	(read_graph_file): Read conditions counters.
>>>> 	(read_count_file): Read conditions counters.
>>>> 	(file_summary): Print conditions.
>>>> 	(accumulate_line_info): Accumulate conditions.
>>>> 	(output_line_details): Print conditions.
>>>> 	* profile.cc (find_conditions): New declaration.
>>>> 	(instrument_decisions): New declaration.
>>>> 	(branch_prob): Call find_conditions, instrument_decisions.
>>>> 	(init_branch_prob): Add total_num_conds.
>>>> 	(end_branch_prob): Likewise.
>>>> 	* tree-profile.cc (INCLUDE_ALGORITHM): Define.
>>>> 	(struct conds_ctx): New.
>>>> 	(CONDITIONS_MAX_TERMS): New.
>>>> 	(index_of): New.
>>>> 	(index_lt): New.
>>>> 	(single): New.
>>>> 	(single_edge): New.
>>>> 	(contract_edge): New.
>>>> 	(contract_edge_up): New.
>>>> 	(is_conditional_p): New.
>>>> 	(collect_neighbors): New.
>>>> 	(find_first_conditional): New.
>>>> 	(emit_bitwise_op): New.
>>>> 	(collect_conditions): New.
>>>> 	(find_conditions): New.
>>>> 	(instrument_decisions): New.
>>>>
>>>> gcc/testsuite/ChangeLog:
>>>>
>>>> 	* lib/gcov.exp:
>>>> 	* g++.dg/gcov/gcov-18.C: New test.
>>>> 	* gcc.misc-tests/gcov-19.c: New test.
>>>> ---
>>>>   gcc/common.opt                         |   8 +
>>>>   gcc/doc/gcov.texi                      |  36 ++
>>>>   gcc/doc/invoke.texi                    |  19 +
>>>>   gcc/gcov-counter.def                   |   3 +
>>>>   gcc/gcov-io.h                          |   3 +
>>>>   gcc/gcov.cc                            | 177 +++++-
>>>>   gcc/profile.cc                         |  51 +-
>>>>   gcc/testsuite/g++.dg/gcov/gcov-18.C    | 209 ++++++
>>>>   gcc/testsuite/gcc.misc-tests/gcov-19.c | 726 +++++++++++++++++++++
>>>>   gcc/testsuite/lib/gcov.exp             | 187 +++++-
>>>>   gcc/tree-profile.cc                    | 838 +++++++++++++++++++++++++
>>>>   11 files changed, 2248 insertions(+), 9 deletions(-)
>>>>   create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-18.C
>>>>   create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-19.c
>>

... And with another tiny change that fixes Martin's while (1); case.

[-- Attachment #2: 0001-Add-condition-coverage-profiling.patch --]
[-- Type: text/plain, Size: 91909 bytes --]

From 6d87b1bede67ef1973fd65f357e244429bd1a38a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B8rgen=20Kvalsvik?=
 <jorgen.kvalsvik@woven-planet.global>
Date: Mon, 31 Jan 2022 12:49:37 +0100
Subject: [PATCH] Add condition coverage profiling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This patch adds support in gcc+gcov for modified condition/decision
coverage (MC/DC) with the -fprofile-conditions flag. MC/DC is a type of
test/code coverage, and it is particularly important in the avation and
automotive industries for safety-critical applications. In particular,
it is required or recommended by:

    * DO-178C for the most critical software (Level A) in avionics
    * IEC 61508 for SIL 4
    * ISO 26262-6 for ASIL D

From the SQLite webpage:

    Two methods of measuring test coverage were described above:
    "statement" and "branch" coverage. There are many other test
    coverage metrics besides these two. Another popular metric is
    "Modified Condition/Decision Coverage" or MC/DC. Wikipedia defines
    MC/DC as follows:

        * Each decision tries every possible outcome.
        * Each condition in a decision takes on every possible outcome.
        * Each entry and exit point is invoked.
        * Each condition in a decision is shown to independently affect
          the outcome of the decision.

    In the C programming language where && and || are "short-circuit"
    operators, MC/DC and branch coverage are very nearly the same thing.
    The primary difference is in boolean vector tests. One can test for
    any of several bits in bit-vector and still obtain 100% branch test
    coverage even though the second element of MC/DC - the requirement
    that each condition in a decision take on every possible outcome -
    might not be satisfied.

    https://sqlite.org/testing.html#mcdc

Wahlen, Heimdahl, and De Silva "Efficient Test Coverage Measurement for
MC/DC" describes an algorithm for adding instrumentation by carrying
over information from the AST, but my algorithm does analysis on the
control flow graph. This should make it work for any language gcc
supports, although I have only tested it on constructs in C and C++, see
testsuite/gcc.misc-tests and testsuite/g++.dg.

Like Wahlen et al this implementation uses bitsets to store conditions,
which gcov later interprets. This is very fast, but introduces an max
limit for the number of terms in a single boolean expression. This limit
is the number of bits in a gcov_unsigned_type (which is typedef'd to
uint64_t), so for most practical purposes this would be acceptable.
limitation can be relaxed with a more sophisticated way of storing and
updating bitsets (for example length-encoding).

In action it looks pretty similar to the branch coverage. The -g short
opt carries no significance, but was chosen because it was an available
option with the upper-case free too.

gcov --conditions:

        3:   17:void fn (int a, int b, int c, int d) {
        3:   18:    if ((a && (b || c)) && d)
conditions covered 5/8
condition  1 not covered (false)
condition  2 not covered (true)
condition  2 not covered (false)
        1:   19:        x = 1;
        -:   20:    else
        2:   21:        x = 2;
        3:   22:}

gcov --conditions --json-format:

    "conditions": [
        {
            "not_covered_false": [
                1,
                2
            ],
            "count": 8,
            "covered": 5,
            "not_covered_true": [
                2
            ]
        }
    ],

C++ destructors will add extra conditionals that are not explicitly
present in source code. These are present in the CFG, which means the
condition coverage will show them.

gcov --conditions

        -:    5:struct A {
        1:    6:    explicit A (int x) : v (x) {}
        1:    7:    operator bool () const { return bool(v); }
        1:    8:    ~A() {}
        -:    9:
        -:   10:    int v;
        -:   11:};
        -:   12:
        2:   13:void fn (int a, int b) {
       2*:   14:    x = a && A(b);
conditions covered 2/4
condition  0 not covered (true)
condition  1 not covered (true)
conditions covered 2/2

They are also reported by the branch coverage.

gcov -bc

       2*:   14:    x = a && A(b);
branch  0 taken 1 (fallthrough)
branch  1 taken 1
call    2 returned 1
call    3 returned 1
branch  4 taken 0 (fallthrough)
branch  5 taken 1
branch  6 taken 1 (fallthrough)
branch  7 taken 1

The algorithm struggles in a particular case where gcc would generate
identical CFGs for different expressions:

and.c:

    if (a && b && c)
        x = 1;

ifs.c:

    if (a)
        if (b)
            if (c)
                x = 1;

    if (a && b && c)
        x = 1;

and.c.gcov:

            #####:    2:    if (a && b && c)
conditions covered 2/2
conditions covered 2/2
conditions covered 2/2
    #####:    3:        x = 1;

ifs.c.gcov:
    #####:    2:    if (a)
conditions covered 2/2
    #####:    3:   2    if (b)
conditions covered 2/2
    #####:    4:   2        if (c)
conditions covered 2/2
    #####:    5:                x = 1;

These programs are semantically equivalent, and are interpreted as 3
separate conditional expressions. It does not matter w.r.t. coverage,
but is not ideal. Adding an else to and.c fixes the issue:

    #####:    2:    if (a && b && c)
conditions covered 6/6
    #####:    3:        x = 1;
    #####:    4:    else x = x;

In the (a && b && c) case the else block must be shared between all
three conditionals, and for if (a) if (b) if (c) there would be three
*separate* else blocks.

Since the algorithm works on CFGs, it cannot detect conditionals (from
ternaries) that don't get an entry in the cfg. For example,
int x = a ? 0 : 1 in gimple becomes _x = (_a == 0). From source you
would expect coverage, but it gets neither branch nor condition
coverage. For completeness, it could be achieved by scanning all gimple
statements for comparisons, and insert an extra instruction for
recording the outcome.

The test suite consists of all different CFG kinds and control flow
structures I could find, but there is certainly room for many more.

Alternative email: Jørgen Kvalsvik <j@lambda.is>

gcc/ChangeLog:

	* builtins.cc (expand_builtin_fork_or_exec): Check
	profile_condition_flag.
	* common.opt: Add new options -fprofile-conditions and
	-Wcoverage-too-many-conditions.
	* doc/gcov.texi: Add --conditions documentation.
	* doc/invoke.texi: Add -fprofile-conditions documentation.
	* gcc.cc: Link gcov on -fprofile-conditions.
	* gcov-counter.def (GCOV_COUNTER_CONDS): New.
	* gcov-io.h (GCOV_TAG_CONDS): New.
	(GCOV_TAG_CONDS_LENGTH): Likewise.
	(GCOV_TAG_CONDS_NUM): Likewise.
	* gcov.cc (class condition_info): New.
	(condition_info::condition_info): New.
	(condition_info::popcount): New.
	(struct coverage_info): New.
	(add_condition_counts): New.
	(output_conditions): New.
	(print_usage): Add -g, --conditions.
	(process_args): Likewise.
	(output_intermediate_json_line): Output conditions.
	(read_graph_file): Read conditions counters.
	(read_count_file): Read conditions counters.
	(file_summary): Print conditions.
	(accumulate_line_info): Accumulate conditions.
	(output_line_details): Print conditions.
	* ipa-inline.cc (can_early_inline_edge_p): Check
	profile_condition_flag.
	* ipa-split.cc (pass_split_functions::gate): Likewise.
	* passes.cc (finish_optimization_passes): Likewise.
	* profile.cc (find_conditions): New declaration.
	(instrument_decisions): New declaration.
	(branch_prob): Call find_conditions, instrument_decisions.
	(init_branch_prob): Add total_num_conds.
	(end_branch_prob): Likewise.
	* tree-profile.cc (INCLUDE_ALGORITHM): Define.
	(struct conds_ctx): New.
	(CONDITIONS_MAX_TERMS): New.
	(index_of): New.
	(index_lt): New.
	(single): New.
	(single_edge): New.
	(contract_edge): New.
	(contract_edge_up): New.
	(is_conditional_p): New.
	(collect_neighbors): New.
	(find_first_conditional): New.
	(emit_bitwise_op): New.
	(collect_conditions): New.
	(find_conditions): New.
	(instrument_decisions): New.
	(pass_ipa_tree_profile::gate): Check profile_condition_flag.

gcc/testsuite/ChangeLog:

	* lib/gcov.exp: Add condition coverage test function.
	* g++.dg/gcov/gcov-18.C: New test.
	* gcc.misc-tests/gcov-19.c: New test.
	* gcc.misc-tests/gcov-20.c: New test.
---
 gcc/builtins.cc                        |   2 +-
 gcc/common.opt                         |   8 +
 gcc/doc/gcov.texi                      |  36 +
 gcc/doc/invoke.texi                    |  19 +
 gcc/gcc.cc                             |   2 +-
 gcc/gcov-counter.def                   |   3 +
 gcc/gcov-io.h                          |   3 +
 gcc/gcov.cc                            | 193 +++++-
 gcc/ipa-inline.cc                      |   2 +-
 gcc/ipa-split.cc                       |   3 +-
 gcc/passes.cc                          |   3 +-
 gcc/profile.cc                         |  72 +-
 gcc/testsuite/g++.dg/gcov/gcov-18.C    | 227 +++++++
 gcc/testsuite/gcc.misc-tests/gcov-19.c | 735 +++++++++++++++++++++
 gcc/testsuite/gcc.misc-tests/gcov-20.c |  21 +
 gcc/testsuite/lib/gcov.exp             | 187 +++++-
 gcc/tree-profile.cc                    | 882 ++++++++++++++++++++++++-
 17 files changed, 2373 insertions(+), 25 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-18.C
 create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-19.c
 create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-20.c

diff --git a/gcc/builtins.cc b/gcc/builtins.cc
index 4c6c2939053..669936ef7b6 100644
--- a/gcc/builtins.cc
+++ b/gcc/builtins.cc
@@ -5569,7 +5569,7 @@ expand_builtin_fork_or_exec (tree fn, tree exp, rtx target, int ignore)
   tree call;
 
   /* If we are not profiling, just call the function.  */
-  if (!profile_arc_flag)
+  if (!profile_arc_flag && !profile_condition_flag)
     return NULL_RTX;
 
   /* Otherwise call the wrapper.  This should be equivalent for the rest of
diff --git a/gcc/common.opt b/gcc/common.opt
index 8b6513de47c..2afe4384d9d 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -861,6 +861,10 @@ Wcoverage-invalid-line-number
 Common Var(warn_coverage_invalid_linenum) Init(1) Warning
 Warn in case a function ends earlier than it begins due to an invalid linenum macros.
 
+Wcoverage-too-many-conditions
+Common Var(warn_too_many_conditions) Init(1) Warning
+Warn when a conditional has too many terms and coverage gives up.
+
 Wmissing-profile
 Common Var(warn_missing_profile) Init(1) Warning
 Warn in case profiles in -fprofile-use do not exist.
@@ -2290,6 +2294,10 @@ fprofile-arcs
 Common Var(profile_arc_flag)
 Insert arc-based program profiling code.
 
+fprofile-conditions
+Common Var(profile_condition_flag)
+Insert condition coverage profiling code.
+
 fprofile-dir=
 Common Joined RejectNegative Var(profile_data_prefix)
 Set the top-level directory for storing the profile data.
diff --git a/gcc/doc/gcov.texi b/gcc/doc/gcov.texi
index fc39da0f02d..a85b7f09769 100644
--- a/gcc/doc/gcov.texi
+++ b/gcc/doc/gcov.texi
@@ -122,6 +122,7 @@ gcov [@option{-v}|@option{--version}] [@option{-h}|@option{--help}]
      [@option{-a}|@option{--all-blocks}]
      [@option{-b}|@option{--branch-probabilities}]
      [@option{-c}|@option{--branch-counts}]
+     [@option{-g}|@option{--conditions}]
      [@option{-d}|@option{--display-progress}]
      [@option{-f}|@option{--function-summaries}]
      [@option{-j}|@option{--json-format}]
@@ -167,6 +168,13 @@ be shown, unless the @option{-u} option is given.
 Write branch frequencies as the number of branches taken, rather than
 the percentage of branches taken.
 
+@item -g
+@itemx --conditions
+Write condition coverage to the output file, and write condition summary info
+to the standard output.  This option allows you to see if the conditions in
+your program at least once had an independent effect on the outcome of the
+boolean expression (modified condition/decision coverage).
+
 @item -d
 @itemx --display-progress
 Display the progress on the standard output.
@@ -291,6 +299,7 @@ Each @var{line} has the following form:
 @{
   "branches": ["$branch"],
   "count": 2,
+  "conditions": ["$condition"],
   "line_number": 15,
   "unexecuted_block": false,
   "function_name": "foo",
@@ -339,6 +348,33 @@ Fields of the @var{branch} element have following semantics:
 @var{throw}: true when the branch is an exceptional branch
 @end itemize
 
+Each @var{condition} element have the following semantics:
+
+@smallexample
+@{
+  "count": 4,
+  "covered": 2,
+  "not_covered_false": [],
+  "not_covered_true": [0, 1],
+@}
+@end smallexample
+
+Fields of the @var{condition} element have following semantics:
+
+@itemize @bullet
+@item
+@var{count}: number of conditions in this expression
+
+@item
+@var{covered}: number of covered conditions in this expression
+
+@item
+@var{not_covered_true}: terms, by index, not seen as true in this expression
+
+@item
+@var{not_covered_false}: terms, by index, not seen as false in this expression
+@end itemize
+
 @item -H
 @itemx --human-readable
 Write counts in human readable format (like 24.6k).
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index e3f2e82cde5..0e24e43ca42 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -594,6 +594,7 @@ Objective-C and Objective-C++ Dialects}.
 @item Program Instrumentation Options
 @xref{Instrumentation Options,,Program Instrumentation Options}.
 @gccoptlist{-p  -pg  -fprofile-arcs  --coverage  -ftest-coverage @gol
+-fprofile-conditions @gol
 -fprofile-abs-path @gol
 -fprofile-dir=@var{path}  -fprofile-generate  -fprofile-generate=@var{path} @gol
 -fprofile-info-section  -fprofile-info-section=@var{name} @gol
@@ -6020,6 +6021,13 @@ error.  @option{-Wno-coverage-invalid-line-number} can be used to disable the
 warning or @option{-Wno-error=coverage-invalid-line-number} can be used to
 disable the error.
 
+@item -Wno-coverage-too-many-conditions
+@opindex Wno-coverage-too-many-conditions
+@opindex Wcoverage-too-many-conditions
+Warn in case a condition have too many terms and GCC gives up coverage.
+Coverage is given up when there are more terms in the conditional than there
+are bits in a @code{gcov_type_unsigned}.  This warning is enabled by default.
+
 @item -Wno-cpp @r{(C, Objective-C, C++, Objective-C++ and Fortran only)}
 @opindex Wno-cpp
 @opindex Wcpp
@@ -15271,6 +15279,13 @@ E.g. @code{gcc a.c b.c -o binary} would generate @file{binary-a.gcda} and
 
 @xref{Cross-profiling}.
 
+@item -fprofile-conditions
+@opindex fprofile-conditions
+Add code so that program conditions are instrumented.  During execution the
+program records what terms in a conditional contributes to a decision.  The
+data may be used to verify that all terms in a booleans are tested and have an
+effect on the outcome of a condition.
+
 @cindex @command{gcov}
 @item --coverage
 @opindex coverage
@@ -15331,6 +15346,10 @@ executed.  When an arc is the only exit or only entrance to a block, the
 instrumentation code can be added to the block; otherwise, a new basic
 block must be created to hold the instrumentation code.
 
+With @option{-fprofile-conditions}, for each conditional in your program GCC
+creates a bitset and records the boolean values that have an independent effect
+on the outcome of that expression.
+
 @need 2000
 @item -ftest-coverage
 @opindex ftest-coverage
diff --git a/gcc/gcc.cc b/gcc/gcc.cc
index a4d863ca457..5a25a47b7d0 100644
--- a/gcc/gcc.cc
+++ b/gcc/gcc.cc
@@ -1165,7 +1165,7 @@ proper position among the other output files.  */
 	%:include(libgomp.spec)%(link_gomp)}\
     %{fgnu-tm:%:include(libitm.spec)%(link_itm)}\
     %(mflib) " STACK_SPLIT_SPEC "\
-    %{fprofile-arcs|fprofile-generate*|coverage:-lgcov} " SANITIZER_SPEC " \
+    %{fprofile-arcs|fprofile-conditions|fprofile-generate*|coverage:-lgcov} " SANITIZER_SPEC " \
     %{!nostdlib:%{!r:%{!nodefaultlibs:%(link_ssp) %(link_gcc_c_sequence)}}}\
     %{!nostdlib:%{!r:%{!nostartfiles:%E}}} %{T*}  \n%(post_link) }}}}}}"
 #endif
diff --git a/gcc/gcov-counter.def b/gcc/gcov-counter.def
index 6d2182bd3db..96563a59a45 100644
--- a/gcc/gcov-counter.def
+++ b/gcc/gcov-counter.def
@@ -49,3 +49,6 @@ DEF_GCOV_COUNTER(GCOV_COUNTER_IOR, "ior", _ior)
 
 /* Time profile collecting first run of a function */
 DEF_GCOV_COUNTER(GCOV_TIME_PROFILER, "time_profiler", _time_profile)
+
+/* Conditions.  The counter is interpreted as a bit-set.  */
+DEF_GCOV_COUNTER(GCOV_COUNTER_CONDS, "conditions", _ior)
diff --git a/gcc/gcov-io.h b/gcc/gcov-io.h
index 99ce7dbccc8..c84f58b99ac 100644
--- a/gcc/gcov-io.h
+++ b/gcc/gcov-io.h
@@ -253,6 +253,9 @@ typedef uint64_t gcov_type_unsigned;
 #define GCOV_TAG_ARCS		 ((gcov_unsigned_t)0x01430000)
 #define GCOV_TAG_ARCS_LENGTH(NUM)  (1 + (NUM) * 2 * GCOV_WORD_SIZE)
 #define GCOV_TAG_ARCS_NUM(LENGTH)  (((LENGTH / GCOV_WORD_SIZE) - 1) / 2)
+#define GCOV_TAG_CONDS             ((gcov_unsigned_t)0x01470000)
+#define GCOV_TAG_CONDS_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE)
+#define GCOV_TAG_CONDS_NUM(LENGTH) (((LENGTH) / GCOV_WORD_SIZE) / 2)
 #define GCOV_TAG_LINES		 ((gcov_unsigned_t)0x01450000)
 #define GCOV_TAG_COUNTER_BASE 	 ((gcov_unsigned_t)0x01a10000)
 #define GCOV_TAG_COUNTER_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE)
diff --git a/gcc/gcov.cc b/gcc/gcov.cc
index 27be5ff0911..4db40c18ae7 100644
--- a/gcc/gcov.cc
+++ b/gcc/gcov.cc
@@ -79,6 +79,7 @@ using namespace std;
 class function_info;
 class block_info;
 class source_info;
+class condition_info;
 
 /* Describes an arc between two basic blocks.  */
 
@@ -132,6 +133,28 @@ public:
   vector<unsigned> lines;
 };
 
+class condition_info
+{
+public:
+  condition_info ();
+
+  int popcount () const;
+
+  gcov_type_unsigned truev;
+  gcov_type_unsigned falsev;
+
+  unsigned n_terms;
+};
+
+condition_info::condition_info (): truev (0), falsev (0), n_terms (0)
+{
+}
+
+int condition_info::popcount () const
+{
+    return __builtin_popcountll (truev) + __builtin_popcountll (falsev);
+}
+
 /* Describes a basic block. Contains lists of arcs to successor and
    predecessor blocks.  */
 
@@ -165,6 +188,8 @@ public:
   /* Block is a landing pad for longjmp or throw.  */
   unsigned is_nonlocal_return : 1;
 
+  condition_info conditions;
+
   vector<block_location_info> locations;
 
   struct
@@ -275,6 +300,8 @@ public:
   vector<block_info> blocks;
   unsigned blocks_executed;
 
+  vector<condition_info*> conditions;
+
   /* Raw arc coverage counts.  */
   vector<gcov_type> counts;
 
@@ -351,6 +378,9 @@ struct coverage_info
   int branches_executed;
   int branches_taken;
 
+  int conditions;
+  int conditions_covered;
+
   int calls;
   int calls_executed;
 
@@ -550,6 +580,10 @@ static int multiple_files = 0;
 
 static int flag_branches = 0;
 
+/* Output conditions (modified condition/decision coverage) */
+
+static int flag_conditions = 0;
+
 /* Show unconditional branches too.  */
 static int flag_unconditional = 0;
 
@@ -656,6 +690,7 @@ static int read_count_file (void);
 static void solve_flow_graph (function_info *);
 static void find_exception_blocks (function_info *);
 static void add_branch_counts (coverage_info *, const arc_info *);
+static void add_condition_counts (coverage_info *, const block_info *);
 static void add_line_counts (coverage_info *, function_info *);
 static void executed_summary (unsigned, unsigned);
 static void function_summary (const coverage_info *);
@@ -664,6 +699,7 @@ static const char *format_gcov (gcov_type, gcov_type, int);
 static void accumulate_line_counts (source_info *);
 static void output_gcov_file (const char *, source_info *);
 static int output_branch_count (FILE *, int, const arc_info *);
+static void output_conditions (FILE *, const block_info *);
 static void output_lines (FILE *, const source_info *);
 static string make_gcov_file_name (const char *, const char *);
 static char *mangle_name (const char *);
@@ -928,6 +964,7 @@ print_usage (int error_p)
   fnotice (file, "  -b, --branch-probabilities      Include branch probabilities in output\n");
   fnotice (file, "  -c, --branch-counts             Output counts of branches taken\n\
                                     rather than percentages\n");
+  fnotice (file, "  -g, --conditions                Include condition coverage in output (MC/DC)\n");
   fnotice (file, "  -d, --display-progress          Display progress information\n");
   fnotice (file, "  -D, --debug			    Display debugging dumps\n");
   fnotice (file, "  -f, --function-summaries        Output summaries for each function\n");
@@ -980,6 +1017,7 @@ static const struct option options[] =
   { "all-blocks",           no_argument,       NULL, 'a' },
   { "branch-probabilities", no_argument,       NULL, 'b' },
   { "branch-counts",        no_argument,       NULL, 'c' },
+  { "conditions",           no_argument,       NULL, 'g' },
   { "json-format",	    no_argument,       NULL, 'j' },
   { "human-readable",	    no_argument,       NULL, 'H' },
   { "no-output",            no_argument,       NULL, 'n' },
@@ -1008,7 +1046,7 @@ process_args (int argc, char **argv)
 {
   int opt;
 
-  const char *opts = "abcdDfhHijklmno:pqrs:tuvwx";
+  const char *opts = "abcdDfghHijklmno:pqrs:tuvwx";
   while ((opt = getopt_long (argc, argv, opts, options, NULL)) != -1)
     {
       switch (opt)
@@ -1025,6 +1063,9 @@ process_args (int argc, char **argv)
 	case 'f':
 	  flag_function_summary = 1;
 	  break;
+	case 'g':
+	  flag_conditions = 1;
+	  break;
 	case 'h':
 	  print_usage (false);
 	  /* print_usage will exit.  */
@@ -1132,6 +1173,45 @@ output_intermediate_json_line (json::array *object,
 	  }
       }
 
+  json::array *conditions = new json::array ();
+  lineo->set ("conditions", conditions);
+  if (flag_conditions)
+  {
+    vector<block_info *>::const_iterator it;
+    for (it = line->blocks.begin (); it != line->blocks.end (); it++)
+      {
+	const condition_info& info = (*it)->conditions;
+	if (info.n_terms == 0)
+	    continue;
+
+	const int count = 2 * info.n_terms;
+	const int covered = info.popcount ();
+
+	json::object *cond = new json::object ();
+	cond->set ("count",   new json::integer_number (count));
+	cond->set ("covered", new json::integer_number (covered));
+
+	json::array *mtrue = new json::array ();
+	json::array *mfalse = new json::array ();
+	cond->set ("not_covered_true",  mtrue);
+	cond->set ("not_covered_false", mfalse);
+
+	if (count != covered)
+	  {
+	    for (unsigned i = 0; i < info.n_terms; i++)
+	      {
+		gcov_type_unsigned index = 1;
+		index <<= i;
+		if (!(index & info.truev))
+		    mtrue->append (new json::integer_number (i));
+		if (!(index & info.falsev))
+		    mfalse->append (new json::integer_number (i));
+	      }
+	  }
+	conditions->append (cond);
+      }
+  }
+
   object->append (lineo);
 }
 
@@ -1956,6 +2036,28 @@ read_graph_file (void)
 		  }
 	    }
 	}
+      else if (fn && tag == GCOV_TAG_CONDS)
+	{
+	  unsigned num_dests = GCOV_TAG_CONDS_NUM (length);
+
+	  if (!fn->conditions.empty ())
+	    fnotice (stderr, "%s:already seen conditions for '%s'\n",
+		     bbg_file_name, fn->get_name ());
+	  else
+	    fn->conditions.resize (num_dests);
+
+	  for (unsigned i = 0; i < num_dests; ++i)
+	    {
+	      unsigned idx = gcov_read_unsigned ();
+
+	      if (idx >= fn->blocks.size ())
+		goto corrupt;
+
+	      condition_info *info = &fn->blocks[idx].conditions;
+	      info->n_terms = gcov_read_unsigned ();
+	      fn->conditions[i] = info;
+	    }
+	}
       else if (fn && tag == GCOV_TAG_LINES)
 	{
 	  unsigned blockno = gcov_read_unsigned ();
@@ -2086,11 +2188,26 @@ read_count_file (void)
 	      goto cleanup;
 	    }
 	}
-      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn)
+      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_CONDS) && fn)
 	{
+	  length = abs (read_length);
+	    if (length != GCOV_TAG_COUNTER_LENGTH (2 * fn->conditions.size ()))
+		goto mismatch;
+
+	    if (read_length > 0)
+	      {
+		for (ix = 0; ix != fn->conditions.size (); ix++)
+		{
+		    fn->conditions[ix]->truev  |= gcov_read_counter ();
+		    fn->conditions[ix]->falsev |= gcov_read_counter ();
+		}
+	      }
+	}
+      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn)
+      {
 	  length = abs (read_length);
 	  if (length != GCOV_TAG_COUNTER_LENGTH (fn->counts.size ()))
-	    goto mismatch;
+	      goto mismatch;
 
 	  if (read_length > 0)
 	    for (ix = 0; ix != fn->counts.size (); ix++)
@@ -2430,6 +2547,13 @@ add_branch_counts (coverage_info *coverage, const arc_info *arc)
     }
 }
 
+static void
+add_condition_counts (coverage_info *coverage, const block_info *block)
+{
+  coverage->conditions += 2 * block->conditions.n_terms;
+  coverage->conditions_covered += block->conditions.popcount ();
+}
+
 /* Format COUNT, if flag_human_readable_numbers is set, return it human
    readable format.  */
 
@@ -2533,7 +2657,14 @@ file_summary (const coverage_info *coverage)
 		 coverage->calls);
       else
 	fnotice (stdout, "No calls\n");
+
     }
+
+  if (flag_conditions)
+      fnotice (stdout, "Conditions covered:%s of %d\n",
+	       format_gcov (coverage->conditions_covered,
+			    coverage->conditions, 2),
+	       coverage->conditions);
 }
 
 /* Canonicalize the filename NAME by canonicalizing directory
@@ -2760,6 +2891,12 @@ static void accumulate_line_info (line_info *line, source_info *src,
 	 it != line->branches.end (); it++)
       add_branch_counts (&src->coverage, *it);
 
+  if (add_coverage)
+    for (vector<block_info *>::iterator it = line->blocks.begin ();
+	 it != line->blocks.end (); it++)
+      add_condition_counts (&src->coverage, *it);
+
+
   if (!line->blocks.empty ())
     {
       /* The user expects the line count to be the number of times
@@ -2861,6 +2998,31 @@ accumulate_line_counts (source_info *src)
       }
 }
 
+static void
+output_conditions (FILE *gcov_file, const block_info *binfo)
+{
+    const condition_info& info = binfo->conditions;
+    if (info.n_terms == 0)
+	return;
+
+    const int expected = 2 * info.n_terms;
+    const int got = info.popcount ();
+
+    fnotice (gcov_file, "conditions covered %d/%d\n", got, expected);
+    if (expected == got)
+	return;
+
+    for (unsigned i = 0; i < info.n_terms; i++)
+    {
+	gcov_type_unsigned index = 1;
+	index <<= i;
+	if (!(index & info.truev))
+	    fnotice (gcov_file, "condition %2u not covered (true)\n", i);
+	if (!(index & info.falsev))
+	    fnotice (gcov_file, "condition %2u not covered (false)\n", i);
+    }
+}
+
 /* Output information about ARC number IX.  Returns nonzero if
    anything is output.  */
 
@@ -3071,16 +3233,29 @@ output_line_details (FILE *f, const line_info *line, unsigned line_num)
 	  if (flag_branches)
 	    for (arc = (*it)->succ; arc; arc = arc->succ_next)
 	      jx += output_branch_count (f, jx, arc);
+
+	  if (flag_conditions)
+	      output_conditions (f, *it);
 	}
     }
-  else if (flag_branches)
+  else
     {
-      int ix;
+      if (flag_branches)
+	{
+	  int ix;
+
+	  ix = 0;
+	  for (vector<arc_info *>::const_iterator it = line->branches.begin ();
+		  it != line->branches.end (); it++)
+	      ix += output_branch_count (f, ix, (*it));
+	}
 
-      ix = 0;
-      for (vector<arc_info *>::const_iterator it = line->branches.begin ();
-	   it != line->branches.end (); it++)
-	ix += output_branch_count (f, ix, (*it));
+      if (flag_conditions)
+	{
+	  for (vector<block_info *>::const_iterator it = line->blocks.begin ();
+	       it != line->blocks.end (); it++)
+	      output_conditions (f, *it);
+	}
     }
 }
 
diff --git a/gcc/ipa-inline.cc b/gcc/ipa-inline.cc
index f8bb072c422..b826dcd580c 100644
--- a/gcc/ipa-inline.cc
+++ b/gcc/ipa-inline.cc
@@ -646,7 +646,7 @@ can_early_inline_edge_p (struct cgraph_edge *e)
 			 "  edge not inlinable: not in SSA form\n");
       return false;
     }
-  else if (profile_arc_flag
+  else if ((profile_arc_flag || profile_condition_flag)
 	   && ((lookup_attribute ("no_profile_instrument_function",
 				 DECL_ATTRIBUTES (caller->decl)) == NULL_TREE)
 	       != (lookup_attribute ("no_profile_instrument_function",
diff --git a/gcc/ipa-split.cc b/gcc/ipa-split.cc
index 60021bad13c..9890518e11f 100644
--- a/gcc/ipa-split.cc
+++ b/gcc/ipa-split.cc
@@ -1929,7 +1929,8 @@ pass_split_functions::gate (function *)
   /* When doing profile feedback, we want to execute the pass after profiling
      is read.  So disable one in early optimization.  */
   return (flag_partial_inlining
-	  && !profile_arc_flag && !flag_branch_probabilities);
+      && !profile_arc_flag && !flag_branch_probabilities
+      && !profile_condition_flag);
 }
 
 } // anon namespace
diff --git a/gcc/passes.cc b/gcc/passes.cc
index 36e5b4ac45f..ee099188844 100644
--- a/gcc/passes.cc
+++ b/gcc/passes.cc
@@ -352,7 +352,8 @@ finish_optimization_passes (void)
   gcc::dump_manager *dumps = m_ctxt->get_dumps ();
 
   timevar_push (TV_DUMP);
-  if (profile_arc_flag || flag_test_coverage || flag_branch_probabilities)
+  if (profile_arc_flag || profile_condition_flag || flag_test_coverage
+          || flag_branch_probabilities)
     {
       dumps->dump_start (pass_profile_1->static_pass_number, NULL);
       end_branch_prob ();
diff --git a/gcc/profile.cc b/gcc/profile.cc
index a67cce5b199..b51fabbb59b 100644
--- a/gcc/profile.cc
+++ b/gcc/profile.cc
@@ -69,6 +69,9 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "profile.h"
 
+int find_conditions (basic_block, basic_block, basic_block*, int*, int);
+int instrument_decisions (basic_block*, int, int);
+
 /* Map from BBs/edges to gcov counters.  */
 vec<gcov_type> bb_gcov_counts;
 hash_map<edge,gcov_type> *edge_gcov_counts;
@@ -100,6 +103,7 @@ static int total_num_passes;
 static int total_num_times_called;
 static int total_hist_br_prob[20];
 static int total_num_branches;
+static int total_num_conds;
 
 /* Forward declarations.  */
 static void find_spanning_tree (struct edge_list *);
@@ -1155,6 +1159,12 @@ read_thunk_profile (struct cgraph_node *node)
    the flow graph that are needed to reconstruct the dynamic behavior of the
    flow graph.  This data is written to the gcno file for gcov.
 
+   When FLAG_PROFILE_CONDITIONS is nonzero, this functions instruments the
+   edges in the control flow graph to track what conditions are evaluated to in
+   order to determine what conditions are covered and have an independent
+   effect on the outcome (modified condition/decision coverage).  This data is
+   written to the gcno file for gcov.
+
    When FLAG_BRANCH_PROBABILITIES is nonzero, this function reads auxiliary
    information from the gcda file containing edge count information from
    previous executions of the function being compiled.  In this case, the
@@ -1173,6 +1183,7 @@ branch_prob (bool thunk)
   struct edge_list *el;
   histogram_values values = histogram_values ();
   unsigned cfg_checksum, lineno_checksum;
+  bool output_to_file;
 
   total_num_times_called++;
 
@@ -1381,7 +1392,7 @@ branch_prob (bool thunk)
 
   /* Compute two different checksums. Note that we want to compute
      the checksum in only once place, since it depends on the shape
-     of the control flow which can change during 
+     of the control flow which can change during
      various transformations.  */
   if (thunk)
     {
@@ -1397,10 +1408,18 @@ branch_prob (bool thunk)
 
   /* Write the data from which gcov can reconstruct the basic block
      graph and function line numbers (the gcno file).  */
+  output_to_file = false;
   if (coverage_begin_function (lineno_checksum, cfg_checksum))
     {
       gcov_position_t offset;
 
+      /* The condition coverage needs a deeper analysis to identify expressions
+       * of conditions, which means it is not yet ready to write to the gcno
+       * file.  It will write its entries later, but needs to know if it do it
+       * in the first place, which is controlled by the return value of
+       * coverage_begin_function. */
+      output_to_file = true;
+
       /* Basic block flags */
       offset = gcov_write_tag (GCOV_TAG_BLOCKS);
       gcov_write_unsigned (n_basic_blocks_for_fn (cfun));
@@ -1512,29 +1531,67 @@ branch_prob (bool thunk)
 
   remove_fake_edges ();
 
+  if (profile_condition_flag || profile_arc_flag)
+      gimple_init_gcov_profiler ();
+
+  if (profile_condition_flag)
+    {
+      basic_block entry = ENTRY_BLOCK_PTR_FOR_FN (cfun)->next_bb;
+      basic_block exit  = EXIT_BLOCK_PTR_FOR_FN (cfun);
+
+      // find_conditions () expect memory up front, see that function for
+      // details
+      const int max_blocks = 5 * n_basic_blocks_for_fn (cfun);
+      auto_vec<basic_block> blocks (max_blocks);
+      blocks.quick_grow (max_blocks);
+
+      const int max_sizes = n_basic_blocks_for_fn (cfun) + 1;
+      auto_vec<int> sizes (max_sizes);
+      sizes.quick_grow (max_sizes);
+
+      int nconds = find_conditions
+	  (entry, exit, blocks.address (), sizes.address (), max_blocks);
+      total_num_conds += nconds;
+
+      if (nconds > 0
+	  && coverage_counter_alloc (GCOV_COUNTER_CONDS, 2 * nconds)
+	  && output_to_file)
+	{
+	  gcov_position_t offset = gcov_write_tag (GCOV_TAG_CONDS);
+	  for (int i = 0; i < nconds; ++i)
+	    {
+	      int idx = sizes[i];
+	      int len = sizes[i + 1] - idx;
+	      basic_block *itr = blocks.address () + idx;
+	      int terms = instrument_decisions (itr, len, i);
+	      gcov_write_unsigned (blocks[idx]->index);
+	      gcov_write_unsigned (terms);
+	    }
+	  gcov_write_length (offset);
+	}
+    }
+
   /* For each edge not on the spanning tree, add counting code.  */
   if (profile_arc_flag
       && coverage_counter_alloc (GCOV_COUNTER_ARCS, num_instrumented))
     {
       unsigned n_instrumented;
 
-      gimple_init_gcov_profiler ();
-
       n_instrumented = instrument_edges (el);
 
       gcc_assert (n_instrumented == num_instrumented);
 
       if (flag_profile_values)
 	instrument_values (values);
-
-      /* Commit changes done by instrumentation.  */
-      gsi_commit_edge_inserts ();
     }
 
   free_aux_for_edges ();
 
   values.release ();
   free_edge_list (el);
+  /* Commit changes done by instrumentation.  */
+  gsi_commit_edge_inserts ();
+
   coverage_end_function (lineno_checksum, cfg_checksum);
   if (flag_branch_probabilities
       && (profile_status_for_fn (cfun) == PROFILE_READ))
@@ -1664,6 +1721,7 @@ init_branch_prob (void)
   total_num_passes = 0;
   total_num_times_called = 0;
   total_num_branches = 0;
+  total_num_conds = 0;
   for (i = 0; i < 20; i++)
     total_hist_br_prob[i] = 0;
 }
@@ -1703,5 +1761,7 @@ end_branch_prob (void)
 		     (total_hist_br_prob[i] + total_hist_br_prob[19-i]) * 100
 		     / total_num_branches, 5*i, 5*i+5);
 	}
+      fprintf (dump_file, "Total number of conditions: %d\n",
+	       total_num_conds);
     }
 }
diff --git a/gcc/testsuite/g++.dg/gcov/gcov-18.C b/gcc/testsuite/g++.dg/gcov/gcov-18.C
new file mode 100644
index 00000000000..9ac14dfeff6
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gcov/gcov-18.C
@@ -0,0 +1,227 @@
+/* { dg-options "--coverage -fprofile-conditions -std=c++11" } */
+/* { dg-do run { target native } } */
+
+#include <vector>
+#include <stdexcept>
+
+class nontrivial_destructor
+{
+public:
+    explicit nontrivial_destructor (int v) : val (v) {}
+    ~nontrivial_destructor () {}
+
+    explicit operator bool() const { return bool(val); }
+
+    int val;
+};
+
+int identity (int x) { return x; }
+int throws (int) { throw std::runtime_error("exception"); }
+
+int throw_if (int x)
+{
+    if (x) /* conditions(1/2) true(0) */
+        throw std::runtime_error("exception");
+    return x;
+}
+
+/* used for side effects to insert nodes in conditional bodies etc. */
+int x = 0;
+
+/* conditionals work in the presence of non-trivial destructors */
+void mcdc001a (int a)
+{
+    nontrivial_destructor v (a);
+
+    if (v.val > 0) /* conditions(2/2) */
+        x = v.val;
+    else
+        x = -v.val;
+}
+
+/* non-trivial destructor in-loop temporary */
+nontrivial_destructor
+mcdc002a (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+        nontrivial_destructor tmp (a);
+        if (tmp.val % b) /* conditions(2/2) */
+            return nontrivial_destructor (0);
+        x += i;
+    } /* conditions(suppress) */
+      /* conditions(end) */
+
+    return nontrivial_destructor (a * b);
+}
+
+/* conditional in constructor */
+void mcdc003a (int a)
+{
+    class C
+    {
+    public:
+        explicit C (int e) : v (e) {
+            if (e) /* conditions(1/2) false(0) */
+                v = x - e;
+        }
+        int v;
+    };
+
+    C c (a);
+    if (c.v > 2) /* conditions(1/2) true(0) */
+        x = c.v + a;
+}
+
+/* conditional in destructor */
+void mcdc004a (int a)
+{
+    class C
+    {
+    public:
+        explicit C (int e) : v (e) {}
+        ~C () {
+            if (v) /* conditions(2/2) */
+                x = 2 * v;
+        }
+        int v;
+    };
+
+    C c (a);
+    x = 1; // arbitrary action between ctor+dtor
+}
+
+/* conditional in try */
+void mcdc005a (int a)
+{
+    try
+    {
+        if (a) /* conditions(1/2) true(0) */
+            x = 2 * identity (a);
+        else
+            x = 1;
+    }
+    catch (...)
+    {
+        x = 0;
+    }
+}
+
+/* conditional in catch */
+void mcdc006a (int a) {
+    try
+    {
+        throws (a);
+    }
+    catch (std::exception&)
+    {
+        if (a) /* conditions(1/2) false(0) */
+            x = identity (a);
+        else
+            x = 0;
+    }
+}
+
+void mcdc006b (int a)
+{
+    if (a) /* conditions(1/2) true(0) */
+        throws (a);
+    else
+        x = 1;
+}
+
+void mcdc006c (int a) try
+{
+    throws (a);
+}
+catch (...) {
+    if (a) /* conditions(2/2) */
+        x = 5;
+}
+
+/* temporary with destructor as term */
+void mcdc007a (int a, int b)
+{
+    x = a && nontrivial_destructor (b); /* conditions(3/4) false(1) destructor() */
+}
+
+void mcdc007b (int a, int b)
+{
+    if (a || throw_if (b)) /* conditions(3/4) true(1) destructor() */
+        x = -1;
+    else
+        x = 1;
+}
+
+void mcdc007c (int a, int b)
+{
+    if (throw_if (a) || throw_if (b)) /* conditions(2/4) true(0 1) destructor() */
+        x = -1;
+    else
+        x = 1;
+}
+
+/* destructor with delete */
+void mcdc008a (int a)
+{
+    class C
+    {
+    public:
+        int size = 5;
+        int* ptr = nullptr;
+
+        explicit C (int v) : size(v + 5), ptr (new int[size]) /* conditions(suppress) */
+                                                              /* conditions(end) */
+        {
+            for (int i = 0; i < size; i++) /* conditions(2/2) */
+                ptr[i] = i + 1;
+        }
+        ~C()
+        {
+            if (ptr) /* conditions(1/2) false(0) */
+                delete ptr; /* conditions(suppress) */
+                            /* conditions(end) */
+        }
+    };
+
+    C c (a);
+    if (c.ptr[a + 1]) /* conditions(1/2) false(0) */
+        x = a;
+}
+
+int
+main (void)
+{
+    mcdc001a (0);
+    mcdc001a (1);
+
+    mcdc002a (1, 1);
+    mcdc002a (1, 2);
+
+    mcdc003a (1);
+
+    mcdc004a (0);
+    mcdc004a (1);
+
+    mcdc005a (0);
+
+    mcdc006a (1);
+
+    mcdc006b (0);
+
+    mcdc006c (0);
+    mcdc006c (1);
+
+    mcdc007a (0, 0);
+    mcdc007a (1, 1);
+
+    mcdc007b (0, 0);
+    mcdc007b (1, 0);
+
+    mcdc007c (0, 0);
+
+    mcdc008a (1);
+
+}
+
+/* { dg-final { run-gcov conditions { --conditions gcov-18.C } } } */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-19.c b/gcc/testsuite/gcc.misc-tests/gcov-19.c
new file mode 100644
index 00000000000..c1ea8acee7d
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-19.c
@@ -0,0 +1,735 @@
+/* { dg-options "-fprofile-conditions -ftest-coverage" } */
+/* { dg-do run { target native } } */
+
+/* some side effect to stop branches from being pruned */
+int x = 0;
+
+/* || works */
+void mcdc001a (int a, int b)
+{
+    if (a || b) /* conditions(1/4) true(0) false(0 1) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void mcdc001b (int a, int b)
+{
+    if (a || b) /* conditions(3/4) true(0) false() */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void mcdc001c (int a, int b)
+{
+    if (a || b) /* conditions(4/4) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void mcdc001d (int a, int b, int c)
+{
+    if (a || b || c) /* conditions(3/6) false(1 2) true(2) */
+		     /* conditions(end) */
+	x = 1;
+}
+
+/* && works */
+void mcdc002a (int a, int b)
+{
+    if (a && b) /* conditions(1/4) true(0 1) false(0) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void mcdc002b (int a, int b)
+{
+    if (a && b) /* conditions(3/4) false(0) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void mcdc002c (int a, int b)
+{
+    if (a && b) /* conditions(4/4) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void mcdc002d (int a, int b, int c)
+{
+    /*
+     * This is an odd case, and falls victim to trying to detect nested ifs.
+     *
+     * if (a) if (b) if (c) with no else is equivalent to if (a && b && c) and
+     * the CFGs are identical *unless* the else nodes are generated too. In the
+     * && expression, all false edges should go to the same else, but in the
+     * nested-if case they go to different elses.
+     *
+     * This can be surprising, and bad for MC/DC because non-independent
+     * conditionals masked by terms further-right can not be detected. If an
+     * else node is generated, this expression becomes a 3-term decision again.
+     */
+    if (a && b && c) /* conditions(suppress) conditions(4/6) false(0 2) */
+	/* conditions(end) */
+	x = 1;
+}
+
+/* negation works */
+void mcdc003a (int a, int b)
+{
+    if (!a || !b) /* conditions(2/4) false(0 1) */
+		  /* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+/* single conditionals with and without else */
+void mcdc004a (int a)
+{
+    if (a) /* conditions(1/2) true(0) */
+	   /* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void mcdc004b (int a)
+{
+    if (a) /* conditions(2/2) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void mcdc004c (int a)
+{
+    if (a) /* conditions(1/2) false(0) */
+	   /* conditions(end) */
+	x = 1;
+}
+
+void mcdc004d (int a, int b, int c) {
+    if (a)  /* conditions(2/2) */
+    {
+	if (b || c) /* conditions(1/4) true(1) false(0 1) */
+	    x = a + b + c;
+    }
+}
+
+/* mixing && and || works */
+void mcdc005a (int a, int b, int c)
+{
+    if ((a && b) || c) /* conditions(1/6) true(0 1) false(0 1 2) */
+		       /* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void mcdc005b (int a, int b, int c, int d)
+{
+    if ((a && (b || c)) && d) /* conditions(4/8) true(1 2 3) false(0) */
+			      /* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+/* nested conditionals */
+void mcdc006a (int a, int b, int c, int d, int e)
+{
+    if (a) /* conditions(2/2) */
+    {
+	if (b && c) /* conditions(3/4) false(1) */
+		    /* conditions(end) */
+	    x = 1;
+	else
+	    x = 2;
+    }
+    else
+    {
+	if (c || d) /* conditions(2/4) true(0 1) */
+		    /* conditions(end) */
+	    x = 3;
+	else
+	    x = 4;
+    }
+}
+
+void mcdc006b (int a, int b, int c)
+{
+    if (a) /* conditions(2/2) */
+    {
+	if (b) /*conditions(2/2) */
+	{
+	    if (c) /* conditions(2/2) */
+	    {
+		x = a + b + c;
+	    }
+	}
+    }
+}
+
+/* else/if */
+void mcdc007a (int a, int b, int c, int d)
+{
+    if (a) /* conditions(2/2) */
+    {
+	if (b) /* conditions(1/2) true(0) */
+	       /* conditions(end) */
+	    x = 1;
+	else
+	    x = 2;
+    }
+    else if (c) /* conditions(2/2) */
+    {
+	if (d) /* conditions(1/2) true(0) */
+	       /* conditions(end) */
+	    x = 3;
+    }
+}
+
+void mcdc007b (int a, int b, int c)
+{
+    /* similar to if (a || b || c) x = 1 */
+    if (a) /* conditions(2/2) */
+	goto then;
+    else if (b) /* conditions(2/2) */
+	goto then;
+    else if (c) /* conditions(1/2) true(0) */
+	/* conditions(end) */
+	goto then;
+
+    return;
+
+then:
+    x = 1;
+}
+
+/* while loop */
+void mcdc008a (int a)
+{
+    while (a < 10) /* conditions(2/2) */
+	x = a++;
+}
+
+void mcdc008b (int a)
+{
+    while (a > 10) /* conditions(1/2) true(0) */
+		   /* conditions(end) */
+	x = a--;
+}
+
+void mcdc008c (int a)
+{
+    // should work, even with no body
+    while (a) /* conditions(2/2) */
+	break;
+}
+
+void mcdc008d (int a, int b, int c, int d)
+{
+    /* multi-term loop conditional */
+    while ((a && (b || c)) && d) /* conditions(8/8) */
+	a = b = c = d = 0;
+}
+
+void mcdc009a (int a, int b)
+{
+    while (a > 0 && b > 0) /* conditions(3/4) false(1) */
+			   /* conditions(end) */
+	x = a--;
+}
+
+/* for loop */
+void mcdc010a(int a, int b)
+{
+    for (int i = 0; i < b; i++) /* conditions(2/2) */
+    {
+	if (a < b) /* conditions(2/2) */
+	    x = 1;
+	else
+	    x = a += 2;
+    }
+}
+
+int always (int) { return 1; }
+
+/* no-condition infinite loops */
+void mcdc010b (int a)
+{
+    for (;;)
+    {
+	if (always(a)) /* conditions(1/2) false(0) */
+		       /* conditions(end) */
+	{
+	    x = a;
+	    break;
+	}
+	x += a + 1;
+    }
+}
+
+/* conditionals without control flow constructs work */
+void mcdc011a (int a, int b, int c)
+{
+    x = (a && b) || c; /* conditions(5/6) false(1) */
+		       /* conditions(end) */
+}
+
+/* sequential expressions are handled independently */
+void mcdc012a (int a, int b, int c) {
+    if (a || b) /* conditions(3/4) true(0) */
+	/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+
+    if (c) /* conditions(2/2) */
+	x = 1;
+}
+
+/*
+ * cannot ever satisfy MC/DC, even with all input combinations, because not all
+ * variables independently affect the decision
+ */
+void mcdc013a (int a, int /* b */, int c)
+{
+    /*
+     * Specification: (a && b) || c
+     *
+     * But the expression was implemented wrong. This has branch coverage, but
+     * not MC/DC
+     */
+    if ((a && !c) || c) /* conditions(5/6) false(1) */
+	/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void mcdc014a ()
+{
+    int conds[64] = { 0 };
+    /* conditions(64/128) true(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63) */
+    x = conds[ 0] || conds[ 1] || conds[ 2] || conds[ 3] || conds[ 4] ||
+	conds[ 5] || conds[ 6] || conds[ 7] || conds[ 8] || conds[ 9] ||
+	conds[10] || conds[11] || conds[12] || conds[13] || conds[14] ||
+	conds[15] || conds[16] || conds[17] || conds[18] || conds[19] ||
+	conds[20] || conds[21] || conds[22] || conds[23] || conds[24] ||
+	conds[25] || conds[26] || conds[27] || conds[28] || conds[29] ||
+	conds[30] || conds[31] || conds[32] || conds[33] || conds[34] ||
+	conds[35] || conds[36] || conds[37] || conds[38] || conds[39] ||
+	conds[40] || conds[41] || conds[42] || conds[43] || conds[44] ||
+	conds[45] || conds[46] || conds[47] || conds[48] || conds[49] ||
+	conds[50] || conds[51] || conds[52] || conds[53] || conds[54] ||
+	conds[55] || conds[56] || conds[57] || conds[58] || conds[59] ||
+	conds[60] || conds[61] || conds[62] || conds[63]
+	;  /* conditions(end) */
+}
+
+/* early returns */
+void mcdc015a (int a, int b)
+{
+    if (a) /* conditions(2/2) */
+	return;
+
+    if (b) /* conditions(1/2) true(0) */
+	   /* conditions(end) */
+	x = 1;
+}
+
+void mcdc015b (int a, int b)
+{
+    for (int i = 5; i > a; i--) /* conditions(2/2) */
+    {
+	if (i == b) /* conditions(2/2) */
+	    return;
+	x = i;
+    }
+}
+
+void mcdc015c (int a, int b)
+{
+    for (int i = 5; i > a; i--) /* conditions(2/2) */
+    {
+	if (i == b) /* conditions(2/2) */
+	{
+	    x = 0;
+	    return;
+	}
+	else
+	{
+	    x = 1;
+	    return;
+	}
+
+	x = i;
+    }
+}
+
+
+/* check nested loops */
+void mcdc016a (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+	for (int k = 0; k < b; k++) /* conditions(2/2) */
+	    x = i + k;
+}
+
+void mcdc016b (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+	if (a > 5) /* conditions(2/2) */
+	    break;
+
+	for (int k = 0; k < b; k++) /* conditions(2/2) */
+	    x = i + k;
+    }
+}
+
+void mcdc016c (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+	if (a > 5) /* conditions(1/2) true(0) */
+		   /* conditions(end) */
+	    return;
+
+	for (int k = 0; k < b; k++) /* conditions(2/2) */
+	    x = i + k;
+    }
+}
+
+void mcdc016d (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+	for (int k = 0; k < 5; k++) /* conditions(2/2) */
+	{
+	    if (b > 5) /* conditions(1/2) true(0) */
+		       /* conditions(end) */
+		return;
+	    x = i + k;
+	}
+
+    }
+}
+
+/* do-while loops */
+void mcdc017a (int a)
+{
+    do {
+	a--;
+    } while (a > 0); /* conditions(2/2) */
+}
+
+void noop () {}
+
+void mcdc017b (int a, int b)
+{
+    do {
+	/*
+	 * This call is important; it can add more nodes to the body in the
+	 * CFG, which has changes how close exits and breaks are to the loop
+	 * conditional.
+	 */
+	noop ();
+	a--;
+	if (b) /* conditions(2/2) */
+	    break;
+
+    } while (a > 0); /* conditions(2/2) */
+}
+
+void mcdc017c (int a, int b)
+{
+    int left = 0;
+    int right = 0;
+    int n = a + b;
+    do {
+	if (a) /* conditions(1/2) false(0) */
+	       /* conditions(end) */
+	{
+	    left = a > left ? b : left; /* conditions(2/2) */
+	}
+	if (b) /* conditions(1/2) false(0) */
+	{
+	    right = b > right ? a : right; /* conditions(2/2) */
+	}
+    } while (n-- > 0); /* conditions(2/2) */
+}
+
+int id  (int x) { return  x; }
+int inv (int x) { return !x; }
+
+/* collection of odd cases lifted-and-adapted from real-world code */
+int mcdc018a (int a, int b, int c, int d, int e, int f, int g, int len)
+{
+    int n;
+    /* adapted from zlib/gz_read */
+    do
+    {
+	n = -1;
+	if (n > len) /* conditions(2/2) */
+	    n = len;
+
+	if (b) /* conditions(2/2) */
+	{
+	    if (b < 5) /* conditions(2/2) */
+		x = 1;
+	    noop();
+	}
+	else if (c && d) /* conditions(3/4) false(1) */
+	{
+	    x = 2;
+	    break;
+	}
+	else if (e || f) /* conditions(2/4) false(0 1) */
+			 /* conditions(end) */
+	{
+	    if (id(g)) /* conditions(2/2) */
+		return 0;
+	    continue;
+	}
+    } while (a-- > 0); /* conditions(2/2) */
+
+    return 1;
+}
+
+void mcdc018b (int a, int b, int c) {
+    int n;
+    while (a) /* conditions(2/2) */
+    {
+	/*
+	 * else block does not make a difference for the problem, but ensures
+	 * loop termination
+	 */
+	if (b) /* conditions(2/2) */
+	    n = c ? 0 : 0; // does not show up in CFG (embedded in the block)
+	else
+	    n = 0;
+	a = n;
+    }
+}
+
+/* too many conditions, coverage gives up */
+void mcdc019a ()
+{
+    int conds[65] = { 0 };
+    #pragma GCC diagnostic push
+    #pragma GCC diagnostic ignored "-Wcoverage-too-many-conditions"
+    x = conds[ 0] || conds[ 1] || conds[ 2] || conds[ 3] || conds[ 4] ||
+	conds[ 5] || conds[ 6] || conds[ 7] || conds[ 8] || conds[ 9] ||
+	conds[10] || conds[11] || conds[12] || conds[13] || conds[14] ||
+	conds[15] || conds[16] || conds[17] || conds[18] || conds[19] ||
+	conds[20] || conds[21] || conds[22] || conds[23] || conds[24] ||
+	conds[25] || conds[26] || conds[27] || conds[28] || conds[29] ||
+	conds[30] || conds[31] || conds[32] || conds[33] || conds[34] ||
+	conds[35] || conds[36] || conds[37] || conds[38] || conds[39] ||
+	conds[40] || conds[41] || conds[42] || conds[43] || conds[44] ||
+	conds[45] || conds[46] || conds[47] || conds[48] || conds[49] ||
+	conds[50] || conds[51] || conds[52] || conds[53] || conds[54] ||
+	conds[55] || conds[56] || conds[57] || conds[58] || conds[59] ||
+	conds[60] || conds[61] || conds[62] || conds[63] || conds[64]
+	;
+    #pragma GCC diagnostic pop
+}
+
+/* ternary */
+void mcdc020a (int a)
+{
+    // special case, this can be reduced to:
+    // _1 = argc != 0;
+    // e = (int) _1;
+    x = a ? 1 : 0;
+
+    // changing to different int makes branch
+    x = a ? 2 : 1; /* conditions(2/2) */
+}
+
+void mcdc020b (int a, int b)
+{
+    x = (a || b) ? 1 : 0; /* conditions(3/4) true(1) */
+}
+
+void mcdc020c (int a, int b)
+{
+    x = a ? 0
+	: b ? 1 /* conditions(2/2) */
+	: 2;    /* conditions(1/2) false(0) */
+		/* conditions(end) */
+}
+
+/* infinite loop (no exit-edge), this should not be called, but it should
+   compile fine */
+void mcdc021a ()
+{
+    while (1)
+	;
+}
+
+/* test with functions as conditionals */
+
+int main ()
+{
+    mcdc001a (0, 1);
+
+    mcdc001b (0, 1);
+    mcdc001b (0, 0);
+
+    mcdc001c (0, 1);
+    mcdc001c (0, 0);
+    mcdc001c (1, 1);
+
+    mcdc001d (1, 1, 1);
+    mcdc001d (0, 1, 0);
+
+    mcdc002a (1, 0);
+
+    mcdc002b (1, 0);
+    mcdc002b (1, 1);
+
+    mcdc002c (0, 0);
+    mcdc002c (1, 1);
+    mcdc002c (1, 0);
+
+    mcdc002d (1, 1, 1);
+    mcdc002d (1, 0, 0);
+
+    mcdc003a (0, 0);
+    mcdc003a (1, 0);
+
+    mcdc004a (0);
+    mcdc004b (0);
+    mcdc004b (1);
+    mcdc004c (1);
+
+    mcdc004d (0, 0, 0);
+    mcdc004d (1, 1, 1);
+
+    mcdc005a (1, 0, 1);
+
+    mcdc005b (1, 1, 0, 0);
+    mcdc005b (1, 0, 0, 0);
+
+    mcdc006a (0, 0, 0, 0, 0);
+    mcdc006a (1, 0, 0, 0, 0);
+    mcdc006a (1, 1, 1, 0, 0);
+
+    mcdc006b (0, 0, 0);
+    mcdc006b (1, 0, 0);
+    mcdc006b (1, 1, 0);
+    mcdc006b (1, 1, 1);
+
+    mcdc007a (0, 0, 0, 0);
+    mcdc007a (1, 0, 0, 0);
+    mcdc007a (0, 0, 1, 0);
+
+    mcdc007b (0, 0, 0);
+    mcdc007b (0, 1, 1);
+    mcdc007b (1, 0, 1);
+
+    mcdc008a (0);
+
+    mcdc008b (0);
+
+    mcdc008c (0);
+    mcdc008c (1);
+
+    mcdc008d (0, 0, 0, 0);
+    mcdc008d (1, 0, 0, 0);
+    mcdc008d (1, 0, 1, 0);
+    mcdc008d (1, 0, 1, 1);
+    mcdc008d (1, 1, 1, 1);
+
+    mcdc009a (0, 0);
+    mcdc009a (1, 1);
+
+    mcdc010a (0, 0);
+    mcdc010a (0, 9);
+    mcdc010a (2, 1);
+
+    mcdc010b (1);
+
+    mcdc011a (0, 0, 0);
+    mcdc011a (1, 1, 0);
+    mcdc011a (1, 0, 1);
+
+    mcdc012a (0, 0, 0);
+    mcdc012a (0, 1, 1);
+
+    mcdc013a (0, 0, 0);
+    mcdc013a (0, 0, 1);
+    mcdc013a (0, 1, 0);
+    mcdc013a (0, 1, 1);
+    mcdc013a (1, 0, 0);
+    mcdc013a (1, 0, 1);
+    mcdc013a (1, 1, 0);
+    mcdc013a (1, 1, 1);
+
+    mcdc014a ();
+
+    mcdc015a (0, 0);
+    mcdc015a (1, 0);
+
+    mcdc015b (0, 0);
+    mcdc015b (0, 1);
+    mcdc015b (6, 1);
+
+    mcdc015c (0, 0);
+    mcdc015c (0, 5);
+    mcdc015c (6, 1);
+
+    mcdc016a (5, 5);
+
+    mcdc016b (5, 5);
+    mcdc016b (6, 5);
+
+    mcdc016c (5, 5);
+
+    mcdc016d (1, 0);
+
+    mcdc017a (0);
+    mcdc017a (2);
+
+    mcdc017b (2, 0);
+    mcdc017b (0, 1);
+
+    mcdc017c (1, 1);
+
+    mcdc018a (0, 0, 1, 1, 0, 0, 0, 0);
+    mcdc018a (0, 1, 0, 0, 0, 0, 1, -2);
+    mcdc018a (0, 6, 0, 0, 0, 0, 1, -2);
+    mcdc018a (0, 6, 0, 0, 0, 0, 1, -2);
+    mcdc018a (0, 0, 0, 1, 0, 1, 1, 0);
+    mcdc018a (1, 0, 0, 0, 1, 1, 0, 0);
+
+    mcdc018b (1, 0, 0);
+    mcdc018b (1, 1, 0);
+
+    mcdc019a ();
+
+    mcdc020a (0);
+    mcdc020a (1);
+
+    mcdc020b (0, 0);
+    mcdc020b (1, 0);
+
+    mcdc020c (0, 1);
+    mcdc020c (1, 1);
+}
+
+/* { dg-final { run-gcov conditions { --conditions gcov-19.c } } } */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-20.c b/gcc/testsuite/gcc.misc-tests/gcov-20.c
new file mode 100644
index 00000000000..56dd6cf89bb
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-20.c
@@ -0,0 +1,21 @@
+/* { dg-options "-fprofile-conditions -ftest-coverage -fprofile-update=atomic" } */
+/* { dg-do run { target native } } */
+
+/* some side effect to stop branches from being pruned */
+int x = 0;
+
+void conditions_atomic001(int a, int b)
+{
+  if (a || b) /* conditions(1/4) true(0) false(0 1) */
+	      /* conditions(end) */
+      x = 1;
+  else
+      x = 2;
+}
+
+int main ()
+{
+    conditions_atomic001 (0, 1);
+}
+
+/* { dg-final { run-gcov conditions { --conditions gcov-20.c } } } */
diff --git a/gcc/testsuite/lib/gcov.exp b/gcc/testsuite/lib/gcov.exp
index 9d5b2cdb86b..88f28d26bb6 100644
--- a/gcc/testsuite/lib/gcov.exp
+++ b/gcc/testsuite/lib/gcov.exp
@@ -174,6 +174,180 @@ proc verify-branches { testname testcase file } {
     return $failed
 }
 
+#
+# verify-conditions -- check that conditions are checked as expected
+#
+# TESTNAME is the name of the test, including unique flags.
+# TESTCASE is the name of the test file.
+# FILE is the name of the gcov output file.
+#
+# Checks are based on comments in the source file. Condition coverage comes
+# with with two types of output, a summary and a list of the uncovered
+# conditions. Both must be checked to pass the test
+#
+# To check for conditions, add a comment the line of a conditional:
+# /* conditions(n/m) true(0 1) false(1) */
+#
+# where n/m are the covered and total conditions in the expression. The true()
+# and false() take the indices expected *not* covered.
+#
+# This means that all coverage statements should have been seen:
+# /* conditions(end) */
+#
+# If all conditions are covered i.e. n == m, then conditions(end) can be
+# omitted. If either true() or false() are empty they can be omitted too.
+#
+# C++ can insert conditionals in the CFG that are not present in source code.
+# These must be manually suppressed since unexpected and unhandled conditions
+# are an error (to help combat regressions). Output can be suppressed with
+# conditions(suppress) and conditions(end). suppress should usually be on a
+# closing brace.
+#
+# Some expressions, when using unnamed temporaries as operands, will have
+# destructors in expressions. The coverage of the destructor will be reported
+# on the same line as the expression itself, but suppress() would also swallow
+# the expected tested-for messages. To handle these, use the destructor() [1]
+# which will suppress everything from and including the second "conditions
+# covered".
+#
+# [1] it is important that the destructor() is *on the same line* as the
+#     conditions(m/n)
+proc verify-conditions { testname testcase file } {
+    set failed 0
+    set suppress 0
+    set destructor 0
+    set shouldt ""
+    set shouldf ""
+    set shouldall ""
+    set fd [open $file r]
+    set n 0
+    set keywords {"end" "suppress"}
+    while {[gets $fd line] >= 0} {
+        regexp "^\[^:\]+: *(\[0-9\]+):" "$line" all n
+        set prefix "$testname line $n"
+
+        if {![regexp "condition" $line]} {
+            continue
+        }
+
+        # Missing coverage for both true and false will cause a failure, but
+        # only count it once for the report.
+        set ok 1
+        if [regexp {conditions *\(([0-9a-z/]+)\)} "$line" all e] {
+            # *Very* coarse sanity check: conditions() should either be a
+            # keyword or n/m, anything else means a buggy test case. end is
+            # optional for cases where all conditions are covered, since it
+            # only expects a single line of output.
+            if {([lsearch -exact $keywords $e] >= 0 || [regexp {\d+/\d+} "$e"]) == 0} {
+                fail "$prefix: expected conditions (n/m), (suppress) or (end); was ($e)"
+                incr failed
+                continue
+            }
+
+            # Any keyword means a new context. Set the error flag if not all
+            # expected output has been seen, and reset the state.
+
+            if {[llength $shouldt] != 0} {
+                fail "$prefix: expected uncovered (true) for terms $shouldt"
+                set ok 0
+            }
+
+            if {[llength $shouldf] != 0} {
+                fail "$prefix: expected uncovered (false) for terms $shouldf"
+                set ok 0
+            }
+
+            if {$shouldall ne ""} {
+                fail "$prefix: coverage summary not found; expected $shouldall"
+                set ok 0
+            }
+
+            set suppress 0
+            set destructor 0
+            set shouldt ""
+            set shouldf ""
+            set shouldall ""
+            set newt ""
+            set newf ""
+
+            if [regexp {destructor\(\)} "$line"] {
+                set destructor 1
+            }
+
+            if [regexp {(\d+)/(\d+)} "$e" all i k] {
+                regexp {true\(([0-9 ]+)\)}  "$line" all newt
+                regexp {false\(([0-9 ]+)\)} "$line" all newf
+
+                # Sanity check - if the true() and false() vectors should have
+                # m-n elements to cover all uncovered conditions. Because of
+                # masking it can sometimes be surprising what terms are
+                # independent, so this makes for more robust test at the cost
+                # of being slightly more annoying to write.
+                set nterms [expr [llength $newt] + [llength $newf]]
+                set nexpected [expr {$k - $i}]
+                if {$nterms != $nexpected} {
+                    fail "$prefix: expected $nexpected uncovered terms; got $nterms"
+                    set ok 0
+                }
+                set shouldall $e
+                set shouldt $newt
+                set shouldf $newf
+            } elseif {$e == "end"} {
+                # no-op - state has already been reset, and errors flagged
+            } elseif {$e == "suppress"} {
+                set suppress 1
+            } else {
+                # this should be unreachable, 
+                fail "$prefix: unhandled control ($e), should be unreachable"
+                set ok 0
+            }
+        } elseif {$suppress == 1} {
+            # ignore everything in a suppress block. C++ especially can insert
+            # conditionals in exceptions and destructors which would otherwise
+            # be considered unhandled.
+            continue
+        } elseif [regexp {condition +(\d+) not covered \(true\)} "$line" all cond] {
+            set i [lsearch $shouldt $cond]
+            if {$i != -1} {
+                set shouldt [lreplace $shouldt $i $i]
+            } else {
+                fail "$testname line $n: unexpected uncovered term $cond (true)"
+                set ok 0
+            }
+        } elseif [regexp {condition +(\d+) not covered \(false\)} "$line" all cond] {
+            set i [lsearch $shouldf $cond]
+            if {$i != -1} {
+                set shouldf [lreplace $shouldf $i $i]
+            } else {
+                fail "$testname line $n: unexpected uncovered term $cond (false)"
+                set ok 0
+            }
+        } elseif [regexp {conditions covered (\d+/\d+)} "$line" all cond] {
+            # the destructor-generated "conditions covered" lines will be
+            # written after all expression-related output. Handle these by
+            # turning on suppression if the destructor-suppression is
+            # requested.
+            if {$shouldall == "" && $destructor == 1} {
+                set suppress 1
+                continue
+            }
+
+            if {$cond == $shouldall} {
+                set shouldall ""
+            } else {
+                fail "$testname line $n: unexpected summary $cond"
+                set ok 0
+            }
+        }
+
+        if {$ok != 1} {
+            incr failed
+        }
+    }
+    close $fd
+    return $failed
+}
+
 #
 # verify-calls -- check that call return percentages are as expected
 #
@@ -321,6 +495,7 @@ proc run-gcov { args } {
     set gcov_args ""
     set gcov_verify_calls 0
     set gcov_verify_branches 0
+    set gcov_verify_conditions 0
     set gcov_verify_lines 1
     set gcov_verify_intermediate 0
     set gcov_remove_gcda 0
@@ -331,10 +506,13 @@ proc run-gcov { args } {
 	  set gcov_verify_calls 1
 	} elseif { $a == "branches" } {
 	  set gcov_verify_branches 1
+	} elseif { $a == "conditions" } {
+	  set gcov_verify_conditions 1
 	} elseif { $a == "intermediate" } {
 	  set gcov_verify_intermediate 1
 	  set gcov_verify_calls 0
 	  set gcov_verify_branches 0
+	  set gcov_verify_conditions 0
 	  set gcov_verify_lines 0
 	} elseif { $a == "remove-gcda" } {
 	  set gcov_remove_gcda 1
@@ -404,6 +582,11 @@ proc run-gcov { args } {
     } else {
 	set bfailed 0
     }
+    if { $gcov_verify_conditions } {
+	set cdfailed [verify-conditions $testname $testcase $testcase.gcov]
+    } else {
+	set cdfailed 0
+    }
     if { $gcov_verify_calls } {
 	set cfailed [verify-calls $testname $testcase $testcase.gcov]
     } else {
@@ -418,12 +601,12 @@ proc run-gcov { args } {
 
     # Report whether the gcov test passed or failed.  If there were
     # multiple failures then the message is a summary.
-    set tfailed [expr $lfailed + $bfailed + $cfailed + $ifailed]
+    set tfailed [expr $lfailed + $bfailed + $cdfailed + $cfailed + $ifailed]
     if { $xfailed } {
 	setup_xfail "*-*-*"
     }
     if { $tfailed > 0 } {
-	fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cfailed in return percentages, $ifailed in intermediate format"
+	fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cdfailed in condition/decision, $cfailed in return percentages, $ifailed in intermediate format"
 	if { $xfailed } {
 	    clean-gcov $testcase
 	}
diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
index 6d40401f86f..d737e45cd62 100644
--- a/gcc/tree-profile.cc
+++ b/gcc/tree-profile.cc
@@ -24,6 +24,7 @@ along with GCC; see the file COPYING3.  If not see
 /* Generate basic block profile instrumentation and auxiliary files.
    Tree-based version.  See profile.cc for overview.  */
 
+#define INCLUDE_ALGORITHM
 #include "config.h"
 #include "system.h"
 #include "coretypes.h"
@@ -58,6 +59,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "alloc-pool.h"
 #include "symbol-summary.h"
 #include "symtab-thunks.h"
+#include "cfganal.h"
+#include "cfgloop.h"
 
 static GTY(()) tree gcov_type_node;
 static GTY(()) tree tree_interval_profiler_fn;
@@ -73,6 +76,879 @@ static GTY(()) tree ic_tuple_var;
 static GTY(()) tree ic_tuple_counters_field;
 static GTY(()) tree ic_tuple_callee_field;
 
+namespace
+{
+
+struct conds_ctx
+{
+    /* Output arrays allocated by the caller.  */
+    basic_block *blocks;
+    int *sizes;
+
+    /* The size of the blocks buffer.  This is just bug protection,
+       the caller should have allocated enough memory for blocks to never get
+       this many elements.
+       */
+    int maxsize;
+
+    /* Number of expressions found - this value is the number of entries in the
+       gcov output and the parameter to gcov_counter_alloc ().
+       */
+    int exprs;
+
+    /* Bitmap of the processed blocks - bit n set means basic_block->index has
+       been processed as a first-in-expression block.  This effectively stops
+       loop edges from being taken and subgraphs re-processed.
+       */
+    auto_sbitmap seen;
+
+    /* Pre-allocate bitmaps for per-function book keeping.  This is pure
+       instance reuse and the bitmaps carries no data between function calls.
+       */
+    auto_sbitmap expr;
+    auto_sbitmap reachable;
+
+    explicit conds_ctx (unsigned size) noexcept (true) : maxsize (0), exprs (0),
+    seen (size), expr (size), reachable (size)
+    {
+	bitmap_clear (seen);
+    }
+
+    void commit (basic_block top, int nblocks) noexcept (true)
+    {
+	blocks  += nblocks;
+	*sizes  += nblocks;
+	maxsize -= nblocks;
+
+	exprs++;
+	sizes++;
+	*sizes = 0;
+
+	bitmap_set_bit (seen, top->index);
+    }
+};
+
+/* Only instrument terms with fewer than number of bits in a (wide) gcov
+   integer, which is probably 64.  The algorithm itself does not impose this
+   limitation, but it makes for a simpler implementation.
+
+   * Allocating the output data structure (coverage_counter_alloc ()) can
+     assume pairs of gcov_type_unsigned and not use a separate length field.
+   * A pair gcov_type_unsigned can be used as accumulators.
+   * Updating accumulators is can use the bitwise operations |=, &= and not
+     custom operators that work for arbitrary-sized bit-sets.
+
+   Most real-world code should be unaffected by this, but it is possible
+   (especially for generated code) to exceed this limit.
+ */
+#define CONDITIONS_MAX_TERMS (sizeof (gcov_type_unsigned) * BITS_PER_UNIT)
+
+int
+index_of (const_basic_block needle, const const_basic_block *blocks, int size)
+{
+    for (int i = 0; i < size; i++)
+    {
+	if (blocks[i] == needle)
+	    return i;
+    }
+    return -1;
+}
+
+bool
+index_lt (const basic_block x, const basic_block y)
+{
+    return x->index < y->index;
+}
+
+/* Special cases of the single_*_p and single_*_edge functions in basic-block.h
+   that don't consider exception handling or other complex edges.  This helps
+   create a view of the CFG with only normal edges - if a basic block has both
+   an outgoing fallthrough and exceptional edge [1], it should be considered a
+   single-successor.
+
+   [1] if this is not possible, these functions can be removed and replaced by
+       their basic-block.h cousins.
+ */
+bool
+single (const vec<edge, va_gc> *edges)
+{
+    int n = EDGE_COUNT (edges);
+    if (n == 0)
+	return false;
+
+    for (edge e : edges)
+	if (e->flags & EDGE_COMPLEX)
+	    n -= 1;
+
+    return n == 1;
+}
+
+edge
+single_edge (const vec<edge, va_gc> *edges)
+{
+    for (edge e : edges)
+    {
+	if (e->flags & EDGE_COMPLEX)
+	    continue;
+	return e;
+    }
+    gcc_unreachable ();
+}
+
+/* Sometimes, for example with function calls and C++ destructors the CFG gets
+   extra nodes that are essentially single-entry-single-exit in the middle of
+   boolean expressions.  For example:
+
+      x || can_throw (y)
+
+               A
+              /|
+             / |
+            B  |
+            |  |
+            C  |
+           / \ |
+          /   \|
+         F     T
+
+   Without the extra node inserted by the function + exception it becomes a
+   proper 2-term graph, not 2 single-term graphs.
+
+               A
+              /|
+             C |
+            / \|
+           F   T
+
+   contract_edge ignores the series of intermediate nodes and makes a virtual
+   edge A -> C, without having to construct a new simplified CFG explicitly.
+
+   Such an expression cannot correctly be repreted as two 1-term expressions,
+   as it would break the condition masking.
+ */
+edge
+contract_edge (edge e, sbitmap expr)
+{
+    while (true)
+    {
+	basic_block src  = e->src;
+	basic_block dest = e->dest;
+
+	if (!single (dest->succs))
+	    return e;
+	if (!single (dest->preds))
+	    return e;
+	if (!single (src->preds))
+	    return e;
+
+	edge succe = single_edge (dest->succs);
+	if (!single (succe->dest->preds))
+	    return e;
+
+	bitmap_set_bit (expr, dest->index);
+	e = succe;
+    }
+}
+
+edge
+contract_edge_up (edge e, sbitmap expr)
+{
+    while (true)
+    {
+	basic_block src  = e->src;
+	basic_block dest = e->dest;
+
+	if (!single (dest->succs))
+	    return e;
+	if (!single (dest->preds))
+	    return e;
+	if (!single (src->preds))
+	    return e;
+
+	if (expr) bitmap_set_bit (expr, src->index);
+	e = single_pred_edge (src);
+    }
+}
+
+bool
+is_conditional_p (const basic_block b)
+{
+    if (single_succ_p (b))
+        return false;
+
+    unsigned t = 0;
+    unsigned f = 0;
+    for (edge e : b->succs)
+    {
+        t |= (e->flags & EDGE_TRUE_VALUE);
+        f |= (e->flags & EDGE_FALSE_VALUE);
+    }
+    return t && f;
+}
+
+/* The first block in the output will always be the source block of the edge
+   that will apply the masking operation, with the remaining blocks effectively
+   unordered.
+ */
+int
+find_conditions_masked_by (
+    basic_block block,
+    const sbitmap expr,
+    const unsigned *flag,
+    basic_block *out,
+    int maxsize)
+{
+    int n = 0;
+    for (edge e : block->preds)
+    {
+	/* Skip any predecessor not in the expression - there might be such an
+	   edge to the enclosing expression or in the presence of loops, but
+	   masking cannot happen outside the expression itself. */
+	if (!bitmap_bit_p (expr, e->src->index))
+	    continue;
+
+	e = contract_edge_up (e, NULL);
+	if (e->flags & flag[0])
+	    out[n++] = e->src;
+    }
+
+    if (n > 1)
+    {
+	basic_block *top = std::max_element (out, out + n, index_lt);
+	std::iter_swap (top, out);
+    }
+
+    for (int pos = 0; pos < n; pos++)
+    {
+	for (edge e : out[pos]->preds)
+	{
+	    if (!bitmap_bit_p (expr, e->src->index))
+		continue;
+	    if (index_of (e->src, out, n) != -1)
+		continue;
+
+	    e = contract_edge_up (e, NULL);
+	    if (e->flags & flag[1])
+		out[n++] = e->src;
+
+	    gcc_assert (n < maxsize);
+	}
+    }
+
+    return n;
+}
+
+/* Scan the blocks that make up an expression and look for conditions that
+   would mask other conditions.  For a deeper discussion on masking, see
+   Whalen, Heimdahl, De Silva "Efficient Test Coverage Measurement for MC/DC".
+   Masking is best illustrated with an example:
+
+   A || B.  If B is true then A will does not independently affect the decision.
+   In a way, this is "reverse" short circuiting, and always work on the
+   right-hand side of expressions.  * || true is always true and * && false is
+   always false - the left-hand-side does not affect the outcome, and their
+   values should not contribute to modifidied condition/decision coverage.
+
+   A || B interpreted as a decision diagram becomes:
+
+    A
+   t|\f
+    | \
+    |  B
+    |t/ \f
+    |/   \
+    T     F
+
+   The algorithm looks for triangles like ATB.  Masking right-hand sides happen
+   when a block has a pair of incoming edges of the same boolean value, and
+   there is an edge connecting the two predecessors with the *opposite* boolean
+   value.  The masking block is B, and the masked block is A.  In this
+   particular example:
+
+   Masking can affect "outside" its own subexpression; in A || (B && C) if C is
+   false, B is masked.  However, if (B && C) is true, A gets masked.  B && C
+   can be determined from evaluating C since !B would short-circuit, so a true
+   C would mask A.
+
+    A
+   t|\f
+    | \
+    |  \
+    |   \
+    |    B
+    |  t/ \f
+    |  C   |
+    |t/ \f |
+    |/   \ |
+    T     F
+
+   Notice how BFC forms a triangle.  Expressions masked by an edge are
+   determined by:
+   * Go to the predecessor with truth value b (if it exists).
+   * Follow the path of ancestors with taking only !b edges, recording every
+     node from here on.
+
+   For the case where C can mask A, the path goes C [true]-> B -> [false] A, so
+   C [true] masks A.
+
+   The mask is output as a bit-set stored in a gcov_type_unsigned.  The
+   bit-sets are output in pairs, one for each decision (the outcome of the
+   boolean expression, or which arc to take in the CFG).
+ */
+void
+find_subexpr_masks (
+    const basic_block *blocks,
+    int nblocks,
+    gcov_type_unsigned *masks)
+{
+    const unsigned flags[] = {
+	EDGE_TRUE_VALUE,
+	EDGE_FALSE_VALUE,
+	EDGE_TRUE_VALUE,
+    };
+
+    basic_block path[CONDITIONS_MAX_TERMS];
+    auto_sbitmap expr (n_basic_blocks_for_fn (cfun));
+    bitmap_clear (expr);
+    for (int i = 0; i < nblocks; i++)
+	bitmap_set_bit (expr, blocks[i]->index);
+
+    for (int i = 0; i < nblocks; i++)
+    {
+	basic_block block = blocks[i];
+	if (single_pred_p (block))
+	    continue;
+
+	for (int k = 0; k < 2; k++)
+	{
+	    const int n = find_conditions_masked_by
+		(block, expr, flags + k, path, CONDITIONS_MAX_TERMS);
+
+	    if (n < 2)
+		continue;
+
+	    const int m = 2*index_of (path[0], blocks, nblocks) + k;
+	    for (int i = 1; i < n; i++)
+	    {
+		const int index = index_of (path[i], blocks, nblocks);
+		masks[m] |= gcov_type_unsigned (1) << index;
+	    }
+	}
+    }
+}
+
+int
+collect_reachable_conditionals (
+    basic_block pre,
+    basic_block post,
+    basic_block *out,
+    int maxsize,
+    sbitmap expr)
+{
+    gcc_assert (maxsize > 0);
+
+    basic_block loop = pre->loop_father->header;
+    int n = 0;
+    out[n++] = pre;
+    bitmap_set_bit (expr, pre->index);
+
+    for (int pos = 0; pos < n; pos++)
+    {
+	basic_block block = out[pos];
+
+	for (edge e : block->succs)
+	{
+	    basic_block dest = contract_edge (e, expr)->dest;
+
+	    /* Skip loop edges, as they go outside the expression.  */
+	    if (dest == loop)
+		continue;
+	    if (dest == post)
+		continue;
+	    if (!is_conditional_p (dest))
+		continue;
+	    /* Already-seen, don't re-add.  */
+	    if (bitmap_bit_p (expr, dest->index))
+		continue;
+
+	    bitmap_set_bit (expr, dest->index);
+	    out[n++] = dest;
+	    if (n == maxsize)
+		return n;
+	}
+    }
+
+    return n;
+}
+
+int
+collect_neighbors (basic_block *blocks, int nblocks, sbitmap reachable)
+{
+    int n = 0;
+    basic_block *exits = blocks + nblocks;
+    for (int i = 0; i < nblocks; i++)
+    {
+	for (edge e : blocks[i]->succs)
+	{
+	    if (bitmap_bit_p (reachable, e->dest->index))
+		continue;
+
+	    bitmap_set_bit (reachable, e->dest->index);
+	    exits[n++] = e->dest;
+	}
+    }
+
+    return n;
+}
+
+/* Find and isolate the first expression between two dominators.
+
+   Either block of a conditional could have more decisions and loops, so
+   isolate the first decision by set-intersecting all paths from the
+   post-dominator to the entry block.
+
+   The function returns the number of blocks from n that make up the leading
+   expression in prefix order (i.e. the order expected by the instrumenting
+   code).  When this function returns 0 there are no decisions between pre and
+   post, and this segment of the CFG can safely be skipped.
+
+   The post nodes can have predecessors that do not belong to this subgraph,
+   which are skipped.  This is expected, for example when there is a
+   conditional in the else-block of a larger expression:
+
+   if (A)
+   {
+      if (B) {}
+   }
+   else
+   {
+      if (C) {} else {}
+   }
+
+             A
+          t / \ f
+           /   \
+          B     C
+         /\    / \
+        /  \  T   F
+       T    \  \ /
+        \   |   o
+         \  |  /
+          \ | /
+           \|/
+            E
+
+   Processing [B, E) which looks like:
+
+      B
+     /|
+    / |
+   T  |
+    \ /
+     E ----> o // predecessor outside [B, e)
+ */
+
+/* Do a (upwards) search for reachable nodes and mark them in the reachable
+   set, making sure not to take loop edges.  dfs_enumerate_from () won't work as
+   the filter function needs information from the edge. */
+void
+find_reachable (
+    sbitmap reachable,
+    basic_block pre,
+    basic_block post,
+    basic_block *stack)
+{
+    stack[0] = pre;
+    bitmap_set_bit (reachable, pre->index);
+    bitmap_set_bit (reachable, post->index);
+    for (int n = 0; n >= 0; n--)
+    {
+	for (edge e : stack[n]->preds)
+	{
+	    if (bitmap_bit_p (reachable, e->src->index))
+		continue;
+
+	    /* Ignore any loop edges.  */
+	    if (dominated_by_p (CDI_DOMINATORS, e->src, e->dest))
+		continue;
+
+	    basic_block src = contract_edge_up (e, reachable)->src;
+	    bitmap_set_bit (reachable, src->index);
+	    stack[n++] = src;
+	}
+    }
+}
+
+int
+find_first_conditional (conds_ctx &ctx, basic_block pre, basic_block post)
+{
+    basic_block *blocks = ctx.blocks;
+    sbitmap expr = ctx.expr;
+    sbitmap reachable = ctx.reachable;
+    bitmap_clear (expr);
+    bitmap_clear (reachable);
+    blocks[0] = pre;
+
+    /* If there is a direct edge to the post dominator then this cannot only be
+       a single-term conditional *unless* it is a loop (in which case the
+       to-post edge is the loop exit edge).
+     */
+    const bool dowhile = !loop_exits_from_bb_p (pre->loop_father, pre);
+    const bool isloop = bb_loop_header_p (pre) && !dowhile;
+    if (find_edge (pre, post) && !isloop)
+	return 1;
+
+    const int nblocks = collect_reachable_conditionals
+	(pre, post, blocks, ctx.maxsize, expr);
+    if (nblocks == 1)
+	return nblocks;
+
+    bitmap_copy (reachable, expr);
+    const int nexits = collect_neighbors (blocks, nblocks, reachable);
+    if (nexits == 2)
+	return nblocks;
+
+    /* Find reachable nodes from the neighbors.  */
+    basic_block *exits = blocks + nblocks;
+    for (int i = 0; i < nexits; i++)
+    {
+	for (edge e : exits[i]->preds)
+	{
+	    if (!dominated_by_p (CDI_DOMINATORS, e->src, pre))
+		continue;
+
+	    bitmap_clear (reachable);
+	    find_reachable (reachable, e->src, pre, exits + nexits);
+	    for (edge f : exits[i]->preds)
+		bitmap_set_bit (reachable, f->src->index);
+	    bitmap_and (expr, expr, reachable);
+	}
+    }
+
+    int k = 0;
+    for (int i = 0; i < nblocks; i++)
+	if (bitmap_bit_p (expr, blocks[i]->index))
+	    blocks[k++] = blocks[i];
+    return k;
+}
+
+void
+emit_bitwise_op (edge e, tree lhs, tree op1, tree_code op, tree op2,
+		 int atomic = 0)
+{
+    tree tmp;
+    gassign *read;
+    gassign *bitw;
+    gimple  *write;
+
+    /* Insert lhs = op1 <bit-op> op2.
+
+       The operands are assumed to be local accumulators or already SSA'd (and
+       not referencing globals) and don't need atomic reads anymore.
+     */
+    tmp   = make_temp_ssa_name (gcov_type_node, NULL, "__conditions_tmp");
+    read  = gimple_build_assign (tmp, op1);
+    tmp   = make_temp_ssa_name (gcov_type_node, NULL, "__conditions_tmp");
+    bitw  = gimple_build_assign (tmp, op, gimple_assign_lhs (read), op2);
+
+    if (atomic)
+    {
+	tree relaxed = build_int_cst (integer_type_node, MEMMODEL_RELAXED);
+	tree store = builtin_decl_explicit (
+		TYPE_PRECISION (gcov_type_node) > 32
+		? BUILT_IN_ATOMIC_STORE_8
+		: BUILT_IN_ATOMIC_STORE_4);
+
+	// __atomic_store_8 (&lhs, (op1 | op2), __ATOMIC_RELAXED)
+	write = gimple_build_call
+	    (store, 3, build_addr (lhs), gimple_assign_lhs (bitw), relaxed);
+    }
+    else
+    {
+	write = gimple_build_assign (lhs, gimple_assign_lhs (bitw));
+    }
+
+    gsi_insert_on_edge (e, read);
+    gsi_insert_on_edge (e, bitw);
+    gsi_insert_on_edge (e, write);
+}
+
+/* Walk the CFG and collect conditionals.
+
+   1. Collect all nodes reachable from the root node through (contracted) paths
+      of true/false edges.
+   2. Collect the neighbors of the reachable node (set).
+   3. From every node in the neighborhood, walk the up the CFG and mark every
+      reachable node.  Only the nodes reachable from *every* node in the
+      neighborhood are a part of the first expression.
+   4. Record the expression plus the two successors of the last (highest-index)
+      node in the expression, i.e. the last term.
+   5. Repeat using the two successors as new root nodes.
+
+   It is not guaranteed to find nodes in the order of the expression, i.e. it
+   might find (a || b) && c as [a c b], so the output is sorted by
+   basic_block->index.
+
+   Steps 2 and 3 are necessary to distinguish chained conditionals from
+   multi-term conditionals, e.g. to separate
+
+       if (a)
+       {
+           if (b)
+               work ();
+       }
+       if (a && b)
+           work ();
+ */
+void
+collect_conditions (conds_ctx& ctx, basic_block entry, basic_block exit)
+{
+    basic_block pre;
+    basic_block post;
+    for (pre = entry ;; pre = post)
+    {
+	if (pre == exit)
+	    break;
+	if (bitmap_bit_p (ctx.seen, pre->index))
+	    break;
+
+	bitmap_set_bit (ctx.seen, pre->index);
+	post = get_immediate_dominator (CDI_POST_DOMINATORS, pre);
+	if (!is_conditional_p (pre))
+	{
+	    for (edge e : pre->succs)
+		collect_conditions (ctx, e->dest, post);
+	    continue;
+	}
+
+	int nterms = find_first_conditional (ctx, pre, post);
+	std::sort (ctx.blocks, ctx.blocks + nterms, index_lt);
+	basic_block last = ctx.blocks[nterms - 1];
+	if (size_t (nterms) <= CONDITIONS_MAX_TERMS)
+	{
+	    for (edge e : last->succs)
+		if (e->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE))
+		    ctx.blocks[nterms++] = e->dest;
+	    ctx.commit (pre, nterms);
+	}
+	else
+	{
+	    location_t loc = gimple_location (gsi_stmt (gsi_last_bb (pre)));
+	    warning_at
+		(loc, OPT_Wcoverage_too_many_conditions,
+		 "Too many conditions (found %d); giving up coverage", nterms);
+	}
+
+	for (edge e : last->succs)
+	    collect_conditions (ctx, e->dest, post);
+    }
+}
+
+}
+
+/* Condition coverage (MC/DC)
+
+   Algorithm
+   ---------
+   Whalen, Heimdahl, De Silva in "Efficient Test Coverage Measurement for
+   MC/DC" describe an algorithm for modified condition/decision coverage based
+   on AST analysis.  This algorithm analyses the control flow graph, but the
+   accumulation and recording of (partial) results is inspired by their work.
+   The individual phases are described in more detail closer to the
+   implementation.
+
+   The CFG is broken up into segments between dominators.  This isn't strictly
+   necessary, but since boolean expressions cannot cross dominators it makes
+   for a nice way to reduce work.
+
+   The coverage only considers the positions, not the symbols, in a
+   conditional, e.g. !A || (!B && A) is a 3-term conditional even though A
+   appears twice.  Subexpressions have no effect on term ordering:
+   (a && (b || (c && d)) || e) comes out as [a b c d e].
+
+   The output for gcov is a vector of pairs of unsigned integers, interpreted
+   as bit-sets, where the bit index corresponds to the index of the condition
+   in the expression.
+
+   Implementation and interface
+   ----------------------------
+   Two public functions - find_conditions and instrument_decisions.
+
+   find_conditions outputs two arrays, blocks and sizes.  The sizes describes
+   the ranges of blocks that make up every conditional, in a [first, last)
+   fashion, i.e. begin = blocks[sizes[n]], end = blocks[sizes[n+1]] for
+   expression n.  The function returns the number of expressions.
+
+   The coverage is designed to get most of its memory needs met by the caller,
+   and heavily uses the end of the blocks array for buffer.  This is both for
+   performance (no resizes, amortized cost of allocation) and
+   ease-of-implementation.  This makes the caller responsible for allocating
+   large enough arrays.
+
+   blocks:
+    Every permanent conditions add 2 blocks (the true & false dest blocks), and
+    assuming a worst case of one-block-per-expr just storing the output needs
+    3*n_basic_blocks_for_fn ().  Additionally, the searches might need to buffer
+    the full graph between entry and exit [1]. In total that means
+    5*n_basic_blocks_for_fn () should should be plenty, and the savings for
+    reducing this number is probably not worth the risk.
+   sizes:
+    sizes gets one entry per expression plus initial, so
+    1+n_basic_blocks_for_fn () is sufficient.
+
+   instrument_decisions uses the information provided by find_conditions to
+   inject code onto edges in the CFG.  Every instrumented function gets local
+   accumulators zero'd on function entry, which on function exit are flushed to
+   the global accumulators (created by coverage_counter_alloc ()).
+
+   [1] In truth, the set of nodes that could be buffered gets smaller as the
+       algorithm walks the CFG, but assuming just using node-count comes at
+       little run-time cost and is guaranteed to be sufficient.
+ */
+int
+find_conditions (
+    basic_block entry,
+    basic_block exit,
+    basic_block *blocks,
+    int *sizes,
+    int maxsize)
+{
+    record_loop_exits ();
+    bool free_dom = false;
+    bool free_post_dom = false;
+    if (!dom_info_available_p (CDI_POST_DOMINATORS))
+    {
+	calculate_dominance_info (CDI_POST_DOMINATORS);
+	free_post_dom = true;
+    }
+
+    if (!dom_info_available_p (CDI_DOMINATORS))
+    {
+	calculate_dominance_info (CDI_DOMINATORS);
+	free_dom = true;
+    }
+
+    conds_ctx ctx (maxsize);
+    ctx.blocks = blocks;
+    ctx.sizes = sizes + 1;
+    ctx.maxsize = maxsize;
+    sizes[0] = sizes[1] = 0;
+    collect_conditions (ctx, entry, exit);
+
+    /* Partial sum.  */
+    for (int i = 0; i < ctx.exprs; ++i)
+	sizes[i + 1] += sizes[i];
+
+    if (free_post_dom)
+	free_dominance_info (CDI_POST_DOMINATORS);
+    if (free_dom)
+	free_dominance_info (CDI_DOMINATORS);
+
+    return ctx.exprs;
+}
+
+int instrument_decisions (basic_block *blocks, int nblocks, int condno)
+{
+    /* Insert function-local accumulators per decision.  */
+    tree accu[2] = {
+	build_decl
+	    (UNKNOWN_LOCATION, VAR_DECL,
+	     get_identifier ("__conditions_accu_true"), gcov_type_node),
+	build_decl
+	    (UNKNOWN_LOCATION, VAR_DECL,
+	     get_identifier ("__conditions_accu_false"), gcov_type_node),
+    };
+    for (tree acc : accu)
+    {
+	tree zero = build_int_cst (gcov_type_node, 0);
+	for (edge e : ENTRY_BLOCK_PTR_FOR_FN (cfun)->succs)
+	    gsi_insert_on_edge (e, gimple_build_assign (acc, zero));
+    }
+
+    auto_vec<gcov_type_unsigned, 32> masks (nblocks * 2);
+    masks.quick_grow_cleared (nblocks * 2);
+    find_subexpr_masks (blocks, nblocks, masks.address ());
+
+    /* The true/false target blocks are included in the nblocks set, but
+       their outgoing edges should not be instrumented.
+     */
+    gcc_assert (nblocks > 2);
+
+    /* Add instructions for updating the function-local accumulators.  */
+    for (int i = 0; i < nblocks - 2; i++)
+    {
+	for (edge e : blocks[i]->succs)
+	{
+	    if (!(e->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE)))
+		continue;
+
+	    /* accu |= expr[i] */
+	    const int t = !!(e->flags & EDGE_FALSE_VALUE);
+	    tree rhs = build_int_cst (gcov_type_node, 1ULL << i);
+	    emit_bitwise_op (e, accu[t], accu[t], BIT_IOR_EXPR, rhs);
+
+	    if (masks[2*i + t] == 0)
+		continue;
+
+	    /* accu &= mask[i] */
+	    tree mask = build_int_cst (gcov_type_node, ~masks[2*i + t]);
+	    for (int j = 0; j < 2; j++)
+		emit_bitwise_op (e, accu[j], accu[j], BIT_AND_EXPR, mask);
+	}
+    }
+
+    /* Add instructions for updating the global accumulators.  */
+    basic_block exit = EXIT_BLOCK_PTR_FOR_FN (cfun);
+    for (edge e : exit->preds)
+    {
+	for (int k = 0; k < 2; k++)
+	{
+	    tree ref = tree_coverage_counter_ref
+		(GCOV_COUNTER_CONDS, 2*condno + k);
+
+	    tree tmp = make_temp_ssa_name
+		(gcov_type_node, NULL, "__conditions_tmp");
+
+	    const bool atomic = flag_profile_update == PROFILE_UPDATE_ATOMIC;
+	    if (atomic)
+	    {
+		tree relaxed = build_int_cst
+		    (integer_type_node, MEMMODEL_RELAXED);
+		tree atomic_load = builtin_decl_explicit
+		    (TYPE_PRECISION (gcov_type_node) > 32
+		     ? BUILT_IN_ATOMIC_LOAD_8
+		     : BUILT_IN_ATOMIC_LOAD_4);
+
+		tree lhs_type = TREE_TYPE (TREE_TYPE (atomic_load));
+		tree lhs = make_temp_ssa_name
+		    (lhs_type, NULL, "__conditions_load_lhs");
+
+		gcall *load = gimple_build_call
+		    (atomic_load, 2, build_addr (ref), relaxed);
+		gimple_set_lhs (load, lhs);
+		gassign *assign = gimple_build_assign
+		    (tmp, NOP_EXPR, gimple_call_lhs (load));
+
+		gsi_insert_on_edge (e, load);
+		gsi_insert_on_edge (e, assign);
+	    }
+	    else
+	    {
+		gassign *read = gimple_build_assign (tmp, ref);
+		tmp = gimple_assign_lhs (read);
+		gsi_insert_on_edge (e, read);
+	    }
+
+	    ref = unshare_expr (ref);
+	    emit_bitwise_op (e, ref, accu[k], BIT_IOR_EXPR, tmp, atomic);
+	}
+    }
+
+    return nblocks - 2;
+}
+
+#undef CONDITIONS_MAX_TERMS
+
 /* Do initialization work for the edge profiler.  */
 
 /* Add code:
@@ -758,7 +1634,7 @@ tree_profiling (void)
 	  thunk = true;
 	  /* When generate profile, expand thunk to gimple so it can be
 	     instrumented same way as other functions.  */
-	  if (profile_arc_flag)
+	  if (profile_arc_flag || profile_condition_flag)
 	    expand_thunk (node, false, true);
 	  /* Read cgraph profile but keep function as thunk at profile-use
 	     time.  */
@@ -803,7 +1679,7 @@ tree_profiling (void)
   release_profile_file_filtering ();
 
   /* Drop pure/const flags from instrumented functions.  */
-  if (profile_arc_flag || flag_test_coverage)
+  if (profile_arc_flag || profile_condition_flag || flag_test_coverage)
     FOR_EACH_DEFINED_FUNCTION (node)
       {
 	if (!gimple_has_body_p (node->decl)
@@ -897,7 +1773,7 @@ pass_ipa_tree_profile::gate (function *)
      disabled.  */
   return (!in_lto_p && !flag_auto_profile
 	  && (flag_branch_probabilities || flag_test_coverage
-	      || profile_arc_flag));
+	      || profile_arc_flag || profile_condition_flag));
 }
 
 } // anon namespace
-- 
2.20.1


^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH] Add condition coverage profiling
  2022-03-25 19:44   ` Jørgen Kvalsvik
  2022-03-28 13:39     ` Martin Liška
@ 2022-03-28 13:52     ` Jørgen Kvalsvik
  2022-03-28 14:40       ` Jørgen Kvalsvik
  1 sibling, 1 reply; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-03-28 13:52 UTC (permalink / raw)
  To: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 27909 bytes --]

Hello,

I have made some changes based on the review feedback, please have a look. In
summary:

* Support for -profile=atomic
* gcc -fprofile-conditions does not ICE without -fprofile-arcs
* Indentation and formatting complies with contrib/GNU_style_check
* Some small adjustment to documentation, phrasing

As mentioned in the previous message, I think it should only be necessary to do
atomic fetch/store on the global accumulators, not the locals (as they are
specific to the activation record). If is wrong then I have no problems
extending the atomics to the function-local accumulators too, please let me
know. I added a simple test case for -profile=update, gcov-20.c.

I've added checks for profile_condition_flag everywhere the profile_arc_flag is
checked, as they share the same code paths and infrastructure. I also made it so
that -fprofile-conditions is sufficient for linking gcov.

gcc will no longer crash on -fprofile-conditions -fprofile-generate by
explicitly checking coverage_begin_*. I'm not too happy with how it turned out,
but the condition coverage needs a lot more analysis before it can write out its
gcov structure, which doesn't fit with the flow of branch_prob ().

Thanks,
Jørgen

On 25/03/2022 20:44, Jørgen Kvalsvik wrote:
> Hello, and thanks for the review!
> 
>> 1) Do I correctly understand that __conditions_accu_true/false track
>> every short-circuit sub-expression of a condition and record
>> if a given sub-expr is seen to be true or false?
> 
> Sort-of. It is not really aware of sub-expressions at all, but tracks every
> boolean condition/term in the full expression, mapped to a bitvector. I usually
> find it easier to understand visually:
> 
> if (a || (b && c) || d)
> 
> terms | a b c d
> ------|--------
> true  | 0 0 0 0
> false | 0 0 0 0
> 
> Whenever a = true, true becomes 1000 and false remains 0000. a = true would
> short-circuit, (implemented as a normal edge to the "exit" node of the
> expression) leaving bcd unevaluated. Coverage is then determined as the number
> of unset bits in this vector. The accumulators are zero'd on every function
> entry, and |= into the global "counter" on function exit.
> 
>> 2) As mentioned these are bit sets, can you please explain why you (&
>> -value) from these sets?
> 
> Yes, excellent question. This should answer 3) too, and is the most surprsing
> thing when unfamiliar with MC/DC.
> 
> For modified condition/decision coverage we need to prove that a condition's
> value independently affects the outcome/decision. In other words, would
> flipping a condition change the outcome?
> 
> Let's take your if (a || b) check, which says that 1/4 conditions are covered
> on (0, 1). If a = 1, the expression is always true, and b is never evaluated.
> In fact, the value of b does not change the outcome at all, so intuitively only
> 1/4 of the conditions are covered.
> 
> On (0, 1) b gets evaluated, so in a way we know that a = 0. However, the value
> of a will no longer affect the outcome because (* || 1) is always true. In a
> way, you can think of it as reverse short-circuiting, the same thing would
> happen on (* && 0). This is what Wahlen, Heimdahl, and De Silva calls masking.
> What I do is figure out when masking must happen by analyzing the CFG and zero
> out the bits that are masked (i.e. no longer affect the outcome). This is in
> practice what happens to the bits when regular short circuiting happen.
> 
> So while (0, 1) "sees" the condition a = false, it cannot prove that it
> mattered based on your inputs. In general, you need N+1 test cases to cover N
> conditionals with MC/DC. As a consequence, the only way of covering a = false
> is (0, 0), which alone would be 2/4 cases covered.
> 
> I hope this made it clearer, if not I can write a more elaborate explanation
> with more examples.
> 
>> 4) I noticed various CFG patterns in tree-profile.cc which are handled. Can
>> you please explain how the algorithm works even without knowing the original
>> paper?
> 
> My paper is not written yet, but I will describe the high-level algorithm (+
> extracts from source comments) at the end of this email, as it is a bit long.
> 
>> 5) I noticed the following ICE:
> Yes, this is an unfortunate flaw - I piggyback on the setup done by branch
> coverage, which means I silently assume --coverage is used. I was unsure what
> to do (and meant to ask, sorry!) as it is a larger decision - should
> condition-coverage also imply branch coverage? Should condition coverage work
> on its own? Other options?
> 
> I'm happy to implement either strategy - please advise on what you would
> prefer.
> 
> 
>> 6) Please follow the GNU coding style, most notable we replace 8 spaces with 
>> a tab. And we break line before {, (, ... Remove noexcept specifiers for 
>> functions and I think most of the newly added functions can be static. And 
>> each newly added function should have a comment.
> 
> Sure thing, I'll add some tabs. All the helper functions are already static (in
> an unnamed namespace), but I can remove the namespace and/or add static.
> 
>> 7) Please consider supporting of -profile-update=atomic where you'll need to
>> utilize an atomic builts (see how other instrumentation looks like with
>> it).
> Will do! I always intended to support the atomics, not having them there is an
> oversight. To confirm, I will only need to use atomics for the global counters,
> right? The per-call accumulators are local to each activation record and should
> not need the atomics, or am I missing something?
> 
> ALGORITHM
> 
> Phase 1: expression isolation from CFG analysis
> 
> Break the CFG into smaller pieces ("sections") by splitting it on dominators.
> No expression can cross a dominator, so becomes a natural way of terminating
> expression searches.
> 
> For each section, do a BFS from the root node and collect all reachable nodes
> following edges that point to "conditional" nodes, i.e. nodes with outgoing
> true&false edges. Series of single-parent single-exit nodes are contracted.
> This gives a good estimate for an expression, but might include conditions in
> the if/else blocks.
> 
> The algorithm then collects the immediate neighborhood, i.e. all nodes adjacent
> to the collected set, dropping everything in the set itself. Then, a new BFS is
> performed, but now "upwards" (i.e. following predecessors), skipping any
> neighbor not dominated by the root (this eliminates loops and other expressions
> with the same "exit" block, see BFS-up). The intersection of all the paths
> found with this BFS is the isolated expression.
> 
> If the root is not a conditional, it is skipped and it's successor
> becomes the new root. If an expression is found it has a pair of "expression
> sucessors", and the algorithm recurses into both, evaluating this sub-section
> in isolation, i.e.
> 
> if (a || b) {
> root:
>     if (c && d) {
>         ...
>     }
>     ...
> sink:
> }
> 
> The sections between the root and sink labels are evaulated as if the outer
> expression did not exist.
> 
> From the comments:
> 
>     1. Collect all nodes reachable from the root node through (contracted) paths
>        of true/false edges.
>     2. Collect the neighbors of the reachable node (set).
>     3. From every node in the neighborhood, walk the up the CFG and mark every
>        reachable node. Only the nodes reachable from *every* node in the
>        neighborhood are a part of the first expression.
>     4. Record the expression plus the two successors of the last (highest-index)
>        node in the expression, i.e. the last term.
>     5. Repeat using the two successors as new root nodes.
> 
>     It is not guaranteed to find nodes in the order of the expression, i.e. it
>     might find (a || b) && c as [a c b], so the output is sorted by
>     basic_block->index.
> 
>     Steps 2 and 3 are necessary to distinguish chained conditionals from
>     multi-term conditionals, e.g. to separate
> 
>         if (a)
>         {
>             if (b)
>                 work ();
>         }
>         if (a && b)
>             work ();
> 
> 
> On BFS-up:
> 
>     Either block of a conditional could have more decisions and loops, so
>     isolate the first decision by set-intersecting all paths from the
>     post-dominator to the entry block.
> 
>     The function returns the number of blocks from n that make up the leading
>     expression in prefix order (i.e. the order expected by the instrumenting
>     code).  When this function returns 0 there are no decisions between pre and
>     post, and this segment of the CFG can safely be skipped.
> 
>     The post nodes can have predecessors that do not belong to this subgraph,
>     which are skipped.  This is expected, for example when there is a
>     conditional in the else-block of a larger expression:
> 
>     if (A)
>     {
>        if (B) {}
>     } else {
>        if (C) {} else {}
>     }
> 
>              A
>           t / \ f
>            /   \
>           B     C
>          /\    / \
>         /  \  T   F
>        T    \  \ /
>         \   |   o
>          \  |  /
>           \ | /
>            \|/
>             E
> 
>     Processing [B, E) which looks like:
> 
>        B
>       /|
>      / |
>     T  |
>      \ /
>       E ----> o // predecessor outside [B, e)
> 
> 
> Phase 2: Masking vector and instrumentation
> 
> The input to this phase is the basic blocks that make up every expression, plus
> its two exit nodes, i.e. the 3-term expression (a || b || c) yields the input
> [a b c T F] to phase 2.
> 
> For every expression, allocate two local accumulators (bit-vectors), one for
> the true-vector, one for the false-vector. These are zero'd on function entry
> and flushed on function exit.
> 
> For each "conditional" block (a b c), unconditionally set the corresponding bit
> when an edge in the CFG is taken. Rough pseudo-gimple illustration:
> 
> if (a) {
>     accu_true |= 1 << 0
>     goto done;
> } else {
>     accu_false |= 1 << 0
>     if (b) {
>         accu_true |= 1 << 1;
>         goto done;
>     } else {
>         accu_false |= 1 << 1;
>         ...
>     }
> }
> 
> global_true[expr_n]  |= accu_true
> global_false[expr_n] |= accu_false
> 
> This alone would more-or-less be branch coverage, but would not prove that
> terms would independently decide the outcome. To find masking, the expression
> is walked in search of triangles. A || B as a decision diagram becomes
> 
>    A
>   t|\f
>    | \
>    |  B
>    |t/ \f
>    |/   \
>    T     F
> 
> and we search for triangles like ATB, that is nodes with 2 predecessors with
> the same boolean value, where the two predecessors are connected with the
> opposite value. This marks a candidate for masking, and the terms masked are
> determined by chasing predecessors starting at the top of the triangle for as
> long as the chain of predecessors have the same truth value as the "exit"
> edges. This set is often empty.
> 
> The edge that triggers masking is then instrumented with a accu &= mask which
> wipes out anything that does not independently affect the outcome. Note that
> nothing is ever wiped from the global accumulator, only the local. The (a || b)
> example had a false vector of [1, 0] at some point, but it was wiped by the
> masking operation (b = 1 is the only masking value for a || b).
> 
> From the comments:
> 
>     Scan the blocks that make up an expression and look for conditions that
>     would mask other conditions.  For a deeper discussion on masking, see
>     Whalen, Heimdahl, De Silva "Efficient Test Coverage Measurement for MC/DC".
>     Masking is best illustrated with an example:
> 
>     A || B.  If B is true then A will does not independently affect the
>     decision.  In a way, this is "reverse" short circuiting, and always work on
>     the right-hand side of expressions.  * || true is always true and * &&
>     false is always false - the left-hand-side does not affect the outcome, and
>     their values should not contribute to modified condition/decision coverage.
> 
>     A || B interpreted as a decision diagram becomes:
> 
>      A
>     t|\f
>      | \
>      |  B
>      |t/ \f
>      |/   \
>      T     F
> 
>     The algorithm looks for triangles like ATB.  Masking right-hand sides happen
>     when a block has a pair of incoming edges of the same boolean value, and
>     there is an edge connecting the two predecessors with the *opposite* boolean
>     value.  The masking block is B, and the masked block is A.  In this
>     particular example:
> 
>     Masking can affect "outside" its own subexpression; in A || (B && C) if C is
>     false, B is masked.  However, if (B && C) is true, A gets masked.  B && C
>     can be determined from evaluating C since !B would short-circuit, so a true
>     C would mask A.
> 
>      A
>     t|\f
>      | \
>      |  \
>      |   \
>      |    B
>      |  t/ \f
>      |  C   |
>      |t/ \f |
>      |/   \ |
>      T     F
> 
>     Notice how BFC forms a triangle.  Expressions masked by an edge are
>     determined by:
>     * Go to the predecessor with truth value b (if it exists).
>     * Follow the path of ancestors with taking only !b edges, recording every
>       node from here on.
> 
>     For the case where C can mask A, the path goes C [true]-> B -> [false] A, so
>     C [true] masks A.
> 
>     The mask is output as a bit-set stored in a gcov_type_unsigned.  The
>     bit-sets are output in pairs, one for each decision (the outcome of the
>     boolean expression, or which arc to take in the CFG).
> 
> Phase 3: reading the report
> 
> This is pretty standard gcov stuff, except the "counters" are interpreted as
> bitsets, and the number of terms in the expressions is stored. Coverage then
> becomes popcount / expected.
> 
> Final remarks:
> 
> Granted, this is a bit dense, but I think the overall approach is easy to grasp
> when supported by a lot of example graphs and stepping. I will try to type up a
> bunch with the paper and publish, but for now all I have is the code (which I
> hope for the most part is easy to follow). I'll update this with style updates
> and atomics support, but I would prefer a maintainer to make the call if this
> condition coverage should imply branch coverage, or if it should work without it.
> 
> Thanks,
> Jørgen
> 
> On 24/03/2022 17:08, Martin Liška via Gcc-patches wrote:
>> On 3/21/22 12:55, Jørgen Kvalsvik via Gcc-patches wrote:
>>
>> Hello.
>>
>> Thanks for very interesting extension of the existing GCOV profiling.
>>
>> I briefly read the patch and investigated what happens and I've got various
>> questions/comments about it:
>>
>> 1) Do I correctly understand that __conditions_accu_true/false track
>> every short-circuit sub-expression of a condition and record
>> if a given sub-expr is seen to be true or false?
>>
>> 2) As mentioned these are bit sets, can you please explain why you (& -value)
>> from these sets?
>>
>> 3) I noticed a false report for a simple test-case:
>>
>> $ cat cond.c
>> int x;
>>
>> void foo (int a, int b)
>> {
>>      if (a || b)
>>        x = 1;
>>      else
>>        x = 2;
>> }
>>
>> int main()
>> {
>>    foo (0, 1);
>>    return 0;
>> }
>>
>> $ rm *gcda ; gcc --coverage cond.c -fprofile-conditions && ./a.out && gcov -g -t a-cond.c
>>          -:    0:Source:cond.c
>>          -:    0:Graph:a-cond.gcno
>>          -:    0:Data:a-cond.gcda
>>          -:    0:Runs:1
>>          -:    1:int x;
>>          -:    2:
>>          1:    3:void foo (int a, int b)
>>          -:    4:{
>>          1:    5:    if (a || b)
>> conditions covered 1/4
>> condition  0 not covered (true)
>> condition  0 not covered (false) <-- this was executed if I'm correct
>> condition  1 not covered (false)
>>          1:    6:      x = 1;
>>          -:    7:    else
>>      #####:    8:      x = 2;
>>          1:    9:}
>>          -:   10:
>>          1:   11:int main()
>>          -:   12:{
>>          1:   13:  foo (0, 1);
>>          1:   14:  return 0;
>>          -:   15:}
>>
>> 4) I noticed various CFG patterns in tree-profile.cc which are handled. Can you please
>> explain how the algorithm works even without knowing the original paper?
>>
>> 5) I noticed the following ICE:
>>
>> $ gcc cond.c -fprofile-conditions -fprofile-generate
>> during IPA pass: profile
>> cond.c: In function ?foo?:
>> cond.c:15:1: internal compiler error: Segmentation fault
>>     15 | }
>>        | ^
>> 0xf4d64a crash_signal
>> 	/home/marxin/Programming/gcc/gcc/toplev.cc:322
>> 0x7ffff78b93cf ???
>> 	/usr/src/debug/glibc-2.35-2.1.x86_64/signal/../sysdeps/unix/sysv/linux/x86_64/libc_sigaction.c:0
>> 0x7ffff78f29ed __GI__IO_ftell
>> 	/usr/src/debug/glibc-2.35-2.1.x86_64/libio/ioftell.c:37
>> 0xa9dfda gcov_position
>> 	/home/marxin/Programming/gcc/gcc/gcov-io.cc:48
>> 0xa9dfda gcov_write_tag(unsigned int)
>> 	/home/marxin/Programming/gcc/gcc/gcov-io.cc:321
>> 0xe9734a branch_prob(bool)
>> 	/home/marxin/Programming/gcc/gcc/profile.cc:1542
>> 0x1032e08 tree_profiling
>> 	/home/marxin/Programming/gcc/gcc/tree-profile.cc:1620
>> 0x1032e08 execute
>> 	/home/marxin/Programming/gcc/gcc/tree-profile.cc:1726
>> Please submit a full bug report, with preprocessed source (by using -freport-bug).
>> Please include the complete backtrace with any bug report.
>> See <https://gcc.gnu.org/bugs/> for instructions.
>>
>> 6) Please follow the GNU coding style, most notable we replace 8 spaces with a tab.
>> And we break line before {, (, ... Remove noexcept specifiers for functions and I think
>> most of the newly added functions can be static. And each newly added function should
>> have a comment.
>>
>> 7) Please consider supporting of -profile-update=atomic where you'll need to utilize
>> an atomic builts (see how other instrumentation looks like with it).
>>
>> That's the very first round of the review. Hope it's helpful.
>>
>> Cheers,
>> Martin
>>
>>> This patch adds support in gcc+gcov for modified condition/decision
>>> coverage (MC/DC) with the -fprofile-conditions flag. MC/DC is a type of
>>> test/code coverage, and it is particularly important in the avation and
>>> automotive industries for safety-critical applications. In particular,
>>> it is required or recommended by:
>>>
>>>      * DO-178C for the most critical software (Level A) in avionics
>>>      * IEC 61508 for SIL 4
>>>      * ISO 26262-6 for ASIL D
>>>
>>>  From the SQLite webpage:
>>>
>>>      Two methods of measuring test coverage were described above:
>>>      "statement" and "branch" coverage. There are many other test
>>>      coverage metrics besides these two. Another popular metric is
>>>      "Modified Condition/Decision Coverage" or MC/DC. Wikipedia defines
>>>      MC/DC as follows:
>>>
>>>          * Each decision tries every possible outcome.
>>>          * Each condition in a decision takes on every possible outcome.
>>>          * Each entry and exit point is invoked.
>>>          * Each condition in a decision is shown to independently affect
>>>            the outcome of the decision.
>>>
>>>      In the C programming language where && and || are "short-circuit"
>>>      operators, MC/DC and branch coverage are very nearly the same thing.
>>>      The primary difference is in boolean vector tests. One can test for
>>>      any of several bits in bit-vector and still obtain 100% branch test
>>>      coverage even though the second element of MC/DC - the requirement
>>>      that each condition in a decision take on every possible outcome -
>>>      might not be satisfied.
>>>
>>>      https://sqlite.org/testing.html#mcdc
>>>
>>> Wahlen, Heimdahl, and De Silva "Efficient Test Coverage Measurement for
>>> MC/DC" describes an algorithm for adding instrumentation by carrying
>>> over information from the AST, but my algorithm does analysis on the
>>> control flow graph. This should make it work for any language gcc
>>> supports, although I have only tested it on constructs in C and C++, see
>>> testsuite/gcc.misc-tests and testsuite/g++.dg.
>>>
>>> Like Wahlen et al this implementation uses bitsets to store conditions,
>>> which gcov later interprets. This is very fast, but introduces an max
>>> limit for the number of terms in a single boolean expression. This limit
>>> is the number of bits in a gcov_unsigned_type (which is typedef'd to
>>> uint64_t), so for most practical purposes this would be acceptable.
>>> limitation can be relaxed with a more sophisticated way of storing and
>>> updating bitsets (for example length-encoding).
>>>
>>> In action it looks pretty similar to the branch coverage. The -g short
>>> opt carries no significance, but was chosen because it was an available
>>> option with the upper-case free too.
>>>
>>> gcov --conditions:
>>>
>>>          3:   17:void fn (int a, int b, int c, int d) {
>>>          3:   18:    if ((a && (b || c)) && d)
>>> conditions covered 5/8
>>> condition  1 not covered (false)
>>> condition  2 not covered (true)
>>> condition  2 not covered (false)
>>>          1:   19:        x = 1;
>>>          -:   20:    else
>>>          2:   21:        x = 2;
>>>          3:   22:}
>>>
>>> gcov --conditions --json-format:
>>>
>>>      "conditions": [
>>>          {
>>>              "not_covered_false": [
>>>                  1,
>>>                  2
>>>              ],
>>>              "count": 8,
>>>              "covered": 5,
>>>              "not_covered_true": [
>>>                  2
>>>              ]
>>>          }
>>>      ],
>>>
>>> C++ destructors will add extra conditionals that are not explicitly
>>> present in source code. These are present in the CFG, which means the
>>> condition coverage will show them.
>>>
>>> gcov --conditions
>>>
>>>          -:    5:struct A {
>>>          1:    6:    explicit A (int x) : v (x) {}
>>>          1:    7:    operator bool () const { return bool(v); }
>>>          1:    8:    ~A() {}
>>>          -:    9:
>>>          -:   10:    int v;
>>>          -:   11:};
>>>          -:   12:
>>>          2:   13:void fn (int a, int b) {
>>>         2*:   14:    x = a && A(b);
>>> conditions covered 2/4
>>> condition  0 not covered (true)
>>> condition  1 not covered (true)
>>> conditions covered 2/2
>>>
>>> They are also reported by the branch coverage.
>>>
>>> gcov -bc
>>>
>>>         2*:   14:    x = a && A(b);
>>> branch  0 taken 1 (fallthrough)
>>> branch  1 taken 1
>>> call    2 returned 1
>>> call    3 returned 1
>>> branch  4 taken 0 (fallthrough)
>>> branch  5 taken 1
>>> branch  6 taken 1 (fallthrough)
>>> branch  7 taken 1
>>>
>>> The algorithm struggles in a particular case where gcc would generate
>>> identical CFGs for different expressions:
>>>
>>> and.c:
>>>
>>>      if (a && b && c)
>>>          x = 1;
>>>
>>> ifs.c:
>>>
>>>      if (a)
>>>          if (b)
>>>              if (c)
>>>                  x = 1;
>>>
>>>      if (a && b && c)
>>>          x = 1;
>>>
>>> and.c.gcov:
>>>
>>>              #####:    2:    if (a && b && c)
>>> conditions covered 2/2
>>> conditions covered 2/2
>>> conditions covered 2/2
>>>      #####:    3:        x = 1;
>>>
>>> ifs.c.gcov:
>>>      #####:    2:    if (a)
>>> conditions covered 2/2
>>>      #####:    3:   2    if (b)
>>> conditions covered 2/2
>>>      #####:    4:   2        if (c)
>>> conditions covered 2/2
>>>      #####:    5:                x = 1;
>>>
>>> These programs are semantically equivalent, and are interpreted as 3
>>> separate conditional expressions. It does not matter w.r.t. coverage,
>>> but is not ideal. Adding an else to and.c fixes the issue:
>>>
>>>      #####:    2:    if (a && b && c)
>>> conditions covered 6/6
>>>      #####:    3:        x = 1;
>>>      #####:    4:    else x = x;
>>>
>>> In the (a && b && c) case the else block must be shared between all
>>> three conditionals, and for if (a) if (b) if (c) there would be three
>>> *separate* else blocks.
>>>
>>> Since the algorithm works on CFGs, it cannot detect conditionals (from
>>> ternaries) that don't get an entry in the cfg. For example,
>>> int x = a ? 0 : 1 in gimple becomes _x = (_a == 0). From source you
>>> would expect coverage, but it gets neither branch nor condition
>>> coverage. For completeness, it could be achieved by scanning all gimple
>>> statements for comparisons, and insert an extra instruction for
>>> recording the outcome.
>>>
>>> The test suite consists of all different CFG kinds and control flow
>>> structures I could find, but there is certainly room for many more.
>>>
>>> Alternative email: Jørgen Kvalsvik <j@lambda.is>
>>>
>>> --
>>>
>>> Some final remarks (not a part of the commit message), this is written in a C++ style that I felt meshed well with what was already there, but if the maintainers would prefer a different approach then I'm happy to to make adjustments. I plan to write a more thorough description of the algorithm, as well as adding the same feature to clang as it is very useful for us at Woven Planet.
>>>
>>> I tried to pick flag names and options that were comfortable and descriptive, but if you have better names or different ideas for flags then I am happy to change them. The --coverage flag was not changed to imply -fprofile-conditions, but it is possibly something to consider.
>>>
>>> gcc/ChangeLog:
>>>
>>> 	* common.opt: Add new options -fprofile-conditions and
>>> 	-Wcoverage-too-many-conditions.
>>> 	* doc/gcov.texi: Add --conditions documentation.
>>> 	* doc/invoke.texi: Add -fprofile-conditions documentation.
>>> 	* gcov-counter.def (GCOV_COUNTER_CONDS): New.
>>> 	* gcov-io.h (GCOV_TAG_CONDS): New.
>>> 	(GCOV_TAG_CONDS_LENGTH): Likewise.
>>> 	(GCOV_TAG_CONDS_NUM): Likewise.
>>> 	* gcov.cc (class condition_info): New.
>>> 	(condition_info::condition_info): New.
>>> 	(condition_info::popcount): New.
>>> 	(struct coverage_info): New.
>>> 	(add_condition_counts): New.
>>> 	(output_conditions): New.
>>> 	(print_usage): Add -g, --conditions.
>>> 	(process_args): Likewise.
>>> 	(output_intermediate_json_line): Output conditions.
>>> 	(read_graph_file): Read conditions counters.
>>> 	(read_count_file): Read conditions counters.
>>> 	(file_summary): Print conditions.
>>> 	(accumulate_line_info): Accumulate conditions.
>>> 	(output_line_details): Print conditions.
>>> 	* profile.cc (find_conditions): New declaration.
>>> 	(instrument_decisions): New declaration.
>>> 	(branch_prob): Call find_conditions, instrument_decisions.
>>> 	(init_branch_prob): Add total_num_conds.
>>> 	(end_branch_prob): Likewise.
>>> 	* tree-profile.cc (INCLUDE_ALGORITHM): Define.
>>> 	(struct conds_ctx): New.
>>> 	(CONDITIONS_MAX_TERMS): New.
>>> 	(index_of): New.
>>> 	(index_lt): New.
>>> 	(single): New.
>>> 	(single_edge): New.
>>> 	(contract_edge): New.
>>> 	(contract_edge_up): New.
>>> 	(is_conditional_p): New.
>>> 	(collect_neighbors): New.
>>> 	(find_first_conditional): New.
>>> 	(emit_bitwise_op): New.
>>> 	(collect_conditions): New.
>>> 	(find_conditions): New.
>>> 	(instrument_decisions): New.
>>>
>>> gcc/testsuite/ChangeLog:
>>>
>>> 	* lib/gcov.exp:
>>> 	* g++.dg/gcov/gcov-18.C: New test.
>>> 	* gcc.misc-tests/gcov-19.c: New test.
>>> ---
>>>   gcc/common.opt                         |   8 +
>>>   gcc/doc/gcov.texi                      |  36 ++
>>>   gcc/doc/invoke.texi                    |  19 +
>>>   gcc/gcov-counter.def                   |   3 +
>>>   gcc/gcov-io.h                          |   3 +
>>>   gcc/gcov.cc                            | 177 +++++-
>>>   gcc/profile.cc                         |  51 +-
>>>   gcc/testsuite/g++.dg/gcov/gcov-18.C    | 209 ++++++
>>>   gcc/testsuite/gcc.misc-tests/gcov-19.c | 726 +++++++++++++++++++++
>>>   gcc/testsuite/lib/gcov.exp             | 187 +++++-
>>>   gcc/tree-profile.cc                    | 838 +++++++++++++++++++++++++
>>>   11 files changed, 2248 insertions(+), 9 deletions(-)
>>>   create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-18.C
>>>   create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-19.c
> 

[-- Attachment #2: 0001-Add-condition-coverage-profiling.patch --]
[-- Type: text/plain, Size: 91734 bytes --]

From 53d404a6e836875d8243ea71c2aeb92115224f98 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B8rgen=20Kvalsvik?=
 <jorgen.kvalsvik@woven-planet.global>
Date: Mon, 31 Jan 2022 12:49:37 +0100
Subject: [PATCH] Add condition coverage profiling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This patch adds support in gcc+gcov for modified condition/decision
coverage (MC/DC) with the -fprofile-conditions flag. MC/DC is a type of
test/code coverage, and it is particularly important in the avation and
automotive industries for safety-critical applications. In particular,
it is required or recommended by:

    * DO-178C for the most critical software (Level A) in avionics
    * IEC 61508 for SIL 4
    * ISO 26262-6 for ASIL D

From the SQLite webpage:

    Two methods of measuring test coverage were described above:
    "statement" and "branch" coverage. There are many other test
    coverage metrics besides these two. Another popular metric is
    "Modified Condition/Decision Coverage" or MC/DC. Wikipedia defines
    MC/DC as follows:

        * Each decision tries every possible outcome.
        * Each condition in a decision takes on every possible outcome.
        * Each entry and exit point is invoked.
        * Each condition in a decision is shown to independently affect
          the outcome of the decision.

    In the C programming language where && and || are "short-circuit"
    operators, MC/DC and branch coverage are very nearly the same thing.
    The primary difference is in boolean vector tests. One can test for
    any of several bits in bit-vector and still obtain 100% branch test
    coverage even though the second element of MC/DC - the requirement
    that each condition in a decision take on every possible outcome -
    might not be satisfied.

    https://sqlite.org/testing.html#mcdc

Wahlen, Heimdahl, and De Silva "Efficient Test Coverage Measurement for
MC/DC" describes an algorithm for adding instrumentation by carrying
over information from the AST, but my algorithm does analysis on the
control flow graph. This should make it work for any language gcc
supports, although I have only tested it on constructs in C and C++, see
testsuite/gcc.misc-tests and testsuite/g++.dg.

Like Wahlen et al this implementation uses bitsets to store conditions,
which gcov later interprets. This is very fast, but introduces an max
limit for the number of terms in a single boolean expression. This limit
is the number of bits in a gcov_unsigned_type (which is typedef'd to
uint64_t), so for most practical purposes this would be acceptable.
limitation can be relaxed with a more sophisticated way of storing and
updating bitsets (for example length-encoding).

In action it looks pretty similar to the branch coverage. The -g short
opt carries no significance, but was chosen because it was an available
option with the upper-case free too.

gcov --conditions:

        3:   17:void fn (int a, int b, int c, int d) {
        3:   18:    if ((a && (b || c)) && d)
conditions covered 5/8
condition  1 not covered (false)
condition  2 not covered (true)
condition  2 not covered (false)
        1:   19:        x = 1;
        -:   20:    else
        2:   21:        x = 2;
        3:   22:}

gcov --conditions --json-format:

    "conditions": [
        {
            "not_covered_false": [
                1,
                2
            ],
            "count": 8,
            "covered": 5,
            "not_covered_true": [
                2
            ]
        }
    ],

C++ destructors will add extra conditionals that are not explicitly
present in source code. These are present in the CFG, which means the
condition coverage will show them.

gcov --conditions

        -:    5:struct A {
        1:    6:    explicit A (int x) : v (x) {}
        1:    7:    operator bool () const { return bool(v); }
        1:    8:    ~A() {}
        -:    9:
        -:   10:    int v;
        -:   11:};
        -:   12:
        2:   13:void fn (int a, int b) {
       2*:   14:    x = a && A(b);
conditions covered 2/4
condition  0 not covered (true)
condition  1 not covered (true)
conditions covered 2/2

They are also reported by the branch coverage.

gcov -bc

       2*:   14:    x = a && A(b);
branch  0 taken 1 (fallthrough)
branch  1 taken 1
call    2 returned 1
call    3 returned 1
branch  4 taken 0 (fallthrough)
branch  5 taken 1
branch  6 taken 1 (fallthrough)
branch  7 taken 1

The algorithm struggles in a particular case where gcc would generate
identical CFGs for different expressions:

and.c:

    if (a && b && c)
        x = 1;

ifs.c:

    if (a)
        if (b)
            if (c)
                x = 1;

    if (a && b && c)
        x = 1;

and.c.gcov:

            #####:    2:    if (a && b && c)
conditions covered 2/2
conditions covered 2/2
conditions covered 2/2
    #####:    3:        x = 1;

ifs.c.gcov:
    #####:    2:    if (a)
conditions covered 2/2
    #####:    3:   2    if (b)
conditions covered 2/2
    #####:    4:   2        if (c)
conditions covered 2/2
    #####:    5:                x = 1;

These programs are semantically equivalent, and are interpreted as 3
separate conditional expressions. It does not matter w.r.t. coverage,
but is not ideal. Adding an else to and.c fixes the issue:

    #####:    2:    if (a && b && c)
conditions covered 6/6
    #####:    3:        x = 1;
    #####:    4:    else x = x;

In the (a && b && c) case the else block must be shared between all
three conditionals, and for if (a) if (b) if (c) there would be three
*separate* else blocks.

Since the algorithm works on CFGs, it cannot detect conditionals (from
ternaries) that don't get an entry in the cfg. For example,
int x = a ? 0 : 1 in gimple becomes _x = (_a == 0). From source you
would expect coverage, but it gets neither branch nor condition
coverage. For completeness, it could be achieved by scanning all gimple
statements for comparisons, and insert an extra instruction for
recording the outcome.

The test suite consists of all different CFG kinds and control flow
structures I could find, but there is certainly room for many more.

Alternative email: Jørgen Kvalsvik <j@lambda.is>

gcc/ChangeLog:

	* builtins.cc (expand_builtin_fork_or_exec): Check
	profile_condition_flag.
	* common.opt: Add new options -fprofile-conditions and
	-Wcoverage-too-many-conditions.
	* doc/gcov.texi: Add --conditions documentation.
	* doc/invoke.texi: Add -fprofile-conditions documentation.
	* gcc.cc: Link gcov on -fprofile-conditions.
	* gcov-counter.def (GCOV_COUNTER_CONDS): New.
	* gcov-io.h (GCOV_TAG_CONDS): New.
	(GCOV_TAG_CONDS_LENGTH): Likewise.
	(GCOV_TAG_CONDS_NUM): Likewise.
	* gcov.cc (class condition_info): New.
	(condition_info::condition_info): New.
	(condition_info::popcount): New.
	(struct coverage_info): New.
	(add_condition_counts): New.
	(output_conditions): New.
	(print_usage): Add -g, --conditions.
	(process_args): Likewise.
	(output_intermediate_json_line): Output conditions.
	(read_graph_file): Read conditions counters.
	(read_count_file): Read conditions counters.
	(file_summary): Print conditions.
	(accumulate_line_info): Accumulate conditions.
	(output_line_details): Print conditions.
	* ipa-inline.cc (can_early_inline_edge_p): Check
	profile_condition_flag.
	* ipa-split.cc (pass_split_functions::gate): Likewise.
	* passes.cc (finish_optimization_passes): Likewise.
	* profile.cc (find_conditions): New declaration.
	(instrument_decisions): New declaration.
	(branch_prob): Call find_conditions, instrument_decisions.
	(init_branch_prob): Add total_num_conds.
	(end_branch_prob): Likewise.
	* tree-profile.cc (INCLUDE_ALGORITHM): Define.
	(struct conds_ctx): New.
	(CONDITIONS_MAX_TERMS): New.
	(index_of): New.
	(index_lt): New.
	(single): New.
	(single_edge): New.
	(contract_edge): New.
	(contract_edge_up): New.
	(is_conditional_p): New.
	(collect_neighbors): New.
	(find_first_conditional): New.
	(emit_bitwise_op): New.
	(collect_conditions): New.
	(find_conditions): New.
	(instrument_decisions): New.
	(pass_ipa_tree_profile::gate): Check profile_condition_flag.

gcc/testsuite/ChangeLog:

	* lib/gcov.exp: Add condition coverage test function.
	* g++.dg/gcov/gcov-18.C: New test.
	* gcc.misc-tests/gcov-19.c: New test.
	* gcc.misc-tests/gcov-20.c: New test.
---
 gcc/builtins.cc                        |   2 +-
 gcc/common.opt                         |   8 +
 gcc/doc/gcov.texi                      |  36 +
 gcc/doc/invoke.texi                    |  19 +
 gcc/gcc.cc                             |   2 +-
 gcc/gcov-counter.def                   |   3 +
 gcc/gcov-io.h                          |   3 +
 gcc/gcov.cc                            | 193 +++++-
 gcc/ipa-inline.cc                      |   2 +-
 gcc/ipa-split.cc                       |   3 +-
 gcc/passes.cc                          |   3 +-
 gcc/profile.cc                         |  72 +-
 gcc/testsuite/g++.dg/gcov/gcov-18.C    | 227 +++++++
 gcc/testsuite/gcc.misc-tests/gcov-19.c | 727 ++++++++++++++++++++
 gcc/testsuite/gcc.misc-tests/gcov-20.c |  21 +
 gcc/testsuite/lib/gcov.exp             | 187 +++++-
 gcc/tree-profile.cc                    | 883 ++++++++++++++++++++++++-
 17 files changed, 2366 insertions(+), 25 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-18.C
 create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-19.c
 create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-20.c

diff --git a/gcc/builtins.cc b/gcc/builtins.cc
index 4c6c2939053..669936ef7b6 100644
--- a/gcc/builtins.cc
+++ b/gcc/builtins.cc
@@ -5569,7 +5569,7 @@ expand_builtin_fork_or_exec (tree fn, tree exp, rtx target, int ignore)
   tree call;
 
   /* If we are not profiling, just call the function.  */
-  if (!profile_arc_flag)
+  if (!profile_arc_flag && !profile_condition_flag)
     return NULL_RTX;
 
   /* Otherwise call the wrapper.  This should be equivalent for the rest of
diff --git a/gcc/common.opt b/gcc/common.opt
index 8b6513de47c..2afe4384d9d 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -861,6 +861,10 @@ Wcoverage-invalid-line-number
 Common Var(warn_coverage_invalid_linenum) Init(1) Warning
 Warn in case a function ends earlier than it begins due to an invalid linenum macros.
 
+Wcoverage-too-many-conditions
+Common Var(warn_too_many_conditions) Init(1) Warning
+Warn when a conditional has too many terms and coverage gives up.
+
 Wmissing-profile
 Common Var(warn_missing_profile) Init(1) Warning
 Warn in case profiles in -fprofile-use do not exist.
@@ -2290,6 +2294,10 @@ fprofile-arcs
 Common Var(profile_arc_flag)
 Insert arc-based program profiling code.
 
+fprofile-conditions
+Common Var(profile_condition_flag)
+Insert condition coverage profiling code.
+
 fprofile-dir=
 Common Joined RejectNegative Var(profile_data_prefix)
 Set the top-level directory for storing the profile data.
diff --git a/gcc/doc/gcov.texi b/gcc/doc/gcov.texi
index fc39da0f02d..a85b7f09769 100644
--- a/gcc/doc/gcov.texi
+++ b/gcc/doc/gcov.texi
@@ -122,6 +122,7 @@ gcov [@option{-v}|@option{--version}] [@option{-h}|@option{--help}]
      [@option{-a}|@option{--all-blocks}]
      [@option{-b}|@option{--branch-probabilities}]
      [@option{-c}|@option{--branch-counts}]
+     [@option{-g}|@option{--conditions}]
      [@option{-d}|@option{--display-progress}]
      [@option{-f}|@option{--function-summaries}]
      [@option{-j}|@option{--json-format}]
@@ -167,6 +168,13 @@ be shown, unless the @option{-u} option is given.
 Write branch frequencies as the number of branches taken, rather than
 the percentage of branches taken.
 
+@item -g
+@itemx --conditions
+Write condition coverage to the output file, and write condition summary info
+to the standard output.  This option allows you to see if the conditions in
+your program at least once had an independent effect on the outcome of the
+boolean expression (modified condition/decision coverage).
+
 @item -d
 @itemx --display-progress
 Display the progress on the standard output.
@@ -291,6 +299,7 @@ Each @var{line} has the following form:
 @{
   "branches": ["$branch"],
   "count": 2,
+  "conditions": ["$condition"],
   "line_number": 15,
   "unexecuted_block": false,
   "function_name": "foo",
@@ -339,6 +348,33 @@ Fields of the @var{branch} element have following semantics:
 @var{throw}: true when the branch is an exceptional branch
 @end itemize
 
+Each @var{condition} element have the following semantics:
+
+@smallexample
+@{
+  "count": 4,
+  "covered": 2,
+  "not_covered_false": [],
+  "not_covered_true": [0, 1],
+@}
+@end smallexample
+
+Fields of the @var{condition} element have following semantics:
+
+@itemize @bullet
+@item
+@var{count}: number of conditions in this expression
+
+@item
+@var{covered}: number of covered conditions in this expression
+
+@item
+@var{not_covered_true}: terms, by index, not seen as true in this expression
+
+@item
+@var{not_covered_false}: terms, by index, not seen as false in this expression
+@end itemize
+
 @item -H
 @itemx --human-readable
 Write counts in human readable format (like 24.6k).
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index e3f2e82cde5..0e24e43ca42 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -594,6 +594,7 @@ Objective-C and Objective-C++ Dialects}.
 @item Program Instrumentation Options
 @xref{Instrumentation Options,,Program Instrumentation Options}.
 @gccoptlist{-p  -pg  -fprofile-arcs  --coverage  -ftest-coverage @gol
+-fprofile-conditions @gol
 -fprofile-abs-path @gol
 -fprofile-dir=@var{path}  -fprofile-generate  -fprofile-generate=@var{path} @gol
 -fprofile-info-section  -fprofile-info-section=@var{name} @gol
@@ -6020,6 +6021,13 @@ error.  @option{-Wno-coverage-invalid-line-number} can be used to disable the
 warning or @option{-Wno-error=coverage-invalid-line-number} can be used to
 disable the error.
 
+@item -Wno-coverage-too-many-conditions
+@opindex Wno-coverage-too-many-conditions
+@opindex Wcoverage-too-many-conditions
+Warn in case a condition have too many terms and GCC gives up coverage.
+Coverage is given up when there are more terms in the conditional than there
+are bits in a @code{gcov_type_unsigned}.  This warning is enabled by default.
+
 @item -Wno-cpp @r{(C, Objective-C, C++, Objective-C++ and Fortran only)}
 @opindex Wno-cpp
 @opindex Wcpp
@@ -15271,6 +15279,13 @@ E.g. @code{gcc a.c b.c -o binary} would generate @file{binary-a.gcda} and
 
 @xref{Cross-profiling}.
 
+@item -fprofile-conditions
+@opindex fprofile-conditions
+Add code so that program conditions are instrumented.  During execution the
+program records what terms in a conditional contributes to a decision.  The
+data may be used to verify that all terms in a booleans are tested and have an
+effect on the outcome of a condition.
+
 @cindex @command{gcov}
 @item --coverage
 @opindex coverage
@@ -15331,6 +15346,10 @@ executed.  When an arc is the only exit or only entrance to a block, the
 instrumentation code can be added to the block; otherwise, a new basic
 block must be created to hold the instrumentation code.
 
+With @option{-fprofile-conditions}, for each conditional in your program GCC
+creates a bitset and records the boolean values that have an independent effect
+on the outcome of that expression.
+
 @need 2000
 @item -ftest-coverage
 @opindex ftest-coverage
diff --git a/gcc/gcc.cc b/gcc/gcc.cc
index a4d863ca457..5a25a47b7d0 100644
--- a/gcc/gcc.cc
+++ b/gcc/gcc.cc
@@ -1165,7 +1165,7 @@ proper position among the other output files.  */
 	%:include(libgomp.spec)%(link_gomp)}\
     %{fgnu-tm:%:include(libitm.spec)%(link_itm)}\
     %(mflib) " STACK_SPLIT_SPEC "\
-    %{fprofile-arcs|fprofile-generate*|coverage:-lgcov} " SANITIZER_SPEC " \
+    %{fprofile-arcs|fprofile-conditions|fprofile-generate*|coverage:-lgcov} " SANITIZER_SPEC " \
     %{!nostdlib:%{!r:%{!nodefaultlibs:%(link_ssp) %(link_gcc_c_sequence)}}}\
     %{!nostdlib:%{!r:%{!nostartfiles:%E}}} %{T*}  \n%(post_link) }}}}}}"
 #endif
diff --git a/gcc/gcov-counter.def b/gcc/gcov-counter.def
index 6d2182bd3db..96563a59a45 100644
--- a/gcc/gcov-counter.def
+++ b/gcc/gcov-counter.def
@@ -49,3 +49,6 @@ DEF_GCOV_COUNTER(GCOV_COUNTER_IOR, "ior", _ior)
 
 /* Time profile collecting first run of a function */
 DEF_GCOV_COUNTER(GCOV_TIME_PROFILER, "time_profiler", _time_profile)
+
+/* Conditions.  The counter is interpreted as a bit-set.  */
+DEF_GCOV_COUNTER(GCOV_COUNTER_CONDS, "conditions", _ior)
diff --git a/gcc/gcov-io.h b/gcc/gcov-io.h
index 99ce7dbccc8..c84f58b99ac 100644
--- a/gcc/gcov-io.h
+++ b/gcc/gcov-io.h
@@ -253,6 +253,9 @@ typedef uint64_t gcov_type_unsigned;
 #define GCOV_TAG_ARCS		 ((gcov_unsigned_t)0x01430000)
 #define GCOV_TAG_ARCS_LENGTH(NUM)  (1 + (NUM) * 2 * GCOV_WORD_SIZE)
 #define GCOV_TAG_ARCS_NUM(LENGTH)  (((LENGTH / GCOV_WORD_SIZE) - 1) / 2)
+#define GCOV_TAG_CONDS             ((gcov_unsigned_t)0x01470000)
+#define GCOV_TAG_CONDS_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE)
+#define GCOV_TAG_CONDS_NUM(LENGTH) (((LENGTH) / GCOV_WORD_SIZE) / 2)
 #define GCOV_TAG_LINES		 ((gcov_unsigned_t)0x01450000)
 #define GCOV_TAG_COUNTER_BASE 	 ((gcov_unsigned_t)0x01a10000)
 #define GCOV_TAG_COUNTER_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE)
diff --git a/gcc/gcov.cc b/gcc/gcov.cc
index 27be5ff0911..4db40c18ae7 100644
--- a/gcc/gcov.cc
+++ b/gcc/gcov.cc
@@ -79,6 +79,7 @@ using namespace std;
 class function_info;
 class block_info;
 class source_info;
+class condition_info;
 
 /* Describes an arc between two basic blocks.  */
 
@@ -132,6 +133,28 @@ public:
   vector<unsigned> lines;
 };
 
+class condition_info
+{
+public:
+  condition_info ();
+
+  int popcount () const;
+
+  gcov_type_unsigned truev;
+  gcov_type_unsigned falsev;
+
+  unsigned n_terms;
+};
+
+condition_info::condition_info (): truev (0), falsev (0), n_terms (0)
+{
+}
+
+int condition_info::popcount () const
+{
+    return __builtin_popcountll (truev) + __builtin_popcountll (falsev);
+}
+
 /* Describes a basic block. Contains lists of arcs to successor and
    predecessor blocks.  */
 
@@ -165,6 +188,8 @@ public:
   /* Block is a landing pad for longjmp or throw.  */
   unsigned is_nonlocal_return : 1;
 
+  condition_info conditions;
+
   vector<block_location_info> locations;
 
   struct
@@ -275,6 +300,8 @@ public:
   vector<block_info> blocks;
   unsigned blocks_executed;
 
+  vector<condition_info*> conditions;
+
   /* Raw arc coverage counts.  */
   vector<gcov_type> counts;
 
@@ -351,6 +378,9 @@ struct coverage_info
   int branches_executed;
   int branches_taken;
 
+  int conditions;
+  int conditions_covered;
+
   int calls;
   int calls_executed;
 
@@ -550,6 +580,10 @@ static int multiple_files = 0;
 
 static int flag_branches = 0;
 
+/* Output conditions (modified condition/decision coverage) */
+
+static int flag_conditions = 0;
+
 /* Show unconditional branches too.  */
 static int flag_unconditional = 0;
 
@@ -656,6 +690,7 @@ static int read_count_file (void);
 static void solve_flow_graph (function_info *);
 static void find_exception_blocks (function_info *);
 static void add_branch_counts (coverage_info *, const arc_info *);
+static void add_condition_counts (coverage_info *, const block_info *);
 static void add_line_counts (coverage_info *, function_info *);
 static void executed_summary (unsigned, unsigned);
 static void function_summary (const coverage_info *);
@@ -664,6 +699,7 @@ static const char *format_gcov (gcov_type, gcov_type, int);
 static void accumulate_line_counts (source_info *);
 static void output_gcov_file (const char *, source_info *);
 static int output_branch_count (FILE *, int, const arc_info *);
+static void output_conditions (FILE *, const block_info *);
 static void output_lines (FILE *, const source_info *);
 static string make_gcov_file_name (const char *, const char *);
 static char *mangle_name (const char *);
@@ -928,6 +964,7 @@ print_usage (int error_p)
   fnotice (file, "  -b, --branch-probabilities      Include branch probabilities in output\n");
   fnotice (file, "  -c, --branch-counts             Output counts of branches taken\n\
                                     rather than percentages\n");
+  fnotice (file, "  -g, --conditions                Include condition coverage in output (MC/DC)\n");
   fnotice (file, "  -d, --display-progress          Display progress information\n");
   fnotice (file, "  -D, --debug			    Display debugging dumps\n");
   fnotice (file, "  -f, --function-summaries        Output summaries for each function\n");
@@ -980,6 +1017,7 @@ static const struct option options[] =
   { "all-blocks",           no_argument,       NULL, 'a' },
   { "branch-probabilities", no_argument,       NULL, 'b' },
   { "branch-counts",        no_argument,       NULL, 'c' },
+  { "conditions",           no_argument,       NULL, 'g' },
   { "json-format",	    no_argument,       NULL, 'j' },
   { "human-readable",	    no_argument,       NULL, 'H' },
   { "no-output",            no_argument,       NULL, 'n' },
@@ -1008,7 +1046,7 @@ process_args (int argc, char **argv)
 {
   int opt;
 
-  const char *opts = "abcdDfhHijklmno:pqrs:tuvwx";
+  const char *opts = "abcdDfghHijklmno:pqrs:tuvwx";
   while ((opt = getopt_long (argc, argv, opts, options, NULL)) != -1)
     {
       switch (opt)
@@ -1025,6 +1063,9 @@ process_args (int argc, char **argv)
 	case 'f':
 	  flag_function_summary = 1;
 	  break;
+	case 'g':
+	  flag_conditions = 1;
+	  break;
 	case 'h':
 	  print_usage (false);
 	  /* print_usage will exit.  */
@@ -1132,6 +1173,45 @@ output_intermediate_json_line (json::array *object,
 	  }
       }
 
+  json::array *conditions = new json::array ();
+  lineo->set ("conditions", conditions);
+  if (flag_conditions)
+  {
+    vector<block_info *>::const_iterator it;
+    for (it = line->blocks.begin (); it != line->blocks.end (); it++)
+      {
+	const condition_info& info = (*it)->conditions;
+	if (info.n_terms == 0)
+	    continue;
+
+	const int count = 2 * info.n_terms;
+	const int covered = info.popcount ();
+
+	json::object *cond = new json::object ();
+	cond->set ("count",   new json::integer_number (count));
+	cond->set ("covered", new json::integer_number (covered));
+
+	json::array *mtrue = new json::array ();
+	json::array *mfalse = new json::array ();
+	cond->set ("not_covered_true",  mtrue);
+	cond->set ("not_covered_false", mfalse);
+
+	if (count != covered)
+	  {
+	    for (unsigned i = 0; i < info.n_terms; i++)
+	      {
+		gcov_type_unsigned index = 1;
+		index <<= i;
+		if (!(index & info.truev))
+		    mtrue->append (new json::integer_number (i));
+		if (!(index & info.falsev))
+		    mfalse->append (new json::integer_number (i));
+	      }
+	  }
+	conditions->append (cond);
+      }
+  }
+
   object->append (lineo);
 }
 
@@ -1956,6 +2036,28 @@ read_graph_file (void)
 		  }
 	    }
 	}
+      else if (fn && tag == GCOV_TAG_CONDS)
+	{
+	  unsigned num_dests = GCOV_TAG_CONDS_NUM (length);
+
+	  if (!fn->conditions.empty ())
+	    fnotice (stderr, "%s:already seen conditions for '%s'\n",
+		     bbg_file_name, fn->get_name ());
+	  else
+	    fn->conditions.resize (num_dests);
+
+	  for (unsigned i = 0; i < num_dests; ++i)
+	    {
+	      unsigned idx = gcov_read_unsigned ();
+
+	      if (idx >= fn->blocks.size ())
+		goto corrupt;
+
+	      condition_info *info = &fn->blocks[idx].conditions;
+	      info->n_terms = gcov_read_unsigned ();
+	      fn->conditions[i] = info;
+	    }
+	}
       else if (fn && tag == GCOV_TAG_LINES)
 	{
 	  unsigned blockno = gcov_read_unsigned ();
@@ -2086,11 +2188,26 @@ read_count_file (void)
 	      goto cleanup;
 	    }
 	}
-      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn)
+      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_CONDS) && fn)
 	{
+	  length = abs (read_length);
+	    if (length != GCOV_TAG_COUNTER_LENGTH (2 * fn->conditions.size ()))
+		goto mismatch;
+
+	    if (read_length > 0)
+	      {
+		for (ix = 0; ix != fn->conditions.size (); ix++)
+		{
+		    fn->conditions[ix]->truev  |= gcov_read_counter ();
+		    fn->conditions[ix]->falsev |= gcov_read_counter ();
+		}
+	      }
+	}
+      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn)
+      {
 	  length = abs (read_length);
 	  if (length != GCOV_TAG_COUNTER_LENGTH (fn->counts.size ()))
-	    goto mismatch;
+	      goto mismatch;
 
 	  if (read_length > 0)
 	    for (ix = 0; ix != fn->counts.size (); ix++)
@@ -2430,6 +2547,13 @@ add_branch_counts (coverage_info *coverage, const arc_info *arc)
     }
 }
 
+static void
+add_condition_counts (coverage_info *coverage, const block_info *block)
+{
+  coverage->conditions += 2 * block->conditions.n_terms;
+  coverage->conditions_covered += block->conditions.popcount ();
+}
+
 /* Format COUNT, if flag_human_readable_numbers is set, return it human
    readable format.  */
 
@@ -2533,7 +2657,14 @@ file_summary (const coverage_info *coverage)
 		 coverage->calls);
       else
 	fnotice (stdout, "No calls\n");
+
     }
+
+  if (flag_conditions)
+      fnotice (stdout, "Conditions covered:%s of %d\n",
+	       format_gcov (coverage->conditions_covered,
+			    coverage->conditions, 2),
+	       coverage->conditions);
 }
 
 /* Canonicalize the filename NAME by canonicalizing directory
@@ -2760,6 +2891,12 @@ static void accumulate_line_info (line_info *line, source_info *src,
 	 it != line->branches.end (); it++)
       add_branch_counts (&src->coverage, *it);
 
+  if (add_coverage)
+    for (vector<block_info *>::iterator it = line->blocks.begin ();
+	 it != line->blocks.end (); it++)
+      add_condition_counts (&src->coverage, *it);
+
+
   if (!line->blocks.empty ())
     {
       /* The user expects the line count to be the number of times
@@ -2861,6 +2998,31 @@ accumulate_line_counts (source_info *src)
       }
 }
 
+static void
+output_conditions (FILE *gcov_file, const block_info *binfo)
+{
+    const condition_info& info = binfo->conditions;
+    if (info.n_terms == 0)
+	return;
+
+    const int expected = 2 * info.n_terms;
+    const int got = info.popcount ();
+
+    fnotice (gcov_file, "conditions covered %d/%d\n", got, expected);
+    if (expected == got)
+	return;
+
+    for (unsigned i = 0; i < info.n_terms; i++)
+    {
+	gcov_type_unsigned index = 1;
+	index <<= i;
+	if (!(index & info.truev))
+	    fnotice (gcov_file, "condition %2u not covered (true)\n", i);
+	if (!(index & info.falsev))
+	    fnotice (gcov_file, "condition %2u not covered (false)\n", i);
+    }
+}
+
 /* Output information about ARC number IX.  Returns nonzero if
    anything is output.  */
 
@@ -3071,16 +3233,29 @@ output_line_details (FILE *f, const line_info *line, unsigned line_num)
 	  if (flag_branches)
 	    for (arc = (*it)->succ; arc; arc = arc->succ_next)
 	      jx += output_branch_count (f, jx, arc);
+
+	  if (flag_conditions)
+	      output_conditions (f, *it);
 	}
     }
-  else if (flag_branches)
+  else
     {
-      int ix;
+      if (flag_branches)
+	{
+	  int ix;
+
+	  ix = 0;
+	  for (vector<arc_info *>::const_iterator it = line->branches.begin ();
+		  it != line->branches.end (); it++)
+	      ix += output_branch_count (f, ix, (*it));
+	}
 
-      ix = 0;
-      for (vector<arc_info *>::const_iterator it = line->branches.begin ();
-	   it != line->branches.end (); it++)
-	ix += output_branch_count (f, ix, (*it));
+      if (flag_conditions)
+	{
+	  for (vector<block_info *>::const_iterator it = line->blocks.begin ();
+	       it != line->blocks.end (); it++)
+	      output_conditions (f, *it);
+	}
     }
 }
 
diff --git a/gcc/ipa-inline.cc b/gcc/ipa-inline.cc
index f8bb072c422..b826dcd580c 100644
--- a/gcc/ipa-inline.cc
+++ b/gcc/ipa-inline.cc
@@ -646,7 +646,7 @@ can_early_inline_edge_p (struct cgraph_edge *e)
 			 "  edge not inlinable: not in SSA form\n");
       return false;
     }
-  else if (profile_arc_flag
+  else if ((profile_arc_flag || profile_condition_flag)
 	   && ((lookup_attribute ("no_profile_instrument_function",
 				 DECL_ATTRIBUTES (caller->decl)) == NULL_TREE)
 	       != (lookup_attribute ("no_profile_instrument_function",
diff --git a/gcc/ipa-split.cc b/gcc/ipa-split.cc
index 60021bad13c..9890518e11f 100644
--- a/gcc/ipa-split.cc
+++ b/gcc/ipa-split.cc
@@ -1929,7 +1929,8 @@ pass_split_functions::gate (function *)
   /* When doing profile feedback, we want to execute the pass after profiling
      is read.  So disable one in early optimization.  */
   return (flag_partial_inlining
-	  && !profile_arc_flag && !flag_branch_probabilities);
+      && !profile_arc_flag && !flag_branch_probabilities
+      && !profile_condition_flag);
 }
 
 } // anon namespace
diff --git a/gcc/passes.cc b/gcc/passes.cc
index 36e5b4ac45f..ee099188844 100644
--- a/gcc/passes.cc
+++ b/gcc/passes.cc
@@ -352,7 +352,8 @@ finish_optimization_passes (void)
   gcc::dump_manager *dumps = m_ctxt->get_dumps ();
 
   timevar_push (TV_DUMP);
-  if (profile_arc_flag || flag_test_coverage || flag_branch_probabilities)
+  if (profile_arc_flag || profile_condition_flag || flag_test_coverage
+          || flag_branch_probabilities)
     {
       dumps->dump_start (pass_profile_1->static_pass_number, NULL);
       end_branch_prob ();
diff --git a/gcc/profile.cc b/gcc/profile.cc
index a67cce5b199..b51fabbb59b 100644
--- a/gcc/profile.cc
+++ b/gcc/profile.cc
@@ -69,6 +69,9 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "profile.h"
 
+int find_conditions (basic_block, basic_block, basic_block*, int*, int);
+int instrument_decisions (basic_block*, int, int);
+
 /* Map from BBs/edges to gcov counters.  */
 vec<gcov_type> bb_gcov_counts;
 hash_map<edge,gcov_type> *edge_gcov_counts;
@@ -100,6 +103,7 @@ static int total_num_passes;
 static int total_num_times_called;
 static int total_hist_br_prob[20];
 static int total_num_branches;
+static int total_num_conds;
 
 /* Forward declarations.  */
 static void find_spanning_tree (struct edge_list *);
@@ -1155,6 +1159,12 @@ read_thunk_profile (struct cgraph_node *node)
    the flow graph that are needed to reconstruct the dynamic behavior of the
    flow graph.  This data is written to the gcno file for gcov.
 
+   When FLAG_PROFILE_CONDITIONS is nonzero, this functions instruments the
+   edges in the control flow graph to track what conditions are evaluated to in
+   order to determine what conditions are covered and have an independent
+   effect on the outcome (modified condition/decision coverage).  This data is
+   written to the gcno file for gcov.
+
    When FLAG_BRANCH_PROBABILITIES is nonzero, this function reads auxiliary
    information from the gcda file containing edge count information from
    previous executions of the function being compiled.  In this case, the
@@ -1173,6 +1183,7 @@ branch_prob (bool thunk)
   struct edge_list *el;
   histogram_values values = histogram_values ();
   unsigned cfg_checksum, lineno_checksum;
+  bool output_to_file;
 
   total_num_times_called++;
 
@@ -1381,7 +1392,7 @@ branch_prob (bool thunk)
 
   /* Compute two different checksums. Note that we want to compute
      the checksum in only once place, since it depends on the shape
-     of the control flow which can change during 
+     of the control flow which can change during
      various transformations.  */
   if (thunk)
     {
@@ -1397,10 +1408,18 @@ branch_prob (bool thunk)
 
   /* Write the data from which gcov can reconstruct the basic block
      graph and function line numbers (the gcno file).  */
+  output_to_file = false;
   if (coverage_begin_function (lineno_checksum, cfg_checksum))
     {
       gcov_position_t offset;
 
+      /* The condition coverage needs a deeper analysis to identify expressions
+       * of conditions, which means it is not yet ready to write to the gcno
+       * file.  It will write its entries later, but needs to know if it do it
+       * in the first place, which is controlled by the return value of
+       * coverage_begin_function. */
+      output_to_file = true;
+
       /* Basic block flags */
       offset = gcov_write_tag (GCOV_TAG_BLOCKS);
       gcov_write_unsigned (n_basic_blocks_for_fn (cfun));
@@ -1512,29 +1531,67 @@ branch_prob (bool thunk)
 
   remove_fake_edges ();
 
+  if (profile_condition_flag || profile_arc_flag)
+      gimple_init_gcov_profiler ();
+
+  if (profile_condition_flag)
+    {
+      basic_block entry = ENTRY_BLOCK_PTR_FOR_FN (cfun)->next_bb;
+      basic_block exit  = EXIT_BLOCK_PTR_FOR_FN (cfun);
+
+      // find_conditions () expect memory up front, see that function for
+      // details
+      const int max_blocks = 5 * n_basic_blocks_for_fn (cfun);
+      auto_vec<basic_block> blocks (max_blocks);
+      blocks.quick_grow (max_blocks);
+
+      const int max_sizes = n_basic_blocks_for_fn (cfun) + 1;
+      auto_vec<int> sizes (max_sizes);
+      sizes.quick_grow (max_sizes);
+
+      int nconds = find_conditions
+	  (entry, exit, blocks.address (), sizes.address (), max_blocks);
+      total_num_conds += nconds;
+
+      if (nconds > 0
+	  && coverage_counter_alloc (GCOV_COUNTER_CONDS, 2 * nconds)
+	  && output_to_file)
+	{
+	  gcov_position_t offset = gcov_write_tag (GCOV_TAG_CONDS);
+	  for (int i = 0; i < nconds; ++i)
+	    {
+	      int idx = sizes[i];
+	      int len = sizes[i + 1] - idx;
+	      basic_block *itr = blocks.address () + idx;
+	      int terms = instrument_decisions (itr, len, i);
+	      gcov_write_unsigned (blocks[idx]->index);
+	      gcov_write_unsigned (terms);
+	    }
+	  gcov_write_length (offset);
+	}
+    }
+
   /* For each edge not on the spanning tree, add counting code.  */
   if (profile_arc_flag
       && coverage_counter_alloc (GCOV_COUNTER_ARCS, num_instrumented))
     {
       unsigned n_instrumented;
 
-      gimple_init_gcov_profiler ();
-
       n_instrumented = instrument_edges (el);
 
       gcc_assert (n_instrumented == num_instrumented);
 
       if (flag_profile_values)
 	instrument_values (values);
-
-      /* Commit changes done by instrumentation.  */
-      gsi_commit_edge_inserts ();
     }
 
   free_aux_for_edges ();
 
   values.release ();
   free_edge_list (el);
+  /* Commit changes done by instrumentation.  */
+  gsi_commit_edge_inserts ();
+
   coverage_end_function (lineno_checksum, cfg_checksum);
   if (flag_branch_probabilities
       && (profile_status_for_fn (cfun) == PROFILE_READ))
@@ -1664,6 +1721,7 @@ init_branch_prob (void)
   total_num_passes = 0;
   total_num_times_called = 0;
   total_num_branches = 0;
+  total_num_conds = 0;
   for (i = 0; i < 20; i++)
     total_hist_br_prob[i] = 0;
 }
@@ -1703,5 +1761,7 @@ end_branch_prob (void)
 		     (total_hist_br_prob[i] + total_hist_br_prob[19-i]) * 100
 		     / total_num_branches, 5*i, 5*i+5);
 	}
+      fprintf (dump_file, "Total number of conditions: %d\n",
+	       total_num_conds);
     }
 }
diff --git a/gcc/testsuite/g++.dg/gcov/gcov-18.C b/gcc/testsuite/g++.dg/gcov/gcov-18.C
new file mode 100644
index 00000000000..9ac14dfeff6
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gcov/gcov-18.C
@@ -0,0 +1,227 @@
+/* { dg-options "--coverage -fprofile-conditions -std=c++11" } */
+/* { dg-do run { target native } } */
+
+#include <vector>
+#include <stdexcept>
+
+class nontrivial_destructor
+{
+public:
+    explicit nontrivial_destructor (int v) : val (v) {}
+    ~nontrivial_destructor () {}
+
+    explicit operator bool() const { return bool(val); }
+
+    int val;
+};
+
+int identity (int x) { return x; }
+int throws (int) { throw std::runtime_error("exception"); }
+
+int throw_if (int x)
+{
+    if (x) /* conditions(1/2) true(0) */
+        throw std::runtime_error("exception");
+    return x;
+}
+
+/* used for side effects to insert nodes in conditional bodies etc. */
+int x = 0;
+
+/* conditionals work in the presence of non-trivial destructors */
+void mcdc001a (int a)
+{
+    nontrivial_destructor v (a);
+
+    if (v.val > 0) /* conditions(2/2) */
+        x = v.val;
+    else
+        x = -v.val;
+}
+
+/* non-trivial destructor in-loop temporary */
+nontrivial_destructor
+mcdc002a (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+        nontrivial_destructor tmp (a);
+        if (tmp.val % b) /* conditions(2/2) */
+            return nontrivial_destructor (0);
+        x += i;
+    } /* conditions(suppress) */
+      /* conditions(end) */
+
+    return nontrivial_destructor (a * b);
+}
+
+/* conditional in constructor */
+void mcdc003a (int a)
+{
+    class C
+    {
+    public:
+        explicit C (int e) : v (e) {
+            if (e) /* conditions(1/2) false(0) */
+                v = x - e;
+        }
+        int v;
+    };
+
+    C c (a);
+    if (c.v > 2) /* conditions(1/2) true(0) */
+        x = c.v + a;
+}
+
+/* conditional in destructor */
+void mcdc004a (int a)
+{
+    class C
+    {
+    public:
+        explicit C (int e) : v (e) {}
+        ~C () {
+            if (v) /* conditions(2/2) */
+                x = 2 * v;
+        }
+        int v;
+    };
+
+    C c (a);
+    x = 1; // arbitrary action between ctor+dtor
+}
+
+/* conditional in try */
+void mcdc005a (int a)
+{
+    try
+    {
+        if (a) /* conditions(1/2) true(0) */
+            x = 2 * identity (a);
+        else
+            x = 1;
+    }
+    catch (...)
+    {
+        x = 0;
+    }
+}
+
+/* conditional in catch */
+void mcdc006a (int a) {
+    try
+    {
+        throws (a);
+    }
+    catch (std::exception&)
+    {
+        if (a) /* conditions(1/2) false(0) */
+            x = identity (a);
+        else
+            x = 0;
+    }
+}
+
+void mcdc006b (int a)
+{
+    if (a) /* conditions(1/2) true(0) */
+        throws (a);
+    else
+        x = 1;
+}
+
+void mcdc006c (int a) try
+{
+    throws (a);
+}
+catch (...) {
+    if (a) /* conditions(2/2) */
+        x = 5;
+}
+
+/* temporary with destructor as term */
+void mcdc007a (int a, int b)
+{
+    x = a && nontrivial_destructor (b); /* conditions(3/4) false(1) destructor() */
+}
+
+void mcdc007b (int a, int b)
+{
+    if (a || throw_if (b)) /* conditions(3/4) true(1) destructor() */
+        x = -1;
+    else
+        x = 1;
+}
+
+void mcdc007c (int a, int b)
+{
+    if (throw_if (a) || throw_if (b)) /* conditions(2/4) true(0 1) destructor() */
+        x = -1;
+    else
+        x = 1;
+}
+
+/* destructor with delete */
+void mcdc008a (int a)
+{
+    class C
+    {
+    public:
+        int size = 5;
+        int* ptr = nullptr;
+
+        explicit C (int v) : size(v + 5), ptr (new int[size]) /* conditions(suppress) */
+                                                              /* conditions(end) */
+        {
+            for (int i = 0; i < size; i++) /* conditions(2/2) */
+                ptr[i] = i + 1;
+        }
+        ~C()
+        {
+            if (ptr) /* conditions(1/2) false(0) */
+                delete ptr; /* conditions(suppress) */
+                            /* conditions(end) */
+        }
+    };
+
+    C c (a);
+    if (c.ptr[a + 1]) /* conditions(1/2) false(0) */
+        x = a;
+}
+
+int
+main (void)
+{
+    mcdc001a (0);
+    mcdc001a (1);
+
+    mcdc002a (1, 1);
+    mcdc002a (1, 2);
+
+    mcdc003a (1);
+
+    mcdc004a (0);
+    mcdc004a (1);
+
+    mcdc005a (0);
+
+    mcdc006a (1);
+
+    mcdc006b (0);
+
+    mcdc006c (0);
+    mcdc006c (1);
+
+    mcdc007a (0, 0);
+    mcdc007a (1, 1);
+
+    mcdc007b (0, 0);
+    mcdc007b (1, 0);
+
+    mcdc007c (0, 0);
+
+    mcdc008a (1);
+
+}
+
+/* { dg-final { run-gcov conditions { --conditions gcov-18.C } } } */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-19.c b/gcc/testsuite/gcc.misc-tests/gcov-19.c
new file mode 100644
index 00000000000..5190c3fd58a
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-19.c
@@ -0,0 +1,727 @@
+/* { dg-options "-fprofile-conditions -ftest-coverage" } */
+/* { dg-do run { target native } } */
+
+/* some side effect to stop branches from being pruned */
+int x = 0;
+
+/* || works */
+void mcdc001a (int a, int b)
+{
+    if (a || b) /* conditions(1/4) true(0) false(0 1) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void mcdc001b (int a, int b)
+{
+    if (a || b) /* conditions(3/4) true(0) false() */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void mcdc001c (int a, int b)
+{
+    if (a || b) /* conditions(4/4) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void mcdc001d (int a, int b, int c)
+{
+    if (a || b || c) /* conditions(3/6) false(1 2) true(2) */
+		     /* conditions(end) */
+	x = 1;
+}
+
+/* && works */
+void mcdc002a (int a, int b)
+{
+    if (a && b) /* conditions(1/4) true(0 1) false(0) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void mcdc002b (int a, int b)
+{
+    if (a && b) /* conditions(3/4) false(0) */
+		/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void mcdc002c (int a, int b)
+{
+    if (a && b) /* conditions(4/4) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void mcdc002d (int a, int b, int c)
+{
+    /*
+     * This is an odd case, and falls victim to trying to detect nested ifs.
+     *
+     * if (a) if (b) if (c) with no else is equivalent to if (a && b && c) and
+     * the CFGs are identical *unless* the else nodes are generated too. In the
+     * && expression, all false edges should go to the same else, but in the
+     * nested-if case they go to different elses.
+     *
+     * This can be surprising, and bad for MC/DC because non-independent
+     * conditionals masked by terms further-right can not be detected. If an
+     * else node is generated, this expression becomes a 3-term decision again.
+     */
+    if (a && b && c) /* conditions(suppress) conditions(4/6) false(0 2) */
+	/* conditions(end) */
+	x = 1;
+}
+
+/* negation works */
+void mcdc003a (int a, int b)
+{
+    if (!a || !b) /* conditions(2/4) false(0 1) */
+		  /* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+/* single conditionals with and without else */
+void mcdc004a (int a)
+{
+    if (a) /* conditions(1/2) true(0) */
+	   /* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void mcdc004b (int a)
+{
+    if (a) /* conditions(2/2) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void mcdc004c (int a)
+{
+    if (a) /* conditions(1/2) false(0) */
+	   /* conditions(end) */
+	x = 1;
+}
+
+void mcdc004d (int a, int b, int c) {
+    if (a)  /* conditions(2/2) */
+    {
+	if (b || c) /* conditions(1/4) true(1) false(0 1) */
+	    x = a + b + c;
+    }
+}
+
+/* mixing && and || works */
+void mcdc005a (int a, int b, int c)
+{
+    if ((a && b) || c) /* conditions(1/6) true(0 1) false(0 1 2) */
+		       /* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void mcdc005b (int a, int b, int c, int d)
+{
+    if ((a && (b || c)) && d) /* conditions(4/8) true(1 2 3) false(0) */
+			      /* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+/* nested conditionals */
+void mcdc006a (int a, int b, int c, int d, int e)
+{
+    if (a) /* conditions(2/2) */
+    {
+	if (b && c) /* conditions(3/4) false(1) */
+		    /* conditions(end) */
+	    x = 1;
+	else
+	    x = 2;
+    }
+    else
+    {
+	if (c || d) /* conditions(2/4) true(0 1) */
+		    /* conditions(end) */
+	    x = 3;
+	else
+	    x = 4;
+    }
+}
+
+void mcdc006b (int a, int b, int c)
+{
+    if (a) /* conditions(2/2) */
+    {
+	if (b) /*conditions(2/2) */
+	{
+	    if (c) /* conditions(2/2) */
+	    {
+		x = a + b + c;
+	    }
+	}
+    }
+}
+
+/* else/if */
+void mcdc007a (int a, int b, int c, int d)
+{
+    if (a) /* conditions(2/2) */
+    {
+	if (b) /* conditions(1/2) true(0) */
+	       /* conditions(end) */
+	    x = 1;
+	else
+	    x = 2;
+    }
+    else if (c) /* conditions(2/2) */
+    {
+	if (d) /* conditions(1/2) true(0) */
+	       /* conditions(end) */
+	    x = 3;
+    }
+}
+
+void mcdc007b (int a, int b, int c)
+{
+    /* similar to if (a || b || c) x = 1 */
+    if (a) /* conditions(2/2) */
+	goto then;
+    else if (b) /* conditions(2/2) */
+	goto then;
+    else if (c) /* conditions(1/2) true(0) */
+	/* conditions(end) */
+	goto then;
+
+    return;
+
+then:
+    x = 1;
+}
+
+/* while loop */
+void mcdc008a (int a)
+{
+    while (a < 10) /* conditions(2/2) */
+	x = a++;
+}
+
+void mcdc008b (int a)
+{
+    while (a > 10) /* conditions(1/2) true(0) */
+		   /* conditions(end) */
+	x = a--;
+}
+
+void mcdc008c (int a)
+{
+    // should work, even with no body
+    while (a) /* conditions(2/2) */
+	break;
+}
+
+void mcdc008d (int a, int b, int c, int d)
+{
+    /* multi-term loop conditional */
+    while ((a && (b || c)) && d) /* conditions(8/8) */
+	a = b = c = d = 0;
+}
+
+void mcdc009a (int a, int b)
+{
+    while (a > 0 && b > 0) /* conditions(3/4) false(1) */
+			   /* conditions(end) */
+	x = a--;
+}
+
+/* for loop */
+void mcdc010a(int a, int b)
+{
+    for (int i = 0; i < b; i++) /* conditions(2/2) */
+    {
+	if (a < b) /* conditions(2/2) */
+	    x = 1;
+	else
+	    x = a += 2;
+    }
+}
+
+int always (int) { return 1; }
+
+/* no-condition infinite loops */
+void mcdc010b (int a)
+{
+    for (;;)
+    {
+	if (always(a)) /* conditions(1/2) false(0) */
+		       /* conditions(end) */
+	{
+	    x = a;
+	    break;
+	}
+	x += a + 1;
+    }
+}
+
+/* conditionals without control flow constructs work */
+void mcdc011a (int a, int b, int c)
+{
+    x = (a && b) || c; /* conditions(5/6) false(1) */
+		       /* conditions(end) */
+}
+
+/* sequential expressions are handled independently */
+void mcdc012a (int a, int b, int c) {
+    if (a || b) /* conditions(3/4) true(0) */
+	/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+
+    if (c) /* conditions(2/2) */
+	x = 1;
+}
+
+/*
+ * cannot ever satisfy MC/DC, even with all input combinations, because not all
+ * variables independently affect the decision
+ */
+void mcdc013a (int a, int /* b */, int c)
+{
+    /*
+     * Specification: (a && b) || c
+     *
+     * But the expression was implemented wrong. This has branch coverage, but
+     * not MC/DC
+     */
+    if ((a && !c) || c) /* conditions(5/6) false(1) */
+	/* conditions(end) */
+	x = 1;
+    else
+	x = 2;
+}
+
+void mcdc014a ()
+{
+    int conds[64] = { 0 };
+    /* conditions(64/128) true(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63) */
+    x = conds[ 0] || conds[ 1] || conds[ 2] || conds[ 3] || conds[ 4] ||
+	conds[ 5] || conds[ 6] || conds[ 7] || conds[ 8] || conds[ 9] ||
+	conds[10] || conds[11] || conds[12] || conds[13] || conds[14] ||
+	conds[15] || conds[16] || conds[17] || conds[18] || conds[19] ||
+	conds[20] || conds[21] || conds[22] || conds[23] || conds[24] ||
+	conds[25] || conds[26] || conds[27] || conds[28] || conds[29] ||
+	conds[30] || conds[31] || conds[32] || conds[33] || conds[34] ||
+	conds[35] || conds[36] || conds[37] || conds[38] || conds[39] ||
+	conds[40] || conds[41] || conds[42] || conds[43] || conds[44] ||
+	conds[45] || conds[46] || conds[47] || conds[48] || conds[49] ||
+	conds[50] || conds[51] || conds[52] || conds[53] || conds[54] ||
+	conds[55] || conds[56] || conds[57] || conds[58] || conds[59] ||
+	conds[60] || conds[61] || conds[62] || conds[63]
+	;  /* conditions(end) */
+}
+
+/* early returns */
+void mcdc015a (int a, int b)
+{
+    if (a) /* conditions(2/2) */
+	return;
+
+    if (b) /* conditions(1/2) true(0) */
+	   /* conditions(end) */
+	x = 1;
+}
+
+void mcdc015b (int a, int b)
+{
+    for (int i = 5; i > a; i--) /* conditions(2/2) */
+    {
+	if (i == b) /* conditions(2/2) */
+	    return;
+	x = i;
+    }
+}
+
+void mcdc015c (int a, int b)
+{
+    for (int i = 5; i > a; i--) /* conditions(2/2) */
+    {
+	if (i == b) /* conditions(2/2) */
+	{
+	    x = 0;
+	    return;
+	}
+	else
+	{
+	    x = 1;
+	    return;
+	}
+
+	x = i;
+    }
+}
+
+
+/* check nested loops */
+void mcdc016a (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+	for (int k = 0; k < b; k++) /* conditions(2/2) */
+	    x = i + k;
+}
+
+void mcdc016b (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+	if (a > 5) /* conditions(2/2) */
+	    break;
+
+	for (int k = 0; k < b; k++) /* conditions(2/2) */
+	    x = i + k;
+    }
+}
+
+void mcdc016c (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+	if (a > 5) /* conditions(1/2) true(0) */
+		   /* conditions(end) */
+	    return;
+
+	for (int k = 0; k < b; k++) /* conditions(2/2) */
+	    x = i + k;
+    }
+}
+
+void mcdc016d (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+	for (int k = 0; k < 5; k++) /* conditions(2/2) */
+	{
+	    if (b > 5) /* conditions(1/2) true(0) */
+		       /* conditions(end) */
+		return;
+	    x = i + k;
+	}
+
+    }
+}
+
+/* do-while loops */
+void mcdc017a (int a)
+{
+    do {
+	a--;
+    } while (a > 0); /* conditions(2/2) */
+}
+
+void noop () {}
+
+void mcdc017b (int a, int b)
+{
+    do {
+	/*
+	 * This call is important; it can add more nodes to the body in the
+	 * CFG, which has changes how close exits and breaks are to the loop
+	 * conditional.
+	 */
+	noop ();
+	a--;
+	if (b) /* conditions(2/2) */
+	    break;
+
+    } while (a > 0); /* conditions(2/2) */
+}
+
+void mcdc017c (int a, int b)
+{
+    int left = 0;
+    int right = 0;
+    int n = a + b;
+    do {
+	if (a) /* conditions(1/2) false(0) */
+	       /* conditions(end) */
+	{
+	    left = a > left ? b : left; /* conditions(2/2) */
+	}
+	if (b) /* conditions(1/2) false(0) */
+	{
+	    right = b > right ? a : right; /* conditions(2/2) */
+	}
+    } while (n-- > 0); /* conditions(2/2) */
+}
+
+int id  (int x) { return  x; }
+int inv (int x) { return !x; }
+
+/* collection of odd cases lifted-and-adapted from real-world code */
+int mcdc018a (int a, int b, int c, int d, int e, int f, int g, int len)
+{
+    int n;
+    /* adapted from zlib/gz_read */
+    do
+    {
+	n = -1;
+	if (n > len) /* conditions(2/2) */
+	    n = len;
+
+	if (b) /* conditions(2/2) */
+	{
+	    if (b < 5) /* conditions(2/2) */
+		x = 1;
+	    noop();
+	}
+	else if (c && d) /* conditions(3/4) false(1) */
+	{
+	    x = 2;
+	    break;
+	}
+	else if (e || f) /* conditions(2/4) false(0 1) */
+			 /* conditions(end) */
+	{
+	    if (id(g)) /* conditions(2/2) */
+		return 0;
+	    continue;
+	}
+    } while (a-- > 0); /* conditions(2/2) */
+
+    return 1;
+}
+
+void mcdc018b (int a, int b, int c) {
+    int n;
+    while (a) /* conditions(2/2) */
+    {
+	/*
+	 * else block does not make a difference for the problem, but ensures
+	 * loop termination
+	 */
+	if (b) /* conditions(2/2) */
+	    n = c ? 0 : 0; // does not show up in CFG (embedded in the block)
+	else
+	    n = 0;
+	a = n;
+    }
+}
+
+/* too many conditions, coverage gives up */
+void mcdc019a ()
+{
+    int conds[65] = { 0 };
+    #pragma GCC diagnostic push
+    #pragma GCC diagnostic ignored "-Wcoverage-too-many-conditions"
+    x = conds[ 0] || conds[ 1] || conds[ 2] || conds[ 3] || conds[ 4] ||
+	conds[ 5] || conds[ 6] || conds[ 7] || conds[ 8] || conds[ 9] ||
+	conds[10] || conds[11] || conds[12] || conds[13] || conds[14] ||
+	conds[15] || conds[16] || conds[17] || conds[18] || conds[19] ||
+	conds[20] || conds[21] || conds[22] || conds[23] || conds[24] ||
+	conds[25] || conds[26] || conds[27] || conds[28] || conds[29] ||
+	conds[30] || conds[31] || conds[32] || conds[33] || conds[34] ||
+	conds[35] || conds[36] || conds[37] || conds[38] || conds[39] ||
+	conds[40] || conds[41] || conds[42] || conds[43] || conds[44] ||
+	conds[45] || conds[46] || conds[47] || conds[48] || conds[49] ||
+	conds[50] || conds[51] || conds[52] || conds[53] || conds[54] ||
+	conds[55] || conds[56] || conds[57] || conds[58] || conds[59] ||
+	conds[60] || conds[61] || conds[62] || conds[63] || conds[64]
+	;
+    #pragma GCC diagnostic pop
+}
+
+/* ternary */
+void mcdc020a (int a)
+{
+    // special case, this can be reduced to:
+    // _1 = argc != 0;
+    // e = (int) _1;
+    x = a ? 1 : 0;
+
+    // changing to different int makes branch
+    x = a ? 2 : 1; /* conditions(2/2) */
+}
+
+void mcdc020b (int a, int b)
+{
+    x = (a || b) ? 1 : 0; /* conditions(3/4) true(1) */
+}
+
+void mcdc020c (int a, int b)
+{
+    x = a ? 0
+	: b ? 1 /* conditions(2/2) */
+	: 2;    /* conditions(1/2) false(0) */
+		/* conditions(end) */
+}
+
+/* test with functions as conditionals */
+
+int main ()
+{
+    mcdc001a (0, 1);
+
+    mcdc001b (0, 1);
+    mcdc001b (0, 0);
+
+    mcdc001c (0, 1);
+    mcdc001c (0, 0);
+    mcdc001c (1, 1);
+
+    mcdc001d (1, 1, 1);
+    mcdc001d (0, 1, 0);
+
+    mcdc002a (1, 0);
+
+    mcdc002b (1, 0);
+    mcdc002b (1, 1);
+
+    mcdc002c (0, 0);
+    mcdc002c (1, 1);
+    mcdc002c (1, 0);
+
+    mcdc002d (1, 1, 1);
+    mcdc002d (1, 0, 0);
+
+    mcdc003a (0, 0);
+    mcdc003a (1, 0);
+
+    mcdc004a (0);
+    mcdc004b (0);
+    mcdc004b (1);
+    mcdc004c (1);
+
+    mcdc004d (0, 0, 0);
+    mcdc004d (1, 1, 1);
+
+    mcdc005a (1, 0, 1);
+
+    mcdc005b (1, 1, 0, 0);
+    mcdc005b (1, 0, 0, 0);
+
+    mcdc006a (0, 0, 0, 0, 0);
+    mcdc006a (1, 0, 0, 0, 0);
+    mcdc006a (1, 1, 1, 0, 0);
+
+    mcdc006b (0, 0, 0);
+    mcdc006b (1, 0, 0);
+    mcdc006b (1, 1, 0);
+    mcdc006b (1, 1, 1);
+
+    mcdc007a (0, 0, 0, 0);
+    mcdc007a (1, 0, 0, 0);
+    mcdc007a (0, 0, 1, 0);
+
+    mcdc007b (0, 0, 0);
+    mcdc007b (0, 1, 1);
+    mcdc007b (1, 0, 1);
+
+    mcdc008a (0);
+
+    mcdc008b (0);
+
+    mcdc008c (0);
+    mcdc008c (1);
+
+    mcdc008d (0, 0, 0, 0);
+    mcdc008d (1, 0, 0, 0);
+    mcdc008d (1, 0, 1, 0);
+    mcdc008d (1, 0, 1, 1);
+    mcdc008d (1, 1, 1, 1);
+
+    mcdc009a (0, 0);
+    mcdc009a (1, 1);
+
+    mcdc010a (0, 0);
+    mcdc010a (0, 9);
+    mcdc010a (2, 1);
+
+    mcdc010b (1);
+
+    mcdc011a (0, 0, 0);
+    mcdc011a (1, 1, 0);
+    mcdc011a (1, 0, 1);
+
+    mcdc012a (0, 0, 0);
+    mcdc012a (0, 1, 1);
+
+    mcdc013a (0, 0, 0);
+    mcdc013a (0, 0, 1);
+    mcdc013a (0, 1, 0);
+    mcdc013a (0, 1, 1);
+    mcdc013a (1, 0, 0);
+    mcdc013a (1, 0, 1);
+    mcdc013a (1, 1, 0);
+    mcdc013a (1, 1, 1);
+
+    mcdc014a ();
+
+    mcdc015a (0, 0);
+    mcdc015a (1, 0);
+
+    mcdc015b (0, 0);
+    mcdc015b (0, 1);
+    mcdc015b (6, 1);
+
+    mcdc015c (0, 0);
+    mcdc015c (0, 5);
+    mcdc015c (6, 1);
+
+    mcdc016a (5, 5);
+
+    mcdc016b (5, 5);
+    mcdc016b (6, 5);
+
+    mcdc016c (5, 5);
+
+    mcdc016d (1, 0);
+
+    mcdc017a (0);
+    mcdc017a (2);
+
+    mcdc017b (2, 0);
+    mcdc017b (0, 1);
+
+    mcdc017c (1, 1);
+
+    mcdc018a (0, 0, 1, 1, 0, 0, 0, 0);
+    mcdc018a (0, 1, 0, 0, 0, 0, 1, -2);
+    mcdc018a (0, 6, 0, 0, 0, 0, 1, -2);
+    mcdc018a (0, 6, 0, 0, 0, 0, 1, -2);
+    mcdc018a (0, 0, 0, 1, 0, 1, 1, 0);
+    mcdc018a (1, 0, 0, 0, 1, 1, 0, 0);
+
+    mcdc018b (1, 0, 0);
+    mcdc018b (1, 1, 0);
+
+    mcdc019a ();
+
+    mcdc020a (0);
+    mcdc020a (1);
+
+    mcdc020b (0, 0);
+    mcdc020b (1, 0);
+
+    mcdc020c (0, 1);
+    mcdc020c (1, 1);
+}
+
+/* { dg-final { run-gcov conditions { --conditions gcov-19.c } } } */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-20.c b/gcc/testsuite/gcc.misc-tests/gcov-20.c
new file mode 100644
index 00000000000..56dd6cf89bb
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-20.c
@@ -0,0 +1,21 @@
+/* { dg-options "-fprofile-conditions -ftest-coverage -fprofile-update=atomic" } */
+/* { dg-do run { target native } } */
+
+/* some side effect to stop branches from being pruned */
+int x = 0;
+
+void conditions_atomic001(int a, int b)
+{
+  if (a || b) /* conditions(1/4) true(0) false(0 1) */
+	      /* conditions(end) */
+      x = 1;
+  else
+      x = 2;
+}
+
+int main ()
+{
+    conditions_atomic001 (0, 1);
+}
+
+/* { dg-final { run-gcov conditions { --conditions gcov-20.c } } } */
diff --git a/gcc/testsuite/lib/gcov.exp b/gcc/testsuite/lib/gcov.exp
index 9d5b2cdb86b..88f28d26bb6 100644
--- a/gcc/testsuite/lib/gcov.exp
+++ b/gcc/testsuite/lib/gcov.exp
@@ -174,6 +174,180 @@ proc verify-branches { testname testcase file } {
     return $failed
 }
 
+#
+# verify-conditions -- check that conditions are checked as expected
+#
+# TESTNAME is the name of the test, including unique flags.
+# TESTCASE is the name of the test file.
+# FILE is the name of the gcov output file.
+#
+# Checks are based on comments in the source file. Condition coverage comes
+# with with two types of output, a summary and a list of the uncovered
+# conditions. Both must be checked to pass the test
+#
+# To check for conditions, add a comment the line of a conditional:
+# /* conditions(n/m) true(0 1) false(1) */
+#
+# where n/m are the covered and total conditions in the expression. The true()
+# and false() take the indices expected *not* covered.
+#
+# This means that all coverage statements should have been seen:
+# /* conditions(end) */
+#
+# If all conditions are covered i.e. n == m, then conditions(end) can be
+# omitted. If either true() or false() are empty they can be omitted too.
+#
+# C++ can insert conditionals in the CFG that are not present in source code.
+# These must be manually suppressed since unexpected and unhandled conditions
+# are an error (to help combat regressions). Output can be suppressed with
+# conditions(suppress) and conditions(end). suppress should usually be on a
+# closing brace.
+#
+# Some expressions, when using unnamed temporaries as operands, will have
+# destructors in expressions. The coverage of the destructor will be reported
+# on the same line as the expression itself, but suppress() would also swallow
+# the expected tested-for messages. To handle these, use the destructor() [1]
+# which will suppress everything from and including the second "conditions
+# covered".
+#
+# [1] it is important that the destructor() is *on the same line* as the
+#     conditions(m/n)
+proc verify-conditions { testname testcase file } {
+    set failed 0
+    set suppress 0
+    set destructor 0
+    set shouldt ""
+    set shouldf ""
+    set shouldall ""
+    set fd [open $file r]
+    set n 0
+    set keywords {"end" "suppress"}
+    while {[gets $fd line] >= 0} {
+        regexp "^\[^:\]+: *(\[0-9\]+):" "$line" all n
+        set prefix "$testname line $n"
+
+        if {![regexp "condition" $line]} {
+            continue
+        }
+
+        # Missing coverage for both true and false will cause a failure, but
+        # only count it once for the report.
+        set ok 1
+        if [regexp {conditions *\(([0-9a-z/]+)\)} "$line" all e] {
+            # *Very* coarse sanity check: conditions() should either be a
+            # keyword or n/m, anything else means a buggy test case. end is
+            # optional for cases where all conditions are covered, since it
+            # only expects a single line of output.
+            if {([lsearch -exact $keywords $e] >= 0 || [regexp {\d+/\d+} "$e"]) == 0} {
+                fail "$prefix: expected conditions (n/m), (suppress) or (end); was ($e)"
+                incr failed
+                continue
+            }
+
+            # Any keyword means a new context. Set the error flag if not all
+            # expected output has been seen, and reset the state.
+
+            if {[llength $shouldt] != 0} {
+                fail "$prefix: expected uncovered (true) for terms $shouldt"
+                set ok 0
+            }
+
+            if {[llength $shouldf] != 0} {
+                fail "$prefix: expected uncovered (false) for terms $shouldf"
+                set ok 0
+            }
+
+            if {$shouldall ne ""} {
+                fail "$prefix: coverage summary not found; expected $shouldall"
+                set ok 0
+            }
+
+            set suppress 0
+            set destructor 0
+            set shouldt ""
+            set shouldf ""
+            set shouldall ""
+            set newt ""
+            set newf ""
+
+            if [regexp {destructor\(\)} "$line"] {
+                set destructor 1
+            }
+
+            if [regexp {(\d+)/(\d+)} "$e" all i k] {
+                regexp {true\(([0-9 ]+)\)}  "$line" all newt
+                regexp {false\(([0-9 ]+)\)} "$line" all newf
+
+                # Sanity check - if the true() and false() vectors should have
+                # m-n elements to cover all uncovered conditions. Because of
+                # masking it can sometimes be surprising what terms are
+                # independent, so this makes for more robust test at the cost
+                # of being slightly more annoying to write.
+                set nterms [expr [llength $newt] + [llength $newf]]
+                set nexpected [expr {$k - $i}]
+                if {$nterms != $nexpected} {
+                    fail "$prefix: expected $nexpected uncovered terms; got $nterms"
+                    set ok 0
+                }
+                set shouldall $e
+                set shouldt $newt
+                set shouldf $newf
+            } elseif {$e == "end"} {
+                # no-op - state has already been reset, and errors flagged
+            } elseif {$e == "suppress"} {
+                set suppress 1
+            } else {
+                # this should be unreachable, 
+                fail "$prefix: unhandled control ($e), should be unreachable"
+                set ok 0
+            }
+        } elseif {$suppress == 1} {
+            # ignore everything in a suppress block. C++ especially can insert
+            # conditionals in exceptions and destructors which would otherwise
+            # be considered unhandled.
+            continue
+        } elseif [regexp {condition +(\d+) not covered \(true\)} "$line" all cond] {
+            set i [lsearch $shouldt $cond]
+            if {$i != -1} {
+                set shouldt [lreplace $shouldt $i $i]
+            } else {
+                fail "$testname line $n: unexpected uncovered term $cond (true)"
+                set ok 0
+            }
+        } elseif [regexp {condition +(\d+) not covered \(false\)} "$line" all cond] {
+            set i [lsearch $shouldf $cond]
+            if {$i != -1} {
+                set shouldf [lreplace $shouldf $i $i]
+            } else {
+                fail "$testname line $n: unexpected uncovered term $cond (false)"
+                set ok 0
+            }
+        } elseif [regexp {conditions covered (\d+/\d+)} "$line" all cond] {
+            # the destructor-generated "conditions covered" lines will be
+            # written after all expression-related output. Handle these by
+            # turning on suppression if the destructor-suppression is
+            # requested.
+            if {$shouldall == "" && $destructor == 1} {
+                set suppress 1
+                continue
+            }
+
+            if {$cond == $shouldall} {
+                set shouldall ""
+            } else {
+                fail "$testname line $n: unexpected summary $cond"
+                set ok 0
+            }
+        }
+
+        if {$ok != 1} {
+            incr failed
+        }
+    }
+    close $fd
+    return $failed
+}
+
 #
 # verify-calls -- check that call return percentages are as expected
 #
@@ -321,6 +495,7 @@ proc run-gcov { args } {
     set gcov_args ""
     set gcov_verify_calls 0
     set gcov_verify_branches 0
+    set gcov_verify_conditions 0
     set gcov_verify_lines 1
     set gcov_verify_intermediate 0
     set gcov_remove_gcda 0
@@ -331,10 +506,13 @@ proc run-gcov { args } {
 	  set gcov_verify_calls 1
 	} elseif { $a == "branches" } {
 	  set gcov_verify_branches 1
+	} elseif { $a == "conditions" } {
+	  set gcov_verify_conditions 1
 	} elseif { $a == "intermediate" } {
 	  set gcov_verify_intermediate 1
 	  set gcov_verify_calls 0
 	  set gcov_verify_branches 0
+	  set gcov_verify_conditions 0
 	  set gcov_verify_lines 0
 	} elseif { $a == "remove-gcda" } {
 	  set gcov_remove_gcda 1
@@ -404,6 +582,11 @@ proc run-gcov { args } {
     } else {
 	set bfailed 0
     }
+    if { $gcov_verify_conditions } {
+	set cdfailed [verify-conditions $testname $testcase $testcase.gcov]
+    } else {
+	set cdfailed 0
+    }
     if { $gcov_verify_calls } {
 	set cfailed [verify-calls $testname $testcase $testcase.gcov]
     } else {
@@ -418,12 +601,12 @@ proc run-gcov { args } {
 
     # Report whether the gcov test passed or failed.  If there were
     # multiple failures then the message is a summary.
-    set tfailed [expr $lfailed + $bfailed + $cfailed + $ifailed]
+    set tfailed [expr $lfailed + $bfailed + $cdfailed + $cfailed + $ifailed]
     if { $xfailed } {
 	setup_xfail "*-*-*"
     }
     if { $tfailed > 0 } {
-	fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cfailed in return percentages, $ifailed in intermediate format"
+	fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cdfailed in condition/decision, $cfailed in return percentages, $ifailed in intermediate format"
 	if { $xfailed } {
 	    clean-gcov $testcase
 	}
diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
index 6d40401f86f..2f515daf9dd 100644
--- a/gcc/tree-profile.cc
+++ b/gcc/tree-profile.cc
@@ -24,6 +24,7 @@ along with GCC; see the file COPYING3.  If not see
 /* Generate basic block profile instrumentation and auxiliary files.
    Tree-based version.  See profile.cc for overview.  */
 
+#define INCLUDE_ALGORITHM
 #include "config.h"
 #include "system.h"
 #include "coretypes.h"
@@ -58,6 +59,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "alloc-pool.h"
 #include "symbol-summary.h"
 #include "symtab-thunks.h"
+#include "cfganal.h"
+#include "cfgloop.h"
 
 static GTY(()) tree gcov_type_node;
 static GTY(()) tree tree_interval_profiler_fn;
@@ -73,6 +76,880 @@ static GTY(()) tree ic_tuple_var;
 static GTY(()) tree ic_tuple_counters_field;
 static GTY(()) tree ic_tuple_callee_field;
 
+namespace
+{
+
+struct conds_ctx
+{
+    /* Output arrays allocated by the caller.  */
+    basic_block *blocks;
+    int *sizes;
+
+    /* The size of the blocks buffer.  This is just bug protection,
+       the caller should have allocated enough memory for blocks to never get
+       this many elements.
+       */
+    int maxsize;
+
+    /* Number of expressions found - this value is the number of entries in the
+       gcov output and the parameter to gcov_counter_alloc ().
+       */
+    int exprs;
+
+    /* Bitmap of the processed blocks - bit n set means basic_block->index has
+       been processed as a first-in-expression block.  This effectively stops
+       loop edges from being taken and subgraphs re-processed.
+       */
+    auto_sbitmap seen;
+
+    /* Pre-allocate bitmaps for per-function book keeping.  This is pure
+       instance reuse and the bitmaps carries no data between function calls.
+       */
+    auto_sbitmap expr;
+    auto_sbitmap reachable;
+
+    explicit conds_ctx (unsigned size) noexcept (true) : maxsize (0), exprs (0),
+    seen (size), expr (size), reachable (size)
+    {
+	bitmap_clear (seen);
+    }
+
+    void commit (basic_block top, int nblocks) noexcept (true)
+    {
+	blocks  += nblocks;
+	*sizes  += nblocks;
+	maxsize -= nblocks;
+
+	exprs++;
+	sizes++;
+	*sizes = 0;
+
+	bitmap_set_bit (seen, top->index);
+    }
+};
+
+/* Only instrument terms with fewer than number of bits in a (wide) gcov
+   integer, which is probably 64.  The algorithm itself does not impose this
+   limitation, but it makes for a simpler implementation.
+
+   * Allocating the output data structure (coverage_counter_alloc ()) can
+     assume pairs of gcov_type_unsigned and not use a separate length field.
+   * A pair gcov_type_unsigned can be used as accumulators.
+   * Updating accumulators is can use the bitwise operations |=, &= and not
+     custom operators that work for arbitrary-sized bit-sets.
+
+   Most real-world code should be unaffected by this, but it is possible
+   (especially for generated code) to exceed this limit.
+ */
+#define CONDITIONS_MAX_TERMS (sizeof (gcov_type_unsigned) * BITS_PER_UNIT)
+
+int
+index_of (const_basic_block needle, const const_basic_block *blocks, int size)
+{
+    for (int i = 0; i < size; i++)
+    {
+	if (blocks[i] == needle)
+	    return i;
+    }
+    return -1;
+}
+
+bool
+index_lt (const basic_block x, const basic_block y)
+{
+    return x->index < y->index;
+}
+
+/* Special cases of the single_*_p and single_*_edge functions in basic-block.h
+   that don't consider exception handling or other complex edges.  This helps
+   create a view of the CFG with only normal edges - if a basic block has both
+   an outgoing fallthrough and exceptional edge [1], it should be considered a
+   single-successor.
+
+   [1] if this is not possible, these functions can be removed and replaced by
+       their basic-block.h cousins.
+ */
+bool
+single (const vec<edge, va_gc> *edges)
+{
+    int n = EDGE_COUNT (edges);
+    if (n == 0)
+	return false;
+
+    for (edge e : edges)
+	if (e->flags & EDGE_COMPLEX)
+	    n -= 1;
+
+    return n == 1;
+}
+
+edge
+single_edge (const vec<edge, va_gc> *edges)
+{
+    for (edge e : edges)
+    {
+	if (e->flags & EDGE_COMPLEX)
+	    continue;
+	return e;
+    }
+    gcc_unreachable ();
+}
+
+/* Sometimes, for example with function calls and C++ destructors the CFG gets
+   extra nodes that are essentially single-entry-single-exit in the middle of
+   boolean expressions.  For example:
+
+      x || can_throw (y)
+
+               A
+              /|
+             / |
+            B  |
+            |  |
+            C  |
+           / \ |
+          /   \|
+         F     T
+
+   Without the extra node inserted by the function + exception it becomes a
+   proper 2-term graph, not 2 single-term graphs.
+
+               A
+              /|
+             C |
+            / \|
+           F   T
+
+   contract_edge ignores the series of intermediate nodes and makes a virtual
+   edge A -> C, without having to construct a new simplified CFG explicitly.
+
+   Such an expression cannot correctly be repreted as two 1-term expressions,
+   as it would break the condition masking.
+ */
+edge
+contract_edge (edge e, sbitmap expr)
+{
+    while (true)
+    {
+	basic_block src  = e->src;
+	basic_block dest = e->dest;
+
+	if (!single (dest->succs))
+	    return e;
+	if (!single (dest->preds))
+	    return e;
+	if (!single (src->preds))
+	    return e;
+
+	edge succe = single_edge (dest->succs);
+	if (!single (succe->dest->preds))
+	    return e;
+
+	bitmap_set_bit (expr, dest->index);
+	e = succe;
+    }
+}
+
+edge
+contract_edge_up (edge e, sbitmap expr)
+{
+    while (true)
+    {
+	basic_block src  = e->src;
+	basic_block dest = e->dest;
+
+	if (!single (dest->succs))
+	    return e;
+	if (!single (dest->preds))
+	    return e;
+	if (!single (src->preds))
+	    return e;
+
+	if (expr) bitmap_set_bit (expr, src->index);
+	e = single_pred_edge (src);
+    }
+}
+
+bool
+is_conditional_p (const basic_block b)
+{
+    if (single_succ_p (b))
+        return false;
+
+    unsigned t = 0;
+    unsigned f = 0;
+    for (edge e : b->succs)
+    {
+        t |= (e->flags & EDGE_TRUE_VALUE);
+        f |= (e->flags & EDGE_FALSE_VALUE);
+    }
+    return t && f;
+}
+
+/* The first block in the output will always be the source block of the edge
+   that will apply the masking operation, with the remaining blocks effectively
+   unordered.
+ */
+int
+find_conditions_masked_by (
+    basic_block block,
+    const sbitmap expr,
+    const unsigned *flag,
+    basic_block *out,
+    int maxsize)
+{
+    int n = 0;
+    for (edge e : block->preds)
+    {
+	/* Skip any predecessor not in the expression - there might be such an
+	   edge to the enclosing expression or in the presence of loops, but
+	   masking cannot happen outside the expression itself.
+	   */
+	if (!bitmap_bit_p (expr, e->src->index))
+	    continue;
+
+	e = contract_edge_up (e, NULL);
+	if (e->flags & flag[0])
+	    out[n++] = e->src;
+    }
+
+    if (n > 1)
+    {
+	basic_block *top = std::max_element (out, out + n, index_lt);
+	std::iter_swap (top, out);
+    }
+
+    for (int pos = 0; pos < n; pos++)
+    {
+	for (edge e : out[pos]->preds)
+	{
+	    if (!bitmap_bit_p (expr, e->src->index))
+		continue;
+	    if (index_of (e->src, out, n) != -1)
+		continue;
+
+	    e = contract_edge_up (e, NULL);
+	    if (e->flags & flag[1])
+		out[n++] = e->src;
+
+	    gcc_assert (n < maxsize);
+	}
+    }
+
+    return n;
+}
+
+/* Scan the blocks that make up an expression and look for conditions that
+   would mask other conditions.  For a deeper discussion on masking, see
+   Whalen, Heimdahl, De Silva "Efficient Test Coverage Measurement for MC/DC".
+   Masking is best illustrated with an example:
+
+   A || B.  If B is true then A will does not independently affect the decision.
+   In a way, this is "reverse" short circuiting, and always work on the
+   right-hand side of expressions.  * || true is always true and * && false is
+   always false - the left-hand-side does not affect the outcome, and their
+   values should not contribute to modifidied condition/decision coverage.
+
+   A || B interpreted as a decision diagram becomes:
+
+    A
+   t|\f
+    | \
+    |  B
+    |t/ \f
+    |/   \
+    T     F
+
+   The algorithm looks for triangles like ATB.  Masking right-hand sides happen
+   when a block has a pair of incoming edges of the same boolean value, and
+   there is an edge connecting the two predecessors with the *opposite* boolean
+   value.  The masking block is B, and the masked block is A.  In this
+   particular example:
+
+   Masking can affect "outside" its own subexpression; in A || (B && C) if C is
+   false, B is masked.  However, if (B && C) is true, A gets masked.  B && C
+   can be determined from evaluating C since !B would short-circuit, so a true
+   C would mask A.
+
+    A
+   t|\f
+    | \
+    |  \
+    |   \
+    |    B
+    |  t/ \f
+    |  C   |
+    |t/ \f |
+    |/   \ |
+    T     F
+
+   Notice how BFC forms a triangle.  Expressions masked by an edge are
+   determined by:
+   * Go to the predecessor with truth value b (if it exists).
+   * Follow the path of ancestors with taking only !b edges, recording every
+     node from here on.
+
+   For the case where C can mask A, the path goes C [true]-> B -> [false] A, so
+   C [true] masks A.
+
+   The mask is output as a bit-set stored in a gcov_type_unsigned.  The
+   bit-sets are output in pairs, one for each decision (the outcome of the
+   boolean expression, or which arc to take in the CFG).
+ */
+void
+find_subexpr_masks (
+    const basic_block *blocks,
+    int nblocks,
+    gcov_type_unsigned *masks)
+{
+    const unsigned flags[] = {
+	EDGE_TRUE_VALUE,
+	EDGE_FALSE_VALUE,
+	EDGE_TRUE_VALUE,
+    };
+
+    basic_block path[CONDITIONS_MAX_TERMS];
+    auto_sbitmap expr (n_basic_blocks_for_fn (cfun));
+    bitmap_clear (expr);
+    for (int i = 0; i < nblocks; i++)
+	bitmap_set_bit (expr, blocks[i]->index);
+
+    for (int i = 0; i < nblocks; i++)
+    {
+	basic_block block = blocks[i];
+	if (single_pred_p (block))
+	    continue;
+
+	for (int k = 0; k < 2; k++)
+	{
+	    const int n = find_conditions_masked_by
+		(block, expr, flags + k, path, CONDITIONS_MAX_TERMS);
+
+	    if (n < 2)
+		continue;
+
+	    const int m = 2*index_of (path[0], blocks, nblocks) + k;
+	    for (int i = 1; i < n; i++)
+	    {
+		const int index = index_of (path[i], blocks, nblocks);
+		masks[m] |= gcov_type_unsigned (1) << index;
+	    }
+	}
+    }
+}
+
+int
+collect_reachable_conditionals (
+    basic_block pre,
+    basic_block post,
+    basic_block *out,
+    int maxsize,
+    sbitmap expr)
+{
+    gcc_assert (maxsize > 0);
+
+    basic_block loop = pre->loop_father->header;
+    int n = 0;
+    out[n++] = pre;
+    bitmap_set_bit (expr, pre->index);
+
+    for (int pos = 0; pos < n; pos++)
+    {
+	basic_block block = out[pos];
+
+	for (edge e : block->succs)
+	{
+	    basic_block dest = contract_edge (e, expr)->dest;
+
+	    /* Skip loop edges, as they go outside the expression.  */
+	    if (dest == loop)
+		continue;
+	    if (dest == post)
+		continue;
+	    if (!is_conditional_p (dest))
+		continue;
+	    /* Already-seen, don't re-add.  */
+	    if (bitmap_bit_p (expr, dest->index))
+		continue;
+
+	    bitmap_set_bit (expr, dest->index);
+	    out[n++] = dest;
+	    if (n == maxsize)
+		return n;
+	}
+    }
+
+    return n;
+}
+
+int
+collect_neighbors (basic_block *blocks, int nblocks, sbitmap reachable)
+{
+    int n = 0;
+    basic_block *exits = blocks + nblocks;
+    for (int i = 0; i < nblocks; i++)
+    {
+	for (edge e : blocks[i]->succs)
+	{
+	    if (bitmap_bit_p (reachable, e->dest->index))
+		continue;
+
+	    bitmap_set_bit (reachable, e->dest->index);
+	    exits[n++] = e->dest;
+	}
+    }
+
+    return n;
+}
+
+/* Find and isolate the first expression between two dominators.
+
+   Either block of a conditional could have more decisions and loops, so
+   isolate the first decision by set-intersecting all paths from the
+   post-dominator to the entry block.
+
+   The function returns the number of blocks from n that make up the leading
+   expression in prefix order (i.e. the order expected by the instrumenting
+   code).  When this function returns 0 there are no decisions between pre and
+   post, and this segment of the CFG can safely be skipped.
+
+   The post nodes can have predecessors that do not belong to this subgraph,
+   which are skipped.  This is expected, for example when there is a
+   conditional in the else-block of a larger expression:
+
+   if (A)
+   {
+      if (B) {}
+   }
+   else
+   {
+      if (C) {} else {}
+   }
+
+             A
+          t / \ f
+           /   \
+          B     C
+         /\    / \
+        /  \  T   F
+       T    \  \ /
+        \   |   o
+         \  |  /
+          \ | /
+           \|/
+            E
+
+   Processing [B, E) which looks like:
+
+      B
+     /|
+    / |
+   T  |
+    \ /
+     E ----> o // predecessor outside [B, e)
+ */
+
+/* Do a (upwards) search for reachable nodes and mark them in the reachable
+   set, making sure not to take loop edges.  dfs_enumerate_from () won't work as
+   the filter function needs information from the edge.
+ */
+void
+find_reachable (
+    sbitmap reachable,
+    basic_block pre,
+    basic_block post,
+    basic_block *stack)
+{
+    stack[0] = pre;
+    bitmap_set_bit (reachable, pre->index);
+    bitmap_set_bit (reachable, post->index);
+    for (int n = 0; n >= 0; n--)
+    {
+	for (edge e : stack[n]->preds)
+	{
+	    if (bitmap_bit_p (reachable, e->src->index))
+		continue;
+
+	    /* Ignore any loop edges.  */
+	    if (dominated_by_p (CDI_DOMINATORS, e->src, e->dest))
+		continue;
+
+	    basic_block src = contract_edge_up (e, reachable)->src;
+	    bitmap_set_bit (reachable, src->index);
+	    stack[n++] = src;
+	}
+    }
+}
+
+int
+find_first_conditional (conds_ctx &ctx, basic_block pre, basic_block post)
+{
+    basic_block *blocks = ctx.blocks;
+    sbitmap expr = ctx.expr;
+    sbitmap reachable = ctx.reachable;
+    bitmap_clear (expr);
+    bitmap_clear (reachable);
+    blocks[0] = pre;
+
+    /* If there is a direct edge to the post dominator then this cannot only be
+       a single-term conditional *unless* it is a loop (in which case the
+       to-post edge is the loop exit edge).
+     */
+    const bool dowhile = !loop_exits_from_bb_p (pre->loop_father, pre);
+    const bool isloop = bb_loop_header_p (pre) && !dowhile;
+    if (find_edge (pre, post) && !isloop)
+	return 1;
+
+    const int nblocks = collect_reachable_conditionals
+	(pre, post, blocks, ctx.maxsize, expr);
+    if (nblocks == 1)
+	return nblocks;
+
+    bitmap_copy (reachable, expr);
+    const int nexits = collect_neighbors (blocks, nblocks, reachable);
+    if (nexits == 2)
+	return nblocks;
+
+    /* Find reachable nodes from the neighbors.  */
+    basic_block *exits = blocks + nblocks;
+    for (int i = 0; i < nexits; i++)
+    {
+	for (edge e : exits[i]->preds)
+	{
+	    if (!dominated_by_p (CDI_DOMINATORS, e->src, pre))
+		continue;
+
+	    bitmap_clear (reachable);
+	    find_reachable (reachable, e->src, pre, exits + nexits);
+	    for (edge f : exits[i]->preds)
+		bitmap_set_bit (reachable, f->src->index);
+	    bitmap_and (expr, expr, reachable);
+	}
+    }
+
+    int k = 0;
+    for (int i = 0; i < nblocks; i++)
+	if (bitmap_bit_p (expr, blocks[i]->index))
+	    blocks[k++] = blocks[i];
+    return k;
+}
+
+void
+emit_bitwise_op (edge e, tree lhs, tree op1, tree_code op, tree op2,
+		 int atomic = 0)
+{
+    tree tmp;
+    gassign *read;
+    gassign *bitw;
+    gimple  *write;
+
+    /* Insert lhs = op1 <bit-op> op2.
+
+       The operands are assumed to be local accumulators or already SSA'd (and
+       not referencing globals) and don't need atomic reads anymore.
+     */
+    tmp   = make_temp_ssa_name (gcov_type_node, NULL, "__conditions_tmp");
+    read  = gimple_build_assign (tmp, op1);
+    tmp   = make_temp_ssa_name (gcov_type_node, NULL, "__conditions_tmp");
+    bitw  = gimple_build_assign (tmp, op, gimple_assign_lhs (read), op2);
+
+    if (atomic)
+    {
+	tree relaxed = build_int_cst (integer_type_node, MEMMODEL_RELAXED);
+	tree store = builtin_decl_explicit (
+		TYPE_PRECISION (gcov_type_node) > 32
+		? BUILT_IN_ATOMIC_STORE_8
+		: BUILT_IN_ATOMIC_STORE_4);
+
+	// __atomic_store_8 (&lhs, (op1 | op2), __ATOMIC_RELAXED)
+	write = gimple_build_call
+	    (store, 3, build_addr (lhs), gimple_assign_lhs (bitw), relaxed);
+    }
+    else
+    {
+	write = gimple_build_assign (lhs, gimple_assign_lhs (bitw));
+    }
+
+    gsi_insert_on_edge (e, read);
+    gsi_insert_on_edge (e, bitw);
+    gsi_insert_on_edge (e, write);
+}
+
+/* Walk the CFG and collect conditionals.
+
+   1. Collect all nodes reachable from the root node through (contracted) paths
+      of true/false edges.
+   2. Collect the neighbors of the reachable node (set).
+   3. From every node in the neighborhood, walk the up the CFG and mark every
+      reachable node.  Only the nodes reachable from *every* node in the
+      neighborhood are a part of the first expression.
+   4. Record the expression plus the two successors of the last (highest-index)
+      node in the expression, i.e. the last term.
+   5. Repeat using the two successors as new root nodes.
+
+   It is not guaranteed to find nodes in the order of the expression, i.e. it
+   might find (a || b) && c as [a c b], so the output is sorted by
+   basic_block->index.
+
+   Steps 2 and 3 are necessary to distinguish chained conditionals from
+   multi-term conditionals, e.g. to separate
+
+       if (a)
+       {
+           if (b)
+               work ();
+       }
+       if (a && b)
+           work ();
+ */
+void
+collect_conditions (conds_ctx& ctx, basic_block entry, basic_block exit)
+{
+    basic_block pre;
+    basic_block post;
+    for (pre = entry ;; pre = post)
+    {
+	if (pre == exit)
+	    break;
+	if (bitmap_bit_p (ctx.seen, pre->index))
+	    break;
+
+	post = get_immediate_dominator (CDI_POST_DOMINATORS, pre);
+	if (!is_conditional_p (pre))
+	{
+	    for (edge e : pre->succs)
+		collect_conditions (ctx, e->dest, post);
+	    continue;
+	}
+
+	int nterms = find_first_conditional (ctx, pre, post);
+	std::sort (ctx.blocks, ctx.blocks + nterms, index_lt);
+	basic_block last = ctx.blocks[nterms - 1];
+	if (size_t (nterms) <= CONDITIONS_MAX_TERMS)
+	{
+	    for (edge e : last->succs)
+		if (e->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE))
+		    ctx.blocks[nterms++] = e->dest;
+	    ctx.commit (pre, nterms);
+	}
+	else
+	{
+	    location_t loc = gimple_location (gsi_stmt (gsi_last_bb (pre)));
+	    warning_at
+		(loc, OPT_Wcoverage_too_many_conditions,
+		 "Too many conditions (found %d); giving up coverage", nterms);
+	}
+
+	for (edge e : last->succs)
+	    collect_conditions (ctx, e->dest, post);
+    }
+}
+
+}
+
+/* Condition coverage (MC/DC)
+
+   Algorithm
+   ---------
+   Whalen, Heimdahl, De Silva in "Efficient Test Coverage Measurement for
+   MC/DC" describe an algorithm for modified condition/decision coverage based
+   on AST analysis.  This algorithm analyses the control flow graph, but the
+   accumulation and recording of (partial) results is inspired by their work.
+   The individual phases are described in more detail closer to the
+   implementation.
+
+   The CFG is broken up into segments between dominators.  This isn't strictly
+   necessary, but since boolean expressions cannot cross dominators it makes
+   for a nice way to reduce work.
+
+   The coverage only considers the positions, not the symbols, in a
+   conditional, e.g. !A || (!B && A) is a 3-term conditional even though A
+   appears twice.  Subexpressions have no effect on term ordering:
+   (a && (b || (c && d)) || e) comes out as [a b c d e].
+
+   The output for gcov is a vector of pairs of unsigned integers, interpreted
+   as bit-sets, where the bit index corresponds to the index of the condition
+   in the expression.
+
+   Implementation and interface
+   ----------------------------
+   Two public functions - find_conditions and instrument_decisions.
+
+   find_conditions outputs two arrays, blocks and sizes.  The sizes describes
+   the ranges of blocks that make up every conditional, in a [first, last)
+   fashion, i.e. begin = blocks[sizes[n]], end = blocks[sizes[n+1]] for
+   expression n.  The function returns the number of expressions.
+
+   The coverage is designed to get most of its memory needs met by the caller,
+   and heavily uses the end of the blocks array for buffer.  This is both for
+   performance (no resizes, amortized cost of allocation) and
+   ease-of-implementation.  This makes the caller responsible for allocating
+   large enough arrays.
+
+   blocks:
+    Every permanent conditions add 2 blocks (the true & false dest blocks), and
+    assuming a worst case of one-block-per-expr just storing the output needs
+    3*n_basic_blocks_for_fn ().  Additionally, the searches might need to buffer
+    the full graph between entry and exit [1]. In total that means
+    5*n_basic_blocks_for_fn () should should be plenty, and the savings for
+    reducing this number is probably not worth the risk.
+   sizes:
+    sizes gets one entry per expression plus initial, so
+    1+n_basic_blocks_for_fn () is sufficient.
+
+   instrument_decisions uses the information provided by find_conditions to
+   inject code onto edges in the CFG.  Every instrumented function gets local
+   accumulators zero'd on function entry, which on function exit are flushed to
+   the global accumulators (created by coverage_counter_alloc ()).
+
+   [1] In truth, the set of nodes that could be buffered gets smaller as the
+       algorithm walks the CFG, but assuming just using node-count comes at
+       little run-time cost and is guaranteed to be sufficient.
+ */
+int
+find_conditions (
+    basic_block entry,
+    basic_block exit,
+    basic_block *blocks,
+    int *sizes,
+    int maxsize)
+{
+    record_loop_exits ();
+    bool free_dom = false;
+    bool free_post_dom = false;
+    if (!dom_info_available_p (CDI_POST_DOMINATORS))
+    {
+	calculate_dominance_info (CDI_POST_DOMINATORS);
+	free_post_dom = true;
+    }
+
+    if (!dom_info_available_p (CDI_DOMINATORS))
+    {
+	calculate_dominance_info (CDI_DOMINATORS);
+	free_dom = true;
+    }
+
+    conds_ctx ctx (maxsize);
+    ctx.blocks = blocks;
+    ctx.sizes = sizes + 1;
+    ctx.maxsize = maxsize;
+    sizes[0] = sizes[1] = 0;
+    collect_conditions (ctx, entry, exit);
+
+    /* Partial sum.  */
+    for (int i = 0; i < ctx.exprs; ++i)
+	sizes[i + 1] += sizes[i];
+
+    if (free_post_dom)
+	free_dominance_info (CDI_POST_DOMINATORS);
+    if (free_dom)
+	free_dominance_info (CDI_DOMINATORS);
+
+    return ctx.exprs;
+}
+
+int instrument_decisions (basic_block *blocks, int nblocks, int condno)
+{
+    /* Insert function-local accumulators per decision.  */
+    tree accu[2] = {
+	build_decl
+	    (UNKNOWN_LOCATION, VAR_DECL,
+	     get_identifier ("__conditions_accu_true"), gcov_type_node),
+	build_decl
+	    (UNKNOWN_LOCATION, VAR_DECL,
+	     get_identifier ("__conditions_accu_false"), gcov_type_node),
+    };
+    for (tree acc : accu)
+    {
+	tree zero = build_int_cst (gcov_type_node, 0);
+	for (edge e : ENTRY_BLOCK_PTR_FOR_FN (cfun)->succs)
+	    gsi_insert_on_edge (e, gimple_build_assign (acc, zero));
+    }
+
+    auto_vec<gcov_type_unsigned, 32> masks (nblocks * 2);
+    masks.quick_grow_cleared (nblocks * 2);
+    find_subexpr_masks (blocks, nblocks, masks.address ());
+
+    /* The true/false target blocks are included in the nblocks set, but
+       their outgoing edges should not be instrumented.
+     */
+    gcc_assert (nblocks > 2);
+
+    /* Add instructions for updating the function-local accumulators.  */
+    for (int i = 0; i < nblocks - 2; i++)
+    {
+	for (edge e : blocks[i]->succs)
+	{
+	    if (!(e->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE)))
+		continue;
+
+	    /* accu |= expr[i] */
+	    const int t = !!(e->flags & EDGE_FALSE_VALUE);
+	    tree rhs = build_int_cst (gcov_type_node, 1ULL << i);
+	    emit_bitwise_op (e, accu[t], accu[t], BIT_IOR_EXPR, rhs);
+
+	    if (masks[2*i + t] == 0)
+		continue;
+
+	    /* accu &= mask[i] */
+	    tree mask = build_int_cst (gcov_type_node, ~masks[2*i + t]);
+	    for (int j = 0; j < 2; j++)
+		emit_bitwise_op (e, accu[j], accu[j], BIT_AND_EXPR, mask);
+	}
+    }
+
+    /* Add instructions for updating the global accumulators.  */
+    basic_block exit = EXIT_BLOCK_PTR_FOR_FN (cfun);
+    for (edge e : exit->preds)
+    {
+	for (int k = 0; k < 2; k++)
+	{
+	    tree ref = tree_coverage_counter_ref
+		(GCOV_COUNTER_CONDS, 2*condno + k);
+
+	    tree tmp = make_temp_ssa_name
+		(gcov_type_node, NULL, "__conditions_tmp");
+
+	    const bool atomic = flag_profile_update == PROFILE_UPDATE_ATOMIC;
+	    if (atomic)
+	    {
+		tree relaxed = build_int_cst
+		    (integer_type_node, MEMMODEL_RELAXED);
+		tree atomic_load = builtin_decl_explicit
+		    (TYPE_PRECISION (gcov_type_node) > 32
+		     ? BUILT_IN_ATOMIC_LOAD_8
+		     : BUILT_IN_ATOMIC_LOAD_4);
+
+		tree lhs_type = TREE_TYPE (TREE_TYPE (atomic_load));
+		tree lhs = make_temp_ssa_name
+		    (lhs_type, NULL, "__conditions_load_lhs");
+
+		gcall *load = gimple_build_call
+		    (atomic_load, 2, build_addr (ref), relaxed);
+		gimple_set_lhs (load, lhs);
+		gassign *assign = gimple_build_assign
+		    (tmp, NOP_EXPR, gimple_call_lhs (load));
+
+		gsi_insert_on_edge (e, load);
+		gsi_insert_on_edge (e, assign);
+	    }
+	    else
+	    {
+		gassign *read = gimple_build_assign (tmp, ref);
+		tmp = gimple_assign_lhs (read);
+		gsi_insert_on_edge (e, read);
+	    }
+
+	    ref = unshare_expr (ref);
+	    emit_bitwise_op (e, ref, accu[k], BIT_IOR_EXPR, tmp, atomic);
+	}
+    }
+
+    return nblocks - 2;
+}
+
+#undef CONDITIONS_MAX_TERMS
+
 /* Do initialization work for the edge profiler.  */
 
 /* Add code:
@@ -758,7 +1635,7 @@ tree_profiling (void)
 	  thunk = true;
 	  /* When generate profile, expand thunk to gimple so it can be
 	     instrumented same way as other functions.  */
-	  if (profile_arc_flag)
+	  if (profile_arc_flag || profile_condition_flag)
 	    expand_thunk (node, false, true);
 	  /* Read cgraph profile but keep function as thunk at profile-use
 	     time.  */
@@ -803,7 +1680,7 @@ tree_profiling (void)
   release_profile_file_filtering ();
 
   /* Drop pure/const flags from instrumented functions.  */
-  if (profile_arc_flag || flag_test_coverage)
+  if (profile_arc_flag || profile_condition_flag || flag_test_coverage)
     FOR_EACH_DEFINED_FUNCTION (node)
       {
 	if (!gimple_has_body_p (node->decl)
@@ -897,7 +1774,7 @@ pass_ipa_tree_profile::gate (function *)
      disabled.  */
   return (!in_lto_p && !flag_auto_profile
 	  && (flag_branch_probabilities || flag_test_coverage
-	      || profile_arc_flag));
+	      || profile_arc_flag || profile_condition_flag));
 }
 
 } // anon namespace
-- 
2.20.1


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-03-25 19:44   ` Jørgen Kvalsvik
@ 2022-03-28 13:39     ` Martin Liška
  2022-03-28 13:52     ` Jørgen Kvalsvik
  1 sibling, 0 replies; 39+ messages in thread
From: Martin Liška @ 2022-03-28 13:39 UTC (permalink / raw)
  To: Jørgen Kvalsvik, gcc-patches

On 3/25/22 20:44, Jørgen Kvalsvik wrote:
> Hello, and thanks for the review!
> 
>> 1) Do I correctly understand that __conditions_accu_true/false track
>> every short-circuit sub-expression of a condition and record
>> if a given sub-expr is seen to be true or false?
> 
> Sort-of. It is not really aware of sub-expressions at all, but tracks every
> boolean condition/term in the full expression, mapped to a bitvector. I usually
> find it easier to understand visually:
> 
> if (a || (b && c) || d)
> 
> terms | a b c d
> ------|--------
> true  | 0 0 0 0
> false | 0 0 0 0
> 
> Whenever a = true, true becomes 1000 and false remains 0000. a = true would
> short-circuit, (implemented as a normal edge to the "exit" node of the
> expression) leaving bcd unevaluated. Coverage is then determined as the number
> of unset bits in this vector. The accumulators are zero'd on every function
> entry, and |= into the global "counter" on function exit.
> 
>> 2) As mentioned these are bit sets, can you please explain why you (&
>> -value) from these sets?
> 
> Yes, excellent question. This should answer 3) too, and is the most surprsing
> thing when unfamiliar with MC/DC.
> 
> For modified condition/decision coverage we need to prove that a condition's
> value independently affects the outcome/decision. In other words, would
> flipping a condition change the outcome?
> 
> Let's take your if (a || b) check, which says that 1/4 conditions are covered
> on (0, 1). If a = 1, the expression is always true, and b is never evaluated.
> In fact, the value of b does not change the outcome at all, so intuitively only
> 1/4 of the conditions are covered.

Hello.

> 
> On (0, 1) b gets evaluated, so in a way we know that a = 0. However, the value
> of a will no longer affect the outcome because (* || 1) is always true. In a
> way, you can think of it as reverse short-circuiting, the same thing would
> happen on (* && 0). This is what Wahlen, Heimdahl, and De Silva calls masking.
> What I do is figure out when masking must happen by analyzing the CFG and zero
> out the bits that are masked (i.e. no longer affect the outcome). This is in
> practice what happens to the bits when regular short circuiting happen.

Oh, now I've got it. It makes sense, what a clever idea.

> 
> So while (0, 1) "sees" the condition a = false, it cannot prove that it
> mattered based on your inputs. In general, you need N+1 test cases to cover N
> conditionals with MC/DC. As a consequence, the only way of covering a = false
> is (0, 0), which alone would be 2/4 cases covered.
> 
> I hope this made it clearer, if not I can write a more elaborate explanation
> with more examples.
> 
>> 4) I noticed various CFG patterns in tree-profile.cc which are handled. Can
>> you please explain how the algorithm works even without knowing the original
>> paper?
> 
> My paper is not written yet, but I will describe the high-level algorithm (+
> extracts from source comments) at the end of this email, as it is a bit long.
> 
>> 5) I noticed the following ICE:
> Yes, this is an unfortunate flaw - I piggyback on the setup done by branch
> coverage, which means I silently assume --coverage is used. I was unsure what
> to do (and meant to ask, sorry!) as it is a larger decision - should
> condition-coverage also imply branch coverage? Should condition coverage work
> on its own? Other options?

Yes, I think the option (-fprofile-conditions) should imply -fprofile-arcs
(or maybe even --coverage which is -fprofile-arcs + -ftest-coverage).

> 
> I'm happy to implement either strategy - please advise on what you would
> prefer.
> 
> 
>> 6) Please follow the GNU coding style, most notable we replace 8 spaces with
>> a tab. And we break line before {, (, ... Remove noexcept specifiers for
>> functions and I think most of the newly added functions can be static. And
>> each newly added function should have a comment.
> 
> Sure thing, I'll add some tabs. All the helper functions are already static (in
> an unnamed namespace), but I can remove the namespace and/or add static.

Oh, you use an anonymous namespace, then I'm fine with any approach you prefer.

> 
>> 7) Please consider supporting of -profile-update=atomic where you'll need to
>> utilize an atomic builts (see how other instrumentation looks like with
>> it).
> Will do! I always intended to support the atomics, not having them there is an
> oversight. To confirm, I will only need to use atomics for the global counters,
> right?

Yes.

> The per-call accumulators are local to each activation record and should
> not need the atomics, or am I missing something?

Correct.

About the algorithm itself. I must confess it's quite hard for me to fully understand
it and it can be made easier by providing an article you're planning to write.

Anyway, I guess some more experiences guys like Richi can help with the CFG tree decomposition.

Note I noticed an ICE for a simple test-case:

$ cat demangle.i
int
cplus_demangle_mangled_name() {
   while (1)
     ;
}

$ /dev/shm/objdir/gcc/xgcc -B/dev/shm/objdir/gcc/ --coverage -fprofile-conditions demangle.i -O2 -c
xgcc: internal compiler error: Segmentation fault signal terminated program cc1
Please submit a full bug report, with preprocessed source (by using -freport-bug).
See <https://gcc.gnu.org/bugs/> for instructions.

It's a stack overflow in a recursive function.

Anyway, please fix this and I'll carry on with testing.

Cheers,
Martin

> 
> ALGORITHM
> 
> Phase 1: expression isolation from CFG analysis
> 
> Break the CFG into smaller pieces ("sections") by splitting it on dominators.
> No expression can cross a dominator, so becomes a natural way of terminating
> expression searches.
> 
> For each section, do a BFS from the root node and collect all reachable nodes
> following edges that point to "conditional" nodes, i.e. nodes with outgoing
> true&false edges. Series of single-parent single-exit nodes are contracted.
> This gives a good estimate for an expression, but might include conditions in
> the if/else blocks.
> 
> The algorithm then collects the immediate neighborhood, i.e. all nodes adjacent
> to the collected set, dropping everything in the set itself. Then, a new BFS is
> performed, but now "upwards" (i.e. following predecessors), skipping any
> neighbor not dominated by the root (this eliminates loops and other expressions
> with the same "exit" block, see BFS-up). The intersection of all the paths
> found with this BFS is the isolated expression.
> 
> If the root is not a conditional, it is skipped and it's successor
> becomes the new root. If an expression is found it has a pair of "expression
> sucessors", and the algorithm recurses into both, evaluating this sub-section
> in isolation, i.e.
> 
> if (a || b) {
> root:
>      if (c && d) {
>          ...
>      }
>      ...
> sink:
> }
> 
> The sections between the root and sink labels are evaulated as if the outer
> expression did not exist.
> 
>  From the comments:
> 
>      1. Collect all nodes reachable from the root node through (contracted) paths
>         of true/false edges.
>      2. Collect the neighbors of the reachable node (set).
>      3. From every node in the neighborhood, walk the up the CFG and mark every
>         reachable node. Only the nodes reachable from *every* node in the
>         neighborhood are a part of the first expression.
>      4. Record the expression plus the two successors of the last (highest-index)
>         node in the expression, i.e. the last term.
>      5. Repeat using the two successors as new root nodes.
> 
>      It is not guaranteed to find nodes in the order of the expression, i.e. it
>      might find (a || b) && c as [a c b], so the output is sorted by
>      basic_block->index.
> 
>      Steps 2 and 3 are necessary to distinguish chained conditionals from
>      multi-term conditionals, e.g. to separate
> 
>          if (a)
>          {
>              if (b)
>                  work ();
>          }
>          if (a && b)
>              work ();
> 
> 
> On BFS-up:
> 
>      Either block of a conditional could have more decisions and loops, so
>      isolate the first decision by set-intersecting all paths from the
>      post-dominator to the entry block.
> 
>      The function returns the number of blocks from n that make up the leading
>      expression in prefix order (i.e. the order expected by the instrumenting
>      code).  When this function returns 0 there are no decisions between pre and
>      post, and this segment of the CFG can safely be skipped.
> 
>      The post nodes can have predecessors that do not belong to this subgraph,
>      which are skipped.  This is expected, for example when there is a
>      conditional in the else-block of a larger expression:
> 
>      if (A)
>      {
>         if (B) {}
>      } else {
>         if (C) {} else {}
>      }
> 
>               A
>            t / \ f
>             /   \
>            B     C
>           /\    / \
>          /  \  T   F
>         T    \  \ /
>          \   |   o
>           \  |  /
>            \ | /
>             \|/
>              E
> 
>      Processing [B, E) which looks like:
> 
>         B
>        /|
>       / |
>      T  |
>       \ /
>        E ----> o // predecessor outside [B, e)
> 
> 
> Phase 2: Masking vector and instrumentation
> 
> The input to this phase is the basic blocks that make up every expression, plus
> its two exit nodes, i.e. the 3-term expression (a || b || c) yields the input
> [a b c T F] to phase 2.
> 
> For every expression, allocate two local accumulators (bit-vectors), one for
> the true-vector, one for the false-vector. These are zero'd on function entry
> and flushed on function exit.
> 
> For each "conditional" block (a b c), unconditionally set the corresponding bit
> when an edge in the CFG is taken. Rough pseudo-gimple illustration:
> 
> if (a) {
>      accu_true |= 1 << 0
>      goto done;
> } else {
>      accu_false |= 1 << 0
>      if (b) {
>          accu_true |= 1 << 1;
>          goto done;
>      } else {
>          accu_false |= 1 << 1;
>          ...
>      }
> }
> 
> global_true[expr_n]  |= accu_true
> global_false[expr_n] |= accu_false
> 
> This alone would more-or-less be branch coverage, but would not prove that
> terms would independently decide the outcome. To find masking, the expression
> is walked in search of triangles. A || B as a decision diagram becomes
> 
>     A
>    t|\f
>     | \
>     |  B
>     |t/ \f
>     |/   \
>     T     F
> 
> and we search for triangles like ATB, that is nodes with 2 predecessors with
> the same boolean value, where the two predecessors are connected with the
> opposite value. This marks a candidate for masking, and the terms masked are
> determined by chasing predecessors starting at the top of the triangle for as
> long as the chain of predecessors have the same truth value as the "exit"
> edges. This set is often empty.
> 
> The edge that triggers masking is then instrumented with a accu &= mask which
> wipes out anything that does not independently affect the outcome. Note that
> nothing is ever wiped from the global accumulator, only the local. The (a || b)
> example had a false vector of [1, 0] at some point, but it was wiped by the
> masking operation (b = 1 is the only masking value for a || b).
> 
>  From the comments:
> 
>      Scan the blocks that make up an expression and look for conditions that
>      would mask other conditions.  For a deeper discussion on masking, see
>      Whalen, Heimdahl, De Silva "Efficient Test Coverage Measurement for MC/DC".
>      Masking is best illustrated with an example:
> 
>      A || B.  If B is true then A will does not independently affect the
>      decision.  In a way, this is "reverse" short circuiting, and always work on
>      the right-hand side of expressions.  * || true is always true and * &&
>      false is always false - the left-hand-side does not affect the outcome, and
>      their values should not contribute to modified condition/decision coverage.
> 
>      A || B interpreted as a decision diagram becomes:
> 
>       A
>      t|\f
>       | \
>       |  B
>       |t/ \f
>       |/   \
>       T     F
> 
>      The algorithm looks for triangles like ATB.  Masking right-hand sides happen
>      when a block has a pair of incoming edges of the same boolean value, and
>      there is an edge connecting the two predecessors with the *opposite* boolean
>      value.  The masking block is B, and the masked block is A.  In this
>      particular example:
> 
>      Masking can affect "outside" its own subexpression; in A || (B && C) if C is
>      false, B is masked.  However, if (B && C) is true, A gets masked.  B && C
>      can be determined from evaluating C since !B would short-circuit, so a true
>      C would mask A.
> 
>       A
>      t|\f
>       | \
>       |  \
>       |   \
>       |    B
>       |  t/ \f
>       |  C   |
>       |t/ \f |
>       |/   \ |
>       T     F
> 
>      Notice how BFC forms a triangle.  Expressions masked by an edge are
>      determined by:
>      * Go to the predecessor with truth value b (if it exists).
>      * Follow the path of ancestors with taking only !b edges, recording every
>        node from here on.
> 
>      For the case where C can mask A, the path goes C [true]-> B -> [false] A, so
>      C [true] masks A.
> 
>      The mask is output as a bit-set stored in a gcov_type_unsigned.  The
>      bit-sets are output in pairs, one for each decision (the outcome of the
>      boolean expression, or which arc to take in the CFG).
> 
> Phase 3: reading the report
> 
> This is pretty standard gcov stuff, except the "counters" are interpreted as
> bitsets, and the number of terms in the expressions is stored. Coverage then
> becomes popcount / expected.
> 
> Final remarks:
> 
> Granted, this is a bit dense, but I think the overall approach is easy to grasp
> when supported by a lot of example graphs and stepping. I will try to type up a
> bunch with the paper and publish, but for now all I have is the code (which I
> hope for the most part is easy to follow). I'll update this with style updates
> and atomics support, but I would prefer a maintainer to make the call if this
> condition coverage should imply branch coverage, or if it should work without it.
> 
> Thanks,
> Jørgen
> 
> On 24/03/2022 17:08, Martin Liška via Gcc-patches wrote:
>> On 3/21/22 12:55, Jørgen Kvalsvik via Gcc-patches wrote:
>>
>> Hello.
>>
>> Thanks for very interesting extension of the existing GCOV profiling.
>>
>> I briefly read the patch and investigated what happens and I've got various
>> questions/comments about it:
>>
>> 1) Do I correctly understand that __conditions_accu_true/false track
>> every short-circuit sub-expression of a condition and record
>> if a given sub-expr is seen to be true or false?
>>
>> 2) As mentioned these are bit sets, can you please explain why you (& -value)
>> from these sets?
>>
>> 3) I noticed a false report for a simple test-case:
>>
>> $ cat cond.c
>> int x;
>>
>> void foo (int a, int b)
>> {
>>       if (a || b)
>>         x = 1;
>>       else
>>         x = 2;
>> }
>>
>> int main()
>> {
>>     foo (0, 1);
>>     return 0;
>> }
>>
>> $ rm *gcda ; gcc --coverage cond.c -fprofile-conditions && ./a.out && gcov -g -t a-cond.c
>>           -:    0:Source:cond.c
>>           -:    0:Graph:a-cond.gcno
>>           -:    0:Data:a-cond.gcda
>>           -:    0:Runs:1
>>           -:    1:int x;
>>           -:    2:
>>           1:    3:void foo (int a, int b)
>>           -:    4:{
>>           1:    5:    if (a || b)
>> conditions covered 1/4
>> condition  0 not covered (true)
>> condition  0 not covered (false) <-- this was executed if I'm correct
>> condition  1 not covered (false)
>>           1:    6:      x = 1;
>>           -:    7:    else
>>       #####:    8:      x = 2;
>>           1:    9:}
>>           -:   10:
>>           1:   11:int main()
>>           -:   12:{
>>           1:   13:  foo (0, 1);
>>           1:   14:  return 0;
>>           -:   15:}
>>
>> 4) I noticed various CFG patterns in tree-profile.cc which are handled. Can you please
>> explain how the algorithm works even without knowing the original paper?
>>
>> 5) I noticed the following ICE:
>>
>> $ gcc cond.c -fprofile-conditions -fprofile-generate
>> during IPA pass: profile
>> cond.c: In function ?foo?:
>> cond.c:15:1: internal compiler error: Segmentation fault
>>      15 | }
>>         | ^
>> 0xf4d64a crash_signal
>> 	/home/marxin/Programming/gcc/gcc/toplev.cc:322
>> 0x7ffff78b93cf ???
>> 	/usr/src/debug/glibc-2.35-2.1.x86_64/signal/../sysdeps/unix/sysv/linux/x86_64/libc_sigaction.c:0
>> 0x7ffff78f29ed __GI__IO_ftell
>> 	/usr/src/debug/glibc-2.35-2.1.x86_64/libio/ioftell.c:37
>> 0xa9dfda gcov_position
>> 	/home/marxin/Programming/gcc/gcc/gcov-io.cc:48
>> 0xa9dfda gcov_write_tag(unsigned int)
>> 	/home/marxin/Programming/gcc/gcc/gcov-io.cc:321
>> 0xe9734a branch_prob(bool)
>> 	/home/marxin/Programming/gcc/gcc/profile.cc:1542
>> 0x1032e08 tree_profiling
>> 	/home/marxin/Programming/gcc/gcc/tree-profile.cc:1620
>> 0x1032e08 execute
>> 	/home/marxin/Programming/gcc/gcc/tree-profile.cc:1726
>> Please submit a full bug report, with preprocessed source (by using -freport-bug).
>> Please include the complete backtrace with any bug report.
>> See <https://gcc.gnu.org/bugs/> for instructions.
>>
>> 6) Please follow the GNU coding style, most notable we replace 8 spaces with a tab.
>> And we break line before {, (, ... Remove noexcept specifiers for functions and I think
>> most of the newly added functions can be static. And each newly added function should
>> have a comment.
>>
>> 7) Please consider supporting of -profile-update=atomic where you'll need to utilize
>> an atomic builts (see how other instrumentation looks like with it).
>>
>> That's the very first round of the review. Hope it's helpful.
>>
>> Cheers,
>> Martin
>>
>>> This patch adds support in gcc+gcov for modified condition/decision
>>> coverage (MC/DC) with the -fprofile-conditions flag. MC/DC is a type of
>>> test/code coverage, and it is particularly important in the avation and
>>> automotive industries for safety-critical applications. In particular,
>>> it is required or recommended by:
>>>
>>>       * DO-178C for the most critical software (Level A) in avionics
>>>       * IEC 61508 for SIL 4
>>>       * ISO 26262-6 for ASIL D
>>>
>>>   From the SQLite webpage:
>>>
>>>       Two methods of measuring test coverage were described above:
>>>       "statement" and "branch" coverage. There are many other test
>>>       coverage metrics besides these two. Another popular metric is
>>>       "Modified Condition/Decision Coverage" or MC/DC. Wikipedia defines
>>>       MC/DC as follows:
>>>
>>>           * Each decision tries every possible outcome.
>>>           * Each condition in a decision takes on every possible outcome.
>>>           * Each entry and exit point is invoked.
>>>           * Each condition in a decision is shown to independently affect
>>>             the outcome of the decision.
>>>
>>>       In the C programming language where && and || are "short-circuit"
>>>       operators, MC/DC and branch coverage are very nearly the same thing.
>>>       The primary difference is in boolean vector tests. One can test for
>>>       any of several bits in bit-vector and still obtain 100% branch test
>>>       coverage even though the second element of MC/DC - the requirement
>>>       that each condition in a decision take on every possible outcome -
>>>       might not be satisfied.
>>>
>>>       https://sqlite.org/testing.html#mcdc
>>>
>>> Wahlen, Heimdahl, and De Silva "Efficient Test Coverage Measurement for
>>> MC/DC" describes an algorithm for adding instrumentation by carrying
>>> over information from the AST, but my algorithm does analysis on the
>>> control flow graph. This should make it work for any language gcc
>>> supports, although I have only tested it on constructs in C and C++, see
>>> testsuite/gcc.misc-tests and testsuite/g++.dg.
>>>
>>> Like Wahlen et al this implementation uses bitsets to store conditions,
>>> which gcov later interprets. This is very fast, but introduces an max
>>> limit for the number of terms in a single boolean expression. This limit
>>> is the number of bits in a gcov_unsigned_type (which is typedef'd to
>>> uint64_t), so for most practical purposes this would be acceptable.
>>> limitation can be relaxed with a more sophisticated way of storing and
>>> updating bitsets (for example length-encoding).
>>>
>>> In action it looks pretty similar to the branch coverage. The -g short
>>> opt carries no significance, but was chosen because it was an available
>>> option with the upper-case free too.
>>>
>>> gcov --conditions:
>>>
>>>           3:   17:void fn (int a, int b, int c, int d) {
>>>           3:   18:    if ((a && (b || c)) && d)
>>> conditions covered 5/8
>>> condition  1 not covered (false)
>>> condition  2 not covered (true)
>>> condition  2 not covered (false)
>>>           1:   19:        x = 1;
>>>           -:   20:    else
>>>           2:   21:        x = 2;
>>>           3:   22:}
>>>
>>> gcov --conditions --json-format:
>>>
>>>       "conditions": [
>>>           {
>>>               "not_covered_false": [
>>>                   1,
>>>                   2
>>>               ],
>>>               "count": 8,
>>>               "covered": 5,
>>>               "not_covered_true": [
>>>                   2
>>>               ]
>>>           }
>>>       ],
>>>
>>> C++ destructors will add extra conditionals that are not explicitly
>>> present in source code. These are present in the CFG, which means the
>>> condition coverage will show them.
>>>
>>> gcov --conditions
>>>
>>>           -:    5:struct A {
>>>           1:    6:    explicit A (int x) : v (x) {}
>>>           1:    7:    operator bool () const { return bool(v); }
>>>           1:    8:    ~A() {}
>>>           -:    9:
>>>           -:   10:    int v;
>>>           -:   11:};
>>>           -:   12:
>>>           2:   13:void fn (int a, int b) {
>>>          2*:   14:    x = a && A(b);
>>> conditions covered 2/4
>>> condition  0 not covered (true)
>>> condition  1 not covered (true)
>>> conditions covered 2/2
>>>
>>> They are also reported by the branch coverage.
>>>
>>> gcov -bc
>>>
>>>          2*:   14:    x = a && A(b);
>>> branch  0 taken 1 (fallthrough)
>>> branch  1 taken 1
>>> call    2 returned 1
>>> call    3 returned 1
>>> branch  4 taken 0 (fallthrough)
>>> branch  5 taken 1
>>> branch  6 taken 1 (fallthrough)
>>> branch  7 taken 1
>>>
>>> The algorithm struggles in a particular case where gcc would generate
>>> identical CFGs for different expressions:
>>>
>>> and.c:
>>>
>>>       if (a && b && c)
>>>           x = 1;
>>>
>>> ifs.c:
>>>
>>>       if (a)
>>>           if (b)
>>>               if (c)
>>>                   x = 1;
>>>
>>>       if (a && b && c)
>>>           x = 1;
>>>
>>> and.c.gcov:
>>>
>>>               #####:    2:    if (a && b && c)
>>> conditions covered 2/2
>>> conditions covered 2/2
>>> conditions covered 2/2
>>>       #####:    3:        x = 1;
>>>
>>> ifs.c.gcov:
>>>       #####:    2:    if (a)
>>> conditions covered 2/2
>>>       #####:    3:   2    if (b)
>>> conditions covered 2/2
>>>       #####:    4:   2        if (c)
>>> conditions covered 2/2
>>>       #####:    5:                x = 1;
>>>
>>> These programs are semantically equivalent, and are interpreted as 3
>>> separate conditional expressions. It does not matter w.r.t. coverage,
>>> but is not ideal. Adding an else to and.c fixes the issue:
>>>
>>>       #####:    2:    if (a && b && c)
>>> conditions covered 6/6
>>>       #####:    3:        x = 1;
>>>       #####:    4:    else x = x;
>>>
>>> In the (a && b && c) case the else block must be shared between all
>>> three conditionals, and for if (a) if (b) if (c) there would be three
>>> *separate* else blocks.
>>>
>>> Since the algorithm works on CFGs, it cannot detect conditionals (from
>>> ternaries) that don't get an entry in the cfg. For example,
>>> int x = a ? 0 : 1 in gimple becomes _x = (_a == 0). From source you
>>> would expect coverage, but it gets neither branch nor condition
>>> coverage. For completeness, it could be achieved by scanning all gimple
>>> statements for comparisons, and insert an extra instruction for
>>> recording the outcome.
>>>
>>> The test suite consists of all different CFG kinds and control flow
>>> structures I could find, but there is certainly room for many more.
>>>
>>> Alternative email: J?rgen Kvalsvik <j@lambda.is>
>>>
>>> --
>>>
>>> Some final remarks (not a part of the commit message), this is written in a C++ style that I felt meshed well with what was already there, but if the maintainers would prefer a different approach then I'm happy to to make adjustments. I plan to write a more thorough description of the algorithm, as well as adding the same feature to clang as it is very useful for us at Woven Planet.
>>>
>>> I tried to pick flag names and options that were comfortable and descriptive, but if you have better names or different ideas for flags then I am happy to change them. The --coverage flag was not changed to imply -fprofile-conditions, but it is possibly something to consider.
>>>
>>> gcc/ChangeLog:
>>>
>>> 	* common.opt: Add new options -fprofile-conditions and
>>> 	-Wcoverage-too-many-conditions.
>>> 	* doc/gcov.texi: Add --conditions documentation.
>>> 	* doc/invoke.texi: Add -fprofile-conditions documentation.
>>> 	* gcov-counter.def (GCOV_COUNTER_CONDS): New.
>>> 	* gcov-io.h (GCOV_TAG_CONDS): New.
>>> 	(GCOV_TAG_CONDS_LENGTH): Likewise.
>>> 	(GCOV_TAG_CONDS_NUM): Likewise.
>>> 	* gcov.cc (class condition_info): New.
>>> 	(condition_info::condition_info): New.
>>> 	(condition_info::popcount): New.
>>> 	(struct coverage_info): New.
>>> 	(add_condition_counts): New.
>>> 	(output_conditions): New.
>>> 	(print_usage): Add -g, --conditions.
>>> 	(process_args): Likewise.
>>> 	(output_intermediate_json_line): Output conditions.
>>> 	(read_graph_file): Read conditions counters.
>>> 	(read_count_file): Read conditions counters.
>>> 	(file_summary): Print conditions.
>>> 	(accumulate_line_info): Accumulate conditions.
>>> 	(output_line_details): Print conditions.
>>> 	* profile.cc (find_conditions): New declaration.
>>> 	(instrument_decisions): New declaration.
>>> 	(branch_prob): Call find_conditions, instrument_decisions.
>>> 	(init_branch_prob): Add total_num_conds.
>>> 	(end_branch_prob): Likewise.
>>> 	* tree-profile.cc (INCLUDE_ALGORITHM): Define.
>>> 	(struct conds_ctx): New.
>>> 	(CONDITIONS_MAX_TERMS): New.
>>> 	(index_of): New.
>>> 	(index_lt): New.
>>> 	(single): New.
>>> 	(single_edge): New.
>>> 	(contract_edge): New.
>>> 	(contract_edge_up): New.
>>> 	(is_conditional_p): New.
>>> 	(collect_neighbors): New.
>>> 	(find_first_conditional): New.
>>> 	(emit_bitwise_op): New.
>>> 	(collect_conditions): New.
>>> 	(find_conditions): New.
>>> 	(instrument_decisions): New.
>>>
>>> gcc/testsuite/ChangeLog:
>>>
>>> 	* lib/gcov.exp:
>>> 	* g++.dg/gcov/gcov-18.C: New test.
>>> 	* gcc.misc-tests/gcov-19.c: New test.
>>> ---
>>>    gcc/common.opt                         |   8 +
>>>    gcc/doc/gcov.texi                      |  36 ++
>>>    gcc/doc/invoke.texi                    |  19 +
>>>    gcc/gcov-counter.def                   |   3 +
>>>    gcc/gcov-io.h                          |   3 +
>>>    gcc/gcov.cc                            | 177 +++++-
>>>    gcc/profile.cc                         |  51 +-
>>>    gcc/testsuite/g++.dg/gcov/gcov-18.C    | 209 ++++++
>>>    gcc/testsuite/gcc.misc-tests/gcov-19.c | 726 +++++++++++++++++++++
>>>    gcc/testsuite/lib/gcov.exp             | 187 +++++-
>>>    gcc/tree-profile.cc                    | 838 +++++++++++++++++++++++++
>>>    11 files changed, 2248 insertions(+), 9 deletions(-)
>>>    create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-18.C
>>>    create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-19.c
> 


^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH] Add condition coverage profiling
  2022-03-24 16:08 ` Martin Liška
@ 2022-03-25 19:44   ` Jørgen Kvalsvik
  2022-03-28 13:39     ` Martin Liška
  2022-03-28 13:52     ` Jørgen Kvalsvik
  0 siblings, 2 replies; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-03-25 19:44 UTC (permalink / raw)
  To: gcc-patches

Hello, and thanks for the review!

> 1) Do I correctly understand that __conditions_accu_true/false track
> every short-circuit sub-expression of a condition and record
> if a given sub-expr is seen to be true or false?

Sort-of. It is not really aware of sub-expressions at all, but tracks every
boolean condition/term in the full expression, mapped to a bitvector. I usually
find it easier to understand visually:

if (a || (b && c) || d)

terms | a b c d
------|--------
true  | 0 0 0 0
false | 0 0 0 0

Whenever a = true, true becomes 1000 and false remains 0000. a = true would
short-circuit, (implemented as a normal edge to the "exit" node of the
expression) leaving bcd unevaluated. Coverage is then determined as the number
of unset bits in this vector. The accumulators are zero'd on every function
entry, and |= into the global "counter" on function exit.

> 2) As mentioned these are bit sets, can you please explain why you (&
> -value) from these sets?

Yes, excellent question. This should answer 3) too, and is the most surprsing
thing when unfamiliar with MC/DC.

For modified condition/decision coverage we need to prove that a condition's
value independently affects the outcome/decision. In other words, would
flipping a condition change the outcome?

Let's take your if (a || b) check, which says that 1/4 conditions are covered
on (0, 1). If a = 1, the expression is always true, and b is never evaluated.
In fact, the value of b does not change the outcome at all, so intuitively only
1/4 of the conditions are covered.

On (0, 1) b gets evaluated, so in a way we know that a = 0. However, the value
of a will no longer affect the outcome because (* || 1) is always true. In a
way, you can think of it as reverse short-circuiting, the same thing would
happen on (* && 0). This is what Wahlen, Heimdahl, and De Silva calls masking.
What I do is figure out when masking must happen by analyzing the CFG and zero
out the bits that are masked (i.e. no longer affect the outcome). This is in
practice what happens to the bits when regular short circuiting happen.

So while (0, 1) "sees" the condition a = false, it cannot prove that it
mattered based on your inputs. In general, you need N+1 test cases to cover N
conditionals with MC/DC. As a consequence, the only way of covering a = false
is (0, 0), which alone would be 2/4 cases covered.

I hope this made it clearer, if not I can write a more elaborate explanation
with more examples.

> 4) I noticed various CFG patterns in tree-profile.cc which are handled. Can
> you please explain how the algorithm works even without knowing the original
> paper?

My paper is not written yet, but I will describe the high-level algorithm (+
extracts from source comments) at the end of this email, as it is a bit long.

> 5) I noticed the following ICE:
Yes, this is an unfortunate flaw - I piggyback on the setup done by branch
coverage, which means I silently assume --coverage is used. I was unsure what
to do (and meant to ask, sorry!) as it is a larger decision - should
condition-coverage also imply branch coverage? Should condition coverage work
on its own? Other options?

I'm happy to implement either strategy - please advise on what you would
prefer.


> 6) Please follow the GNU coding style, most notable we replace 8 spaces with 
> a tab. And we break line before {, (, ... Remove noexcept specifiers for 
> functions and I think most of the newly added functions can be static. And 
> each newly added function should have a comment.

Sure thing, I'll add some tabs. All the helper functions are already static (in
an unnamed namespace), but I can remove the namespace and/or add static.

> 7) Please consider supporting of -profile-update=atomic where you'll need to
> utilize an atomic builts (see how other instrumentation looks like with
> it).
Will do! I always intended to support the atomics, not having them there is an
oversight. To confirm, I will only need to use atomics for the global counters,
right? The per-call accumulators are local to each activation record and should
not need the atomics, or am I missing something?

ALGORITHM

Phase 1: expression isolation from CFG analysis

Break the CFG into smaller pieces ("sections") by splitting it on dominators.
No expression can cross a dominator, so becomes a natural way of terminating
expression searches.

For each section, do a BFS from the root node and collect all reachable nodes
following edges that point to "conditional" nodes, i.e. nodes with outgoing
true&false edges. Series of single-parent single-exit nodes are contracted.
This gives a good estimate for an expression, but might include conditions in
the if/else blocks.

The algorithm then collects the immediate neighborhood, i.e. all nodes adjacent
to the collected set, dropping everything in the set itself. Then, a new BFS is
performed, but now "upwards" (i.e. following predecessors), skipping any
neighbor not dominated by the root (this eliminates loops and other expressions
with the same "exit" block, see BFS-up). The intersection of all the paths
found with this BFS is the isolated expression.

If the root is not a conditional, it is skipped and it's successor
becomes the new root. If an expression is found it has a pair of "expression
sucessors", and the algorithm recurses into both, evaluating this sub-section
in isolation, i.e.

if (a || b) {
root:
    if (c && d) {
        ...
    }
    ...
sink:
}

The sections between the root and sink labels are evaulated as if the outer
expression did not exist.

From the comments:

    1. Collect all nodes reachable from the root node through (contracted) paths
       of true/false edges.
    2. Collect the neighbors of the reachable node (set).
    3. From every node in the neighborhood, walk the up the CFG and mark every
       reachable node. Only the nodes reachable from *every* node in the
       neighborhood are a part of the first expression.
    4. Record the expression plus the two successors of the last (highest-index)
       node in the expression, i.e. the last term.
    5. Repeat using the two successors as new root nodes.

    It is not guaranteed to find nodes in the order of the expression, i.e. it
    might find (a || b) && c as [a c b], so the output is sorted by
    basic_block->index.

    Steps 2 and 3 are necessary to distinguish chained conditionals from
    multi-term conditionals, e.g. to separate

        if (a)
        {
            if (b)
                work ();
        }
        if (a && b)
            work ();


On BFS-up:

    Either block of a conditional could have more decisions and loops, so
    isolate the first decision by set-intersecting all paths from the
    post-dominator to the entry block.

    The function returns the number of blocks from n that make up the leading
    expression in prefix order (i.e. the order expected by the instrumenting
    code).  When this function returns 0 there are no decisions between pre and
    post, and this segment of the CFG can safely be skipped.

    The post nodes can have predecessors that do not belong to this subgraph,
    which are skipped.  This is expected, for example when there is a
    conditional in the else-block of a larger expression:

    if (A)
    {
       if (B) {}
    } else {
       if (C) {} else {}
    }

             A
          t / \ f
           /   \
          B     C
         /\    / \
        /  \  T   F
       T    \  \ /
        \   |   o
         \  |  /
          \ | /
           \|/
            E

    Processing [B, E) which looks like:

       B
      /|
     / |
    T  |
     \ /
      E ----> o // predecessor outside [B, e)


Phase 2: Masking vector and instrumentation

The input to this phase is the basic blocks that make up every expression, plus
its two exit nodes, i.e. the 3-term expression (a || b || c) yields the input
[a b c T F] to phase 2.

For every expression, allocate two local accumulators (bit-vectors), one for
the true-vector, one for the false-vector. These are zero'd on function entry
and flushed on function exit.

For each "conditional" block (a b c), unconditionally set the corresponding bit
when an edge in the CFG is taken. Rough pseudo-gimple illustration:

if (a) {
    accu_true |= 1 << 0
    goto done;
} else {
    accu_false |= 1 << 0
    if (b) {
        accu_true |= 1 << 1;
        goto done;
    } else {
        accu_false |= 1 << 1;
        ...
    }
}

global_true[expr_n]  |= accu_true
global_false[expr_n] |= accu_false

This alone would more-or-less be branch coverage, but would not prove that
terms would independently decide the outcome. To find masking, the expression
is walked in search of triangles. A || B as a decision diagram becomes

   A
  t|\f
   | \
   |  B
   |t/ \f
   |/   \
   T     F

and we search for triangles like ATB, that is nodes with 2 predecessors with
the same boolean value, where the two predecessors are connected with the
opposite value. This marks a candidate for masking, and the terms masked are
determined by chasing predecessors starting at the top of the triangle for as
long as the chain of predecessors have the same truth value as the "exit"
edges. This set is often empty.

The edge that triggers masking is then instrumented with a accu &= mask which
wipes out anything that does not independently affect the outcome. Note that
nothing is ever wiped from the global accumulator, only the local. The (a || b)
example had a false vector of [1, 0] at some point, but it was wiped by the
masking operation (b = 1 is the only masking value for a || b).

From the comments:

    Scan the blocks that make up an expression and look for conditions that
    would mask other conditions.  For a deeper discussion on masking, see
    Whalen, Heimdahl, De Silva "Efficient Test Coverage Measurement for MC/DC".
    Masking is best illustrated with an example:

    A || B.  If B is true then A will does not independently affect the
    decision.  In a way, this is "reverse" short circuiting, and always work on
    the right-hand side of expressions.  * || true is always true and * &&
    false is always false - the left-hand-side does not affect the outcome, and
    their values should not contribute to modified condition/decision coverage.

    A || B interpreted as a decision diagram becomes:

     A
    t|\f
     | \
     |  B
     |t/ \f
     |/   \
     T     F

    The algorithm looks for triangles like ATB.  Masking right-hand sides happen
    when a block has a pair of incoming edges of the same boolean value, and
    there is an edge connecting the two predecessors with the *opposite* boolean
    value.  The masking block is B, and the masked block is A.  In this
    particular example:

    Masking can affect "outside" its own subexpression; in A || (B && C) if C is
    false, B is masked.  However, if (B && C) is true, A gets masked.  B && C
    can be determined from evaluating C since !B would short-circuit, so a true
    C would mask A.

     A
    t|\f
     | \
     |  \
     |   \
     |    B
     |  t/ \f
     |  C   |
     |t/ \f |
     |/   \ |
     T     F

    Notice how BFC forms a triangle.  Expressions masked by an edge are
    determined by:
    * Go to the predecessor with truth value b (if it exists).
    * Follow the path of ancestors with taking only !b edges, recording every
      node from here on.

    For the case where C can mask A, the path goes C [true]-> B -> [false] A, so
    C [true] masks A.

    The mask is output as a bit-set stored in a gcov_type_unsigned.  The
    bit-sets are output in pairs, one for each decision (the outcome of the
    boolean expression, or which arc to take in the CFG).

Phase 3: reading the report

This is pretty standard gcov stuff, except the "counters" are interpreted as
bitsets, and the number of terms in the expressions is stored. Coverage then
becomes popcount / expected.

Final remarks:

Granted, this is a bit dense, but I think the overall approach is easy to grasp
when supported by a lot of example graphs and stepping. I will try to type up a
bunch with the paper and publish, but for now all I have is the code (which I
hope for the most part is easy to follow). I'll update this with style updates
and atomics support, but I would prefer a maintainer to make the call if this
condition coverage should imply branch coverage, or if it should work without it.

Thanks,
Jørgen

On 24/03/2022 17:08, Martin Liška via Gcc-patches wrote:
> On 3/21/22 12:55, Jørgen Kvalsvik via Gcc-patches wrote:
> 
> Hello.
> 
> Thanks for very interesting extension of the existing GCOV profiling.
> 
> I briefly read the patch and investigated what happens and I've got various
> questions/comments about it:
> 
> 1) Do I correctly understand that __conditions_accu_true/false track
> every short-circuit sub-expression of a condition and record
> if a given sub-expr is seen to be true or false?
> 
> 2) As mentioned these are bit sets, can you please explain why you (& -value)
> from these sets?
> 
> 3) I noticed a false report for a simple test-case:
> 
> $ cat cond.c
> int x;
> 
> void foo (int a, int b)
> {
>      if (a || b)
>        x = 1;
>      else
>        x = 2;
> }
> 
> int main()
> {
>    foo (0, 1);
>    return 0;
> }
> 
> $ rm *gcda ; gcc --coverage cond.c -fprofile-conditions && ./a.out && gcov -g -t a-cond.c
>          -:    0:Source:cond.c
>          -:    0:Graph:a-cond.gcno
>          -:    0:Data:a-cond.gcda
>          -:    0:Runs:1
>          -:    1:int x;
>          -:    2:
>          1:    3:void foo (int a, int b)
>          -:    4:{
>          1:    5:    if (a || b)
> conditions covered 1/4
> condition  0 not covered (true)
> condition  0 not covered (false) <-- this was executed if I'm correct
> condition  1 not covered (false)
>          1:    6:      x = 1;
>          -:    7:    else
>      #####:    8:      x = 2;
>          1:    9:}
>          -:   10:
>          1:   11:int main()
>          -:   12:{
>          1:   13:  foo (0, 1);
>          1:   14:  return 0;
>          -:   15:}
> 
> 4) I noticed various CFG patterns in tree-profile.cc which are handled. Can you please
> explain how the algorithm works even without knowing the original paper?
> 
> 5) I noticed the following ICE:
> 
> $ gcc cond.c -fprofile-conditions -fprofile-generate
> during IPA pass: profile
> cond.c: In function ?foo?:
> cond.c:15:1: internal compiler error: Segmentation fault
>     15 | }
>        | ^
> 0xf4d64a crash_signal
> 	/home/marxin/Programming/gcc/gcc/toplev.cc:322
> 0x7ffff78b93cf ???
> 	/usr/src/debug/glibc-2.35-2.1.x86_64/signal/../sysdeps/unix/sysv/linux/x86_64/libc_sigaction.c:0
> 0x7ffff78f29ed __GI__IO_ftell
> 	/usr/src/debug/glibc-2.35-2.1.x86_64/libio/ioftell.c:37
> 0xa9dfda gcov_position
> 	/home/marxin/Programming/gcc/gcc/gcov-io.cc:48
> 0xa9dfda gcov_write_tag(unsigned int)
> 	/home/marxin/Programming/gcc/gcc/gcov-io.cc:321
> 0xe9734a branch_prob(bool)
> 	/home/marxin/Programming/gcc/gcc/profile.cc:1542
> 0x1032e08 tree_profiling
> 	/home/marxin/Programming/gcc/gcc/tree-profile.cc:1620
> 0x1032e08 execute
> 	/home/marxin/Programming/gcc/gcc/tree-profile.cc:1726
> Please submit a full bug report, with preprocessed source (by using -freport-bug).
> Please include the complete backtrace with any bug report.
> See <https://gcc.gnu.org/bugs/> for instructions.
> 
> 6) Please follow the GNU coding style, most notable we replace 8 spaces with a tab.
> And we break line before {, (, ... Remove noexcept specifiers for functions and I think
> most of the newly added functions can be static. And each newly added function should
> have a comment.
> 
> 7) Please consider supporting of -profile-update=atomic where you'll need to utilize
> an atomic builts (see how other instrumentation looks like with it).
> 
> That's the very first round of the review. Hope it's helpful.
> 
> Cheers,
> Martin
> 
>> This patch adds support in gcc+gcov for modified condition/decision
>> coverage (MC/DC) with the -fprofile-conditions flag. MC/DC is a type of
>> test/code coverage, and it is particularly important in the avation and
>> automotive industries for safety-critical applications. In particular,
>> it is required or recommended by:
>>
>>      * DO-178C for the most critical software (Level A) in avionics
>>      * IEC 61508 for SIL 4
>>      * ISO 26262-6 for ASIL D
>>
>>  From the SQLite webpage:
>>
>>      Two methods of measuring test coverage were described above:
>>      "statement" and "branch" coverage. There are many other test
>>      coverage metrics besides these two. Another popular metric is
>>      "Modified Condition/Decision Coverage" or MC/DC. Wikipedia defines
>>      MC/DC as follows:
>>
>>          * Each decision tries every possible outcome.
>>          * Each condition in a decision takes on every possible outcome.
>>          * Each entry and exit point is invoked.
>>          * Each condition in a decision is shown to independently affect
>>            the outcome of the decision.
>>
>>      In the C programming language where && and || are "short-circuit"
>>      operators, MC/DC and branch coverage are very nearly the same thing.
>>      The primary difference is in boolean vector tests. One can test for
>>      any of several bits in bit-vector and still obtain 100% branch test
>>      coverage even though the second element of MC/DC - the requirement
>>      that each condition in a decision take on every possible outcome -
>>      might not be satisfied.
>>
>>      https://sqlite.org/testing.html#mcdc
>>
>> Wahlen, Heimdahl, and De Silva "Efficient Test Coverage Measurement for
>> MC/DC" describes an algorithm for adding instrumentation by carrying
>> over information from the AST, but my algorithm does analysis on the
>> control flow graph. This should make it work for any language gcc
>> supports, although I have only tested it on constructs in C and C++, see
>> testsuite/gcc.misc-tests and testsuite/g++.dg.
>>
>> Like Wahlen et al this implementation uses bitsets to store conditions,
>> which gcov later interprets. This is very fast, but introduces an max
>> limit for the number of terms in a single boolean expression. This limit
>> is the number of bits in a gcov_unsigned_type (which is typedef'd to
>> uint64_t), so for most practical purposes this would be acceptable.
>> limitation can be relaxed with a more sophisticated way of storing and
>> updating bitsets (for example length-encoding).
>>
>> In action it looks pretty similar to the branch coverage. The -g short
>> opt carries no significance, but was chosen because it was an available
>> option with the upper-case free too.
>>
>> gcov --conditions:
>>
>>          3:   17:void fn (int a, int b, int c, int d) {
>>          3:   18:    if ((a && (b || c)) && d)
>> conditions covered 5/8
>> condition  1 not covered (false)
>> condition  2 not covered (true)
>> condition  2 not covered (false)
>>          1:   19:        x = 1;
>>          -:   20:    else
>>          2:   21:        x = 2;
>>          3:   22:}
>>
>> gcov --conditions --json-format:
>>
>>      "conditions": [
>>          {
>>              "not_covered_false": [
>>                  1,
>>                  2
>>              ],
>>              "count": 8,
>>              "covered": 5,
>>              "not_covered_true": [
>>                  2
>>              ]
>>          }
>>      ],
>>
>> C++ destructors will add extra conditionals that are not explicitly
>> present in source code. These are present in the CFG, which means the
>> condition coverage will show them.
>>
>> gcov --conditions
>>
>>          -:    5:struct A {
>>          1:    6:    explicit A (int x) : v (x) {}
>>          1:    7:    operator bool () const { return bool(v); }
>>          1:    8:    ~A() {}
>>          -:    9:
>>          -:   10:    int v;
>>          -:   11:};
>>          -:   12:
>>          2:   13:void fn (int a, int b) {
>>         2*:   14:    x = a && A(b);
>> conditions covered 2/4
>> condition  0 not covered (true)
>> condition  1 not covered (true)
>> conditions covered 2/2
>>
>> They are also reported by the branch coverage.
>>
>> gcov -bc
>>
>>         2*:   14:    x = a && A(b);
>> branch  0 taken 1 (fallthrough)
>> branch  1 taken 1
>> call    2 returned 1
>> call    3 returned 1
>> branch  4 taken 0 (fallthrough)
>> branch  5 taken 1
>> branch  6 taken 1 (fallthrough)
>> branch  7 taken 1
>>
>> The algorithm struggles in a particular case where gcc would generate
>> identical CFGs for different expressions:
>>
>> and.c:
>>
>>      if (a && b && c)
>>          x = 1;
>>
>> ifs.c:
>>
>>      if (a)
>>          if (b)
>>              if (c)
>>                  x = 1;
>>
>>      if (a && b && c)
>>          x = 1;
>>
>> and.c.gcov:
>>
>>              #####:    2:    if (a && b && c)
>> conditions covered 2/2
>> conditions covered 2/2
>> conditions covered 2/2
>>      #####:    3:        x = 1;
>>
>> ifs.c.gcov:
>>      #####:    2:    if (a)
>> conditions covered 2/2
>>      #####:    3:   2    if (b)
>> conditions covered 2/2
>>      #####:    4:   2        if (c)
>> conditions covered 2/2
>>      #####:    5:                x = 1;
>>
>> These programs are semantically equivalent, and are interpreted as 3
>> separate conditional expressions. It does not matter w.r.t. coverage,
>> but is not ideal. Adding an else to and.c fixes the issue:
>>
>>      #####:    2:    if (a && b && c)
>> conditions covered 6/6
>>      #####:    3:        x = 1;
>>      #####:    4:    else x = x;
>>
>> In the (a && b && c) case the else block must be shared between all
>> three conditionals, and for if (a) if (b) if (c) there would be three
>> *separate* else blocks.
>>
>> Since the algorithm works on CFGs, it cannot detect conditionals (from
>> ternaries) that don't get an entry in the cfg. For example,
>> int x = a ? 0 : 1 in gimple becomes _x = (_a == 0). From source you
>> would expect coverage, but it gets neither branch nor condition
>> coverage. For completeness, it could be achieved by scanning all gimple
>> statements for comparisons, and insert an extra instruction for
>> recording the outcome.
>>
>> The test suite consists of all different CFG kinds and control flow
>> structures I could find, but there is certainly room for many more.
>>
>> Alternative email: J?rgen Kvalsvik <j@lambda.is>
>>
>> --
>>
>> Some final remarks (not a part of the commit message), this is written in a C++ style that I felt meshed well with what was already there, but if the maintainers would prefer a different approach then I'm happy to to make adjustments. I plan to write a more thorough description of the algorithm, as well as adding the same feature to clang as it is very useful for us at Woven Planet.
>>
>> I tried to pick flag names and options that were comfortable and descriptive, but if you have better names or different ideas for flags then I am happy to change them. The --coverage flag was not changed to imply -fprofile-conditions, but it is possibly something to consider.
>>
>> gcc/ChangeLog:
>>
>> 	* common.opt: Add new options -fprofile-conditions and
>> 	-Wcoverage-too-many-conditions.
>> 	* doc/gcov.texi: Add --conditions documentation.
>> 	* doc/invoke.texi: Add -fprofile-conditions documentation.
>> 	* gcov-counter.def (GCOV_COUNTER_CONDS): New.
>> 	* gcov-io.h (GCOV_TAG_CONDS): New.
>> 	(GCOV_TAG_CONDS_LENGTH): Likewise.
>> 	(GCOV_TAG_CONDS_NUM): Likewise.
>> 	* gcov.cc (class condition_info): New.
>> 	(condition_info::condition_info): New.
>> 	(condition_info::popcount): New.
>> 	(struct coverage_info): New.
>> 	(add_condition_counts): New.
>> 	(output_conditions): New.
>> 	(print_usage): Add -g, --conditions.
>> 	(process_args): Likewise.
>> 	(output_intermediate_json_line): Output conditions.
>> 	(read_graph_file): Read conditions counters.
>> 	(read_count_file): Read conditions counters.
>> 	(file_summary): Print conditions.
>> 	(accumulate_line_info): Accumulate conditions.
>> 	(output_line_details): Print conditions.
>> 	* profile.cc (find_conditions): New declaration.
>> 	(instrument_decisions): New declaration.
>> 	(branch_prob): Call find_conditions, instrument_decisions.
>> 	(init_branch_prob): Add total_num_conds.
>> 	(end_branch_prob): Likewise.
>> 	* tree-profile.cc (INCLUDE_ALGORITHM): Define.
>> 	(struct conds_ctx): New.
>> 	(CONDITIONS_MAX_TERMS): New.
>> 	(index_of): New.
>> 	(index_lt): New.
>> 	(single): New.
>> 	(single_edge): New.
>> 	(contract_edge): New.
>> 	(contract_edge_up): New.
>> 	(is_conditional_p): New.
>> 	(collect_neighbors): New.
>> 	(find_first_conditional): New.
>> 	(emit_bitwise_op): New.
>> 	(collect_conditions): New.
>> 	(find_conditions): New.
>> 	(instrument_decisions): New.
>>
>> gcc/testsuite/ChangeLog:
>>
>> 	* lib/gcov.exp:
>> 	* g++.dg/gcov/gcov-18.C: New test.
>> 	* gcc.misc-tests/gcov-19.c: New test.
>> ---
>>   gcc/common.opt                         |   8 +
>>   gcc/doc/gcov.texi                      |  36 ++
>>   gcc/doc/invoke.texi                    |  19 +
>>   gcc/gcov-counter.def                   |   3 +
>>   gcc/gcov-io.h                          |   3 +
>>   gcc/gcov.cc                            | 177 +++++-
>>   gcc/profile.cc                         |  51 +-
>>   gcc/testsuite/g++.dg/gcov/gcov-18.C    | 209 ++++++
>>   gcc/testsuite/gcc.misc-tests/gcov-19.c | 726 +++++++++++++++++++++
>>   gcc/testsuite/lib/gcov.exp             | 187 +++++-
>>   gcc/tree-profile.cc                    | 838 +++++++++++++++++++++++++
>>   11 files changed, 2248 insertions(+), 9 deletions(-)
>>   create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-18.C
>>   create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-19.c


^ permalink raw reply	[flat|nested] 39+ messages in thread

* Re: [PATCH] Add condition coverage profiling
  2022-03-21 11:55 Jørgen Kvalsvik
@ 2022-03-24 16:08 ` Martin Liška
  2022-03-25 19:44   ` Jørgen Kvalsvik
  2022-04-04  8:14 ` Sebastian Huber
  2022-07-08 13:45 ` Sebastian Huber
  2 siblings, 1 reply; 39+ messages in thread
From: Martin Liška @ 2022-03-24 16:08 UTC (permalink / raw)
  To: Jørgen Kvalsvik, gcc-patches

On 3/21/22 12:55, Jørgen Kvalsvik via Gcc-patches wrote:

Hello.

Thanks for very interesting extension of the existing GCOV profiling.

I briefly read the patch and investigated what happens and I've got various
questions/comments about it:

1) Do I correctly understand that __conditions_accu_true/false track
every short-circuit sub-expression of a condition and record
if a given sub-expr is seen to be true or false?

2) As mentioned these are bit sets, can you please explain why you (& -value)
from these sets?

3) I noticed a false report for a simple test-case:

$ cat cond.c
int x;

void foo (int a, int b)
{
     if (a || b)
       x = 1;
     else
       x = 2;
}

int main()
{
   foo (0, 1);
   return 0;
}

$ rm *gcda ; gcc --coverage cond.c -fprofile-conditions && ./a.out && gcov -g -t a-cond.c
         -:    0:Source:cond.c
         -:    0:Graph:a-cond.gcno
         -:    0:Data:a-cond.gcda
         -:    0:Runs:1
         -:    1:int x;
         -:    2:
         1:    3:void foo (int a, int b)
         -:    4:{
         1:    5:    if (a || b)
conditions covered 1/4
condition  0 not covered (true)
condition  0 not covered (false) <-- this was executed if I'm correct
condition  1 not covered (false)
         1:    6:      x = 1;
         -:    7:    else
     #####:    8:      x = 2;
         1:    9:}
         -:   10:
         1:   11:int main()
         -:   12:{
         1:   13:  foo (0, 1);
         1:   14:  return 0;
         -:   15:}

4) I noticed various CFG patterns in tree-profile.cc which are handled. Can you please
explain how the algorithm works even without knowing the original paper?

5) I noticed the following ICE:

$ gcc cond.c -fprofile-conditions -fprofile-generate
during IPA pass: profile
cond.c: In function ‘foo’:
cond.c:15:1: internal compiler error: Segmentation fault
    15 | }
       | ^
0xf4d64a crash_signal
	/home/marxin/Programming/gcc/gcc/toplev.cc:322
0x7ffff78b93cf ???
	/usr/src/debug/glibc-2.35-2.1.x86_64/signal/../sysdeps/unix/sysv/linux/x86_64/libc_sigaction.c:0
0x7ffff78f29ed __GI__IO_ftell
	/usr/src/debug/glibc-2.35-2.1.x86_64/libio/ioftell.c:37
0xa9dfda gcov_position
	/home/marxin/Programming/gcc/gcc/gcov-io.cc:48
0xa9dfda gcov_write_tag(unsigned int)
	/home/marxin/Programming/gcc/gcc/gcov-io.cc:321
0xe9734a branch_prob(bool)
	/home/marxin/Programming/gcc/gcc/profile.cc:1542
0x1032e08 tree_profiling
	/home/marxin/Programming/gcc/gcc/tree-profile.cc:1620
0x1032e08 execute
	/home/marxin/Programming/gcc/gcc/tree-profile.cc:1726
Please submit a full bug report, with preprocessed source (by using -freport-bug).
Please include the complete backtrace with any bug report.
See <https://gcc.gnu.org/bugs/> for instructions.

6) Please follow the GNU coding style, most notable we replace 8 spaces with a tab.
And we break line before {, (, ... Remove noexcept specifiers for functions and I think
most of the newly added functions can be static. And each newly added function should
have a comment.

7) Please consider supporting of -profile-update=atomic where you'll need to utilize
an atomic builts (see how other instrumentation looks like with it).

That's the very first round of the review. Hope it's helpful.

Cheers,
Martin

> This patch adds support in gcc+gcov for modified condition/decision
> coverage (MC/DC) with the -fprofile-conditions flag. MC/DC is a type of
> test/code coverage, and it is particularly important in the avation and
> automotive industries for safety-critical applications. In particular,
> it is required or recommended by:
> 
>      * DO-178C for the most critical software (Level A) in avionics
>      * IEC 61508 for SIL 4
>      * ISO 26262-6 for ASIL D
> 
>  From the SQLite webpage:
> 
>      Two methods of measuring test coverage were described above:
>      "statement" and "branch" coverage. There are many other test
>      coverage metrics besides these two. Another popular metric is
>      "Modified Condition/Decision Coverage" or MC/DC. Wikipedia defines
>      MC/DC as follows:
> 
>          * Each decision tries every possible outcome.
>          * Each condition in a decision takes on every possible outcome.
>          * Each entry and exit point is invoked.
>          * Each condition in a decision is shown to independently affect
>            the outcome of the decision.
> 
>      In the C programming language where && and || are "short-circuit"
>      operators, MC/DC and branch coverage are very nearly the same thing.
>      The primary difference is in boolean vector tests. One can test for
>      any of several bits in bit-vector and still obtain 100% branch test
>      coverage even though the second element of MC/DC - the requirement
>      that each condition in a decision take on every possible outcome -
>      might not be satisfied.
> 
>      https://sqlite.org/testing.html#mcdc
> 
> Wahlen, Heimdahl, and De Silva "Efficient Test Coverage Measurement for
> MC/DC" describes an algorithm for adding instrumentation by carrying
> over information from the AST, but my algorithm does analysis on the
> control flow graph. This should make it work for any language gcc
> supports, although I have only tested it on constructs in C and C++, see
> testsuite/gcc.misc-tests and testsuite/g++.dg.
> 
> Like Wahlen et al this implementation uses bitsets to store conditions,
> which gcov later interprets. This is very fast, but introduces an max
> limit for the number of terms in a single boolean expression. This limit
> is the number of bits in a gcov_unsigned_type (which is typedef'd to
> uint64_t), so for most practical purposes this would be acceptable.
> limitation can be relaxed with a more sophisticated way of storing and
> updating bitsets (for example length-encoding).
> 
> In action it looks pretty similar to the branch coverage. The -g short
> opt carries no significance, but was chosen because it was an available
> option with the upper-case free too.
> 
> gcov --conditions:
> 
>          3:   17:void fn (int a, int b, int c, int d) {
>          3:   18:    if ((a && (b || c)) && d)
> conditions covered 5/8
> condition  1 not covered (false)
> condition  2 not covered (true)
> condition  2 not covered (false)
>          1:   19:        x = 1;
>          -:   20:    else
>          2:   21:        x = 2;
>          3:   22:}
> 
> gcov --conditions --json-format:
> 
>      "conditions": [
>          {
>              "not_covered_false": [
>                  1,
>                  2
>              ],
>              "count": 8,
>              "covered": 5,
>              "not_covered_true": [
>                  2
>              ]
>          }
>      ],
> 
> C++ destructors will add extra conditionals that are not explicitly
> present in source code. These are present in the CFG, which means the
> condition coverage will show them.
> 
> gcov --conditions
> 
>          -:    5:struct A {
>          1:    6:    explicit A (int x) : v (x) {}
>          1:    7:    operator bool () const { return bool(v); }
>          1:    8:    ~A() {}
>          -:    9:
>          -:   10:    int v;
>          -:   11:};
>          -:   12:
>          2:   13:void fn (int a, int b) {
>         2*:   14:    x = a && A(b);
> conditions covered 2/4
> condition  0 not covered (true)
> condition  1 not covered (true)
> conditions covered 2/2
> 
> They are also reported by the branch coverage.
> 
> gcov -bc
> 
>         2*:   14:    x = a && A(b);
> branch  0 taken 1 (fallthrough)
> branch  1 taken 1
> call    2 returned 1
> call    3 returned 1
> branch  4 taken 0 (fallthrough)
> branch  5 taken 1
> branch  6 taken 1 (fallthrough)
> branch  7 taken 1
> 
> The algorithm struggles in a particular case where gcc would generate
> identical CFGs for different expressions:
> 
> and.c:
> 
>      if (a && b && c)
>          x = 1;
> 
> ifs.c:
> 
>      if (a)
>          if (b)
>              if (c)
>                  x = 1;
> 
>      if (a && b && c)
>          x = 1;
> 
> and.c.gcov:
> 
>              #####:    2:    if (a && b && c)
> conditions covered 2/2
> conditions covered 2/2
> conditions covered 2/2
>      #####:    3:        x = 1;
> 
> ifs.c.gcov:
>      #####:    2:    if (a)
> conditions covered 2/2
>      #####:    3:   2    if (b)
> conditions covered 2/2
>      #####:    4:   2        if (c)
> conditions covered 2/2
>      #####:    5:                x = 1;
> 
> These programs are semantically equivalent, and are interpreted as 3
> separate conditional expressions. It does not matter w.r.t. coverage,
> but is not ideal. Adding an else to and.c fixes the issue:
> 
>      #####:    2:    if (a && b && c)
> conditions covered 6/6
>      #####:    3:        x = 1;
>      #####:    4:    else x = x;
> 
> In the (a && b && c) case the else block must be shared between all
> three conditionals, and for if (a) if (b) if (c) there would be three
> *separate* else blocks.
> 
> Since the algorithm works on CFGs, it cannot detect conditionals (from
> ternaries) that don't get an entry in the cfg. For example,
> int x = a ? 0 : 1 in gimple becomes _x = (_a == 0). From source you
> would expect coverage, but it gets neither branch nor condition
> coverage. For completeness, it could be achieved by scanning all gimple
> statements for comparisons, and insert an extra instruction for
> recording the outcome.
> 
> The test suite consists of all different CFG kinds and control flow
> structures I could find, but there is certainly room for many more.
> 
> Alternative email: Jørgen Kvalsvik <j@lambda.is>
> 
> --
> 
> Some final remarks (not a part of the commit message), this is written in a C++ style that I felt meshed well with what was already there, but if the maintainers would prefer a different approach then I'm happy to to make adjustments. I plan to write a more thorough description of the algorithm, as well as adding the same feature to clang as it is very useful for us at Woven Planet.
> 
> I tried to pick flag names and options that were comfortable and descriptive, but if you have better names or different ideas for flags then I am happy to change them. The --coverage flag was not changed to imply -fprofile-conditions, but it is possibly something to consider.
> 
> gcc/ChangeLog:
> 
> 	* common.opt: Add new options -fprofile-conditions and
> 	-Wcoverage-too-many-conditions.
> 	* doc/gcov.texi: Add --conditions documentation.
> 	* doc/invoke.texi: Add -fprofile-conditions documentation.
> 	* gcov-counter.def (GCOV_COUNTER_CONDS): New.
> 	* gcov-io.h (GCOV_TAG_CONDS): New.
> 	(GCOV_TAG_CONDS_LENGTH): Likewise.
> 	(GCOV_TAG_CONDS_NUM): Likewise.
> 	* gcov.cc (class condition_info): New.
> 	(condition_info::condition_info): New.
> 	(condition_info::popcount): New.
> 	(struct coverage_info): New.
> 	(add_condition_counts): New.
> 	(output_conditions): New.
> 	(print_usage): Add -g, --conditions.
> 	(process_args): Likewise.
> 	(output_intermediate_json_line): Output conditions.
> 	(read_graph_file): Read conditions counters.
> 	(read_count_file): Read conditions counters.
> 	(file_summary): Print conditions.
> 	(accumulate_line_info): Accumulate conditions.
> 	(output_line_details): Print conditions.
> 	* profile.cc (find_conditions): New declaration.
> 	(instrument_decisions): New declaration.
> 	(branch_prob): Call find_conditions, instrument_decisions.
> 	(init_branch_prob): Add total_num_conds.
> 	(end_branch_prob): Likewise.
> 	* tree-profile.cc (INCLUDE_ALGORITHM): Define.
> 	(struct conds_ctx): New.
> 	(CONDITIONS_MAX_TERMS): New.
> 	(index_of): New.
> 	(index_lt): New.
> 	(single): New.
> 	(single_edge): New.
> 	(contract_edge): New.
> 	(contract_edge_up): New.
> 	(is_conditional_p): New.
> 	(collect_neighbors): New.
> 	(find_first_conditional): New.
> 	(emit_bitwise_op): New.
> 	(collect_conditions): New.
> 	(find_conditions): New.
> 	(instrument_decisions): New.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* lib/gcov.exp:
> 	* g++.dg/gcov/gcov-18.C: New test.
> 	* gcc.misc-tests/gcov-19.c: New test.
> ---
>   gcc/common.opt                         |   8 +
>   gcc/doc/gcov.texi                      |  36 ++
>   gcc/doc/invoke.texi                    |  19 +
>   gcc/gcov-counter.def                   |   3 +
>   gcc/gcov-io.h                          |   3 +
>   gcc/gcov.cc                            | 177 +++++-
>   gcc/profile.cc                         |  51 +-
>   gcc/testsuite/g++.dg/gcov/gcov-18.C    | 209 ++++++
>   gcc/testsuite/gcc.misc-tests/gcov-19.c | 726 +++++++++++++++++++++
>   gcc/testsuite/lib/gcov.exp             | 187 +++++-
>   gcc/tree-profile.cc                    | 838 +++++++++++++++++++++++++
>   11 files changed, 2248 insertions(+), 9 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-18.C
>   create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-19.c


^ permalink raw reply	[flat|nested] 39+ messages in thread

* [PATCH] Add condition coverage profiling
@ 2022-03-21 11:55 Jørgen Kvalsvik
  2022-03-24 16:08 ` Martin Liška
                   ` (2 more replies)
  0 siblings, 3 replies; 39+ messages in thread
From: Jørgen Kvalsvik @ 2022-03-21 11:55 UTC (permalink / raw)
  To: gcc-patches

[-- Attachment #1: Type: text/plain, Size: 8978 bytes --]

This patch adds support in gcc+gcov for modified condition/decision
coverage (MC/DC) with the -fprofile-conditions flag. MC/DC is a type of
test/code coverage, and it is particularly important in the avation and
automotive industries for safety-critical applications. In particular,
it is required or recommended by:

    * DO-178C for the most critical software (Level A) in avionics
    * IEC 61508 for SIL 4
    * ISO 26262-6 for ASIL D

From the SQLite webpage:

    Two methods of measuring test coverage were described above:
    "statement" and "branch" coverage. There are many other test
    coverage metrics besides these two. Another popular metric is
    "Modified Condition/Decision Coverage" or MC/DC. Wikipedia defines
    MC/DC as follows:

        * Each decision tries every possible outcome.
        * Each condition in a decision takes on every possible outcome.
        * Each entry and exit point is invoked.
        * Each condition in a decision is shown to independently affect
          the outcome of the decision.

    In the C programming language where && and || are "short-circuit"
    operators, MC/DC and branch coverage are very nearly the same thing.
    The primary difference is in boolean vector tests. One can test for
    any of several bits in bit-vector and still obtain 100% branch test
    coverage even though the second element of MC/DC - the requirement
    that each condition in a decision take on every possible outcome -
    might not be satisfied.

    https://sqlite.org/testing.html#mcdc

Wahlen, Heimdahl, and De Silva "Efficient Test Coverage Measurement for
MC/DC" describes an algorithm for adding instrumentation by carrying
over information from the AST, but my algorithm does analysis on the
control flow graph. This should make it work for any language gcc
supports, although I have only tested it on constructs in C and C++, see
testsuite/gcc.misc-tests and testsuite/g++.dg.

Like Wahlen et al this implementation uses bitsets to store conditions,
which gcov later interprets. This is very fast, but introduces an max
limit for the number of terms in a single boolean expression. This limit
is the number of bits in a gcov_unsigned_type (which is typedef'd to
uint64_t), so for most practical purposes this would be acceptable.
limitation can be relaxed with a more sophisticated way of storing and
updating bitsets (for example length-encoding).

In action it looks pretty similar to the branch coverage. The -g short
opt carries no significance, but was chosen because it was an available
option with the upper-case free too.

gcov --conditions:

        3:   17:void fn (int a, int b, int c, int d) {
        3:   18:    if ((a && (b || c)) && d)
conditions covered 5/8
condition  1 not covered (false)
condition  2 not covered (true)
condition  2 not covered (false)
        1:   19:        x = 1;
        -:   20:    else
        2:   21:        x = 2;
        3:   22:}

gcov --conditions --json-format:

    "conditions": [
        {
            "not_covered_false": [
                1,
                2
            ],
            "count": 8,
            "covered": 5,
            "not_covered_true": [
                2
            ]
        }
    ],

C++ destructors will add extra conditionals that are not explicitly
present in source code. These are present in the CFG, which means the
condition coverage will show them.

gcov --conditions

        -:    5:struct A {
        1:    6:    explicit A (int x) : v (x) {}
        1:    7:    operator bool () const { return bool(v); }
        1:    8:    ~A() {}
        -:    9:
        -:   10:    int v;
        -:   11:};
        -:   12:
        2:   13:void fn (int a, int b) {
       2*:   14:    x = a && A(b);
conditions covered 2/4
condition  0 not covered (true)
condition  1 not covered (true)
conditions covered 2/2

They are also reported by the branch coverage.

gcov -bc

       2*:   14:    x = a && A(b);
branch  0 taken 1 (fallthrough)
branch  1 taken 1
call    2 returned 1
call    3 returned 1
branch  4 taken 0 (fallthrough)
branch  5 taken 1
branch  6 taken 1 (fallthrough)
branch  7 taken 1

The algorithm struggles in a particular case where gcc would generate
identical CFGs for different expressions:

and.c:

    if (a && b && c)
        x = 1;

ifs.c:

    if (a)
        if (b)
            if (c)
                x = 1;

    if (a && b && c)
        x = 1;

and.c.gcov:

            #####:    2:    if (a && b && c)
conditions covered 2/2
conditions covered 2/2
conditions covered 2/2
    #####:    3:        x = 1;

ifs.c.gcov:
    #####:    2:    if (a)
conditions covered 2/2
    #####:    3:   2    if (b)
conditions covered 2/2
    #####:    4:   2        if (c)
conditions covered 2/2
    #####:    5:                x = 1;

These programs are semantically equivalent, and are interpreted as 3
separate conditional expressions. It does not matter w.r.t. coverage,
but is not ideal. Adding an else to and.c fixes the issue:

    #####:    2:    if (a && b && c)
conditions covered 6/6
    #####:    3:        x = 1;
    #####:    4:    else x = x;

In the (a && b && c) case the else block must be shared between all
three conditionals, and for if (a) if (b) if (c) there would be three
*separate* else blocks.

Since the algorithm works on CFGs, it cannot detect conditionals (from
ternaries) that don't get an entry in the cfg. For example,
int x = a ? 0 : 1 in gimple becomes _x = (_a == 0). From source you
would expect coverage, but it gets neither branch nor condition
coverage. For completeness, it could be achieved by scanning all gimple
statements for comparisons, and insert an extra instruction for
recording the outcome.

The test suite consists of all different CFG kinds and control flow
structures I could find, but there is certainly room for many more.

Alternative email: Jørgen Kvalsvik <j@lambda.is>

--

Some final remarks (not a part of the commit message), this is written in a C++ style that I felt meshed well with what was already there, but if the maintainers would prefer a different approach then I'm happy to to make adjustments. I plan to write a more thorough description of the algorithm, as well as adding the same feature to clang as it is very useful for us at Woven Planet.

I tried to pick flag names and options that were comfortable and descriptive, but if you have better names or different ideas for flags then I am happy to change them. The --coverage flag was not changed to imply -fprofile-conditions, but it is possibly something to consider.

gcc/ChangeLog:

	* common.opt: Add new options -fprofile-conditions and
	-Wcoverage-too-many-conditions.
	* doc/gcov.texi: Add --conditions documentation.
	* doc/invoke.texi: Add -fprofile-conditions documentation.
	* gcov-counter.def (GCOV_COUNTER_CONDS): New.
	* gcov-io.h (GCOV_TAG_CONDS): New.
	(GCOV_TAG_CONDS_LENGTH): Likewise.
	(GCOV_TAG_CONDS_NUM): Likewise.
	* gcov.cc (class condition_info): New.
	(condition_info::condition_info): New.
	(condition_info::popcount): New.
	(struct coverage_info): New.
	(add_condition_counts): New.
	(output_conditions): New.
	(print_usage): Add -g, --conditions.
	(process_args): Likewise.
	(output_intermediate_json_line): Output conditions.
	(read_graph_file): Read conditions counters.
	(read_count_file): Read conditions counters.
	(file_summary): Print conditions.
	(accumulate_line_info): Accumulate conditions.
	(output_line_details): Print conditions.
	* profile.cc (find_conditions): New declaration.
	(instrument_decisions): New declaration.
	(branch_prob): Call find_conditions, instrument_decisions.
	(init_branch_prob): Add total_num_conds.
	(end_branch_prob): Likewise.
	* tree-profile.cc (INCLUDE_ALGORITHM): Define.
	(struct conds_ctx): New.
	(CONDITIONS_MAX_TERMS): New.
	(index_of): New.
	(index_lt): New.
	(single): New.
	(single_edge): New.
	(contract_edge): New.
	(contract_edge_up): New.
	(is_conditional_p): New.
	(collect_neighbors): New.
	(find_first_conditional): New.
	(emit_bitwise_op): New.
	(collect_conditions): New.
	(find_conditions): New.
	(instrument_decisions): New.

gcc/testsuite/ChangeLog:

	* lib/gcov.exp:
	* g++.dg/gcov/gcov-18.C: New test.
	* gcc.misc-tests/gcov-19.c: New test.
---
 gcc/common.opt                         |   8 +
 gcc/doc/gcov.texi                      |  36 ++
 gcc/doc/invoke.texi                    |  19 +
 gcc/gcov-counter.def                   |   3 +
 gcc/gcov-io.h                          |   3 +
 gcc/gcov.cc                            | 177 +++++-
 gcc/profile.cc                         |  51 +-
 gcc/testsuite/g++.dg/gcov/gcov-18.C    | 209 ++++++
 gcc/testsuite/gcc.misc-tests/gcov-19.c | 726 +++++++++++++++++++++
 gcc/testsuite/lib/gcov.exp             | 187 +++++-
 gcc/tree-profile.cc                    | 838 +++++++++++++++++++++++++
 11 files changed, 2248 insertions(+), 9 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-18.C
 create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-19.c

[-- Attachment #2: 0001-Add-condition-coverage-profiling.patch --]
[-- Type: text/plain, Size: 85863 bytes --]

From 6d0650340e538d8dd0ccc8369a89ba9b99d7904c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B8rgen=20Kvalsvik?=
 <jorgen.kvalsvik@woven-planet.global>
Date: Mon, 31 Jan 2022 12:49:37 +0100
Subject: [PATCH] Add condition coverage profiling
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This patch adds support in gcc+gcov for modified condition/decision
coverage (MC/DC) with the -fprofile-conditions flag. MC/DC is a type of
test/code coverage, and it is particularly important in the avation and
automotive industries for safety-critical applications. In particular,
it is required or recommended by:

    * DO-178C for the most critical software (Level A) in avionics
    * IEC 61508 for SIL 4
    * ISO 26262-6 for ASIL D

From the SQLite webpage:

    Two methods of measuring test coverage were described above:
    "statement" and "branch" coverage. There are many other test
    coverage metrics besides these two. Another popular metric is
    "Modified Condition/Decision Coverage" or MC/DC. Wikipedia defines
    MC/DC as follows:

        * Each decision tries every possible outcome.
        * Each condition in a decision takes on every possible outcome.
        * Each entry and exit point is invoked.
        * Each condition in a decision is shown to independently affect
          the outcome of the decision.

    In the C programming language where && and || are "short-circuit"
    operators, MC/DC and branch coverage are very nearly the same thing.
    The primary difference is in boolean vector tests. One can test for
    any of several bits in bit-vector and still obtain 100% branch test
    coverage even though the second element of MC/DC - the requirement
    that each condition in a decision take on every possible outcome -
    might not be satisfied.

    https://sqlite.org/testing.html#mcdc

Wahlen, Heimdahl, and De Silva "Efficient Test Coverage Measurement for
MC/DC" describes an algorithm for adding instrumentation by carrying
over information from the AST, but my algorithm does analysis on the
control flow graph. This should make it work for any language gcc
supports, although I have only tested it on constructs in C and C++, see
testsuite/gcc.misc-tests and testsuite/g++.dg.

Like Wahlen et al this implementation uses bitsets to store conditions,
which gcov later interprets. This is very fast, but introduces an max
limit for the number of terms in a single boolean expression. This limit
is the number of bits in a gcov_unsigned_type (which is typedef'd to
uint64_t), so for most practical purposes this would be acceptable.
limitation can be relaxed with a more sophisticated way of storing and
updating bitsets (for example length-encoding).

In action it looks pretty similar to the branch coverage. The -g short
opt carries no significance, but was chosen because it was an available
option with the upper-case free too.

gcov --conditions:

        3:   17:void fn (int a, int b, int c, int d) {
        3:   18:    if ((a && (b || c)) && d)
conditions covered 5/8
condition  1 not covered (false)
condition  2 not covered (true)
condition  2 not covered (false)
        1:   19:        x = 1;
        -:   20:    else
        2:   21:        x = 2;
        3:   22:}

gcov --conditions --json-format:

    "conditions": [
        {
            "not_covered_false": [
                1,
                2
            ],
            "count": 8,
            "covered": 5,
            "not_covered_true": [
                2
            ]
        }
    ],

C++ destructors will add extra conditionals that are not explicitly
present in source code. These are present in the CFG, which means the
condition coverage will show them.

gcov --conditions

        -:    5:struct A {
        1:    6:    explicit A (int x) : v (x) {}
        1:    7:    operator bool () const { return bool(v); }
        1:    8:    ~A() {}
        -:    9:
        -:   10:    int v;
        -:   11:};
        -:   12:
        2:   13:void fn (int a, int b) {
       2*:   14:    x = a && A(b);
conditions covered 2/4
condition  0 not covered (true)
condition  1 not covered (true)
conditions covered 2/2

They are also reported by the branch coverage.

gcov -bc

       2*:   14:    x = a && A(b);
branch  0 taken 1 (fallthrough)
branch  1 taken 1
call    2 returned 1
call    3 returned 1
branch  4 taken 0 (fallthrough)
branch  5 taken 1
branch  6 taken 1 (fallthrough)
branch  7 taken 1

The algorithm struggles in a particular case where gcc would generate
identical CFGs for different expressions:

and.c:

    if (a && b && c)
        x = 1;

ifs.c:

    if (a)
        if (b)
            if (c)
                x = 1;

    if (a && b && c)
        x = 1;

and.c.gcov:

            #####:    2:    if (a && b && c)
conditions covered 2/2
conditions covered 2/2
conditions covered 2/2
    #####:    3:        x = 1;

ifs.c.gcov:
    #####:    2:    if (a)
conditions covered 2/2
    #####:    3:   2    if (b)
conditions covered 2/2
    #####:    4:   2        if (c)
conditions covered 2/2
    #####:    5:                x = 1;

These programs are semantically equivalent, and are interpreted as 3
separate conditional expressions. It does not matter w.r.t. coverage,
but is not ideal. Adding an else to and.c fixes the issue:

    #####:    2:    if (a && b && c)
conditions covered 6/6
    #####:    3:        x = 1;
    #####:    4:    else x = x;

In the (a && b && c) case the else block must be shared between all
three conditionals, and for if (a) if (b) if (c) there would be three
*separate* else blocks.

Since the algorithm works on CFGs, it cannot detect conditionals (from
ternaries) that don't get an entry in the cfg. For example,
int x = a ? 0 : 1 in gimple becomes _x = (_a == 0). From source you
would expect coverage, but it gets neither branch nor condition
coverage. For completeness, it could be achieved by scanning all gimple
statements for comparisons, and insert an extra instruction for
recording the outcome.

The test suite consists of all different CFG kinds and control flow
structures I could find, but there is certainly room for many more.

Alternative email: Jørgen Kvalsvik <j@lambda.is>

gcc/ChangeLog:

	* common.opt: Add new options -fprofile-conditions and
	-Wcoverage-too-many-conditions.
	* doc/gcov.texi: Add --conditions documentation.
	* doc/invoke.texi: Add -fprofile-conditions documentation.
	* gcov-counter.def (GCOV_COUNTER_CONDS): New.
	* gcov-io.h (GCOV_TAG_CONDS): New.
	(GCOV_TAG_CONDS_LENGTH): Likewise.
	(GCOV_TAG_CONDS_NUM): Likewise.
	* gcov.cc (class condition_info): New.
	(condition_info::condition_info): New.
	(condition_info::popcount): New.
	(struct coverage_info): New.
	(add_condition_counts): New.
	(output_conditions): New.
	(print_usage): Add -g, --conditions.
	(process_args): Likewise.
	(output_intermediate_json_line): Output conditions.
	(read_graph_file): Read conditions counters.
	(read_count_file): Read conditions counters.
	(file_summary): Print conditions.
	(accumulate_line_info): Accumulate conditions.
	(output_line_details): Print conditions.
	* profile.cc (find_conditions): New declaration.
	(instrument_decisions): New declaration.
	(branch_prob): Call find_conditions, instrument_decisions.
	(init_branch_prob): Add total_num_conds.
	(end_branch_prob): Likewise.
	* tree-profile.cc (INCLUDE_ALGORITHM): Define.
	(struct conds_ctx): New.
	(CONDITIONS_MAX_TERMS): New.
	(index_of): New.
	(index_lt): New.
	(single): New.
	(single_edge): New.
	(contract_edge): New.
	(contract_edge_up): New.
	(is_conditional_p): New.
	(collect_neighbors): New.
	(find_first_conditional): New.
	(emit_bitwise_op): New.
	(collect_conditions): New.
	(find_conditions): New.
	(instrument_decisions): New.

gcc/testsuite/ChangeLog:

	* lib/gcov.exp:
	* g++.dg/gcov/gcov-18.C: New test.
	* gcc.misc-tests/gcov-19.c: New test.
---
 gcc/common.opt                         |   8 +
 gcc/doc/gcov.texi                      |  36 ++
 gcc/doc/invoke.texi                    |  19 +
 gcc/gcov-counter.def                   |   3 +
 gcc/gcov-io.h                          |   3 +
 gcc/gcov.cc                            | 177 +++++-
 gcc/profile.cc                         |  51 +-
 gcc/testsuite/g++.dg/gcov/gcov-18.C    | 209 ++++++
 gcc/testsuite/gcc.misc-tests/gcov-19.c | 726 +++++++++++++++++++++
 gcc/testsuite/lib/gcov.exp             | 187 +++++-
 gcc/tree-profile.cc                    | 838 +++++++++++++++++++++++++
 11 files changed, 2248 insertions(+), 9 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/gcov/gcov-18.C
 create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-19.c

diff --git a/gcc/common.opt b/gcc/common.opt
index 8b6513de47c..2afe4384d9d 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -861,6 +861,10 @@ Wcoverage-invalid-line-number
 Common Var(warn_coverage_invalid_linenum) Init(1) Warning
 Warn in case a function ends earlier than it begins due to an invalid linenum macros.
 
+Wcoverage-too-many-conditions
+Common Var(warn_too_many_conditions) Init(1) Warning
+Warn when a conditional has too many terms and coverage gives up.
+
 Wmissing-profile
 Common Var(warn_missing_profile) Init(1) Warning
 Warn in case profiles in -fprofile-use do not exist.
@@ -2290,6 +2294,10 @@ fprofile-arcs
 Common Var(profile_arc_flag)
 Insert arc-based program profiling code.
 
+fprofile-conditions
+Common Var(profile_condition_flag)
+Insert condition coverage profiling code.
+
 fprofile-dir=
 Common Joined RejectNegative Var(profile_data_prefix)
 Set the top-level directory for storing the profile data.
diff --git a/gcc/doc/gcov.texi b/gcc/doc/gcov.texi
index fc39da0f02d..a85b7f09769 100644
--- a/gcc/doc/gcov.texi
+++ b/gcc/doc/gcov.texi
@@ -122,6 +122,7 @@ gcov [@option{-v}|@option{--version}] [@option{-h}|@option{--help}]
      [@option{-a}|@option{--all-blocks}]
      [@option{-b}|@option{--branch-probabilities}]
      [@option{-c}|@option{--branch-counts}]
+     [@option{-g}|@option{--conditions}]
      [@option{-d}|@option{--display-progress}]
      [@option{-f}|@option{--function-summaries}]
      [@option{-j}|@option{--json-format}]
@@ -167,6 +168,13 @@ be shown, unless the @option{-u} option is given.
 Write branch frequencies as the number of branches taken, rather than
 the percentage of branches taken.
 
+@item -g
+@itemx --conditions
+Write condition coverage to the output file, and write condition summary info
+to the standard output.  This option allows you to see if the conditions in
+your program at least once had an independent effect on the outcome of the
+boolean expression (modified condition/decision coverage).
+
 @item -d
 @itemx --display-progress
 Display the progress on the standard output.
@@ -291,6 +299,7 @@ Each @var{line} has the following form:
 @{
   "branches": ["$branch"],
   "count": 2,
+  "conditions": ["$condition"],
   "line_number": 15,
   "unexecuted_block": false,
   "function_name": "foo",
@@ -339,6 +348,33 @@ Fields of the @var{branch} element have following semantics:
 @var{throw}: true when the branch is an exceptional branch
 @end itemize
 
+Each @var{condition} element have the following semantics:
+
+@smallexample
+@{
+  "count": 4,
+  "covered": 2,
+  "not_covered_false": [],
+  "not_covered_true": [0, 1],
+@}
+@end smallexample
+
+Fields of the @var{condition} element have following semantics:
+
+@itemize @bullet
+@item
+@var{count}: number of conditions in this expression
+
+@item
+@var{covered}: number of covered conditions in this expression
+
+@item
+@var{not_covered_true}: terms, by index, not seen as true in this expression
+
+@item
+@var{not_covered_false}: terms, by index, not seen as false in this expression
+@end itemize
+
 @item -H
 @itemx --human-readable
 Write counts in human readable format (like 24.6k).
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index e3f2e82cde5..0e24e43ca42 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -594,6 +594,7 @@ Objective-C and Objective-C++ Dialects}.
 @item Program Instrumentation Options
 @xref{Instrumentation Options,,Program Instrumentation Options}.
 @gccoptlist{-p  -pg  -fprofile-arcs  --coverage  -ftest-coverage @gol
+-fprofile-conditions @gol
 -fprofile-abs-path @gol
 -fprofile-dir=@var{path}  -fprofile-generate  -fprofile-generate=@var{path} @gol
 -fprofile-info-section  -fprofile-info-section=@var{name} @gol
@@ -6020,6 +6021,13 @@ error.  @option{-Wno-coverage-invalid-line-number} can be used to disable the
 warning or @option{-Wno-error=coverage-invalid-line-number} can be used to
 disable the error.
 
+@item -Wno-coverage-too-many-conditions
+@opindex Wno-coverage-too-many-conditions
+@opindex Wcoverage-too-many-conditions
+Warn in case a condition have too many terms and GCC gives up coverage.
+Coverage is given up when there are more terms in the conditional than there
+are bits in a @code{gcov_type_unsigned}.  This warning is enabled by default.
+
 @item -Wno-cpp @r{(C, Objective-C, C++, Objective-C++ and Fortran only)}
 @opindex Wno-cpp
 @opindex Wcpp
@@ -15271,6 +15279,13 @@ E.g. @code{gcc a.c b.c -o binary} would generate @file{binary-a.gcda} and
 
 @xref{Cross-profiling}.
 
+@item -fprofile-conditions
+@opindex fprofile-conditions
+Add code so that program conditions are instrumented.  During execution the
+program records what terms in a conditional contributes to a decision.  The
+data may be used to verify that all terms in a booleans are tested and have an
+effect on the outcome of a condition.
+
 @cindex @command{gcov}
 @item --coverage
 @opindex coverage
@@ -15331,6 +15346,10 @@ executed.  When an arc is the only exit or only entrance to a block, the
 instrumentation code can be added to the block; otherwise, a new basic
 block must be created to hold the instrumentation code.
 
+With @option{-fprofile-conditions}, for each conditional in your program GCC
+creates a bitset and records the boolean values that have an independent effect
+on the outcome of that expression.
+
 @need 2000
 @item -ftest-coverage
 @opindex ftest-coverage
diff --git a/gcc/gcov-counter.def b/gcc/gcov-counter.def
index 6d2182bd3db..96563a59a45 100644
--- a/gcc/gcov-counter.def
+++ b/gcc/gcov-counter.def
@@ -49,3 +49,6 @@ DEF_GCOV_COUNTER(GCOV_COUNTER_IOR, "ior", _ior)
 
 /* Time profile collecting first run of a function */
 DEF_GCOV_COUNTER(GCOV_TIME_PROFILER, "time_profiler", _time_profile)
+
+/* Conditions.  The counter is interpreted as a bit-set.  */
+DEF_GCOV_COUNTER(GCOV_COUNTER_CONDS, "conditions", _ior)
diff --git a/gcc/gcov-io.h b/gcc/gcov-io.h
index 99ce7dbccc8..c84f58b99ac 100644
--- a/gcc/gcov-io.h
+++ b/gcc/gcov-io.h
@@ -253,6 +253,9 @@ typedef uint64_t gcov_type_unsigned;
 #define GCOV_TAG_ARCS		 ((gcov_unsigned_t)0x01430000)
 #define GCOV_TAG_ARCS_LENGTH(NUM)  (1 + (NUM) * 2 * GCOV_WORD_SIZE)
 #define GCOV_TAG_ARCS_NUM(LENGTH)  (((LENGTH / GCOV_WORD_SIZE) - 1) / 2)
+#define GCOV_TAG_CONDS             ((gcov_unsigned_t)0x01470000)
+#define GCOV_TAG_CONDS_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE)
+#define GCOV_TAG_CONDS_NUM(LENGTH) (((LENGTH) / GCOV_WORD_SIZE) / 2)
 #define GCOV_TAG_LINES		 ((gcov_unsigned_t)0x01450000)
 #define GCOV_TAG_COUNTER_BASE 	 ((gcov_unsigned_t)0x01a10000)
 #define GCOV_TAG_COUNTER_LENGTH(NUM) ((NUM) * 2 * GCOV_WORD_SIZE)
diff --git a/gcc/gcov.cc b/gcc/gcov.cc
index 27be5ff0911..ba47e726d0d 100644
--- a/gcc/gcov.cc
+++ b/gcc/gcov.cc
@@ -79,6 +79,7 @@ using namespace std;
 class function_info;
 class block_info;
 class source_info;
+class condition_info;
 
 /* Describes an arc between two basic blocks.  */
 
@@ -132,6 +133,27 @@ public:
   vector<unsigned> lines;
 };
 
+class condition_info
+{
+public:
+  condition_info ();
+
+  int popcount () const;
+
+  gcov_type_unsigned truev;
+  gcov_type_unsigned falsev;
+
+  unsigned n_terms;
+};
+
+condition_info::condition_info (): truev (0), falsev (0), n_terms (0)
+{
+}
+
+int condition_info::popcount () const {
+    return __builtin_popcountll (truev) + __builtin_popcountll (falsev);
+}
+
 /* Describes a basic block. Contains lists of arcs to successor and
    predecessor blocks.  */
 
@@ -165,6 +187,8 @@ public:
   /* Block is a landing pad for longjmp or throw.  */
   unsigned is_nonlocal_return : 1;
 
+  condition_info conditions;
+
   vector<block_location_info> locations;
 
   struct
@@ -275,6 +299,8 @@ public:
   vector<block_info> blocks;
   unsigned blocks_executed;
 
+  vector<condition_info*> conditions;
+
   /* Raw arc coverage counts.  */
   vector<gcov_type> counts;
 
@@ -351,6 +377,9 @@ struct coverage_info
   int branches_executed;
   int branches_taken;
 
+  int conditions;
+  int conditions_covered;
+
   int calls;
   int calls_executed;
 
@@ -550,6 +579,10 @@ static int multiple_files = 0;
 
 static int flag_branches = 0;
 
+/* Output conditions (modified condition/decision coverage) */
+
+static int flag_conditions = 0;
+
 /* Show unconditional branches too.  */
 static int flag_unconditional = 0;
 
@@ -656,6 +689,7 @@ static int read_count_file (void);
 static void solve_flow_graph (function_info *);
 static void find_exception_blocks (function_info *);
 static void add_branch_counts (coverage_info *, const arc_info *);
+static void add_condition_counts (coverage_info *, const block_info *);
 static void add_line_counts (coverage_info *, function_info *);
 static void executed_summary (unsigned, unsigned);
 static void function_summary (const coverage_info *);
@@ -664,6 +698,7 @@ static const char *format_gcov (gcov_type, gcov_type, int);
 static void accumulate_line_counts (source_info *);
 static void output_gcov_file (const char *, source_info *);
 static int output_branch_count (FILE *, int, const arc_info *);
+static void output_conditions (FILE *, const block_info *);
 static void output_lines (FILE *, const source_info *);
 static string make_gcov_file_name (const char *, const char *);
 static char *mangle_name (const char *);
@@ -928,6 +963,7 @@ print_usage (int error_p)
   fnotice (file, "  -b, --branch-probabilities      Include branch probabilities in output\n");
   fnotice (file, "  -c, --branch-counts             Output counts of branches taken\n\
                                     rather than percentages\n");
+  fnotice (file, "  -g, --conditions                Include condition coverage in output (MC/DC)\n");
   fnotice (file, "  -d, --display-progress          Display progress information\n");
   fnotice (file, "  -D, --debug			    Display debugging dumps\n");
   fnotice (file, "  -f, --function-summaries        Output summaries for each function\n");
@@ -980,6 +1016,7 @@ static const struct option options[] =
   { "all-blocks",           no_argument,       NULL, 'a' },
   { "branch-probabilities", no_argument,       NULL, 'b' },
   { "branch-counts",        no_argument,       NULL, 'c' },
+  { "conditions",           no_argument,       NULL, 'g' },
   { "json-format",	    no_argument,       NULL, 'j' },
   { "human-readable",	    no_argument,       NULL, 'H' },
   { "no-output",            no_argument,       NULL, 'n' },
@@ -1008,7 +1045,7 @@ process_args (int argc, char **argv)
 {
   int opt;
 
-  const char *opts = "abcdDfhHijklmno:pqrs:tuvwx";
+  const char *opts = "abcdDfghHijklmno:pqrs:tuvwx";
   while ((opt = getopt_long (argc, argv, opts, options, NULL)) != -1)
     {
       switch (opt)
@@ -1025,6 +1062,9 @@ process_args (int argc, char **argv)
 	case 'f':
 	  flag_function_summary = 1;
 	  break;
+	case 'g':
+	  flag_conditions = 1;
+	  break;
 	case 'h':
 	  print_usage (false);
 	  /* print_usage will exit.  */
@@ -1132,6 +1172,46 @@ output_intermediate_json_line (json::array *object,
 	  }
       }
 
+  json::array *conditions = new json::array ();
+  lineo->set ("conditions", conditions);
+  if (flag_conditions)
+  {
+    vector<block_info *>::const_iterator it;
+    for (it = line->blocks.begin (); it != line->blocks.end (); it++)
+    {
+      const condition_info& info = (*it)->conditions;
+      if (info.n_terms == 0)
+        continue;
+
+      const int count = 2 * info.n_terms;
+      const int covered = info.popcount ();
+
+      json::object *cond = new json::object ();
+      cond->set ("count",   new json::integer_number (count));
+      cond->set ("covered", new json::integer_number (covered));
+
+      json::array *mtrue = new json::array ();
+      json::array *mfalse = new json::array ();
+      cond->set("not_covered_true",  mtrue);
+      cond->set("not_covered_false", mfalse);
+
+      if (count != covered)
+      {
+        for (unsigned i = 0; i < info.n_terms; i++)
+        {
+          gcov_type_unsigned index = 1;
+          index <<= i;
+          if (!(index & info.truev))
+            mtrue->append (new json::integer_number (i));
+          if (!(index & info.falsev))
+            mfalse->append (new json::integer_number (i));
+        }
+      }
+
+      conditions->append (cond);
+    }
+  }
+
   object->append (lineo);
 }
 
@@ -1956,6 +2036,28 @@ read_graph_file (void)
 		  }
 	    }
 	}
+      else if (fn && tag == GCOV_TAG_CONDS)
+	{
+        unsigned num_dests = GCOV_TAG_CONDS_NUM (length);
+
+        if (!fn->conditions.empty ())
+            fnotice (stderr, "%s:already seen conditions for '%s'\n",
+                    bbg_file_name, fn->get_name ());
+        else
+            fn->conditions.resize (num_dests);
+
+        for (unsigned i = 0; i < num_dests; ++i)
+        {
+            unsigned idx = gcov_read_unsigned ();
+
+            if (idx >= fn->blocks.size ())
+                goto corrupt;
+
+            condition_info *info = &fn->blocks[idx].conditions;
+            info->n_terms = gcov_read_unsigned ();
+            fn->conditions[i] = info;
+        }
+	}
       else if (fn && tag == GCOV_TAG_LINES)
 	{
 	  unsigned blockno = gcov_read_unsigned ();
@@ -2086,6 +2188,21 @@ read_count_file (void)
 	      goto cleanup;
 	    }
 	}
+      else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_CONDS) && fn)
+    {
+        length = abs (read_length);
+        if (length != GCOV_TAG_COUNTER_LENGTH (2 * fn->conditions.size ()))
+            goto mismatch;
+
+        if (read_length > 0)
+        {
+            for (ix = 0; ix != fn->conditions.size (); ix++)
+            {
+                fn->conditions[ix]->truev  |= gcov_read_counter ();
+                fn->conditions[ix]->falsev |= gcov_read_counter ();
+            }
+        }
+    }
       else if (tag == GCOV_TAG_FOR_COUNTER (GCOV_COUNTER_ARCS) && fn)
 	{
 	  length = abs (read_length);
@@ -2430,6 +2547,13 @@ add_branch_counts (coverage_info *coverage, const arc_info *arc)
     }
 }
 
+static void
+add_condition_counts (coverage_info *coverage, const block_info *block)
+{
+  coverage->conditions += 2 * block->conditions.n_terms;
+  coverage->conditions_covered += block->conditions.popcount ();
+}
+
 /* Format COUNT, if flag_human_readable_numbers is set, return it human
    readable format.  */
 
@@ -2533,7 +2657,13 @@ file_summary (const coverage_info *coverage)
 		 coverage->calls);
       else
 	fnotice (stdout, "No calls\n");
+
     }
+
+  if (flag_conditions)
+      fnotice (stdout, "Conditions covered:%s of %d\n",
+        format_gcov (coverage->conditions_covered, coverage->conditions, 2),
+        coverage->conditions);
 }
 
 /* Canonicalize the filename NAME by canonicalizing directory
@@ -2760,6 +2890,12 @@ static void accumulate_line_info (line_info *line, source_info *src,
 	 it != line->branches.end (); it++)
       add_branch_counts (&src->coverage, *it);
 
+  if (add_coverage)
+    for (vector<block_info *>::iterator it = line->blocks.begin ();
+	 it != line->blocks.end (); it++)
+      add_condition_counts (&src->coverage, *it);
+
+
   if (!line->blocks.empty ())
     {
       /* The user expects the line count to be the number of times
@@ -2861,6 +2997,30 @@ accumulate_line_counts (source_info *src)
       }
 }
 
+static void
+output_conditions (FILE *gcov_file, const block_info *binfo)
+{
+    const condition_info& info = binfo->conditions;
+    if (info.n_terms == 0)
+        return;
+
+    const int expected = 2 * info.n_terms;
+    const int got = info.popcount ();
+
+    fnotice (gcov_file, "conditions covered %d/%d\n", got, expected);
+    if (expected == got)
+        return;
+
+    for (unsigned i = 0; i < info.n_terms; i++) {
+        gcov_type_unsigned index = 1;
+        index <<= i;
+        if (!(index & info.truev))
+            fnotice (gcov_file, "condition %2u not covered (true)\n", i);
+        if (!(index & info.falsev))
+            fnotice (gcov_file, "condition %2u not covered (false)\n", i);
+    }
+}
+
 /* Output information about ARC number IX.  Returns nonzero if
    anything is output.  */
 
@@ -3071,16 +3231,29 @@ output_line_details (FILE *f, const line_info *line, unsigned line_num)
 	  if (flag_branches)
 	    for (arc = (*it)->succ; arc; arc = arc->succ_next)
 	      jx += output_branch_count (f, jx, arc);
+
+      if (flag_conditions)
+        output_conditions (f, *it);
 	}
     }
-  else if (flag_branches)
+  else
     {
+      if (flag_branches)
+      {
       int ix;
 
       ix = 0;
       for (vector<arc_info *>::const_iterator it = line->branches.begin ();
 	   it != line->branches.end (); it++)
 	ix += output_branch_count (f, ix, (*it));
+      }
+
+      if (flag_conditions)
+         {
+           for (vector<block_info *>::const_iterator it = line->blocks.begin ();
+	            it != line->blocks.end (); it++)
+              output_conditions (f, *it);
+          }
     }
 }
 
diff --git a/gcc/profile.cc b/gcc/profile.cc
index a67cce5b199..82ae1f9a8d6 100644
--- a/gcc/profile.cc
+++ b/gcc/profile.cc
@@ -69,6 +69,9 @@ along with GCC; see the file COPYING3.  If not see
 
 #include "profile.h"
 
+int find_conditions (basic_block, basic_block, basic_block*, int*, int);
+int instrument_decisions (basic_block*, int, int);
+
 /* Map from BBs/edges to gcov counters.  */
 vec<gcov_type> bb_gcov_counts;
 hash_map<edge,gcov_type> *edge_gcov_counts;
@@ -100,6 +103,7 @@ static int total_num_passes;
 static int total_num_times_called;
 static int total_hist_br_prob[20];
 static int total_num_branches;
+static int total_num_conds;
 
 /* Forward declarations.  */
 static void find_spanning_tree (struct edge_list *);
@@ -1512,29 +1516,63 @@ branch_prob (bool thunk)
 
   remove_fake_edges ();
 
+  if (profile_condition_flag || profile_arc_flag)
+      gimple_init_gcov_profiler ();
+
+  if (profile_condition_flag)
+  {
+    basic_block entry = ENTRY_BLOCK_PTR_FOR_FN (cfun)->next_bb;
+    basic_block exit  = EXIT_BLOCK_PTR_FOR_FN (cfun);
+
+    // find_conditions () expect memory up front, see that function for details
+    const int max_blocks = 5 * n_basic_blocks_for_fn (cfun);
+    auto_vec<basic_block> blocks (max_blocks);
+    blocks.quick_grow (max_blocks);
+
+    const int max_sizes = n_basic_blocks_for_fn (cfun) + 1;
+    auto_vec<int> sizes (max_sizes);
+    sizes.quick_grow (max_sizes);
+
+    int nconds = find_conditions
+        (entry, exit, blocks.address (), sizes.address (), max_blocks);
+    total_num_conds += nconds;
+
+    if (nconds > 0 && coverage_counter_alloc (GCOV_COUNTER_CONDS, 2 * nconds))
+    {
+      gcov_position_t offset = gcov_write_tag (GCOV_TAG_CONDS);
+      for (int i = 0; i < nconds; ++i)
+      {
+        int idx = sizes[i];
+        int len = sizes[i + 1] - idx;
+        int terms = instrument_decisions (blocks.address () + idx, len, i);
+        gcov_write_unsigned (blocks[idx]->index);
+        gcov_write_unsigned (terms);
+      }
+      gcov_write_length (offset);
+    }
+  }
+
   /* For each edge not on the spanning tree, add counting code.  */
   if (profile_arc_flag
       && coverage_counter_alloc (GCOV_COUNTER_ARCS, num_instrumented))
     {
       unsigned n_instrumented;
 
-      gimple_init_gcov_profiler ();
-
       n_instrumented = instrument_edges (el);
 
       gcc_assert (n_instrumented == num_instrumented);
 
       if (flag_profile_values)
 	instrument_values (values);
-
-      /* Commit changes done by instrumentation.  */
-      gsi_commit_edge_inserts ();
     }
 
   free_aux_for_edges ();
 
   values.release ();
   free_edge_list (el);
+  /* Commit changes done by instrumentation.  */
+  gsi_commit_edge_inserts ();
+
   coverage_end_function (lineno_checksum, cfg_checksum);
   if (flag_branch_probabilities
       && (profile_status_for_fn (cfun) == PROFILE_READ))
@@ -1664,6 +1702,7 @@ init_branch_prob (void)
   total_num_passes = 0;
   total_num_times_called = 0;
   total_num_branches = 0;
+  total_num_conds = 0;
   for (i = 0; i < 20; i++)
     total_hist_br_prob[i] = 0;
 }
@@ -1703,5 +1742,7 @@ end_branch_prob (void)
 		     (total_hist_br_prob[i] + total_hist_br_prob[19-i]) * 100
 		     / total_num_branches, 5*i, 5*i+5);
 	}
+      fprintf (dump_file, "Total number of conditions: %d\n",
+	       total_num_conds);
     }
 }
diff --git a/gcc/testsuite/g++.dg/gcov/gcov-18.C b/gcc/testsuite/g++.dg/gcov/gcov-18.C
new file mode 100644
index 00000000000..7f09569392f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gcov/gcov-18.C
@@ -0,0 +1,209 @@
+/* { dg-options "--coverage -fprofile-conditions -std=c++11" } */
+/* { dg-do run { target native } } */
+
+#include <vector>
+#include <stdexcept>
+
+class nontrivial_destructor {
+public:
+    explicit nontrivial_destructor (int v) : val (v) {}
+    ~nontrivial_destructor () {}
+
+    explicit operator bool() const { return bool(val); }
+
+    int val;
+};
+
+int identity (int x) { return x; }
+int throws (int) { throw std::runtime_error("exception"); }
+
+int throw_if (int x)
+{
+    if (x) /* conditions(1/2) true(0) */
+        throw std::runtime_error("exception");
+    return x;
+}
+
+/* used for side effects to insert nodes in conditional bodies etc. */
+int x = 0;
+
+/* conditionals work in the presence of non-trivial destructors */
+void mcdc001a (int a)
+{
+    nontrivial_destructor v (a);
+
+    if (v.val > 0) /* conditions(2/2) */
+        x = v.val;
+    else
+        x = -v.val;
+}
+
+/* non-trivial destructor in-loop temporary */
+nontrivial_destructor
+mcdc002a (int a, int b) {
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+        nontrivial_destructor tmp (a);
+        if (tmp.val % b) /* conditions(2/2) */
+            return nontrivial_destructor (0);
+        x += i;
+    } /* conditions(suppress) */
+      /* conditions(end) */
+
+    return nontrivial_destructor (a * b);
+}
+
+/* conditional in constructor */
+void mcdc003a (int a)
+{
+    class C
+    {
+    public:
+        explicit C (int e) : v (e) {
+            if (e) /* conditions(1/2) false(0) */
+                v = x - e;
+        }
+        int v;
+    };
+
+    C c (a);
+    if (c.v > 2) /* conditions(1/2) true(0) */
+        x = c.v + a;
+}
+
+/* conditional in destructor */
+void mcdc004a (int a)
+{
+    class C
+    {
+    public:
+        explicit C (int e) : v (e) {}
+        ~C () {
+            if (v) /* conditions(2/2) */
+                x = 2 * v;
+        }
+        int v;
+    };
+
+    C c (a);
+    x = 1; // arbitrary action between ctor+dtor
+}
+
+/* conditional in try */
+void mcdc005a (int a) {
+    try {
+        if (a) /* conditions(1/2) true(0) */
+            x = 2 * identity (a);
+        else
+            x = 1;
+    } catch (...) {
+        x = 0;
+    }
+}
+
+/* conditional in catch */
+void mcdc006a (int a) {
+    try {
+        throws (a);
+    } catch (std::exception&) {
+        if (a) /* conditions(1/2) false(0) */
+            x = identity (a);
+        else
+            x = 0;
+    }
+}
+
+void mcdc006b (int a) {
+    if (a) /* conditions(1/2) true(0) */
+        throws (a);
+    else
+        x = 1;
+}
+
+void mcdc006c (int a) try {
+    throws (a);
+} catch (...) {
+    if (a) /* conditions(2/2) */
+        x = 5;
+}
+
+/* temporary with destructor as term */
+void mcdc007a (int a, int b) {
+    x = a && nontrivial_destructor (b); /* conditions(3/4) false(1) destructor() */
+}
+
+void mcdc007b (int a, int b) {
+    if (a || throw_if (b)) /* conditions(3/4) true(1) destructor() */
+        x = -1;
+    else
+        x = 1;
+}
+
+void mcdc007c (int a, int b) {
+    if (throw_if (a) || throw_if (b)) /* conditions(2/4) true(0 1) destructor() */
+        x = -1;
+    else
+        x = 1;
+}
+
+/* destructor with delete */
+void mcdc008a (int a) {
+    class C {
+    public:
+        int size = 5;
+        int* ptr = nullptr;
+
+        explicit C (int v) : size(v + 5), ptr (new int[size]) /* conditions(suppress) */
+                                                              /* conditions(end) */
+        {
+            for (int i = 0; i < size; i++) /* conditions(2/2) */
+                ptr[i] = i + 1;
+        }
+        ~C() {
+            if (ptr) /* conditions(1/2) false(0) */
+                delete ptr; /* conditions(suppress) */
+                            /* conditions(end) */
+        }
+    };
+
+    C c (a);
+    if (c.ptr[a + 1]) /* conditions(1/2) false(0) */
+        x = a;
+}
+
+int
+main (void)
+{
+    mcdc001a (0);
+    mcdc001a (1);
+
+    mcdc002a (1, 1);
+    mcdc002a (1, 2);
+
+    mcdc003a (1);
+
+    mcdc004a (0);
+    mcdc004a (1);
+
+    mcdc005a (0);
+
+    mcdc006a (1);
+
+    mcdc006b (0);
+
+    mcdc006c (0);
+    mcdc006c (1);
+
+    mcdc007a (0, 0);
+    mcdc007a (1, 1);
+
+    mcdc007b (0, 0);
+    mcdc007b (1, 0);
+
+    mcdc007c (0, 0);
+
+    mcdc008a (1);
+
+}
+
+/* { dg-final { run-gcov conditions { --conditions gcov-18.C } } } */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-19.c b/gcc/testsuite/gcc.misc-tests/gcov-19.c
new file mode 100644
index 00000000000..4c4d13e0f0b
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-19.c
@@ -0,0 +1,726 @@
+/* { dg-options "-fprofile-arcs -fprofile-conditions -ftest-coverage" } */
+/* { dg-do run { target native } } */
+
+/* some side effect to stop branches from being pruned */
+int x = 0;
+
+/* || works */
+void mcdc001a (int a, int b)
+{
+    if (a || b) /* conditions(1/4) true(0) false(0 1) */
+                /* conditions(end) */
+        x = 1;
+    else
+        x = 2;
+}
+
+void mcdc001b (int a, int b)
+{
+    if (a || b) /* conditions(3/4) true(0) false() */
+                /* conditions(end) */
+        x = 1;
+    else
+        x = 2;
+}
+
+void mcdc001c (int a, int b)
+{
+    if (a || b) /* conditions(4/4) */
+        x = 1;
+    else
+        x = 2;
+}
+
+void mcdc001d (int a, int b, int c)
+{
+    if (a || b || c) /* conditions(3/6) false(1 2) true(2) */
+                     /* conditions(end) */
+        x = 1;
+}
+
+/* && works */
+void mcdc002a (int a, int b)
+{
+    if (a && b) /* conditions(1/4) true(0 1) false(0) */
+                /* conditions(end) */
+        x = 1;
+    else
+        x = 2;
+}
+
+void mcdc002b (int a, int b)
+{
+    if (a && b) /* conditions(3/4) false(0) */
+                /* conditions(end) */
+      x = 1;
+    else
+      x = 2;
+}
+
+void mcdc002c (int a, int b)
+{
+    if (a && b) /* conditions(4/4) */
+      x = 1;
+    else
+      x = 2;
+}
+
+void mcdc002d (int a, int b, int c)
+{
+    /*
+     * This is an odd case, and falls victim to trying to detect nested ifs.
+     *
+     * if (a) if (b) if (c) with no else is equivalent to if (a && b && c) and
+     * the CFGs are identical *unless* the else nodes are generated too. In the
+     * && expression, all false edges should go to the same else, but in the
+     * nested-if case they go to different elses.
+     *
+     * This can be surprising, and bad for MC/DC because non-independent
+     * conditionals masked by terms further-right can not be detected. If an
+     * else node is generated, this expression becomes a 3-term decision again.
+     */
+    if (a && b && c) /* conditions(suppress) conditions(4/6) false(0 2) */
+                     /* conditions(end) */
+        x = 1;
+}
+
+/* negation works */
+void mcdc003a (int a, int b)
+{
+    if (!a || !b) /* conditions(2/4) false(0 1) */
+                  /* conditions(end) */
+      x = 1;
+    else
+      x = 2;
+}
+
+/* single conditionals with and without else */
+void mcdc004a (int a)
+{
+    if (a) /* conditions(1/2) true(0) */
+           /* conditions(end) */
+      x = 1;
+    else
+      x = 2;
+}
+
+void mcdc004b (int a)
+{
+    if (a) /* conditions(2/2) */
+      x = 1;
+    else
+      x = 2;
+}
+
+void mcdc004c (int a)
+{
+    if (a) /* conditions(1/2) false(0) */
+           /* conditions(end) */
+        x = 1;
+}
+
+void mcdc004d (int a, int b, int c) {
+    if (a)  /* conditions(2/2) */
+    {
+        if (b || c) /* conditions(1/4) true(1) false(0 1) */
+            x = a + b + c;
+    }
+}
+
+/* mixing && and || works */
+void mcdc005a (int a, int b, int c)
+{
+    if ((a && b) || c) /* conditions(1/6) true(0 1) false(0 1 2) */
+                       /* conditions(end) */
+        x = 1;
+    else
+        x = 2;
+}
+
+void mcdc005b (int a, int b, int c, int d)
+{
+    if ((a && (b || c)) && d) /* conditions(4/8) true(1 2 3) false(0) */
+                              /* conditions(end) */
+        x = 1;
+    else
+        x = 2;
+}
+
+/* nested conditionals */
+void mcdc006a (int a, int b, int c, int d, int e)
+{
+    if (a) /* conditions(2/2) */
+    {
+        if (b && c) /* conditions(3/4) false(1) */
+                    /* conditions(end) */
+            x = 1;
+        else
+            x = 2;
+    }
+    else
+    {
+        if (c || d) /* conditions(2/4) true(0 1) */
+            /* conditions(end) */
+            x = 3;
+        else
+            x = 4;
+    }
+}
+
+void mcdc006b (int a, int b, int c)
+{
+    if (a) /* conditions(2/2) */
+    {
+        if (b) /*conditions(2/2) */
+        {
+            if (c) /* conditions(2/2) */
+            {
+                x = a + b + c;
+            }
+        }
+    }
+}
+
+/* else/if */
+void mcdc007a (int a, int b, int c, int d)
+{
+    if (a) /* conditions(2/2) */
+    {
+        if (b) /* conditions(1/2) true(0) */
+               /* conditions(end) */
+            x = 1;
+        else
+            x = 2;
+    }
+    else if (c) /* conditions(2/2) */
+    {
+        if (d) /* conditions(1/2) true(0) */
+               /* conditions(end) */
+            x = 3;
+    }
+}
+
+void mcdc007b (int a, int b, int c)
+{
+    /* similar to if (a || b || c) x = 1 */
+    if (a) /* conditions(2/2) */
+        goto then;
+    else if (b) /* conditions(2/2) */
+        goto then;
+    else if (c) /* conditions(1/2) true(0) */
+                /* conditions(end) */
+        goto then;
+
+    return;
+
+then:
+        x = 1;
+}
+
+/* while loop */
+void mcdc008a (int a)
+{
+    while (a < 10) /* conditions(2/2) */
+        x = a++;
+}
+
+void mcdc008b (int a)
+{
+    while (a > 10) /* conditions(1/2) true(0) */
+                   /* conditions(end) */
+        x = a--;
+}
+
+void mcdc008c (int a)
+{
+    // should work, even with no body
+    while (a) /* conditions(2/2) */
+        break;
+}
+
+void mcdc008d (int a, int b, int c, int d)
+{
+    /* multi-term loop conditional */
+    while ((a && (b || c)) && d) /* conditions(8/8) */
+        a = b = c = d = 0;
+}
+
+void mcdc009a (int a, int b)
+{
+    while (a > 0 && b > 0) /* conditions(3/4) false(1) */
+                           /* conditions(end) */
+        x = a--;
+}
+
+/* for loop */
+void mcdc010a(int a, int b)
+{
+    for (int i = 0; i < b; i++) /* conditions(2/2) */
+      {
+        if (a < b) /* conditions(2/2) */
+            x = 1;
+        else
+            x = a += 2;
+      }
+}
+
+int always (int) { return 1; }
+
+/* no-condition infinite loops */
+void mcdc010b (int a)
+{
+    for (;;)
+    {
+        if (always(a)) /* conditions(1/2) false(0) */
+                       /* conditions(end) */
+        {
+            x = a;
+            break;
+        }
+        x += a + 1;
+    }
+}
+
+/* conditionals without control flow constructs work */
+void mcdc011a (int a, int b, int c)
+{
+    x = (a && b) || c; /* conditions(5/6) false(1) */
+                       /* conditions(end) */
+}
+
+/* sequential expressions are handled independently */
+void mcdc012a (int a, int b, int c) {
+    if (a || b) /* conditions(3/4) true(0) */
+                /* conditions(end) */
+      x = 1;
+    else
+      x = 2;
+
+    if (c) /* conditions(2/2) */
+      x = 1;
+}
+
+/*
+ * cannot ever satisfy MC/DC, even with all input combinations, because not all
+ * variables independently affect the decision
+ */
+void mcdc013a (int a, int /* b */, int c)
+{
+    /*
+     * Specification: (a && b) || c
+     *
+     * But the expression was implemented wrong. This has branch coverage, but
+     * not MC/DC
+     */
+    if ((a && !c) || c) /* conditions(5/6) false(1) */
+                        /* conditions(end) */
+        x = 1;
+    else
+        x = 2;
+}
+
+void mcdc014a ()
+{
+    int conds[64] = { 0 };
+    /* conditions(64/128) true(0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63) */
+    x = conds[ 0] || conds[ 1] || conds[ 2] || conds[ 3] || conds[ 4] ||
+        conds[ 5] || conds[ 6] || conds[ 7] || conds[ 8] || conds[ 9] ||
+        conds[10] || conds[11] || conds[12] || conds[13] || conds[14] ||
+        conds[15] || conds[16] || conds[17] || conds[18] || conds[19] ||
+        conds[20] || conds[21] || conds[22] || conds[23] || conds[24] ||
+        conds[25] || conds[26] || conds[27] || conds[28] || conds[29] ||
+        conds[30] || conds[31] || conds[32] || conds[33] || conds[34] ||
+        conds[35] || conds[36] || conds[37] || conds[38] || conds[39] ||
+        conds[40] || conds[41] || conds[42] || conds[43] || conds[44] ||
+        conds[45] || conds[46] || conds[47] || conds[48] || conds[49] ||
+        conds[50] || conds[51] || conds[52] || conds[53] || conds[54] ||
+        conds[55] || conds[56] || conds[57] || conds[58] || conds[59] ||
+        conds[60] || conds[61] || conds[62] || conds[63]
+     ;  /* conditions(end) */
+}
+
+/* early returns */
+void mcdc015a (int a, int b)
+{
+    if (a) /* conditions(2/2) */
+        return;
+
+    if (b) /* conditions(1/2) true(0) */
+           /* conditions(end) */
+        x = 1;
+}
+
+void mcdc015b (int a, int b)
+{
+    for (int i = 5; i > a; i--) /* conditions(2/2) */
+    {
+        if (i == b) /* conditions(2/2) */
+            return;
+        x = i;
+    }
+}
+
+void mcdc015c (int a, int b)
+{
+    for (int i = 5; i > a; i--) /* conditions(2/2) */
+    {
+        if (i == b) /* conditions(2/2) */
+        {
+            x = 0;
+            return;
+        }
+        else
+        {
+            x = 1;
+            return;
+        }
+
+        x = i;
+    }
+}
+
+
+/* check nested loops */
+void mcdc016a (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+        for (int k = 0; k < b; k++) /* conditions(2/2) */
+            x = i + k;
+}
+
+void mcdc016b (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+        if (a > 5) /* conditions(2/2) */
+            break;
+
+        for (int k = 0; k < b; k++) /* conditions(2/2) */
+            x = i + k;
+    }
+}
+
+void mcdc016c (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+        if (a > 5) /* conditions(1/2) true(0) */
+                   /* conditions(end) */
+            return;
+
+        for (int k = 0; k < b; k++) /* conditions(2/2) */
+            x = i + k;
+    }
+}
+
+void mcdc016d (int a, int b)
+{
+    for (int i = 0; i < a; i++) /* conditions(2/2) */
+    {
+        for (int k = 0; k < 5; k++) /* conditions(2/2) */
+        {
+            if (b > 5) /* conditions(1/2) true(0) */
+                       /* conditions(end) */
+                return;
+            x = i + k;
+        }
+
+    }
+}
+
+/* do-while loops */
+void mcdc017a (int a)
+{
+    do {
+        a--;
+    } while (a > 0); /* conditions(2/2) */
+}
+
+void noop () {}
+
+void mcdc017b (int a, int b)
+{
+    do {
+        /*
+         * This call is important; it can add more nodes to the body in the
+         * CFG, which has changes how close exits and breaks are to the loop
+         * conditional.
+         */
+        noop ();
+        a--;
+        if (b) /* conditions(2/2) */
+            break;
+
+    } while (a > 0); /* conditions(2/2) */
+}
+
+void mcdc017c (int a, int b)
+{
+    int left = 0;
+    int right = 0;
+    int n = a + b;
+    do {
+        if (a) /* conditions(1/2) false(0) */
+               /* conditions(end) */
+        {
+            left = a > left ? b : left; /* conditions(2/2) */
+        }
+        if (b) /* conditions(1/2) false(0) */
+        {
+            right = b > right ? a : right; /* conditions(2/2) */
+        }
+    } while (n-- > 0); /* conditions(2/2) */
+}
+
+int id  (int x) { return  x; }
+int inv (int x) { return !x; }
+
+/* collection of odd cases lifted-and-adapted from real-world code */
+int mcdc018a (int a, int b, int c, int d, int e, int f, int g, int len)
+{
+    int n;
+    /* adapted from zlib/gz_read */
+    do
+    {
+        n = -1;
+        if (n > len) /* conditions(2/2) */
+            n = len;
+
+        if (b) /* conditions(2/2) */
+        {
+            if (b < 5) /* conditions(2/2) */
+                x = 1;
+            noop();
+        }
+        else if (c && d) /* conditions(3/4) false(1) */
+        {
+            x = 2;
+            break;
+        }
+        else if (e || f) /* conditions(2/4) false(0 1) */
+                         /* conditions(end) */
+        {
+            if (id(g)) /* conditions(2/2) */
+                return 0;
+            continue;
+        }
+    } while (a-- > 0); /* conditions(2/2) */
+
+    return 1;
+}
+
+void mcdc018b (int a, int b, int c) {
+    int n;
+    while (a) /* conditions(2/2) */
+    {
+        /*
+         * else block does not make a difference for the problem, but ensures
+         * loop termination
+         */
+        if (b) /* conditions(2/2) */
+            n = c ? 0 : 0; // does not show up in CFG (embedded in the block)
+        else
+            n = 0;
+        a = n;
+    }
+}
+
+/* too many conditions, coverage gives up */
+void mcdc019a () {
+    int conds[65] = { 0 };
+    #pragma GCC diagnostic push
+    #pragma GCC diagnostic ignored "-Wcoverage-too-many-conditions"
+    x = conds[ 0] || conds[ 1] || conds[ 2] || conds[ 3] || conds[ 4] ||
+        conds[ 5] || conds[ 6] || conds[ 7] || conds[ 8] || conds[ 9] ||
+        conds[10] || conds[11] || conds[12] || conds[13] || conds[14] ||
+        conds[15] || conds[16] || conds[17] || conds[18] || conds[19] ||
+        conds[20] || conds[21] || conds[22] || conds[23] || conds[24] ||
+        conds[25] || conds[26] || conds[27] || conds[28] || conds[29] ||
+        conds[30] || conds[31] || conds[32] || conds[33] || conds[34] ||
+        conds[35] || conds[36] || conds[37] || conds[38] || conds[39] ||
+        conds[40] || conds[41] || conds[42] || conds[43] || conds[44] ||
+        conds[45] || conds[46] || conds[47] || conds[48] || conds[49] ||
+        conds[50] || conds[51] || conds[52] || conds[53] || conds[54] ||
+        conds[55] || conds[56] || conds[57] || conds[58] || conds[59] ||
+        conds[60] || conds[61] || conds[62] || conds[63] || conds[64]
+    ;
+    #pragma GCC diagnostic pop
+}
+
+/* ternary */
+void mcdc020a (int a)
+{
+    // special case, this can be reduced to:
+    // _1 = argc != 0;
+    // e = (int) _1;
+    x = a ? 1 : 0;
+
+    // changing to different int makes branch
+    x = a ? 2 : 1; /* conditions(2/2) */
+}
+
+void mcdc020b (int a, int b)
+{
+    x = (a || b) ? 1 : 0; /* conditions(3/4) true(1) */
+}
+
+void mcdc020c (int a, int b)
+{
+    x = a ? 0
+      : b ? 1 /* conditions(2/2) */
+      : 2;    /* conditions(1/2) false(0) */
+              /* conditions(end) */
+}
+
+/* test with functions as conditionals */
+
+int main ()
+{
+  mcdc001a (0, 1);
+
+  mcdc001b (0, 1);
+  mcdc001b (0, 0);
+
+  mcdc001c (0, 1);
+  mcdc001c (0, 0);
+  mcdc001c (1, 1);
+
+  mcdc001d (1, 1, 1);
+  mcdc001d (0, 1, 0);
+
+  mcdc002a (1, 0);
+
+  mcdc002b (1, 0);
+  mcdc002b (1, 1);
+
+  mcdc002c (0, 0);
+  mcdc002c (1, 1);
+  mcdc002c (1, 0);
+
+  mcdc002d (1, 1, 1);
+  mcdc002d (1, 0, 0);
+
+  mcdc003a (0, 0);
+  mcdc003a (1, 0);
+
+  mcdc004a (0);
+  mcdc004b (0);
+  mcdc004b (1);
+  mcdc004c (1);
+
+  mcdc004d (0, 0, 0);
+  mcdc004d (1, 1, 1);
+
+  mcdc005a (1, 0, 1);
+
+  mcdc005b (1, 1, 0, 0);
+  mcdc005b (1, 0, 0, 0);
+
+  mcdc006a (0, 0, 0, 0, 0);
+  mcdc006a (1, 0, 0, 0, 0);
+  mcdc006a (1, 1, 1, 0, 0);
+
+  mcdc006b (0, 0, 0);
+  mcdc006b (1, 0, 0);
+  mcdc006b (1, 1, 0);
+  mcdc006b (1, 1, 1);
+
+  mcdc007a (0, 0, 0, 0);
+  mcdc007a (1, 0, 0, 0);
+  mcdc007a (0, 0, 1, 0);
+
+  mcdc007b (0, 0, 0);
+  mcdc007b (0, 1, 1);
+  mcdc007b (1, 0, 1);
+
+  mcdc008a (0);
+
+  mcdc008b (0);
+
+  mcdc008c (0);
+  mcdc008c (1);
+
+  mcdc008d (0, 0, 0, 0);
+  mcdc008d (1, 0, 0, 0);
+  mcdc008d (1, 0, 1, 0);
+  mcdc008d (1, 0, 1, 1);
+  mcdc008d (1, 1, 1, 1);
+
+  mcdc009a (0, 0);
+  mcdc009a (1, 1);
+
+  mcdc010a (0, 0);
+  mcdc010a (0, 9);
+  mcdc010a (2, 1);
+
+  mcdc010b (1);
+
+  mcdc011a (0, 0, 0);
+  mcdc011a (1, 1, 0);
+  mcdc011a (1, 0, 1);
+
+  mcdc012a (0, 0, 0);
+  mcdc012a (0, 1, 1);
+
+  mcdc013a (0, 0, 0);
+  mcdc013a (0, 0, 1);
+  mcdc013a (0, 1, 0);
+  mcdc013a (0, 1, 1);
+  mcdc013a (1, 0, 0);
+  mcdc013a (1, 0, 1);
+  mcdc013a (1, 1, 0);
+  mcdc013a (1, 1, 1);
+
+  mcdc014a ();
+
+  mcdc015a (0, 0);
+  mcdc015a (1, 0);
+
+  mcdc015b (0, 0);
+  mcdc015b (0, 1);
+  mcdc015b (6, 1);
+
+  mcdc015c (0, 0);
+  mcdc015c (0, 5);
+  mcdc015c (6, 1);
+
+  mcdc016a (5, 5);
+
+  mcdc016b (5, 5);
+  mcdc016b (6, 5);
+
+  mcdc016c (5, 5);
+
+  mcdc016d (1, 0);
+
+  mcdc017a (0);
+  mcdc017a (2);
+
+  mcdc017b (2, 0);
+  mcdc017b (0, 1);
+
+  mcdc017c (1, 1);
+
+  mcdc018a (0, 0, 1, 1, 0, 0, 0, 0);
+  mcdc018a (0, 1, 0, 0, 0, 0, 1, -2);
+  mcdc018a (0, 6, 0, 0, 0, 0, 1, -2);
+  mcdc018a (0, 6, 0, 0, 0, 0, 1, -2);
+  mcdc018a (0, 0, 0, 1, 0, 1, 1, 0);
+  mcdc018a (1, 0, 0, 0, 1, 1, 0, 0);
+
+  mcdc018b (1, 0, 0);
+  mcdc018b (1, 1, 0);
+
+  mcdc019a ();
+
+  mcdc020a (0);
+  mcdc020a (1);
+
+  mcdc020b (0, 0);
+  mcdc020b (1, 0);
+
+  mcdc020c (0, 1);
+  mcdc020c (1, 1);
+}
+
+/* { dg-final { run-gcov conditions { --conditions gcov-19.c } } } */
diff --git a/gcc/testsuite/lib/gcov.exp b/gcc/testsuite/lib/gcov.exp
index 9d5b2cdb86b..88f28d26bb6 100644
--- a/gcc/testsuite/lib/gcov.exp
+++ b/gcc/testsuite/lib/gcov.exp
@@ -174,6 +174,180 @@ proc verify-branches { testname testcase file } {
     return $failed
 }
 
+#
+# verify-conditions -- check that conditions are checked as expected
+#
+# TESTNAME is the name of the test, including unique flags.
+# TESTCASE is the name of the test file.
+# FILE is the name of the gcov output file.
+#
+# Checks are based on comments in the source file. Condition coverage comes
+# with with two types of output, a summary and a list of the uncovered
+# conditions. Both must be checked to pass the test
+#
+# To check for conditions, add a comment the line of a conditional:
+# /* conditions(n/m) true(0 1) false(1) */
+#
+# where n/m are the covered and total conditions in the expression. The true()
+# and false() take the indices expected *not* covered.
+#
+# This means that all coverage statements should have been seen:
+# /* conditions(end) */
+#
+# If all conditions are covered i.e. n == m, then conditions(end) can be
+# omitted. If either true() or false() are empty they can be omitted too.
+#
+# C++ can insert conditionals in the CFG that are not present in source code.
+# These must be manually suppressed since unexpected and unhandled conditions
+# are an error (to help combat regressions). Output can be suppressed with
+# conditions(suppress) and conditions(end). suppress should usually be on a
+# closing brace.
+#
+# Some expressions, when using unnamed temporaries as operands, will have
+# destructors in expressions. The coverage of the destructor will be reported
+# on the same line as the expression itself, but suppress() would also swallow
+# the expected tested-for messages. To handle these, use the destructor() [1]
+# which will suppress everything from and including the second "conditions
+# covered".
+#
+# [1] it is important that the destructor() is *on the same line* as the
+#     conditions(m/n)
+proc verify-conditions { testname testcase file } {
+    set failed 0
+    set suppress 0
+    set destructor 0
+    set shouldt ""
+    set shouldf ""
+    set shouldall ""
+    set fd [open $file r]
+    set n 0
+    set keywords {"end" "suppress"}
+    while {[gets $fd line] >= 0} {
+        regexp "^\[^:\]+: *(\[0-9\]+):" "$line" all n
+        set prefix "$testname line $n"
+
+        if {![regexp "condition" $line]} {
+            continue
+        }
+
+        # Missing coverage for both true and false will cause a failure, but
+        # only count it once for the report.
+        set ok 1
+        if [regexp {conditions *\(([0-9a-z/]+)\)} "$line" all e] {
+            # *Very* coarse sanity check: conditions() should either be a
+            # keyword or n/m, anything else means a buggy test case. end is
+            # optional for cases where all conditions are covered, since it
+            # only expects a single line of output.
+            if {([lsearch -exact $keywords $e] >= 0 || [regexp {\d+/\d+} "$e"]) == 0} {
+                fail "$prefix: expected conditions (n/m), (suppress) or (end); was ($e)"
+                incr failed
+                continue
+            }
+
+            # Any keyword means a new context. Set the error flag if not all
+            # expected output has been seen, and reset the state.
+
+            if {[llength $shouldt] != 0} {
+                fail "$prefix: expected uncovered (true) for terms $shouldt"
+                set ok 0
+            }
+
+            if {[llength $shouldf] != 0} {
+                fail "$prefix: expected uncovered (false) for terms $shouldf"
+                set ok 0
+            }
+
+            if {$shouldall ne ""} {
+                fail "$prefix: coverage summary not found; expected $shouldall"
+                set ok 0
+            }
+
+            set suppress 0
+            set destructor 0
+            set shouldt ""
+            set shouldf ""
+            set shouldall ""
+            set newt ""
+            set newf ""
+
+            if [regexp {destructor\(\)} "$line"] {
+                set destructor 1
+            }
+
+            if [regexp {(\d+)/(\d+)} "$e" all i k] {
+                regexp {true\(([0-9 ]+)\)}  "$line" all newt
+                regexp {false\(([0-9 ]+)\)} "$line" all newf
+
+                # Sanity check - if the true() and false() vectors should have
+                # m-n elements to cover all uncovered conditions. Because of
+                # masking it can sometimes be surprising what terms are
+                # independent, so this makes for more robust test at the cost
+                # of being slightly more annoying to write.
+                set nterms [expr [llength $newt] + [llength $newf]]
+                set nexpected [expr {$k - $i}]
+                if {$nterms != $nexpected} {
+                    fail "$prefix: expected $nexpected uncovered terms; got $nterms"
+                    set ok 0
+                }
+                set shouldall $e
+                set shouldt $newt
+                set shouldf $newf
+            } elseif {$e == "end"} {
+                # no-op - state has already been reset, and errors flagged
+            } elseif {$e == "suppress"} {
+                set suppress 1
+            } else {
+                # this should be unreachable, 
+                fail "$prefix: unhandled control ($e), should be unreachable"
+                set ok 0
+            }
+        } elseif {$suppress == 1} {
+            # ignore everything in a suppress block. C++ especially can insert
+            # conditionals in exceptions and destructors which would otherwise
+            # be considered unhandled.
+            continue
+        } elseif [regexp {condition +(\d+) not covered \(true\)} "$line" all cond] {
+            set i [lsearch $shouldt $cond]
+            if {$i != -1} {
+                set shouldt [lreplace $shouldt $i $i]
+            } else {
+                fail "$testname line $n: unexpected uncovered term $cond (true)"
+                set ok 0
+            }
+        } elseif [regexp {condition +(\d+) not covered \(false\)} "$line" all cond] {
+            set i [lsearch $shouldf $cond]
+            if {$i != -1} {
+                set shouldf [lreplace $shouldf $i $i]
+            } else {
+                fail "$testname line $n: unexpected uncovered term $cond (false)"
+                set ok 0
+            }
+        } elseif [regexp {conditions covered (\d+/\d+)} "$line" all cond] {
+            # the destructor-generated "conditions covered" lines will be
+            # written after all expression-related output. Handle these by
+            # turning on suppression if the destructor-suppression is
+            # requested.
+            if {$shouldall == "" && $destructor == 1} {
+                set suppress 1
+                continue
+            }
+
+            if {$cond == $shouldall} {
+                set shouldall ""
+            } else {
+                fail "$testname line $n: unexpected summary $cond"
+                set ok 0
+            }
+        }
+
+        if {$ok != 1} {
+            incr failed
+        }
+    }
+    close $fd
+    return $failed
+}
+
 #
 # verify-calls -- check that call return percentages are as expected
 #
@@ -321,6 +495,7 @@ proc run-gcov { args } {
     set gcov_args ""
     set gcov_verify_calls 0
     set gcov_verify_branches 0
+    set gcov_verify_conditions 0
     set gcov_verify_lines 1
     set gcov_verify_intermediate 0
     set gcov_remove_gcda 0
@@ -331,10 +506,13 @@ proc run-gcov { args } {
 	  set gcov_verify_calls 1
 	} elseif { $a == "branches" } {
 	  set gcov_verify_branches 1
+	} elseif { $a == "conditions" } {
+	  set gcov_verify_conditions 1
 	} elseif { $a == "intermediate" } {
 	  set gcov_verify_intermediate 1
 	  set gcov_verify_calls 0
 	  set gcov_verify_branches 0
+	  set gcov_verify_conditions 0
 	  set gcov_verify_lines 0
 	} elseif { $a == "remove-gcda" } {
 	  set gcov_remove_gcda 1
@@ -404,6 +582,11 @@ proc run-gcov { args } {
     } else {
 	set bfailed 0
     }
+    if { $gcov_verify_conditions } {
+	set cdfailed [verify-conditions $testname $testcase $testcase.gcov]
+    } else {
+	set cdfailed 0
+    }
     if { $gcov_verify_calls } {
 	set cfailed [verify-calls $testname $testcase $testcase.gcov]
     } else {
@@ -418,12 +601,12 @@ proc run-gcov { args } {
 
     # Report whether the gcov test passed or failed.  If there were
     # multiple failures then the message is a summary.
-    set tfailed [expr $lfailed + $bfailed + $cfailed + $ifailed]
+    set tfailed [expr $lfailed + $bfailed + $cdfailed + $cfailed + $ifailed]
     if { $xfailed } {
 	setup_xfail "*-*-*"
     }
     if { $tfailed > 0 } {
-	fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cfailed in return percentages, $ifailed in intermediate format"
+	fail "$testname gcov: $lfailed failures in line counts, $bfailed in branch percentages, $cdfailed in condition/decision, $cfailed in return percentages, $ifailed in intermediate format"
 	if { $xfailed } {
 	    clean-gcov $testcase
 	}
diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
index 6d40401f86f..e9a59fd4848 100644
--- a/gcc/tree-profile.cc
+++ b/gcc/tree-profile.cc
@@ -24,6 +24,7 @@ along with GCC; see the file COPYING3.  If not see
 /* Generate basic block profile instrumentation and auxiliary files.
    Tree-based version.  See profile.cc for overview.  */
 
+#define INCLUDE_ALGORITHM
 #include "config.h"
 #include "system.h"
 #include "coretypes.h"
@@ -58,6 +59,8 @@ along with GCC; see the file COPYING3.  If not see
 #include "alloc-pool.h"
 #include "symbol-summary.h"
 #include "symtab-thunks.h"
+#include "cfganal.h"
+#include "cfgloop.h"
 
 static GTY(()) tree gcov_type_node;
 static GTY(()) tree tree_interval_profiler_fn;
@@ -73,6 +76,841 @@ static GTY(()) tree ic_tuple_var;
 static GTY(()) tree ic_tuple_counters_field;
 static GTY(()) tree ic_tuple_callee_field;
 
+namespace {
+
+struct conds_ctx {
+    /* Output arrays allocated by the caller.  */
+    basic_block *blocks;
+    int *sizes;
+
+    /* The size of the blocks buffer.  This is just bug protection,
+       the caller should have allocated enough memory for blocks to never get
+       this many elements.
+     */
+    int maxsize;
+
+    /* Number of expressions found - this value is the number of entries in the
+       gcov output and the parameter to gcov_counter_alloc ().
+     */
+    int exprs;
+
+    /* Bitmap of the processed blocks - bit n set means basic_block->index has
+       been processed as a first-in-expression block.  This effectively stops
+       loop edges from being taken and subgraphs re-processed.
+     */
+    auto_sbitmap seen;
+
+    /* Pre-allocate bitmaps for per-function book keeping.  This is pure
+       instance reuse and the bitmaps carries no data between function calls.
+     */
+    auto_sbitmap expr;
+    auto_sbitmap reachable;
+
+    explicit conds_ctx (unsigned size) noexcept (true) : maxsize (0), exprs (0),
+        seen (size), expr (size), reachable (size)
+    {
+        bitmap_clear (seen);
+    }
+
+    void commit (basic_block top, int nblocks) noexcept (true)
+    {
+        blocks  += nblocks;
+        *sizes  += nblocks;
+        maxsize -= nblocks;
+
+        exprs++;
+        sizes++;
+        *sizes = 0;
+
+        bitmap_set_bit (seen, top->index);
+    }
+};
+
+/* Only instrument terms with fewer than number of bits in a (wide) gcov
+   integer, which is probably 64.  The algorithm itself does not impose this
+   limitation, but it makes for a simpler implementation.
+
+   * Allocating the output data structure (coverage_counter_alloc ()) can
+     assume pairs of gcov_type_unsigned and not use a separate length field.
+   * A pair gcov_type_unsigned can be used as accumulators.
+   * Updating accumulators is can use the bitwise operations |=, &= and not
+     custom operators that work for arbitrary-sized bit-sets.
+
+   Most real-world code should be unaffected by this, but it is possible
+   (especially for generated code) to exceed this limit.
+ */
+#define CONDITIONS_MAX_TERMS (sizeof (gcov_type_unsigned) * BITS_PER_UNIT)
+
+int
+index_of (const_basic_block needle, const const_basic_block *blocks, int size)
+noexcept (true)
+{
+    for (int i = 0; i < size; i++)
+    {
+        if (blocks[i] == needle)
+            return i;
+    }
+    return -1;
+}
+
+bool
+index_lt (const basic_block x, const basic_block y)
+noexcept (true)
+{
+    return x->index < y->index;
+}
+
+/* Special cases of the single_*_p and single_*_edge functions in basic-block.h
+   that don't consider exception handling or other complex edges.  This helps
+   create a view of the CFG with only normal edges - if a basic block has both
+   an outgoing fallthrough and exceptional edge [1], it should be considered a
+   single-successor.
+
+   [1] if this is not possible, these functions can be removed and replaced by
+       their basic-block.h cousins.
+ */
+bool
+single (const vec<edge, va_gc> *edges)
+noexcept (true)
+{
+    int n = EDGE_COUNT (edges);
+    if (n == 0)
+        return false;
+
+    for (edge e : edges)
+        if (e->flags & EDGE_COMPLEX)
+            n -= 1;
+
+    return n == 1;
+}
+
+edge
+single_edge (const vec<edge, va_gc> *edges)
+noexcept (true)
+{
+    for (edge e : edges)
+    {
+        if (e->flags & EDGE_COMPLEX)
+            continue;
+        return e;
+    }
+    gcc_unreachable ();
+}
+
+/* Sometimes, for example with function calls and C++ destructors the CFG gets
+   extra nodes that are essentially single-entry-single-exit in the middle of
+   boolean expressions.  For example:
+
+      x || can_throw (y)
+
+               A
+              /|
+             / |
+            B  |
+            |  |
+            C  |
+           / \ |
+          /   \|
+         F     T
+
+   Without the extra node inserted by the function + exception it becomes a
+   proper 2-term graph, not 2 single-term graphs.
+
+               A
+              /|
+             C |
+            / \|
+           F   T
+
+   contract_edge ignores the series of intermediate nodes and makes a virtual
+   edge A -> C, without having to construct a new simplified CFG explicitly.
+
+   Such an expression cannot correctly be repreted as two 1-term expressions,
+   as it would break the condition masking.
+ */
+edge
+contract_edge (edge e, sbitmap expr)
+noexcept (true)
+{
+    while (true)
+    {
+        basic_block src  = e->src;
+        basic_block dest = e->dest;
+
+        if (!single (dest->succs))
+            return e;
+        if (!single (dest->preds))
+            return e;
+        if (!single (src->preds))
+            return e;
+
+        edge succe = single_edge (dest->succs);
+        if (!single (succe->dest->preds))
+            return e;
+
+        bitmap_set_bit (expr, dest->index);
+        e = succe;
+    }
+}
+
+edge
+contract_edge_up (edge e, sbitmap expr)
+noexcept (true)
+{
+    while (true)
+    {
+        basic_block src  = e->src;
+        basic_block dest = e->dest;
+
+        if (!single (dest->succs))
+            return e;
+        if (!single (dest->preds))
+            return e;
+        if (!single (src->preds))
+            return e;
+
+        if (expr) bitmap_set_bit (expr, src->index);
+        e = single_pred_edge (src);
+    }
+}
+
+bool
+is_conditional_p (const basic_block b)
+noexcept (true)
+{
+    if (single_succ_p (b))
+        return false;
+
+    unsigned t = 0;
+    unsigned f = 0;
+    for (edge e : b->succs)
+    {
+        t |= (e->flags & EDGE_TRUE_VALUE);
+        f |= (e->flags & EDGE_FALSE_VALUE);
+    }
+    return t && f;
+}
+
+/* The first block in the output will always be the source block of the edge
+   that will apply the masking operation, with the remaining blocks effectively
+   unordered.
+ */
+int
+find_conditions_masked_by (
+    basic_block block,
+    const sbitmap expr,
+    const unsigned *flag,
+    basic_block *out,
+    int maxsize)
+noexcept (true)
+{
+    int n = 0;
+    for (edge e : block->preds)
+    {
+        /* Skip any predecessor not in the expression - there might be such an
+           edge to the enclosing expression or in the presence of loops, but
+           masking cannot happen outside the expression itself.
+         */
+        if (!bitmap_bit_p (expr, e->src->index))
+            continue;
+
+        e = contract_edge_up (e, NULL);
+        if (e->flags & flag[0])
+            out[n++] = e->src;
+    }
+
+    if (n > 1)
+    {
+        basic_block *top = std::max_element (out, out + n, index_lt);
+        std::iter_swap (top, out);
+    }
+
+    for (int pos = 0; pos < n; pos++)
+    {
+        for (edge e : out[pos]->preds)
+        {
+            if (!bitmap_bit_p (expr, e->src->index))
+                continue;
+            if (index_of (e->src, out, n) != -1)
+                continue;
+
+            e = contract_edge_up (e, NULL);
+            if (e->flags & flag[1])
+                out[n++] = e->src;
+
+            gcc_assert (n < maxsize);
+        }
+    }
+
+    return n;
+}
+
+/* Scan the blocks that make up an expression and look for conditions that
+   would mask other conditions.  For a deeper discussion on masking, see
+   Whalen, Heimdahl, De Silva "Efficient Test Coverage Measurement for MC/DC".
+   Masking is best illustrated with an example:
+
+   A || B.  If B is true then A will does not independently affect the decision.
+   In a way, this is "reverse" short circuiting, and always work on the
+   right-hand side of expressions.  * || true is always true and * && false is
+   always false - the left-hand-side does not affect the outcome, and their
+   values should not contribute to modifidied condition/decision coverage.
+
+   A || B interpreted as a decision diagram becomes:
+
+   A
+  t|\f
+   | \
+   |  B
+   |t/ \f
+   |/   \
+   T     F
+
+   The algorithm looks for triangles like ATB.  Masking right-hand sides happen
+   when a block has a pair of incoming edges of the same boolean value, and
+   there is an edge connecting the two predecessors with the *opposite* boolean
+   value.  The masking block is B, and the masked block is A.  In this
+   particular example:
+
+   Masking can affect "outside" its own subexpression; in A || (B && C) if C is
+   false, B is masked.  However, if (B && C) is true, A gets masked.  B && C
+   can be determined from evaluating C since !B would short-circuit, so a true
+   C would mask A.
+
+   A
+  t|\f
+   | \
+   |  \
+   |   \
+   |    B
+   |  t/ \f
+   |  C   |
+   |t/ \f |
+   |/   \ |
+   T     F
+
+   Notice how BFC forms a triangle.  Expressions masked by an edge are
+   determined by:
+   * Go to the predecessor with truth value b (if it exists).
+   * Follow the path of ancestors with taking only !b edges, recording every
+     node from here on.
+
+   For the case where C can mask A, the path goes C [true]-> B -> [false] A, so
+   C [true] masks A.
+
+   The mask is output as a bit-set stored in a gcov_type_unsigned.  The
+   bit-sets are output in pairs, one for each decision (the outcome of the
+   boolean expression, or which arc to take in the CFG).
+ */
+void
+find_subexpr_masks (
+    const basic_block *blocks,
+    int nblocks,
+    gcov_type_unsigned *masks)
+noexcept (true)
+{
+    const unsigned flags[] = {
+        EDGE_TRUE_VALUE,
+        EDGE_FALSE_VALUE,
+        EDGE_TRUE_VALUE,
+    };
+
+    basic_block path[CONDITIONS_MAX_TERMS];
+    auto_sbitmap expr (n_basic_blocks_for_fn (cfun));
+    bitmap_clear (expr);
+    for (int i = 0; i < nblocks; i++)
+        bitmap_set_bit (expr, blocks[i]->index);
+
+    for (int i = 0; i < nblocks; i++)
+    {
+        basic_block block = blocks[i];
+        if (single_pred_p (block))
+            continue;
+
+        for (int k = 0; k < 2; k++)
+        {
+            const int n = find_conditions_masked_by
+                (block, expr, flags + k, path, CONDITIONS_MAX_TERMS);
+
+            if (n < 2)
+                continue;
+
+            const int m = 2*index_of (path[0], blocks, nblocks) + k;
+            for (int i = 1; i < n; i++)
+            {
+                const int index = index_of (path[i], blocks, nblocks);
+                masks[m] |= gcov_type_unsigned (1) << index;
+            }
+        }
+    }
+}
+
+int
+collect_reachable_conditionals (
+    basic_block pre,
+    basic_block post,
+    basic_block *out,
+    int maxsize,
+    sbitmap expr)
+noexcept (true)
+{
+    gcc_assert (maxsize > 0);
+
+    basic_block loop = pre->loop_father->header;
+    int n = 0;
+    out[n++] = pre;
+    bitmap_set_bit (expr, pre->index);
+
+    for (int pos = 0; pos < n; pos++)
+    {
+        basic_block block = out[pos];
+
+        for (edge e : block->succs)
+        {
+            basic_block dest = contract_edge (e, expr)->dest;
+
+            /* Skip loop edges, as they go outside the expression.  */
+            if (dest == loop)
+                continue;
+            if (dest == post)
+                continue;
+            if (!is_conditional_p (dest))
+                continue;
+            /* Already-seen, don't re-add.  */
+            if (bitmap_bit_p (expr, dest->index))
+                continue;
+
+            bitmap_set_bit (expr, dest->index);
+            out[n++] = dest;
+            if (n == maxsize)
+                return n;
+        }
+    }
+
+    return n;
+}
+
+int
+collect_neighbors (basic_block *blocks, int nblocks, sbitmap reachable)
+noexcept (true)
+{
+    int n = 0;
+    basic_block *exits = blocks + nblocks;
+    for (int i = 0; i < nblocks; i++)
+    {
+        for (edge e : blocks[i]->succs)
+        {
+            if (bitmap_bit_p (reachable, e->dest->index))
+                continue;
+
+            bitmap_set_bit (reachable, e->dest->index);
+            exits[n++] = e->dest;
+        }
+    }
+
+    return n;
+}
+
+/* Find and isolate the first expression between two dominators.
+
+   Either block of a conditional could have more decisions and loops, so
+   isolate the first decision by set-intersecting all paths from the
+   post-dominator to the entry block.
+
+   The function returns the number of blocks from n that make up the leading
+   expression in prefix order (i.e. the order expected by the instrumenting
+   code).  When this function returns 0 there are no decisions between pre and
+   post, and this segment of the CFG can safely be skipped.
+
+   The post nodes can have predecessors that do not belong to this subgraph,
+   which are skipped.  This is expected, for example when there is a
+   conditional in the else-block of a larger expression:
+
+   if (A)
+{
+      if (B) {}
+   } else {
+      if (C) {} else {}
+   }
+
+             A
+          t / \ f
+           /   \
+          B     C
+         /\    / \
+        /  \  T   F
+       T    \  \ /
+        \   |   o
+         \  |  /
+          \ | /
+           \|/
+            E
+
+   Processing [B, E) which looks like:
+
+      B
+     /|
+    / |
+   T  |
+    \ /
+     E ----> o // predecessor outside [B, e)
+ */
+
+/* Do a (upwards) search for reachable nodes and mark them in the reachable
+   set, making sure not to take loop edges.  dfs_enumerate_from () won't work as
+   the filter function needs information from the edge.
+ */
+void
+find_reachable (
+    sbitmap reachable,
+    basic_block pre,
+    basic_block post,
+    basic_block *stack)
+noexcept (true)
+{
+    stack[0] = pre;
+    bitmap_set_bit (reachable, pre->index);
+    bitmap_set_bit (reachable, post->index);
+    for (int n = 0; n >= 0; n--)
+    {
+        for (edge e : stack[n]->preds)
+        {
+            if (bitmap_bit_p (reachable, e->src->index))
+                continue;
+
+            /* Ignore any loop edges.  */
+            if (dominated_by_p (CDI_DOMINATORS, e->src, e->dest))
+                continue;
+
+            basic_block src = contract_edge_up (e, reachable)->src;
+            bitmap_set_bit (reachable, src->index);
+            stack[n++] = src;
+        }
+    }
+}
+
+int
+find_first_conditional (conds_ctx &ctx, basic_block pre, basic_block post)
+noexcept (true)
+{
+    basic_block *blocks = ctx.blocks;
+    sbitmap expr = ctx.expr;
+    sbitmap reachable = ctx.reachable;
+    bitmap_clear (expr);
+    bitmap_clear (reachable);
+    blocks[0] = pre;
+
+    /* If there is a direct edge to the post dominator then this cannot only be
+       a single-term conditional *unless* it is a loop (in which case the
+       to-post edge is the loop exit edge).
+     */
+    const bool dowhile = !loop_exits_from_bb_p (pre->loop_father, pre);
+    const bool isloop = bb_loop_header_p (pre) && !dowhile;
+    if (find_edge (pre, post) && !isloop)
+        return 1;
+
+    const int nblocks = collect_reachable_conditionals
+        (pre, post, blocks, ctx.maxsize, expr);
+    if (nblocks == 1)
+        return nblocks;
+
+    bitmap_copy (reachable, expr);
+    const int nexits = collect_neighbors (blocks, nblocks, reachable);
+    if (nexits == 2)
+        return nblocks;
+
+    /* Find reachable nodes from the neighbors.  */
+    basic_block *exits = blocks + nblocks;
+    for (int i = 0; i < nexits; i++)
+    {
+        for (edge e : exits[i]->preds)
+        {
+            if (!dominated_by_p (CDI_DOMINATORS, e->src, pre))
+                continue;
+
+            bitmap_clear (reachable);
+            find_reachable (reachable, e->src, pre, exits + nexits);
+            for (edge f : exits[i]->preds)
+                bitmap_set_bit (reachable, f->src->index);
+            bitmap_and (expr, expr, reachable);
+        }
+    }
+
+    int k = 0;
+    for (int i = 0; i < nblocks; i++)
+        if (bitmap_bit_p (expr, blocks[i]->index))
+            blocks[k++] = blocks[i];
+    return k;
+}
+
+void
+emit_bitwise_op (edge e, tree lhs, tree op1, tree_code op, tree op2)
+noexcept (true)
+{
+    tree tmp;
+    gassign *read;
+    gassign *bitw;
+    gassign *write;
+    /* Insert lhs = op1 <bit-op> op2.  */
+    tmp   = make_temp_ssa_name  (gcov_type_node, NULL, "__conditions_tmp");
+    read  = gimple_build_assign (tmp, op1);
+    tmp   = make_temp_ssa_name  (gcov_type_node, NULL, "__conditions_tmp");
+    bitw  = gimple_build_assign (tmp, op, gimple_assign_lhs (read), op2);
+    write = gimple_build_assign (lhs, gimple_assign_lhs (bitw));
+
+    gsi_insert_on_edge (e, read);
+    gsi_insert_on_edge (e, bitw);
+    gsi_insert_on_edge (e, write);
+}
+
+/* Walk the CFG and collect conditionals.
+
+   1. Collect all nodes reachable from the root node through (contracted) paths
+      of true/false edges.
+   2. Collect the neighbors of the reachable node (set).
+   3. From every node in the neighborhood, walk the up the CFG and mark every
+      reachable node. Only the nodes reachable from *every* node in the
+      neighborhood are a part of the first expression.
+   4. Record the expression plus the two successors of the last (highest-index)
+      node in the expression, i.e. the last term.
+   5. Repeat using the two successors as new root nodes.
+
+   It is not guaranteed to find nodes in the order of the expression, i.e. it
+   might find (a || b) && c as [a c b], so the output is sorted by
+   basic_block->index.
+
+   Steps 2 and 3 are necessary to distinguish chained conditionals from
+   multi-term conditionals, e.g. to separate
+
+       if (a)
+       {
+           if (b)
+               work ();
+       }
+       if (a && b)
+           work ();
+ */
+void
+collect_conditions (conds_ctx& ctx, basic_block entry, basic_block exit)
+noexcept (true)
+{
+    basic_block pre;
+    basic_block post;
+    for (pre = entry ;; pre = post)
+    {
+        if (pre == exit)
+            break;
+        if (bitmap_bit_p (ctx.seen, pre->index))
+            break;
+
+        post = get_immediate_dominator (CDI_POST_DOMINATORS, pre);
+        if (!is_conditional_p (pre))
+        {
+            for (edge e : pre->succs)
+                collect_conditions (ctx, e->dest, post);
+            continue;
+        }
+
+        int nterms = find_first_conditional (ctx, pre, post);
+        std::sort (ctx.blocks, ctx.blocks + nterms, index_lt);
+        basic_block last = ctx.blocks[nterms - 1];
+        if (size_t (nterms) <= CONDITIONS_MAX_TERMS)
+        {
+            for (edge e : last->succs)
+                if (e->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE))
+                    ctx.blocks[nterms++] = e->dest;
+            ctx.commit (pre, nterms);
+        } else {
+            location_t loc = gimple_location (gsi_stmt (gsi_last_bb (pre)));
+            warning_at
+                (loc, OPT_Wcoverage_too_many_conditions,
+                 "Too many conditions (found %d); giving up coverage", nterms);
+        }
+
+        for (edge e : last->succs)
+            collect_conditions (ctx, e->dest, post);
+    }
+}
+
+}
+
+/* Condition coverage (MC/DC)
+
+   Algorithm
+   ---------
+   This is a modified version of the algorithm in Whalen, Heimdahl, De Silva
+   "Efficient Test Coverage Measurement for MC/DC".  Their algorithm work on
+   ASTs, but this algorithm work on control flow graphs.  The individual phases
+   are described in more detail closer to the implementation.
+
+   The CFG is broken up into segments between dominators.  This isn't strictly
+   necessary, but since boolean expressions cannot cross dominators it makes
+   for a nice way to reduce work.
+
+   The coverage only considers the positions, not the symbols, in a
+   conditional, e.g. !A || (!B && A) is a 3-term conditional even though A
+   appears twice.  Subexpressions have no effect on term ordering:
+   (a && (b || (c && d)) || e) comes out as [a b c d e].
+
+   The output for gcov is a vector of pairs of unsigned integers, interpreted
+   as bit-sets, where the bit index corresponds to the index of the condition
+   in the expression.
+
+   Implementation and interface
+   ----------------------------
+   Two public functions - find_conditions and instrument_decisions.
+
+   find_conditions outputs two arrays, blocks and sizes.  The sizes describes
+   the ranges of blocks that make up every conditional, in a [first, last)
+   fashion, i.e. begin = blocks[sizes[n]], end = blocks[sizes[n+1]] for
+   expression n.  The function returns the number of expressions.
+
+   The coverage is designed to get most of its memory needs met by the caller,
+   and heavily uses the end of the blocks array for buffer.  This is both for
+   performance (no resizes, amortized cost of allocation) and
+   ease-of-implementation.  This makes the caller responsible for allocating
+   large enough arrays.
+
+   blocks:
+    Every permanent conditions add 2 blocks (the true & false dest blocks), and
+    assuming a worst case of one-block-per-expr just storing the output needs
+    3*n_basic_blocks_for_fn ().  Additionally, the searches might need to buffer
+    the full graph between entry and exit [1]. In total that means
+    5*n_basic_blocks_for_fn () should should be plenty, and the savings for
+    reducing this number is probably not worth the risk.
+   sizes:
+    sizes gets one entry per expression plus initial, so
+    1+n_basic_blocks_for_fn () is sufficient.
+
+   instrument_decisions uses the information provided by find_conditions to
+   inject code onto edges in the CFG.  Every instrumented function gets local
+   accumulators zero'd on function entry, which on function exit are flushed to
+   the global accumulators (created by coverage_counter_alloc ()).
+
+   [1] In truth, the set of nodes that could be buffered gets smaller as the
+       algorithm walks the CFG, but assuming just using node-count comes at
+       little run-time cost and is guaranteed to be sufficient.
+ */
+int
+find_conditions (
+    basic_block entry,
+    basic_block exit,
+    basic_block *blocks,
+    int *sizes,
+    int maxsize)
+{
+    record_loop_exits ();
+    bool free_dom = false;
+    bool free_post_dom = false;
+    if (!dom_info_available_p (CDI_POST_DOMINATORS))
+    {
+        calculate_dominance_info (CDI_POST_DOMINATORS);
+        free_post_dom = true;
+    }
+
+    if (!dom_info_available_p (CDI_DOMINATORS))
+    {
+        calculate_dominance_info (CDI_DOMINATORS);
+        free_dom = true;
+    }
+
+    conds_ctx ctx (maxsize);
+    ctx.blocks = blocks;
+    ctx.sizes = sizes + 1;
+    ctx.maxsize = maxsize;
+    sizes[0] = sizes[1] = 0;
+    collect_conditions (ctx, entry, exit);
+
+    /* Partial sum.  */
+    for (int i = 0; i < ctx.exprs; ++i)
+        sizes[i + 1] += sizes[i];
+
+    if (free_post_dom)
+        free_dominance_info (CDI_POST_DOMINATORS);
+    if (free_dom)
+        free_dominance_info (CDI_DOMINATORS);
+
+    return ctx.exprs;
+}
+
+int instrument_decisions (basic_block *blocks, int nblocks, int condno)
+{
+    /* Insert function-local accumulators per decision.  */
+    tree accu[2] = {
+        build_decl (
+            UNKNOWN_LOCATION,
+            VAR_DECL,
+            get_identifier ("__conditions_accu_true"),
+            gcov_type_node),
+        build_decl (
+            UNKNOWN_LOCATION,
+            VAR_DECL,
+            get_identifier ("__conditions_accu_false"),
+            gcov_type_node),
+    };
+    for (tree acc : accu)
+    {
+        tree zero = build_int_cst (gcov_type_node, 0);
+        for (edge e : ENTRY_BLOCK_PTR_FOR_FN (cfun)->succs)
+            gsi_insert_on_edge (e, gimple_build_assign (acc, zero));
+    }
+
+    auto_vec<gcov_type_unsigned, 32> masks (nblocks * 2);
+    masks.quick_grow_cleared (nblocks * 2);
+    find_subexpr_masks (blocks, nblocks, masks.address ());
+
+    /* The true/false target blocks are included in the nblocks set, but
+       their outgoing edges should not be instrumented.
+     */
+    gcc_assert (nblocks > 2);
+
+    /* Add instructions for updating the function-local accumulators.  */
+    for (int i = 0; i < nblocks - 2; i++)
+    {
+        for (edge e : blocks[i]->succs)
+        {
+            if (!(e->flags & (EDGE_TRUE_VALUE | EDGE_FALSE_VALUE)))
+                continue;
+
+            /* accu |= expr[i] */
+            const int t = !!(e->flags & EDGE_FALSE_VALUE);
+            tree rhs = build_int_cst (gcov_type_node, 1ULL << i);
+            emit_bitwise_op (e, accu[t], accu[t], BIT_IOR_EXPR, rhs);
+
+            if (masks[2*i + t] == 0)
+                continue;
+
+            /* accu &= mask[i] */
+            tree mask = build_int_cst (gcov_type_node, ~masks[2*i + t]);
+            for (int j = 0; j < 2; j++)
+                emit_bitwise_op (e, accu[j], accu[j], BIT_AND_EXPR, mask);
+        }
+    }
+
+    /* Add instructions for updating the global accumulators.  */
+    basic_block exit = EXIT_BLOCK_PTR_FOR_FN (cfun);
+    for (edge e : exit->preds)
+    {
+        for (int k = 0; k < 2; k++)
+        {
+            tree ref = tree_coverage_counter_ref
+                (GCOV_COUNTER_CONDS, 2*condno + k);
+
+            tree tmp = make_temp_ssa_name
+                (gcov_type_node, NULL, "__conditions_tmp");
+            gassign *read = gimple_build_assign (tmp, ref);
+            gsi_insert_on_edge (e, read);
+
+            tree rop = gimple_assign_lhs (read);
+            emit_bitwise_op (e, unshare_expr (ref), accu[k], BIT_IOR_EXPR, rop);
+        }
+    }
+
+    return nblocks - 2;
+}
+
+#undef CONDITIONS_MAX_TERMS
+
 /* Do initialization work for the edge profiler.  */
 
 /* Add code:
-- 
2.20.1


^ permalink raw reply	[flat|nested] 39+ messages in thread

end of thread, other threads:[~2022-11-10 14:19 UTC | newest]

Thread overview: 39+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-10-12 10:16 [PATCH] Add condition coverage profiling Jørgen Kvalsvik
2022-10-18  0:17 ` Hans-Peter Nilsson
2022-10-18 10:13   ` Jørgen Kvalsvik
2022-10-25  6:33 ` Ping " Jørgen Kvalsvik
2022-10-27 10:34   ` Martin Liška
2022-11-02  6:16   ` Jørgen Kvalsvik
2022-11-09 20:08     ` [Ping 2][PATCH] " Jørgen Kvalsvik
2022-11-10 14:19     ` Ping [PATCH] " Martin Liška
  -- strict thread matches above, loose matches on Subject: below --
2022-07-15 11:39 Jørgen Kvalsvik
2022-07-15 11:47 ` Jørgen Kvalsvik
2022-07-15 13:31   ` Sebastian Huber
2022-07-15 13:47     ` Jørgen Kvalsvik
2022-08-02  7:58       ` Jørgen Kvalsvik
2022-08-04  7:43         ` Sebastian Huber
2022-08-04  9:13           ` Jørgen Kvalsvik
2022-07-11 10:02 Jørgen Kvalsvik
2022-07-12 14:05 ` Sebastian Huber
2022-07-13  2:04   ` Jørgen Kvalsvik
2022-03-21 11:55 Jørgen Kvalsvik
2022-03-24 16:08 ` Martin Liška
2022-03-25 19:44   ` Jørgen Kvalsvik
2022-03-28 13:39     ` Martin Liška
2022-03-28 13:52     ` Jørgen Kvalsvik
2022-03-28 14:40       ` Jørgen Kvalsvik
2022-04-07 12:04         ` Martin Liška
2022-04-19 14:22           ` Jørgen Kvalsvik
2022-04-07 16:53         ` Sebastian Huber
2022-04-08  7:28           ` Jørgen Kvalsvik
2022-04-08  7:33             ` Jørgen Kvalsvik
2022-04-08  8:50               ` Sebastian Huber
2022-04-04  8:14 ` Sebastian Huber
2022-04-05  7:04   ` Sebastian Huber
2022-04-05 20:07   ` Jørgen Kvalsvik
2022-04-06  7:35     ` Sebastian Huber
2022-04-17 11:27       ` Jørgen Kvalsvik
2022-04-22  5:37         ` Sebastian Huber
2022-04-22 10:13           ` Jørgen Kvalsvik
2022-07-08 13:45 ` Sebastian Huber
2022-07-11  7:26   ` Jørgen Kvalsvik

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).