public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH v5] Add condition coverage profiling
@ 2023-10-04 12:38 Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 01/22] " Jørgen Kvalsvik
                   ` (21 more replies)
  0 siblings, 22 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:38 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh

This is an extended set of patches with both review feedback from the v4 set,
as well as a few serious bug fixes and improvements, plus test cases.

There has only been a small-ish adjustment to the algorithm itself, in
expression isolation step. Because of gotos this might need to run multiple
times if it is detected that the expression subgraph has more than two
neighbors (in which case it *must* be more than a single Boolean expression).
This fixes some problems that mostly happen under optimization, but it also
helps correctness by having extra checks that what we found satisifies some
fundamental properties. Most other bugs were implementation errors.

I have broken the work up into smaller pieces that fix a single issue
each, including addressing review feedback. I hope this will make the patch set
easier to review and check how comments are addressed, but it also means that
most of the patch set does not have a the proper commit format etc. I plan to
squash most, if not all, of the commits before merge where this will be
addressed.

In September I proposed [1] an idea on how to fix the if (a) if (b) {} counted
as if (a && b) problem. If such a fix should be implemented no real changes
will have to happen to the tree profiling, the only change necessary to the
tree profiling is updating the test to reflect the counting.

To test the compiler I have built all dependencies (libraries, programs) to
build git, systemd, and openjdk, which includes llvm, perl, python, openssl,
icu, valgrind, and elfutils. This list is non-exhaustive. All these programs
now *build* with coverage enabled without failing (hitting an assert), but I
have not verified counting for these programs.

[1] https://gcc.gnu.org/pipermail/gcc-patches/2023-September/631254.html

---

 gcc/builtins.cc                        |    2 +-
 gcc/collect2.cc                        |    7 +-
 gcc/common.opt                         |    9 +
 gcc/doc/gcov.texi                      |   38 +
 gcc/doc/invoke.texi                    |   21 +
 gcc/gcc.cc                             |    4 +-
 gcc/gcov-counter.def                   |    3 +
 gcc/gcov-dump.cc                       |   24 +
 gcc/gcov-io.h                          |    3 +
 gcc/gcov.cc                            |  209 ++++-
 gcc/ipa-inline.cc                      |    2 +-
 gcc/ipa-split.cc                       |    3 +-
 gcc/passes.cc                          |    3 +-
 gcc/profile.cc                         |   92 +-
 gcc/testsuite/g++.dg/gcov/gcov-18.C    |  246 ++++++
 gcc/testsuite/gcc.misc-tests/gcov-19.c | 1471 ++++++++++++++++++++++++++++++++
 gcc/testsuite/gcc.misc-tests/gcov-20.c |   22 +
 gcc/testsuite/gcc.misc-tests/gcov-21.c |   16 +
 gcc/testsuite/gcc.misc-tests/gcov-22.c |   71 ++
 gcc/testsuite/gcc.misc-tests/gcov-23.c |  149 ++++
 gcc/testsuite/lib/gcov.exp             |  191 ++++-
 gcc/tree-profile.cc                    | 1123 +++++++++++++++++++++++-
 libgcc/libgcov-merge.c                 |    5 +
 23 files changed, 3688 insertions(+), 26 deletions(-)



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

* [PATCH 01/22] Add condition coverage profiling
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  2023-10-05 12:59   ` Jan Hubicka
  2023-10-04 12:39 ` [PATCH 02/22] Add "Condition coverage profiling" term to --help Jørgen Kvalsvik
                   ` (20 subsequent siblings)
  21 siblings, 1 reply; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

From: Jørgen Kvalsvik <jorgen.kvalsvik@woven-planet.global>

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. I have only tested it on primarily in C and C++,
see testsuite/gcc.misc-tests and testsuite/g++.dg, and run some manual
tests using D, Rust, and go. D and rust mostly behave as you would
expect, although sometimes conditions are really expanded and therefore
instrumented in another module than the one with the source, which also
applies to the branch coverage. It does not work as expected for go as
the go front end evaluates multi-conditional expressions by folding
results into temporaries.

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                        |    7 +-
 gcc/common.opt                         |    8 +
 gcc/doc/gcov.texi                      |   37 +
 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                            |  197 +++-
 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 | 1249 ++++++++++++++++++++++++
 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                    | 1048 +++++++++++++++++++-
 libgcc/libgcov-merge.c                 |    5 +
 21 files changed, 3135 insertions(+), 26 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 cb90bd03b3e..ecd1c189b3f 100644
--- a/gcc/builtins.cc
+++ b/gcc/builtins.cc
@@ -5879,7 +5879,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 63b9a0c233a..12ff5d81424 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 f137a1f81ac..94b1b358585 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -862,6 +862,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.
@@ -2377,6 +2381,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 3019efc4674..10cfdcf24aa 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.
@@ -301,6 +309,7 @@ Each @var{line} has the following form:
   "branches": ["$branch"],
   "calls": ["$call"],
   "count": 2,
+  "conditions": ["$condition"],
   "line_number": 15,
   "unexecuted_block": false,
   "function_name": "foo",
@@ -384,6 +393,34 @@ to @var{line::count})
 @var{destination_block_id}: ID of the basic block this calls continues after return
 @end itemize
 
+Each @var{condition} has the following form:
+
+@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 condition outcomes in this expression
+
+@item
+@var{covered}: number of covered condition outcomes 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 4085fc90907..21419b9a442 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -628,6 +628,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
+-fprofile-conditions
 -fprofile-abs-path
 -fprofile-dir=@var{path}  -fprofile-generate  -fprofile-generate=@var{path}
 -fprofile-info-section  -fprofile-info-section=@var{name}
@@ -6474,6 +6475,13 @@ poorly optimized code and is useful only in the
 case of very minor changes such as bug fixes to an existing code-base.
 Completely disabling the warning is not recommended.
 
+@opindex Wno-coverage-too-many-conditions
+@opindex Wcoverage-too-many-conditions
+@item -Wno-coverage-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.
+
 @opindex Wno-coverage-invalid-line-number
 @opindex Wcoverage-invalid-line-number
 @item -Wno-coverage-invalid-line-number
@@ -16627,6 +16635,13 @@ Note that if a command line directly links source files, the corresponding
 E.g. @code{gcc a.c b.c -o binary} would generate @file{binary-a.gcda} and
 @file{binary-b.gcda} files.
 
+@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.
+
 @xref{Cross-profiling}.
 
 @cindex @command{gcov}
@@ -16689,6 +16704,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 exercised boolean values that have an
+independent effect on the outcome of that expression.
+
 @need 2000
 @opindex ftest-coverage
 @item -ftest-coverage
diff --git a/gcc/gcc.cc b/gcc/gcc.cc
index c6e600fa0d3..f4d8e9bfa2c 100644
--- a/gcc/gcc.cc
+++ b/gcc/gcc.cc
@@ -1157,7 +1157,7 @@ proper position among the other output files.  */
 	%:include(libgomp.spec)%(link_gomp)}\
     %{fgnu-tm:%:include(libitm.spec)%(link_itm)}\
     " 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
@@ -1276,7 +1276,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 727ef424181..4d5b9c65a70 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 20e464022dc..65fa2d7b44b 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 bfe4439d02d..32ccd43743c 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 2fad6aa7ede..21a6da1a7fa 100644
--- a/gcc/gcov.cc
+++ b/gcc/gcov.cc
@@ -81,6 +81,7 @@ using namespace std;
 class function_info;
 class block_info;
 class source_info;
+class condition_info;
 
 /* Describes an arc between two basic blocks.  */
 
@@ -134,6 +135,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.  */
 
@@ -167,6 +190,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
@@ -277,6 +302,8 @@ public:
   vector<block_info> blocks;
   unsigned blocks_executed;
 
+  vector<condition_info*> conditions;
+
   /* Raw arc coverage counts.  */
   vector<gcov_type> counts;
 
@@ -353,6 +380,9 @@ struct coverage_info
   int branches_executed;
   int branches_taken;
 
+  int conditions;
+  int conditions_covered;
+
   int calls;
   int calls_executed;
 
@@ -552,6 +582,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;
 
@@ -658,6 +692,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 *);
@@ -666,6 +701,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 *);
@@ -930,6 +966,8 @@ 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 modified condition/decision\n\
+                                    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");
@@ -983,6 +1021,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' },
@@ -1011,7 +1050,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)
@@ -1028,6 +1067,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.  */
@@ -1158,6 +1200,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);
 }
 
@@ -1982,6 +2063,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 ();
@@ -2112,6 +2215,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);
@@ -2456,6 +2574,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.  */
 
@@ -2559,6 +2684,18 @@ file_summary (const coverage_info *coverage)
 		 coverage->calls);
       else
 	fnotice (stdout, "No calls\n");
+
+    }
+
+  if (flag_conditions)
+    {
+      if (coverage->conditions)
+	fnotice (stdout, "Condition outcomes covered:%s of %d\n",
+		 format_gcov (coverage->conditions_covered,
+			      coverage->conditions, 2),
+		 coverage->conditions);
+      else
+	fnotice (stdout, "No conditions\n");
     }
 }
 
@@ -2793,6 +2930,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
@@ -2894,6 +3037,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.  */
 
@@ -3104,16 +3274,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 dc120e6da5a..b540a79bfd2 100644
--- a/gcc/ipa-inline.cc
+++ b/gcc/ipa-inline.cc
@@ -682,7 +682,7 @@ can_early_inline_edge_p (struct cgraph_edge *e)
     }
   gcc_assert (gimple_in_ssa_p (DECL_STRUCT_FUNCTION (e->caller->decl))
 	      && gimple_in_ssa_p (DECL_STRUCT_FUNCTION (callee->decl)));
-  if (profile_arc_flag
+  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 6730f4f9d0e..276ead617c9 100644
--- a/gcc/ipa-split.cc
+++ b/gcc/ipa-split.cc
@@ -1930,7 +1930,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 6f894a41d22..02194fe286f 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 fc59326a19b..4407bb0683d 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));
@@ -1514,29 +1540,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))
@@ -1669,6 +1740,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;
 }
@@ -1708,5 +1780,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..73e268ab15e
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-19.c
@@ -0,0 +1,1249 @@
+/* { 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) */
+		    /* conditions(end) */
+	    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 (masking) 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
+       The implementation does not match the specification.  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) */
+	       /* conditions(end) */
+	{
+	    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) */
+	   /* conditions(end) */
+	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) */
+			  /* conditions(end) */
+}
+
+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 e5e94fa5a76..7e04b371e74 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 da300d5f9e8..c8b917afb9a 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,1046 @@ 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 c hecks.  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 +1800,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 +1845,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)
@@ -920,7 +1962,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 5d6e17d1483..eed3556373b 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.30.2


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

* [PATCH 02/22] Add "Condition coverage profiling" term to --help
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 01/22] " Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 03/22] Mention relevant flags in condition coverage docs Jørgen Kvalsvik
                   ` (19 subsequent siblings)
  21 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

From: Jørgen Kvalsvik <jorgen.kvalsvik@woven.toyota>

---
 gcc/common.opt | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/gcc/common.opt b/gcc/common.opt
index 94b1b358585..cd769ad95e0 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -864,7 +864,8 @@ Warn in case a function ends earlier than it begins due to an invalid linenum ma
 
 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.
+Warn when a conditional has too many terms and condition coverage profiling
+gives up instrumenting the expression.
 
 Wmissing-profile
 Common Var(warn_missing_profile) Init(1) Warning
-- 
2.30.2


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

* [PATCH 03/22] Mention relevant flags in condition coverage docs
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 01/22] " Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 02/22] Add "Condition coverage profiling" term to --help Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 04/22] Describe, remove ATTRIBUTE_UNUSED from tag_conditions Jørgen Kvalsvik
                   ` (18 subsequent siblings)
  21 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

From: Jørgen Kvalsvik <jorgen.kvalsvik@woven.toyota>

---
 gcc/doc/gcov.texi   |  3 ++-
 gcc/doc/invoke.texi | 14 ++++++++------
 2 files changed, 10 insertions(+), 7 deletions(-)

diff --git a/gcc/doc/gcov.texi b/gcc/doc/gcov.texi
index 10cfdcf24aa..f6db593a62a 100644
--- a/gcc/doc/gcov.texi
+++ b/gcc/doc/gcov.texi
@@ -175,7 +175,8 @@ the percentage of branches taken.
 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).
+boolean expression (modified condition/decision coverage).  This requires you
+to compile the source with @option{-fprofile-conditions}.
 
 @item -d
 @itemx --display-progress
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 21419b9a442..fbe6fa5c825 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -6478,9 +6478,10 @@ Completely disabling the warning is not recommended.
 @opindex Wno-coverage-too-many-conditions
 @opindex Wcoverage-too-many-conditions
 @item -Wno-coverage-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.
+Warn if @option{-fprofile-conditions} is used and an expression 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.
 
 @opindex Wno-coverage-invalid-line-number
 @opindex Wcoverage-invalid-line-number
@@ -16638,9 +16639,10 @@ E.g. @code{gcc a.c b.c -o binary} would generate @file{binary-a.gcda} and
 @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.
+program records what terms in a conditional contributes to a decision, which
+can be used to verify that all terms in a booleans are tested and have an
+independent effect on the outcome of a decision.  The result can be read with
+@code{gcov --conditions}.
 
 @xref{Cross-profiling}.
 
-- 
2.30.2


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

* [PATCH 04/22] Describe, remove ATTRIBUTE_UNUSED from tag_conditions
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
                   ` (2 preceding siblings ...)
  2023-10-04 12:39 ` [PATCH 03/22] Mention relevant flags in condition coverage docs Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 05/22] Describe condition_info Jørgen Kvalsvik
                   ` (17 subsequent siblings)
  21 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

From: Jørgen Kvalsvik <jorgen.kvalsvik@woven.toyota>

---
 gcc/gcov-dump.cc | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/gcc/gcov-dump.cc b/gcc/gcov-dump.cc
index 65fa2d7b44b..d85fa98ddd5 100644
--- a/gcc/gcov-dump.cc
+++ b/gcc/gcov-dump.cc
@@ -394,14 +394,14 @@ tag_arcs (const char *filename ATTRIBUTE_UNUSED,
     }
 }
 
+/* Print number of conditions (not outcomes, i.e. if (x && y) is 2, not 4) */
 static void
-tag_conditions (const char *filename ATTRIBUTE_UNUSED,
-		unsigned tag ATTRIBUTE_UNUSED, int length ATTRIBUTE_UNUSED,
+tag_conditions (const char *filename, unsigned /* tag */, int length,
 		unsigned depth)
 {
   unsigned n_conditions = GCOV_TAG_CONDS_NUM (length);
 
-  printf (" %u conditionals", n_conditions);
+  printf (" %u conditions", n_conditions);
   if (flag_dump_contents)
     {
       for (unsigned ix = 0; ix != n_conditions; ix++)
-- 
2.30.2


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

* [PATCH 05/22] Describe condition_info
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
                   ` (3 preceding siblings ...)
  2023-10-04 12:39 ` [PATCH 04/22] Describe, remove ATTRIBUTE_UNUSED from tag_conditions Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 06/22] Use popcount_hwi rather than builtin Jørgen Kvalsvik
                   ` (16 subsequent siblings)
  21 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

From: Jørgen Kvalsvik <jorgen.kvalsvik@woven.toyota>

---
 gcc/gcov.cc | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/gcc/gcov.cc b/gcc/gcov.cc
index 21a6da1a7fa..274f2fc5d9f 100644
--- a/gcc/gcov.cc
+++ b/gcc/gcov.cc
@@ -135,6 +135,8 @@ public:
   vector<unsigned> lines;
 };
 
+/* Describes a single conditional expression and the (recorded) conditions
+ * shown to independently affect the outcome.  */
 class condition_info
 {
 public:
@@ -142,9 +144,12 @@ public:
 
   int popcount () const;
 
+  /* Bitsets storing the independently significant outcomes for true and false,
+   * respectively.  */
   gcov_type_unsigned truev;
   gcov_type_unsigned falsev;
 
+  /* Number of terms in the expression; if (x) -> 1, if (x && y) -> 2 etc.  */
   unsigned n_terms;
 };
 
-- 
2.30.2


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

* [PATCH 06/22] Use popcount_hwi rather than builtin
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
                   ` (4 preceding siblings ...)
  2023-10-04 12:39 ` [PATCH 05/22] Describe condition_info Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  2023-10-05 13:01   ` Jan Hubicka
  2023-10-04 12:39 ` [PATCH 07/22] Describe add_condition_counts Jørgen Kvalsvik
                   ` (15 subsequent siblings)
  21 siblings, 1 reply; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

From: Jørgen Kvalsvik <jorgen.kvalsvik@woven.toyota>

---
 gcc/gcov.cc | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/gcc/gcov.cc b/gcc/gcov.cc
index 274f2fc5d9f..35be97cf5ac 100644
--- a/gcc/gcov.cc
+++ b/gcc/gcov.cc
@@ -46,6 +46,7 @@ along with Gcov; see the file COPYING3.  If not see
 #include "color-macros.h"
 #include "pretty-print.h"
 #include "json.h"
+#include "hwint.h"
 
 #include <zlib.h>
 #include <getopt.h>
@@ -159,7 +160,7 @@ condition_info::condition_info (): truev (0), falsev (0), n_terms (0)
 
 int condition_info::popcount () const
 {
-    return __builtin_popcountll (truev) + __builtin_popcountll (falsev);
+    return popcount_hwi (truev) + popcount_hwi (falsev);
 }
 
 /* Describes a basic block. Contains lists of arcs to successor and
-- 
2.30.2


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

* [PATCH 07/22] Describe add_condition_counts
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
                   ` (5 preceding siblings ...)
  2023-10-04 12:39 ` [PATCH 06/22] Use popcount_hwi rather than builtin Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 08/22] Describe output_conditions Jørgen Kvalsvik
                   ` (14 subsequent siblings)
  21 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

From: Jørgen Kvalsvik <jorgen.kvalsvik@woven.toyota>

---
 gcc/gcov.cc | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/gcc/gcov.cc b/gcc/gcov.cc
index 35be97cf5ac..95e0a11bc08 100644
--- a/gcc/gcov.cc
+++ b/gcc/gcov.cc
@@ -2580,6 +2580,8 @@ add_branch_counts (coverage_info *coverage, const arc_info *arc)
     }
 }
 
+/* Increment totals in COVERAGE according to to block BLOCK.  */
+
 static void
 add_condition_counts (coverage_info *coverage, const block_info *block)
 {
-- 
2.30.2


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

* [PATCH 08/22] Describe output_conditions
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
                   ` (6 preceding siblings ...)
  2023-10-04 12:39 ` [PATCH 07/22] Describe add_condition_counts Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 09/22] Find reachable conditions unbounded by dominators Jørgen Kvalsvik
                   ` (13 subsequent siblings)
  21 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

From: Jørgen Kvalsvik <jorgen.kvalsvik@woven.toyota>

---
 gcc/gcov.cc | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/gcc/gcov.cc b/gcc/gcov.cc
index 95e0a11bc08..62eac76a971 100644
--- a/gcc/gcov.cc
+++ b/gcc/gcov.cc
@@ -3045,6 +3045,10 @@ accumulate_line_counts (source_info *src)
       }
 }
 
+/* Output information about the conditions in block BINFO.  The output includes
+ * a summary (n/m outcomes covered) and a list of the missing (uncovered)
+ * outcomes.  */
+
 static void
 output_conditions (FILE *gcov_file, const block_info *binfo)
 {
-- 
2.30.2


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

* [PATCH 09/22] Find reachable conditions unbounded by dominators
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
                   ` (7 preceding siblings ...)
  2023-10-04 12:39 ` [PATCH 08/22] Describe output_conditions Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 10/22] Prune search for boolean expr on goto, return Jørgen Kvalsvik
                   ` (12 subsequent siblings)
  21 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

Search for reachable conditions without limiting the search to the nodes
dominated by the left-most term in the expression. Right operands are
conceptually still always dominated by the left-most term, but in
arbitrary programs there may be complex edges that breaks this.

As it turns out, it is not necessary to limit search by the dominator,
and it effectively only served as an optimization.

A new test case has been added with the code that triggered the bug.

gcc/ChangeLog:

	* tree-profile.cc (masking_vectors): Tweak comment.
	(cond_reachable_from): Remove dominated_by_p check.

gcc/testsuite/ChangeLog:

	* gcc.misc-tests/gcov-22.c: New test.
---
 gcc/testsuite/gcc.misc-tests/gcov-22.c | 40 ++++++++++++++++++++++++++
 gcc/tree-profile.cc                    | 15 +++-------
 2 files changed, 44 insertions(+), 11 deletions(-)
 create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-22.c

