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