Record basic blocks that make up a conditional expression with -fcondition-coverage and report when using the gcov -w/--verbose flag. This makes the report more accurate when basic blocks are included as there may be blocks in-between the actual Boolean expressions, e.g. when there a term is the result of a function call. This helps understanding the report as gcc uses the CFG, and not source code, to figure out MC/DC, which is somewhat lost in gcov. While it does not make a tremendous difference for the gcov report directly, it opens up for more analysis and clearer reporting.
This change includes deleting the GCOV_TAG_COND_* macros as the .gcno records are now dynamic in length. Here is an example with, comparing two programs: int main() { int a = 1; int b = 0; if (a && b) printf ("Success!\n"); else printf ("Failure!\n"); } int f(int); int g(int); int main() { int a = 1; int b = 0; if (f (a) && g (b)) printf ("Success!\n"); else printf ("Failure!\n"); } And the corresponding reports: $ gcov -tagw p1 p2 1: 3:int main() { 1: 4: int a = 1; 1: 5: int b = 0; -: 6: 1: 7: if (a && b) 1: 7-block 2 (BB 2) condition outcomes covered 1/4 BBs 2 3 condition 0 not covered (true false) condition 1 not covered (true) 1: 7-block 3 (BB 3) #####: 8: printf ("Success!\n"); %%%%%: 8-block 4 (BB 4) -: 9: else 1: 10: printf ("Failure!\n"); 1: 10-block 5 (BB 5) -: 11:} #####: 6:int main() { #####: 7: int a = 1; #####: 8: int b = 0; -: 9: #####: 10: if (f (a) && g (b)) %%%%%: 10-block 2 (BB 2) condition outcomes covered 0/4 BBs 3 5 condition 0 not covered (true false) condition 1 not covered (true false) %%%%%: 10-block 4 (BB 4) #####: 11: printf ("Success!\n"); %%%%%: 11-block 6 (BB 6) -: 12: else #####: 13: printf ("Failure!\n"); %%%%%: 13-block 7 (BB 7) -: 14:} gcc/ChangeLog: * doc/gcov.texi: Add example. * gcov-dump.cc (tag_conditions): Print basic blocks, not length. * gcov-io.h (GCOV_TAG_CONDS_LENGTH): Delete. (GCOV_TAG_CONDS_NUM): Likewise. * gcov.cc (output_intermediate_json_line): Output basic blocks. (read_graph_file): Read basic blocks. (output_conditions): Output basic blocks. * profile.cc (branch_prob): Write basic blocks for conditions. --- gcc/doc/gcov.texi | 32 ++++++++++++++++++++++++++++++++ gcc/gcov-dump.cc | 12 +++++++----- gcc/gcov-io.h | 2 -- gcc/gcov.cc | 20 +++++++++++++++++++- gcc/profile.cc | 11 +++++++++-- 5 files changed, 67 insertions(+), 10 deletions(-) diff --git a/gcc/doc/gcov.texi b/gcc/doc/gcov.texi index dda279fbff3..268e9e553f3 100644 --- a/gcc/doc/gcov.texi +++ b/gcc/doc/gcov.texi @@ -423,6 +423,7 @@ Each @var{condition} has the following form: "covered": 2, "not_covered_false": [], "not_covered_true": [0, 1], + "basic_blocks": [2, 3] @} @end smallexample @@ -989,6 +990,37 @@ condition 1 not covered (true) -: 12:@} @end smallexample +With @option{-w}, each condition will also print the basic blocks that +make up the decision. + +@smallexample +$ gcov -t -m -g -a -w tmp + -: 0:Source:tmp.c + -: 0:Graph:tmp.gcno + -: 0:Data:tmp.gcda + -: 0:Runs:1 + -: 1:#include <stdio.h> + -: 2: + 1: 3:int main() + -: 4:@{ + 1: 5: int a = 1; + 1: 6: int b = 0; + -: 7: + 1: 7: if (a && b) + 1: 7-block 2 (BB 2) +condition outcomes covered 1/4 +BBs 2 3 +condition 0 not covered (true false) +condition 1 not covered (true) + 1: 7-block 3 (BB 3) + #####: 8: printf ("Success!\n"); + %%%%%: 8-block 4 (BB 4) + -: 9: else + 1: 10: printf ("Failure!\n"); + 1: 10-block 5 (BB 5) + -: 12:@} +@end smallexample + The execution counts are cumulative. If the example program were executed again without removing the @file{.gcda} file, the count for the number of times each line in the source was executed would be added to diff --git a/gcc/gcov-dump.cc b/gcc/gcov-dump.cc index cc7f8a9ebfb..642e58c22bf 100644 --- a/gcc/gcov-dump.cc +++ b/gcc/gcov-dump.cc @@ -396,23 +396,25 @@ tag_arcs (const char *filename ATTRIBUTE_UNUSED, /* Print number of conditions (not outcomes, i.e. if (x && y) is 2, not 4). */ static void -tag_conditions (const char *filename, unsigned /* tag */, int length, +tag_conditions (const char *filename, unsigned /* tag */, int /* length */, unsigned depth) { - unsigned n_conditions = GCOV_TAG_CONDS_NUM (length); + unsigned n_conditions = gcov_read_unsigned (); printf (" %u conditions", n_conditions); if (flag_dump_contents) { for (unsigned ix = 0; ix != n_conditions; ix++) { - const unsigned blockno = gcov_read_unsigned (); + /* Skip the anchor -- it will be included with the blocks */ + (void)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); + printf (VALUE_PADDING_PREFIX "blocks:"); + for (unsigned i = 0; i != nterms; ++i) + printf (" %u", gcov_read_unsigned ()); } } } diff --git a/gcc/gcov-io.h b/gcc/gcov-io.h index 9ad6ad98282..8e1a3c8c903 100644 --- a/gcc/gcov-io.h +++ b/gcc/gcov-io.h @@ -262,8 +262,6 @@ typedef uint64_t gcov_type_unsigned; #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 3e6f2e4212a..ef5d828d1a0 100644 --- a/gcc/gcov.cc +++ b/gcc/gcov.cc @@ -159,6 +159,9 @@ public: /* Number of terms in the expression; if (x) -> 1, if (x && y) -> 2 etc. */ unsigned n_terms; + + /* The block IDs that make up this expression. */ + vector<unsigned> bb_ids; }; condition_info::condition_info (): truev (0), falsev (0), n_terms (0) @@ -1313,6 +1316,11 @@ output_intermediate_json_line (json::array *object, } } conditions->append (cond); + + json::array *bbs = new json::array (); + cond->set ("basic_blocks", bbs); + for (unsigned bb_id : info.bb_ids) + bbs->append (new json::integer_number (bb_id)); } } @@ -2174,7 +2182,7 @@ read_graph_file (void) } else if (fn && tag == GCOV_TAG_CONDS) { - unsigned num_dests = GCOV_TAG_CONDS_NUM (length); + unsigned num_dests = gcov_read_unsigned (); if (!fn->conditions.empty ()) fnotice (stderr, "%s:already seen conditions for '%s'\n", @@ -2191,6 +2199,8 @@ read_graph_file (void) condition_info *info = &fn->blocks[idx].conditions; info->n_terms = gcov_read_unsigned (); + for (unsigned k = 0; k != info->n_terms; ++k) + info->bb_ids.push_back (gcov_read_unsigned()); fn->conditions[i] = info; } } @@ -3163,6 +3173,14 @@ output_conditions (FILE *gcov_file, const block_info *binfo) const int got = info.popcount (); fnotice (gcov_file, "condition outcomes covered %d/%d\n", got, expected); + if (flag_verbose) + { + fnotice (gcov_file, "BBs"); + for (unsigned bb_id : info.bb_ids) + fnotice (gcov_file, " %u", bb_id); + fnotice (gcov_file, "\n"); + } + if (expected == got) return; diff --git a/gcc/profile.cc b/gcc/profile.cc index acc0ae0cc20..76235dd2bee 100644 --- a/gcc/profile.cc +++ b/gcc/profile.cc @@ -1559,8 +1559,10 @@ branch_prob (bool thunk) { gcov_position_t offset {}; if (output_to_file) + { offset = gcov_write_tag (GCOV_TAG_CONDS); - + gcov_write_unsigned (nconds); + } for (size_t i = 0; i != nconds; ++i) { array_slice<basic_block> expr = cov_blocks (cov, i); @@ -1569,12 +1571,17 @@ branch_prob (bool thunk) gcc_assert (expr.is_valid ()); gcc_assert (masks.is_valid ()); gcc_assert (maps.is_valid ()); - size_t terms = instrument_decisions (expr, i, maps, masks); if (output_to_file) { gcov_write_unsigned (expr.front ()->index); gcov_write_unsigned (terms); + /* Write the basic block ids of this condition, in order so + that the nth item is the nth term of the conditional + expression. */ + for (basic_block b : expr) + if (bitmap_bit_p (maps[0], b->index)) + gcov_write_unsigned (b->index); } } if (output_to_file) -- 2.39.5