diff --git a/gcc/testsuite/gcc.misc-tests/gcov-22.c b/gcc/testsuite/gcc.misc-tests/gcov-22.c
new file mode 100644
index 00000000000..8aa8867dbf1
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-22.c
@@ -0,0 +1,40 @@
+/* { dg-options "-fprofile-conditions -ftest-coverage" } */
+/* { dg-do run { target native } } */
+
+#include <setjmp.h>
+jmp_buf buf;
+
+void noop() {}
+
+/* This function is a test to verify that the expression isolation does not
+   break on a CFG with the right set of complex edges.  The (_ && setjmp)
+   created complex edges after the function calls and a circular pair of
+   complex edges around the setjmp call.  This triggered a bug when the search
+   for right operands only would consider nodes dominated by the left-most
+   term, as this would only be the case if the complex edges were removed.
+
+   __builtin_setjmp did not trigger this, so we need setjmp from libc.  */
+void
+pathological001 (int a, int b, int c)
+{
+    if (a)  /* conditions(1/2) true(0) */
+	        /* conditions(end) */
+	noop ();
+
+    if (b)  /* conditions(1/2) false(0) */
+	        /* conditions(end) */
+	noop ();
+
+    if (c && setjmp (buf))  /* conditions(1/4) true(0 1) false(1) */
+			                /* conditions(end) */
+	noop ();
+}
+
+int
+main ()
+{
+    pathological001 (0, 1, 0);
+}
+
+
+/* { dg-final { run-gcov conditions { --conditions gcov-22.c } } } */
diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
index c8b917afb9a..adab0f59c07 100644
--- a/gcc/tree-profile.cc
+++ b/gcc/tree-profile.cc
@@ -619,10 +619,10 @@ masking_vectors (conds_ctx& ctx, array_slice<basic_block> blocks,
 }
 
 /* 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.
+   condition edges and ignoring 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.
@@ -631,11 +631,6 @@ masking_vectors (conds_ctx& ctx, array_slice<basic_block> blocks,
    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
@@ -651,8 +646,6 @@ cond_reachable_from (basic_block p, basic_block q, sbitmap expr,
 	    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))
-- 
2.30.2


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

* [PATCH 10/22] Prune search for boolean expr on goto, return
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
                   ` (8 preceding siblings ...)
  2023-10-04 12:39 ` [PATCH 09/22] Find reachable conditions unbounded by dominators Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 11/22] Add test case showing cross-decision fusing Jørgen Kvalsvik
                   ` (11 subsequent siblings)
  21 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

The search for the initial candidate set for a decision is a delicate
affair in complex CFGs with gotos (mostly from returns). Gotos and
returns in the then-block could create graphs where the goto/return edge
would also be searched for candidates for G, even though they can never
be a part of the expression being searched for.

Nodes may have complex predecessor edges which should be silently
ignored. Complex edges signify some odd or abnormal control flow, and
the flow of evaluation of terms in a boolean expression certainly does
not fall into this category.
---
 gcc/testsuite/gcc.misc-tests/gcov-19.c | 53 ++++++++++++++++++++++++--
 gcc/tree-profile.cc                    | 33 +++++++++++++++-
 2 files changed, 82 insertions(+), 4 deletions(-)

diff --git a/gcc/testsuite/gcc.misc-tests/gcov-19.c b/gcc/testsuite/gcc.misc-tests/gcov-19.c
index 73e268ab15e..662f4ca2864 100644
--- a/gcc/testsuite/gcc.misc-tests/gcov-19.c
+++ b/gcc/testsuite/gcc.misc-tests/gcov-19.c
@@ -312,6 +312,30 @@ begin:
 	goto then3;
 }
 
+void
+noop () {}
+
+int
+mcdc007d (int a, int b, int c, int d, int e)
+{
+    noop ();
+    if (a)  /* conditions(1/2) true(0) */
+	    /* conditions(end) */
+    {
+	if (b || c) /* conditions(0/4) true(0 1) false(0 1) */
+		    /* conditions(end) */
+	    x = 2;
+	if (d)	/* conditions(0/2) true(0) false(0) */
+		/* conditions(end) */
+	    return 1;
+    }
+    if (e)  /* conditions(1/2) false(0) */
+	    /* conditions(end) */
+	return 0;
+
+    return 2;
+}
+
 /* while loop */
 void
 mcdc008a (int a)
@@ -557,9 +581,6 @@ mcdc017a (int a)
     } while (a > 0); /* conditions(2/2) */
 }
 
-void
-noop () {}
-
 void
 mcdc017b (int a, int b)
 {
@@ -845,6 +866,29 @@ mcdc022d (int a)
 	x = a + 1;
 }
 
+/* Adapted from openssl-3.0.1/crypto/cmp/cmp_msg.c ossl_cmp_error_new ().
+   Without proper limiting of the initial candidate search this misidentified
+   { ...; if (fn ()) goto err; } if (c) goto err; as a 2-term expression.  */
+void
+mcdc022e (int a, int b, int c, int d)
+{
+    if (a || b) /* conditions(1/4) true(0) false(0 1) */
+		/* conditions(end) */
+    {
+	if (always (c)) /* conditions(1/2) false(0) */
+			/* conditions(end) */
+	    goto err;
+    }
+
+    if (d)  /* conditions(0/2) true(0) false(0) */
+	    /* conditions(end) */
+	goto err;
+    return;
+
+err:
+    noop ();
+}
+
 /* 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
@@ -1128,6 +1172,8 @@ int main ()
     mcdc007c (0, 1, 1);
     mcdc007c (1, 0, 1);
 
+    mcdc007d (0, 1, 0, 1, 1);
+
     mcdc008a (0);
 
     mcdc008b (0);
@@ -1232,6 +1278,7 @@ int main ()
     mcdc022c (1);
 
     mcdc022d (1);
+    mcdc022e (0, 1, 1, 0);
 
     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);
diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
index adab0f59c07..f98bb1f76c8 100644
--- a/gcc/tree-profile.cc
+++ b/gcc/tree-profile.cc
@@ -618,11 +618,40 @@ masking_vectors (conds_ctx& ctx, array_slice<basic_block> blocks,
     }
 }
 
+/* Check that all predecessors are conditional and belong to the current
+   expression.  This check is necessary in the presence of gotos, setjmp and
+   other complicated control flow that creates extra edges and creates odd
+   reachable paths from mid-expression terms and paths escaping nested
+   expressions.  If a node has an incoming non-complex edge (after contraction)
+   it can not be a part of a single, multi-term conditional expression.
+
+   If the expr[i] is set then nodes[i] is reachable from the leftmost operand
+   and b is a viable candidate.  Otherwise, this has to be an independent but
+   following expression.
+ */
+bool
+all_preds_conditional_p (basic_block b, const sbitmap expr)
+{
+    for (edge e : b->preds)
+    {
+	e = contract_edge_up (e);
+	if (!(e->flags & (EDGE_CONDITION | EDGE_COMPLEX)))
+	    return false;
+
+	if (!bitmap_bit_p (expr, e->src->index))
+	    return false;
+    }
+    return true;
+}
+
 /* Find the nodes reachable from p by following only (possibly contracted)
    condition edges and ignoring 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.
+   G is the returned explicitly by this function and forms the candidate set
+   for an expression.  All nodes in an expression should be connected only by
+   true|false edges, so a node with a non-conditional predecessor must be a
+   part of a different expression and in G', not G.
 
    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.
@@ -652,6 +681,8 @@ cond_reachable_from (basic_block p, basic_block q, sbitmap expr,
 		continue;
 	    if (e->flags & EDGE_DFS_BACK)
 		continue;
+	    if (!all_preds_conditional_p (dest, expr))
+		continue;
 
 	    bitmap_set_bit (expr, dest->index);
 	    out.safe_push (dest);
-- 
2.30.2


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

* [PATCH 11/22] Add test case showing cross-decision fusing
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
                   ` (9 preceding siblings ...)
  2023-10-04 12:39 ` [PATCH 10/22] Prune search for boolean expr on goto, return Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 12/22] Do two-phase filtering in expr isolation Jørgen Kvalsvik
                   ` (10 subsequent siblings)
  21 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

Some expressions create CFGs isomorphic to those with multi-term
expressions, e.g. if (a) if (b) {} is equivalent to if (a && b) {}, and
there is no real recovery for this. The test is added to 1. document the
behaviour and 2. detect if the cfg generation changes in a way that
(correctly) splits the ifs.

Note that some arithmetic is performed between the else-if and the
following if. This is not sufficient to create a new basic block, and
all sorts of arithmetic and side effect is possible between terms in an
arbitrary boolean expression anyway, for example if (a++ && --b).
---
 gcc/testsuite/gcc.misc-tests/gcov-19.c | 22 ++++++++++++++++++++++
 1 file changed, 22 insertions(+)

diff --git a/gcc/testsuite/gcc.misc-tests/gcov-19.c b/gcc/testsuite/gcc.misc-tests/gcov-19.c
index 662f4ca2864..5b5c1c275b0 100644
--- a/gcc/testsuite/gcc.misc-tests/gcov-19.c
+++ b/gcc/testsuite/gcc.misc-tests/gcov-19.c
@@ -146,6 +146,26 @@ mcdc004e (int a, int b, int c)
     }
 }
 
+/* else-if is not immune to the else-less fuse.  This test is also put in as a
+ * detection mechanism - sif this should register as 3 individual decisions
+ * then the test should be updated and fixed to reflect it.  */
+int
+mcdc004f (int a, int b, int c)
+{
+    if (a)  /* conditions(1/2) false(0) */
+	    /* conditions(end) */
+    {
+	x = 1;
+    }
+    else if (b) /* conditions(0/4) true(0 1) false(0 1) */
+		/* conditions(end) */
+    {
+	x = 2;
+	if (c)
+	    x = 3;
+    }
+}
+
 /* mixing && and || works */
 void
 mcdc005a (int a, int b, int c)
@@ -1137,6 +1157,8 @@ int main ()
     mcdc004e (0, 0, 0);
     mcdc004e (1, 1, 1);
 
+    mcdc004f (1, 1, 1);
+
     mcdc005a (1, 0, 1);
 
     mcdc005b (1, 1, 0, 0);
-- 
2.30.2


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

* [PATCH 12/22] Do two-phase filtering in expr isolation
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
                   ` (10 preceding siblings ...)
  2023-10-04 12:39 ` [PATCH 11/22] Add test case showing cross-decision fusing Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 13/22] Handle split-outcome with intrusive flag Jørgen Kvalsvik
                   ` (9 subsequent siblings)
  21 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

In complex control flow graphs where gotos and returns escape the inner
then-blocks and the right else-blocks are omitted, inner conditions
would erroneously be considered a part of the outer. By using the
observation that the neighborhood of a proper boolean expression should
always be the two outcome nodes we can repeat the search and ancestor
filtering to pinpoint the proper expression. This is helped by replacing
every pair of nodes that share some dominator in the candidate set that
is not the leading term, as that dominator must be the leading term in
some other boolean expression than the one we are looking for.
---
 gcc/testsuite/gcc.misc-tests/gcov-19.c | 125 +++++++++++++++++++++++++
 gcc/testsuite/gcc.misc-tests/gcov-22.c |  31 ++++++
 gcc/tree-profile.cc                    |  96 ++++++++++++++++++-
 3 files changed, 251 insertions(+), 1 deletion(-)

diff --git a/gcc/testsuite/gcc.misc-tests/gcov-19.c b/gcc/testsuite/gcc.misc-tests/gcov-19.c
index 5b5c1c275b0..8d6eb610af2 100644
--- a/gcc/testsuite/gcc.misc-tests/gcov-19.c
+++ b/gcc/testsuite/gcc.misc-tests/gcov-19.c
@@ -1036,6 +1036,10 @@ mcdc023g (int a, int b, int c, int d, int e, int f, int g, int h, int i, int k,
 	x = b + c;
 }
 
+/* Gotos, return, labels can make odd graphs.  It is important that conditions
+   are assigned to the right expression, and that there are no miscounts.  In
+   these tests values may be re-used, as checking things like masking an
+   independence is done in other test cases and not so useful here.  */
 void
 mcdc024a (int a, int b)
 {
@@ -1117,6 +1121,123 @@ label4:
     }
 }
 
+int
+mcdc024d (int a, int b, int c)
+{
+    /* Graphs can get complicated with the innermost returns and else-less if,
+       so we must make sure these conditions are counted correctly.  */
+    if (a)  /* conditions(1/2) true(0) */
+	    /* conditions(end) */
+    {
+	if (b)	/* conditions(0/2) true(0) false(0) */
+		/* conditions(end) */
+	{
+	    if (c)  /* conditions(0/2) true(0) false(0) */
+		    /* conditions(end) */
+		return 1;
+	    else
+		return 2;
+	}
+
+	if (a)	/* conditions(0/2) true(0) false(0) */
+		/* conditions(end) */
+	    return 3;
+    }
+
+    return 5;
+}
+
+/* Nested else-less ifs with inner returns needs to be counted right, which
+   puts some pressure on the expression isolation.  The fallthrough from inner
+   expressions into the next if cause the cfg to have edges from deeper in
+   subexpression to the next block sequence, which can confuse the expression
+   isolation. */
+int
+mcdc024e (int a, int b, int c)
+{
+    if (a)  /* conditions(1/2) true(0) */
+	    /* conditions(end) */
+    {
+	if (b)	/* conditions(0/2) true(0) false(0) */
+		/* conditions(end) */
+	{
+	    if (c)  /* conditions(0/2) true(0) false(0) */
+		    /* conditions(end) */
+	    {
+		if (a)	/* conditions(0/2) true(0) false(0) */
+			/* conditions(end) */
+		    return 1;
+		else
+		    return 2;
+	    }
+
+	    if (a)  /* conditions(0/2) true(0) false(0) */
+		    /* conditions(end) */
+		return 3;
+	}
+
+	if (b)	/* conditions(0/2) true(0) false(0) */
+		/* conditions(end) */
+	    return 4;
+    }
+    return 5;
+}
+
+int
+mcdc024f (int a, int b, int c)
+{
+    if (b)  /* conditions(1/2) true(0) */
+	    /* conditions(end) */
+	return 0;
+
+    if (a)  /* conditions(1/2) true(0) */
+	    /* conditions(end) */
+    {
+	if (b)	/* conditions(0/2) true(0) false(0) */
+		/* conditions(end) */
+	{
+	    b += 2;
+	    if (b & 0xFF)   /* conditions(0/2) true(0) false(0) */
+			    /* conditions(end) */
+		c++;
+
+	    return c;
+	}
+	c += 10;
+    }
+}
+
+
+int
+mcdc024g (int a, int b, int c)
+{
+    if (b)  /* conditions(1/2) true(0) */
+	    /* conditions(end) */
+	goto inner;
+
+    if (a)  /* conditions(1/2) true(0) */
+	    /* conditions(end) */
+	++a;
+
+
+    if (a)  /* conditions(1/2) true(0) */
+	    /* conditions(end) */
+    {
+	if (b)	/* conditions(0/2) true(0) false(0) */
+		/* conditions(end) */
+	{
+inner:
+	    b += 2;
+	    if (b & 0xFF)   /* conditions(0/2) true(0) false(0) */
+			    /* conditions(end) */
+		c++;
+
+	    return c;
+	}
+	c += 10;
+    }
+}
+
 int main ()
 {
     mcdc001a (0, 1);
@@ -1313,6 +1434,10 @@ int main ()
     mcdc024a (0, 0);
     mcdc024b (0, 0);
     mcdc024c (0, 0);
+    mcdc024d (0, 0, 0);
+    mcdc024e (0, 0, 0);
+    mcdc024f (0, 0, 0);
+    mcdc024g (0, 0, 0);
 }
 
 /* { dg-final { run-gcov conditions { --conditions gcov-19.c } } } */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-22.c b/gcc/testsuite/gcc.misc-tests/gcov-22.c
index 8aa8867dbf1..28b7de66022 100644
--- a/gcc/testsuite/gcc.misc-tests/gcov-22.c
+++ b/gcc/testsuite/gcc.misc-tests/gcov-22.c
@@ -5,6 +5,7 @@
 jmp_buf buf;
 
 void noop() {}
+int identity(int x) { return x; }
 
 /* This function is a test to verify that the expression isolation does not
    break on a CFG with the right set of complex edges.  The (_ && setjmp)
@@ -30,10 +31,40 @@ pathological001 (int a, int b, int c)
 	noop ();
 }
 
+///* Adapted from freetype-2.13.0 gxvalid/gxvmod.c classic_kern_validate */
+int
+pathological002 (int a)
+{
+    int error = identity(a);
+
+    if (error)	/* conditions(1/2) true(0) */
+		/* conditions(end) */
+	goto Exit;
+
+   if (a+1) /* conditions(1/2) false(0) */
+	    /* conditions(end) */
+   {
+       noop ();
+       if (setjmp (buf))    /* conditions(1/2) true(0) */
+			    /* conditions(end) */
+	   noop ();
+
+	if (error)  /* conditions(1/2) true(0) */
+		    /* conditions(end) */
+	    noop ();
+   }
+
+   error--;
+
+Exit:
+   return error;
+}
+
 int
 main ()
 {
     pathological001 (0, 1, 0);
+    pathological002 (0);
 }
 
 
diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
index f98bb1f76c8..ab96b872db3 100644
--- a/gcc/tree-profile.cc
+++ b/gcc/tree-profile.cc
@@ -292,6 +292,8 @@ contract_edge (edge e)
 	    return source;
 	if (block_conditional_p (dest))
 	    return e;
+	if (dest->index == EXIT_BLOCK)
+	    return source;
 
 	e = single_edge (dest->succs);
 	if (!e)
@@ -360,6 +362,8 @@ merge_split_outcome (basic_block b)
     edge e = single_edge (b->succs);
     for (edge pred : e->dest->preds)
     {
+	if (!(pred->flags & EDGE_FALLTHRU))
+	    return b;
 	if (!single (pred->src->preds))
 	    return b;
 	if (!(single_edge (pred->src->preds)->flags & flag))
@@ -727,7 +731,13 @@ neighborhood (const vec<basic_block>& blocks, sbitmap G, vec<basic_block>& out)
    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).  */
+   ancestors of the nodes in the neighborhood N(G).
+
+   In complex graphs this may capture more than the expression proper.  In that
+   case, perform the algorithm again but on the neighborhood N(B) rather than
+   N(G).  Unless there is complex control flow with deep early returns, gotos
+   and else-less ifs N(B) will be the two outcome nodes, otherwise search again
+   after replacing nodes with their common dominator.  */
 void
 isolate_expression (conds_ctx &ctx, basic_block p, vec<basic_block>& out)
 {
@@ -765,6 +775,90 @@ isolate_expression (conds_ctx &ctx, basic_block p, vec<basic_block>& out)
 	if (bitmap_bit_p (expr, b->index))
 	    out.safe_push (b);
     out.sort (cmp_index_map, &ctx.index_map);
+
+
+    /* In a lot of programs we would be done now, but in complex CFGs with
+       gotos or returns from nested then-blocks we need some post processing.
+
+       Essentially, perform another step of the algorithm - find the
+       neighborhood NG' of the candidate expression B.  If |B| == 2 we have the
+       two outcome nodes and are done.  If not, we know the previous step must
+       have captured something in a nested expression (probably because of a
+       fallthrough from the then-block being merged into the next block.  If
+       two nodes are dominated by some node lower (according to topsort) in the
+       CFG then the dominating node is the first term in some conditional
+       expression, which means it cannot be a part of the expression we're
+       searching for.  We redo the ancestor filtering, but now use a more
+       precise neighborhood that is unaffected.  */
+
+    NG.truncate (0);
+    neighborhood (out, expr, NG);
+    if (NG.length () == 2)
+	return;
+
+    /* Names are reused in this section and generally mean the same thing.  */
+    bitmap_clear (reachable);
+    for (const basic_block b : NG)
+	bitmap_set_bit (reachable, b->index);
+
+    /* If a pair of nodes has a shared dominator *that is not the root*
+       then that dominator must be the first term of another boolean
+       expression or some other structure outside the expression.  */
+    gcc_assert (!NG.is_empty ());
+    for (unsigned i = 0; i != NG.length () - 1; i++)
+    {
+	for (unsigned j = i+1; j != NG.length (); j++)
+	{
+	    auto b = nearest_common_dominator (CDI_DOMINATORS, NG[i], NG[j]);
+	    if (ctx.index_map[b->index] > ctx.index_map[p->index])
+	    {
+		bitmap_clear_bit (reachable, NG[i]->index);
+		bitmap_clear_bit (reachable, NG[j]->index);
+		bitmap_set_bit (reachable, b->index);
+	    }
+	}
+    }
+
+    NG.truncate (0);
+    for (basic_block b : G)
+	if (bitmap_bit_p (reachable, b->index))
+	    NG.safe_push (b);
+
+    bitmap_copy (reachable, expr);
+    for (const basic_block neighbor : NG)
+    {
+	bitmap_clear (ancestors);
+	for (edge e : neighbor->preds)
+	{
+	    /* The e edge might have been contracted past when doing the
+	       first scan, so we must make sure its bit is set to not think it
+	       is outside of our candidate set.  */
+	    bitmap_set_bit (reachable, e->src->index);
+	    ancestors_of (e->src, p, reachable, ancestors);
+	}
+	bitmap_and (expr, expr, ancestors);
+    }
+
+    out.truncate (0);
+    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);
+
+    bitmap_clear (expr);
+    for (const basic_block b : out)
+	bitmap_set_bit (expr, b->index);
+
+    /* Perform a final step as a sanity check - if the neighborhood is still
+       larger than the outcome set (|NG| == 2) the graph is too complex and we
+       give up instrumentation.  Any graph that exhibit this behaviour should
+       be studied as it might reveal an implementation error.  */
+    NG.truncate (0);
+    neighborhood (out, expr, NG);
+    bitmap_clear (expr);
+    for (const basic_block b : NG)
+	bitmap_set_bit (expr, b->index);
+    gcc_assert (bitmap_count_bits (expr) == 2);
 }
 
 /* Emit lhs = op1 <op> op2 on edges.  This emits non-atomic instructions and
-- 
2.30.2


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

* [PATCH 13/22] Handle split-outcome with intrusive flag
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
                   ` (11 preceding siblings ...)
  2023-10-04 12:39 ` [PATCH 12/22] Do two-phase filtering in expr isolation Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 14/22] Unify expression candidate set refinement logic Jørgen Kvalsvik
                   ` (8 subsequent siblings)
  21 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

When the coverage and branch profiling preprocessing happens it can
choose to split certain nodes with labels to accuractely track coverage.
These shapes were recongized, but this causes some expressions to be
counted wrong as this graph may be isomorphic with graphs that have not
been split, for example for this snippet:

    if (b)
        return 0;

    if (a)
    {
        if (b)
        {
            b += 2;
            if (b & 0xFF)
                c++;
            else
                c--;

            return c;
        }
        else
        {
            a++;
        }
        c += 10;
    }
    else
    {
        b++;
    }

This is a nice approach since the need for detecting this pattern only
comes from the coverage instrumentation splitting the edge in the first
place.
---
 gcc/profile.cc                         |  8 ++++++++
 gcc/testsuite/gcc.misc-tests/gcov-19.c |  7 +++----
 gcc/tree-profile.cc                    | 24 ++++--------------------
 3 files changed, 15 insertions(+), 24 deletions(-)

diff --git a/gcc/profile.cc b/gcc/profile.cc
index 4407bb0683d..00975661b0d 100644
--- a/gcc/profile.cc
+++ b/gcc/profile.cc
@@ -1257,6 +1257,9 @@ branch_prob (bool thunk)
 		  basic_block new_bb = split_edge (e);
 		  edge ne = single_succ_edge (new_bb);
 		  ne->goto_locus = e->goto_locus;
+		  /* Mark the edge with IGNORE so condition coverage knows that
+		     the edge split occurred and this should be contracted.  */
+		  ne->flags |= EDGE_IGNORE;
 		}
 	      if ((e->flags & (EDGE_ABNORMAL | EDGE_ABNORMAL_CALL))
 		   && e->dest != EXIT_BLOCK_PTR_FOR_FN (cfun))
