public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
From: "Jørgen Kvalsvik" <jorgen.kvalsvik@woven-planet.global>
To: gcc-patches@gcc.gnu.org
Subject: [PATCH] Add condition coverage profiling
Date: Mon, 21 Mar 2022 12:55:46 +0100	[thread overview]
Message-ID: <b53507bf-1c1f-d946-00a5-10143fa647b4@woven-planet.global> (raw)

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

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

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

From the SQLite webpage:

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

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

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

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

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

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

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

gcov --conditions:

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

gcov --conditions --json-format:

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

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

gcov --conditions

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

They are also reported by the branch coverage.

gcov -bc

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

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

and.c:

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

ifs.c:

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

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

and.c.gcov:

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

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

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

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

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

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

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

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

--

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

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

gcc/ChangeLog:

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

gcc/testsuite/ChangeLog:

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

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

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

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

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

From the SQLite webpage:

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

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

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

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

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

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

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

gcov --conditions:

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

gcov --conditions --json-format:

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

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

gcov --conditions

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

They are also reported by the branch coverage.

gcov -bc

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

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

and.c:

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

ifs.c:

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

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

and.c.gcov:

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

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

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

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

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

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

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

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

gcc/ChangeLog:

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

gcc/testsuite/ChangeLog:

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

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


             reply	other threads:[~2022-03-21 11:55 UTC|newest]

Thread overview: 34+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-03-21 11:55 Jørgen Kvalsvik [this message]
2022-03-24 16:08 ` Martin Liška
2022-03-25 19:44   ` Jørgen Kvalsvik
2022-03-28 13:39     ` Martin Liška
2022-03-28 13:52     ` Jørgen Kvalsvik
2022-03-28 14:40       ` Jørgen Kvalsvik
2022-04-07 12:04         ` Martin Liška
2022-04-19 14:22           ` Jørgen Kvalsvik
2022-04-07 16:53         ` Sebastian Huber
2022-04-08  7:28           ` Jørgen Kvalsvik
2022-04-08  7:33             ` Jørgen Kvalsvik
2022-04-08  8:50               ` Sebastian Huber
2022-04-04  8:14 ` Sebastian Huber
2022-04-05  7:04   ` Sebastian Huber
2022-04-05 20:07   ` Jørgen Kvalsvik
2022-04-06  7:35     ` Sebastian Huber
2022-04-17 11:27       ` Jørgen Kvalsvik
2022-04-22  5:37         ` Sebastian Huber
2022-04-22 10:13           ` Jørgen Kvalsvik
2022-07-08 13:45 ` Sebastian Huber
2022-07-11  7:26   ` Jørgen Kvalsvik
2022-07-11 10:02 Jørgen Kvalsvik
2022-07-12 14:05 ` Sebastian Huber
2022-07-13  2:04   ` Jørgen Kvalsvik
2022-07-15 11:39 Jørgen Kvalsvik
2022-07-15 11:47 ` Jørgen Kvalsvik
2022-07-15 13:31   ` Sebastian Huber
2022-07-15 13:47     ` Jørgen Kvalsvik
2022-08-02  7:58       ` Jørgen Kvalsvik
2022-08-04  7:43         ` Sebastian Huber
2022-08-04  9:13           ` Jørgen Kvalsvik
2022-10-12 10:16 Jørgen Kvalsvik
2022-10-18  0:17 ` Hans-Peter Nilsson
2022-10-18 10:13   ` Jørgen Kvalsvik

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=b53507bf-1c1f-d946-00a5-10143fa647b4@woven-planet.global \
    --to=jorgen.kvalsvik@woven-planet.global \
    --cc=gcc-patches@gcc.gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).