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