@@ -1608,6 +1611,11 @@ branch_prob (bool thunk)
   /* Commit changes done by instrumentation.  */
   gsi_commit_edge_inserts ();
 
+  /* Unset all EDGE_IGNORE set in this pass.  */
+  FOR_EACH_BB_FN (bb, cfun)
+    for (edge e : bb->succs)
+      e->flags &= ~EDGE_IGNORE;
+
   coverage_end_function (lineno_checksum, cfg_checksum);
   if (flag_branch_probabilities
       && (profile_status_for_fn (cfun) == PROFILE_READ))
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-19.c b/gcc/testsuite/gcc.misc-tests/gcov-19.c
index 8d6eb610af2..1c671f7e186 100644
--- a/gcc/testsuite/gcc.misc-tests/gcov-19.c
+++ b/gcc/testsuite/gcc.misc-tests/gcov-19.c
@@ -299,12 +299,11 @@ then:
     return;
 begin:
     /* Evaluates to if (a || b || c) x = 1 */
-    if (a)	    /* conditions(5/6) true(2) */
-		    /* conditions(end) */
+    if (a)	/* conditions(2/2) */
 	goto then;
-    else if (b)
+    else if (b)	/* conditions(2/2) */
 	goto then;
-    else if (c)
+    else if (c) /* conditions(1/2) true(0) */
 	goto then;
 }
 
diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
index ab96b872db3..3fc78ff8ace 100644
--- a/gcc/tree-profile.cc
+++ b/gcc/tree-profile.cc
@@ -344,32 +344,16 @@ contract_edge_up (edge e)
     |  \|
     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.  */
+    When this split happens it flags the edge with EDGE_IGNORE.  */
 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 (!(pred->flags & EDGE_FALLTHRU))
-	    return b;
-	if (!single (pred->src->preds))
-	    return b;
-	if (!(single_edge (pred->src->preds)->flags & flag))
-	    return b;
-    }
-    return e->dest;
+    if (e->flags & EDGE_IGNORE)
+	return e->dest;
+    return b;
 }
 
 
-- 
2.30.2


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

* [PATCH 14/22] Unify expression candidate set refinement logic
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
                   ` (12 preceding siblings ...)
  2023-10-04 12:39 ` [PATCH 13/22] Handle split-outcome with intrusive flag Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 15/22] Fix candidate, neighborhood set reduction phase Jørgen Kvalsvik
                   ` (7 subsequent siblings)
  21 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

It always felt odd that the expression candidate set refinement were in
two more-or-less identical phases. It turns out that this is not
necessary and the neighborhood, ancestor filtering, update candidate set
steps can all be done inside a single loop, which terminates when
|NG| == |Outcomes| == 2. This also provides a nice opportunity for some
error sensitivity - if the candidate set cannot be reduced any more, and
the neighborhood |NG| != 2 we error out.

It is unclear if this graph can possibly be constructed, so for now it
uses an assert for sensitivity to it. What this should really do is drop
the instrumentation for the function altogether, and give a warning.
---
 gcc/tree-profile.cc | 139 ++++++++++++++++----------------------------
 1 file changed, 49 insertions(+), 90 deletions(-)

diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
index 3fc78ff8ace..b75736a22a0 100644
--- a/gcc/tree-profile.cc
+++ b/gcc/tree-profile.cc
@@ -114,9 +114,10 @@ struct conds_ctx
     auto_sbitmap G1;
     auto_sbitmap G2;
     auto_sbitmap G3;
+    auto_sbitmap G4;
 
     explicit conds_ctx (unsigned size) noexcept (true) : marks (size),
-    G1 (size), G2 (size), G3 (size)
+    G1 (size), G2 (size), G3 (size), G4 (size)
     {
 	bitmap_clear (marks);
     }
@@ -728,13 +729,14 @@ isolate_expression (conds_ctx &ctx, basic_block p, vec<basic_block>& out)
     sbitmap expr = ctx.G1;
     sbitmap reachable = ctx.G2;
     sbitmap ancestors = ctx.G3;
+    sbitmap prev = ctx.G3;
     bitmap_clear (expr);
     bitmap_clear (reachable);
+    bitmap_clear (prev);
 
     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);
@@ -744,105 +746,62 @@ isolate_expression (conds_ctx &ctx, basic_block p, vec<basic_block>& out)
 	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);
-
-
-    /* In a lot of programs we would be done now, but in complex CFGs with
-       gotos or returns from nested then-blocks we need some post processing.
-
-       Essentially, perform another step of the algorithm - find the
-       neighborhood NG' of the candidate expression B.  If |B| == 2 we have the
-       two outcome nodes and are done.  If not, we know the previous step must
-       have captured something in a nested expression (probably because of a
-       fallthrough from the then-block being merged into the next block.  If
-       two nodes are dominated by some node lower (according to topsort) in the
-       CFG then the dominating node is the first term in some conditional
-       expression, which means it cannot be a part of the expression we're
-       searching for.  We redo the ancestor filtering, but now use a more
-       precise neighborhood that is unaffected.  */
-
-    NG.truncate (0);
-    neighborhood (out, expr, NG);
-    if (NG.length () == 2)
-	return;
-
-    /* Names are reused in this section and generally mean the same thing.  */
-    bitmap_clear (reachable);
-    for (const basic_block b : NG)
-	bitmap_set_bit (reachable, b->index);
-
-    /* If a pair of nodes has a shared dominator *that is not the root*
-       then that dominator must be the first term of another boolean
-       expression or some other structure outside the expression.  */
-    gcc_assert (!NG.is_empty ());
-    for (unsigned i = 0; i != NG.length () - 1; i++)
+    while (true)
     {
-	for (unsigned j = i+1; j != NG.length (); j++)
+	NG.truncate (0);
+	neighborhood (G, reachable, NG);
+	gcc_assert (!NG.is_empty ());
+
+	bitmap_clear (expr);
+	for (basic_block b : NG)
+	    bitmap_set_bit (expr, b->index);
+
+	if (bitmap_count_bits (expr) == 2)
+	    break;
+
+	/* If the neighborhood does not change between iterations (a fixed
+	   point) we cannot understand the graph properly, and this would loop
+	   infinitely.  If this should happen, we should bail out and give up
+	   instrumentation for the function altogether.  It is possible no such
+	   CFGs exist, so for now this is an assert.  */
+	if (bitmap_equal_p (prev, expr) || bitmap_count_bits (expr) < 2)
+	    gcc_assert (false);
+	bitmap_copy (prev, expr);
+
+	for (unsigned i = 0; i != NG.length () - 1; i++)
 	{
-	    auto b = nearest_common_dominator (CDI_DOMINATORS, NG[i], NG[j]);
-	    if (ctx.index_map[b->index] > ctx.index_map[p->index])
+	    for (unsigned j = i+1; j != NG.length (); j++)
 	    {
-		bitmap_clear_bit (reachable, NG[i]->index);
-		bitmap_clear_bit (reachable, NG[j]->index);
-		bitmap_set_bit (reachable, b->index);
+		auto b = nearest_common_dominator (CDI_DOMINATORS, NG[i], NG[j]);
+		if (ctx.index_map[b->index] > ctx.index_map[p->index])
+		{
+		    bitmap_clear_bit (reachable, NG[i]->index);
+		    bitmap_clear_bit (reachable, NG[j]->index);
+		    bitmap_set_bit (reachable, b->index);
+		}
 	    }
 	}
-    }
-
-    NG.truncate (0);
-    for (basic_block b : G)
-	if (bitmap_bit_p (reachable, b->index))
-	    NG.safe_push (b);
+	bitmap_copy (expr, reachable);
 
-    bitmap_copy (reachable, expr);
-    for (const basic_block neighbor : NG)
-    {
-	bitmap_clear (ancestors);
-	for (edge e : neighbor->preds)
+	for (const basic_block neighbor : NG)
 	{
-	    /* The e edge might have been contracted past when doing the
-	       first scan, so we must make sure its bit is set to not think it
-	       is outside of our candidate set.  */
-	    bitmap_set_bit (reachable, e->src->index);
-	    ancestors_of (e->src, p, reachable, ancestors);
+	    bitmap_clear (ancestors);
+	    for (edge e : neighbor->preds)
+		ancestors_of (e->src, p, reachable, ancestors);
+	    bitmap_and (expr, expr, ancestors);
 	}
-	bitmap_and (expr, expr, ancestors);
-    }
 
-    out.truncate (0);
-    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);
+	for (unsigned i = 0; i != G.length (); i++)
+	    if (!bitmap_bit_p (expr, G[i]->index))
+		G.unordered_remove (i--);
 
-    bitmap_clear (expr);
-    for (const basic_block b : out)
-	bitmap_set_bit (expr, b->index);
+	bitmap_clear (reachable);
+	for (basic_block b : G)
+	    bitmap_set_bit (reachable, b->index);
+    }
 
-    /* Perform a final step as a sanity check - if the neighborhood is still
-       larger than the outcome set (|NG| == 2) the graph is too complex and we
-       give up instrumentation.  Any graph that exhibit this behaviour should
-       be studied as it might reveal an implementation error.  */
-    NG.truncate (0);
-    neighborhood (out, expr, NG);
-    bitmap_clear (expr);
-    for (const basic_block b : NG)
-	bitmap_set_bit (expr, b->index);
-    gcc_assert (bitmap_count_bits (expr) == 2);
+    out.safe_splice (G);
+    out.sort (cmp_index_map, &ctx.index_map);
 }
 
 /* Emit lhs = op1 <op> op2 on edges.  This emits non-atomic instructions and
-- 
2.30.2


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

* [PATCH 15/22] Fix candidate, neighborhood set reduction phase
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
                   ` (13 preceding siblings ...)
  2023-10-04 12:39 ` [PATCH 14/22] Unify expression candidate set refinement logic Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 16/22] Rename pathological -> setjmp Jørgen Kvalsvik
                   ` (6 subsequent siblings)
  21 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

This phase did not at all behave like advertised, which in turn lead to
ICEs for the new tests.
---
 gcc/testsuite/gcc.misc-tests/gcov-19.c | 35 +++++++++++++++++++++++---
 gcc/tree-profile.cc                    | 29 +++++++++++++++------
 2 files changed, 54 insertions(+), 10 deletions(-)

diff --git a/gcc/testsuite/gcc.misc-tests/gcov-19.c b/gcc/testsuite/gcc.misc-tests/gcov-19.c
index 1c671f7e186..5daa06cb7f4 100644
--- a/gcc/testsuite/gcc.misc-tests/gcov-19.c
+++ b/gcc/testsuite/gcc.misc-tests/gcov-19.c
@@ -4,6 +4,9 @@
 /* some side effect to stop branches from being pruned */
 int x = 0;
 
+int id  (int x) { return  x; }
+int inv (int x) { return !x; }
+
 /* || works */
 void
 mcdc001a (int a, int b)
@@ -395,6 +398,15 @@ mcdc009a (int a, int b)
 	x = a--;
 }
 
+/* Multi-term loop condition with empty body, which can give neighborhoods of
+   size 1.  */
+void
+mcdc009b (int a, int b)
+{
+    while (a-- > 0 && b) {} /* conditions(2/4) true(0 1) */
+			    /* conditions(end) */
+}
+
 /* for loop */
 void
 mcdc010a (int a, int b)
@@ -537,6 +549,21 @@ mcdc015c (int a, int b)
     }
 }
 
+/* Early returns, gotos can create candidate sets where the neighborhood
+   internally shares dominator nodes that are not the first-in-expression which
+   implies the neighborhood belongs to some other boolean expression.  When
+   this happens, the candidate set must be properly tidied up.  */
+void
+mcdc015d (int a, int b, int c)
+{
+    if (a) return;  /* conditions(1/2) false(0) */
+		    /* conditions(end) */
+    if (id (b)) return; /* conditions(0/2) true(0) false(0) */
+			/* conditions(end) */
+    if (id (c)) return; /* conditions(0/2) true(0) false(0) */
+			/* conditions(end) */
+}
+
 
 /* check nested loops */
 void
@@ -639,9 +666,6 @@ mcdc017c (int a, int b)
     } 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)
 {
@@ -1332,6 +1356,9 @@ int main ()
     mcdc009a (0, 0);
     mcdc009a (1, 1);
 
+    mcdc009b (0, 0);
+    mcdc009b (1, 0);
+
     mcdc010a (0, 0);
     mcdc010a (0, 9);
     mcdc010a (2, 1);
@@ -1369,6 +1396,8 @@ int main ()
     mcdc015c (0, 5);
     mcdc015c (6, 1);
 
+    mcdc015d (1, 0, 0);
+
     mcdc016a (5, 5);
 
     mcdc016b (5, 5);
diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
index b75736a22a0..98593f53862 100644
--- a/gcc/tree-profile.cc
+++ b/gcc/tree-profile.cc
@@ -759,6 +759,10 @@ isolate_expression (conds_ctx &ctx, basic_block p, vec<basic_block>& out)
 	if (bitmap_count_bits (expr) == 2)
 	    break;
 
+	/* This can happen for loops with no body.  */
+	if (bitmap_count_bits (expr) == 1 && bb_loop_header_p (p))
+	    break;
+
 	/* If the neighborhood does not change between iterations (a fixed
 	   point) we cannot understand the graph properly, and this would loop
 	   infinitely.  If this should happen, we should bail out and give up
@@ -768,21 +772,32 @@ isolate_expression (conds_ctx &ctx, basic_block p, vec<basic_block>& out)
 	    gcc_assert (false);
 	bitmap_copy (prev, expr);
 
-	for (unsigned i = 0; i != NG.length () - 1; i++)
+	const unsigned NGlen = NG.length ();
+	for (unsigned i = 0; i != NGlen - 1; i++)
 	{
-	    for (unsigned j = i+1; j != NG.length (); j++)
+	    for (unsigned j = i+1; j != NGlen; j++)
 	    {
-		auto b = nearest_common_dominator (CDI_DOMINATORS, NG[i], NG[j]);
+		basic_block b = nearest_common_dominator (CDI_DOMINATORS, NG[i], NG[j]);
 		if (ctx.index_map[b->index] > ctx.index_map[p->index])
 		{
-		    bitmap_clear_bit (reachable, NG[i]->index);
-		    bitmap_clear_bit (reachable, NG[j]->index);
-		    bitmap_set_bit (reachable, b->index);
+		    bitmap_clear_bit (expr, NG[i]->index);
+		    bitmap_clear_bit (expr, NG[j]->index);
+		    bitmap_set_bit (expr, b->index);
+		    NG.safe_push (b);
+		    /* It could be that the predecessor edge from the ancestor
+		       was contracted past, in which case we need to mark it to
+		       make sure its ancestors_of do not immediately terminate.  */
+		    for (edge e : b->preds)
+			if (ctx.index_map[e->src->index] > ctx.index_map[p->index])
+			    bitmap_set_bit (reachable, e->src->index);
 		}
 	    }
 	}
-	bitmap_copy (expr, reachable);
+	for (unsigned i = 0; i != NG.length (); i++)
+	    if (!bitmap_bit_p (expr, NG[i]->index))
+		NG.unordered_remove (i--);
 
+	bitmap_copy (expr, reachable);
 	for (const basic_block neighbor : NG)
 	{
 	    bitmap_clear (ancestors);
-- 
2.30.2


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

* [PATCH 16/22] Rename pathological -> setjmp
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
                   ` (14 preceding siblings ...)
  2023-10-04 12:39 ` [PATCH 15/22] Fix candidate, neighborhood set reduction phase Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 17/22] Mark contracted-past nodes in reachable Jørgen Kvalsvik
                   ` (5 subsequent siblings)
  21 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

---
 gcc/testsuite/gcc.misc-tests/gcov-22.c | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/gcc/testsuite/gcc.misc-tests/gcov-22.c b/gcc/testsuite/gcc.misc-tests/gcov-22.c
index 28b7de66022..3737235d40e 100644
--- a/gcc/testsuite/gcc.misc-tests/gcov-22.c
+++ b/gcc/testsuite/gcc.misc-tests/gcov-22.c
@@ -16,24 +16,24 @@ int identity(int x) { return x; }
 
    __builtin_setjmp did not trigger this, so we need setjmp from libc.  */
 void
-pathological001 (int a, int b, int c)
+setjmp001 (int a, int b, int c)
 {
     if (a)  /* conditions(1/2) true(0) */
-	        /* conditions(end) */
+	    /* conditions(end) */
 	noop ();
 
     if (b)  /* conditions(1/2) false(0) */
-	        /* conditions(end) */
+	    /* conditions(end) */
 	noop ();
 
     if (c && setjmp (buf))  /* conditions(1/4) true(0 1) false(1) */
-			                /* conditions(end) */
+			    /* conditions(end) */
 	noop ();
 }
 
-///* Adapted from freetype-2.13.0 gxvalid/gxvmod.c classic_kern_validate */
+/* Adapted from freetype-2.13.0 gxvalid/gxvmod.c classic_kern_validate */
 int
-pathological002 (int a)
+setjmp002 (int a)
 {
     int error = identity(a);
 
@@ -63,8 +63,8 @@ Exit:
 int
 main ()
 {
-    pathological001 (0, 1, 0);
-    pathological002 (0);
+    setjmp001 (0, 1, 0);
+    setjmp002 (0);
 }
 
 
-- 
2.30.2


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

* [PATCH 17/22] Mark contracted-past nodes in reachable
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
                   ` (15 preceding siblings ...)
  2023-10-04 12:39 ` [PATCH 16/22] Rename pathological -> setjmp Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 18/22] Don't contract into random edge in multi-succ node Jørgen Kvalsvik
                   ` (4 subsequent siblings)
  21 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

When there was no candidate set reduction phase this was not necessary,
as every neighbor's predecessor would always be in the reachable set.
Now that the graph cut is refined multiple times this may not hold,
which would lead to incorrect termination of the ancestors search when a
node in the candidate set was reduced to neighbor (effectively moving
the cut) as its predecessor may have been contracted by.
---
 gcc/testsuite/gcc.misc-tests/gcov-23.c | 75 ++++++++++++++++++++++++++
 gcc/tree-profile.cc                    | 41 +++++++++-----
 2 files changed, 102 insertions(+), 14 deletions(-)
 create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-23.c

diff --git a/gcc/testsuite/gcc.misc-tests/gcov-23.c b/gcc/testsuite/gcc.misc-tests/gcov-23.c
new file mode 100644
index 00000000000..c4afc5e700d
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-23.c
@@ -0,0 +1,75 @@
+/* { dg-options "-fprofile-conditions -ftest-coverage -O2 -c" } */
+
+int id (int);
+int idp (int *);
+int err;
+
+/* This becomes problematic only under optimization for the case when the
+   compiler cannot inline the function but have to generate a call.  It is not
+   really interesting to run, only see if it builds.  Notably, both the
+   function calls and the return values are important to construct a
+   problematic graph.
+
+   This test is also a good example of where optimization makes condition
+   coverage unpredictable, but not unusable.  If this is built without
+   optimization the conditions work as you would expect from reading the
+   source.  */
+/* Adapted from cpio-2.14 gnu/utmens.c lutimens ().  */
+int
+mcdc001 (int *v)
+{
+    int adjusted;
+    int adjustment_needed = 0;
+
+    int *ts = v ? &adjusted : 0; /* conditions(0/4) true(0 1) false(0 1) */
+				 /* conditions(end) */
+    if (ts)
+	adjustment_needed = idp (ts);
+    if (adjustment_needed < 0)
+	return -1;
+
+    if (adjustment_needed)  /* conditions(0/2) true(0) false(0) */
+			    /* conditions(end) */
+    {
+	if (adjustment_needed != 3) /* conditions(0/2) true(0) false(0) */
+				    /* conditions(end) */
+	    return -1;
+	if (ts) /* conditions(0/2) true(0) false(0) */
+		/* conditions(end) */
+	    return 0;
+    }
+
+    if (adjustment_needed && idp (&adjusted)) /* conditions(0/2) true(0) false(0) */
+					      /* conditions(end) */
+	return -1;
+    if (adjusted)   /* conditions(0/2) true(0) false(0) */
+		    /* conditions(end) */
+	return idp (ts);
+
+    return -1;
+}
+
+/* This failed when the candidate set internal/contracted-past nodes were not
+   properly marked as reachable in the candidate reduction phase.  */
+/* Adapted from cpio-2.14 gnu/mktime.c mktime_internal ().  */
+int
+mcdc002 ()
+{
+    int a;
+    if (idp (&a)) /* conditions(0/2) true(0) false(0) */
+		  /* conditions(end) */
+    {
+	if (id (a)) /* conditions(0/2) true(0/2) true(0) false(0) */
+		    /* conditions(end) */
+	    goto exit;
+
+	if (err) /* conditions(0/2) true(0/2) true(0) false(0) */
+		 /* conditions(end) */
+	    return -1;
+    }
+
+exit:
+    return a;
+}
+
+/* { dg-final { run-gcov conditions { --conditions gcov-23.c } } } */
diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
index 98593f53862..26e1924d444 100644
--- a/gcc/tree-profile.cc
+++ b/gcc/tree-profile.cc
@@ -279,23 +279,27 @@ single_edge (const vec<edge, va_gc> *edges)
    merged.
 
    Only chains of single-exit single-entry nodes that end with a condition
-   should be contracted.  */
+   should be contracted.  If the optional bitset G is passed, the intermediate
+   "contracted-past" nodes will be recorded, which is only meaningful if the
+   non-source edge is returned.  */
 edge
-contract_edge (edge e)
+contract_edge (edge e, sbitmap G = nullptr)
 {
     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;
 	if (dest->index == EXIT_BLOCK)
 	    return source;
+	if (!single (dest->preds))
+	    return source;
+	if (block_conditional_p (dest))
+	    return e;
 
+	if (G)
+	    bitmap_set_bit (G, dest->index);
 	e = single_edge (dest->succs);
 	if (!e)
 	    return source;
@@ -673,6 +677,8 @@ cond_reachable_from (basic_block p, basic_block q, sbitmap expr,
 	    if (!all_preds_conditional_p (dest, expr))
 		continue;
 
+	    if (dest != e->dest)
+		contract_edge (e, expr);
 	    bitmap_set_bit (expr, dest->index);
 	    out.safe_push (dest);
 	}
@@ -692,8 +698,14 @@ neighborhood (const vec<basic_block>& blocks, sbitmap G, vec<basic_block>& out)
 	    basic_block dest = contract_edge (e)->dest;
 	    if (bitmap_bit_p (G, dest->index))
 		continue;
-	    if (!out.contains (dest))
-		out.safe_push (dest);
+	    if (out.contains (dest))
+		continue;
+	    /* There was a contraction, so replay it but this time also record
+	       the intermediate nodes, so that the reachable set becomes
+	       complete.  This is necessary when reducing the candidate set.  */
+	    if (dest != e->dest)
+		contract_edge (e, G);
+	    out.safe_push (dest);
 	}
     }
 
