From: "Martin Liška" <mliska@suse.cz>
To: "Jørgen Kvalsvik" <jorgen.kvalsvik@woven-planet.global>,
gcc-patches@gcc.gnu.org
Subject: Re: [PATCH] Add condition coverage profiling
Date: Thu, 24 Mar 2022 17:08:42 +0100 [thread overview]
Message-ID: <fbe1575d-e3be-6c02-332b-5ecc9410467d@suse.cz> (raw)
In-Reply-To: <b53507bf-1c1f-d946-00a5-10143fa647b4@woven-planet.global>
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
next prev parent reply other threads:[~2022-03-24 16:08 UTC|newest]
Thread overview: 34+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-03-21 11:55 Jørgen Kvalsvik
2022-03-24 16:08 ` Martin Liška [this message]
2022-03-25 19:44 ` Jørgen Kvalsvik
2022-03-28 13:39 ` Martin Liška
2022-03-28 13:52 ` Jørgen Kvalsvik
2022-03-28 14:40 ` Jørgen Kvalsvik
2022-04-07 12:04 ` Martin Liška
2022-04-19 14:22 ` Jørgen Kvalsvik
2022-04-07 16:53 ` Sebastian Huber
2022-04-08 7:28 ` Jørgen Kvalsvik
2022-04-08 7:33 ` Jørgen Kvalsvik
2022-04-08 8:50 ` Sebastian Huber
2022-04-04 8:14 ` Sebastian Huber
2022-04-05 7:04 ` Sebastian Huber
2022-04-05 20:07 ` Jørgen Kvalsvik
2022-04-06 7:35 ` Sebastian Huber
2022-04-17 11:27 ` Jørgen Kvalsvik
2022-04-22 5:37 ` Sebastian Huber
2022-04-22 10:13 ` Jørgen Kvalsvik
2022-07-08 13:45 ` Sebastian Huber
2022-07-11 7:26 ` Jørgen Kvalsvik
2022-07-11 10:02 Jørgen Kvalsvik
2022-07-12 14:05 ` Sebastian Huber
2022-07-13 2:04 ` Jørgen Kvalsvik
2022-07-15 11:39 Jørgen Kvalsvik
2022-07-15 11:47 ` Jørgen Kvalsvik
2022-07-15 13:31 ` Sebastian Huber
2022-07-15 13:47 ` Jørgen Kvalsvik
2022-08-02 7:58 ` Jørgen Kvalsvik
2022-08-04 7:43 ` Sebastian Huber
2022-08-04 9:13 ` Jørgen Kvalsvik
2022-10-12 10:16 Jørgen Kvalsvik
2022-10-18 0:17 ` Hans-Peter Nilsson
2022-10-18 10:13 ` Jørgen Kvalsvik
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=fbe1575d-e3be-6c02-332b-5ecc9410467d@suse.cz \
--to=mliska@suse.cz \
--cc=gcc-patches@gcc.gnu.org \
--cc=jorgen.kvalsvik@woven-planet.global \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).