@@ -784,12 +796,6 @@ isolate_expression (conds_ctx &ctx, basic_block p, vec<basic_block>& out)
 		    bitmap_clear_bit (expr, NG[j]->index);
 		    bitmap_set_bit (expr, b->index);
 		    NG.safe_push (b);
-		    /* It could be that the predecessor edge from the ancestor
-		       was contracted past, in which case we need to mark it to
-		       make sure its ancestors_of do not immediately terminate.  */
-		    for (edge e : b->preds)
-			if (ctx.index_map[e->src->index] > ctx.index_map[p->index])
-			    bitmap_set_bit (reachable, e->src->index);
 		}
 	    }
 	}
@@ -813,6 +819,13 @@ isolate_expression (conds_ctx &ctx, basic_block p, vec<basic_block>& out)
 	bitmap_clear (reachable);
 	for (basic_block b : G)
 	    bitmap_set_bit (reachable, b->index);
+
+	/* Contracted-past nodes in the subgraph must be re-marked, otherwise
+	   the next neighborhood may be computed wrong.  */
+	for (basic_block b : G)
+	    for (edge e : b->succs)
+		if (bitmap_bit_p (reachable, contract_edge (e)->dest->index))
+		    contract_edge (e, reachable);
     }
 
     out.safe_splice (G);
-- 
2.30.2


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

* [PATCH 18/22] Don't contract into random edge in multi-succ node
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
                   ` (16 preceding siblings ...)
  2023-10-04 12:39 ` [PATCH 17/22] Mark contracted-past nodes in reachable Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  2023-10-04 15:29   ` Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 19/22] Beautify assert Jørgen Kvalsvik
                   ` (3 subsequent siblings)
  21 siblings, 1 reply; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

A check was missing for is-single when contracting, so if a
multi-successor node that was not a condition node (e.g. a switch) a
random edge would be picked and contracted into.
---
 gcc/testsuite/gcc.misc-tests/gcov-23.c | 48 ++++++++++++++++++++++++++
 gcc/tree-profile.cc                    |  4 +++
 2 files changed, 52 insertions(+)

diff --git a/gcc/testsuite/gcc.misc-tests/gcov-23.c b/gcc/testsuite/gcc.misc-tests/gcov-23.c
index c4afc5e700d..856e97f5088 100644
--- a/gcc/testsuite/gcc.misc-tests/gcov-23.c
+++ b/gcc/testsuite/gcc.misc-tests/gcov-23.c
@@ -3,6 +3,7 @@
 int id (int);
 int idp (int *);
 int err;
+char c;
 
 /* This becomes problematic only under optimization for the case when the
    compiler cannot inline the function but have to generate a call.  It is not
@@ -72,4 +73,51 @@ exit:
     return a;
 }
 
+/* Adapted from icu4c-73.1 common/ucase.cpp ucase_getLocaleCase ().  */
+int
+mcdc003 (const char *locale)
+{
+    /* extern, so its effect won't be optimized out.  */
+    c = *locale++;
+    if (c == 'z') /* conditions(0/2) true(0) false(0) */
+		  /* conditions(end) */
+    {
+	return 1;
+    }
+    else if (c >= 'a') /* conditions(0/2) true(0) false(0) */
+		      /* conditions(end) */
+    {
+	if (id (c)) /* conditions(0/2) true(0) false(0) */
+		    /* conditions(end) */
+	    c = *locale++;
+    }
+    else
+    {
+	if (c == 'T')
+	{
+	    if (id (c)) /* conditions(0/2) true(0) false(0) */
+			/* conditions(end) */
+		c = *locale++;
+	    if (id (c)) /* conditions(0/2) true(0) false(0) */
+			/* conditions(end) */
+		c = *locale++;
+	}
+	else if (c == 'L')
+	    c = *locale++;
+	else if (c == 'E')
+	    c = *locale++;
+	else if (c == 'N')
+	    c = *locale++;
+	else if (c == 'H')
+	{
+	    c = *locale++;
+	    if (id (c)) /* conditions(0/2) true(0) false(0) */
+			/* conditions(end) */
+		c = *locale++;
+	}
+    }
+
+    return 1;
+}
+
 /* { dg-final { run-gcov conditions { --conditions gcov-23.c } } } */
diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
index 26e1924d444..ce679130ca0 100644
--- a/gcc/tree-profile.cc
+++ b/gcc/tree-profile.cc
@@ -297,6 +297,10 @@ contract_edge (edge e, sbitmap G = nullptr)
 	    return source;
 	if (block_conditional_p (dest))
 	    return e;
+	/* This happens for switches, and must be checked after the is-conditional
+	   (which is also not single).  */
+	if (!single (dest->succs))
+	    return source;
 
 	if (G)
 	    bitmap_set_bit (G, dest->index);
-- 
2.30.2


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

* [PATCH 19/22] Beautify assert
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
                   ` (17 preceding siblings ...)
  2023-10-04 12:39 ` [PATCH 18/22] Don't contract into random edge in multi-succ node Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 20/22] Don't try to reduce NG from dominators Jørgen Kvalsvik
                   ` (2 subsequent siblings)
  21 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

---
 gcc/tree-profile.cc | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
index ce679130ca0..f329be84ad2 100644
--- a/gcc/tree-profile.cc
+++ b/gcc/tree-profile.cc
@@ -784,8 +784,8 @@ isolate_expression (conds_ctx &ctx, basic_block p, vec<basic_block>& out)
 	   infinitely.  If this should happen, we should bail out and give up
 	   instrumentation for the function altogether.  It is possible no such
 	   CFGs exist, so for now this is an assert.  */
-	if (bitmap_equal_p (prev, expr) || bitmap_count_bits (expr) < 2)
-	    gcc_assert (false);
+	gcc_assert (!bitmap_equal_p (prev, expr));
+	gcc_assert (bitmap_count_bits (expr) > 2);
 	bitmap_copy (prev, expr);
 
 	const unsigned NGlen = NG.length ();
-- 
2.30.2


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

* [PATCH 20/22] Don't try to reduce NG from dominators
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
                   ` (18 preceding siblings ...)
  2023-10-04 12:39 ` [PATCH 19/22] Beautify assert Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 21/22] Walk the cfg in topological order, not depth-first Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 22/22] Return value on separate line Jørgen Kvalsvik
  21 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

The presence of gotos already makes this iffy, but it is already not
necessary as reducing by refining the G should cover it.
---
 gcc/tree-profile.cc | 19 -------------------
 1 file changed, 19 deletions(-)

diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
index f329be84ad2..d1d7265cd1c 100644
--- a/gcc/tree-profile.cc
+++ b/gcc/tree-profile.cc
@@ -788,25 +788,6 @@ isolate_expression (conds_ctx &ctx, basic_block p, vec<basic_block>& out)
 	gcc_assert (bitmap_count_bits (expr) > 2);
 	bitmap_copy (prev, expr);
 
-	const unsigned NGlen = NG.length ();
-	for (unsigned i = 0; i != NGlen - 1; i++)
-	{
-	    for (unsigned j = i+1; j != NGlen; j++)
-	    {
-		basic_block b = nearest_common_dominator (CDI_DOMINATORS, NG[i], NG[j]);
-		if (ctx.index_map[b->index] > ctx.index_map[p->index])
-		{
-		    bitmap_clear_bit (expr, NG[i]->index);
-		    bitmap_clear_bit (expr, NG[j]->index);
-		    bitmap_set_bit (expr, b->index);
-		    NG.safe_push (b);
-		}
-	    }
-	}
-	for (unsigned i = 0; i != NG.length (); i++)
-	    if (!bitmap_bit_p (expr, NG[i]->index))
-		NG.unordered_remove (i--);
-
 	bitmap_copy (expr, reachable);
 	for (const basic_block neighbor : NG)
 	{
-- 
2.30.2


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

* [PATCH 21/22] Walk the cfg in topological order, not depth-first
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
                   ` (19 preceding siblings ...)
  2023-10-04 12:39 ` [PATCH 20/22] Don't try to reduce NG from dominators Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  2023-10-04 15:24   ` Jørgen Kvalsvik
  2023-10-04 12:39 ` [PATCH 22/22] Return value on separate line Jørgen Kvalsvik
  21 siblings, 1 reply; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

Depth first order is insufficient to process expressions in the right
order when there are setjmps in optimized builds. This would create
complex paths from the root past conditions and into the middle of
boolean expressions. Traversing the graph in topological order restores
the expectation that expressions will be processed top-down.
---
 gcc/testsuite/gcc.misc-tests/gcov-23.c | 26 ++++++++++++++++++++++++++
 gcc/tree-profile.cc                    |  1 +
 2 files changed, 27 insertions(+)

diff --git a/gcc/testsuite/gcc.misc-tests/gcov-23.c b/gcc/testsuite/gcc.misc-tests/gcov-23.c
index 856e97f5088..e5b56a5aa44 100644
--- a/gcc/testsuite/gcc.misc-tests/gcov-23.c
+++ b/gcc/testsuite/gcc.misc-tests/gcov-23.c
@@ -1,5 +1,8 @@
 /* { dg-options "-fprofile-conditions -ftest-coverage -O2 -c" } */
 
+#include <setjmp.h>
+jmp_buf buf;
+
 int id (int);
 int idp (int *);
 int err;
@@ -120,4 +123,27 @@ mcdc003 (const char *locale)
     return 1;
 }
 
+/* Adapted from jxl 0.8.2 lib/extras/dec/apng.cc processing_start ().
+   This created a graph where depth-first traversal of the CFG would not
+   process nodes in the wrong order (the extra control inserted from setjmp
+   created a path of complexes from root to !b without going through !a).
+
+    This only happened under optimization.  */
+int
+mcdc004 (int a, int b)
+{
+    a = id (a);
+    b = id (b);
+
+    if (a || b) /* conditions(0/4) true(0 1) false(0 1) */
+		/* conditions(end) */
+	return 1;
+
+    if (setjmp (buf)) /* conditions(0/2) true(0) false(0) */
+		      /* conditions(end) */
+	return 1;
+
+    return 0;
+}
+
 /* { dg-final { run-gcov conditions { --conditions gcov-23.c } } } */
diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
index d1d7265cd1c..8bf280dc018 100644
--- a/gcc/tree-profile.cc
+++ b/gcc/tree-profile.cc
@@ -1045,6 +1045,7 @@ find_conditions (struct function *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);
+    dfs.sort (cmp_index_map, &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
-- 
2.30.2


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

* [PATCH 22/22] Return value on separate line
  2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
                   ` (20 preceding siblings ...)
  2023-10-04 12:39 ` [PATCH 21/22] Walk the cfg in topological order, not depth-first Jørgen Kvalsvik
@ 2023-10-04 12:39 ` Jørgen Kvalsvik
  21 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 12:39 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, Jørgen Kvalsvik

---
 gcc/testsuite/g++.dg/gcov/gcov-18.C | 36 +++++++++++++++++++----------
 1 file changed, 24 insertions(+), 12 deletions(-)

diff --git a/gcc/testsuite/g++.dg/gcov/gcov-18.C b/gcc/testsuite/g++.dg/gcov/gcov-18.C
index 310ed5297c0..b58f8450e44 100644
--- a/gcc/testsuite/g++.dg/gcov/gcov-18.C
+++ b/gcc/testsuite/g++.dg/gcov/gcov-18.C
@@ -18,7 +18,8 @@ public:
 int identity (int x) { return x; }
 int throws (int) { throw std::runtime_error("exception"); }
 
-int throw_if (int x)
+int
+throw_if (int x)
 {
     if (x) /* conditions(1/2) true(0) */
 	   /* conditions(end) */
@@ -30,7 +31,8 @@ int throw_if (int x)
 int x = 0;
 
 /* conditionals work in the presence of non-trivial destructors */
-void mcdc001a (int a)
+void
+mcdc001a (int a)
 {
     nontrivial_destructor v (a);
 
@@ -57,7 +59,8 @@ mcdc002a (int a, int b)
 }
 
 /* conditional in constructor */
-void mcdc003a (int a)
+void
+mcdc003a (int a)
 {
     class C
     {
@@ -77,7 +80,8 @@ void mcdc003a (int a)
 }
 
 /* conditional in destructor */
-void mcdc004a (int a)
+void
+mcdc004a (int a)
 {
     class C
     {
@@ -96,7 +100,8 @@ void mcdc004a (int a)
 }
 
 /* conditional in try */
-void mcdc005a (int a)
+void
+mcdc005a (int a)
 {
     try
     {
@@ -113,7 +118,8 @@ void mcdc005a (int a)
 }
 
 /* conditional in catch */
-void mcdc006a (int a) {
+void
+mcdc006a (int a) {
     try
     {
 	throws (a);
@@ -128,7 +134,8 @@ void mcdc006a (int a) {
     }
 }
 
-void mcdc006b (int a)
+void
+mcdc006b (int a)
 {
     if (a) /* conditions(1/2) true(0) */
 	   /* conditions(end) */
@@ -137,7 +144,8 @@ void mcdc006b (int a)
 	x = 1;
 }
 
-void mcdc006c (int a) try
+void
+mcdc006c (int a) try
 {
     throws (a);
 }
@@ -147,12 +155,14 @@ catch (...) {
 }
 
 /* temporary with destructor as term */
-void mcdc007a (int a, int b)
+void
+mcdc007a (int a, int b)
 {
     x = a && nontrivial_destructor (b); /* conditions(3/4) false(1) destructor() */
 }
 
-void mcdc007b (int a, int b)
+void
+mcdc007b (int a, int b)
 {
     if (a || throw_if (b)) /* conditions(3/4) true(1) destructor() */
 	x = -1;
@@ -160,7 +170,8 @@ void mcdc007b (int a, int b)
 	x = 1;
 }
 
-void mcdc007c (int a, int b)
+void
+mcdc007c (int a, int b)
 {
     if (throw_if (a) || throw_if (b)) /* conditions(2/4) true(0 1) destructor() */
 	x = -1;
@@ -169,7 +180,8 @@ void mcdc007c (int a, int b)
 }
 
 /* destructor with delete */
-void mcdc008a (int a)
+void
+mcdc008a (int a)
 {
     class C
     {
-- 
2.30.2


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

* Re: [PATCH 21/22] Walk the cfg in topological order, not depth-first
  2023-10-04 12:39 ` [PATCH 21/22] Walk the cfg in topological order, not depth-first Jørgen Kvalsvik
@ 2023-10-04 15:24   ` Jørgen Kvalsvik
  0 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 15:24 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh

On 04/10/2023 21:39, Jørgen Kvalsvik wrote:
> Depth first order is insufficient to process expressions in the right
> order when there are setjmps in optimized builds. This would create
> complex paths from the root past conditions and into the middle of
> boolean expressions. Traversing the graph in topological order restores
> the expectation that expressions will be processed top-down.
> ---
>   gcc/testsuite/gcc.misc-tests/gcov-23.c | 26 ++++++++++++++++++++++++++
>   gcc/tree-profile.cc                    |  1 +
>   2 files changed, 27 insertions(+)
> 
> diff --git a/gcc/testsuite/gcc.misc-tests/gcov-23.c b/gcc/testsuite/gcc.misc-tests/gcov-23.c
> index 856e97f5088..e5b56a5aa44 100644
> --- a/gcc/testsuite/gcc.misc-tests/gcov-23.c
> +++ b/gcc/testsuite/gcc.misc-tests/gcov-23.c
> @@ -1,5 +1,8 @@
>   /* { dg-options "-fprofile-conditions -ftest-coverage -O2 -c" } */
>   
> +#include <setjmp.h>
> +jmp_buf buf;
> +
>   int id (int);
>   int idp (int *);
>   int err;
> @@ -120,4 +123,27 @@ mcdc003 (const char *locale)
>       return 1;
>   }
>   
> +/* Adapted from jxl 0.8.2 lib/extras/dec/apng.cc processing_start ().
> +   This created a graph where depth-first traversal of the CFG would not
> +   process nodes in the wrong order (the extra control inserted from setjmp
> +   created a path of complexes from root to !b without going through !a).
> +
> +    This only happened under optimization.  */
> +int
> +mcdc004 (int a, int b)
> +{
> +    a = id (a);
> +    b = id (b);
> +
> +    if (a || b) /* conditions(0/4) true(0 1) false(0 1) */
> +		/* conditions(end) */
> +	return 1;
> +
> +    if (setjmp (buf)) /* conditions(0/2) true(0) false(0) */
> +		      /* conditions(end) */
> +	return 1;
> +
> +    return 0;
> +}
> +
>   /* { dg-final { run-gcov conditions { --conditions gcov-23.c } } } */
> diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
> index d1d7265cd1c..8bf280dc018 100644
> --- a/gcc/tree-profile.cc
> +++ b/gcc/tree-profile.cc
> @@ -1045,6 +1045,7 @@ find_conditions (struct function *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);
> +    dfs.sort (cmp_index_map, &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

This test fails the build because it missed this change:

 From ef01096021841bc3335aa1e80f0e3e75be9b24a2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=B8rgen=20Kvalsvik?= <j@lambda.is>
Date: Thu, 5 Oct 2023 00:14:53 +0900
Subject: [PATCH] Make right operand function to keep 2-term expr

If this is just the variable and not the function call, the comparsion
gets optimized to a|b which reduces the number of conditions and causes
the counting to fail.

The counting is not really an interesting part of this test, verifying
that it doesn't crash is, but it's nice when the numbers line up. An
alternative is to accept the strength reduction and count it as a 1-term
expression instead.
---
  gcc/testsuite/gcc.misc-tests/gcov-23.c | 4 ++--
  1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/gcc/testsuite/gcc.misc-tests/gcov-23.c 
b/gcc/testsuite/gcc.misc-tests/gcov-23.c
index e5b56a5aa44..98e692fcd01 100644
--- a/gcc/testsuite/gcc.misc-tests/gcov-23.c
+++ b/gcc/testsuite/gcc.misc-tests/gcov-23.c
@@ -135,8 +135,8 @@ mcdc004 (int a, int b)
      a = id (a);
      b = id (b);

-    if (a || b) /* conditions(0/4) true(0 1) false(0 1) */
-               /* conditions(end) */
+    if (a || id (b)) /* conditions(0/4) true(0 1) false(0 1) */
+                    /* conditions(end) */
         return 1;

      if (setjmp (buf)) /* conditions(0/2) true(0) false(0) */
-- 
2.30.2

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

* Re: [PATCH 18/22] Don't contract into random edge in multi-succ node
  2023-10-04 12:39 ` [PATCH 18/22] Don't contract into random edge in multi-succ node Jørgen Kvalsvik
@ 2023-10-04 15:29   ` Jørgen Kvalsvik
  0 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-04 15:29 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh

On 04/10/2023 21:39, Jørgen Kvalsvik wrote:
> A check was missing for is-single when contracting, so if a
> multi-successor node that was not a condition node (e.g. a switch) a
> random edge would be picked and contracted into.
> ---
>   gcc/testsuite/gcc.misc-tests/gcov-23.c | 48 ++++++++++++++++++++++++++
>   gcc/tree-profile.cc                    |  4 +++
>   2 files changed, 52 insertions(+)
> 
> diff --git a/gcc/testsuite/gcc.misc-tests/gcov-23.c b/gcc/testsuite/gcc.misc-tests/gcov-23.c
> index c4afc5e700d..856e97f5088 100644
> --- a/gcc/testsuite/gcc.misc-tests/gcov-23.c
> +++ b/gcc/testsuite/gcc.misc-tests/gcov-23.c
> @@ -3,6 +3,7 @@
>   int id (int);
>   int idp (int *);
>   int err;
> +char c;
>   
>   /* This becomes problematic only under optimization for the case when the
>      compiler cannot inline the function but have to generate a call.  It is not
> @@ -72,4 +73,51 @@ exit:
>       return a;
>   }
>   
> +/* Adapted from icu4c-73.1 common/ucase.cpp ucase_getLocaleCase ().  */
> +int
> +mcdc003 (const char *locale)
> +{
> +    /* extern, so its effect won't be optimized out.  */
> +    c = *locale++;
> +    if (c == 'z') /* conditions(0/2) true(0) false(0) */
> +		  /* conditions(end) */
> +    {
> +	return 1;
> +    }
> +    else if (c >= 'a') /* conditions(0/2) true(0) false(0) */
> +		      /* conditions(end) */
> +    {
> +	if (id (c)) /* conditions(0/2) true(0) false(0) */
> +		    /* conditions(end) */
> +	    c = *locale++;
> +    }
> +    else
> +    {
> +	if (c == 'T')
> +	{
> +	    if (id (c)) /* conditions(0/2) true(0) false(0) */
> +			/* conditions(end) */
> +		c = *locale++;
> +	    if (id (c)) /* conditions(0/2) true(0) false(0) */
> +			/* conditions(end) */
> +		c = *locale++;
> +	}
> +	else if (c == 'L')
> +	    c = *locale++;
> +	else if (c == 'E')
> +	    c = *locale++;
> +	else if (c == 'N')
> +	    c = *locale++;
> +	else if (c == 'H')
> +	{
> +	    c = *locale++;
> +	    if (id (c)) /* conditions(0/2) true(0) false(0) */
> +			/* conditions(end) */
> +		c = *locale++;
> +	}
> +    }
> +
> +    return 1;
> +}
> +
>   /* { dg-final { run-gcov conditions { --conditions gcov-23.c } } } */
> diff --git a/gcc/tree-profile.cc b/gcc/tree-profile.cc
> index 26e1924d444..ce679130ca0 100644
> --- a/gcc/tree-profile.cc
> +++ b/gcc/tree-profile.cc
> @@ -297,6 +297,10 @@ contract_edge (edge e, sbitmap G = nullptr)
>   	    return source;
>   	if (block_conditional_p (dest))
>   	    return e;
> +	/* This happens for switches, and must be checked after the is-conditional
> +	   (which is also not single).  */
> +	if (!single (dest->succs))
> +	    return source;
>   
>   	if (G)
>   	    bitmap_set_bit (G, dest->index);

This test seems to fail on aarch64 (thanks, linaro bot)


FAIL: 24 regressions

regressions.sum:
		=== gcc tests ===

Running gcc:gcc.misc-tests/gcov.exp ...
FAIL: gcc.misc-tests/gcov-23.c gcov: 0 failures in line counts, 0 in 
branch percentages, 13 in condition/decision, 0 in return percentages, 0 
in intermediate format
FAIL: gcc.misc-tests/gcov-23.c line 108: unexpected summary 0/2
FAIL: gcc.misc-tests/gcov-23.c line 108: unexpected uncovered term 0 (false)
FAIL: gcc.misc-tests/gcov-23.c line 108: unexpected uncovered term 0 (true)
FAIL: gcc.misc-tests/gcov-23.c line 110: unexpected summary 0/2
FAIL: gcc.misc-tests/gcov-23.c line 110: unexpected uncovered term 0 (false)
FAIL: gcc.misc-tests/gcov-23.c line 110: unexpected uncovered term 0 (true)
... and 19 more entries

Apparently the if-else do not get turned into a switch here. I have a 
look and see if I can reproduce the problem with an explicit switch 
rather than the implied one from if-else, as it is broken in its current 
state.

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

* Re: [PATCH 01/22] Add condition coverage profiling
  2023-10-04 12:39 ` [PATCH 01/22] " Jørgen Kvalsvik
@ 2023-10-05 12:59   ` Jan Hubicka
  2023-10-05 13:39     ` Jørgen Kvalsvik
                       ` (2 more replies)
  0 siblings, 3 replies; 35+ messages in thread
From: Jan Hubicka @ 2023-10-05 12:59 UTC (permalink / raw)
  To: Jørgen Kvalsvik
  Cc: gcc-patches, mliska, Jørgen Kvalsvik, dmalcolm, rguenther

> 
> 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.

This should not be too hard to do - if conditionalis more complex you
simply introduce more than one counter for it, right?
How many times this trigers on GCC sources?
> 
> 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)
It seems understandable, but for bigger conditionals I guess it will be
bit hard to make sense between condition numbers and the actual source
code.  We could probably also show the conditions as ranges in the
conditional?  I am adding David Malcolm to CC, he may have some ideas.

I wonder how much this information is confused by early optimizations
happening before coverage profiling?
> 
> 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

Maybe one can use location information to distinguish those cases?
Don't we store discriminator info about individual statements that is also used for
auto-FDO?
> 
> 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                        |    7 +-
>  gcc/common.opt                         |    8 +
>  gcc/doc/gcov.texi                      |   37 +
>  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                            |  197 +++-
>  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 | 1249 ++++++++++++++++++++++++
>  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                    | 1048 +++++++++++++++++++-
>  libgcc/libgcov-merge.c                 |    5 +
>  21 files changed, 3135 insertions(+), 26 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
> 
> @@ -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)
There should be block comments before functions
> @@ -134,6 +135,28 @@ public:
>    vector<unsigned> lines;
>  };
>  
> +class condition_info
... and datastructures.
> +/* Output conditions (modified condition/decision coverage) */
> +
> +static int flag_conditions = 0;

This is really a bool.  Gcov was not updated for a while, I think
you can simply convert other flag_* to bools as well.
> diff --git a/gcc/ipa-split.cc b/gcc/ipa-split.cc
> index 6730f4f9d0e..276ead617c9 100644
> --- a/gcc/ipa-split.cc
> +++ b/gcc/ipa-split.cc
> @@ -1930,7 +1930,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);

This should be unnecessary - profile_condition is not used for profile
feedback.
>  }
>  
>  } // anon namespace
> diff --git a/gcc/passes.cc b/gcc/passes.cc
> index 6f894a41d22..02194fe286f 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 ();

I think this whole code is unnecesar since branch probability is now an
IPA pass and runs on whole program at once, so the stats done by
end_branch_prob can be output from the pass itself.  

(originally the pass was doine as part of optimization queue and we
needed a place to output stats at the end of compilation).
> @@ -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.  */

No * on ever line.
> +      output_to_file = true;
> +
>        /* Basic block flags */
>        offset = gcov_write_tag (GCOV_TAG_BLOCKS);
>        gcov_write_unsigned (n_basic_blocks_for_fn (cfun));
> +      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.  */
Shouldn't we able to allocate the 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 ()),

Can't those be just SSA names?
> +	  };
> +
> +	  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))
> @@ -1669,6 +1740,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;
>  }
> @@ -1708,5 +1780,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/tree-profile.cc b/gcc/tree-profile.cc
> index da300d5f9e8..c8b917afb9a 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,1046 @@ static GTY(()) tree ic_tuple_var;
>  static GTY(()) tree ic_tuple_counters_field;
>  static GTY(()) tree ic_tuple_callee_field;
>  
> +namespace
> +{

Maybe some overall comment what this code is doing (which you describe
in tthe email).  Also it may make sense to place it to a new source file: it is long enough.
> +/* 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)

You also need to consider target number of bits in the gcov type:
targets can change the 64bit default to something else.
There is gcov_type_node from which you can get number of bits.
> +
> +/* 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.  */

This should be achievable with -fnon-call-exceptions.  For example
division can throw an exception then.
> +bool
> +single (const vec<edge, va_gc> *edges)
Maybe single_p to keep naming similar to basic-block?
> +{
> +    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)
> +{
Perhaps to keep behaviour consistent with basic-block.h you want to add
   gcc_checking_assert (single_p (edges));
> +/* 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)

coding style does not really like using uppercase letter for variables.
I see it was a set in the paper, but one can give it bit more
descriptive name in the source code.
> +{
> +    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;
> +	}
So this walks chain of single pred edges until something non-single is
found and only those are added ancessor bitmaps?
Why single_edge basic blocks are not inserted into the ancestor birmap?
> +
> +	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);
bitmap_set_bit returns boolean value whether the bit was newly set
or previously 1.  So you can avoid bitmap_bit_p above.
> diff --git a/libgcc/libgcov-merge.c b/libgcc/libgcov-merge.c
> index 5d6e17d1483..eed3556373b 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

Why this is necessary?

The profiling part looks good to me.  I am worried about the pattern
matching of gimple to recognize conditionals especially after the early
optimizations was run.  Does it work reasonably with -O2?
Perhaps it would make sense to schedule this kind of profiling early
shortly after build_ssa which would make it incompatible with profile
feedback, but I think that wouold be OK.

Richi, can you please look at the gimple matching part?
Honza

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

* Re: [PATCH 06/22] Use popcount_hwi rather than builtin
  2023-10-04 12:39 ` [PATCH 06/22] Use popcount_hwi rather than builtin Jørgen Kvalsvik
@ 2023-10-05 13:01   ` Jan Hubicka
  0 siblings, 0 replies; 35+ messages in thread
From: Jan Hubicka @ 2023-10-05 13:01 UTC (permalink / raw)
  To: Jørgen Kvalsvik; +Cc: gcc-patches, mliska, Jørgen Kvalsvik

Hi,
can you please also squash those changes which fixes patch #1
so it is easier to review?
Honza
> From: Jørgen Kvalsvik <jorgen.kvalsvik@woven.toyota>
> 
> ---
>  gcc/gcov.cc | 3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)
> 
> diff --git a/gcc/gcov.cc b/gcc/gcov.cc
> index 274f2fc5d9f..35be97cf5ac 100644
> --- a/gcc/gcov.cc
> +++ b/gcc/gcov.cc
> @@ -46,6 +46,7 @@ along with Gcov; see the file COPYING3.  If not see
>  #include "color-macros.h"
>  #include "pretty-print.h"
>  #include "json.h"
> +#include "hwint.h"
>  
>  #include <zlib.h>
>  #include <getopt.h>
> @@ -159,7 +160,7 @@ condition_info::condition_info (): truev (0), falsev (0), n_terms (0)
>  
>  int condition_info::popcount () const
>  {
> -    return __builtin_popcountll (truev) + __builtin_popcountll (falsev);
> +    return popcount_hwi (truev) + popcount_hwi (falsev);
>  }
>  
>  /* Describes a basic block. Contains lists of arcs to successor and
> -- 
> 2.30.2
> 

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

* Re: [PATCH 01/22] Add condition coverage profiling
  2023-10-05 12:59   ` Jan Hubicka
@ 2023-10-05 13:39     ` Jørgen Kvalsvik
  2023-10-05 14:17       ` Jørgen Kvalsvik
  2023-10-21 14:30       ` Jørgen Kvalsvik
  2023-10-05 15:18     ` Jørgen Kvalsvik
  2023-10-06  9:49     ` Richard Biener
  2 siblings, 2 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-05 13:39 UTC (permalink / raw)
  To: Jan Hubicka
  Cc: gcc-patches, mliska, Jørgen Kvalsvik, dmalcolm, rguenther

On 05/10/2023 21:59, Jan Hubicka wrote:
>>
>> 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.
> 
> This should not be too hard to do - if conditionalis more complex you
> simply introduce more than one counter for it, right?
> How many times this trigers on GCC sources?

It shouldn't be, no. But when dynamic bitsets are on the table it would 
be much better to length-encode in smaller multiples than the 64-bit 
counters. Most expressions are small (<4 terms), so the savings would be 
substantial. I opted for the simpler fixed-size to start with because it 
is much simpler and would not introduce any branching or decisions in 
the instrumentation.

>>
>> 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)
> It seems understandable, but for bigger conditionals I guess it will be
> bit hard to make sense between condition numbers and the actual source
> code.  We could probably also show the conditions as ranges in the
> conditional?  I am adding David Malcolm to CC, he may have some ideas.
> 
> I wonder how much this information is confused by early optimizations
> happening before coverage profiling?
>>
>> 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
> 
> Maybe one can use location information to distinguish those cases?
> Don't we store discriminator info about individual statements that is also used for
> auto-FDO?

That is one possibility, which I tried for a bit, but abandoned to focus 
on getting the rest of the algorithm right. I am sure it can be 
revisited (possibly as a future improvement) and weighted against always 
emitting an else block (see 
https://gcc.gnu.org/pipermail/gcc-patches/2023-September/631254.html)

>>
>> 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                        |    7 +-
>>   gcc/common.opt                         |    8 +
>>   gcc/doc/gcov.texi                      |   37 +
>>   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                            |  197 +++-
>>   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 | 1249 ++++++++++++++++++++++++
>>   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                    | 1048 +++++++++++++++++++-
>>   libgcc/libgcov-merge.c                 |    5 +
>>   21 files changed, 3135 insertions(+), 26 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
>>
>> @@ -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)
> There should be block comments before functions
>> @@ -134,6 +135,28 @@ public:
>>     vector<unsigned> lines;
>>   };
>>   
>> +class condition_info
> ... and datastructures.
>> +/* Output conditions (modified condition/decision coverage) */
>> +
>> +static int flag_conditions = 0;
> 
> This is really a bool.  Gcov was not updated for a while, I think
> you can simply convert other flag_* to bools as well.
>> diff --git a/gcc/ipa-split.cc b/gcc/ipa-split.cc
>> index 6730f4f9d0e..276ead617c9 100644
>> --- a/gcc/ipa-split.cc
>> +++ b/gcc/ipa-split.cc
>> @@ -1930,7 +1930,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);
> 
> This should be unnecessary - profile_condition is not used for profile
> feedback.
>>   }
>>   
>>   } // anon namespace
>> diff --git a/gcc/passes.cc b/gcc/passes.cc
>> index 6f894a41d22..02194fe286f 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 ();
> 
> I think this whole code is unnecesar since branch probability is now an
> IPA pass and runs on whole program at once, so the stats done by
> end_branch_prob can be output from the pass itself.
> 
> (originally the pass was doine as part of optimization queue and we
> needed a place to output stats at the end of compilation).
>> @@ -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.  */
> 
> No * on ever line.
>> +      output_to_file = true;
>> +
>>         /* Basic block flags */
>>         offset = gcov_write_tag (GCOV_TAG_BLOCKS);
>>         gcov_write_unsigned (n_basic_blocks_for_fn (cfun));
>> +      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.  */
> Shouldn't we able to allocate the 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 ()),
> 
> Can't those be just SSA names?
>> +	  };
>> +
>> +	  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))
>> @@ -1669,6 +1740,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;
>>   }
>> @@ -1708,5 +1780,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/tree-profile.cc b/gcc/tree-profile.cc
>> index da300d5f9e8..c8b917afb9a 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,1046 @@ static GTY(()) tree ic_tuple_var;
>>   static GTY(()) tree ic_tuple_counters_field;
>>   static GTY(()) tree ic_tuple_callee_field;
>>   
>> +namespace
>> +{
> 
> Maybe some overall comment what this code is doing (which you describe
> in tthe email).  Also it may make sense to place it to a new source file: it is long enough.
>> +/* 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)
> 
> You also need to consider target number of bits in the gcov type:
> targets can change the 64bit default to something else.
> There is gcov_type_node from which you can get number of bits.
>> +
>> +/* 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.  */
> 
> This should be achievable with -fnon-call-exceptions.  For example
> division can throw an exception then.
>> +bool
>> +single (const vec<edge, va_gc> *edges)
> Maybe single_p to keep naming similar to basic-block?
>> +{
>> +    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)
>> +{
> Perhaps to keep behaviour consistent with basic-block.h you want to add
>     gcc_checking_assert (single_p (edges));
>> +/* 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)
> 
> coding style does not really like using uppercase letter for variables.
> I see it was a set in the paper, but one can give it bit more
> descriptive name in the source code.
>> +{
>> +    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;
>> +	}
> So this walks chain of single pred edges until something non-single is
> found and only those are added ancessor bitmaps?
> Why single_edge basic blocks are not inserted into the ancestor birmap?
>> +
>> +	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);
> bitmap_set_bit returns boolean value whether the bit was newly set
> or previously 1.  So you can avoid bitmap_bit_p above.
>> diff --git a/libgcc/libgcov-merge.c b/libgcc/libgcov-merge.c
>> index 5d6e17d1483..eed3556373b 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
> 
> Why this is necessary?
> 
> The profiling part looks good to me.  I am worried about the pattern
> matching of gimple to recognize conditionals especially after the early
> optimizations was run.  Does it work reasonably with -O2?
> Perhaps it would make sense to schedule this kind of profiling early
> shortly after build_ssa which would make it incompatible with profile
> feedback, but I think that wouold be OK.
> 
> Richi, can you please look at the gimple matching part?
> Honza


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

* Re: [PATCH 01/22] Add condition coverage profiling
  2023-10-05 13:39     ` Jørgen Kvalsvik
@ 2023-10-05 14:17       ` Jørgen Kvalsvik
  2023-10-05 14:39         ` Jan Hubicka
  2023-10-21 14:30       ` Jørgen Kvalsvik
  1 sibling, 1 reply; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-05 14:17 UTC (permalink / raw)
  To: Jan Hubicka
  Cc: gcc-patches, mliska, Jørgen Kvalsvik, dmalcolm, rguenther

On 05/10/2023 22:39, Jørgen Kvalsvik wrote:
> On 05/10/2023 21:59, Jan Hubicka wrote:
>>>
>>> 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.
>>
>> This should not be too hard to do - if conditionalis more complex you
>> simply introduce more than one counter for it, right?
>> How many times this trigers on GCC sources?
> 
> It shouldn't be, no. But when dynamic bitsets are on the table it would 
> be much better to length-encode in smaller multiples than the 64-bit 
> counters. Most expressions are small (<4 terms), so the savings would be 
> substantial. I opted for the simpler fixed-size to start with because it 
> is much simpler and would not introduce any branching or decisions in 
> the instrumentation.

Oh, and I forgot - I have never seen a real world case that have been 
more than 64 conditions, but I suppose it may happen with generated code.

> 
>>>
>>> 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)
>> It seems understandable, but for bigger conditionals I guess it will be
>> bit hard to make sense between condition numbers and the actual source
>> code.  We could probably also show the conditions as ranges in the
>> conditional?  I am adding David Malcolm to CC, he may have some ideas.
>>
>> I wonder how much this information is confused by early optimizations
>> happening before coverage profiling?
>>>
>>> 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
>>
>> Maybe one can use location information to distinguish those cases?
>> Don't we store discriminator info about individual statements that is 
>> also used for
>> auto-FDO?
> 
> That is one possibility, which I tried for a bit, but abandoned to focus 
> on getting the rest of the algorithm right. I am sure it can be 
> revisited (possibly as a future improvement) and weighted against always 
> emitting an else block (see 
> https://gcc.gnu.org/pipermail/gcc-patches/2023-September/631254.html)
> 
>>>
>>> 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                        |    7 +-
>>>   gcc/common.opt                         |    8 +
>>>   gcc/doc/gcov.texi                      |   37 +
>>>   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                            |  197 +++-
>>>   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 | 1249 ++++++++++++++++++++++++
>>>   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                    | 1048 +++++++++++++++++++-
>>>   libgcc/libgcov-merge.c                 |    5 +
>>>   21 files changed, 3135 insertions(+), 26 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
>>>
>>> @@ -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)
>> There should be block comments before functions
>>> @@ -134,6 +135,28 @@ public:
>>>     vector<unsigned> lines;
>>>   };
>>> +class condition_info
>> ... and datastructures.
>>> +/* Output conditions (modified condition/decision coverage) */
>>> +
>>> +static int flag_conditions = 0;
>>
>> This is really a bool.  Gcov was not updated for a while, I think
>> you can simply convert other flag_* to bools as well.
>>> diff --git a/gcc/ipa-split.cc b/gcc/ipa-split.cc
>>> index 6730f4f9d0e..276ead617c9 100644
>>> --- a/gcc/ipa-split.cc
>>> +++ b/gcc/ipa-split.cc
>>> @@ -1930,7 +1930,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);
>>
>> This should be unnecessary - profile_condition is not used for profile
>> feedback.
>>>   }
>>>   } // anon namespace
>>> diff --git a/gcc/passes.cc b/gcc/passes.cc
>>> index 6f894a41d22..02194fe286f 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 ();
>>
>> I think this whole code is unnecesar since branch probability is now an
>> IPA pass and runs on whole program at once, so the stats done by
>> end_branch_prob can be output from the pass itself.
>>
>> (originally the pass was doine as part of optimization queue and we
>> needed a place to output stats at the end of compilation).
>>> @@ -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.  */
>>
>> No * on ever line.
>>> +      output_to_file = true;
>>> +
>>>         /* Basic block flags */
>>>         offset = gcov_write_tag (GCOV_TAG_BLOCKS);
>>>         gcov_write_unsigned (n_basic_blocks_for_fn (cfun));
>>> +      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.  */
>> Shouldn't we able to allocate the 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 ()),
>>
>> Can't those be just SSA names?
>>> +      };
>>> +
>>> +      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))
>>> @@ -1669,6 +1740,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;
>>>   }
>>> @@ -1708,5 +1780,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/tree-profile.cc b/gcc/tree-profile.cc
>>> index da300d5f9e8..c8b917afb9a 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,1046 @@ static GTY(()) tree ic_tuple_var;
>>>   static GTY(()) tree ic_tuple_counters_field;
>>>   static GTY(()) tree ic_tuple_callee_field;
>>> +namespace
>>> +{
>>
>> Maybe some overall comment what this code is doing (which you describe
>> in tthe email).  Also it may make sense to place it to a new source 
>> file: it is long enough.
>>> +/* 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)
>>
>> You also need to consider target number of bits in the gcov type:
>> targets can change the 64bit default to something else.
>> There is gcov_type_node from which you can get number of bits.
>>> +
>>> +/* 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.  */
>>
>> This should be achievable with -fnon-call-exceptions.  For example
>> division can throw an exception then.
>>> +bool
>>> +single (const vec<edge, va_gc> *edges)
>> Maybe single_p to keep naming similar to basic-block?
>>> +{
>>> +    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)
>>> +{
>> Perhaps to keep behaviour consistent with basic-block.h you want to add
>>     gcc_checking_assert (single_p (edges));
>>> +/* 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)
>>
>> coding style does not really like using uppercase letter for variables.
>> I see it was a set in the paper, but one can give it bit more
>> descriptive name in the source code.
>>> +{
>>> +    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;
>>> +    }
>> So this walks chain of single pred edges until something non-single is
>> found and only those are added ancessor bitmaps?
>> Why single_edge basic blocks are not inserted into the ancestor birmap?
>>> +
>>> +    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);
>> bitmap_set_bit returns boolean value whether the bit was newly set
>> or previously 1.  So you can avoid bitmap_bit_p above.
>>> diff --git a/libgcc/libgcov-merge.c b/libgcc/libgcov-merge.c
>>> index 5d6e17d1483..eed3556373b 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
>>
>> Why this is necessary?
>>
>> The profiling part looks good to me.  I am worried about the pattern
>> matching of gimple to recognize conditionals especially after the early
>> optimizations was run.  Does it work reasonably with -O2?
>> Perhaps it would make sense to schedule this kind of profiling early
>> shortly after build_ssa which would make it incompatible with profile
>> feedback, but I think that wouold be OK.
>>
>> Richi, can you please look at the gimple matching part?
>> Honza
> 


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

* Re: [PATCH 01/22] Add condition coverage profiling
  2023-10-05 14:17       ` Jørgen Kvalsvik
@ 2023-10-05 14:39         ` Jan Hubicka
  0 siblings, 0 replies; 35+ messages in thread
From: Jan Hubicka @ 2023-10-05 14:39 UTC (permalink / raw)
  To: Jørgen Kvalsvik
  Cc: gcc-patches, mliska, Jørgen Kvalsvik, dmalcolm, rguenther

> On 05/10/2023 22:39, Jørgen Kvalsvik wrote:
> > On 05/10/2023 21:59, Jan Hubicka wrote:
> > > > 
> > > > 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.
> > > 
> > > This should not be too hard to do - if conditionalis more complex you
> > > simply introduce more than one counter for it, right?
> > > How many times this trigers on GCC sources?
> > 
> > It shouldn't be, no. But when dynamic bitsets are on the table it would
> > be much better to length-encode in smaller multiples than the 64-bit
> > counters. Most expressions are small (<4 terms), so the savings would be
> > substantial. I opted for the simpler fixed-size to start with because it
> > is much simpler and would not introduce any branching or decisions in
> > the instrumentation.
> 
> Oh, and I forgot - I have never seen a real world case that have been more
> than 64 conditions, but I suppose it may happen with generated code.

reload.cc has some long hand-written conditionals in it.  The first one
I counted had 38 conditions. Some of them are macros that may expand to
sub-conditions :)

But I agree that such code should not be common and probably the
conditional should be factored to multiple predicates.

Honza

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

* Re: [PATCH 01/22] Add condition coverage profiling
  2023-10-05 12:59   ` Jan Hubicka
  2023-10-05 13:39     ` Jørgen Kvalsvik
@ 2023-10-05 15:18     ` Jørgen Kvalsvik
  2023-10-06  9:49     ` Richard Biener
  2 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-05 15:18 UTC (permalink / raw)
  To: Jan Hubicka
  Cc: gcc-patches, mliska, Jørgen Kvalsvik, dmalcolm, rguenther

On 05/10/2023 21:59, Jan Hubicka wrote:
>>
>> 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.
> 
> This should not be too hard to do - if conditionalis more complex you
> simply introduce more than one counter for it, right?
> How many times this trigers on GCC sources?
>>
>> 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)
> It seems understandable, but for bigger conditionals I guess it will be
> bit hard to make sense between condition numbers and the actual source
> code.  We could probably also show the conditions as ranges in the
> conditional?  I am adding David Malcolm to CC, he may have some ideas.
> 
> I wonder how much this information is confused by early optimizations
> happening before coverage profiling?
>>
>> 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
> 
> Maybe one can use location information to distinguish those cases?
> Don't we store discriminator info about individual statements that is also used for
> auto-FDO?
>>
>> 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                        |    7 +-
>>   gcc/common.opt                         |    8 +
>>   gcc/doc/gcov.texi                      |   37 +
>>   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                            |  197 +++-
>>   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 | 1249 ++++++++++++++++++++++++
>>   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                    | 1048 +++++++++++++++++++-
>>   libgcc/libgcov-merge.c                 |    5 +
>>   21 files changed, 3135 insertions(+), 26 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
>>
>> @@ -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)
> There should be block comments before functions

Ok.

>> @@ -134,6 +135,28 @@ public:
>>     vector<unsigned> lines;
>>   };
>>   
>> +class condition_info
> ... and datastructures.

Ok.

>> +/* Output conditions (modified condition/decision coverage) */
>> +
>> +static int flag_conditions = 0;
> 
> This is really a bool.  Gcov was not updated for a while, I think
> you can simply convert other flag_* to bools as well.
>> diff --git a/gcc/ipa-split.cc b/gcc/ipa-split.cc
>> index 6730f4f9d0e..276ead617c9 100644
>> --- a/gcc/ipa-split.cc
>> +++ b/gcc/ipa-split.cc
>> @@ -1930,7 +1930,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);
> 
> This should be unnecessary - profile_condition is not used for profile
> feedback.

Noted, I will remove it.

>>   }
>>   
>>   } // anon namespace
>> diff --git a/gcc/passes.cc b/gcc/passes.cc
>> index 6f894a41d22..02194fe286f 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 ();
> 
> I think this whole code is unnecesar since branch probability is now an
> IPA pass and runs on whole program at once, so the stats done by
> end_branch_prob can be output from the pass itself.
>

That may very well be - I just looked for places where profile_arc_flag 
was used and added it.

> (originally the pass was doine as part of optimization queue and we
> needed a place to output stats at the end of compilation).
>> @@ -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.  */
> 
> No * on ever line.

Ok.

>> +      output_to_file = true;
>> +
>>         /* Basic block flags */
>>         offset = gcov_write_tag (GCOV_TAG_BLOCKS);
>>         gcov_write_unsigned (n_basic_blocks_for_fn (cfun));
>> +      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.  */
> Shouldn't we able to allocate the stack space.

We are? This is adding 2 more gcov_type to the frame, as opposed to 
adding one accumulator pair per condition.

>> +	  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 ()),
> 
> Can't those be just SSA names?

Maybe? I need them to live through the whole function, which is probably 
not tied to the name. I can give it a go and see if it works.

>> +	  };
>> +
>> +	  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))
>> @@ -1669,6 +1740,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;
>>   }
>> @@ -1708,5 +1780,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/tree-profile.cc b/gcc/tree-profile.cc
>> index da300d5f9e8..c8b917afb9a 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,1046 @@ static GTY(()) tree ic_tuple_var;
>>   static GTY(()) tree ic_tuple_counters_field;
>>   static GTY(()) tree ic_tuple_callee_field;
>>   
>> +namespace
>> +{
> 
> Maybe some overall comment what this code is doing (which you describe
> in tthe email).  Also it may make sense to place it to a new source file: it is long enough.

I will see if I can think of something. I am typing up the algorithm, 
with justification and analysis, and I think a synopsis of that + 
feature summary would do well.

>> +/* 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)
> 
> You also need to consider target number of bits in the gcov type:
> targets can change the 64bit default to something else.
> There is gcov_type_node from which you can get number of bits.

Right - I think all these sizes should be target specific, shouldn't 
they? Because both the instrumentation and .gcno should only be 
sensitive to the target.

>> +
>> +/* 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.  */
> 
> This should be achievable with -fnon-call-exceptions.  For example
> division can throw an exception then.

Ok, nice, then I can remove the doubt in the comment and keep them.

>> +bool
>> +single (const vec<edge, va_gc> *edges)
> Maybe single_p to keep naming similar to basic-block?

Sure.

>> +{
>> +    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)
>> +{
> Perhaps to keep behaviour consistent with basic-block.h you want to add
>     gcc_checking_assert (single_p (edges));

Sure.

>> +/* 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)
> 
> coding style does not really like using uppercase letter for variables.
> I see it was a set in the paper, but one can give it bit more
> descriptive name in the source code.

I will see what I can do. It has been a surprisingly difficult balancing 
act - I started out with longer names, but most functions significantly 
improved by just using the p,q. Now that the concepts are more well 
defined longer names just might make sense again.

>> +{
>> +    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;
>> +	}
> So this walks chain of single pred edges until something non-single is
> found and only those are added ancessor bitmaps?
> Why single_edge basic blocks are not inserted into the ancestor birmap?

Good observation! I actually thought about that yesterday.

The short answer is: I have not gotten around to trying it yet.

The long answer: The testing I have done over the past couple of weeks 
revealed a flaw in the algorithm in the presence of gotos and some 
transformations that would happen under optimization. Throughout 
development I have worked on an implicit transformed graph where the 
complex edges are removed and I have created implicit edges around these 
chains of single-entry single-exit nodes after conditionals. This has 
only been encoded in the traversal function, so that the sets (in 
isolate_expression etc.) are unaware of this happening. This has worked 
ok, I suppose, but then even weirder graphs showed up and contract_edge 
had to learn to record the nodes it zoomed by, which was necessary when 
introducing the loop in isolate_expressions. An implementation bug, really.

Now, the proper solution to this is *probably* (I haven't tried yet, 
it's on my TODO) to not contract at all, but rather record, run ancestor 
filtering like before, and when |NG| == 2, filter the output set on 
is-conditional. No more special casing of traversal, and still only the 
actual condition nodes are recorded.

>> +
>> +	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);
> bitmap_set_bit returns boolean value whether the bit was newly set
> or previously 1.  So you can avoid bitmap_bit_p above.

Oh nice, thanks!

>> diff --git a/libgcc/libgcov-merge.c b/libgcc/libgcov-merge.c
>> index 5d6e17d1483..eed3556373b 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
> 
> Why this is necessary?

I think Martin asked me to earlier, I cannot remember the why. I'll try 
to dig up the email tomorrow for reference.

> 
> The profiling part looks good to me.  I am worried about the pattern
> matching of gimple to recognize conditionals especially after the early
> optimizations was run.  Does it work reasonably with -O2? > Perhaps it would make sense to schedule this kind of profiling early
> shortly after build_ssa which would make it incompatible with profile
> feedback, but I think that wouold be OK.

It does not really work at O2 - well, it works, but if || -> | has 
happened then that's a condition gone. To be fair, branch coverage has 
the same problem and it is generally recommended to measure at -O0. If 
we could have good results also at -O2 that would be even better, of course.

> 
> Richi, can you please look at the gimple matching part?
> Honza

Thanks,
Jørgen

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

* Re: [PATCH 01/22] Add condition coverage profiling
  2023-10-05 12:59   ` Jan Hubicka
  2023-10-05 13:39     ` Jørgen Kvalsvik
  2023-10-05 15:18     ` Jørgen Kvalsvik
@ 2023-10-06  9:49     ` Richard Biener
  2 siblings, 0 replies; 35+ messages in thread
From: Richard Biener @ 2023-10-06  9:49 UTC (permalink / raw)
  To: Jan Hubicka
  Cc: Jørgen Kvalsvik, gcc-patches, mliska, Jørgen Kvalsvik,
	dmalcolm

On Thu, 5 Oct 2023, Jan Hubicka wrote:

[...]
> Richi, can you please look at the gimple matching part?

What did you have in mind?  I couldn't find anything obvious in the
patch counting as gimple matching - do you have a pointer?

Thanks,
Richard.

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

* Re: [PATCH 01/22] Add condition coverage profiling
  2023-10-05 13:39     ` Jørgen Kvalsvik
  2023-10-05 14:17       ` Jørgen Kvalsvik
@ 2023-10-21 14:30       ` Jørgen Kvalsvik
  2023-10-23  8:10         ` Richard Biener
  1 sibling, 1 reply; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-21 14:30 UTC (permalink / raw)
  To: Jan Hubicka; +Cc: gcc-patches, dmalcolm, rguenther

On 05/10/2023 22:39, Jørgen Kvalsvik wrote:
> On 05/10/2023 21:59, Jan Hubicka wrote:
>>>
>>> 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.
>>
>> This should not be too hard to do - if conditionalis more complex you
>> simply introduce more than one counter for it, right?
>> How many times this trigers on GCC sources?
> 
> It shouldn't be, no. But when dynamic bitsets are on the table it would 
> be much better to length-encode in smaller multiples than the 64-bit 
> counters. Most expressions are small (<4 terms), so the savings would be 
> substantial. I opted for the simpler fixed-size to start with because it 
> is much simpler and would not introduce any branching or decisions in 
> the instrumentation.

I just posted v6 of this patch, and the bitsets are still fixed size. I 
consider dynamic bitsets out of scope for this particular effort, 
although I think it might be worth pursuing later.

> 
>>>
>>> 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)
>> It seems understandable, but for bigger conditionals I guess it will be
>> bit hard to make sense between condition numbers and the actual source
>> code.  We could probably also show the conditions as ranges in the
>> conditional?  I am adding David Malcolm to CC, he may have some ideas.
>>
>> I wonder how much this information is confused by early optimizations
>> happening before coverage profiling?

Yes, but I could not figure out strong gcov mechanisms to make a truly 
better one, and the json + extra tooling is more in line with what you 
want for large conditionals, I think. I also specifically did one unit 
of information per line to play nicer with grep, so it currently looks like:

conditions covered 3/8
condition  0 not covered (true false)
...

I think improving gcov's printing abilities would do wonders, but I 
couldn't find anything that would support it currently. Did I miss 
something?

>>>
>>> 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
>>
>> Maybe one can use location information to distinguish those cases?
>> Don't we store discriminator info about individual statements that is 
>> also used for
>> auto-FDO?
> 
> That is one possibility, which I tried for a bit, but abandoned to focus 
> on getting the rest of the algorithm right. I am sure it can be 
> revisited (possibly as a future improvement) and weighted against always 
> emitting an else block (see 
> https://gcc.gnu.org/pipermail/gcc-patches/2023-September/631254.html)

This has not been changed in v6. I think the always-else approach is nicer.

> 
>>>
>>> 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                        |    7 +-
>>>   gcc/common.opt                         |    8 +
>>>   gcc/doc/gcov.texi                      |   37 +
>>>   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                            |  197 +++-
>>>   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 | 1249 ++++++++++++++++++++++++
>>>   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                    | 1048 +++++++++++++++++++-
>>>   libgcc/libgcov-merge.c                 |    5 +
>>>   21 files changed, 3135 insertions(+), 26 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
>>>
>>> @@ -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)
>> There should be block comments before functions
Done.

>>> @@ -134,6 +135,28 @@ public:
>>>     vector<unsigned> lines;
>>>   };
>>> +class condition_info
>> ... and datastructures.
Done.

>>> +/* Output conditions (modified condition/decision coverage) */
>>> +
>>> +static int flag_conditions = 0;
>>
>> This is really a bool.  Gcov was not updated for a while, I think
>> you can simply convert other flag_* to bools as well.
Done, but only for my flag. I can change the remaining flags in another 
patch.

>>> diff --git a/gcc/ipa-split.cc b/gcc/ipa-split.cc
>>> index 6730f4f9d0e..276ead617c9 100644
>>> --- a/gcc/ipa-split.cc
>>> +++ b/gcc/ipa-split.cc
>>> @@ -1930,7 +1930,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);
>>
>> This should be unnecessary - profile_condition is not used for profile
>> feedback.

Removed in v6.

>>>   }
>>>   } // anon namespace
>>> diff --git a/gcc/passes.cc b/gcc/passes.cc
>>> index 6f894a41d22..02194fe286f 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 ();
>>
>> I think this whole code is unnecesar since branch probability is now an
>> IPA pass and runs on whole program at once, so the stats done by
>> end_branch_prob can be output from the pass itself.
Done, removed.

>>
>> (originally the pass was doine as part of optimization queue and we
>> needed a place to output stats at the end of compilation).
>>> @@ -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.  */
>>
>> No * on ever line.
Done, except I missed a few. I have fixed them locally, my apologies.

>>> +      output_to_file = true;
>>> +
>>>         /* Basic block flags */
>>>         offset = gcov_write_tag (GCOV_TAG_BLOCKS);
>>>         gcov_write_unsigned (n_basic_blocks_for_fn (cfun));
>>> +      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.  */
>> Shouldn't we able to allocate the 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 ()),
>>
>> Can't those be just SSA names?
They can, and it was a lot harder than I thought to get it working 
(largely due to my unfamiliarity with it). v6 uses SSA form and passes 
the test suite.

>>> +      };
>>> +
>>> +      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))
>>> @@ -1669,6 +1740,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;
>>>   }
>>> @@ -1708,5 +1780,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/tree-profile.cc b/gcc/tree-profile.cc
>>> index da300d5f9e8..c8b917afb9a 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,1046 @@ static GTY(()) tree ic_tuple_var;
>>>   static GTY(()) tree ic_tuple_counters_field;
>>>   static GTY(()) tree ic_tuple_callee_field;
>>> +namespace
>>> +{
>>
>> Maybe some overall comment what this code is doing (which you describe
>> in tthe email).  Also it may make sense to place it to a new source 
>> file: it is long enough.
I added a comment, but did not move it out of tree-profiling.cc. As you 
say, it is large enough, and I will do it if you want me to, but I still 
find it manageable.

>>> +/* 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)
>>
>> You also need to consider target number of bits in the gcov type:
>> targets can change the 64bit default to something else.
>> There is gcov_type_node from which you can get number of bits.
Ah, yes, that was bad. So, what I have done is change the compiler 
storage type for bitsets to uint64_t, and it seems to be reasonable for 
a while that the target gcov_type_unsigned is <= 64 bits, and moved 
target check to TYPE_PRECISION.

>>> +
>>> +/* 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.  */
>>
>> This should be achievable with -fnon-call-exceptions.  For example
>> division can throw an exception then.
>>> +bool
>>> +single (const vec<edge, va_gc> *edges)
>> Maybe single_p to keep naming similar to basic-block?
Done.

>>> +{
>>> +    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)
>>> +{
>> Perhaps to keep behaviour consistent with basic-block.h you want to add
>>     gcc_checking_assert (single_p (edges));
Done.

>>> +/* 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)
>>
>> coding style does not really like using uppercase letter for variables.
>> I see it was a set in the paper, but one can give it bit more
>> descriptive name in the source code.
Done.

>>> +{
>>> +    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;
>>> +    }
>> So this walks chain of single pred edges until something non-single is
>> found and only those are added ancessor bitmaps?
>> Why single_edge basic blocks are not inserted into the ancestor birmap?
This patch also adds intermediate nodes to the bitmap, which simplified 
a few things in isolate_expression, and made the new 
instrument_decisions quite nice.

>>> +
>>> +    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);
>> bitmap_set_bit returns boolean value whether the bit was newly set
>> or previously 1.  So you can avoid bitmap_bit_p above.
Done.

>>> diff --git a/libgcc/libgcov-merge.c b/libgcc/libgcov-merge.c
>>> index 5d6e17d1483..eed3556373b 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
>>
>> Why this is necessary?

To build in freestanding environments, proposed here: 
https://inbox.sourceware.org/gcc-patches/ad3f098f-9caa-b656-47e8-6cd1b216fec4@embedded-brains.de/

>>
>> The profiling part looks good to me.  I am worried about the pattern
>> matching of gimple to recognize conditionals especially after the early
>> optimizations was run.  Does it work reasonably with -O2?
>> Perhaps it would make sense to schedule this kind of profiling early
>> shortly after build_ssa which would make it incompatible with profile
>> feedback, but I think that wouold be OK.
I would assume scheduling earlier would improve the accuracy also under 
optimization (which is nice, but usually these measurements require -O0).

>>
>> Richi, can you please look at the gimple matching part?
>> Honza
> 

When doing the SSA form instrumentation I found a peculiar problem with 
this program:

int
setjmp003 (int a)
{
     if (a || setjmp (buf)
         a += 12;

     return a;
}

void
jump ()
{
     longjmp (buf, 1);
}

This gives me coverages like 35/4, 36/4. The setjmp creates a central 
node with complex incoming edges, and an outgoing complex edge to the 
start of the right operand (the longjmp resume).

Now, the behaviour of this program is undefined, which makes me curious 
to some of the mechanics. I tried to poison the counters on the complex 
edge into (_ || setjmp) so that whenever the longjmp happen, the mask 
would be all-zero and the counter update a no-op. That failed with 
complex edges not being splitable. I tried selecting the poisoned value 
in a phi node and pick it on the incoming complex edge, but the complex 
edge never seemed to be picked in the phi. Is it not possible to use 
complex edges in phi nodes, or is there some way I can detect that the 
longjmp happened so I can poison the counters? Or is this behaviour ok 
considering it is already undefined behaviour?

Thanks,
Jørgen

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

* Re: [PATCH 01/22] Add condition coverage profiling
  2023-10-21 14:30       ` Jørgen Kvalsvik
@ 2023-10-23  8:10         ` Richard Biener
  0 siblings, 0 replies; 35+ messages in thread
From: Richard Biener @ 2023-10-23  8:10 UTC (permalink / raw)
  To: Jørgen Kvalsvik; +Cc: Jan Hubicka, gcc-patches, dmalcolm

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

On Sat, 21 Oct 2023, J?rgen Kvalsvik wrote:

> On 05/10/2023 22:39, J?rgen Kvalsvik wrote:
> > On 05/10/2023 21:59, Jan Hubicka wrote:
> >>>
> >>> 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.
> >>
> >> This should not be too hard to do - if conditionalis more complex you
> >> simply introduce more than one counter for it, right?
> >> How many times this trigers on GCC sources?
> > 
> > It shouldn't be, no. But when dynamic bitsets are on the table it would be
> > much better to length-encode in smaller multiples than the 64-bit counters.
> > Most expressions are small (<4 terms), so the savings would be substantial.
> > I opted for the simpler fixed-size to start with because it is much simpler
> > and would not introduce any branching or decisions in the instrumentation.
> 
> I just posted v6 of this patch, and the bitsets are still fixed size. I
> consider dynamic bitsets out of scope for this particular effort, although I
> think it might be worth pursuing later.
> 
> > 
> >>>
> >>> 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)
> >> It seems understandable, but for bigger conditionals I guess it will be
> >> bit hard to make sense between condition numbers and the actual source
> >> code.  We could probably also show the conditions as ranges in the
> >> conditional?  I am adding David Malcolm to CC, he may have some ideas.
> >>
> >> I wonder how much this information is confused by early optimizations
> >> happening before coverage profiling?
> 
> Yes, but I could not figure out strong gcov mechanisms to make a truly better
> one, and the json + extra tooling is more in line with what you want for large
> conditionals, I think. I also specifically did one unit of information per
> line to play nicer with grep, so it currently looks like:
> 
> conditions covered 3/8
> condition  0 not covered (true false)
> ...
> 
> I think improving gcov's printing abilities would do wonders, but I couldn't
> find anything that would support it currently. Did I miss something?
> 
> >>>
> >>> 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
> >>
> >> Maybe one can use location information to distinguish those cases?
> >> Don't we store discriminator info about individual statements that is also
> >> used for
> >> auto-FDO?
> > 
> > That is one possibility, which I tried for a bit, but abandoned to focus on
> > getting the rest of the algorithm right. I am sure it can be revisited
> > (possibly as a future improvement) and weighted against always emitting an
> > else block (see
> > https://gcc.gnu.org/pipermail/gcc-patches/2023-September/631254.html)
> 
> This has not been changed in v6. I think the always-else approach is nicer.
> 
> > 
> >>>
> >>> 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                        |    7 +-
> >>>   gcc/common.opt                         |    8 +
> >>>   gcc/doc/gcov.texi                      |   37 +
> >>>   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                            |  197 +++-
> >>>   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 | 1249 ++++++++++++++++++++++++
> >>>   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                    | 1048 +++++++++++++++++++-
> >>>   libgcc/libgcov-merge.c                 |    5 +
> >>>   21 files changed, 3135 insertions(+), 26 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
> >>>
> >>> @@ -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)
> >> There should be block comments before functions
> Done.
> 
> >>> @@ -134,6 +135,28 @@ public:
> >>>     vector<unsigned> lines;
> >>>   };
> >>> +class condition_info
> >> ... and datastructures.
> Done.
> 
> >>> +/* Output conditions (modified condition/decision coverage) */
> >>> +
> >>> +static int flag_conditions = 0;
> >>
> >> This is really a bool.  Gcov was not updated for a while, I think
> >> you can simply convert other flag_* to bools as well.
> Done, but only for my flag. I can change the remaining flags in another patch.
> 
> >>> diff --git a/gcc/ipa-split.cc b/gcc/ipa-split.cc
> >>> index 6730f4f9d0e..276ead617c9 100644
> >>> --- a/gcc/ipa-split.cc
> >>> +++ b/gcc/ipa-split.cc
> >>> @@ -1930,7 +1930,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);
> >>
> >> This should be unnecessary - profile_condition is not used for profile
> >> feedback.
> 
> Removed in v6.
> 
> >>>   }
> >>>   } // anon namespace
> >>> diff --git a/gcc/passes.cc b/gcc/passes.cc
> >>> index 6f894a41d22..02194fe286f 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 ();
> >>
> >> I think this whole code is unnecesar since branch probability is now an
> >> IPA pass and runs on whole program at once, so the stats done by
> >> end_branch_prob can be output from the pass itself.
> Done, removed.
> 
> >>
> >> (originally the pass was doine as part of optimization queue and we
> >> needed a place to output stats at the end of compilation).
> >>> @@ -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.  */
> >>
> >> No * on ever line.
> Done, except I missed a few. I have fixed them locally, my apologies.
> 
> >>> +      output_to_file = true;
> >>> +
> >>>         /* Basic block flags */
> >>>         offset = gcov_write_tag (GCOV_TAG_BLOCKS);
> >>>         gcov_write_unsigned (n_basic_blocks_for_fn (cfun));
> >>> +      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.  */
> >> Shouldn't we able to allocate the 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 ()),
> >>
> >> Can't those be just SSA names?
> They can, and it was a lot harder than I thought to get it working (largely
> due to my unfamiliarity with it). v6 uses SSA form and passes the test suite.
> 
> >>> +      };
> >>> +
> >>> +      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))
> >>> @@ -1669,6 +1740,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;
> >>>   }
> >>> @@ -1708,5 +1780,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/tree-profile.cc b/gcc/tree-profile.cc
> >>> index da300d5f9e8..c8b917afb9a 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,1046 @@ static GTY(()) tree ic_tuple_var;
> >>>   static GTY(()) tree ic_tuple_counters_field;
> >>>   static GTY(()) tree ic_tuple_callee_field;
> >>> +namespace
> >>> +{
> >>
> >> Maybe some overall comment what this code is doing (which you describe
> >> in tthe email).  Also it may make sense to place it to a new source file:
> >> it is long enough.
> I added a comment, but did not move it out of tree-profiling.cc. As you say,
> it is large enough, and I will do it if you want me to, but I still find it
> manageable.
> 
> >>> +/* 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)
> >>
> >> You also need to consider target number of bits in the gcov type:
> >> targets can change the 64bit default to something else.
> >> There is gcov_type_node from which you can get number of bits.
> Ah, yes, that was bad. So, what I have done is change the compiler storage
> type for bitsets to uint64_t, and it seems to be reasonable for a while that
> the target gcov_type_unsigned is <= 64 bits, and moved target check to
> TYPE_PRECISION.
> 
> >>> +
> >>> +/* 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.  */
> >>
> >> This should be achievable with -fnon-call-exceptions.  For example
> >> division can throw an exception then.
> >>> +bool
> >>> +single (const vec<edge, va_gc> *edges)
> >> Maybe single_p to keep naming similar to basic-block?
> Done.
> 
> >>> +{
> >>> +    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)
> >>> +{
> >> Perhaps to keep behaviour consistent with basic-block.h you want to add
> >>     gcc_checking_assert (single_p (edges));
> Done.
> 
> >>> +/* 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)
> >>
> >> coding style does not really like using uppercase letter for variables.
> >> I see it was a set in the paper, but one can give it bit more
> >> descriptive name in the source code.
> Done.
> 
> >>> +{
> >>> +    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;
> >>> +    }
> >> So this walks chain of single pred edges until something non-single is
> >> found and only those are added ancessor bitmaps?
> >> Why single_edge basic blocks are not inserted into the ancestor birmap?
> This patch also adds intermediate nodes to the bitmap, which simplified a few
> things in isolate_expression, and made the new instrument_decisions quite
> nice.
> 
> >>> +
> >>> +    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);
> >> bitmap_set_bit returns boolean value whether the bit was newly set
> >> or previously 1.  So you can avoid bitmap_bit_p above.
> Done.
> 
> >>> diff --git a/libgcc/libgcov-merge.c b/libgcc/libgcov-merge.c
> >>> index 5d6e17d1483..eed3556373b 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
> >>
> >> Why this is necessary?
> 
> To build in freestanding environments, proposed here:
> https://inbox.sourceware.org/gcc-patches/ad3f098f-9caa-b656-47e8-6cd1b216fec4@embedded-brains.de/
> 
> >>
> >> The profiling part looks good to me.  I am worried about the pattern
> >> matching of gimple to recognize conditionals especially after the early
> >> optimizations was run.  Does it work reasonably with -O2?
> >> Perhaps it would make sense to schedule this kind of profiling early
> >> shortly after build_ssa which would make it incompatible with profile
> >> feedback, but I think that wouold be OK.
> I would assume scheduling earlier would improve the accuracy also under
> optimization (which is nice, but usually these measurements require -O0).
> 
> >>
> >> Richi, can you please look at the gimple matching part?
> >> Honza
> > 
> 
> When doing the SSA form instrumentation I found a peculiar problem with this
> program:
> 
> int
> setjmp003 (int a)
> {
>     if (a || setjmp (buf)
>         a += 12;
> 
>     return a;
> }
> 
> void
> jump ()
> {
>     longjmp (buf, 1);
> }
> 
> This gives me coverages like 35/4, 36/4. The setjmp creates a central node
> with complex incoming edges, and an outgoing complex edge to the start of the
> right operand (the longjmp resume).
> 
> Now, the behaviour of this program is undefined, which makes me curious to
> some of the mechanics. I tried to poison the counters on the complex edge into
> (_ || setjmp) so that whenever the longjmp happen, the mask would be all-zero
> and the counter update a no-op. That failed with complex edges not being
> splitable. I tried selecting the poisoned value in a phi node and pick it on
> the incoming complex edge, but the complex edge never seemed to be picked in
> the phi. Is it not possible to use complex edges in phi nodes, or is there
> some way I can detect that the longjmp happened so I can poison the counters?
> Or is this behaviour ok considering it is already undefined behaviour?

If it's undefined behavior doing anything should be OK.  It is possible
to have PHI nodes with incoming abnormal edges, but we need to be
able to coalesce arguments and result to the same register (out-of-SSA
will ICE if that's not possible).  Also usual language applies
with respect to what state is saved across setjmp/longjmp,
POSIX talks about volatile, basically the values need to be in memory
since what part of the register set is preserved across setjmp/longjmp
is not very well defined - you probably have to look at generated code
to see if there's sth obviously wrong.

Richard.

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

* [PATCH v5] Add condition coverage profiling
@ 2023-10-05 14:10 Jørgen Kvalsvik
  0 siblings, 0 replies; 35+ messages in thread
From: Jørgen Kvalsvik @ 2023-10-05 14:10 UTC (permalink / raw)
  To: gcc-patches; +Cc: mliska, jh, 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

Whalen, 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. I have primarily tested it on C and C++, see
testsuite/gcc.misc-tests and testsuite/g++.dg, and run some manual tests
using D, Rust, and Go. D and Rust mostly behave as you would expect (I
found D would sometimes put the conditions for lambdas into the module)
It does not work as expected for Go as the go front-end evaluates
multi-conditional expressions by folding results into temporaries.

Like Whalen 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 and.c which
is a more severe error. While surprising, users would probably expect
some minor rewriting of semantically-identical expressions. I think this
is something that can be improved on later.

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 <jorgen.kvalsvik@woven.toyota>

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.
	(all_preds_conditional_p): 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.

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.misc-tests/gcov-22.c: New test.
	* gcc.misc-tests/gcov-23.c: New test.
---
 gcc/builtins.cc                        |    2 +-
 gcc/collect2.cc                        |    7 +-
 gcc/common.opt                         |    9 +
 gcc/doc/gcov.texi                      |   38 +
 gcc/doc/invoke.texi                    |   21 +
 gcc/gcc.cc                             |    4 +-
 gcc/gcov-counter.def                   |    3 +
 gcc/gcov-dump.cc                       |   24 +
 gcc/gcov-io.h                          |    3 +
 gcc/gcov.cc                            |  209 +++-
 gcc/ipa-inline.cc                      |    2 +-
 gcc/ipa-split.cc                       |    3 +-
 gcc/passes.cc                          |    3 +-
 gcc/profile.cc                         |   92 +-
 gcc/testsuite/g++.dg/gcov/gcov-18.C    |  246 ++++
 gcc/testsuite/gcc.misc-tests/gcov-19.c | 1471 ++++++++++++++++++++++++
 gcc/testsuite/gcc.misc-tests/gcov-20.c |   22 +
 gcc/testsuite/gcc.misc-tests/gcov-21.c |   16 +
 gcc/testsuite/gcc.misc-tests/gcov-22.c |   71 ++
 gcc/testsuite/gcc.misc-tests/gcov-23.c |  197 ++++
 gcc/testsuite/lib/gcov.exp             |  191 ++-
 gcc/tree-profile.cc                    | 1123 +++++++++++++++++-
 libgcc/libgcov-merge.c                 |    5 +
 23 files changed, 3736 insertions(+), 26 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
 create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-22.c
 create mode 100644 gcc/testsuite/gcc.misc-tests/gcov-23.c

diff --git a/gcc/builtins.cc b/gcc/builtins.cc
index cb90bd03b3e..ecd1c189b3f 100644
--- a/gcc/builtins.cc
+++ b/gcc/builtins.cc
@@ -5879,7 +5879,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 63b9a0c233a..12ff5d81424 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 f137a1f81ac..cd769ad95e0 100644
--- a/gcc/common.opt
+++ b/gcc/common.opt
@@ -862,6 +862,11 @@ 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 condition coverage profiling
+gives up instrumenting the expression.
+
 Wmissing-profile
 Common Var(warn_missing_profile) Init(1) Warning
 Warn in case profiles in -fprofile-use do not exist.
@@ -2377,6 +2382,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 3019efc4674..f6db593a62a 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,14 @@ 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).  This requires you
+to compile the source with @option{-fprofile-conditions}.
+
 @item -d
 @itemx --display-progress
 Display the progress on the standard output.
@@ -301,6 +310,7 @@ Each @var{line} has the following form:
   "branches": ["$branch"],
   "calls": ["$call"],
   "count": 2,
+  "conditions": ["$condition"],
   "line_number": 15,
   "unexecuted_block": false,
   "function_name": "foo",
@@ -384,6 +394,34 @@ to @var{line::count})
 @var{destination_block_id}: ID of the basic block this calls continues after return
 @end itemize
 
+Each @var{condition} has the following form:
+
+@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 condition outcomes in this expression
+
+@item
+@var{covered}: number of covered condition outcomes 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 4085fc90907..fbe6fa5c825 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -628,6 +628,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
+-fprofile-conditions
 -fprofile-abs-path
 -fprofile-dir=@var{path}  -fprofile-generate  -fprofile-generate=@var{path}
 -fprofile-info-section  -fprofile-info-section=@var{name}
@@ -6474,6 +6475,14 @@ poorly optimized code and is useful only in the
 case of very minor changes such as bug fixes to an existing code-base.
 Completely disabling the warning is not recommended.
 
+@opindex Wno-coverage-too-many-conditions
+@opindex Wcoverage-too-many-conditions
+@item -Wno-coverage-too-many-conditions
+Warn if @option{-fprofile-conditions} is used and an expression 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.
+
 @opindex Wno-coverage-invalid-line-number
 @opindex Wcoverage-invalid-line-number
 @item -Wno-coverage-invalid-line-number
@@ -16627,6 +16636,14 @@ Note that if a command line directly links source files, the corresponding
 E.g. @code{gcc a.c b.c -o binary} would generate @file{binary-a.gcda} and
 @file{binary-b.gcda} files.
 
+@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, which
+can be used to verify that all terms in a booleans are tested and have an
+independent effect on the outcome of a decision.  The result can be read with
+@code{gcov --conditions}.
+
 @xref{Cross-profiling}.
 
 @cindex @command{gcov}
@@ -16689,6 +16706,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 exercised boolean values that have an
+independent effect on the outcome of that expression.
+
 @need 2000
 @opindex ftest-coverage
 @item -ftest-coverage
diff --git a/gcc/gcc.cc b/gcc/gcc.cc
index c6e600fa0d3..f4d8e9bfa2c 100644
--- a/gcc/gcc.cc
+++ b/gcc/gcc.cc
@@ -1157,7 +1157,7 @@ proper position among the other output files.  */
 	%:include(libgomp.spec)%(link_gomp)}\
     %{fgnu-tm:%:include(libitm.spec)%(link_itm)}\
     " 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
@@ -1276,7 +1276,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 727ef424181..4d5b9c65a70 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 20e464022dc..d85fa98ddd5 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,
     }
 }
 
+/* Print number of conditions (not outcomes, i.e. if (x && y) is 2, not 4) */
+static void
+tag_conditions (const char *filename, unsigned /* tag */, int length,
+		unsigned depth)
+{
+  unsigned n_conditions = GCOV_TAG_CONDS_NUM (length);
+
+  printf (" %u conditions", 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 bfe4439d02d..32ccd43743c 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 2fad6aa7ede..62eac76a971 100644
--- a/gcc/gcov.cc
+++ b/gcc/gcov.cc
@@ -46,6 +46,7 @@ along with Gcov; see the file COPYING3.  If not see
 #include "color-macros.h"
 #include "pretty-print.h"
 #include "json.h"
+#include "hwint.h"
 
 #include <zlib.h>
 #include <getopt.h>
@@ -81,6 +82,7 @@ using namespace std;
 class function_info;
 class block_info;
 class source_info;
+class condition_info;
 
 /* Describes an arc between two basic blocks.  */
 
@@ -134,6 +136,33 @@ public:
   vector<unsigned> lines;
 };
 
+/* Describes a single conditional expression and the (recorded) conditions
+ * shown to independently affect the outcome.  */
+class condition_info
+{
+public:
+  condition_info ();
+
+  int popcount () const;
+
+  /* Bitsets storing the independently significant outcomes for true and false,
+   * respectively.  */
+  gcov_type_unsigned truev;
+  gcov_type_unsigned falsev;
+
+  /* Number of terms in the expression; if (x) -> 1, if (x && y) -> 2 etc.  */
+  unsigned n_terms;
+};
+
+condition_info::condition_info (): truev (0), falsev (0), n_terms (0)
+{
+}
+
+int condition_info::popcount () const
+{
+    return popcount_hwi (truev) + popcount_hwi (falsev);
+}
+
 /* Describes a basic block. Contains lists of arcs to successor and
    predecessor blocks.  */
 
@@ -167,6 +196,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
@@ -277,6 +308,8 @@ public:
   vector<block_info> blocks;
   unsigned blocks_executed;
 
+  vector<condition_info*> conditions;
+
   /* Raw arc coverage counts.  */
   vector<gcov_type> counts;
 
@@ -353,6 +386,9 @@ struct coverage_info
   int branches_executed;
   int branches_taken;
 
+  int conditions;
+  int conditions_covered;
+
   int calls;
   int calls_executed;
 
@@ -552,6 +588,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;
 
@@ -658,6 +698,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 *);
@@ -666,6 +707,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 *);
@@ -930,6 +972,8 @@ 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 modified condition/decision\n\
+                                    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");
@@ -983,6 +1027,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' },
@@ -1011,7 +1056,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)
@@ -1028,6 +1073,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.  */
@@ -1158,6 +1206,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);
 }
 
@@ -1982,6 +2069,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 ();
@@ -2112,6 +2221,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);
@@ -2456,6 +2580,15 @@ add_branch_counts (coverage_info *coverage, const arc_info *arc)
     }
 }
 
+/* Increment totals in COVERAGE according to to block BLOCK.  */
+
+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.  */
 
@@ -2559,6 +2692,18 @@ file_summary (const coverage_info *coverage)
 		 coverage->calls);
       else
 	fnotice (stdout, "No calls\n");
+
+    }
+
+  if (flag_conditions)
+    {
+      if (coverage->conditions)
+	fnotice (stdout, "Condition outcomes covered:%s of %d\n",
+		 format_gcov (coverage->conditions_covered,
+			      coverage->conditions, 2),
+		 coverage->conditions);
+      else
+	fnotice (stdout, "No conditions\n");
     }
 }
 
@@ -2793,6 +2938,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
@@ -2894,6 +3045,37 @@ accumulate_line_counts (source_info *src)
       }
 }
 
+/* Output information about the conditions in block BINFO.  The output includes
+ * a summary (n/m outcomes covered) and a list of the missing (uncovered)
+ * outcomes.  */
+
+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.  */
 
@@ -3104,16 +3286,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 dc120e6da5a..b540a79bfd2 100644
--- a/gcc/ipa-inline.cc
+++ b/gcc/ipa-inline.cc
@@ -682,7 +682,7 @@ can_early_inline_edge_p (struct cgraph_edge *e)
     }
   gcc_assert (gimple_in_ssa_p (DECL_STRUCT_FUNCTION (e->caller->decl))
 	      && gimple_in_ssa_p (DECL_STRUCT_FUNCTION (callee->decl)));
-  if (profile_arc_flag
+  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 6730f4f9d0e..276ead617c9 100644
--- a/gcc/ipa-split.cc
+++ b/gcc/ipa-split.cc
@@ -1930,7 +1930,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 6f894a41d22..02194fe286f 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 fc59326a19b..00975661b0d 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++;
 
@@ -1239,6 +1257,9 @@ branch_prob (bool thunk)
 		  basic_block new_bb = split_edge (e);
 		  edge ne = single_succ_edge (new_bb);
 		  ne->goto_locus = e->goto_locus;
+		  /* Mark the edge with IGNORE so condition coverage knows that
+		     the edge split occurred and this should be contracted.  */
+		  ne->flags |= EDGE_IGNORE;
 		}
 	      if ((e->flags & (EDGE_ABNORMAL | EDGE_ABNORMAL_CALL))
 		   && e->dest != EXIT_BLOCK_PTR_FOR_FN (cfun))
@@ -1397,10 +1418,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));
@@ -1514,29 +1543,79 @@ 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 ();
+
+  /* Unset all EDGE_IGNORE set in this pass.  */
+  FOR_EACH_BB_FN (bb, cfun)
+    for (edge e : bb->succs)
+      e->flags &= ~EDGE_IGNORE;
+
   coverage_end_function (lineno_checksum, cfg_checksum);
   if (flag_branch_probabilities
       && (profile_status_for_fn (cfun) == PROFILE_READ))
@@ -1669,6 +1748,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;
 }
@@ -1708,5 +1788,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..b58f8450e44
--- /dev/null
+++ b/gcc/testsuite/g++.dg/gcov/gcov-18.C
@@ -0,0 +1,246 @@
+/* { 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..5daa06cb7f4
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-19.c
@@ -0,0 +1,1471 @@
+/* { dg-options "-fprofile-conditions -ftest-coverage" } */
+/* { dg-do run { target native } } */
+
+/* some side effect to stop branches from being pruned */
+int x = 0;
+
+int id  (int x) { return  x; }
+int inv (int x) { return !x; }
+
+/* || 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) */
+		    /* conditions(end) */
+	    x = a + b + c;
+    }
+    else
+    {
+	x = c;
+    }
+}
+
+/* else-if is not immune to the else-less fuse.  This test is also put in as a
+ * detection mechanism - sif this should register as 3 individual decisions
+ * then the test should be updated and fixed to reflect it.  */
+int
+mcdc004f (int a, int b, int c)
+{
+    if (a)  /* conditions(1/2) false(0) */
+	    /* conditions(end) */
+    {
+	x = 1;
+    }
+    else if (b) /* conditions(0/4) true(0 1) false(0 1) */
+		/* conditions(end) */
+    {
+	x = 2;
+	if (c)
+	    x = 3;
+    }
+}
+
+/* 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(2/2) */
+	goto then;
+    else if (b)	/* conditions(2/2) */
+	goto then;
+    else if (c) /* conditions(1/2) true(0) */
+	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;
+}
+
+void
+noop () {}
+
+int
+mcdc007d (int a, int b, int c, int d, int e)
+{
+    noop ();
+    if (a)  /* conditions(1/2) true(0) */
+	    /* conditions(end) */
+    {
+	if (b || c) /* conditions(0/4) true(0 1) false(0 1) */
+		    /* conditions(end) */
+	    x = 2;
+	if (d)	/* conditions(0/2) true(0) false(0) */
+		/* conditions(end) */
+	    return 1;
+    }
+    if (e)  /* conditions(1/2) false(0) */
+	    /* conditions(end) */
+	return 0;
+
+    return 2;
+}
+
+/* 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--;
+}
+
+/* Multi-term loop condition with empty body, which can give neighborhoods of
+   size 1.  */
+void
+mcdc009b (int a, int b)
+{
+    while (a-- > 0 && b) {} /* conditions(2/4) true(0 1) */
+			    /* conditions(end) */
+}
+
+/* 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 (masking) 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
+       The implementation does not match the specification.  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;
+    }
+}
+
+/* Early returns, gotos can create candidate sets where the neighborhood
+   internally shares dominator nodes that are not the first-in-expression which
+   implies the neighborhood belongs to some other boolean expression.  When
+   this happens, the candidate set must be properly tidied up.  */
+void
+mcdc015d (int a, int b, int c)
+{
+    if (a) return;  /* conditions(1/2) false(0) */
+		    /* conditions(end) */
+    if (id (b)) return; /* conditions(0/2) true(0) false(0) */
+			/* conditions(end) */
+    if (id (c)) return; /* conditions(0/2) true(0) false(0) */
+			/* conditions(end) */
+}
+
+
+/* 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
+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) */
+	       /* conditions(end) */
+	{
+	    right = b > right ? a : right; /* conditions(2/2) */
+	}
+    } while (n-- > 0); /* conditions(2/2) */
+}
+
+/* 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) */
+	   /* conditions(end) */
+	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) */
+			  /* conditions(end) */
+}
+
+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;
+}
+
+/* Adapted from openssl-3.0.1/crypto/cmp/cmp_msg.c ossl_cmp_error_new ().
+   Without proper limiting of the initial candidate search this misidentified
+   { ...; if (fn ()) goto err; } if (c) goto err; as a 2-term expression.  */
+void
+mcdc022e (int a, int b, int c, int d)
+{
+    if (a || b) /* conditions(1/4) true(0) false(0 1) */
+		/* conditions(end) */
+    {
+	if (always (c)) /* conditions(1/2) false(0) */
+			/* conditions(end) */
+	    goto err;
+    }
+
+    if (d)  /* conditions(0/2) true(0) false(0) */
+	    /* conditions(end) */
+	goto err;
+    return;
+
+err:
+    noop ();
+}
+
+/* 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;
+}
+
+/* Gotos, return, labels can make odd graphs.  It is important that conditions
+   are assigned to the right expression, and that there are no miscounts.  In
+   these tests values may be re-used, as checking things like masking an
+   independence is done in other test cases and not so useful here.  */
+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
+mcdc024d (int a, int b, int c)
+{
+    /* Graphs can get complicated with the innermost returns and else-less if,
+       so we must make sure these conditions are counted correctly.  */
+    if (a)  /* conditions(1/2) true(0) */
+	    /* conditions(end) */
+    {
+	if (b)	/* conditions(0/2) true(0) false(0) */
+		/* conditions(end) */
+	{
+	    if (c)  /* conditions(0/2) true(0) false(0) */
+		    /* conditions(end) */
+		return 1;
+	    else
+		return 2;
+	}
+
+	if (a)	/* conditions(0/2) true(0) false(0) */
+		/* conditions(end) */
+	    return 3;
+    }
+
+    return 5;
+}
+
+/* Nested else-less ifs with inner returns needs to be counted right, which
+   puts some pressure on the expression isolation.  The fallthrough from inner
+   expressions into the next if cause the cfg to have edges from deeper in
+   subexpression to the next block sequence, which can confuse the expression
+   isolation. */
+int
+mcdc024e (int a, int b, int c)
+{
+    if (a)  /* conditions(1/2) true(0) */
+	    /* conditions(end) */
+    {
+	if (b)	/* conditions(0/2) true(0) false(0) */
+		/* conditions(end) */
+	{
+	    if (c)  /* conditions(0/2) true(0) false(0) */
+		    /* conditions(end) */
+	    {
+		if (a)	/* conditions(0/2) true(0) false(0) */
+			/* conditions(end) */
+		    return 1;
+		else
+		    return 2;
+	    }
+
+	    if (a)  /* conditions(0/2) true(0) false(0) */
+		    /* conditions(end) */
+		return 3;
+	}
+
+	if (b)	/* conditions(0/2) true(0) false(0) */
+		/* conditions(end) */
+	    return 4;
+    }
+    return 5;
+}
+
+int
+mcdc024f (int a, int b, int c)
+{
+    if (b)  /* conditions(1/2) true(0) */
+	    /* conditions(end) */
+	return 0;
+
+    if (a)  /* conditions(1/2) true(0) */
+	    /* conditions(end) */
+    {
+	if (b)	/* conditions(0/2) true(0) false(0) */
+		/* conditions(end) */
+	{
+	    b += 2;
+	    if (b & 0xFF)   /* conditions(0/2) true(0) false(0) */
+			    /* conditions(end) */
+		c++;
+
+	    return c;
+	}
+	c += 10;
+    }
+}
+
+
+int
+mcdc024g (int a, int b, int c)
+{
+    if (b)  /* conditions(1/2) true(0) */
+	    /* conditions(end) */
+	goto inner;
+
+    if (a)  /* conditions(1/2) true(0) */
+	    /* conditions(end) */
+	++a;
+
+
+    if (a)  /* conditions(1/2) true(0) */
+	    /* conditions(end) */
+    {
+	if (b)	/* conditions(0/2) true(0) false(0) */
+		/* conditions(end) */
+	{
+inner:
+	    b += 2;
+	    if (b & 0xFF)   /* conditions(0/2) true(0) false(0) */
+			    /* conditions(end) */
+		c++;
+
+	    return c;
+	}
+	c += 10;
+    }
+}
+
+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);
+
+    mcdc004f (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);
+
+    mcdc007d (0, 1, 0, 1, 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);
+
+    mcdc009b (0, 0);
+    mcdc009b (1, 0);
+
+    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);
+
+    mcdc015d (1, 0, 0);
+
+    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);
+    mcdc022e (0, 1, 1, 0);
+
+    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);
+    mcdc024d (0, 0, 0);
+    mcdc024e (0, 0, 0);
+    mcdc024f (0, 0, 0);
+    mcdc024g (0, 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/gcc.misc-tests/gcov-22.c b/gcc/testsuite/gcc.misc-tests/gcov-22.c
new file mode 100644
index 00000000000..3737235d40e
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-22.c
@@ -0,0 +1,71 @@
+/* { dg-options "-fprofile-conditions -ftest-coverage" } */
+/* { dg-do run { target native } } */
+
+#include <setjmp.h>
+jmp_buf buf;
+
+void noop() {}
+int identity(int x) { return x; }
+
+/* This function is a test to verify that the expression isolation does not
+   break on a CFG with the right set of complex edges.  The (_ && setjmp)
+   created complex edges after the function calls and a circular pair of
+   complex edges around the setjmp call.  This triggered a bug when the search
+   for right operands only would consider nodes dominated by the left-most
+   term, as this would only be the case if the complex edges were removed.
+
+   __builtin_setjmp did not trigger this, so we need setjmp from libc.  */
+void
+setjmp001 (int a, int b, int c)
+{
+    if (a)  /* conditions(1/2) true(0) */
+	    /* conditions(end) */
+	noop ();
+
+    if (b)  /* conditions(1/2) false(0) */
+	    /* conditions(end) */
+	noop ();
+
+    if (c && setjmp (buf))  /* conditions(1/4) true(0 1) false(1) */
+			    /* conditions(end) */
+	noop ();
+}
+
+/* Adapted from freetype-2.13.0 gxvalid/gxvmod.c classic_kern_validate */
+int
+setjmp002 (int a)
+{
+    int error = identity(a);
+
+    if (error)	/* conditions(1/2) true(0) */
+		/* conditions(end) */
+	goto Exit;
+
+   if (a+1) /* conditions(1/2) false(0) */
+	    /* conditions(end) */
+   {
+       noop ();
+       if (setjmp (buf))    /* conditions(1/2) true(0) */
+			    /* conditions(end) */
+	   noop ();
+
+	if (error)  /* conditions(1/2) true(0) */
+		    /* conditions(end) */
+	    noop ();
+   }
+
+   error--;
+
+Exit:
+   return error;
+}
+
+int
+main ()
+{
+    setjmp001 (0, 1, 0);
+    setjmp002 (0);
+}
+
+
+/* { dg-final { run-gcov conditions { --conditions gcov-22.c } } } */
diff --git a/gcc/testsuite/gcc.misc-tests/gcov-23.c b/gcc/testsuite/gcc.misc-tests/gcov-23.c
new file mode 100644
index 00000000000..8287a183889
--- /dev/null
+++ b/gcc/testsuite/gcc.misc-tests/gcov-23.c
@@ -0,0 +1,197 @@
+/* { dg-options "-fprofile-conditions -ftest-coverage -O2 -c" } */
+
+#include <setjmp.h>
+jmp_buf buf;
+
+int id (int);
+int idp (int *);
+int err;
+char c;
+
+/* This becomes problematic only under optimization for the case when the
+   compiler cannot inline the function but have to generate a call.  It is not
+   really interesting to run, only see if it builds.  Notably, both the
+   function calls and the return values are important to construct a
+   problematic graph.
+
+   This test is also a good example of where optimization makes condition
+   coverage unpredictable, but not unusable.  If this is built without
+   optimization the conditions work as you would expect from reading the
+   source.  */
+/* Adapted from cpio-2.14 gnu/utmens.c lutimens ().  */
+int
+mcdc001 (int *v)
+{
+    int adjusted;
+    int adjustment_needed = 0;
+
+    int *ts = v ? &adjusted : 0; /* conditions(0/4) true(0 1) false(0 1) */
+				 /* conditions(end) */
+    if (ts)
+	adjustment_needed = idp (ts);
+    if (adjustment_needed < 0)
+	return -1;
+
+    if (adjustment_needed)  /* conditions(0/2) true(0) false(0) */
+			    /* conditions(end) */
+    {
+	if (adjustment_needed != 3) /* conditions(0/2) true(0) false(0) */
+				    /* conditions(end) */
+	    return -1;
+	if (ts) /* conditions(0/2) true(0) false(0) */
+		/* conditions(end) */
+	    return 0;
+    }
+
+    if (adjustment_needed && idp (&adjusted)) /* conditions(0/2) true(0) false(0) */
+					      /* conditions(end) */
+	return -1;
+    if (adjusted)   /* conditions(0/2) true(0) false(0) */
+		    /* conditions(end) */
+	return idp (ts);
+
+    return -1;
+}
+
+/* This failed when the candidate set internal/contracted-past nodes were not
+   properly marked as reachable in the candidate reduction phase.  */
+/* Adapted from cpio-2.14 gnu/mktime.c mktime_internal ().  */
+int
+mcdc002 ()
+{
+    int a;
+    if (idp (&a)) /* conditions(0/2) true(0) false(0) */
+		  /* conditions(end) */
+    {
+	if (id (a)) /* conditions(0/2) true(0/2) true(0) false(0) */
+		    /* conditions(end) */
+	    goto exit;
+
+	if (err) /* conditions(0/2) true(0/2) true(0) false(0) */
+		 /* conditions(end) */
+	    return -1;
+    }
+
+exit:
+    return a;
+}
+
+/* Adapted from icu4c-73.1 common/ucase.cpp ucase_getCaseLocale ().  */
+int
+mcdc003 (const char *locale)
+{
+    /* extern, so its effect won't be optimized out.  */
+    c = *locale++;
+    if (c == 'z') /* conditions(0/2) true(0) false(0) */
+		  /* conditions(end) */
+    {
+	return 1;
+    }
+    else if (c >= 'a') /* conditions(0/2) true(0) false(0) */
+		      /* conditions(end) */
+    {
+	if (id (c)) /* conditions(0/2) true(0) false(0) */
+		    /* conditions(end) */
+	    c = *locale++;
+    }
+    else
+    {
+	if (c == 'T')
+	{
+	    if (id (c)) /* conditions(0/2) true(0) false(0) */
+			/* conditions(end) */
+		c = *locale++;
+	    if (id (c)) /* conditions(0/2) true(0) false(0) */
+			/* conditions(end) */
+		c = *locale++;
+	}
+	/* This may or may not become a jump table.  */
+	else if (c == 'L') /* conditions(suppress) */
+			   /* conditions(end) */
+	    c = *locale++;
+	else if (c == 'E') /* conditions(suppress) */
+			   /* conditions(end) */
+	    c = *locale++;
+	else if (c == 'N') /* conditions(suppress) */
+			   /* conditions(end) */
+	    c = *locale++;
+	else if (c == 'H') /* conditions(suppress) */
+			   /* conditions(end) */
+	{
+	    c = *locale++;
+	    if (id (c)) /* conditions(0/2) true(0) false(0) */
+			/* conditions(end) */
+		c = *locale++;
+	}
+    }
+
+    return 1;
+}
+
+/* The || will be changed to |, so it is impractical to predict the number of
+   conditions.  If the walk is not properly implemented this will not finish
+   compiling, so the actual coverage is not interesting.  */
+/* Adapted from icu4c-73.1 common/uresdata.cpp res_findResource ().  */
+int
+mcdc004 (int r, char* path, char* key)
+{
+    char *idcc (char *, char);
+    #define is_kind1(type) ((type) == 23 || (type) == 14 || (type == 115))
+    #define is_kind2(type) ((type) == 16 || (type) == 77 || (type == 118))
+    #define is_kind12(type) (is_kind1 ((type)) || is_kind2 ((type)))
+
+    char *nextSepP = path;
+    int t1 = r;
+    int type = id (t1);
+
+    if (!is_kind12 (type)) /* conditions(suppress) */
+			   /* conditions(end) */
+	return -1;
+
+    while (*path && t1 != -1 && is_kind12(type)) /* conditions(suppress) */
+						 /* conditions(end) */
+    {
+	nextSepP = idcc(path, '/');
+	if(nextSepP == path) /* conditions(0/2) true(0) false(0) */
+			     /* conditions(end) */
+	    return -1;
+
+	if (*nextSepP == 'a') /* conditions(0/2) true(0) false(0) */
+			      /* conditions(end) */
+	    *key = *path;
+	else if (*nextSepP == 'b')  /* conditions(0/2) true(0) false(0) */
+				    /* conditions(end) */
+	    *key = 0;
+	type = t1;
+    }
+
+    return t1;
+}
+
+/* Adapted from jxl 0.8.2 lib/extras/dec/apng.cc processing_start ().
+   This created a graph where depth-first traversal of the CFG would not
+   process nodes in the wrong order (the extra control inserted from setjmp
+   created a path of complexes from root to !b without going through !a).
+
+   This only happened under optimization.  */
+int
+mcdc005 (int a, int b)
+{
+    a = id (a);
+    b = id (b);
+
+    /* The a || b gets transformed to a | b, then fused with setjmp because
+       they both have the same return value.  */
+    if (a || b) /* conditions(0/4) true(0 1) false(0 1) */
+		/* conditions(end) */
+	return 1;
+    else
+	a += 1;
+
+    if (setjmp (buf))
+	return 1;
+
+    return a;
+}
+
+/* { dg-final { run-gcov conditions { --conditions gcov-23.c } } } */
diff --git a/gcc/testsuite/lib/gcov.exp b/gcc/testsuite/lib/gcov.exp
index e5e94fa5a76..7e04b371e74 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 da300d5f9e8..8bf280dc018 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,1121 @@ 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;
+    auto_sbitmap G4;
+
+    explicit conds_ctx (unsigned size) noexcept (true) : marks (size),
+    G1 (size), G2 (size), G3 (size), G4 (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 c hecks.  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.  If the optional bitset G is passed, the intermediate
+   "contracted-past" nodes will be recorded, which is only meaningful if the
+   non-source edge is returned.  */
+edge
+contract_edge (edge e, sbitmap G = nullptr)
+{
+    edge source = e;
+    while (true)
+    {
+	basic_block dest = e->dest;
+	if (e->flags & EDGE_DFS_BACK)
+	    return source;
+	if (dest->index == EXIT_BLOCK)
+	    return source;
+	if (!single (dest->preds))
+	    return source;
+	if (block_conditional_p (dest))
+	    return e;
+	/* This happens for switches, and must be checked after the is-conditional
+	   (which is also not single).  */
+	if (!single (dest->succs))
+	    return source;
+
+	if (G)
+	    bitmap_set_bit (G, dest->index);
+	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
+
+    When this split happens it flags the edge with EDGE_IGNORE.  */
+basic_block
+merge_split_outcome (basic_block b)
+{
+    if (!single (b->succs))
+	return b;
+    edge e = single_edge (b->succs);
+    if (e->flags & EDGE_IGNORE)
+	return e->dest;
+    return b;
+}
+
+
+/* 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);
+		}
+	    }
+	}
+    }
+}
+
+/* Check that all predecessors are conditional and belong to the current
+   expression.  This check is necessary in the presence of gotos, setjmp and
+   other complicated control flow that creates extra edges and creates odd
+   reachable paths from mid-expression terms and paths escaping nested
+   expressions.  If a node has an incoming non-complex edge (after contraction)
+   it can not be a part of a single, multi-term conditional expression.
+
+   If the expr[i] is set then nodes[i] is reachable from the leftmost operand
+   and b is a viable candidate.  Otherwise, this has to be an independent but
+   following expression.
+ */
+bool
+all_preds_conditional_p (basic_block b, const sbitmap expr)
+{
+    for (edge e : b->preds)
+    {
+	e = contract_edge_up (e);
+	if (!(e->flags & (EDGE_CONDITION | EDGE_COMPLEX)))
+	    return false;
+
+	if (!bitmap_bit_p (expr, e->src->index))
+	    return false;
+    }
+    return true;
+}
+
+/* Find the nodes reachable from p by following only (possibly contracted)
+   condition edges and ignoring 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 and forms the candidate set
+   for an expression.  All nodes in an expression should be connected only by
+   true|false edges, so a node with a non-conditional predecessor must be a
+   part of a different expression and in G', not G.
+
+   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.
+
+   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 (!block_conditional_p (dest))
+		continue;
+	    if (bitmap_bit_p (expr, dest->index))
+		continue;
+	    if (e->flags & EDGE_DFS_BACK)
+		continue;
+	    if (!all_preds_conditional_p (dest, expr))
+		continue;
+
+	    if (dest != e->dest)
+		contract_edge (e, expr);
+	    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))
+		continue;
+	    /* There was a contraction, so replay it but this time also record
+	       the intermediate nodes, so that the reachable set becomes
+	       complete.  This is necessary when reducing the candidate set.  */
+	    if (dest != e->dest)
+		contract_edge (e, G);
+	    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).
+
+   In complex graphs this may capture more than the expression proper.  In that
+   case, perform the algorithm again but on the neighborhood N(B) rather than
+   N(G).  Unless there is complex control flow with deep early returns, gotos
+   and else-less ifs N(B) will be the two outcome nodes, otherwise search again
+   after replacing nodes with their common dominator.  */
+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;
+    sbitmap prev = ctx.G3;
+    bitmap_clear (expr);
+    bitmap_clear (reachable);
+    bitmap_clear (prev);
+
+    vec<basic_block>& G = ctx.B1;
+    vec<basic_block>& NG = ctx.B2;
+    G.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;
+    }
+
+    while (true)
+    {
+	NG.truncate (0);
+	neighborhood (G, reachable, NG);
+	gcc_assert (!NG.is_empty ());
+
+	bitmap_clear (expr);
+	for (basic_block b : NG)
+	    bitmap_set_bit (expr, b->index);
+
+	if (bitmap_count_bits (expr) == 2)
+	    break;
+
+	/* This can happen for loops with no body.  */
+	if (bitmap_count_bits (expr) == 1 && bb_loop_header_p (p))
+	    break;
+
+	/* If the neighborhood does not change between iterations (a fixed
+	   point) we cannot understand the graph properly, and this would loop
+	   infinitely.  If this should happen, we should bail out and give up
+	   instrumentation for the function altogether.  It is possible no such
+	   CFGs exist, so for now this is an assert.  */
+	gcc_assert (!bitmap_equal_p (prev, expr));
+	gcc_assert (bitmap_count_bits (expr) > 2);
+	bitmap_copy (prev, expr);
+
+	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 (unsigned i = 0; i != G.length (); i++)
+	    if (!bitmap_bit_p (expr, G[i]->index))
+		G.unordered_remove (i--);
+
+	bitmap_clear (reachable);
+	for (basic_block b : G)
+	    bitmap_set_bit (reachable, b->index);
+
+	/* Contracted-past nodes in the subgraph must be re-marked, otherwise
+	   the next neighborhood may be computed wrong.  */
+	for (basic_block b : G)
+	    for (edge e : b->succs)
+		if (bitmap_bit_p (reachable, contract_edge (e)->dest->index))
+		    contract_edge (e, reachable);
+    }
+
+    out.safe_splice (G);
+    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);
+    dfs.sort (cmp_index_map, &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 +1875,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 +1920,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)
@@ -920,7 +2037,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 5d6e17d1483..eed3556373b 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.30.2


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

end of thread, other threads:[~2023-10-23  8:10 UTC | newest]

Thread overview: 35+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-10-04 12:38 [PATCH v5] Add condition coverage profiling Jørgen Kvalsvik
2023-10-04 12:39 ` [PATCH 01/22] " Jørgen Kvalsvik
2023-10-05 12:59   ` Jan Hubicka
2023-10-05 13:39     ` Jørgen Kvalsvik
2023-10-05 14:17       ` Jørgen Kvalsvik
2023-10-05 14:39         ` Jan Hubicka
2023-10-21 14:30       ` Jørgen Kvalsvik
2023-10-23  8:10         ` Richard Biener
2023-10-05 15:18     ` Jørgen Kvalsvik
2023-10-06  9:49     ` Richard Biener
2023-10-04 12:39 ` [PATCH 02/22] Add "Condition coverage profiling" term to --help Jørgen Kvalsvik
2023-10-04 12:39 ` [PATCH 03/22] Mention relevant flags in condition coverage docs Jørgen Kvalsvik
2023-10-04 12:39 ` [PATCH 04/22] Describe, remove ATTRIBUTE_UNUSED from tag_conditions Jørgen Kvalsvik
2023-10-04 12:39 ` [PATCH 05/22] Describe condition_info Jørgen Kvalsvik
2023-10-04 12:39 ` [PATCH 06/22] Use popcount_hwi rather than builtin Jørgen Kvalsvik
2023-10-05 13:01   ` Jan Hubicka
2023-10-04 12:39 ` [PATCH 07/22] Describe add_condition_counts Jørgen Kvalsvik
2023-10-04 12:39 ` [PATCH 08/22] Describe output_conditions Jørgen Kvalsvik
2023-10-04 12:39 ` [PATCH 09/22] Find reachable conditions unbounded by dominators Jørgen Kvalsvik
2023-10-04 12:39 ` [PATCH 10/22] Prune search for boolean expr on goto, return Jørgen Kvalsvik
2023-10-04 12:39 ` [PATCH 11/22] Add test case showing cross-decision fusing Jørgen Kvalsvik
2023-10-04 12:39 ` [PATCH 12/22] Do two-phase filtering in expr isolation Jørgen Kvalsvik
2023-10-04 12:39 ` [PATCH 13/22] Handle split-outcome with intrusive flag Jørgen Kvalsvik
2023-10-04 12:39 ` [PATCH 14/22] Unify expression candidate set refinement logic Jørgen Kvalsvik
2023-10-04 12:39 ` [PATCH 15/22] Fix candidate, neighborhood set reduction phase Jørgen Kvalsvik
2023-10-04 12:39 ` [PATCH 16/22] Rename pathological -> setjmp Jørgen Kvalsvik
2023-10-04 12:39 ` [PATCH 17/22] Mark contracted-past nodes in reachable Jørgen Kvalsvik
2023-10-04 12:39 ` [PATCH 18/22] Don't contract into random edge in multi-succ node Jørgen Kvalsvik
2023-10-04 15:29   ` Jørgen Kvalsvik
2023-10-04 12:39 ` [PATCH 19/22] Beautify assert Jørgen Kvalsvik
2023-10-04 12:39 ` [PATCH 20/22] Don't try to reduce NG from dominators Jørgen Kvalsvik
2023-10-04 12:39 ` [PATCH 21/22] Walk the cfg in topological order, not depth-first Jørgen Kvalsvik
2023-10-04 15:24   ` Jørgen Kvalsvik
2023-10-04 12:39 ` [PATCH 22/22] Return value on separate line Jørgen Kvalsvik
2023-10-05 14:10 [PATCH v5] Add condition coverage profiling 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).